UNPKG

12.3 kBJavaScriptView Raw
1import { CLEAR_MODES, DRAW_MODES, MSAA_QUALITY } from '@pixi/constants';
2import { ExtensionType, extensions } from '@pixi/extensions';
3import { Point, Matrix, Rectangle } from '@pixi/math';
4import { RenderTexturePool } from '../renderTexture/RenderTexturePool.mjs';
5import { UniformGroup } from '../shader/UniformGroup.mjs';
6import { Quad } from '../utils/Quad.mjs';
7import { QuadUv } from '../utils/QuadUv.mjs';
8import { FilterState } from './FilterState.mjs';
9
10const tempPoints = [new Point(), new Point(), new Point(), new Point()];
11const tempMatrix = new Matrix();
12class 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}
280FilterSystem.extension = {
281 type: ExtensionType.RendererSystem,
282 name: "filter"
283};
284extensions.add(FilterSystem);
285
286export { FilterSystem };
287//# sourceMappingURL=FilterSystem.mjs.map