"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module WebGL
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScreenSpaceEffects = void 0;
exports.createScreenSpaceEffectBuilder = createScreenSpaceEffectBuilder;
const core_bentley_1 = require("@itwin/core-bentley");
const ScreenSpaceEffectBuilder_1 = require("../../../render/ScreenSpaceEffectBuilder");
const RenderState_1 = require("./RenderState");
const CachedGeometry_1 = require("./CachedGeometry");
const FrameBuffer_1 = require("./FrameBuffer");
const ScratchDrawParams_1 = require("./ScratchDrawParams");
const Technique_1 = require("./Technique");
const System_1 = require("./System");
const ScreenSpaceEffect_1 = require("./glsl/ScreenSpaceEffect");
function getUniformVariableType(type) {
    switch (type) {
        case ScreenSpaceEffectBuilder_1.UniformType.Bool: return 0 /* VariableType.Boolean */;
        case ScreenSpaceEffectBuilder_1.UniformType.Int: return 1 /* VariableType.Int */;
        case ScreenSpaceEffectBuilder_1.UniformType.Float: return 2 /* VariableType.Float */;
        case ScreenSpaceEffectBuilder_1.UniformType.Vec2: return 3 /* VariableType.Vec2 */;
        case ScreenSpaceEffectBuilder_1.UniformType.Vec3: return 4 /* VariableType.Vec3 */;
        case ScreenSpaceEffectBuilder_1.UniformType.Vec4: return 5 /* VariableType.Vec4 */;
    }
}
function getVaryingVariableType(type) {
    switch (type) {
        case ScreenSpaceEffectBuilder_1.VaryingType.Float: return 2 /* VariableType.Float */;
        case ScreenSpaceEffectBuilder_1.VaryingType.Vec2: return 3 /* VariableType.Vec2 */;
        case ScreenSpaceEffectBuilder_1.VaryingType.Vec3: return 4 /* VariableType.Vec3 */;
        case ScreenSpaceEffectBuilder_1.VaryingType.Vec4: return 5 /* VariableType.Vec4 */;
    }
}
class Builder {
    _name;
    _builder;
    _shiftsPixels;
    shouldApply;
    constructor(params) {
        this._name = params.name;
        this._builder = (0, ScreenSpaceEffect_1.createScreenSpaceEffectProgramBuilder)(params);
        this._builder.setDebugDescription(`Screen-space: ${this._name}`);
        this._shiftsPixels = undefined !== params.source.sampleSourcePixel;
    }
    get isWebGL2() {
        return true;
    }
    addUniform(params) {
        const name = params.name;
        const type = getUniformVariableType(params.type);
        const bind = params.bind;
        this._builder.addUniform(name, type, (prog) => {
            prog.addProgramUniform(name, (uniform, progParams) => {
                bind(uniform, progParams.target.screenSpaceEffectContext);
            });
        });
    }
    addUniformArray(params) {
        const { name, bind, length } = { ...params };
        const type = getUniformVariableType(params.type);
        this._builder.addUniformArray(name, type, length, (prog) => {
            prog.addProgramUniform(name, (uniform, progParams) => {
                bind(uniform, progParams.target.screenSpaceEffectContext);
            });
        });
    }
    addVarying(name, type) {
        this._builder.addVarying(name, getVaryingVariableType(type));
    }
    finish() {
        const program = this._builder.buildProgram(System_1.System.instance.context);
        // NB: compile() will throw with WebGL error log if compile/link fails.
        if (0 /* CompileStatus.Success */ !== program.compile())
            throw new Error(`Failed to produce shader program for screen-space effect "${this._name}"`);
        const technique = new Technique_1.SingularTechnique(program);
        const techniqueId = System_1.System.instance.techniques.addDynamicTechnique(technique, this._name);
        const effect = new ScreenSpaceEffect(techniqueId, this._name, this._shiftsPixels, this.shouldApply);
        System_1.System.instance.screenSpaceEffects.add(effect);
    }
}
class ScreenSpaceEffect {
    techniqueId;
    name;
    _shouldApply;
    _shiftsPixels;
    constructor(techniqueId, name, shiftsPixels, shouldApply) {
        this.techniqueId = techniqueId;
        this.name = name;
        this._shouldApply = shouldApply;
        this._shiftsPixels = shiftsPixels;
    }
    shouldApply(target) {
        // Effects only apply during readPixels() if they move pixels around (we need to move pixels in the pick buffers correspondingly).
        if (target.isReadPixelsInProgress && !this._shiftsPixels)
            return false;
        return undefined === this._shouldApply || this._shouldApply(target.screenSpaceEffectContext);
    }
}
class ScreenSpaceGeometry extends CachedGeometry_1.ViewportQuadGeometry {
    setTechniqueId(id) {
        this._techniqueId = id;
    }
}
/** @internal */
class ScreenSpaceEffects {
    _effects = new Map();
    _effectGeometry;
    _copyGeometry;
    _workingArray = [];
    constructor() {
        // We will change the geometry's TechniqueId before applying each technique.
        const effectGeometry = ScreenSpaceGeometry.create(-1 /* TechniqueId.Invalid */);
        (0, core_bentley_1.assert)(effectGeometry instanceof ScreenSpaceGeometry);
        this._effectGeometry = effectGeometry;
        // NB: We'll replace the texture each time we draw.
        const copyGeometry = CachedGeometry_1.SingleTexturedViewportQuadGeometry.createGeometry((0, core_bentley_1.expectDefined)(System_1.System.instance.lineCodeTexture?.getHandle()), 18 /* TechniqueId.CopyColor */);
        (0, core_bentley_1.assert)(undefined !== copyGeometry);
        this._copyGeometry = copyGeometry;
    }
    [Symbol.dispose]() {
        (0, core_bentley_1.dispose)(this._effectGeometry);
        (0, core_bentley_1.dispose)(this._copyGeometry);
    }
    add(effect) {
        if (undefined !== this._effects.get(effect.name))
            throw new Error(`Screen-space effect "${effect.name}" is already registered.`);
        this._effects.set(effect.name, effect);
    }
    /** Return true if any effects should be applied to this Target. */
    shouldApply(target) {
        return this.getApplicableEffects(target).length > 0;
    }
    getApplicableEffects(target) {
        const effects = this._workingArray;
        effects.length = 0;
        const names = target.screenSpaceEffects;
        for (const name of names) {
            const effect = this._effects.get(name);
            if (effect && effect.shouldApply(target))
                effects.push(effect);
        }
        return effects;
    }
    /** Apply screen-space effects to the Target's rendered image. */
    apply(target) {
        if (0 === this._effects.size)
            return;
        const effects = this.getApplicableEffects(target);
        if (0 === effects.length)
            return;
        if (target.isReadPixelsInProgress) {
            this.applyForReadPixels(effects, target);
            return;
        }
        const system = System_1.System.instance;
        system.applyRenderState(RenderState_1.RenderState.defaults);
        const copyFbo = target.compositor.screenSpaceEffectFbo;
        for (const effect of effects) {
            // Copy the rendered image to texture as input to the effect shader.
            this._copyGeometry.texture = (0, core_bentley_1.expectDefined)(system.frameBufferStack.currentColorBuffer?.getHandle());
            system.frameBufferStack.execute(copyFbo, true, false, () => {
                const copyParams = (0, ScratchDrawParams_1.getDrawParams)(target, this._copyGeometry);
                system.techniques.draw(copyParams);
            });
            // Run the effect shader with a copy of the current image as input.
            this._effectGeometry.setTechniqueId(effect.techniqueId);
            const params = (0, ScratchDrawParams_1.getDrawParams)(target, this._effectGeometry);
            system.techniques.draw(params);
        }
    }
    applyForReadPixels(effects, target) {
        const system = System_1.System.instance;
        system.applyRenderState(RenderState_1.RenderState.defaults);
        // ###TODO: We could use MRT if available here rather than two passes.
        const copyFbo = target.compositor.screenSpaceEffectFbo;
        for (const effect of effects) {
            this._effectGeometry.setTechniqueId(effect.techniqueId);
            for (let i = 0; i <= 1; i++) {
                // Copy the pick buffer as input to the effect shader.
                const buffer = 0 === i ? target.compositor.featureIds : target.compositor.depthAndOrder;
                this._copyGeometry.texture = (0, core_bentley_1.expectDefined)(buffer.getHandle());
                system.frameBufferStack.execute(copyFbo, true, false, () => {
                    const copyParams = (0, ScratchDrawParams_1.getDrawParams)(target, this._copyGeometry);
                    system.techniques.draw(copyParams);
                });
                // Run the effect shader with a copy of the current pick buffer to output to pick buffer.
                // ###TODO: Avoid frequent framebuffer allocation.
                const effectFbo = FrameBuffer_1.FrameBuffer.create([buffer]);
                (0, core_bentley_1.assert)(undefined !== effectFbo);
                system.frameBufferStack.execute(effectFbo, true, false, () => {
                    const effectParams = (0, ScratchDrawParams_1.getDrawParams)(target, this._effectGeometry);
                    system.techniques.draw(effectParams);
                });
                effectFbo[Symbol.dispose]();
            }
        }
    }
}
exports.ScreenSpaceEffects = ScreenSpaceEffects;
function createScreenSpaceEffectBuilder(params) {
    return new Builder(params);
}
//# sourceMappingURL=ScreenSpaceEffect.js.map