UNPKG

@pixi/core

Version:
288 lines (285 loc) 12.3 kB
import { CLEAR_MODES, DRAW_MODES, MSAA_QUALITY } from '@pixi/constants'; import { ExtensionType, extensions } from '@pixi/extensions'; import { Point, Matrix, Rectangle } from '@pixi/math'; import { RenderTexturePool } from '../renderTexture/RenderTexturePool.mjs'; import { UniformGroup } from '../shader/UniformGroup.mjs'; import { Quad } from '../utils/Quad.mjs'; import { QuadUv } from '../utils/QuadUv.mjs'; import { FilterState } from './FilterState.mjs'; const tempPoints = [new Point(), new Point(), new Point(), new Point()]; const tempMatrix = new Matrix(); class FilterSystem { constructor(renderer) { this.renderer = renderer; this.defaultFilterStack = [{}]; this.texturePool = new RenderTexturePool(); this.statePool = []; this.quad = new Quad(); this.quadUv = new QuadUv(); this.tempRect = new Rectangle(); this.activeState = {}; this.globalUniforms = new UniformGroup({ outputFrame: new Rectangle(), inputSize: new Float32Array(4), inputPixel: new Float32Array(4), inputClamp: new Float32Array(4), resolution: 1, filterArea: new Float32Array(4), filterClamp: new Float32Array(4) }, true); this.forceClear = false; this.useMaxPadding = false; } init() { this.texturePool.setScreenSize(this.renderer.view); } push(target, filters) { const renderer = this.renderer; const filterStack = this.defaultFilterStack; const state = this.statePool.pop() || new FilterState(); const renderTextureSystem = this.renderer.renderTexture; let resolution = filters[0].resolution; let multisample = filters[0].multisample; let padding = filters[0].padding; let autoFit = filters[0].autoFit; let legacy = filters[0].legacy ?? true; for (let i = 1; i < filters.length; i++) { const filter = filters[i]; resolution = Math.min(resolution, filter.resolution); multisample = Math.min(multisample, filter.multisample); padding = this.useMaxPadding ? Math.max(padding, filter.padding) : padding + filter.padding; autoFit = autoFit && filter.autoFit; legacy = legacy || (filter.legacy ?? true); } if (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(true)); state.sourceFrame.pad(padding); const sourceFrameProjected = this.tempRect.copyFrom(renderTextureSystem.sourceFrame); if (renderer.projection.transform) { this.transformAABB(tempMatrix.copyFrom(renderer.projection.transform).invert(), sourceFrameProjected); } if (autoFit) { state.sourceFrame.fit(sourceFrameProjected); if (state.sourceFrame.width <= 0 || state.sourceFrame.height <= 0) { state.sourceFrame.width = 0; state.sourceFrame.height = 0; } } else if (!state.sourceFrame.intersects(sourceFrameProjected)) { state.sourceFrame.width = 0; state.sourceFrame.height = 0; } this.roundFrame(state.sourceFrame, renderTextureSystem.current ? renderTextureSystem.current.resolution : renderer.resolution, renderTextureSystem.sourceFrame, renderTextureSystem.destinationFrame, renderer.projection.transform); state.renderTexture = this.getOptimalFilterTexture(state.sourceFrame.width, state.sourceFrame.height, resolution, multisample); state.filters = filters; state.destinationFrame.width = state.renderTexture.width; state.destinationFrame.height = state.renderTexture.height; const destinationFrame = this.tempRect; 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); } pop() { const filterStack = this.defaultFilterStack; const state = filterStack.pop(); const filters = state.filters; this.activeState = state; const globalUniforms = this.globalUniforms.uniforms; globalUniforms.outputFrame = state.sourceFrame; globalUniforms.resolution = state.resolution; const inputSize = globalUniforms.inputSize; const inputPixel = globalUniforms.inputPixel; const inputClamp = globalUniforms.inputClamp; 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]; if (state.legacy) { const filterArea = globalUniforms.filterArea; filterArea[0] = state.destinationFrame.width; filterArea[1] = state.destinationFrame.height; filterArea[2] = state.sourceFrame.x; filterArea[3] = state.sourceFrame.y; globalUniforms.filterClamp = globalUniforms.inputClamp; } this.globalUniforms.update(); const lastState = filterStack[filterStack.length - 1]; this.renderer.framebuffer.blit(); if (filters.length === 1) { filters[0].apply(this, state.renderTexture, lastState.renderTexture, CLEAR_MODES.BLEND, state); this.returnFilterTexture(state.renderTexture); } else { let flip = state.renderTexture; let flop = this.getOptimalFilterTexture(flip.width, flip.height, state.resolution); flop.filterFrame = flip.filterFrame; let i = 0; for (i = 0; i < filters.length - 1; ++i) { if (i === 1 && state.multisample > 1) { flop = this.getOptimalFilterTexture(flip.width, flip.height, state.resolution); flop.filterFrame = flip.filterFrame; } filters[i].apply(this, flip, flop, CLEAR_MODES.CLEAR, state); const t = flip; flip = flop; flop = t; } filters[i].apply(this, flip, lastState.renderTexture, CLEAR_MODES.BLEND, state); if (i > 1 && state.multisample > 1) { this.returnFilterTexture(state.renderTexture); } this.returnFilterTexture(flip); this.returnFilterTexture(flop); } state.clear(); this.statePool.push(state); } bindAndClear(filterTexture, clearMode = CLEAR_MODES.CLEAR) { const { renderTexture: renderTextureSystem, state: stateSystem } = this.renderer; if (filterTexture === this.defaultFilterStack[this.defaultFilterStack.length - 1].renderTexture) { this.renderer.projection.transform = this.activeState.transform; } else { this.renderer.projection.transform = null; } if (filterTexture?.filterFrame) { const destinationFrame = this.tempRect; destinationFrame.x = 0; destinationFrame.y = 0; destinationFrame.width = filterTexture.filterFrame.width; destinationFrame.height = filterTexture.filterFrame.height; renderTextureSystem.bind(filterTexture, filterTexture.filterFrame, destinationFrame); } else if (filterTexture !== this.defaultFilterStack[this.defaultFilterStack.length - 1].renderTexture) { renderTextureSystem.bind(filterTexture); } else { this.renderer.renderTexture.bind(filterTexture, this.activeState.bindingSourceFrame, this.activeState.bindingDestinationFrame); } const autoClear = stateSystem.stateId & 1 || this.forceClear; if (clearMode === CLEAR_MODES.CLEAR || clearMode === CLEAR_MODES.BLIT && autoClear) { this.renderer.framebuffer.clear(0, 0, 0, 0); } } applyFilter(filter, input, output, clearMode) { const renderer = this.renderer; 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; if (filter.legacy) { this.quadUv.map(input._frame, input.filterFrame); renderer.geometry.bind(this.quadUv); renderer.geometry.draw(DRAW_MODES.TRIANGLES); } else { renderer.geometry.bind(this.quad); renderer.geometry.draw(DRAW_MODES.TRIANGLE_STRIP); } } calculateSpriteMatrix(outputMatrix, sprite) { const { sourceFrame, destinationFrame } = this.activeState; const { orig } = sprite._texture; const mappedMatrix = outputMatrix.set(destinationFrame.width, 0, 0, destinationFrame.height, sourceFrame.x, sourceFrame.y); const worldTransform = sprite.worldTransform.copyTo(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); mappedMatrix.scale(1 / orig.width, 1 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; } destroy() { this.renderer = null; this.texturePool.clear(false); } getOptimalFilterTexture(minWidth, minHeight, resolution = 1, multisample = MSAA_QUALITY.NONE) { return this.texturePool.getOptimalTexture(minWidth, minHeight, resolution, multisample); } getFilterTexture(input, resolution, multisample) { if (typeof input === "number") { const swap = input; input = resolution; resolution = swap; } input = input || this.activeState.renderTexture; const filterTexture = this.texturePool.getOptimalTexture(input.width, input.height, resolution || input.resolution, multisample || MSAA_QUALITY.NONE); filterTexture.filterFrame = input.filterFrame; return filterTexture; } returnFilterTexture(renderTexture) { this.texturePool.returnTexture(renderTexture); } emptyPool() { this.texturePool.clear(true); } resize() { this.texturePool.setScreenSize(this.renderer.view); } transformAABB(matrix, rect) { const lt = tempPoints[0]; const lb = tempPoints[1]; const rt = tempPoints[2]; const rb = tempPoints[3]; 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); const x0 = Math.min(lt.x, lb.x, rt.x, rb.x); const y0 = Math.min(lt.y, lb.y, rt.y, rb.y); const x1 = Math.max(lt.x, lb.x, rt.x, rb.x); const y1 = Math.max(lt.y, lb.y, rt.y, rb.y); rect.x = x0; rect.y = y0; rect.width = x1 - x0; rect.height = y1 - y0; } roundFrame(frame, resolution, bindingSourceFrame, bindingDestinationFrame, transform) { if (frame.width <= 0 || frame.height <= 0 || bindingSourceFrame.width <= 0 || bindingSourceFrame.height <= 0) { return; } if (transform) { const { a, b, c, d } = transform; if ((Math.abs(b) > 1e-4 || Math.abs(c) > 1e-4) && (Math.abs(a) > 1e-4 || Math.abs(d) > 1e-4)) { return; } } transform = transform ? tempMatrix.copyFrom(transform) : tempMatrix.identity(); transform.translate(-bindingSourceFrame.x, -bindingSourceFrame.y).scale(bindingDestinationFrame.width / bindingSourceFrame.width, bindingDestinationFrame.height / bindingSourceFrame.height).translate(bindingDestinationFrame.x, bindingDestinationFrame.y); this.transformAABB(transform, frame); frame.ceil(resolution); this.transformAABB(transform.invert(), frame); } } FilterSystem.extension = { type: ExtensionType.RendererSystem, name: "filter" }; extensions.add(FilterSystem); export { FilterSystem }; //# sourceMappingURL=FilterSystem.mjs.map