- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
import { FilterSystem, RenderTexture, Filter } from '@pixi/core';
import { BLEND_MODES, CLEAR_MODES } from '@pixi/constants';
import { BlendFilter } from './BlendFilter';
import { FlipYFilter } from './FlipYFilter';
/**
* The RGBA channel for {@link MaskFilter} to use to detect the mask region.
*
* When applying a {@link MaskFilter} to a mask {@link DisplayObject}, the object should render
* into that channel. For example, if using the alpha channel - the mask should render with alpha
* 1.0 where-ever the mask region is.
*
* @property {number} RED
* @property {number} GREEN
* @property {number} BLUE
* @property {number} ALPHA
*/
export enum MASK_CHANNEL {
RED = 0,
GREEN,
BLUE,
ALPHA
}
/** The mask configuration for {@link MaskFilter}. */
export class MaskConfig
{
/**
* @param maskBefore - If true, {@link MaskFilter} will mask the input of the applied filter instead of
* the output. In the case of a blur filter, this would cause cause the boundaries of the mask to soften
* as the blur would apply to the masked region instead of being clipped into it.
* @param channel - The mask channel indicating which pixels are in the mask region.
*/
constructor(public maskBefore = false, channel: MASK_CHANNEL = MASK_CHANNEL.ALPHA)
{
this.uniforms.uChannel[channel] = 1.0;
}
/** @ignore */
uniformCode = 'uniform vec4 uChannel;';
/** @ignore */
uniforms: any = {
uChannel: new Float32Array([0, 0, 0, 0]), // shared uniform for all those shaders? ok, just set it before apply
};
/** @ignore */
blendCode = `b_res = dot(b_src, uChannel) * b_dest;`;
/**
* Flag that indicates the applied filter is Y-symmetric.
*
* {@link MaskFilter} will optimize rendering by not flipping the screen backdrop before passing it to the
* blend filter for Y-symmetric filters.
*
* A filter is Y-symmetric if giving it an inverted input and then inverting the output is equivalent
* to giving it an upright input.
*/
safeFlipY = false;
}
const tmpArray = new Float32Array([0, 1]);
/**
* A higher-order filter that applies the output of a filter to a masked region of the destination framebuffer.
*
* The masked region is defined by where-ever the target {@link DisplayObject} renders to in the world. For
* example, if you draw a rectangle in the world and apply a masked-blur filter, the blur filter will apply
* to pixels in the backdrop within the rectangle. The {@link DisplayObject} must render by drawing
* a solid RGBA channel (see {@link MaskConfig}'s constructor).
*/
export class MaskFilter extends BlendFilter
{
/**
* @param baseFilter - The filter being applied.
* @param config - The configuration for the mask.
*/
constructor(public baseFilter: Filter, public config: MaskConfig = new MaskConfig())
{
super(config);
this.padding = baseFilter.padding;
this.safeFlipY = config.safeFlipY;
}
/** @ignore */
static _flipYFilter: FlipYFilter = null;
/**
* if base filter is not sensitive to flipping Y axis, you can turn this ON and save a temporary texture bind / drawcall
*/
safeFlipY: boolean;
apply(filterManager: FilterSystem, input: RenderTexture, output: RenderTexture,
clearMode: CLEAR_MODES): void
{
const target = filterManager.getFilterTexture(input);
if (this.config.maskBefore)
{
const { blendMode } = this.state;
this.state.blendMode = BLEND_MODES.NONE;
filterManager.applyFilter(this, input, target, CLEAR_MODES.YES);
this.baseFilter.blendMode = blendMode;
this.baseFilter.apply(filterManager, target, output, clearMode);
this.state.blendMode = blendMode;
}
else
{
const { uBackdrop, uBackdrop_flipY } = this.uniforms;
if (uBackdrop_flipY[1] > 0 || this.safeFlipY)
{
this.baseFilter.apply(filterManager, uBackdrop, target, CLEAR_MODES.YES);
}
else
{
// in case there was a flip and base filter is not flipY-safe, we have to use extra flip operation
const targetFlip = filterManager.getFilterTexture(input);
if (!MaskFilter._flipYFilter)
{
MaskFilter._flipYFilter = new FlipYFilter();
}
MaskFilter._flipYFilter.uniforms.flipY[0] = uBackdrop_flipY[0];
MaskFilter._flipYFilter.uniforms.flipY[1] = uBackdrop_flipY[1];
MaskFilter._flipYFilter.apply(filterManager, uBackdrop, targetFlip, CLEAR_MODES.YES);
this.baseFilter.apply(filterManager, targetFlip, target, CLEAR_MODES.YES);
filterManager.returnFilterTexture(targetFlip);
this.uniforms.uBackdrop_flipY = tmpArray;
}
this.uniforms.uBackdrop = target;
filterManager.applyFilter(this, input, output, clearMode);
this.uniforms.uBackdrop = uBackdrop;
this.uniforms.uBackdrop_flipY = uBackdrop_flipY;
}
filterManager.returnFilterTexture(target);
}
}