pixi.js
Version:
PixiJS — The HTML5 Creation Engine =============
344 lines (340 loc) • 13 kB
JavaScript
'use strict';
var Extensions = require('../extensions/Extensions.js');
var Matrix = require('../maths/matrix/Matrix.js');
var Point = require('../maths/point/Point.js');
var BindGroup = require('../rendering/renderers/gpu/shader/BindGroup.js');
var Geometry = require('../rendering/renderers/shared/geometry/Geometry.js');
var UniformGroup = require('../rendering/renderers/shared/shader/UniformGroup.js');
var Texture = require('../rendering/renderers/shared/texture/Texture.js');
var TexturePool = require('../rendering/renderers/shared/texture/TexturePool.js');
var types = require('../rendering/renderers/types.js');
var Bounds = require('../scene/container/bounds/Bounds.js');
var getFastGlobalBounds = require('../scene/container/bounds/getFastGlobalBounds.js');
var getRenderableBounds = require('../scene/container/bounds/getRenderableBounds.js');
var warn = require('../utils/logging/warn.js');
"use strict";
const quadGeometry = new Geometry.Geometry({
attributes: {
aPosition: {
buffer: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
format: "float32x2",
stride: 2 * 4,
offset: 0
}
},
indexBuffer: new Uint32Array([0, 1, 2, 0, 2, 3])
});
class FilterSystem {
constructor(renderer) {
this._filterStackIndex = 0;
this._filterStack = [];
this._filterGlobalUniforms = new UniformGroup.UniformGroup({
uInputSize: { value: new Float32Array(4), type: "vec4<f32>" },
uInputPixel: { value: new Float32Array(4), type: "vec4<f32>" },
uInputClamp: { value: new Float32Array(4), type: "vec4<f32>" },
uOutputFrame: { value: new Float32Array(4), type: "vec4<f32>" },
uGlobalFrame: { value: new Float32Array(4), type: "vec4<f32>" },
uOutputTexture: { value: new Float32Array(4), type: "vec4<f32>" }
});
this._globalFilterBindGroup = new BindGroup.BindGroup({});
this.renderer = renderer;
}
/**
* The back texture of the currently active filter. Requires the filter to have `blendRequired` set to true.
* @readonly
*/
get activeBackTexture() {
return this._activeFilterData?.backTexture;
}
push(instruction) {
const renderer = this.renderer;
const filters = instruction.filterEffect.filters;
if (!this._filterStack[this._filterStackIndex]) {
this._filterStack[this._filterStackIndex] = this._getFilterData();
}
const filterData = this._filterStack[this._filterStackIndex];
this._filterStackIndex++;
if (filters.length === 0) {
filterData.skip = true;
return;
}
const bounds = filterData.bounds;
if (instruction.renderables) {
getRenderableBounds.getGlobalRenderableBounds(instruction.renderables, bounds);
} else if (instruction.filterEffect.filterArea) {
bounds.clear();
bounds.addRect(instruction.filterEffect.filterArea);
bounds.applyMatrix(instruction.container.worldTransform);
} else {
getFastGlobalBounds.getFastGlobalBounds(instruction.container, bounds);
}
const colorTextureSource = renderer.renderTarget.renderTarget.colorTexture.source;
let resolution = Infinity;
let padding = 0;
let antialias = true;
let blendRequired = false;
let enabled = false;
for (let i = 0; i < filters.length; i++) {
const filter = filters[i];
resolution = Math.min(resolution, filter.resolution === "inherit" ? colorTextureSource._resolution : filter.resolution);
padding += filter.padding;
if (filter.antialias === "off") {
antialias = false;
} else if (filter.antialias === "inherit") {
antialias && (antialias = colorTextureSource.antialias);
}
const isCompatible = !!(filter.compatibleRenderers & renderer.type);
if (!isCompatible) {
enabled = false;
break;
}
if (filter.blendRequired && !(renderer.backBuffer?.useBackBuffer ?? true)) {
warn.warn("Blend filter requires backBuffer on WebGL renderer to be enabled. Set `useBackBuffer: true` in the renderer options.");
enabled = false;
break;
}
enabled = filter.enabled || enabled;
blendRequired = blendRequired || filter.blendRequired;
}
if (!enabled) {
filterData.skip = true;
return;
}
const viewPort = renderer.renderTarget.rootViewPort;
bounds.scale(resolution).fitBounds(0, viewPort.width, 0, viewPort.height).scale(1 / resolution).pad(padding).ceil();
if (!bounds.isPositive) {
filterData.skip = true;
return;
}
filterData.skip = false;
filterData.bounds = bounds;
filterData.blendRequired = blendRequired;
filterData.container = instruction.container;
filterData.filterEffect = instruction.filterEffect;
filterData.previousRenderSurface = renderer.renderTarget.renderSurface;
filterData.inputTexture = TexturePool.TexturePool.getOptimalTexture(
bounds.width,
bounds.height,
resolution,
antialias
);
renderer.renderTarget.bind(filterData.inputTexture, true);
renderer.globalUniforms.push({
offset: bounds
});
}
pop() {
const renderer = this.renderer;
this._filterStackIndex--;
const filterData = this._filterStack[this._filterStackIndex];
if (filterData.skip) {
return;
}
this._activeFilterData = filterData;
const inputTexture = filterData.inputTexture;
const bounds = filterData.bounds;
let backTexture = Texture.Texture.EMPTY;
renderer.renderTarget.finishRenderPass();
if (filterData.blendRequired) {
const previousBounds = this._filterStackIndex > 0 ? this._filterStack[this._filterStackIndex - 1].bounds : null;
const renderTarget = renderer.renderTarget.getRenderTarget(filterData.previousRenderSurface);
backTexture = this.getBackTexture(renderTarget, bounds, previousBounds);
}
filterData.backTexture = backTexture;
const filters = filterData.filterEffect.filters;
this._globalFilterBindGroup.setResource(inputTexture.source.style, 2);
this._globalFilterBindGroup.setResource(backTexture.source, 3);
renderer.globalUniforms.pop();
if (filters.length === 1) {
filters[0].apply(this, inputTexture, filterData.previousRenderSurface, false);
TexturePool.TexturePool.returnTexture(inputTexture);
} else {
let flip = filterData.inputTexture;
let flop = TexturePool.TexturePool.getOptimalTexture(
bounds.width,
bounds.height,
flip.source._resolution,
false
);
let i = 0;
for (i = 0; i < filters.length - 1; ++i) {
const filter = filters[i];
filter.apply(this, flip, flop, true);
const t = flip;
flip = flop;
flop = t;
}
filters[i].apply(this, flip, filterData.previousRenderSurface, false);
TexturePool.TexturePool.returnTexture(flip);
TexturePool.TexturePool.returnTexture(flop);
}
if (filterData.blendRequired) {
TexturePool.TexturePool.returnTexture(backTexture);
}
}
getBackTexture(lastRenderSurface, bounds, previousBounds) {
const backgroundResolution = lastRenderSurface.colorTexture.source._resolution;
const backTexture = TexturePool.TexturePool.getOptimalTexture(
bounds.width,
bounds.height,
backgroundResolution,
false
);
let x = bounds.minX;
let y = bounds.minY;
if (previousBounds) {
x -= previousBounds.minX;
y -= previousBounds.minY;
}
x = Math.floor(x * backgroundResolution);
y = Math.floor(y * backgroundResolution);
const width = Math.ceil(bounds.width * backgroundResolution);
const height = Math.ceil(bounds.height * backgroundResolution);
this.renderer.renderTarget.copyToTexture(
lastRenderSurface,
backTexture,
{ x, y },
{ width, height },
{ x: 0, y: 0 }
);
return backTexture;
}
applyFilter(filter, input, output, clear) {
const renderer = this.renderer;
const filterData = this._filterStack[this._filterStackIndex];
const bounds = filterData.bounds;
const offset = Point.Point.shared;
const previousRenderSurface = filterData.previousRenderSurface;
const isFinalTarget = previousRenderSurface === output;
let resolution = this.renderer.renderTarget.rootRenderTarget.colorTexture.source._resolution;
let currentIndex = this._filterStackIndex - 1;
while (currentIndex > 0 && this._filterStack[currentIndex].skip) {
--currentIndex;
}
if (currentIndex > 0) {
resolution = this._filterStack[currentIndex].inputTexture.source._resolution;
}
const filterUniforms = this._filterGlobalUniforms;
const uniforms = filterUniforms.uniforms;
const outputFrame = uniforms.uOutputFrame;
const inputSize = uniforms.uInputSize;
const inputPixel = uniforms.uInputPixel;
const inputClamp = uniforms.uInputClamp;
const globalFrame = uniforms.uGlobalFrame;
const outputTexture = uniforms.uOutputTexture;
if (isFinalTarget) {
let lastIndex = this._filterStackIndex;
while (lastIndex > 0) {
lastIndex--;
const filterData2 = this._filterStack[this._filterStackIndex - 1];
if (!filterData2.skip) {
offset.x = filterData2.bounds.minX;
offset.y = filterData2.bounds.minY;
break;
}
}
outputFrame[0] = bounds.minX - offset.x;
outputFrame[1] = bounds.minY - offset.y;
} else {
outputFrame[0] = 0;
outputFrame[1] = 0;
}
outputFrame[2] = input.frame.width;
outputFrame[3] = input.frame.height;
inputSize[0] = input.source.width;
inputSize[1] = input.source.height;
inputSize[2] = 1 / inputSize[0];
inputSize[3] = 1 / inputSize[1];
inputPixel[0] = input.source.pixelWidth;
inputPixel[1] = input.source.pixelHeight;
inputPixel[2] = 1 / inputPixel[0];
inputPixel[3] = 1 / inputPixel[1];
inputClamp[0] = 0.5 * inputPixel[2];
inputClamp[1] = 0.5 * inputPixel[3];
inputClamp[2] = input.frame.width * inputSize[2] - 0.5 * inputPixel[2];
inputClamp[3] = input.frame.height * inputSize[3] - 0.5 * inputPixel[3];
const rootTexture = this.renderer.renderTarget.rootRenderTarget.colorTexture;
globalFrame[0] = offset.x * resolution;
globalFrame[1] = offset.y * resolution;
globalFrame[2] = rootTexture.source.width * resolution;
globalFrame[3] = rootTexture.source.height * resolution;
const renderTarget = this.renderer.renderTarget.getRenderTarget(output);
renderer.renderTarget.bind(output, !!clear);
if (output instanceof Texture.Texture) {
outputTexture[0] = output.frame.width;
outputTexture[1] = output.frame.height;
} else {
outputTexture[0] = renderTarget.width;
outputTexture[1] = renderTarget.height;
}
outputTexture[2] = renderTarget.isRoot ? -1 : 1;
filterUniforms.update();
if (renderer.renderPipes.uniformBatch) {
const batchUniforms = renderer.renderPipes.uniformBatch.getUboResource(filterUniforms);
this._globalFilterBindGroup.setResource(batchUniforms, 0);
} else {
this._globalFilterBindGroup.setResource(filterUniforms, 0);
}
this._globalFilterBindGroup.setResource(input.source, 1);
this._globalFilterBindGroup.setResource(input.source.style, 2);
filter.groups[0] = this._globalFilterBindGroup;
renderer.encoder.draw({
geometry: quadGeometry,
shader: filter,
state: filter._state,
topology: "triangle-list"
});
if (renderer.type === types.RendererType.WEBGL) {
renderer.renderTarget.finishRenderPass();
}
}
_getFilterData() {
return {
skip: false,
inputTexture: null,
bounds: new Bounds.Bounds(),
container: null,
filterEffect: null,
blendRequired: false,
previousRenderSurface: null
};
}
/**
* Multiply _input normalized coordinates_ to this matrix to get _sprite texture normalized coordinates_.
*
* Use `outputMatrix * vTextureCoord` in the shader.
* @param outputMatrix - The matrix to output to.
* @param {Sprite} sprite - The sprite to map to.
* @returns The mapped matrix.
*/
calculateSpriteMatrix(outputMatrix, sprite) {
const data = this._activeFilterData;
const mappedMatrix = outputMatrix.set(
data.inputTexture._source.width,
0,
0,
data.inputTexture._source.height,
data.bounds.minX,
data.bounds.minY
);
const worldTransform = sprite.worldTransform.copyTo(Matrix.Matrix.shared);
worldTransform.invert();
mappedMatrix.prepend(worldTransform);
mappedMatrix.scale(
1 / sprite.texture.frame.width,
1 / sprite.texture.frame.height
);
mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y);
return mappedMatrix;
}
}
/** @ignore */
FilterSystem.extension = {
type: [
Extensions.ExtensionType.WebGLSystem,
Extensions.ExtensionType.WebGPUSystem
],
name: "filter"
};
exports.FilterSystem = FilterSystem;
//# sourceMappingURL=FilterSystem.js.map