/*---------------------------------------------------------------------------------------------
* 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
 */
import { assert, dispose, expectDefined } from "@itwin/core-bentley";
import { EDLCalcBasicGeometry, EDLCalcFullGeometry, EDLFilterGeometry, EDLMixGeometry } from "./CachedGeometry";
import { FrameBuffer } from "./FrameBuffer";
import { GL } from "./GL";
import { RenderState } from "./RenderState";
import { collectGeometryStatistics, collectTextureStatistics } from "./SceneCompositor";
import { getDrawParams } from "./ScratchDrawParams";
import { System } from "./System";
import { TextureHandle } from "./Texture";
class Bundle {
    edlCalcTex1;
    edlCalcTex2;
    edlCalcTex4;
    edlFiltTex2;
    edlFiltTex4;
    edlCalcFbo1;
    edlCalcFbo2;
    edlCalcFbo4;
    edlFiltFbo2;
    edlFiltFbo4;
    edlCalcBasicGeom;
    edlCalcFullGeom;
    edlFiltGeom;
    edlMixGeom;
    constructor(edlCalcTex1, edlCalcTex2, edlCalcTex4, edlFiltTex2, edlFiltTex4, edlCalcFbo1, edlCalcFbo2, edlCalcFbo4, edlFiltFbo2, edlFiltFbo4, edlCalcBasicGeom, edlCalcFullGeom, edlFiltGeom, edlMixGeom) {
        this.edlCalcTex1 = edlCalcTex1;
        this.edlCalcTex2 = edlCalcTex2;
        this.edlCalcTex4 = edlCalcTex4;
        this.edlFiltTex2 = edlFiltTex2;
        this.edlFiltTex4 = edlFiltTex4;
        this.edlCalcFbo1 = edlCalcFbo1;
        this.edlCalcFbo2 = edlCalcFbo2;
        this.edlCalcFbo4 = edlCalcFbo4;
        this.edlFiltFbo2 = edlFiltFbo2;
        this.edlFiltFbo4 = edlFiltFbo4;
        this.edlCalcBasicGeom = edlCalcBasicGeom;
        this.edlCalcFullGeom = edlCalcFullGeom;
        this.edlFiltGeom = edlFiltGeom;
        this.edlMixGeom = edlMixGeom;
    }
    static create(width, height) {
        const edlCalcTex1 = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte);
        const edlCalcTex2 = TextureHandle.createForAttachment(width >> 1, height >> 1, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte);
        const edlCalcTex4 = TextureHandle.createForAttachment(width >> 2, height >> 2, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte);
        const edlFiltTex2 = TextureHandle.createForAttachment(width >> 1, height >> 1, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte);
        const edlFiltTex4 = TextureHandle.createForAttachment(width >> 2, height >> 2, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte);
        if (undefined === edlCalcTex1 || undefined === edlCalcTex2 || undefined === edlCalcTex4 || undefined === edlFiltTex2 || undefined === edlFiltTex4) {
            dispose(edlCalcTex1);
            dispose(edlCalcTex2);
            dispose(edlCalcTex4);
            dispose(edlFiltTex2);
            dispose(edlFiltTex4);
            return undefined;
        }
        const edlCalcFbo1 = FrameBuffer.create([edlCalcTex1]);
        const edlCalcFbo2 = FrameBuffer.create([edlCalcTex2]);
        const edlCalcFbo4 = FrameBuffer.create([edlCalcTex4]);
        const edlFiltFbo2 = FrameBuffer.create([edlFiltTex2]);
        const edlFiltFbo4 = FrameBuffer.create([edlFiltTex4]);
        if (undefined === edlCalcFbo1 || undefined === edlCalcFbo2 || undefined === edlCalcFbo4 || undefined === edlFiltFbo2 || undefined === edlFiltFbo4) {
            dispose(edlCalcFbo1);
            dispose(edlCalcFbo2);
            dispose(edlCalcFbo4);
            dispose(edlFiltFbo2);
            dispose(edlFiltFbo4);
            return undefined;
        }
        return new Bundle(edlCalcTex1, edlCalcTex2, edlCalcTex4, edlFiltTex2, edlFiltTex4, edlCalcFbo1, edlCalcFbo2, edlCalcFbo4, edlFiltFbo2, edlFiltFbo4);
    }
    get isDisposed() {
        return undefined === this.edlCalcTex1
            && undefined === this.edlCalcTex2
            && undefined === this.edlCalcTex4
            && undefined === this.edlFiltTex2
            && undefined === this.edlFiltTex4
            && undefined === this.edlCalcFbo1
            && undefined === this.edlCalcFbo2
            && undefined === this.edlCalcFbo4
            && undefined === this.edlFiltFbo2
            && undefined === this.edlFiltFbo4
            && undefined === this.edlCalcBasicGeom
            && undefined === this.edlCalcFullGeom?.[0]
            && undefined === this.edlCalcFullGeom?.[1]
            && undefined === this.edlCalcFullGeom?.[2]
            && undefined === this.edlCalcFullGeom
            && undefined === this.edlFiltGeom?.[0]
            && undefined === this.edlFiltGeom?.[1]
            && undefined === this.edlFiltGeom
            && undefined === this.edlMixGeom;
    }
    [Symbol.dispose]() {
        this.edlCalcTex1 = dispose(this.edlCalcTex1);
        this.edlCalcTex2 = dispose(this.edlCalcTex2);
        this.edlCalcTex4 = dispose(this.edlCalcTex4);
        this.edlFiltTex2 = dispose(this.edlFiltTex2);
        this.edlFiltTex4 = dispose(this.edlFiltTex4);
        this.edlCalcFbo1 = dispose(this.edlCalcFbo1);
        this.edlCalcFbo2 = dispose(this.edlCalcFbo2);
        this.edlCalcFbo4 = dispose(this.edlCalcFbo4);
        this.edlFiltFbo2 = dispose(this.edlFiltFbo2);
        this.edlFiltFbo4 = dispose(this.edlFiltFbo4);
        this.edlCalcBasicGeom = dispose(this.edlCalcBasicGeom);
        if (this.edlCalcFullGeom) {
            this.edlCalcFullGeom[0] = dispose(this.edlCalcFullGeom?.[0]);
            this.edlCalcFullGeom[1] = dispose(this.edlCalcFullGeom?.[1]);
            this.edlCalcFullGeom[2] = dispose(this.edlCalcFullGeom?.[2]);
            this.edlCalcFullGeom = undefined;
        }
        if (this.edlFiltGeom) {
            this.edlFiltGeom[0] = dispose(this.edlFiltGeom?.[0]);
            this.edlFiltGeom[1] = dispose(this.edlFiltGeom?.[1]);
            this.edlFiltGeom = undefined;
        }
        this.edlMixGeom = dispose(this.edlMixGeom);
    }
}
/** @internal */
export var EDLMode;
(function (EDLMode) {
    EDLMode[EDLMode["Off"] = 0] = "Off";
    EDLMode[EDLMode["On"] = 1] = "On";
    EDLMode[EDLMode["Full"] = 2] = "Full";
})(EDLMode || (EDLMode = {}));
export class EyeDomeLighting {
    _bundle;
    _width;
    _height;
    _depth; // depth buffer to read from, has to be non-MS and be up to date in draw if MS is used
    _edlFinalFbo;
    _edlFinalBufs;
    _target;
    getBundle() {
        if (undefined === this._bundle) {
            this._bundle = Bundle.create(this._width, this._height);
            assert(undefined !== this._bundle);
        }
        return this._bundle;
    }
    constructor(target) {
        this._target = target;
        this._width = target.viewRect.width;
        this._height = target.viewRect.height;
    }
    init(width, height, depth) {
        this._width = width;
        this._height = height;
        this._depth = depth;
        // don't create buffers until we know we will use them (first draw)
        return true;
    }
    collectStatistics(stats) {
        const bundle = this._bundle;
        if (undefined !== bundle) {
            collectTextureStatistics(bundle.edlCalcTex1, stats);
            collectTextureStatistics(bundle.edlCalcTex2, stats);
            collectTextureStatistics(bundle.edlCalcTex4, stats);
            collectTextureStatistics(bundle.edlFiltTex2, stats);
            collectTextureStatistics(bundle.edlFiltTex4, stats);
            collectGeometryStatistics(bundle.edlCalcBasicGeom, stats);
            collectGeometryStatistics(bundle.edlCalcFullGeom?.[0], stats);
            collectGeometryStatistics(bundle.edlCalcFullGeom?.[1], stats);
            collectGeometryStatistics(bundle.edlCalcFullGeom?.[2], stats);
            collectGeometryStatistics(bundle.edlFiltGeom?.[0], stats);
            collectGeometryStatistics(bundle.edlFiltGeom?.[1], stats);
            collectGeometryStatistics(bundle.edlMixGeom, stats);
        }
    }
    get isDisposed() { return undefined === this._bundle && undefined === this._edlFinalFbo; }
    [Symbol.dispose]() {
        this._bundle = dispose(this._bundle);
        this._edlFinalFbo = dispose(this._edlFinalFbo);
    }
    reset() {
        this[Symbol.dispose]();
    }
    /** calculate EyeDomeLighting at specified quality using screen space shaders
     * returns true if succeeds
     */
    draw(edlParams) {
        if (undefined === edlParams.inputTex || undefined === this._depth || undefined === edlParams.curFbo)
            return false;
        const bundle = this.getBundle();
        if (undefined === bundle)
            return false;
        // NB: have to test and create MS buffer as well if useMsBuffers, not outputting to depth
        const finalBufs = edlParams.curFbo.getColorTargets(edlParams.useMsBuffers, 0);
        if (undefined === this._edlFinalFbo || this._edlFinalBufs?.tex !== finalBufs.tex ||
            (edlParams.useMsBuffers && this._edlFinalBufs?.msBuf !== finalBufs.msBuf)) {
            this._edlFinalFbo = dispose(this._edlFinalFbo);
            this._edlFinalBufs = finalBufs;
            const filters = [GL.MultiSampling.Filter.Linear];
            this._edlFinalFbo = FrameBuffer.create([this._edlFinalBufs.tex], undefined, edlParams.useMsBuffers && this._edlFinalBufs.msBuf ? [this._edlFinalBufs.msBuf] : undefined, filters, undefined);
            if (undefined === this._edlFinalFbo)
                return false;
        }
        const fbStack = System.instance.frameBufferStack;
        const useMsBuffers = edlParams.useMsBuffers;
        System.instance.applyRenderState(RenderState.defaults);
        // ###TODO: should radius be (optionally?) voxel based instead of pixel here?
        if (edlParams.edlMode === EDLMode.On) {
            // draw using single pass version (still 8 samples, full size)
            fbStack.execute(this._edlFinalFbo, true, useMsBuffers, () => {
                if (bundle.edlCalcBasicGeom === undefined) {
                    const ct1 = edlParams.inputTex;
                    const ctd = expectDefined(this._depth?.getHandle());
                    bundle.edlCalcBasicGeom = EDLCalcBasicGeometry.createGeometry(expectDefined(ct1.getHandle()), ctd, ct1.width, ct1.height);
                }
                const params = getDrawParams(this._target, expectDefined(bundle.edlCalcBasicGeom));
                this._target.techniques.draw(params);
            });
        }
        else { // EDLMode.Full
            // draw with full method based on original paper using full, 1/2, and 1/4 sizes
            const edlCalc2FB = [expectDefined(bundle.edlCalcFbo1), expectDefined(bundle.edlCalcFbo2), expectDefined(bundle.edlCalcFbo4)];
            if (bundle.edlCalcFullGeom === undefined) {
                const ct1 = edlParams.inputTex;
                const ct2 = bundle.edlCalcTex2;
                const ct4 = bundle.edlCalcTex4;
                const ctd = expectDefined(this._depth?.getHandle());
                bundle.edlCalcFullGeom = [EDLCalcFullGeometry.createGeometry(expectDefined(ct1.getHandle()), ctd, 1, ct1.width, ct1.height),
                    EDLCalcFullGeometry.createGeometry(expectDefined(ct1.getHandle()), ctd, 2, expectDefined(ct2?.width), expectDefined(ct2?.height)),
                    EDLCalcFullGeometry.createGeometry(expectDefined(ct1.getHandle()), ctd, 4, expectDefined(ct4?.width), expectDefined(ct4?.height))];
            }
            const edlFiltFbos = [expectDefined(bundle.edlFiltFbo2), expectDefined(bundle.edlFiltFbo4)];
            if (bundle.edlFiltGeom === undefined) {
                const ft2 = bundle.edlCalcTex2;
                const ft4 = bundle.edlCalcTex4;
                const ftd = expectDefined(this._depth?.getHandle());
                bundle.edlFiltGeom = [EDLFilterGeometry.createGeometry(expectDefined(ft2?.getHandle()), ftd, 2, expectDefined(ft2?.width), expectDefined(ft2?.height)),
                    EDLFilterGeometry.createGeometry(expectDefined(ft4?.getHandle()), ftd, 4, expectDefined(ft4?.width), expectDefined(ft4?.height))];
            }
            const gl = System.instance.context;
            // Loop through the 3 sizes calculating an edl buffer, and if not first size, then optionally filtering those
            for (let i = 0; i < 3; ++i) {
                fbStack.execute(edlCalc2FB[i], true, false, () => {
                    const colTex = edlCalc2FB[i].getColor(0);
                    gl.viewport(0, 0, colTex.width, colTex.height); // have to set viewport to current texture size
                    const params = getDrawParams(this._target, expectDefined(bundle.edlCalcFullGeom?.[i]));
                    this._target.techniques.draw(params);
                });
                if (edlParams.edlFilter && i > 0) {
                    fbStack.execute(edlFiltFbos[i - 1], true, false, () => {
                        const params = getDrawParams(this._target, expectDefined(bundle.edlFiltGeom?.[i - 1]));
                        this._target.techniques.draw(params);
                    });
                }
            }
            gl.viewport(0, 0, this._width, this._height); // Restore viewport
            // Now combine the 3 results and output
            const tex1 = expectDefined(bundle.edlCalcTex1?.getHandle());
            const tex2 = expectDefined(edlParams.edlFilter ? bundle.edlFiltTex2?.getHandle() : bundle.edlCalcTex2?.getHandle());
            const tex4 = expectDefined(edlParams.edlFilter ? bundle.edlFiltTex4?.getHandle() : bundle.edlCalcTex4?.getHandle());
            fbStack.execute(this._edlFinalFbo, true, useMsBuffers, () => {
                if (bundle.edlMixGeom === undefined) {
                    bundle.edlMixGeom = EDLMixGeometry.createGeometry(tex1, tex2, tex4);
                }
                else {
                    if (bundle.edlMixGeom.colorTexture1 !== tex1 || bundle.edlMixGeom.colorTexture2 !== tex2 || bundle.edlMixGeom.colorTexture4 !== tex4) {
                        dispose(bundle.edlMixGeom);
                        bundle.edlMixGeom = EDLMixGeometry.createGeometry(tex1, tex2, tex4);
                    }
                }
                const params = getDrawParams(this._target, expectDefined(bundle.edlMixGeom));
                this._target.techniques.draw(params);
            });
        }
        return true;
    }
}
//# sourceMappingURL=EDL.js.map