{"version":3,"file":"MaskSystem.mjs","sources":["../../src/mask/MaskSystem.ts"],"sourcesContent":["import { MASK_TYPES } from '@pixi/constants';\nimport { extensions, ExtensionType } from '@pixi/extensions';\nimport { SpriteMaskFilter } from '../filters/spriteMask/SpriteMaskFilter';\nimport { MaskData } from './MaskData';\n\nimport type { ExtensionMetadata } from '@pixi/extensions';\nimport type { Renderer } from '../Renderer';\nimport type { ISystem } from '../system/ISystem';\nimport type { IMaskTarget } from './MaskData';\n\n/**\n * System plugin to the renderer to manage masks.\n *\n * There are three built-in types of masking:\n * **Scissor Masking**: Scissor masking discards pixels that are outside of a rectangle called the scissor box. It is\n *  the most performant as the scissor test is inexpensive. However, it can only be used when the mask is rectangular.\n * **Stencil Masking**: Stencil masking discards pixels that don't overlap with the pixels rendered into the stencil\n *  buffer. It is the next fastest option as it does not require rendering into a separate framebuffer. However, it does\n *  cause the mask to be rendered **twice** for each masking operation; hence, minimize the rendering cost of your masks.\n * **Sprite Mask Filtering**: Sprite mask filtering discards pixels based on the red channel of the sprite-mask's\n *  texture. (Generally, the masking texture is grayscale). Using advanced techniques, you might be able to embed this\n *  type of masking in a custom shader - and hence, bypassing the masking system fully for performance wins.\n *\n * The best type of masking is auto-detected when you `push` one. To use scissor masking, you must pass in a `Graphics`\n * object with just a rectangle drawn.\n *\n * ## Mask Stacks\n *\n * In the scene graph, masks can be applied recursively, i.e. a mask can be applied during a masking operation. The mask\n * stack stores the currently applied masks in order. Each {@link PIXI.BaseRenderTexture} holds its own mask stack, i.e.\n * when you switch render-textures, the old masks only applied when you switch back to rendering to the old render-target.\n * @memberof PIXI\n */\nexport class MaskSystem implements ISystem\n{\n    /** @ignore */\n    static extension: ExtensionMetadata = {\n        type: ExtensionType.RendererSystem,\n        name: 'mask',\n    };\n\n    /**\n     * Flag to enable scissor masking.\n     * @default true\n     */\n    public enableScissor: boolean;\n\n    /** Pool of used sprite mask filters. */\n    protected readonly alphaMaskPool: Array<SpriteMaskFilter[]>;\n\n    /**\n     * Current index of alpha mask pool.\n     * @default 0\n     * @readonly\n     */\n    protected alphaMaskIndex: number;\n\n    /** Pool of mask data. */\n    private readonly maskDataPool: Array<MaskData>;\n    private maskStack: Array<MaskData>;\n    private renderer: Renderer;\n\n    /**\n     * @param renderer - The renderer this System works for.\n     */\n    constructor(renderer: Renderer)\n    {\n        this.renderer = renderer;\n\n        this.enableScissor = true;\n        this.alphaMaskPool = [];\n        this.maskDataPool = [];\n\n        this.maskStack = [];\n        this.alphaMaskIndex = 0;\n    }\n\n    /**\n     * Changes the mask stack that is used by this System.\n     * @param maskStack - The mask stack\n     */\n    setMaskStack(maskStack: Array<MaskData>): void\n    {\n        this.maskStack = maskStack;\n        this.renderer.scissor.setMaskStack(maskStack);\n        this.renderer.stencil.setMaskStack(maskStack);\n    }\n\n    /**\n     * Enables the mask and appends it to the current mask stack.\n     *\n     * NOTE: The batch renderer should be flushed beforehand to prevent pending renders from being masked.\n     * @param {PIXI.DisplayObject} target - Display Object to push the mask to\n     * @param {PIXI.MaskData|PIXI.Sprite|PIXI.Graphics|PIXI.DisplayObject} maskDataOrTarget - The masking data.\n     */\n    push(target: IMaskTarget, maskDataOrTarget: MaskData | IMaskTarget): void\n    {\n        let maskData = maskDataOrTarget as MaskData;\n\n        if (!maskData.isMaskData)\n        {\n            const d = this.maskDataPool.pop() || new MaskData();\n\n            d.pooled = true;\n            d.maskObject = maskDataOrTarget as IMaskTarget;\n            maskData = d;\n        }\n\n        const maskAbove = this.maskStack.length !== 0 ? this.maskStack[this.maskStack.length - 1] : null;\n\n        maskData.copyCountersOrReset(maskAbove);\n        maskData._colorMask = maskAbove ? maskAbove._colorMask : 0xf;\n\n        if (maskData.autoDetect)\n        {\n            this.detect(maskData);\n        }\n\n        maskData._target = target;\n\n        if (maskData.type !== MASK_TYPES.SPRITE)\n        {\n            this.maskStack.push(maskData);\n        }\n\n        if (maskData.enabled)\n        {\n            switch (maskData.type)\n            {\n                case MASK_TYPES.SCISSOR:\n                    this.renderer.scissor.push(maskData);\n                    break;\n                case MASK_TYPES.STENCIL:\n                    this.renderer.stencil.push(maskData);\n                    break;\n                case MASK_TYPES.SPRITE:\n                    maskData.copyCountersOrReset(null);\n                    this.pushSpriteMask(maskData);\n                    break;\n                case MASK_TYPES.COLOR:\n                    this.pushColorMask(maskData);\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        if (maskData.type === MASK_TYPES.SPRITE)\n        {\n            this.maskStack.push(maskData);\n        }\n    }\n\n    /**\n     * Removes the last mask from the mask stack and doesn't return it.\n     *\n     * NOTE: The batch renderer should be flushed beforehand to render the masked contents before the mask is removed.\n     * @param {PIXI.IMaskTarget} target - Display Object to pop the mask from\n     */\n    pop(target: IMaskTarget): void\n    {\n        const maskData = this.maskStack.pop();\n\n        if (!maskData || maskData._target !== target)\n        {\n            // TODO: add an assert when we have it\n\n            return;\n        }\n\n        if (maskData.enabled)\n        {\n            switch (maskData.type)\n            {\n                case MASK_TYPES.SCISSOR:\n                    this.renderer.scissor.pop(maskData);\n                    break;\n                case MASK_TYPES.STENCIL:\n                    this.renderer.stencil.pop(maskData.maskObject);\n                    break;\n                case MASK_TYPES.SPRITE:\n                    this.popSpriteMask(maskData);\n                    break;\n                case MASK_TYPES.COLOR:\n                    this.popColorMask(maskData);\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        maskData.reset();\n\n        if (maskData.pooled)\n        {\n            this.maskDataPool.push(maskData);\n        }\n\n        if (this.maskStack.length !== 0)\n        {\n            const maskCurrent = this.maskStack[this.maskStack.length - 1];\n\n            if (maskCurrent.type === MASK_TYPES.SPRITE && maskCurrent._filters)\n            {\n                maskCurrent._filters[0].maskSprite = maskCurrent.maskObject;\n            }\n        }\n    }\n\n    /**\n     * Sets type of MaskData based on its maskObject.\n     * @param maskData\n     */\n    detect(maskData: MaskData): void\n    {\n        const maskObject = maskData.maskObject;\n\n        if (!maskObject)\n        {\n            maskData.type = MASK_TYPES.COLOR;\n        }\n        else if (maskObject.isSprite)\n        {\n            maskData.type = MASK_TYPES.SPRITE;\n        }\n        else if (this.enableScissor && this.renderer.scissor.testScissor(maskData))\n        {\n            maskData.type = MASK_TYPES.SCISSOR;\n        }\n        else\n        {\n            maskData.type = MASK_TYPES.STENCIL;\n        }\n    }\n\n    /**\n     * Applies the Mask and adds it to the current filter stack.\n     * @param maskData - Sprite to be used as the mask.\n     */\n    pushSpriteMask(maskData: MaskData): void\n    {\n        const { maskObject } = maskData;\n        const target = maskData._target;\n        let alphaMaskFilter = maskData._filters;\n\n        if (!alphaMaskFilter)\n        {\n            alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex];\n\n            if (!alphaMaskFilter)\n            {\n                alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new SpriteMaskFilter()];\n            }\n        }\n\n        alphaMaskFilter[0].resolution = maskData.resolution;\n        alphaMaskFilter[0].multisample = maskData.multisample;\n        alphaMaskFilter[0].maskSprite = maskObject;\n\n        const stashFilterArea = target.filterArea;\n\n        target.filterArea = maskObject.getBounds(true);\n        this.renderer.filter.push(target, alphaMaskFilter);\n        target.filterArea = stashFilterArea;\n\n        if (!maskData._filters)\n        {\n            this.alphaMaskIndex++;\n        }\n    }\n\n    /**\n     * Removes the last filter from the filter stack and doesn't return it.\n     * @param maskData - Sprite to be used as the mask.\n     */\n    popSpriteMask(maskData: MaskData): void\n    {\n        this.renderer.filter.pop();\n\n        if (maskData._filters)\n        {\n            maskData._filters[0].maskSprite = null;\n        }\n        else\n        {\n            this.alphaMaskIndex--;\n            this.alphaMaskPool[this.alphaMaskIndex][0].maskSprite = null;\n        }\n    }\n\n    /**\n     * Pushes the color mask.\n     * @param maskData - The mask data\n     */\n    pushColorMask(maskData: MaskData): void\n    {\n        const currColorMask = maskData._colorMask;\n        const nextColorMask = maskData._colorMask = currColorMask & maskData.colorMask;\n\n        if (nextColorMask !== currColorMask)\n        {\n            this.renderer.gl.colorMask(\n                (nextColorMask & 0x1) !== 0,\n                (nextColorMask & 0x2) !== 0,\n                (nextColorMask & 0x4) !== 0,\n                (nextColorMask & 0x8) !== 0\n            );\n        }\n    }\n\n    /**\n     * Pops the color mask.\n     * @param maskData - The mask data\n     */\n    popColorMask(maskData: MaskData): void\n    {\n        const currColorMask = maskData._colorMask;\n        const nextColorMask = this.maskStack.length > 0\n            ? this.maskStack[this.maskStack.length - 1]._colorMask : 0xf;\n\n        if (nextColorMask !== currColorMask)\n        {\n            this.renderer.gl.colorMask(\n                (nextColorMask & 0x1) !== 0,\n                (nextColorMask & 0x2) !== 0,\n                (nextColorMask & 0x4) !== 0,\n                (nextColorMask & 0x8) !== 0\n            );\n        }\n    }\n\n    destroy(): void\n    {\n        this.renderer = null;\n    }\n}\n\nextensions.add(MaskSystem);\n"],"names":[],"mappings":";;;;AAiCO,MAAM,WACb;AAAA;AAAA;AAAA;AAAA,EA+BI,YAAY,UACZ;AACI,SAAK,WAAW,UAEhB,KAAK,gBAAgB,IACrB,KAAK,gBAAgB,CAAA,GACrB,KAAK,eAAe,CAEpB,GAAA,KAAK,YAAY,CAAA,GACjB,KAAK,iBAAiB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,WACb;AACI,SAAK,YAAY,WACjB,KAAK,SAAS,QAAQ,aAAa,SAAS,GAC5C,KAAK,SAAS,QAAQ,aAAa,SAAS;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,KAAK,QAAqB,kBAC1B;AACI,QAAI,WAAW;AAEX,QAAA,CAAC,SAAS,YACd;AACI,YAAM,IAAI,KAAK,aAAa,IAAI,KAAK,IAAI;AAEzC,QAAE,SAAS,IACX,EAAE,aAAa,kBACf,WAAW;AAAA,IACf;AAEM,UAAA,YAAY,KAAK,UAAU,WAAW,IAAI,KAAK,UAAU,KAAK,UAAU,SAAS,CAAC,IAAI;AAiB5F,QAfA,SAAS,oBAAoB,SAAS,GACtC,SAAS,aAAa,YAAY,UAAU,aAAa,IAErD,SAAS,cAET,KAAK,OAAO,QAAQ,GAGxB,SAAS,UAAU,QAEf,SAAS,SAAS,WAAW,UAE7B,KAAK,UAAU,KAAK,QAAQ,GAG5B,SAAS;AAET,cAAQ,SAAS,MACjB;AAAA,QACI,KAAK,WAAW;AACP,eAAA,SAAS,QAAQ,KAAK,QAAQ;AACnC;AAAA,QACJ,KAAK,WAAW;AACP,eAAA,SAAS,QAAQ,KAAK,QAAQ;AACnC;AAAA,QACJ,KAAK,WAAW;AACZ,mBAAS,oBAAoB,IAAI,GACjC,KAAK,eAAe,QAAQ;AAC5B;AAAA,QACJ,KAAK,WAAW;AACZ,eAAK,cAAc,QAAQ;AAC3B;AAAA,QACJ;AACI;AAAA,MACR;AAGA,aAAS,SAAS,WAAW,UAE7B,KAAK,UAAU,KAAK,QAAQ;AAAA,EAEpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,QACJ;AACU,UAAA,WAAW,KAAK,UAAU,IAAI;AAEpC,QAAI,EAAC,CAAA,YAAY,SAAS,YAAY,SAOtC;AAAA,UAAI,SAAS;AAET,gBAAQ,SAAS,MACjB;AAAA,UACI,KAAK,WAAW;AACP,iBAAA,SAAS,QAAQ,IAAI,QAAQ;AAClC;AAAA,UACJ,KAAK,WAAW;AACZ,iBAAK,SAAS,QAAQ,IAAI,SAAS,UAAU;AAC7C;AAAA,UACJ,KAAK,WAAW;AACZ,iBAAK,cAAc,QAAQ;AAC3B;AAAA,UACJ,KAAK,WAAW;AACZ,iBAAK,aAAa,QAAQ;AAC1B;AAAA,UACJ;AACI;AAAA,QACR;AAUJ,UAPA,SAAS,MAAA,GAEL,SAAS,UAET,KAAK,aAAa,KAAK,QAAQ,GAG/B,KAAK,UAAU,WAAW,GAC9B;AACI,cAAM,cAAc,KAAK,UAAU,KAAK,UAAU,SAAS,CAAC;AAExD,oBAAY,SAAS,WAAW,UAAU,YAAY,aAEtD,YAAY,SAAS,CAAC,EAAE,aAAa,YAAY;AAAA,MAEzD;AAAA,IAAA;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,UACP;AACI,UAAM,aAAa,SAAS;AAEvB,iBAII,WAAW,WAEhB,SAAS,OAAO,WAAW,SAEtB,KAAK,iBAAiB,KAAK,SAAS,QAAQ,YAAY,QAAQ,IAErE,SAAS,OAAO,WAAW,UAI3B,SAAS,OAAO,WAAW,UAZ3B,SAAS,OAAO,WAAW;AAAA,EAcnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,UACf;AACI,UAAM,EAAE,WAAA,IAAe,UACjB,SAAS,SAAS;AACxB,QAAI,kBAAkB,SAAS;AAE1B,wBAED,kBAAkB,KAAK,cAAc,KAAK,cAAc,GAEnD,oBAED,kBAAkB,KAAK,cAAc,KAAK,cAAc,IAAI,CAAC,IAAI,kBAAkB,KAI3F,gBAAgB,CAAC,EAAE,aAAa,SAAS,YACzC,gBAAgB,CAAC,EAAE,cAAc,SAAS,aAC1C,gBAAgB,CAAC,EAAE,aAAa;AAEhC,UAAM,kBAAkB,OAAO;AAE/B,WAAO,aAAa,WAAW,UAAU,EAAI,GAC7C,KAAK,SAAS,OAAO,KAAK,QAAQ,eAAe,GACjD,OAAO,aAAa,iBAEf,SAAS,YAEV,KAAK;AAAA,EAEb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc,UACd;AACS,SAAA,SAAS,OAAO,OAEjB,SAAS,WAET,SAAS,SAAS,CAAC,EAAE,aAAa,QAIlC,KAAK,kBACL,KAAK,cAAc,KAAK,cAAc,EAAE,CAAC,EAAE,aAAa;AAAA,EAEhE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc,UACd;AACI,UAAM,gBAAgB,SAAS,YACzB,gBAAgB,SAAS,aAAa,gBAAgB,SAAS;AAEjE,sBAAkB,iBAElB,KAAK,SAAS,GAAG;AAAA,OACZ,gBAAgB,OAAS;AAAA,OACzB,gBAAgB,OAAS;AAAA,OACzB,gBAAgB,OAAS;AAAA,OACzB,gBAAgB,OAAS;AAAA,IAAA;AAAA,EAGtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,UACb;AACI,UAAM,gBAAgB,SAAS,YACzB,gBAAgB,KAAK,UAAU,SAAS,IACxC,KAAK,UAAU,KAAK,UAAU,SAAS,CAAC,EAAE,aAAa;AAEzD,sBAAkB,iBAElB,KAAK,SAAS,GAAG;AAAA,OACZ,gBAAgB,OAAS;AAAA,OACzB,gBAAgB,OAAS;AAAA,OACzB,gBAAgB,OAAS;AAAA,OACzB,gBAAgB,OAAS;AAAA,IAAA;AAAA,EAGtC;AAAA,EAEA,UACA;AACI,SAAK,WAAW;AAAA,EACpB;AACJ;AA9Sa,WAGF,YAA+B;AAAA,EAClC,MAAM,cAAc;AAAA,EACpB,MAAM;AACV;AA0SJ,WAAW,IAAI,UAAU;"}