export class WebGL2StateCache {
    descriptor;
    backend = "webgl2";
    currentProgram = UNSET;
    currentVertexArray = UNSET;
    currentArrayBuffer = UNSET;
    currentElementArrayBuffer = UNSET;
    currentFramebuffer = UNSET;
    currentViewport = null;
    currentScissor = null;
    currentColorMask = null;
    currentPolygonOffset = null;
    currentStencilFunc = null;
    currentStencilMask = null;
    currentStencilOp = null;
    currentActiveTextureUnit = null;
    currentDepthMask = null;
    currentDepthFunc = null;
    currentCullFace = null;
    currentBlendFunc = null;
    enabledCaps = new Map();
    textureBindings = new Map();
    samplerBindings = new Map();
    operationStats = new Map();
    constructor(descriptor) {
        this.descriptor = descriptor;
    }
    invalidate() {
        this.currentProgram = UNSET;
        this.currentVertexArray = UNSET;
        this.currentArrayBuffer = UNSET;
        this.currentElementArrayBuffer = UNSET;
        this.currentFramebuffer = UNSET;
        this.currentViewport = null;
        this.currentScissor = null;
        this.currentColorMask = null;
        this.currentPolygonOffset = null;
        this.currentStencilFunc = null;
        this.currentStencilMask = null;
        this.currentStencilOp = null;
        this.currentActiveTextureUnit = null;
        this.currentDepthMask = null;
        this.currentDepthFunc = null;
        this.currentCullFace = null;
        this.currentBlendFunc = null;
        this.enabledCaps.clear();
        this.textureBindings.clear();
        this.samplerBindings.clear();
    }
    resetStats() {
        this.operationStats.clear();
    }
    useProgram(program, apply) {
        if (this.currentProgram === program)
            return this.skip("useProgram");
        this.currentProgram = program;
        return this.issue("useProgram", apply);
    }
    bindBuffer(target, buffer, apply) {
        if (target === ARRAY_BUFFER_TARGET) {
            if (this.currentArrayBuffer === buffer)
                return this.skip("bindBuffer");
            this.currentArrayBuffer = buffer;
        }
        else if (target === ELEMENT_ARRAY_BUFFER_TARGET) {
            if (this.currentElementArrayBuffer === buffer)
                return this.skip("bindBuffer");
            this.currentElementArrayBuffer = buffer;
        }
        else {
            const key = `buffer:${target}`;
            if (this.textureBindings.get(key) === buffer)
                return this.skip("bindBuffer");
            this.textureBindings.set(key, buffer);
        }
        return this.issue("bindBuffer", apply);
    }
    bindFramebuffer(target, framebuffer, apply) {
        const key = `framebuffer:${target}`;
        if (target === FRAMEBUFFER_TARGET) {
            if (this.currentFramebuffer === framebuffer)
                return this.skip("bindFramebuffer");
            this.currentFramebuffer = framebuffer;
        }
        else if (this.textureBindings.get(key) === framebuffer) {
            return this.skip("bindFramebuffer");
        }
        else {
            this.textureBindings.set(key, framebuffer);
        }
        return this.issue("bindFramebuffer", apply);
    }
    bindVertexArray(vertexArray, apply) {
        if (this.currentVertexArray === vertexArray)
            return this.skip("bindVertexArray");
        this.currentVertexArray = vertexArray;
        this.currentElementArrayBuffer = UNSET;
        return this.issue("bindVertexArray", apply);
    }
    viewport(x, y, width, height, apply) {
        const next = [x, y, width, height];
        if (this.currentViewport && tuple4Equals(this.currentViewport, next))
            return this.skip("viewport");
        this.currentViewport = next;
        return this.issue("viewport", apply);
    }
    scissor(x, y, width, height, apply) {
        const next = [x, y, width, height];
        if (this.currentScissor && tuple4Equals(this.currentScissor, next))
            return this.skip("scissor");
        this.currentScissor = next;
        return this.issue("scissor", apply);
    }
    colorMask(red, green, blue, alpha, apply) {
        const next = [red, green, blue, alpha];
        if (this.currentColorMask && boolTuple4Equals(this.currentColorMask, next))
            return this.skip("colorMask");
        this.currentColorMask = next;
        return this.issue("colorMask", apply);
    }
    polygonOffset(factor, units, apply) {
        const next = [factor, units];
        if (this.currentPolygonOffset && this.currentPolygonOffset[0] === factor && this.currentPolygonOffset[1] === units)
            return this.skip("polygonOffset");
        this.currentPolygonOffset = next;
        return this.issue("polygonOffset", apply);
    }
    stencilFunc(func, reference, mask, apply) {
        const next = [func, reference, mask];
        if (this.currentStencilFunc && tuple3Equals(this.currentStencilFunc, next))
            return this.skip("stencilFunc");
        this.currentStencilFunc = next;
        return this.issue("stencilFunc", apply);
    }
    stencilMask(mask, apply) {
        if (this.currentStencilMask === mask)
            return this.skip("stencilMask");
        this.currentStencilMask = mask;
        return this.issue("stencilMask", apply);
    }
    stencilOp(fail, depthFail, depthPass, apply) {
        const next = [fail, depthFail, depthPass];
        if (this.currentStencilOp && tuple3Equals(this.currentStencilOp, next))
            return this.skip("stencilOp");
        this.currentStencilOp = next;
        return this.issue("stencilOp", apply);
    }
    setEnabled(capability, enabled, apply) {
        if (this.enabledCaps.get(capability) === enabled)
            return this.skip(enabled ? "enable" : "disable");
        this.enabledCaps.set(capability, enabled);
        return this.issue(enabled ? "enable" : "disable", apply);
    }
    depthMask(flag, apply) {
        if (this.currentDepthMask === flag)
            return this.skip("depthMask");
        this.currentDepthMask = flag;
        return this.issue("depthMask", apply);
    }
    depthFunc(func, apply) {
        if (this.currentDepthFunc === func)
            return this.skip("depthFunc");
        this.currentDepthFunc = func;
        return this.issue("depthFunc", apply);
    }
    cullFace(mode, apply) {
        if (this.currentCullFace === mode)
            return this.skip("cullFace");
        this.currentCullFace = mode;
        return this.issue("cullFace", apply);
    }
    blendFunc(src, dst, apply) {
        const next = [src, dst];
        if (this.currentBlendFunc && this.currentBlendFunc[0] === src && this.currentBlendFunc[1] === dst)
            return this.skip("blendFunc");
        this.currentBlendFunc = next;
        return this.issue("blendFunc", apply);
    }
    activeTexture(unit, apply) {
        if (this.currentActiveTextureUnit === unit)
            return this.skip("activeTexture");
        this.currentActiveTextureUnit = unit;
        return this.issue("activeTexture", apply);
    }
    bindTexture(target, texture, apply) {
        const key = `${this.currentActiveTextureUnit ?? -1}:${target}`;
        if (this.textureBindings.get(key) === texture)
            return this.skip("bindTexture");
        this.textureBindings.set(key, texture);
        return this.issue("bindTexture", apply);
    }
    bindSampler(textureUnit, sampler, apply) {
        if (this.samplerBindings.get(textureUnit) === sampler)
            return this.skip("bindSampler");
        this.samplerBindings.set(textureUnit, sampler);
        return this.issue("bindSampler", apply);
    }
    snapshot() {
        return {
            program: this.currentProgram === UNSET ? null : this.currentProgram,
            vertexArray: this.currentVertexArray === UNSET ? null : this.currentVertexArray,
            arrayBuffer: this.currentArrayBuffer === UNSET ? null : this.currentArrayBuffer,
            elementArrayBuffer: this.currentElementArrayBuffer === UNSET ? null : this.currentElementArrayBuffer,
            framebuffer: this.currentFramebuffer === UNSET ? null : this.currentFramebuffer,
            viewport: this.currentViewport,
            scissor: this.currentScissor,
            colorMask: this.currentColorMask,
            polygonOffset: this.currentPolygonOffset,
            stencilFunc: this.currentStencilFunc,
            stencilMask: this.currentStencilMask,
            stencilOp: this.currentStencilOp,
            activeTextureUnit: this.currentActiveTextureUnit,
            depthMask: this.currentDepthMask,
            depthFunc: this.currentDepthFunc,
            cullFace: this.currentCullFace,
            blendFunc: this.currentBlendFunc,
            enabled: Object.fromEntries(this.enabledCaps.entries()),
            textures: Object.fromEntries(this.textureBindings.entries()),
            samplers: Object.fromEntries(this.samplerBindings.entries()),
            stats: this.stats()
        };
    }
    stats() {
        let issued = 0;
        let skipped = 0;
        const byOperation = {};
        for (const [operation, stats] of this.operationStats) {
            issued += stats.issued;
            skipped += stats.skipped;
            byOperation[operation] = { ...stats };
        }
        return { issued, skipped, byOperation };
    }
    issue(operation, apply) {
        this.statsFor(operation).issued += 1;
        apply();
        return true;
    }
    skip(operation) {
        this.statsFor(operation).skipped += 1;
        return false;
    }
    statsFor(operation) {
        let stats = this.operationStats.get(operation);
        if (!stats) {
            stats = { issued: 0, skipped: 0 };
            this.operationStats.set(operation, stats);
        }
        return stats;
    }
}
const UNSET = Symbol("unset-webgl-state");
const ARRAY_BUFFER_TARGET = 0x8892;
const ELEMENT_ARRAY_BUFFER_TARGET = 0x8893;
const FRAMEBUFFER_TARGET = 0x8d40;
function tuple4Equals(left, right) {
    return left[0] === right[0] && left[1] === right[1] && left[2] === right[2] && left[3] === right[3];
}
function tuple3Equals(left, right) {
    return left[0] === right[0] && left[1] === right[1] && left[2] === right[2];
}
function boolTuple4Equals(left, right) {
    return left[0] === right[0] && left[1] === right[1] && left[2] === right[2] && left[3] === right[3];
}
//# sourceMappingURL=WebGL2StateCache.js.map