@pixi/core
Version:
Core PixiJS
288 lines (285 loc) • 12.3 kB
JavaScript
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