/*---------------------------------------------------------------------------------------------
* 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 { GL } from "./GL";
import { System } from "./System";
/** @internal */
export class RenderStateFlags {
    constructor(src) {
        this.cull = false;
        this.depthTest = false;
        this.blend = false;
        this.stencilTest = false;
        this.depthMask = true;
        this.colorWrite = true;
        if (src) {
            this.copyFrom(src);
        }
    }
    copyFrom(src) {
        this.cull = src.cull;
        this.depthTest = src.depthTest;
        this.blend = src.blend;
        this.stencilTest = src.stencilTest;
        this.depthMask = src.depthMask;
        this.colorWrite = src.colorWrite;
    }
    clone(result) {
        if (!result) {
            return new RenderStateFlags(this);
        }
        else {
            result.copyFrom(this);
            return result;
        }
    }
    equals(rhs) {
        return this.cull === rhs.cull
            && this.depthTest === rhs.depthTest
            && this.blend === rhs.blend
            && this.stencilTest === rhs.stencilTest
            && this.depthMask === rhs.depthMask
            && this.colorWrite === rhs.colorWrite;
    }
    apply(previousFlags) {
        RenderStateFlags.enableOrDisable(this.cull, GL.Capability.CullFace, previousFlags.cull);
        RenderStateFlags.enableOrDisable(this.depthTest, GL.Capability.DepthTest, previousFlags.depthTest);
        RenderStateFlags.enableOrDisable(this.blend, GL.Capability.Blend, previousFlags.blend);
        RenderStateFlags.enableOrDisable(this.stencilTest, GL.Capability.StencilTest, previousFlags.stencilTest);
        if (previousFlags.depthMask !== this.depthMask) {
            System.instance.context.depthMask(this.depthMask);
        }
        if (previousFlags.colorWrite !== this.colorWrite) {
            System.instance.context.colorMask(this.colorWrite, this.colorWrite, this.colorWrite, this.colorWrite);
        }
    }
    static enableOrDisable(currentFlag, value, previousFlag) {
        if (currentFlag !== previousFlag) {
            const gl = System.instance.context;
            if (currentFlag) {
                gl.enable(value);
            }
            else {
                gl.disable(value);
            }
        }
    }
}
/** @internal */
export class RenderStateBlend {
    constructor(src) {
        this.color = [0.0, 0.0, 0.0, 0.0];
        this.equationRgb = GL.BlendEquation.Default;
        this.equationAlpha = GL.BlendEquation.Default;
        this.functionSourceRgb = GL.BlendFactor.DefaultSrc;
        this.functionSourceAlpha = GL.BlendFactor.DefaultSrc;
        this.functionDestRgb = GL.BlendFactor.DefaultDst;
        this.functionDestAlpha = GL.BlendFactor.DefaultDst;
        if (src) {
            this.copyFrom(src);
        }
    }
    apply(previousBlend) {
        const gl = System.instance.context;
        if (previousBlend === undefined || !this.equalColors(previousBlend)) {
            gl.blendColor(this.color[0], this.color[1], this.color[2], this.color[3]);
        }
        if (previousBlend === undefined || previousBlend.equationRgb !== this.equationRgb || previousBlend.equationAlpha !== this.equationAlpha) {
            gl.blendEquationSeparate(this.equationRgb, this.equationAlpha);
        }
        if (previousBlend === undefined || previousBlend.functionSourceRgb !== this.functionSourceRgb || previousBlend.functionSourceAlpha !== this.functionSourceAlpha
            || previousBlend.functionDestRgb !== this.functionDestRgb || previousBlend.functionDestAlpha !== this.functionDestAlpha) {
            gl.blendFuncSeparate(this.functionSourceRgb, this.functionDestRgb, this.functionSourceAlpha, this.functionDestAlpha);
        }
    }
    copyFrom(src) {
        this.setColor(src.color);
        this.equationRgb = src.equationRgb;
        this.equationAlpha = src.equationAlpha;
        this.functionSourceRgb = src.functionSourceRgb;
        this.functionSourceAlpha = src.functionSourceAlpha;
        this.functionDestRgb = src.functionDestRgb;
        this.functionDestAlpha = src.functionDestAlpha;
    }
    clone(result) {
        if (!result) {
            return new RenderStateBlend(this);
        }
        else {
            result.copyFrom(this);
            return result;
        }
    }
    equals(rhs) {
        return this.equalColors(rhs)
            && this.equationRgb === rhs.equationRgb
            && this.equationAlpha === rhs.equationAlpha
            && this.functionSourceRgb === rhs.functionSourceRgb
            && this.functionSourceAlpha === rhs.functionSourceAlpha
            && this.functionDestRgb === rhs.functionDestRgb
            && this.functionDestAlpha === rhs.functionDestAlpha;
    }
    equalColors(rhs) {
        return this.color[0] === rhs.color[0] &&
            this.color[1] === rhs.color[1] &&
            this.color[2] === rhs.color[2] &&
            this.color[3] === rhs.color[3];
    }
    setColor(color) {
        this.color[0] = color[0];
        this.color[1] = color[1];
        this.color[2] = color[2];
        this.color[3] = color[3];
    }
    setBlendFunc(src, dst) {
        this.setBlendFuncSeparate(src, src, dst, dst);
    }
    setBlendFuncSeparate(srcRgb, srcAlpha, dstRgb, dstAlpha) {
        this.functionSourceRgb = srcRgb;
        this.functionSourceAlpha = srcAlpha;
        this.functionDestRgb = dstRgb;
        this.functionDestAlpha = dstAlpha;
    }
}
/** @internal */
export class RenderStateStencilOperation {
    constructor(src) {
        this.fail = GL.StencilOperation.Default;
        this.zFail = GL.StencilOperation.Default;
        this.zPass = GL.StencilOperation.Default;
        if (src) {
            this.copyFrom(src);
        }
    }
    copyFrom(src) {
        this.fail = src.fail;
        this.zFail = src.zFail;
        this.zPass = src.zPass;
    }
    clone(result) {
        if (!result) {
            return new RenderStateStencilOperation(this);
        }
        else {
            result.copyFrom(this);
            return result;
        }
    }
    equals(rhs) {
        return this.fail === rhs.fail
            && this.zFail === rhs.zFail
            && this.zPass === rhs.zPass;
    }
}
/** @internal */
export class RenderStateStencilFunction {
    constructor(src) {
        this.function = GL.StencilFunction.Default;
        this.ref = 0;
        this.mask = 0xFFFFFFFF;
        if (src) {
            this.copyFrom(src);
        }
    }
    copyFrom(src) {
        this.function = src.function;
        this.ref = src.ref;
        this.mask = src.mask;
    }
    clone(result) {
        if (!result) {
            return new RenderStateStencilFunction(this);
        }
        else {
            result.copyFrom(this);
            return result;
        }
    }
    equals(rhs) {
        return this.function === rhs.function
            && this.ref === rhs.ref
            && this.mask === rhs.mask;
    }
}
/** @internal */
export class RenderStateStencil {
    constructor(src) {
        this.frontFunction = new RenderStateStencilFunction();
        this.backFunction = new RenderStateStencilFunction();
        this.frontOperation = new RenderStateStencilOperation();
        this.backOperation = new RenderStateStencilOperation();
        if (src) {
            this.copyFrom(src);
        }
    }
    apply(previousStencil) {
        const gl = System.instance.context;
        if (previousStencil === undefined || !previousStencil.frontFunction.equals(this.frontFunction)) {
            gl.stencilFuncSeparate(GL.CullFace.Front, this.frontFunction.function, this.frontFunction.ref, this.frontFunction.mask);
        }
        if (previousStencil === undefined || !previousStencil.backFunction.equals(this.backFunction)) {
            gl.stencilFuncSeparate(GL.CullFace.Back, this.backFunction.function, this.backFunction.ref, this.backFunction.mask);
        }
        if (previousStencil === undefined || !previousStencil.frontOperation.equals(this.frontOperation)) {
            gl.stencilOpSeparate(GL.CullFace.Front, this.frontOperation.fail, this.frontOperation.zFail, this.frontOperation.zPass);
        }
        if (previousStencil === undefined || !previousStencil.backOperation.equals(this.backOperation)) {
            gl.stencilOpSeparate(GL.CullFace.Back, this.backOperation.fail, this.backOperation.zFail, this.backOperation.zPass);
        }
    }
    copyFrom(src) {
        this.frontFunction.copyFrom(src.frontFunction);
        this.backFunction.copyFrom(src.backFunction);
        this.frontOperation.copyFrom(src.frontOperation);
        this.backOperation.copyFrom(src.backOperation);
    }
    clone(result) {
        if (!result) {
            return new RenderStateStencil(this);
        }
        else {
            result.copyFrom(this);
            return result;
        }
    }
    equals(rhs) {
        return this.frontFunction.equals(rhs.frontFunction)
            && this.backFunction.equals(rhs.backFunction)
            && this.frontOperation.equals(rhs.frontOperation)
            && this.backOperation.equals(rhs.backOperation);
    }
}
/** Encapsulates the state of an OpenGL context.
 * to modify the context for a rendering operation, do *not* directly call
 * functions like glDepthMask(), glBlendFunc(), etc - otherwise such calls may adversely
 * affect subsequent rendering operations.
 * Instead, set up a RenderState as desired and invoke System.instance.applyRenderState()
 * The context tracks the most-recently applied RenderState, allowing it to minimize
 * the number of GL state changes actually invoked, improving performance.
 * @internal
 */
