/*---------------------------------------------------------------------------------------------
* 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 } from "@itwin/core-bentley";
import { GL } from "./GL";
import { RenderBuffer, RenderBufferMultiSample } from "./RenderBuffer";
import { System } from "./System";
import { TextureHandle } from "./Texture";
/** @internal */
export class FrameBuffer {
    get isDisposed() { return this._fbo === undefined; }
    get isBound() { return 1 /* FrameBufferBindState.Bound */ <= this._bindState && 4 /* FrameBufferBindState.BoundWithAttachmentsMultisampled */ >= this._bindState; }
    get isBoundMultisampled() { return 3 /* FrameBufferBindState.BoundMultisampled */ === this._bindState || 4 /* FrameBufferBindState.BoundWithAttachmentsMultisampled */ === this._bindState; }
    get isSuspended() { return 5 /* FrameBufferBindState.Suspended */ === this._bindState; }
    get isMultisampled() { return this._colorMsBuffers.length > 0 || undefined !== this.depthBufferMs; }
    getColor(ndx) {
        assert(ndx < this._colorTextures.length);
        if (ndx < this._colorMsBuffers.length && this._colorMsBuffers[ndx].isDirty) {
            this.blitMsBuffersToTextures(false, ndx);
        }
        return this._colorTextures[ndx];
    }
    getColorTargets(useMSBuffers, ndx) {
        let msBuf;
        if (useMSBuffers) {
            assert(ndx < this._colorMsBuffers.length);
            msBuf = this._colorMsBuffers[ndx];
        }
        assert(ndx < this._colorTextures.length);
        return { tex: this._colorTextures[ndx], msBuf };
    }
    constructor(fbo, colorTextures, depthBuffer, colorMsBuffers, msFilters, depthBufferMs) {
        this._bindState = 0 /* FrameBufferBindState.Unbound */;
        this._colorTextures = [];
        this._colorMsBuffers = [];
        this._colorAttachments = [];
        this._colorMsFilters = [];
        this._fbo = fbo;
        const gl = System.instance.context;
        this.bind(false);
        let i = 0;
        for (const colTex of colorTextures) {
            const attachmentEnum = gl.COLOR_ATTACHMENT0 + i;
            this._colorAttachments.push(attachmentEnum);
            this._colorTextures.push(colTex);
            const texHandle = colTex.getHandle();
            if (undefined !== texHandle)
                gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentEnum, gl.TEXTURE_2D, texHandle, 0);
            i++;
        }
        if (depthBuffer !== undefined) {
            this.depthBuffer = depthBuffer;
            const dbHandle = depthBuffer.getHandle();
            if (undefined !== dbHandle) {
                if (depthBuffer instanceof RenderBuffer) {
                    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, dbHandle);
                }
                else if (depthBuffer instanceof RenderBufferMultiSample) {
                    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, dbHandle);
                }
                else {
                    // Looks like we only get a 24 bit depth buffer anyway, so use a 24-8 with a stencil.
                    // gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, dbHandle, 0);
                    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, dbHandle, 0);
                }
            }
        }
        this.unbind();
        if (undefined !== colorMsBuffers && colorMsBuffers.length === colorTextures.length && undefined !== msFilters && msFilters.length === colorMsBuffers.length) {
            // Create a matching FBO with multisampling render buffers.
            const fbo2 = System.instance.context.createFramebuffer();
            if (null !== fbo2) {
                this._fboMs = fbo2;
                this.bind(false, true);
                i = 0;
                for (const colMsBuff of colorMsBuffers) {
                    const attachmentEnum = gl.COLOR_ATTACHMENT0 + i;
                    this._colorMsBuffers.push(colMsBuff);
                    this._colorMsFilters.push(msFilters[i]);
                    const msBuffHandle = colMsBuff.getHandle();
                    if (undefined !== msBuffHandle)
                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachmentEnum, gl.RENDERBUFFER, msBuffHandle);
                    i++;
                }
                if (depthBufferMs !== undefined) {
                    this.depthBufferMs = depthBufferMs;
                    const dbHandleMs = depthBufferMs.getHandle();
                    if (undefined !== dbHandleMs) {
                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, dbHandleMs);
                    }
                }
                this.unbind();
            }
        }
    }
    static create(colorTextures, depthBuffer, colorMsBuffers, msFilters, depthBufferMs) {
        const fbo = System.instance.context.createFramebuffer();
        if (null === fbo) {
            return undefined;
        }
        return new FrameBuffer(fbo, colorTextures, depthBuffer, colorMsBuffers, msFilters, depthBufferMs);
    }
    dispose() {
        // NB: The FrameBuffer does not *own* the textures and depth buffer.
        if (!this.isDisposed) {
            System.instance.context.deleteFramebuffer(this._fbo);
            this._fbo = undefined;
            if (undefined !== this._fboMs) {
                System.instance.context.deleteFramebuffer(this._fboMs);
                this._fboMs = undefined;
            }
        }
    }
    bind(bindAttachments = false, bindMS = false) {
        assert(undefined !== this._fbo);
        assert(!this.isBound);
        if (undefined === this._fbo)
            return false;
        const gl = System.instance.context;
        if (bindMS && undefined !== this._fboMs) {
            gl.bindFramebuffer(GL.FrameBuffer.TARGET, this._fboMs);
            this._bindState = 3 /* FrameBufferBindState.BoundMultisampled */;
        }
        else {
            gl.bindFramebuffer(GL.FrameBuffer.TARGET, this._fbo);
            this._bindState = 1 /* FrameBufferBindState.Bound */;
        }
        if (bindAttachments) {
            System.instance.setDrawBuffers(this._colorAttachments);
            this._bindState++;
        }
        return true;
    }
    unbind() {
        assert(this.isBound);
        System.instance.context.bindFramebuffer(GL.FrameBuffer.TARGET, null);
        this._bindState = 0 /* FrameBufferBindState.Unbound */;
    }
    suspend() {
        assert(this.isBound);
        this._bindState = 5 /* FrameBufferBindState.Suspended */;
    }
    markTargetsDirty() {
        for (const msBuff of this._colorMsBuffers) {
            msBuff.markBufferDirty(true);
        }
        if (undefined !== this.depthBufferMs)
            this.depthBufferMs.markBufferDirty(true);
    }
    /** blitDepth is true to blit the depth/stencil buffer. ndx is index of single attachment to blit.
     * All color attachments are blitted if ndx is undefined, none are blitted if ndx is -1.
     */
    blitMsBuffersToTextures(blitDepth, ndx) {
        if (!this._fboMs)
            return;
        System.instance.frameBufferStack.suspend();
        const gl2 = System.instance.context;
        const attachments = [];
        const max = (undefined === ndx ? this._colorMsBuffers.length : ndx + 1);
        for (let i = 0; i < max; ++i) {
            if (undefined !== ndx && i < ndx) {
                attachments.push(gl2.NONE); // skip this one, but first add a NONE for it in the attachment list
                continue;
            }
            if (this._colorMsBuffers[i].isDirty) {
                gl2.bindFramebuffer(gl2.READ_FRAMEBUFFER, this._fboMs);
                gl2.readBuffer(this._colorAttachments[i]);
                gl2.bindFramebuffer(gl2.DRAW_FRAMEBUFFER, this._fbo);
                attachments.push(this._colorAttachments[i]);
                gl2.drawBuffers(attachments);
                attachments.pop();
                attachments.push(gl2.NONE);
                gl2.blitFramebuffer(0, 0, this._colorTextures[i].width, this._colorTextures[i].height, 0, 0, this._colorTextures[i].width, this._colorTextures[i].height, GL.BufferBit.Color, this._colorMsFilters[i]);
                this._colorMsBuffers[i].markBufferDirty(false);
                if (undefined !== ndx && i === ndx)
                    break;
            }
        }
        if (blitDepth && undefined !== this.depthBuffer && undefined !== this.depthBufferMs && this.depthBufferMs.isDirty) {
            const mask = GL.BufferBit.Depth; // (this.depthBuffer instanceof RenderBuffer ? GL.BufferBit.Depth : GL.BufferBit.Depth | GL.BufferBit.Stencil);
            gl2.bindFramebuffer(gl2.READ_FRAMEBUFFER, this._fboMs);
            gl2.bindFramebuffer(gl2.DRAW_FRAMEBUFFER, this._fbo);
            gl2.blitFramebuffer(0, 0, this.depthBuffer.width, this.depthBuffer.height, 0, 0, this.depthBuffer.width, this.depthBuffer.height, mask, GL.MultiSampling.Filter.Nearest);
            this.depthBufferMs.markBufferDirty(false);
        }
        gl2.bindFramebuffer(gl2.READ_FRAMEBUFFER, null);
        gl2.bindFramebuffer(gl2.DRAW_FRAMEBUFFER, null);
        System.instance.frameBufferStack.resume();
    }
    /** invDepth is true to invalidate depth buffer. invStencil is true to invalidate stencil buffer. ndx is index of single color attachment to invalidate.
     * All color attachments are invalidated if ndx is undefined, none are invalidated if ndx is -1.
     * Set withMultiSampling to true to invalidate the MS buffers.
     */
    invalidate(invDepth, invStencil, withMultiSampling, indices) {
        const gl = System.instance.context;
        const attachments = invDepth ? (invStencil ? [gl.DEPTH_STENCIL_ATTACHMENT] : [System.instance.context.DEPTH_ATTACHMENT]) : (invDepth ? [gl.STENCIL_ATTACHMENT] : []);
        if (undefined !== indices) {
            if (indices.length > 0) {
                for (const i of indices)
                    attachments.push(gl.COLOR_ATTACHMENT0 + i);
            }
        }
        else {
            attachments.concat(this._colorAttachments);
        }
        System.instance.frameBufferStack.execute(this, true, withMultiSampling, () => {
            System.instance.invalidateFrameBuffer(attachments);
        });
    }
    // Chiefly for debugging currently - assumes RGBA, unsigned byte, want all pixels.
    get debugPixels() {
        if (!this.isBound || 0 === this._colorTextures.length || !(this._colorTextures[0] instanceof TextureHandle))
            return undefined;
        const tex = this._colorTextures[0];
        if (GL.Texture.Format.Rgba !== tex.format || GL.Texture.DataType.UnsignedByte !== tex.dataType)
            return undefined;
        const buffer = new Uint8Array(tex.width * tex.height * 4);
        for (let i = 0; i < buffer.length; i += 4) {
            buffer[i] = 0xba;
            buffer[i + 1] = 0xad;
            buffer[i + 2] = 0xf0;
            buffer[i + 3] = 0x0d;
        }
        System.instance.context.readPixels(0, 0, tex.width, tex.height, tex.format, tex.dataType, buffer);
        return buffer;
    }
}
/** @internal */
export class FrameBufferStack {
    constructor() {
        // FrameBuffers within this array are not owned, as this is only a storage device holding references
        this._stack = [];
    }
    get _top() { return !this.isEmpty ? this._stack[this._stack.length - 1] : undefined; }
    push(fbo, withAttachments, withMultSampling) {
        if (undefined !== this._top) {
            this._top.fbo.suspend();
        }
        assert(!fbo.isBound);
        fbo.bind(withAttachments, withMultSampling);
        assert(fbo.isBound);
        this._stack.push({ fbo, withAttachments, withMultSampling });
    }
    pop() {
        assert(!this.isEmpty);
        if (undefined === this._top) {
            return;
        }
        const fbo = this._top.fbo;
        this._stack.pop();
        assert(fbo.isBound);
        fbo.unbind();
        assert(!fbo.isBound);
        if (this.isEmpty) {
            System.instance.context.bindFramebuffer(GL.FrameBuffer.TARGET, null);
        }
        else {
            const top = this._top;
            assert(top.fbo.isSuspended);
            top.fbo.bind(top.withAttachments, top.withMultSampling);
            assert(top.fbo.isBound);
        }
    }
    get currentColorBuffer() {
        assert(!this.isEmpty);
        return undefined !== this._top ? this._top.fbo.getColor(0) : undefined;
    }
    get currentFbMultisampled() {
        return undefined !== this._top ? this._top.fbo.isBoundMultisampled : false;
    }
    get isEmpty() { return 0 === this._stack.length; }
    execute(fbo, withAttachments, withMultSampling, func) {
        this.push(fbo, withAttachments, withMultSampling);
        func();
        this.pop();
    }
    markTargetsDirty() {
        const top = this._top;
        if (undefined !== top)
            top.fbo.markTargetsDirty();
    }
    suspend() {
        if (undefined !== this._top) {
            this._top.fbo.suspend();
        }
    }
    resume() {
        if (undefined === this._top) {
            return;
        }
        if (this.isEmpty) {
            System.instance.context.bindFramebuffer(GL.FrameBuffer.TARGET, null);
        }
        else {
            const top = this._top;
            top.fbo.bind(top.withAttachments, top.withMultSampling);
            assert(top.fbo.isBound);
        }
    }
}
//# sourceMappingURL=FrameBuffer.js.map