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