export class RenderState {
    constructor(src) {
        this.flags = new RenderStateFlags();
        this.blend = new RenderStateBlend();
        this.stencil = new RenderStateStencil();
        this.frontFace = GL.FrontFace.Default;
        this.cullFace = GL.CullFace.Default;
        this.depthFunc = GL.DepthFunc.Default;
        this.stencilMask = 0xFFFFFFFF;
        if (src) {
            this.copyFrom(src);
        }
    }
    copyFrom(src) {
        this.flags.copyFrom(src.flags);
        this.blend.copyFrom(src.blend);
        this.stencil.copyFrom(src.stencil);
        this.frontFace = src.frontFace;
        this.cullFace = src.cullFace;
        this.depthFunc = src.depthFunc;
        this.stencilMask = src.stencilMask;
    }
    clone(result) {
        if (!result) {
            return new RenderState(this);
        }
        else {
            result.copyFrom(this);
            return result;
        }
    }
    set clockwiseFrontFace(clockwise) {
        this.frontFace = clockwise ? GL.FrontFace.Clockwise : GL.FrontFace.CounterClockwise;
    }
    equals(rhs) {
        return this.flags.equals(rhs.flags)
            && this.blend.equals(rhs.blend)
            && this.stencil.equals(rhs.stencil)
            && this.frontFace === rhs.frontFace
            && this.cullFace === rhs.cullFace
            && this.depthFunc === rhs.depthFunc
            && this.stencilMask === rhs.stencilMask;
    }
    apply(prevState) {
        this.flags.apply(prevState.flags);
        if (this.flags.blend) {
            if (prevState.flags.blend)
                this.blend.apply(prevState.blend);
            else
                this.blend.apply();
        }
        if (this.flags.cull) {
            if (!prevState.flags.cull || prevState.cullFace !== this.cullFace) {
                System.instance.context.cullFace(this.cullFace);
            }
        }
        if (this.flags.depthTest) {
            if (!prevState.flags.depthTest || prevState.depthFunc !== this.depthFunc) {
                System.instance.context.depthFunc(this.depthFunc);
            }
        }
        if (this.flags.stencilTest) {
            if (prevState.flags.stencilTest)
                this.stencil.apply(prevState.stencil);
            else
                this.stencil.apply();
        }
        if (this.frontFace !== prevState.frontFace) {
            System.instance.context.frontFace(this.frontFace);
        }
        if (this.stencilMask !== prevState.stencilMask) {
            System.instance.context.stencilMask(this.stencilMask);
        }
    }
}
RenderState.defaults = new RenderState();
Object.freeze(RenderState.defaults);
//# sourceMappingURL=RenderState.js.map