/*---------------------------------------------------------------------------------------------
* 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 { System } from "./System";
/** Provides convenience methods for creating a BufferHandleLinkage interface. */
class BufferHandleLinkage {
    constructor() { }
    static create(buffer, params) {
        return { buffer, params };
    }
    static clone(linkage) {
        const clonedParams = [];
        for (const param of linkage.params) {
            clonedParams.push(BufferParameters.clone(param));
        }
        return BufferHandleLinkage.create(linkage.buffer, clonedParams);
    }
}
/**
 * Provides convenience methods for creating a BuffersParameter interface.
 * @internal
 */
export var BufferParameters;
(function (BufferParameters) {
    function create(glAttribLoc, glSize, glType, glNormalized, glStride, glOffset, glInstanced) {
        return { glAttribLoc, glSize, glType, glNormalized, glStride, glOffset, glInstanced };
    }
    BufferParameters.create = create;
    function clone(params) {
        return BufferParameters.create(params.glAttribLoc, params.glSize, params.glType, params.glNormalized, params.glStride, params.glOffset, params.glInstanced);
    }
    BufferParameters.clone = clone;
})(BufferParameters || (BufferParameters = {}));
/**
 * An abstract class which specifies an interface for binding and unbinding vertex buffers and their associated state.
 * @internal
 */
export class BuffersContainer {
    static create() {
        return new BuffersContainer(System.instance.context);
    }
    constructor(context) {
        this.linkages = [];
        this._context = context;
        this._vao = new VAOHandle(this._context);
    }
    // NB: BufferHandle objects contained within BufferHandleLinkage entries are disposed where they are created because they could be shared among multiple BuffersContainer objects.
    dispose() {
        this._vao.dispose();
    }
    get isDisposed() {
        return this._vao.isDisposed;
    }
    addBuffer(buffer, params) {
        const linkage = BufferHandleLinkage.create(buffer, params);
        this.linkages.push(linkage);
        this._bindLinkage(linkage);
    }
    appendLinkages(linkages) {
        for (const linkage of linkages) {
            this.linkages.push(BufferHandleLinkage.clone(linkage));
            this._bindLinkage(linkage);
        }
    }
    _bindLinkage(linkage) {
        this.bind();
        linkage.buffer.bind();
        for (const p of linkage.params) {
            System.instance.context.enableVertexAttribArray(p.glAttribLoc);
            if (p.glInstanced) {
                System.instance.vertexAttribDivisor(p.glAttribLoc, 1);
            }
            System.instance.context.vertexAttribPointer(p.glAttribLoc, p.glSize, p.glType, p.glNormalized, p.glStride, p.glOffset);
        }
        this.unbind();
    }
    bind() {
        this._vao.bind();
    }
    unbind() {
        VAOHandle.unbind(this._context);
    }
}
/** A handle to a WebGLVertexArrayObjectOES.
 * The WebGLVertexArrayObjectOES is allocated by the constructor and should be freed by a call to dispose().
 * @internal
 */
export class VAOHandle {
    /** Allocates the WebGLVertexArrayObjectOES using the supplied context. Free the WebGLVertexArrayObjectOES using dispose() */
    constructor(context) {
        this._context = context;
        const arrayObject = this._context.createVertexArray();
        // vaoExt.createVertexArrayOES() returns WebGLVertexArrayObjectOES | null...
        if (null !== arrayObject) {
            this._arrayObject = arrayObject;
        }
        else {
            this._arrayObject = undefined;
        }
        assert(!this.isDisposed);
    }
    get isDisposed() { return this._arrayObject === undefined; }
    /** Frees the WebGL vertex array object */
    dispose() {
        if (!this.isDisposed) {
            this._context.deleteVertexArray(this._arrayObject);
            this._arrayObject = undefined;
        }
    }
    /** Binds this vertex array object */
    bind() {
        if (undefined !== this._arrayObject) {
            this._context.bindVertexArray(this._arrayObject);
        }
    }
    /** Ensures no vertex array object is bound */
    static unbind(context) {
        context.bindVertexArray(null);
    }
}
/**
 * A handle to a WebGLBuffer, such as a vertex or index buffer.
 * The WebGLBuffer is allocated by the constructor and should be freed by a call to dispose().
 * @internal
 */
