diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example
Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag
index 26e47b8..98e06c6 100644
--- a/packages/filters/filter-fxaa/src/fxaa.frag
+++ b/packages/filters/filter-fxaa/src/fxaa.frag
@@ -6,38 +6,39 @@
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+
/**
Basic FXAA implementation based on the code on geeks3d.com with the
modification that the texture2DLod stuff was removed since it's
unsupported by WebGL.
-
+
--
-
+
From:
https://github.com/mitsuhiko/webgl-meincraft
-
+
Copyright (c) 2011 by Armin Ronacher.
-
+
Some rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
-
+
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
-
+
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
-
+
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -83,26 +84,26 @@
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
+
mediump vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
+
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
-
+
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
-
+
vec3 rgbA = 0.5 * (
texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz +
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);
-
+
float lumaB = dot(rgbB, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
color = vec4(rgbA, texColor.a);
@@ -113,11 +114,11 @@
void main() {
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
vec4 color;
- color = fxaa(uSampler, fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
gl_FragColor = color;
}
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag
index 26e47b8..98e06c6 100644
--- a/packages/filters/filter-fxaa/src/fxaa.frag
+++ b/packages/filters/filter-fxaa/src/fxaa.frag
@@ -6,38 +6,39 @@
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+
/**
Basic FXAA implementation based on the code on geeks3d.com with the
modification that the texture2DLod stuff was removed since it's
unsupported by WebGL.
-
+
--
-
+
From:
https://github.com/mitsuhiko/webgl-meincraft
-
+
Copyright (c) 2011 by Armin Ronacher.
-
+
Some rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
-
+
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
-
+
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
-
+
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -83,26 +84,26 @@
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
+
mediump vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
+
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
-
+
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
-
+
vec3 rgbA = 0.5 * (
texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz +
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);
-
+
float lumaB = dot(rgbB, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
color = vec4(rgbA, texColor.a);
@@ -113,11 +114,11 @@
void main() {
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
vec4 color;
- color = fxaa(uSampler, fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
gl_FragColor = color;
}
diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert
index 98ef9ac..3ef457c 100644
--- a/packages/filters/filter-fxaa/src/fxaa.vert
+++ b/packages/filters/filter-fxaa/src/fxaa.vert
@@ -1,6 +1,5 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
@@ -10,24 +9,21 @@
varying vec2 v_rgbSE;
varying vec2 v_rgbM;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
varying vec2 vTextureCoord;
-vec2 mapCoord( vec2 coord )
+vec4 filterVertexPosition( void )
{
- coord *= filterArea.xy;
- coord += filterArea.zw;
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
- return coord;
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
-vec2 unmapCoord( vec2 coord )
+vec2 filterTextureCoord( void )
{
- coord -= filterArea.zw;
- coord /= filterArea.xy;
-
- return coord;
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
}
void texcoords(vec2 fragCoord, vec2 resolution,
@@ -44,11 +40,11 @@
void main(void) {
- gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
+ gl_Position = filterVertexPosition();
- vTextureCoord = aTextureCoord;
+ vTextureCoord = filterTextureCoord();
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
- texcoords(fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag
index 26e47b8..98e06c6 100644
--- a/packages/filters/filter-fxaa/src/fxaa.frag
+++ b/packages/filters/filter-fxaa/src/fxaa.frag
@@ -6,38 +6,39 @@
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+
/**
Basic FXAA implementation based on the code on geeks3d.com with the
modification that the texture2DLod stuff was removed since it's
unsupported by WebGL.
-
+
--
-
+
From:
https://github.com/mitsuhiko/webgl-meincraft
-
+
Copyright (c) 2011 by Armin Ronacher.
-
+
Some rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
-
+
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
-
+
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
-
+
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -83,26 +84,26 @@
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
+
mediump vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
+
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
-
+
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
-
+
vec3 rgbA = 0.5 * (
texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz +
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);
-
+
float lumaB = dot(rgbB, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
color = vec4(rgbA, texColor.a);
@@ -113,11 +114,11 @@
void main() {
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
vec4 color;
- color = fxaa(uSampler, fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
gl_FragColor = color;
}
diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert
index 98ef9ac..3ef457c 100644
--- a/packages/filters/filter-fxaa/src/fxaa.vert
+++ b/packages/filters/filter-fxaa/src/fxaa.vert
@@ -1,6 +1,5 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
@@ -10,24 +9,21 @@
varying vec2 v_rgbSE;
varying vec2 v_rgbM;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
varying vec2 vTextureCoord;
-vec2 mapCoord( vec2 coord )
+vec4 filterVertexPosition( void )
{
- coord *= filterArea.xy;
- coord += filterArea.zw;
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
- return coord;
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
-vec2 unmapCoord( vec2 coord )
+vec2 filterTextureCoord( void )
{
- coord -= filterArea.zw;
- coord /= filterArea.xy;
-
- return coord;
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
}
void texcoords(vec2 fragCoord, vec2 resolution,
@@ -44,11 +40,11 @@
void main(void) {
- gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
+ gl_Position = filterVertexPosition();
- vTextureCoord = aTextureCoord;
+ vTextureCoord = filterTextureCoord();
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
- texcoords(fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
}
\ No newline at end of file
diff --git a/packages/filters/filter-noise/src/NoiseFilter.js b/packages/filters/filter-noise/src/NoiseFilter.js
index 45d6650..45f6f98 100644
--- a/packages/filters/filter-noise/src/NoiseFilter.js
+++ b/packages/filters/filter-noise/src/NoiseFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './noise.frag';
/**
@@ -22,7 +22,10 @@
*/
constructor(noise = 0.5, seed = Math.random())
{
- super(defaultVertex, fragment);
+ super(defaultFilterVertex, fragment, {
+ uNoise: 0,
+ uSeed: 0,
+ });
this.noise = noise;
this.seed = seed;
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag
index 26e47b8..98e06c6 100644
--- a/packages/filters/filter-fxaa/src/fxaa.frag
+++ b/packages/filters/filter-fxaa/src/fxaa.frag
@@ -6,38 +6,39 @@
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+
/**
Basic FXAA implementation based on the code on geeks3d.com with the
modification that the texture2DLod stuff was removed since it's
unsupported by WebGL.
-
+
--
-
+
From:
https://github.com/mitsuhiko/webgl-meincraft
-
+
Copyright (c) 2011 by Armin Ronacher.
-
+
Some rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
-
+
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
-
+
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
-
+
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -83,26 +84,26 @@
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
+
mediump vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
+
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
-
+
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
-
+
vec3 rgbA = 0.5 * (
texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz +
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);
-
+
float lumaB = dot(rgbB, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
color = vec4(rgbA, texColor.a);
@@ -113,11 +114,11 @@
void main() {
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
vec4 color;
- color = fxaa(uSampler, fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
gl_FragColor = color;
}
diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert
index 98ef9ac..3ef457c 100644
--- a/packages/filters/filter-fxaa/src/fxaa.vert
+++ b/packages/filters/filter-fxaa/src/fxaa.vert
@@ -1,6 +1,5 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
@@ -10,24 +9,21 @@
varying vec2 v_rgbSE;
varying vec2 v_rgbM;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
varying vec2 vTextureCoord;
-vec2 mapCoord( vec2 coord )
+vec4 filterVertexPosition( void )
{
- coord *= filterArea.xy;
- coord += filterArea.zw;
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
- return coord;
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
-vec2 unmapCoord( vec2 coord )
+vec2 filterTextureCoord( void )
{
- coord -= filterArea.zw;
- coord /= filterArea.xy;
-
- return coord;
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
}
void texcoords(vec2 fragCoord, vec2 resolution,
@@ -44,11 +40,11 @@
void main(void) {
- gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
+ gl_Position = filterVertexPosition();
- vTextureCoord = aTextureCoord;
+ vTextureCoord = filterTextureCoord();
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
- texcoords(fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
}
\ No newline at end of file
diff --git a/packages/filters/filter-noise/src/NoiseFilter.js b/packages/filters/filter-noise/src/NoiseFilter.js
index 45d6650..45f6f98 100644
--- a/packages/filters/filter-noise/src/NoiseFilter.js
+++ b/packages/filters/filter-noise/src/NoiseFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './noise.frag';
/**
@@ -22,7 +22,10 @@
*/
constructor(noise = 0.5, seed = Math.random())
{
- super(defaultVertex, fragment);
+ super(defaultFilterVertex, fragment, {
+ uNoise: 0,
+ uSeed: 0,
+ });
this.noise = noise;
this.seed = seed;
diff --git a/packages/fragments/src/defaultFilter.vert b/packages/fragments/src/defaultFilter.vert
new file mode 100644
index 0000000..2e63617
--- /dev/null
+++ b/packages/fragments/src/defaultFilter.vert
@@ -0,0 +1,26 @@
+attribute vec2 aVertexPosition;
+
+uniform mat3 projectionMatrix;
+uniform vec4 sourceFrame;
+uniform vec4 destinationFrame;
+
+varying vec2 vTextureCoord;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
+
+
+void main(void)
+{
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag
index 26e47b8..98e06c6 100644
--- a/packages/filters/filter-fxaa/src/fxaa.frag
+++ b/packages/filters/filter-fxaa/src/fxaa.frag
@@ -6,38 +6,39 @@
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+
/**
Basic FXAA implementation based on the code on geeks3d.com with the
modification that the texture2DLod stuff was removed since it's
unsupported by WebGL.
-
+
--
-
+
From:
https://github.com/mitsuhiko/webgl-meincraft
-
+
Copyright (c) 2011 by Armin Ronacher.
-
+
Some rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
-
+
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
-
+
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
-
+
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -83,26 +84,26 @@
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
+
mediump vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
+
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
-
+
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
-
+
vec3 rgbA = 0.5 * (
texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz +
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);
-
+
float lumaB = dot(rgbB, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
color = vec4(rgbA, texColor.a);
@@ -113,11 +114,11 @@
void main() {
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
vec4 color;
- color = fxaa(uSampler, fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
gl_FragColor = color;
}
diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert
index 98ef9ac..3ef457c 100644
--- a/packages/filters/filter-fxaa/src/fxaa.vert
+++ b/packages/filters/filter-fxaa/src/fxaa.vert
@@ -1,6 +1,5 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
@@ -10,24 +9,21 @@
varying vec2 v_rgbSE;
varying vec2 v_rgbM;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
varying vec2 vTextureCoord;
-vec2 mapCoord( vec2 coord )
+vec4 filterVertexPosition( void )
{
- coord *= filterArea.xy;
- coord += filterArea.zw;
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
- return coord;
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
-vec2 unmapCoord( vec2 coord )
+vec2 filterTextureCoord( void )
{
- coord -= filterArea.zw;
- coord /= filterArea.xy;
-
- return coord;
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
}
void texcoords(vec2 fragCoord, vec2 resolution,
@@ -44,11 +40,11 @@
void main(void) {
- gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
+ gl_Position = filterVertexPosition();
- vTextureCoord = aTextureCoord;
+ vTextureCoord = filterTextureCoord();
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
- texcoords(fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
}
\ No newline at end of file
diff --git a/packages/filters/filter-noise/src/NoiseFilter.js b/packages/filters/filter-noise/src/NoiseFilter.js
index 45d6650..45f6f98 100644
--- a/packages/filters/filter-noise/src/NoiseFilter.js
+++ b/packages/filters/filter-noise/src/NoiseFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './noise.frag';
/**
@@ -22,7 +22,10 @@
*/
constructor(noise = 0.5, seed = Math.random())
{
- super(defaultVertex, fragment);
+ super(defaultFilterVertex, fragment, {
+ uNoise: 0,
+ uSeed: 0,
+ });
this.noise = noise;
this.seed = seed;
diff --git a/packages/fragments/src/defaultFilter.vert b/packages/fragments/src/defaultFilter.vert
new file mode 100644
index 0000000..2e63617
--- /dev/null
+++ b/packages/fragments/src/defaultFilter.vert
@@ -0,0 +1,26 @@
+attribute vec2 aVertexPosition;
+
+uniform mat3 projectionMatrix;
+uniform vec4 sourceFrame;
+uniform vec4 destinationFrame;
+
+varying vec2 vTextureCoord;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
+
+
+void main(void)
+{
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+}
\ No newline at end of file
diff --git a/packages/fragments/src/index.js b/packages/fragments/src/index.js
index ff3b0d7..0daa463 100644
--- a/packages/fragments/src/index.js
+++ b/packages/fragments/src/index.js
@@ -1,3 +1,4 @@
import defaultVertex from './default.vert';
+import defaultFilterVertex from './defaultFilter.vert';
-export { defaultVertex };
+export { defaultVertex, defaultFilterVertex };
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag
index 26e47b8..98e06c6 100644
--- a/packages/filters/filter-fxaa/src/fxaa.frag
+++ b/packages/filters/filter-fxaa/src/fxaa.frag
@@ -6,38 +6,39 @@
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+
/**
Basic FXAA implementation based on the code on geeks3d.com with the
modification that the texture2DLod stuff was removed since it's
unsupported by WebGL.
-
+
--
-
+
From:
https://github.com/mitsuhiko/webgl-meincraft
-
+
Copyright (c) 2011 by Armin Ronacher.
-
+
Some rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
-
+
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
-
+
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
-
+
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -83,26 +84,26 @@
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
+
mediump vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
+
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
-
+
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
-
+
vec3 rgbA = 0.5 * (
texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz +
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);
-
+
float lumaB = dot(rgbB, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
color = vec4(rgbA, texColor.a);
@@ -113,11 +114,11 @@
void main() {
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
vec4 color;
- color = fxaa(uSampler, fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
gl_FragColor = color;
}
diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert
index 98ef9ac..3ef457c 100644
--- a/packages/filters/filter-fxaa/src/fxaa.vert
+++ b/packages/filters/filter-fxaa/src/fxaa.vert
@@ -1,6 +1,5 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
@@ -10,24 +9,21 @@
varying vec2 v_rgbSE;
varying vec2 v_rgbM;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
varying vec2 vTextureCoord;
-vec2 mapCoord( vec2 coord )
+vec4 filterVertexPosition( void )
{
- coord *= filterArea.xy;
- coord += filterArea.zw;
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
- return coord;
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
-vec2 unmapCoord( vec2 coord )
+vec2 filterTextureCoord( void )
{
- coord -= filterArea.zw;
- coord /= filterArea.xy;
-
- return coord;
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
}
void texcoords(vec2 fragCoord, vec2 resolution,
@@ -44,11 +40,11 @@
void main(void) {
- gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
+ gl_Position = filterVertexPosition();
- vTextureCoord = aTextureCoord;
+ vTextureCoord = filterTextureCoord();
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
- texcoords(fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
}
\ No newline at end of file
diff --git a/packages/filters/filter-noise/src/NoiseFilter.js b/packages/filters/filter-noise/src/NoiseFilter.js
index 45d6650..45f6f98 100644
--- a/packages/filters/filter-noise/src/NoiseFilter.js
+++ b/packages/filters/filter-noise/src/NoiseFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './noise.frag';
/**
@@ -22,7 +22,10 @@
*/
constructor(noise = 0.5, seed = Math.random())
{
- super(defaultVertex, fragment);
+ super(defaultFilterVertex, fragment, {
+ uNoise: 0,
+ uSeed: 0,
+ });
this.noise = noise;
this.seed = seed;
diff --git a/packages/fragments/src/defaultFilter.vert b/packages/fragments/src/defaultFilter.vert
new file mode 100644
index 0000000..2e63617
--- /dev/null
+++ b/packages/fragments/src/defaultFilter.vert
@@ -0,0 +1,26 @@
+attribute vec2 aVertexPosition;
+
+uniform mat3 projectionMatrix;
+uniform vec4 sourceFrame;
+uniform vec4 destinationFrame;
+
+varying vec2 vTextureCoord;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
+
+
+void main(void)
+{
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+}
\ No newline at end of file
diff --git a/packages/fragments/src/index.js b/packages/fragments/src/index.js
index ff3b0d7..0daa463 100644
--- a/packages/fragments/src/index.js
+++ b/packages/fragments/src/index.js
@@ -1,3 +1,4 @@
import defaultVertex from './default.vert';
+import defaultFilterVertex from './defaultFilter.vert';
-export { defaultVertex };
+export { defaultVertex, defaultFilterVertex };
diff --git a/packages/graphics/src/GraphicsRenderer.js b/packages/graphics/src/GraphicsRenderer.js
index 9e61d93..57aae42 100644
--- a/packages/graphics/src/GraphicsRenderer.js
+++ b/packages/graphics/src/GraphicsRenderer.js
@@ -218,8 +218,8 @@
{
webGLData = this.graphicsDataPool.pop()
|| new WebGLGraphicsData(this.renderer.gl,
- this.primitiveShader,
- this.renderer.state.attribsState);
+ this.primitiveShader,
+ this.renderer.state.attribsState);
webGLData.nativeLines = nativeLines;
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag
index 26e47b8..98e06c6 100644
--- a/packages/filters/filter-fxaa/src/fxaa.frag
+++ b/packages/filters/filter-fxaa/src/fxaa.frag
@@ -6,38 +6,39 @@
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+
/**
Basic FXAA implementation based on the code on geeks3d.com with the
modification that the texture2DLod stuff was removed since it's
unsupported by WebGL.
-
+
--
-
+
From:
https://github.com/mitsuhiko/webgl-meincraft
-
+
Copyright (c) 2011 by Armin Ronacher.
-
+
Some rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
-
+
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
-
+
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
-
+
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -83,26 +84,26 @@
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
+
mediump vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
+
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
-
+
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
-
+
vec3 rgbA = 0.5 * (
texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz +
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);
-
+
float lumaB = dot(rgbB, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
color = vec4(rgbA, texColor.a);
@@ -113,11 +114,11 @@
void main() {
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
vec4 color;
- color = fxaa(uSampler, fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
gl_FragColor = color;
}
diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert
index 98ef9ac..3ef457c 100644
--- a/packages/filters/filter-fxaa/src/fxaa.vert
+++ b/packages/filters/filter-fxaa/src/fxaa.vert
@@ -1,6 +1,5 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
@@ -10,24 +9,21 @@
varying vec2 v_rgbSE;
varying vec2 v_rgbM;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
varying vec2 vTextureCoord;
-vec2 mapCoord( vec2 coord )
+vec4 filterVertexPosition( void )
{
- coord *= filterArea.xy;
- coord += filterArea.zw;
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
- return coord;
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
-vec2 unmapCoord( vec2 coord )
+vec2 filterTextureCoord( void )
{
- coord -= filterArea.zw;
- coord /= filterArea.xy;
-
- return coord;
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
}
void texcoords(vec2 fragCoord, vec2 resolution,
@@ -44,11 +40,11 @@
void main(void) {
- gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
+ gl_Position = filterVertexPosition();
- vTextureCoord = aTextureCoord;
+ vTextureCoord = filterTextureCoord();
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
- texcoords(fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
}
\ No newline at end of file
diff --git a/packages/filters/filter-noise/src/NoiseFilter.js b/packages/filters/filter-noise/src/NoiseFilter.js
index 45d6650..45f6f98 100644
--- a/packages/filters/filter-noise/src/NoiseFilter.js
+++ b/packages/filters/filter-noise/src/NoiseFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './noise.frag';
/**
@@ -22,7 +22,10 @@
*/
constructor(noise = 0.5, seed = Math.random())
{
- super(defaultVertex, fragment);
+ super(defaultFilterVertex, fragment, {
+ uNoise: 0,
+ uSeed: 0,
+ });
this.noise = noise;
this.seed = seed;
diff --git a/packages/fragments/src/defaultFilter.vert b/packages/fragments/src/defaultFilter.vert
new file mode 100644
index 0000000..2e63617
--- /dev/null
+++ b/packages/fragments/src/defaultFilter.vert
@@ -0,0 +1,26 @@
+attribute vec2 aVertexPosition;
+
+uniform mat3 projectionMatrix;
+uniform vec4 sourceFrame;
+uniform vec4 destinationFrame;
+
+varying vec2 vTextureCoord;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
+
+
+void main(void)
+{
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+}
\ No newline at end of file
diff --git a/packages/fragments/src/index.js b/packages/fragments/src/index.js
index ff3b0d7..0daa463 100644
--- a/packages/fragments/src/index.js
+++ b/packages/fragments/src/index.js
@@ -1,3 +1,4 @@
import defaultVertex from './default.vert';
+import defaultFilterVertex from './defaultFilter.vert';
-export { defaultVertex };
+export { defaultVertex, defaultFilterVertex };
diff --git a/packages/graphics/src/GraphicsRenderer.js b/packages/graphics/src/GraphicsRenderer.js
index 9e61d93..57aae42 100644
--- a/packages/graphics/src/GraphicsRenderer.js
+++ b/packages/graphics/src/GraphicsRenderer.js
@@ -218,8 +218,8 @@
{
webGLData = this.graphicsDataPool.pop()
|| new WebGLGraphicsData(this.renderer.gl,
- this.primitiveShader,
- this.renderer.state.attribsState);
+ this.primitiveShader,
+ this.renderer.state.attribsState);
webGLData.nativeLines = nativeLines;
diff --git a/packages/math/src/shapes/Rectangle.js b/packages/math/src/shapes/Rectangle.js
index 7233a23..af401bd 100644
--- a/packages/math/src/shapes/Rectangle.js
+++ b/packages/math/src/shapes/Rectangle.js
@@ -235,6 +235,16 @@
}
}
+ round(value)
+ {
+ value = value || 1;
+
+ this.x = ((this.x * value) | 0) / value;
+ this.y = ((this.y * value) | 0) / value;
+ this.width = ((this.width * value) | 0) / value;
+ this.height = ((this.height * value) | 0) / value;
+ }
+
/**
* Enlarges this rectangle to include the passed rectangle.
*
diff --git a/package.json b/package.json
index 2816e8a..4d11e5e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@pixi/jsdoc-template": "^2.2.0",
"copyfiles": "^1.2.0",
"electron": "^1.7.9",
- "eslint": "^4.6.1",
+ "eslint": "^4.18.1",
"floss": "^2.1.3",
"glob": "^7.1.2",
"jsdoc": "^3.4.0",
diff --git a/packages/core/src/renderers/Renderer.js b/packages/core/src/renderers/Renderer.js
index ed30b16..a2020b1 100644
--- a/packages/core/src/renderers/Renderer.js
+++ b/packages/core/src/renderers/Renderer.js
@@ -2,7 +2,7 @@
import { sayHello } from '@pixi/utils';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
-// import FilterSystem from './systems/FilterSystem';
+import FilterSystem from './systems/filter/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import TextureSystem from './systems/textures/TextureSystem';
@@ -103,7 +103,7 @@
.addSystem(StencilSystem, 'stencil')
.addSystem(ProjectionSystem, 'projection')
.addSystem(TextureGCSystem, 'textureGC')
- // .addSystem(FilterSystem, 'filter')
+ .addSystem(FilterSystem, 'filter')
.addSystem(RenderTextureSystem, 'renderTexture')
.addSystem(BatchSystem, 'batch');
diff --git a/packages/core/src/renderers/filters/Filter.js b/packages/core/src/renderers/filters/Filter.js
index 2d82c8e..2c07f71 100644
--- a/packages/core/src/renderers/filters/Filter.js
+++ b/packages/core/src/renderers/filters/Filter.js
@@ -1,8 +1,7 @@
import Shader from '../../shader/Shader';
import Program from '../../shader/Program';
-import { BLEND_MODES } from '@pixi/constants';
+import State from '../State';
import { settings } from '@pixi/settings';
-import { uid } from '@pixi/utils';
// import extractUniformsFromSrc from './extractUniformsFromSrc';
import defaultVertex from './defaultFilter.vert';
import defaultFragment from './defaultFilter.frag';
@@ -26,41 +25,6 @@
super(program, uniforms);
- this._blendMode = BLEND_MODES.NORMAL;
-
- this.uniformData = uniforms;
- // || extractUniformsFromSrc(this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler');
-
- /**
- * An object containing the current values of custom uniforms.
- * @example Updating the value of a custom uniform
- * filter.uniforms.time = performance.now();
- *
- * @member {object}
- */
- this.uniforms = {};
-
- for (const i in this.uniformData)
- {
- this.uniforms[i] = this.uniformData[i].value;
- if (this.uniformData[i].type)
- {
- this.uniformData[i].type = this.uniformData[i].type.toLowerCase();
- }
- }
-
- // this is where we store shader references..
- // TODO we could cache this!
- this.glShaders = {};
-
- // used for cacheing.. sure there is a better way!
- if (!Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc])
- {
- Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = uid();
- }
-
- this.glShaderKey = Filter.SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc];
-
/**
* 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
@@ -68,7 +32,7 @@
*
* @member {number}
*/
- this.padding = 4;
+ this.padding = 0;
/**
* The resolution of the filter. Setting this to be lower will lower the quality but
@@ -92,6 +56,12 @@
* @member {boolean}
*/
this.autoFit = true;
+
+ /**
+ * the webGL state the filter requires to render
+ * @type {PIXI.State}
+ */
+ this.state = new State();
}
/**
@@ -105,11 +75,11 @@
* There are some useful properties in the currentState :
* target, filters, sourceFrame, destinationFrame, renderTarget, resolution
*/
- apply(filterManager, input, output, clear, currentState) // eslint-disable-line no-unused-vars
+ apply(filterManager, input, output, clear, currentState, derp) // eslint-disable-line no-unused-vars
{
// do as you please!
- filterManager.applyFilter(this, input, output, clear, currentState);
+ filterManager.applyFilter(this, input, output, clear, currentState, derp);
// or just do a regular render..
}
@@ -122,12 +92,12 @@
*/
get blendMode()
{
- return this._blendMode;
+ return this.state.blendMode;
}
set blendMode(value) // eslint-disable-line require-jsdoc
{
- this._blendMode = value;
+ this.state.blendMode = value;
}
/**
diff --git a/packages/core/src/renderers/systems/FilterSystem.js b/packages/core/src/renderers/systems/FilterSystem.js
deleted file mode 100644
index 0348cf7..0000000
--- a/packages/core/src/renderers/systems/FilterSystem.js
+++ /dev/null
@@ -1,613 +0,0 @@
-import WebGLSystem from './WebGLSystem';
-import RenderTarget from '../utils/RenderTarget';
-import Quad from '../utils/Quad';
-import { Rectangle } from '@pixi/math';
-import Shader from '../../shader/Shader';
-import * as filterTransforms from '../filters/filterTransforms';
-import bitTwiddle from 'bit-twiddle';
-
-/**
- * @ignore
- * @class
- */
-class FilterState
-{
- /**
- *
- */
- constructor()
- {
- this.renderTarget = null;
- this.sourceFrame = new Rectangle();
- this.destinationFrame = new Rectangle();
- this.filters = [];
- this.target = null;
- this.resolution = 1;
- }
-}
-
-/**
- * @class
- * @memberof PIXI
- * @extends PIXI.WebGLSystem
- */
-export default class FilterSystem extends WebGLSystem
-{
- /**
- * @param {PIXI.Renderer} renderer - The renderer this System works for.
- */
- constructor(renderer)
- {
- super(renderer);
-
- this.shaderCache = {};
- // todo add default!
- this.pool = {};
-
- this.filterData = null;
-
- this.managedFilters = [];
- }
-
- contextChange()
- {
- this.gl = this.renderer.gl;
- // know about sprites!
- this.quad = new Quad(this.gl, this.renderer.state.attribState);
- }
-
- /**
- * 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;
-
- let filterData = this.filterData;
-
- if (!filterData)
- {
- filterData = this.renderer.renderTexture.current.filterStack;
-
- // add new stack
- const filterState = new FilterState();
-
- filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
- filterState.renderTarget = renderer._activeRenderTarget;
-
- this.renderer._activeRenderTarget.filterData = filterData = {
- index: 0,
- stack: [filterState],
- };
-
- this.filterData = filterData;
- }
-
- // get the current filter state..
- let currentState = filterData.stack[++filterData.index];
-
- if (!currentState)
- {
- currentState = filterData.stack[filterData.index] = new FilterState();
- }
-
- // for now we go off the filter of the first resolution..
- const resolution = filters[0].resolution;
- const padding = filters[0].padding | 0;
- const targetBounds = target.filterArea || target.getBounds(true);
- const sourceFrame = currentState.sourceFrame;
- const destinationFrame = currentState.destinationFrame;
-
- sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
- sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
- sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
- sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;
-
- if (filterData.stack[0].renderTarget.transform)
- { //
-
- // TODO we should fit the rect around the transform..
- }
- else if (filters[0].autoFit)
- {
- sourceFrame.fit(filterData.stack[0].destinationFrame);
- }
-
- // lets apply the padding After we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
- sourceFrame.pad(padding);
-
- destinationFrame.width = sourceFrame.width;
- destinationFrame.height = sourceFrame.height;
-
- // lets play the padding after we fit the element to the screen.
- // this should stop the strange side effects that can occur when cropping to the edges
-
- const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);
-
- currentState.target = target;
- currentState.filters = filters;
- currentState.resolution = resolution;
- currentState.renderTarget = renderTarget;
-
- // bind the render target to draw the shape in the top corner..
-
- renderTarget.setFrame(destinationFrame, sourceFrame);
-
- // bind the render target
- renderer.bindRenderTarget(renderTarget);
- renderTarget.clear();
- }
-
- /**
- * Pops off the filter and applies it.
- *
- */
- pop()
- {
- const filterData = this.filterData;
-
- const lastState = filterData.stack[filterData.index - 1];
- const currentState = filterData.stack[filterData.index];
-
- this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();
-
- const filters = currentState.filters;
-
- if (filters.length === 1)
- {
- filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false, currentState);
- this.freePotRenderTarget(currentState.renderTarget);
- }
- else
- {
- let flip = currentState.renderTarget;
- let flop = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- currentState.resolution
- );
-
- flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- // finally lets clear the render target before drawing to it..
- flop.clear();
-
- let i = 0;
-
- for (i = 0; i < filters.length - 1; ++i)
- {
- filters[i].apply(this, flip, flop, true, currentState);
-
- const t = flip;
-
- flip = flop;
- flop = t;
- }
-
- filters[i].apply(this, flip, lastState.renderTarget, false, currentState);
-
- this.freePotRenderTarget(flip);
- this.freePotRenderTarget(flop);
- }
-
- filterData.index--;
-
- if (filterData.index === 0)
- {
- this.filterData = null;
- }
- }
-
- /**
- * 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;
- const gl = renderer.gl;
-
- let shader = filter.glShaders[renderer.CONTEXT_UID];
-
- // cacheing..
- if (!shader)
- {
- if (filter.glShaderKey)
- {
- shader = this.shaderCache[filter.glShaderKey];
-
- if (!shader)
- {
- shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
-
- filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
- this.managedFilters.push(filter);
- }
- }
- else
- {
- shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
- this.managedFilters.push(filter);
- }
-
- // TODO - this only needs to be done once?
- renderer.bindVao(null);
-
- this.quad.initVao(shader);
- }
-
- renderer.bindVao(this.quad.vao);
-
- renderer.bindRenderTarget(output);
-
- if (clear)
- {
- gl.disable(gl.SCISSOR_TEST);
- renderer.clear();// [1, 1, 1, 1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- // in case the render target is being masked using a scissor rect
- if (output === renderer.maskSystem.scissorRenderTarget)
- {
- renderer.maskSystem.pushScissorMask(null, renderer.maskSystem.scissorData);
- }
-
- renderer.bindShader(shader);
-
- // free unit 0 for us, doesn't matter what was there
- // don't try to restore it, because syncUniforms can upload it to another slot
- // and it'll be a problem
- const tex = this.renderer.emptyTextures[0];
-
- this.renderer.boundTextures[0] = tex;
- // this syncs the PixiJS filters uniforms with glsl uniforms
- this.syncUniforms(shader, filter);
-
- renderer.state.setBlendMode(filter.blendMode);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, input.texture.texture);
-
- this.quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- /**
- * Uploads the uniforms of the filter.
- *
- * @param {GLShader} shader - The underlying gl shader.
- * @param {PIXI.Filter} filter - The filter we are synchronizing.
- */
- syncUniforms(shader, filter)
- {
- const uniformData = filter.uniformData;
- const uniforms = filter.uniforms;
-
- // 0 is reserved for the PixiJS texture so we start at 1!
- let textureCount = 1;
- let currentState;
-
- // filterArea and filterClamp that are handled by FilterSystem directly
- // they must not appear in uniformData
-
- if (shader.uniforms.filterArea)
- {
- currentState = this.filterData.stack[this.filterData.index];
-
- const filterArea = shader.uniforms.filterArea;
-
- filterArea[0] = currentState.renderTarget.size.width;
- filterArea[1] = currentState.renderTarget.size.height;
- filterArea[2] = currentState.sourceFrame.x;
- filterArea[3] = currentState.sourceFrame.y;
-
- shader.uniforms.filterArea = filterArea;
- }
-
- // use this to clamp displaced texture coords so they belong to filterArea
- // see displacementFilter fragment shader for an example
- if (shader.uniforms.filterClamp)
- {
- currentState = currentState || this.filterData.stack[this.filterData.index];
-
- const filterClamp = shader.uniforms.filterClamp;
-
- filterClamp[0] = 0;
- filterClamp[1] = 0;
- filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
- filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;
-
- shader.uniforms.filterClamp = filterClamp;
- }
-
- // TODO Cacheing layer..
- for (const i in uniformData)
- {
- const type = uniformData[i].type;
-
- if (type === 'sampler2d' && uniforms[i] !== 0)
- {
- if (uniforms[i].baseTexture)
- {
- shader.uniforms[i] = this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
- }
- else
- {
- shader.uniforms[i] = textureCount;
-
- // TODO
- // this is helpful as renderTargets can also be set.
- // Although thinking about it, we could probably
- // make the filter texture cache return a RenderTexture
- // rather than a renderTarget
- const gl = this.renderer.gl;
-
- this.renderer.boundTextures[textureCount] = this.renderer.emptyTextures[textureCount];
- gl.activeTexture(gl.TEXTURE0 + textureCount);
-
- uniforms[i].texture.bind();
- }
-
- textureCount++;
- }
- else if (type === 'mat3')
- {
- // check if its PixiJS matrix..
- if (uniforms[i].a !== undefined)
- {
- shader.uniforms[i] = uniforms[i].toArray(true);
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'vec2')
- {
- // check if its a point..
- if (uniforms[i].x !== undefined)
- {
- const val = shader.uniforms[i] || new Float32Array(2);
-
- val[0] = uniforms[i].x;
- val[1] = uniforms[i].y;
- shader.uniforms[i] = val;
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else if (type === 'float')
- {
- if (shader.uniforms.data[i].value !== uniformData[i])
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- else
- {
- shader.uniforms[i] = uniforms[i];
- }
- }
- }
-
- /**
- * Gets a render target from the pool, or creates a new one.
- *
- * @param {boolean} clear - Should we clear the render texture when we get it?
- * @param {number} resolution - The resolution of the target.
- * @return {PIXI.RenderTarget} The new render target
- */
- getRenderTarget(clear, resolution)
- {
- const currentState = this.filterData.stack[this.filterData.index];
- const renderTarget = this.getPotRenderTarget(
- this.renderer.gl,
- currentState.sourceFrame.width,
- currentState.sourceFrame.height,
- resolution || currentState.resolution
- );
-
- renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);
-
- return renderTarget;
- }
-
- /**
- * Returns a render target to the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The render target to return.
- */
- returnRenderTarget(renderTarget)
- {
- this.freePotRenderTarget(renderTarget);
- }
-
- /**
- * 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size
- );
- }
-
- /**
- * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
- *
- * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
- * @return {PIXI.Matrix} The mapped matrix.
- */
- calculateNormalizedScreenSpaceMatrix(outputMatrix)
- {
- const currentState = this.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateNormalizedScreenSpaceMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- 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.filterData.stack[this.filterData.index];
-
- return filterTransforms.calculateSpriteMatrix(
- outputMatrix,
- currentState.sourceFrame,
- currentState.renderTarget.size,
- sprite
- );
- }
-
- /**
- * Destroys this Filter System.
- *
- * @param {boolean} [contextLost=false] context was lost, do not free shaders
- *
- */
- destroy(contextLost = false)
- {
- const renderer = this.renderer;
- const filters = this.managedFilters;
-
- for (let i = 0; i < filters.length; i++)
- {
- if (!contextLost)
- {
- filters[i].glShaders[renderer.CONTEXT_UID].destroy();
- }
- delete filters[i].glShaders[renderer.CONTEXT_UID];
- }
-
- this.shaderCache = {};
- if (!contextLost)
- {
- this.emptyPool();
- }
- else
- {
- this.pool = {};
- }
- }
-
- /**
- * 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.
- */
- getPotRenderTarget(gl, minWidth, minHeight, resolution)
- {
- // TODO you could return a bigger texture if there is not one in the pool?
- minWidth = bitTwiddle.nextPow2(minWidth * resolution);
- minHeight = bitTwiddle.nextPow2(minHeight * resolution);
-
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- if (!this.pool[key])
- {
- this.pool[key] = [];
- }
-
- let renderTarget = this.pool[key].pop();
-
- // creating render target will cause texture to be bound!
- if (!renderTarget)
- {
- // temporary bypass cache..
- const tex = this.renderer.boundTextures[0];
-
- gl.activeTexture(gl.TEXTURE0);
-
- // internally - this will cause a texture to be bound..
- renderTarget = new RenderTarget(gl, minWidth, minHeight, null, 1);
-
- // set the current one back
- gl.bindTexture(gl.TEXTURE_2D, tex._glTextures[this.renderer.CONTEXT_UID].texture);
- }
-
- // manually tweak the resolution...
- // this will not modify the size of the frame buffer, just its resolution.
- renderTarget.resolution = resolution;
- renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
- renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;
-
- return renderTarget;
- }
-
- /**
- * Empties the texture pool.
- *
- */
- emptyPool()
- {
- for (const i in this.pool)
- {
- const textures = this.pool[i];
-
- if (textures)
- {
- for (let j = 0; j < textures.length; j++)
- {
- textures[j].destroy(true);
- }
- }
- }
-
- this.pool = {};
- }
-
- /**
- * Frees a render target back into the pool.
- *
- * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
- */
- freePotRenderTarget(renderTarget)
- {
- const minWidth = renderTarget.size.width * renderTarget.resolution;
- const minHeight = renderTarget.size.height * renderTarget.resolution;
- const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);
-
- this.pool[key].push(renderTarget);
- }
-}
diff --git a/packages/core/src/renderers/systems/FramebufferSystem.js b/packages/core/src/renderers/systems/FramebufferSystem.js
index 6234e09..df7902b 100644
--- a/packages/core/src/renderers/systems/FramebufferSystem.js
+++ b/packages/core/src/renderers/systems/FramebufferSystem.js
@@ -1,4 +1,5 @@
import WebGLSystem from './WebGLSystem';
+import { Rectangle } from '@pixi/math';
/**
* @class
@@ -16,14 +17,18 @@
{
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)
+ bind(framebuffer, frame)
{
const gl = this.gl;
+ this.current = framebuffer;
+
if (framebuffer)
{
// TODO cacheing layer!
@@ -67,16 +72,56 @@
this.renderer.texture.unbind(framebuffer.depthTexture);
}
- gl.viewport(0, 0, framebuffer.width, framebuffer.height);
+ 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);
- gl.viewport(0, 0, this.renderer.width, this.renderer.height);
+ 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;
diff --git a/packages/core/src/renderers/systems/ProjectionSystem.js b/packages/core/src/renderers/systems/ProjectionSystem.js
index 7c268c9..c91de1d 100644
--- a/packages/core/src/renderers/systems/ProjectionSystem.js
+++ b/packages/core/src/renderers/systems/ProjectionSystem.js
@@ -40,30 +40,25 @@
{
const pm = this.projectionMatrix;
- pm.identity();
+ // I don't think we will need this line..
+ // pm.identity();
- // TODO: make dest scale source
if (!root)
{
- pm.a = 1 / destinationFrame.width * 2;
- pm.d = 1 / destinationFrame.height * 2;
+ 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;
- pm.d = -1 / destinationFrame.height * 2;
+ 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);
}
-
- // apply the resolution..
- // TODO - prob should apply this to x and y too!
- pm.a *= resolution;
- pm.d *= resolution;
}
/**
diff --git a/packages/core/src/renderers/systems/RenderTextureSystem.js b/packages/core/src/renderers/systems/RenderTextureSystem.js
index 42efde6..61cfb28 100644
--- a/packages/core/src/renderers/systems/RenderTextureSystem.js
+++ b/packages/core/src/renderers/systems/RenderTextureSystem.js
@@ -22,16 +22,19 @@
// TODO moe this property somewhere else!
this.defaultMaskStack = [];
+ this.defaultFilterStack = [{}];
// empty render texture?
+ this.renderTexture = null;
+
+ this.destinationFrame = new Rectangle();
}
- bind(renderTexture)
+ bind(renderTexture, sourceFrame, destinationFrame)
{
// TODO - do we want this??
if (this.renderTexture === renderTexture) return;
this.renderTexture = renderTexture;
- this.current = renderTexture;
const renderer = this.renderer;
@@ -39,21 +42,49 @@
{
const baseTexture = renderTexture.baseTexture;
- this.renderer.framebuffer.bind(baseTexture.frameBuffer);
- this.renderer.projection.update(renderTexture.frame, renderTexture.frame, baseTexture.resolution, false);
+ 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
{
- renderer.framebuffer.bind(null);
+ // TODO these validation checks happen deeper down..
+ // thing they can be avoided..
+ if (!destinationFrame)
+ {
+ tempRect.width = renderer.width;
+ tempRect.height = renderer.height;
- 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(tempRect, tempRect, this.renderer.resolution, true);
+ this.renderer.projection.update(destinationFrame, sourceFrame, this.renderer.resolution, true);
this.renderer.stencil.setMaskStack(this.defaultMaskStack);
}
+
+ this.destinationFrame.copyFrom(destinationFrame);
}
/**
diff --git a/packages/core/src/renderers/systems/StencilSystem.js b/packages/core/src/renderers/systems/StencilSystem.js
index 7d29311..15e958a 100644
--- a/packages/core/src/renderers/systems/StencilSystem.js
+++ b/packages/core/src/renderers/systems/StencilSystem.js
@@ -13,7 +13,7 @@
constructor(renderer)
{
super(renderer);
- this.stencilMaskStack = null;
+ this.stencilMaskStack = [];
}
/**
@@ -23,18 +23,21 @@
*/
setMaskStack(stencilMaskStack)
{
- this.stencilMaskStack = stencilMaskStack;
-
const gl = this.renderer.gl;
- if (stencilMaskStack.length === 0)
+ if (stencilMaskStack.length !== this.stencilMaskStack.length)
{
- gl.disable(gl.STENCIL_TEST);
+ if (stencilMaskStack.length === 0)
+ {
+ gl.disable(gl.STENCIL_TEST);
+ }
+ else
+ {
+ gl.enable(gl.STENCIL_TEST);
+ }
}
- else
- {
- gl.enable(gl.STENCIL_TEST);
- }
+
+ this.stencilMaskStack = stencilMaskStack;
}
/**
diff --git a/packages/core/src/renderers/systems/filter/FilterSystem.js b/packages/core/src/renderers/systems/filter/FilterSystem.js
new file mode 100644
index 0000000..fdbe5ba
--- /dev/null
+++ b/packages/core/src/renderers/systems/filter/FilterSystem.js
@@ -0,0 +1,444 @@
+import WebGLSystem from '../WebGLSystem';
+
+import RenderTexture from '../../../textures/RenderTexture';
+import Quad from '../../utils/Quad';
+import { Rectangle } from '@pixi/math';
+import * as filterTransforms from '../../filters/filterTransforms';
+import bitTwiddle from 'bit-twiddle';
+import UniformGroup from '../../../shader/UniformGroup';
+
+//
+/**
+ * @ignore
+ * @class
+ */
+class FilterState
+{
+ /**
+ *
+ */
+ constructor()
+ {
+ this.renderTexture = null;
+ this.sourceFrame = new Rectangle();
+ this.destinationFrame = new Rectangle();
+ this.filters = [];
+ this.target = null;
+ this.resolution = 1;
+ }
+}
+
+/**
+ * @class
+ * @memberof PIXI
+ * @extends PIXI.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();
+
+ /**
+ * 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,
+ }, 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;
+
+ for (let i = 1; i < filters.length; i++)
+ {
+ // lets use the lowest resolution..
+ resolution = Math.min(resolution, filters[i].resolution);
+ // and the largest amout of padding!
+ padding = Math.max(padding, filters[i].padding);
+ // only auto fit if all filters are autofit
+ autoFit = autoFit || filters[i].autoFit;
+ }
+
+ filterStack.push(state);
+
+ state.resolution = resolution;
+
+ // round to whole number based on resolution
+ // TODO move that to the shader too?
+ state.sourceFrame = target.filterArea ? this.transformFilterArea(this.tempRect,
+ target.filterArea,
+ target.transform) : 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;
+
+ this.globalUniforms.update();
+
+ const lastState = filterStack[filterStack.length - 1];
+
+ if (filters.length === 1)
+ {
+ filters[0].apply(this, state.renderTexture, lastState.renderTexture, false, state);
+ renderer.renderTexture.bind(null);
+
+ 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);
+ renderer.geometry.bind(this.quad);
+ renderer.geometry.draw(5);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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 target back into the pool.
+ *
+ * @param {PIXI.RenderTarget} renderTarget - 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 = {};
+ }
+
+ transformFilterArea(out, rectangle, transform)
+ {
+ const x0 = rectangle.x;
+ const y0 = rectangle.y;
+
+ const x1 = rectangle.x + rectangle.width;
+ const y1 = rectangle.y + rectangle.height;
+
+ const matrix = transform.worldTransform;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+
+ let x = (a * x0) + (c * y0) + tx;
+ let y = (b * x0) + (d * y0) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y0) + tx;
+ y = (b * x1) + (d * y0) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x0) + (c * y1) + tx;
+ y = (b * x0) + (d * y1) + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ x = (a * x1) + (c * y1) + tx;
+ y = (b * x1) + (d * y1) + ty;
+
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+
+ out.x = minX;
+ out.y = minY;
+
+ out.width = maxX - minX;
+ out.height = maxY - minY;
+
+ return out;
+ }
+}
diff --git a/packages/core/src/renderers/utils/Quad.js b/packages/core/src/renderers/utils/Quad.js
index d6cc972..34f02b4 100644
--- a/packages/core/src/renderers/utils/Quad.js
+++ b/packages/core/src/renderers/utils/Quad.js
@@ -1,4 +1,3 @@
-import { createIndicesForQuads } from '@pixi/utils';
import Geometry from '../../geometry/Geometry';
/**
@@ -17,159 +16,12 @@
{
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([
+ this.addAttribute('aVertexPosition', [
0, 0,
1, 0,
1, 1,
0, 1,
- ]);
-
- this.interleaved = new Float32Array(8 * 2);
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- /*
- * @member {Uint16Array} An array containing the indices of the vertices
- *
- * @member {Uint16Array}
- */
- this.indices = createIndicesForQuads(1);
-
- /**
- * The vertex buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW);
-
- /**
- * The index buffer
- *
- * @member {glCore.GLBuffer}
- */
- // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
-
- /**
- * The vertex array object
- *
- * @member {glCore.VertexArrayObject}
- */
- // this.vao = new glCore.VertexArrayObject(gl, state);
-
- this.addAttribute('aVertexPosition', this.vertices)
- .addAttribute('aTextureCoord', this.uvs)
- .addIndex(this.indices);
- }
-
- /**
- * Initialises the vaos and uses the shader.
- *
- * @param {PIXI.Shader} shader - the shader to use
- */
- initVao(shader)
- {
- this.vao.clear()
- .addIndex(this.indexBuffer)
- .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0)
- .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4);
- }
-
- /**
- * 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;
-
- return this;
- }
-
- /**
- * Binds the buffer and uploads the data
- *
- * @return {PIXI.Quad} Returns itself.
- */
- upload()
- {
- this.getAttribute('aVertexPosition').update();
- this.getAttribute('aTextureCoord').update();
-
- for (let i = 0; i < 4; i++)
- {
- this.interleaved[i * 4] = this.vertices[(i * 2)];
- this.interleaved[(i * 4) + 1] = this.vertices[(i * 2) + 1];
- this.interleaved[(i * 4) + 2] = this.uvs[i * 2];
- this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1];
- }
-
- this.vertexBuffer.upload(this.interleaved);
-
- return this;
- }
-
- /**
- * Removes this quad from WebGL
- */
- destroy()
- {
- // const gl = this.gl;
-
- // gl.deleteBuffer(this.vertexBuffer);
- // gl.deleteBuffer(this.indexBuffer);
+ ])
+ .addIndex([0, 1, 3, 2]);
}
}
diff --git a/packages/core/src/shader/generateUniformsSync.js b/packages/core/src/shader/generateUniformsSync.js
index 65eb250..98a357f 100644
--- a/packages/core/src/shader/generateUniformsSync.js
+++ b/packages/core/src/shader/generateUniformsSync.js
@@ -174,6 +174,43 @@
\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;
diff --git a/packages/core/src/textures/BaseRenderTexture.js b/packages/core/src/textures/BaseRenderTexture.js
index 6cffdd0..4fc5242 100644
--- a/packages/core/src/textures/BaseRenderTexture.js
+++ b/packages/core/src/textures/BaseRenderTexture.js
@@ -109,7 +109,7 @@
*
* @member {PIXI.Graphics[]}
*/
- this.filterStack = [];
+ this.filterStack = [{}];
}
/**
diff --git a/packages/display/src/Container.js b/packages/display/src/Container.js
index ae08bfd..3e00b87 100644
--- a/packages/display/src/Container.js
+++ b/packages/display/src/Container.js
@@ -400,7 +400,7 @@
}
// do a quick check to see if this element has a mask or a filter.
- if (this._mask || this._filters)
+ if (this._mask || this.filters)
{
this.renderAdvanced(renderer);
}
@@ -426,7 +426,7 @@
{
renderer.batch.flush();
- const filters = this._filters;
+ const filters = this.filters;
const mask = this._mask;
// push filter first as we need to ensure the stencil buffer is correct for any masking
diff --git a/packages/display/src/DisplayObject.js b/packages/display/src/DisplayObject.js
index bf9d3ac..7f45a81 100644
--- a/packages/display/src/DisplayObject.js
+++ b/packages/display/src/DisplayObject.js
@@ -84,7 +84,14 @@
*/
this.filterArea = null;
- this._filters = null;
+ /**
+ * Sets the filters for the displayObject.
+ * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
+ * To remove filters simply set this property to 'null'
+ *
+ * @member {PIXI.Filter[]}
+ */
+ this.filters = null;
this._enabledFilters = null;
/**
@@ -600,23 +607,6 @@
this._mask.isMask = true;
}
}
-
- /**
- * Sets the filters for the displayObject.
- * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
- * To remove filters simply set this property to 'null'
- *
- * @member {PIXI.Filter[]}
- */
- get filters()
- {
- return this._filters && this._filters.slice();
- }
-
- set filters(value) // eslint-disable-line require-jsdoc
- {
- this._filters = value && value.slice();
- }
}
// performance increase to avoid using call.. (10x faster)
diff --git a/packages/filters/filter-alpha/src/AlphaFilter.js b/packages/filters/filter-alpha/src/AlphaFilter.js
index 90a5bf8..5fa8f1a 100644
--- a/packages/filters/filter-alpha/src/AlphaFilter.js
+++ b/packages/filters/filter-alpha/src/AlphaFilter.js
@@ -26,10 +26,9 @@
*/
constructor(alpha = 1.0)
{
- super(defaultVertex, fragment);
+ super(defaultVertex, fragment, { uAlpha: 1 });
this.alpha = alpha;
- this.glShaderKey = 'alpha';
}
/**
diff --git a/packages/filters/filter-blur/src/BlurFilter.js b/packages/filters/filter-blur/src/BlurFilter.js
index 656a3f9..43906dc 100644
--- a/packages/filters/filter-blur/src/BlurFilter.js
+++ b/packages/filters/filter-blur/src/BlurFilter.js
@@ -41,12 +41,12 @@
*/
apply(filterManager, input, output)
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture(true);
this.blurXFilter.apply(filterManager, input, renderTarget, true);
this.blurYFilter.apply(filterManager, renderTarget, output, false);
- filterManager.returnRenderTarget(renderTarget);
+ filterManager.returnFilterTexture(renderTarget);
}
/**
diff --git a/packages/filters/filter-blur/src/BlurXFilter.js b/packages/filters/filter-blur/src/BlurXFilter.js
index 90b8fcc..af46e16 100644
--- a/packages/filters/filter-blur/src/BlurXFilter.js
+++ b/packages/filters/filter-blur/src/BlurXFilter.js
@@ -37,19 +37,12 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
- /**
- * Applies the filter.
- *
- * @param {PIXI.FilterManager} filterManager - The manager.
- * @param {PIXI.RenderTarget} input - The input target.
- * @param {PIXI.RenderTarget} output - The output target.
- * @param {boolean} clear - Should the output be cleared before rendering?
- */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -63,11 +56,18 @@
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.width) * (output.size.width / input.size.width);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.width) * (output.width / input.width);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.width) * (filterManager.renderer.width / input.width);
+ }
// screen space!
this.uniforms.strength *= this.strength;
- this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes);
+ this.uniforms.strength /= this.passes;
if (this.passes === 1)
{
@@ -75,26 +75,35 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
-
/**
* Sets the strength of both the blur.
*
@@ -108,7 +117,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/BlurYFilter.js b/packages/filters/filter-blur/src/BlurYFilter.js
index c21d899..2d337a4 100644
--- a/packages/filters/filter-blur/src/BlurYFilter.js
+++ b/packages/filters/filter-blur/src/BlurYFilter.js
@@ -37,7 +37,8 @@
this._quality = 0;
this.quality = quality || 4;
- this.strength = strength || 8;
+
+ this.blur = strength || 8;
this.firstRun = true;
}
@@ -50,6 +51,14 @@
* @param {PIXI.RenderTarget} output - The output target.
* @param {boolean} clear - Should the output be cleared before rendering?
*/
+ /**
+ * Applies the filter.
+ *
+ * @param {PIXI.FilterManager} filterManager - The manager.
+ * @param {PIXI.RenderTarget} input - The input target.
+ * @param {PIXI.RenderTarget} output - The output target.
+ * @param {boolean} clear - Should the output be cleared before rendering?
+ */
apply(filterManager, input, output, clear)
{
if (this.firstRun)
@@ -57,14 +66,22 @@
const gl = filterManager.renderer.gl;
const kernelSize = getMaxBlurKernelSize(gl);
- this.vertexSrc = generateBlurVertSource(kernelSize, false);
+ this.vertexSrc = generateBlurVertSource(kernelSize, true);
this.fragmentSrc = generateBlurFragSource(kernelSize);
this.firstRun = false;
}
- this.uniforms.strength = (1 / output.size.height) * (output.size.height / input.size.height);
+ if (output)
+ {
+ this.uniforms.strength = (1 / output.height) * (output.height / input.height);
+ }
+ else
+ {
+ this.uniforms.strength = (1 / filterManager.renderer.height) * (filterManager.renderer.height / input.height);
+ }
+ // screen space!
this.uniforms.strength *= this.strength;
this.uniforms.strength /= this.passes;
@@ -74,23 +91,33 @@
}
else
{
- const renderTarget = filterManager.getRenderTarget(true);
+ const renderTarget = filterManager.getFilterTexture();
+ const renderer = filterManager.renderer;
+
let flip = input;
let flop = renderTarget;
- for (let i = 0; i < this.passes - 1; i++)
+ this.state.blend = false;
+ filterManager.applyFilter(this, flip, flop, false);
+
+ for (let i = 1; i < this.passes - 1; i++)
{
- filterManager.applyFilter(this, flip, flop, true);
+ renderer.renderTexture.bind(flip, flip.filterFrame);
+
+ this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
+
+ renderer.shader.bind(this);
+ renderer.geometry.draw(5);
}
- filterManager.applyFilter(this, flip, output, clear);
-
- filterManager.returnRenderTarget(renderTarget);
+ this.state.blend = true;
+ filterManager.applyFilter(this, flop, output, clear);
+ filterManager.returnFilterTexture(renderTarget);
}
}
@@ -107,7 +134,7 @@
set blur(value) // eslint-disable-line require-jsdoc
{
- this.padding = Math.abs(value) * 2;
+ this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js
index e27d103..11b1806 100644
--- a/packages/filters/filter-blur/src/generateBlurVertSource.js
+++ b/packages/filters/filter-blur/src/generateBlurVertSource.js
@@ -1,18 +1,40 @@
-const vertTemplate = [
- 'attribute vec2 aVertexPosition;',
- 'attribute vec2 aTextureCoord;',
+const vertTemplate = `
+ attribute vec2 aVertexPosition;
- 'uniform float strength;',
- 'uniform mat3 projectionMatrix;',
+ uniform mat3 projectionMatrix;
- 'varying vec2 vBlurTexCoords[%size%];',
+ uniform vec4 destinationFrame;
+ uniform vec4 sourceFrame;
- 'void main(void)',
- '{',
- 'gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);',
- '%blur%',
- '}',
-].join('\n');
+ uniform float strength;
+ uniform float resolution;
+
+ varying vec2 vBlurTexCoords[%size%];
+
+ vec4 filterVertexPosition( void )
+ {
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+ }
+
+ vec2 filterTextureCoord( void )
+ {
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+ }
+
+ vec2 size( void )
+ {
+ return ( (sourceFrame.zw -resolution) / destinationFrame.zw);
+ }
+
+ void main(void)
+ {
+ gl_Position = filterVertexPosition();
+
+ vec2 textureCoord = filterTextureCoord();
+ %blur%
+ }`;
export default function generateVertBlurSource(kernelSize, x)
{
@@ -26,11 +48,11 @@
if (x)
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(%sampleIndex% * strength, 0.0);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());';
}
else
{
- template = 'vBlurTexCoords[%index%] = aTextureCoord + vec2(0.0, %sampleIndex% * strength);';
+ template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());';
}
for (let i = 0; i < kernelSize; i++)
diff --git a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
index 39ab18a..73b62a2 100644
--- a/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
+++ b/packages/filters/filter-color-matrix/src/ColorMatrixFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './colorMatrix.frag';
/**
@@ -24,13 +24,15 @@
*/
constructor()
{
- super(defaultVertex, fragment);
+ const uniforms = {
+ m: new Float32Array([1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0]),
+ uAlpha: 1,
+ };
- this.uniforms.m = [
- 1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0];
+ super(defaultFilterVertex, fragment, uniforms);
this.alpha = 1;
}
diff --git a/packages/filters/filter-displacement/src/DisplacementFilter.js b/packages/filters/filter-displacement/src/DisplacementFilter.js
index 29a4624..e32cb5f 100644
--- a/packages/filters/filter-displacement/src/DisplacementFilter.js
+++ b/packages/filters/filter-displacement/src/DisplacementFilter.js
@@ -26,15 +26,15 @@
sprite.renderable = false;
- super(vertex, fragment);
+ super(vertex, fragment, {
+ mapSampler: sprite._texture,
+ filterMatrix: maskMatrix,
+ scale: { x: 1, y: 1 },
+ });
this.maskSprite = sprite;
this.maskMatrix = maskMatrix;
- this.uniforms.mapSampler = sprite._texture;
- this.uniforms.filterMatrix = maskMatrix;
- this.uniforms.scale = { x: 1, y: 1 };
-
if (scale === null || scale === undefined)
{
scale = 20;
diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag
index c310299..8de1030 100644
--- a/packages/filters/filter-displacement/src/displacement.frag
+++ b/packages/filters/filter-displacement/src/displacement.frag
@@ -1,20 +1,20 @@
varying vec2 vFilterCoord;
varying vec2 vTextureCoord;
+varying vec4 vFilterClamp;
uniform vec2 scale;
uniform sampler2D uSampler;
uniform sampler2D mapSampler;
-uniform vec4 filterArea;
-uniform vec4 filterClamp;
+uniform vec4 destinationFrame;
void main(void)
{
vec4 map = texture2D(mapSampler, vFilterCoord);
map -= 0.5;
- map.xy *= scale / filterArea.xy;
+ map.xy *= scale / destinationFrame.zw;
- gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), filterClamp.xy, filterClamp.zw));
+ gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw));
}
diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert
index 75f1824..0682a29 100755
--- a/packages/filters/filter-displacement/src/displacement.vert
+++ b/packages/filters/filter-displacement/src/displacement.vert
@@ -1,15 +1,32 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
+varying vec4 vFilterClamp;
+
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
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;
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+ vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy;
+
+ vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw;
}
\ No newline at end of file
diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag
index 26e47b8..98e06c6 100644
--- a/packages/filters/filter-fxaa/src/fxaa.frag
+++ b/packages/filters/filter-fxaa/src/fxaa.frag
@@ -6,38 +6,39 @@
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+
/**
Basic FXAA implementation based on the code on geeks3d.com with the
modification that the texture2DLod stuff was removed since it's
unsupported by WebGL.
-
+
--
-
+
From:
https://github.com/mitsuhiko/webgl-meincraft
-
+
Copyright (c) 2011 by Armin Ronacher.
-
+
Some rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
-
+
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
-
+
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
-
+
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -83,26 +84,26 @@
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
+
mediump vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
+
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
-
+
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
-
+
vec3 rgbA = 0.5 * (
texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz +
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);
-
+
float lumaB = dot(rgbB, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
color = vec4(rgbA, texColor.a);
@@ -113,11 +114,11 @@
void main() {
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
vec4 color;
- color = fxaa(uSampler, fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
gl_FragColor = color;
}
diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert
index 98ef9ac..3ef457c 100644
--- a/packages/filters/filter-fxaa/src/fxaa.vert
+++ b/packages/filters/filter-fxaa/src/fxaa.vert
@@ -1,6 +1,5 @@
attribute vec2 aVertexPosition;
-attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
@@ -10,24 +9,21 @@
varying vec2 v_rgbSE;
varying vec2 v_rgbM;
-uniform vec4 filterArea;
+uniform vec4 destinationFrame;
+uniform vec4 sourceFrame;
varying vec2 vTextureCoord;
-vec2 mapCoord( vec2 coord )
+vec4 filterVertexPosition( void )
{
- coord *= filterArea.xy;
- coord += filterArea.zw;
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
- return coord;
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
-vec2 unmapCoord( vec2 coord )
+vec2 filterTextureCoord( void )
{
- coord -= filterArea.zw;
- coord /= filterArea.xy;
-
- return coord;
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
}
void texcoords(vec2 fragCoord, vec2 resolution,
@@ -44,11 +40,11 @@
void main(void) {
- gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
+ gl_Position = filterVertexPosition();
- vTextureCoord = aTextureCoord;
+ vTextureCoord = filterTextureCoord();
- vec2 fragCoord = vTextureCoord * filterArea.xy;
+ vec2 fragCoord = vTextureCoord * destinationFrame.zw;
- texcoords(fragCoord, filterArea.xy, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
+ texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM);
}
\ No newline at end of file
diff --git a/packages/filters/filter-noise/src/NoiseFilter.js b/packages/filters/filter-noise/src/NoiseFilter.js
index 45d6650..45f6f98 100644
--- a/packages/filters/filter-noise/src/NoiseFilter.js
+++ b/packages/filters/filter-noise/src/NoiseFilter.js
@@ -1,5 +1,5 @@
import { Filter } from '@pixi/core';
-import { defaultVertex } from '@pixi/fragments';
+import { defaultFilterVertex } from '@pixi/fragments';
import fragment from './noise.frag';
/**
@@ -22,7 +22,10 @@
*/
constructor(noise = 0.5, seed = Math.random())
{
- super(defaultVertex, fragment);
+ super(defaultFilterVertex, fragment, {
+ uNoise: 0,
+ uSeed: 0,
+ });
this.noise = noise;
this.seed = seed;
diff --git a/packages/fragments/src/defaultFilter.vert b/packages/fragments/src/defaultFilter.vert
new file mode 100644
index 0000000..2e63617
--- /dev/null
+++ b/packages/fragments/src/defaultFilter.vert
@@ -0,0 +1,26 @@
+attribute vec2 aVertexPosition;
+
+uniform mat3 projectionMatrix;
+uniform vec4 sourceFrame;
+uniform vec4 destinationFrame;
+
+varying vec2 vTextureCoord;
+
+vec4 filterVertexPosition( void )
+{
+ vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy;
+
+ return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+}
+
+vec2 filterTextureCoord( void )
+{
+ return aVertexPosition * (sourceFrame.zw / destinationFrame.zw);
+}
+
+
+void main(void)
+{
+ gl_Position = filterVertexPosition();
+ vTextureCoord = filterTextureCoord();
+}
\ No newline at end of file
diff --git a/packages/fragments/src/index.js b/packages/fragments/src/index.js
index ff3b0d7..0daa463 100644
--- a/packages/fragments/src/index.js
+++ b/packages/fragments/src/index.js
@@ -1,3 +1,4 @@
import defaultVertex from './default.vert';
+import defaultFilterVertex from './defaultFilter.vert';
-export { defaultVertex };
+export { defaultVertex, defaultFilterVertex };
diff --git a/packages/graphics/src/GraphicsRenderer.js b/packages/graphics/src/GraphicsRenderer.js
index 9e61d93..57aae42 100644
--- a/packages/graphics/src/GraphicsRenderer.js
+++ b/packages/graphics/src/GraphicsRenderer.js
@@ -218,8 +218,8 @@
{
webGLData = this.graphicsDataPool.pop()
|| new WebGLGraphicsData(this.renderer.gl,
- this.primitiveShader,
- this.renderer.state.attribsState);
+ this.primitiveShader,
+ this.renderer.state.attribsState);
webGLData.nativeLines = nativeLines;
diff --git a/packages/math/src/shapes/Rectangle.js b/packages/math/src/shapes/Rectangle.js
index 7233a23..af401bd 100644
--- a/packages/math/src/shapes/Rectangle.js
+++ b/packages/math/src/shapes/Rectangle.js
@@ -235,6 +235,16 @@
}
}
+ round(value)
+ {
+ value = value || 1;
+
+ this.x = ((this.x * value) | 0) / value;
+ this.y = ((this.y * value) | 0) / value;
+ this.width = ((this.width * value) | 0) / value;
+ this.height = ((this.height * value) | 0) / value;
+ }
+
/**
* Enlarges this rectangle to include the passed rectangle.
*
diff --git a/packages/mixin-cache-as-bitmap/src/index.js b/packages/mixin-cache-as-bitmap/src/index.js
index 18bcbbe..e63286c 100644
--- a/packages/mixin-cache-as-bitmap/src/index.js
+++ b/packages/mixin-cache-as-bitmap/src/index.js
@@ -173,9 +173,9 @@
const bounds = this.getLocalBounds().clone();
// add some padding!
- if (this._filters)
+ if (this.filters)
{
- const padding = this._filters[0].padding;
+ const padding = this.filters[0].padding;
bounds.pad(padding);
}