UNPKG

14.7 kBJavaScriptView Raw
1"use strict";
2var constants = require("@pixi/constants"), extensions = require("@pixi/extensions"), math = require("@pixi/math"), RenderTexturePool = require("../renderTexture/RenderTexturePool.js"), UniformGroup = require("../shader/UniformGroup.js"), Quad = require("../utils/Quad.js"), QuadUv = require("../utils/QuadUv.js"), FilterState = require("./FilterState.js");
3const tempPoints = [new math.Point(), new math.Point(), new math.Point(), new math.Point()], tempMatrix = new math.Matrix();
4class FilterSystem {
5 /**
6 * @param renderer - The renderer this System works for.
7 */
8 constructor(renderer) {
9 this.renderer = renderer, this.defaultFilterStack = [{}], this.texturePool = new RenderTexturePool.RenderTexturePool(), this.statePool = [], this.quad = new Quad.Quad(), this.quadUv = new QuadUv.QuadUv(), this.tempRect = new math.Rectangle(), this.activeState = {}, this.globalUniforms = new UniformGroup.UniformGroup({
10 outputFrame: new math.Rectangle(),
11 inputSize: new Float32Array(4),
12 inputPixel: new Float32Array(4),
13 inputClamp: new Float32Array(4),
14 resolution: 1,
15 // legacy variables
16 filterArea: new Float32Array(4),
17 filterClamp: new Float32Array(4)
18 }, !0), this.forceClear = !1, this.useMaxPadding = !1;
19 }
20 init() {
21 this.texturePool.setScreenSize(this.renderer.view);
22 }
23 /**
24 * Pushes a set of filters to be applied later to the system. This will redirect further rendering into an
25 * input render-texture for the rest of the filtering pipeline.
26 * @param {PIXI.DisplayObject} target - The target of the filter to render.
27 * @param filters - The filters to apply.
28 */
29 push(target, filters) {
30 const renderer = this.renderer, filterStack = this.defaultFilterStack, state = this.statePool.pop() || new FilterState.FilterState(), renderTextureSystem = renderer.renderTexture;
31 let currentResolution, currentMultisample;
32 if (renderTextureSystem.current) {
33 const renderTexture = renderTextureSystem.current;
34 currentResolution = renderTexture.resolution, currentMultisample = renderTexture.multisample;
35 } else
36 currentResolution = renderer.resolution, currentMultisample = renderer.multisample;
37 let resolution = filters[0].resolution || currentResolution, multisample = filters[0].multisample ?? currentMultisample, padding = filters[0].padding, autoFit = filters[0].autoFit, legacy = filters[0].legacy ?? !0;
38 for (let i = 1; i < filters.length; i++) {
39 const filter = filters[i];
40 resolution = Math.min(resolution, filter.resolution || currentResolution), multisample = Math.min(multisample, filter.multisample ?? currentMultisample), padding = this.useMaxPadding ? Math.max(padding, filter.padding) : padding + filter.padding, autoFit = autoFit && filter.autoFit, legacy = legacy || (filter.legacy ?? !0);
41 }
42 filterStack.length === 1 && (this.defaultFilterStack[0].renderTexture = renderTextureSystem.current), filterStack.push(state), state.resolution = resolution, state.multisample = multisample, state.legacy = legacy, state.target = target, state.sourceFrame.copyFrom(target.filterArea || target.getBounds(!0)), state.sourceFrame.pad(padding);
43 const sourceFrameProjected = this.tempRect.copyFrom(renderTextureSystem.sourceFrame);
44 renderer.projection.transform && this.transformAABB(
45 tempMatrix.copyFrom(renderer.projection.transform).invert(),
46 sourceFrameProjected
47 ), autoFit ? (state.sourceFrame.fit(sourceFrameProjected), (state.sourceFrame.width <= 0 || state.sourceFrame.height <= 0) && (state.sourceFrame.width = 0, state.sourceFrame.height = 0)) : state.sourceFrame.intersects(sourceFrameProjected) || (state.sourceFrame.width = 0, state.sourceFrame.height = 0), this.roundFrame(
48 state.sourceFrame,
49 renderTextureSystem.current ? renderTextureSystem.current.resolution : renderer.resolution,
50 renderTextureSystem.sourceFrame,
51 renderTextureSystem.destinationFrame,
52 renderer.projection.transform
53 ), state.renderTexture = this.getOptimalFilterTexture(
54 state.sourceFrame.width,
55 state.sourceFrame.height,
56 resolution,
57 multisample
58 ), state.filters = filters, state.destinationFrame.width = state.renderTexture.width, state.destinationFrame.height = state.renderTexture.height;
59 const destinationFrame = this.tempRect;
60 destinationFrame.x = 0, destinationFrame.y = 0, destinationFrame.width = state.sourceFrame.width, destinationFrame.height = state.sourceFrame.height, state.renderTexture.filterFrame = state.sourceFrame, state.bindingSourceFrame.copyFrom(renderTextureSystem.sourceFrame), state.bindingDestinationFrame.copyFrom(renderTextureSystem.destinationFrame), state.transform = renderer.projection.transform, renderer.projection.transform = null, renderTextureSystem.bind(state.renderTexture, state.sourceFrame, destinationFrame), renderer.framebuffer.clear(0, 0, 0, 0);
61 }
62 /** Pops off the filter and applies it. */
63 pop() {
64 const filterStack = this.defaultFilterStack, state = filterStack.pop(), filters = state.filters;
65 this.activeState = state;
66 const globalUniforms = this.globalUniforms.uniforms;
67 globalUniforms.outputFrame = state.sourceFrame, globalUniforms.resolution = state.resolution;
68 const inputSize = globalUniforms.inputSize, inputPixel = globalUniforms.inputPixel, inputClamp = globalUniforms.inputClamp;
69 if (inputSize[0] = state.destinationFrame.width, inputSize[1] = state.destinationFrame.height, inputSize[2] = 1 / inputSize[0], inputSize[3] = 1 / inputSize[1], inputPixel[0] = Math.round(inputSize[0] * state.resolution), inputPixel[1] = Math.round(inputSize[1] * state.resolution), inputPixel[2] = 1 / inputPixel[0], inputPixel[3] = 1 / inputPixel[1], inputClamp[0] = 0.5 * inputPixel[2], inputClamp[1] = 0.5 * inputPixel[3], inputClamp[2] = state.sourceFrame.width * inputSize[2] - 0.5 * inputPixel[2], inputClamp[3] = state.sourceFrame.height * inputSize[3] - 0.5 * inputPixel[3], state.legacy) {
70 const filterArea = globalUniforms.filterArea;
71 filterArea[0] = state.destinationFrame.width, filterArea[1] = state.destinationFrame.height, filterArea[2] = state.sourceFrame.x, filterArea[3] = state.sourceFrame.y, globalUniforms.filterClamp = globalUniforms.inputClamp;
72 }
73 this.globalUniforms.update();
74 const lastState = filterStack[filterStack.length - 1];
75 if (this.renderer.framebuffer.blit(), filters.length === 1)
76 filters[0].apply(this, state.renderTexture, lastState.renderTexture, constants.CLEAR_MODES.BLEND, state), this.returnFilterTexture(state.renderTexture);
77 else {
78 let flip = state.renderTexture, flop = this.getOptimalFilterTexture(
79 flip.width,
80 flip.height,
81 state.resolution
82 );
83 flop.filterFrame = flip.filterFrame;
84 let i = 0;
85 for (i = 0; i < filters.length - 1; ++i) {
86 i === 1 && state.multisample > 1 && (flop = this.getOptimalFilterTexture(
87 flip.width,
88 flip.height,
89 state.resolution
90 ), flop.filterFrame = flip.filterFrame), filters[i].apply(this, flip, flop, constants.CLEAR_MODES.CLEAR, state);
91 const t = flip;
92 flip = flop, flop = t;
93 }
94 filters[i].apply(this, flip, lastState.renderTexture, constants.CLEAR_MODES.BLEND, state), i > 1 && state.multisample > 1 && this.returnFilterTexture(state.renderTexture), this.returnFilterTexture(flip), this.returnFilterTexture(flop);
95 }
96 state.clear(), this.statePool.push(state);
97 }
98 /**
99 * Binds a renderTexture with corresponding `filterFrame`, clears it if mode corresponds.
100 * @param filterTexture - renderTexture to bind, should belong to filter pool or filter stack
101 * @param clearMode - clearMode, by default its CLEAR/YES. See {@link PIXI.CLEAR_MODES}
102 */
103 bindAndClear(filterTexture, clearMode = constants.CLEAR_MODES.CLEAR) {
104 const {
105 renderTexture: renderTextureSystem,
106 state: stateSystem
107 } = this.renderer;
108 if (filterTexture === this.defaultFilterStack[this.defaultFilterStack.length - 1].renderTexture ? this.renderer.projection.transform = this.activeState.transform : this.renderer.projection.transform = null, filterTexture?.filterFrame) {
109 const destinationFrame = this.tempRect;
110 destinationFrame.x = 0, destinationFrame.y = 0, destinationFrame.width = filterTexture.filterFrame.width, destinationFrame.height = filterTexture.filterFrame.height, renderTextureSystem.bind(filterTexture, filterTexture.filterFrame, destinationFrame);
111 } else
112 filterTexture !== this.defaultFilterStack[this.defaultFilterStack.length - 1].renderTexture ? renderTextureSystem.bind(filterTexture) : this.renderer.renderTexture.bind(
113 filterTexture,
114 this.activeState.bindingSourceFrame,
115 this.activeState.bindingDestinationFrame
116 );
117 const autoClear = stateSystem.stateId & 1 || this.forceClear;
118 (clearMode === constants.CLEAR_MODES.CLEAR || clearMode === constants.CLEAR_MODES.BLIT && autoClear) && this.renderer.framebuffer.clear(0, 0, 0, 0);
119 }
120 /**
121 * Draws a filter using the default rendering process.
122 *
123 * This should be called only by {@link PIXI.Filter#apply}.
124 * @param filter - The filter to draw.
125 * @param input - The input render target.
126 * @param output - The target to output to.
127 * @param clearMode - Should the output be cleared before rendering to it
128 */
129 applyFilter(filter, input, output, clearMode) {
130 const renderer = this.renderer;
131 renderer.state.set(filter.state), this.bindAndClear(output, clearMode), filter.uniforms.uSampler = input, filter.uniforms.filterGlobals = this.globalUniforms, renderer.shader.bind(filter), filter.legacy = !!filter.program.attributeData.aTextureCoord, filter.legacy ? (this.quadUv.map(input._frame, input.filterFrame), renderer.geometry.bind(this.quadUv), renderer.geometry.draw(constants.DRAW_MODES.TRIANGLES)) : (renderer.geometry.bind(this.quad), renderer.geometry.draw(constants.DRAW_MODES.TRIANGLE_STRIP));
132 }
133 /**
134 * Multiply _input normalized coordinates_ to this matrix to get _sprite texture normalized coordinates_.
135 *
136 * Use `outputMatrix * vTextureCoord` in the shader.
137 * @param outputMatrix - The matrix to output to.
138 * @param {PIXI.Sprite} sprite - The sprite to map to.
139 * @returns The mapped matrix.
140 */
141 calculateSpriteMatrix(outputMatrix, sprite) {
142 const { sourceFrame, destinationFrame } = this.activeState, { orig } = sprite._texture, mappedMatrix = outputMatrix.set(
143 destinationFrame.width,
144 0,
145 0,
146 destinationFrame.height,
147 sourceFrame.x,
148 sourceFrame.y
149 ), worldTransform = sprite.worldTransform.copyTo(math.Matrix.TEMP_MATRIX);
150 return worldTransform.invert(), mappedMatrix.prepend(worldTransform), mappedMatrix.scale(1 / orig.width, 1 / orig.height), mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y), mappedMatrix;
151 }
152 /** Destroys this Filter System. */
153 destroy() {
154 this.renderer = null, this.texturePool.clear(!1);
155 }
156 /**
157 * Gets a Power-of-Two render texture or fullScreen texture
158 * @param minWidth - The minimum width of the render texture in real pixels.
159 * @param minHeight - The minimum height of the render texture in real pixels.
160 * @param resolution - The resolution of the render texture.
161 * @param multisample - Number of samples of the render texture.
162 * @returns - The new render texture.
163 */
164 getOptimalFilterTexture(minWidth, minHeight, resolution = 1, multisample = constants.MSAA_QUALITY.NONE) {
165 return this.texturePool.getOptimalTexture(minWidth, minHeight, resolution, multisample);
166 }
167 /**
168 * Gets extra render texture to use inside current filter
169 * To be compliant with older filters, you can use params in any order
170 * @param input - renderTexture from which size and resolution will be copied
171 * @param resolution - override resolution of the renderTexture
172 * @param multisample - number of samples of the renderTexture
173 */
174 getFilterTexture(input, resolution, multisample) {
175 if (typeof input == "number") {
176 const swap = input;
177 input = resolution, resolution = swap;
178 }
179 input = input || this.activeState.renderTexture;
180 const filterTexture = this.texturePool.getOptimalTexture(
181 input.width,
182 input.height,
183 resolution || input.resolution,
184 multisample || constants.MSAA_QUALITY.NONE
185 );
186 return filterTexture.filterFrame = input.filterFrame, filterTexture;
187 }
188 /**
189 * Frees a render texture back into the pool.
190 * @param renderTexture - The renderTarget to free
191 */
192 returnFilterTexture(renderTexture) {
193 this.texturePool.returnTexture(renderTexture);
194 }
195 /** Empties the texture pool. */
196 emptyPool() {
197 this.texturePool.clear(!0);
198 }
199 /** Calls `texturePool.resize()`, affects fullScreen renderTextures. */
200 resize() {
201 this.texturePool.setScreenSize(this.renderer.view);
202 }
203 /**
204 * @param matrix - first param
205 * @param rect - second param
206 */
207 transformAABB(matrix, rect) {
208 const lt = tempPoints[0], lb = tempPoints[1], rt = tempPoints[2], rb = tempPoints[3];
209 lt.set(rect.left, rect.top), lb.set(rect.left, rect.bottom), rt.set(rect.right, rect.top), rb.set(rect.right, rect.bottom), matrix.apply(lt, lt), matrix.apply(lb, lb), matrix.apply(rt, rt), matrix.apply(rb, rb);
210 const x0 = Math.min(lt.x, lb.x, rt.x, rb.x), y0 = Math.min(lt.y, lb.y, rt.y, rb.y), x1 = Math.max(lt.x, lb.x, rt.x, rb.x), y1 = Math.max(lt.y, lb.y, rt.y, rb.y);
211 rect.x = x0, rect.y = y0, rect.width = x1 - x0, rect.height = y1 - y0;
212 }
213 roundFrame(frame, resolution, bindingSourceFrame, bindingDestinationFrame, transform) {
214 if (!(frame.width <= 0 || frame.height <= 0 || bindingSourceFrame.width <= 0 || bindingSourceFrame.height <= 0)) {
215 if (transform) {
216 const { a, b, c, d } = transform;
217 if ((Math.abs(b) > 1e-4 || Math.abs(c) > 1e-4) && (Math.abs(a) > 1e-4 || Math.abs(d) > 1e-4))
218 return;
219 }
220 transform = transform ? tempMatrix.copyFrom(transform) : tempMatrix.identity(), transform.translate(-bindingSourceFrame.x, -bindingSourceFrame.y).scale(
221 bindingDestinationFrame.width / bindingSourceFrame.width,
222 bindingDestinationFrame.height / bindingSourceFrame.height
223 ).translate(bindingDestinationFrame.x, bindingDestinationFrame.y), this.transformAABB(transform, frame), frame.ceil(resolution), this.transformAABB(transform.invert(), frame);
224 }
225 }
226}
227FilterSystem.extension = {
228 type: extensions.ExtensionType.RendererSystem,
229 name: "filter"
230};
231extensions.extensions.add(FilterSystem);
232exports.FilterSystem = FilterSystem;
233//# sourceMappingURL=FilterSystem.js.map