export class BufferHandle {
    /** Allocates the WebGLBuffer using the supplied context. Free the WebGLBuffer using dispose() */
    constructor(target) {
        this._bytesUsed = 0;
        this._target = target;
        const glBuffer = System.instance.context.createBuffer();
        // gl.createBuffer() returns WebGLBuffer | null...
        if (null !== glBuffer) {
            this._glBuffer = glBuffer;
        }
        else {
            this._glBuffer = undefined;
        }
        assert(!this.isDisposed);
    }
    get isDisposed() { return this._glBuffer === undefined; }
    get bytesUsed() { return this._bytesUsed; }
    /** Frees the WebGL buffer */
    dispose() {
        if (!this.isDisposed) {
            System.instance.context.deleteBuffer(this._glBuffer);
            this._glBuffer = undefined;
            this._bytesUsed = 0;
        }
    }
    /** Binds this buffer to the target specified during construction */
    bind() {
        if (undefined !== this._glBuffer) {
            System.instance.context.bindBuffer(this._target, this._glBuffer);
        }
    }
    /** Sets the specified target to be bound to no buffer */
    unbind() { System.instance.context.bindBuffer(this._target, null); }
    /** Binds this buffer to the target specified at construction and sets the buffer's data store. */
    bindData(data, usage = GL.Buffer.Usage.StaticDraw) {
        this.bind();
        System.instance.context.bufferData(this._target, data, usage);
        this.unbind();
        this._bytesUsed = data.byteLength;
    }
    /** Creates a BufferHandle and binds its data */
    static createBuffer(target, data, usage = GL.Buffer.Usage.StaticDraw) {
        const handle = new BufferHandle(target);
        if (handle.isDisposed) {
            return undefined;
        }
        handle.bindData(data, usage);
        return handle;
    }
    /** Creates a BufferHandle and binds its data */
    static createArrayBuffer(data, usage = GL.Buffer.Usage.StaticDraw) {
        return BufferHandle.createBuffer(GL.Buffer.Target.ArrayBuffer, data, usage);
    }
    isBound(binding) { return System.instance.context.getParameter(binding) === this._glBuffer; }
}
function setScale(index, value, array) {
    array[index] = 0.0 !== value ? 1.0 / value : value;
}
/**
 * Converts 2d quantization parameters to a format appropriate for submittal to the GPU.
 * params[0] = origin.x
 * params[1] = origin.y
 * params[2] = scale.x
 * params[3] = scale.y
 * @internal
 */
export function qparams2dToArray(params) {
    const arr = new Float32Array(4);
    arr[0] = params.origin.x;
    arr[1] = params.origin.y;
    setScale(2, params.scale.x, arr);
    setScale(3, params.scale.y, arr);
    return arr;
}
/** @internal */
export function qorigin3dToArray(qorigin) {
    const origin = new Float32Array(3);
    origin[0] = qorigin.x;
    origin[1] = qorigin.y;
    origin[2] = qorigin.z;
    return origin;
}
/** @internal */
export function qscale3dToArray(qscale) {
    const scale = new Float32Array(3);
    setScale(0, qscale.x, scale);
    setScale(1, qscale.y, scale);
    setScale(2, qscale.z, scale);
    return scale;
}
/** Converts 3d quantization params to a pair of Float32Arrays
 * @internal
 */
export function qparams3dToArray(params) {
    const origin = qorigin3dToArray(params.origin);
    const scale = qscale3dToArray(params.scale);
    return { origin, scale };
}
/** A handle to a WebGLBuffer intended to hold quantized 2d points
 * @internal
 */
export class QBufferHandle2d extends BufferHandle {
    constructor(qParams) {
        super(GL.Buffer.Target.ArrayBuffer);
        this.params = qparams2dToArray(qParams);
    }
    static create(qParams, data) {
        const handle = new QBufferHandle2d(qParams);
        if (handle.isDisposed) {
            return undefined;
        }
        handle.bindData(data);
        return handle;
    }
}
/* A handle to a WebGLBuffer intended to hold quantized 3d points
 * @internal
 */
export class QBufferHandle3d extends BufferHandle {
    constructor(qParams) {
        super(GL.Buffer.Target.ArrayBuffer);
        this.origin = qorigin3dToArray(qParams.origin);
        this.scale = qscale3dToArray(qParams.scale);
    }
    static create(qParams, data) {
        const handle = new QBufferHandle3d(qParams);
        if (handle.isDisposed) {
            return undefined;
        }
        handle.bindData(data);
        return handle;
    }
}
//# sourceMappingURL=AttributeBuffers.js.map