1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | var RenderTexturePool = require('../renderTexture/RenderTexturePool.js');
|
6 | var Quad = require('../utils/Quad.js');
|
7 | var QuadUv = require('../utils/QuadUv.js');
|
8 | var math = require('@pixi/math');
|
9 | var UniformGroup = require('../shader/UniformGroup.js');
|
10 | var constants = require('@pixi/constants');
|
11 | var FilterState = require('./FilterState.js');
|
12 | var extensions = require('@pixi/extensions');
|
13 |
|
14 | const tempPoints = [new math.Point(), new math.Point(), new math.Point(), new math.Point()];
|
15 | const tempMatrix = new math.Matrix();
|
16 | class FilterSystem {
|
17 | constructor(renderer) {
|
18 | this.renderer = renderer;
|
19 | this.defaultFilterStack = [{}];
|
20 | this.texturePool = new RenderTexturePool.RenderTexturePool();
|
21 | this.statePool = [];
|
22 | this.quad = new Quad.Quad();
|
23 | this.quadUv = new QuadUv.QuadUv();
|
24 | this.tempRect = new math.Rectangle();
|
25 | this.activeState = {};
|
26 | this.globalUniforms = new UniformGroup.UniformGroup({
|
27 | outputFrame: new math.Rectangle(),
|
28 | inputSize: new Float32Array(4),
|
29 | inputPixel: new Float32Array(4),
|
30 | inputClamp: new Float32Array(4),
|
31 | resolution: 1,
|
32 | filterArea: new Float32Array(4),
|
33 | filterClamp: new Float32Array(4)
|
34 | }, true);
|
35 | this.forceClear = false;
|
36 | this.useMaxPadding = false;
|
37 | }
|
38 | init() {
|
39 | this.texturePool.setScreenSize(this.renderer.view);
|
40 | }
|
41 | push(target, filters) {
|
42 | const renderer = this.renderer;
|
43 | const filterStack = this.defaultFilterStack;
|
44 | const state = this.statePool.pop() || new FilterState.FilterState();
|
45 | const renderTextureSystem = this.renderer.renderTexture;
|
46 | let resolution = filters[0].resolution;
|
47 | let multisample = filters[0].multisample;
|
48 | let padding = filters[0].padding;
|
49 | let autoFit = filters[0].autoFit;
|
50 | let legacy = filters[0].legacy ?? true;
|
51 | for (let i = 1; i < filters.length; i++) {
|
52 | const filter = filters[i];
|
53 | resolution = Math.min(resolution, filter.resolution);
|
54 | multisample = Math.min(multisample, filter.multisample);
|
55 | padding = this.useMaxPadding ? Math.max(padding, filter.padding) : padding + filter.padding;
|
56 | autoFit = autoFit && filter.autoFit;
|
57 | legacy = legacy || (filter.legacy ?? true);
|
58 | }
|
59 | if (filterStack.length === 1) {
|
60 | this.defaultFilterStack[0].renderTexture = renderTextureSystem.current;
|
61 | }
|
62 | filterStack.push(state);
|
63 | state.resolution = resolution;
|
64 | state.multisample = multisample;
|
65 | state.legacy = legacy;
|
66 | state.target = target;
|
67 | state.sourceFrame.copyFrom(target.filterArea || target.getBounds(true));
|
68 | state.sourceFrame.pad(padding);
|
69 | const sourceFrameProjected = this.tempRect.copyFrom(renderTextureSystem.sourceFrame);
|
70 | if (renderer.projection.transform) {
|
71 | this.transformAABB(tempMatrix.copyFrom(renderer.projection.transform).invert(), sourceFrameProjected);
|
72 | }
|
73 | if (autoFit) {
|
74 | state.sourceFrame.fit(sourceFrameProjected);
|
75 | if (state.sourceFrame.width <= 0 || state.sourceFrame.height <= 0) {
|
76 | state.sourceFrame.width = 0;
|
77 | state.sourceFrame.height = 0;
|
78 | }
|
79 | } else if (!state.sourceFrame.intersects(sourceFrameProjected)) {
|
80 | state.sourceFrame.width = 0;
|
81 | state.sourceFrame.height = 0;
|
82 | }
|
83 | this.roundFrame(state.sourceFrame, renderTextureSystem.current ? renderTextureSystem.current.resolution : renderer.resolution, renderTextureSystem.sourceFrame, renderTextureSystem.destinationFrame, renderer.projection.transform);
|
84 | state.renderTexture = this.getOptimalFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution, multisample);
|
85 | state.filters = filters;
|
86 | state.destinationFrame.width = state.renderTexture.width;
|
87 | state.destinationFrame.height = state.renderTexture.height;
|
88 | const destinationFrame = this.tempRect;
|
89 | destinationFrame.x = 0;
|
90 | destinationFrame.y = 0;
|
91 | destinationFrame.width = state.sourceFrame.width;
|
92 | destinationFrame.height = state.sourceFrame.height;
|
93 | state.renderTexture.filterFrame = state.sourceFrame;
|
94 | state.bindingSourceFrame.copyFrom(renderTextureSystem.sourceFrame);
|
95 | state.bindingDestinationFrame.copyFrom(renderTextureSystem.destinationFrame);
|
96 | state.transform = renderer.projection.transform;
|
97 | renderer.projection.transform = null;
|
98 | renderTextureSystem.bind(state.renderTexture, state.sourceFrame, destinationFrame);
|
99 | renderer.framebuffer.clear(0, 0, 0, 0);
|
100 | }
|
101 | pop() {
|
102 | const filterStack = this.defaultFilterStack;
|
103 | const state = filterStack.pop();
|
104 | const filters = state.filters;
|
105 | this.activeState = state;
|
106 | const globalUniforms = this.globalUniforms.uniforms;
|
107 | globalUniforms.outputFrame = state.sourceFrame;
|
108 | globalUniforms.resolution = state.resolution;
|
109 | const inputSize = globalUniforms.inputSize;
|
110 | const inputPixel = globalUniforms.inputPixel;
|
111 | const inputClamp = globalUniforms.inputClamp;
|
112 | inputSize[0] = state.destinationFrame.width;
|
113 | inputSize[1] = state.destinationFrame.height;
|
114 | inputSize[2] = 1 / inputSize[0];
|
115 | inputSize[3] = 1 / inputSize[1];
|
116 | inputPixel[0] = Math.round(inputSize[0] * state.resolution);
|
117 | inputPixel[1] = Math.round(inputSize[1] * state.resolution);
|
118 | inputPixel[2] = 1 / inputPixel[0];
|
119 | inputPixel[3] = 1 / inputPixel[1];
|
120 | inputClamp[0] = 0.5 * inputPixel[2];
|
121 | inputClamp[1] = 0.5 * inputPixel[3];
|
122 | inputClamp[2] = state.sourceFrame.width * inputSize[2] - 0.5 * inputPixel[2];
|
123 | inputClamp[3] = state.sourceFrame.height * inputSize[3] - 0.5 * inputPixel[3];
|
124 | if (state.legacy) {
|
125 | const filterArea = globalUniforms.filterArea;
|
126 | filterArea[0] = state.destinationFrame.width;
|
127 | filterArea[1] = state.destinationFrame.height;
|
128 | filterArea[2] = state.sourceFrame.x;
|
129 | filterArea[3] = state.sourceFrame.y;
|
130 | globalUniforms.filterClamp = globalUniforms.inputClamp;
|
131 | }
|
132 | this.globalUniforms.update();
|
133 | const lastState = filterStack[filterStack.length - 1];
|
134 | this.renderer.framebuffer.blit();
|
135 | if (filters.length === 1) {
|
136 | filters[0].apply(this, state.renderTexture, lastState.renderTexture, constants.CLEAR_MODES.BLEND, state);
|
137 | this.returnFilterTexture(state.renderTexture);
|
138 | } else {
|
139 | let flip = state.renderTexture;
|
140 | let flop = this.getOptimalFilterTexture(flip.width, flip.height, state.resolution);
|
141 | flop.filterFrame = flip.filterFrame;
|
142 | let i = 0;
|
143 | for (i = 0; i < filters.length - 1; ++i) {
|
144 | if (i === 1 && state.multisample > 1) {
|
145 | flop = this.getOptimalFilterTexture(flip.width, flip.height, state.resolution);
|
146 | flop.filterFrame = flip.filterFrame;
|
147 | }
|
148 | filters[i].apply(this, flip, flop, constants.CLEAR_MODES.CLEAR, state);
|
149 | const t = flip;
|
150 | flip = flop;
|
151 | flop = t;
|
152 | }
|
153 | filters[i].apply(this, flip, lastState.renderTexture, constants.CLEAR_MODES.BLEND, state);
|
154 | if (i > 1 && state.multisample > 1) {
|
155 | this.returnFilterTexture(state.renderTexture);
|
156 | }
|
157 | this.returnFilterTexture(flip);
|
158 | this.returnFilterTexture(flop);
|
159 | }
|
160 | state.clear();
|
161 | this.statePool.push(state);
|
162 | }
|
163 | bindAndClear(filterTexture, clearMode = constants.CLEAR_MODES.CLEAR) {
|
164 | const {
|
165 | renderTexture: renderTextureSystem,
|
166 | state: stateSystem
|
167 | } = this.renderer;
|
168 | if (filterTexture === this.defaultFilterStack[this.defaultFilterStack.length - 1].renderTexture) {
|
169 | this.renderer.projection.transform = this.activeState.transform;
|
170 | } else {
|
171 | this.renderer.projection.transform = null;
|
172 | }
|
173 | if (filterTexture?.filterFrame) {
|
174 | const destinationFrame = this.tempRect;
|
175 | destinationFrame.x = 0;
|
176 | destinationFrame.y = 0;
|
177 | destinationFrame.width = filterTexture.filterFrame.width;
|
178 | destinationFrame.height = filterTexture.filterFrame.height;
|
179 | renderTextureSystem.bind(filterTexture, filterTexture.filterFrame, destinationFrame);
|
180 | } else if (filterTexture !== this.defaultFilterStack[this.defaultFilterStack.length - 1].renderTexture) {
|
181 | renderTextureSystem.bind(filterTexture);
|
182 | } else {
|
183 | this.renderer.renderTexture.bind(filterTexture, this.activeState.bindingSourceFrame, this.activeState.bindingDestinationFrame);
|
184 | }
|
185 | const autoClear = stateSystem.stateId & 1 || this.forceClear;
|
186 | if (clearMode === constants.CLEAR_MODES.CLEAR || clearMode === constants.CLEAR_MODES.BLIT && autoClear) {
|
187 | this.renderer.framebuffer.clear(0, 0, 0, 0);
|
188 | }
|
189 | }
|
190 | applyFilter(filter, input, output, clearMode) {
|
191 | const renderer = this.renderer;
|
192 | renderer.state.set(filter.state);
|
193 | this.bindAndClear(output, clearMode);
|
194 | filter.uniforms.uSampler = input;
|
195 | filter.uniforms.filterGlobals = this.globalUniforms;
|
196 | renderer.shader.bind(filter);
|
197 | filter.legacy = !!filter.program.attributeData.aTextureCoord;
|
198 | if (filter.legacy) {
|
199 | this.quadUv.map(input._frame, input.filterFrame);
|
200 | renderer.geometry.bind(this.quadUv);
|
201 | renderer.geometry.draw(constants.DRAW_MODES.TRIANGLES);
|
202 | } else {
|
203 | renderer.geometry.bind(this.quad);
|
204 | renderer.geometry.draw(constants.DRAW_MODES.TRIANGLE_STRIP);
|
205 | }
|
206 | }
|
207 | calculateSpriteMatrix(outputMatrix, sprite) {
|
208 | const { sourceFrame, destinationFrame } = this.activeState;
|
209 | const { orig } = sprite._texture;
|
210 | const mappedMatrix = outputMatrix.set(destinationFrame.width, 0, 0, destinationFrame.height, sourceFrame.x, sourceFrame.y);
|
211 | const worldTransform = sprite.worldTransform.copyTo(math.Matrix.TEMP_MATRIX);
|
212 | worldTransform.invert();
|
213 | mappedMatrix.prepend(worldTransform);
|
214 | mappedMatrix.scale(1 / orig.width, 1 / orig.height);
|
215 | mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y);
|
216 | return mappedMatrix;
|
217 | }
|
218 | destroy() {
|
219 | this.renderer = null;
|
220 | this.texturePool.clear(false);
|
221 | }
|
222 | getOptimalFilterTexture(minWidth, minHeight, resolution = 1, multisample = constants.MSAA_QUALITY.NONE) {
|
223 | return this.texturePool.getOptimalTexture(minWidth, minHeight, resolution, multisample);
|
224 | }
|
225 | getFilterTexture(input, resolution, multisample) {
|
226 | if (typeof input === "number") {
|
227 | const swap = input;
|
228 | input = resolution;
|
229 | resolution = swap;
|
230 | }
|
231 | input = input || this.activeState.renderTexture;
|
232 | const filterTexture = this.texturePool.getOptimalTexture(input.width, input.height, resolution || input.resolution, multisample || constants.MSAA_QUALITY.NONE);
|
233 | filterTexture.filterFrame = input.filterFrame;
|
234 | return filterTexture;
|
235 | }
|
236 | returnFilterTexture(renderTexture) {
|
237 | this.texturePool.returnTexture(renderTexture);
|
238 | }
|
239 | emptyPool() {
|
240 | this.texturePool.clear(true);
|
241 | }
|
242 | resize() {
|
243 | this.texturePool.setScreenSize(this.renderer.view);
|
244 | }
|
245 | transformAABB(matrix, rect) {
|
246 | const lt = tempPoints[0];
|
247 | const lb = tempPoints[1];
|
248 | const rt = tempPoints[2];
|
249 | const rb = tempPoints[3];
|
250 | lt.set(rect.left, rect.top);
|
251 | lb.set(rect.left, rect.bottom);
|
252 | rt.set(rect.right, rect.top);
|
253 | rb.set(rect.right, rect.bottom);
|
254 | matrix.apply(lt, lt);
|
255 | matrix.apply(lb, lb);
|
256 | matrix.apply(rt, rt);
|
257 | matrix.apply(rb, rb);
|
258 | const x0 = Math.min(lt.x, lb.x, rt.x, rb.x);
|
259 | const y0 = Math.min(lt.y, lb.y, rt.y, rb.y);
|
260 | const x1 = Math.max(lt.x, lb.x, rt.x, rb.x);
|
261 | const y1 = Math.max(lt.y, lb.y, rt.y, rb.y);
|
262 | rect.x = x0;
|
263 | rect.y = y0;
|
264 | rect.width = x1 - x0;
|
265 | rect.height = y1 - y0;
|
266 | }
|
267 | roundFrame(frame, resolution, bindingSourceFrame, bindingDestinationFrame, transform) {
|
268 | if (frame.width <= 0 || frame.height <= 0 || bindingSourceFrame.width <= 0 || bindingSourceFrame.height <= 0) {
|
269 | return;
|
270 | }
|
271 | if (transform) {
|
272 | const { a, b, c, d } = transform;
|
273 | if ((Math.abs(b) > 1e-4 || Math.abs(c) > 1e-4) && (Math.abs(a) > 1e-4 || Math.abs(d) > 1e-4)) {
|
274 | return;
|
275 | }
|
276 | }
|
277 | transform = transform ? tempMatrix.copyFrom(transform) : tempMatrix.identity();
|
278 | transform.translate(-bindingSourceFrame.x, -bindingSourceFrame.y).scale(bindingDestinationFrame.width / bindingSourceFrame.width, bindingDestinationFrame.height / bindingSourceFrame.height).translate(bindingDestinationFrame.x, bindingDestinationFrame.y);
|
279 | this.transformAABB(transform, frame);
|
280 | frame.ceil(resolution);
|
281 | this.transformAABB(transform.invert(), frame);
|
282 | }
|
283 | }
|
284 | FilterSystem.extension = {
|
285 | type: extensions.ExtensionType.RendererSystem,
|
286 | name: "filter"
|
287 | };
|
288 | extensions.extensions.add(FilterSystem);
|
289 |
|
290 | exports.FilterSystem = FilterSystem;
|
291 |
|