import { RenderDeviceError } from "./RenderDevice.js";
import { VertexFormat } from "./VertexFormat.js";
export class VertexBuffer {
    format;
    vertexCount;
    data;
    floats;
    gpuBuffer = null;
    dirtyStart = Number.POSITIVE_INFINITY;
    dirtyEnd = 0;
    disposed = false;
    constructor(format, vertexCount) {
        this.format = format;
        this.vertexCount = vertexCount;
        if (vertexCount <= 0 || !Number.isInteger(vertexCount)) {
            throw new Error("VertexBuffer vertexCount must be a positive integer");
        }
        this.data = new ArrayBuffer(format.stride * vertexCount);
        this.floats = new Float32Array(this.data);
        this.markDirty(0, this.data.byteLength);
    }
    get byteLength() {
        return this.data.byteLength;
    }
    get uploadedBuffer() {
        return this.gpuBuffer;
    }
    setAttribute(vertexIndex, semantic, values) {
        this.assertAlive();
        this.assertVertexIndex(vertexIndex);
        const attribute = this.format.getAttribute(semantic);
        if (values.length !== attribute.components) {
            throw new Error(`Semantic ${semantic} requires ${attribute.components} values, got ${values.length}`);
        }
        if (!values.every(Number.isFinite)) {
            throw new Error(`Semantic ${semantic} values must be finite`);
        }
        const byteOffset = vertexIndex * this.format.stride + attribute.offset;
        const floatOffset = byteOffset / 4;
        for (let i = 0; i < values.length; i += 1) {
            this.floats[floatOffset + i] = values[i] ?? 0;
        }
        this.markDirty(byteOffset, attribute.byteLength);
    }
    getAttribute(vertexIndex, semantic) {
        this.assertAlive();
        this.assertVertexIndex(vertexIndex);
        const attribute = this.format.getAttribute(semantic);
        const byteOffset = vertexIndex * this.format.stride + attribute.offset;
        const floatOffset = byteOffset / 4;
        return Array.from(this.floats.slice(floatOffset, floatOffset + attribute.components));
    }
    upload(device) {
        this.assertAlive();
        if (!this.gpuBuffer || this.gpuBuffer.disposed) {
            this.gpuBuffer = device.createBuffer("vertex", this.byteLength, new Uint8Array(this.data));
            this.clearDirty();
            return this.gpuBuffer;
        }
        if (this.isDirty()) {
            const start = alignDown(this.dirtyStart, 4);
            const end = alignUp(this.dirtyEnd, 4);
            device.updateBuffer(this.gpuBuffer, start, new Uint8Array(this.data, start, end - start));
            this.clearDirty();
        }
        return this.gpuBuffer;
    }
    getDirtyRange() {
        if (!this.isDirty()) {
            return null;
        }
        return { start: this.dirtyStart, end: this.dirtyEnd };
    }
    dispose() {
        this.gpuBuffer?.dispose();
        this.disposed = true;
    }
    assertVertexIndex(vertexIndex) {
        if (!Number.isInteger(vertexIndex) || vertexIndex < 0 || vertexIndex >= this.vertexCount) {
            throw new RangeError(`Vertex index ${vertexIndex} is out of range`);
        }
    }
    assertAlive() {
        if (this.disposed) {
            throw new RenderDeviceError("VertexBuffer is disposed", "DISPOSED_RESOURCE");
        }
    }
    markDirty(start, byteLength) {
        this.dirtyStart = Math.min(this.dirtyStart, start);
        this.dirtyEnd = Math.max(this.dirtyEnd, start + byteLength);
    }
    isDirty() {
        return this.dirtyStart <= this.dirtyEnd;
    }
    clearDirty() {
        this.dirtyStart = Number.POSITIVE_INFINITY;
        this.dirtyEnd = 0;
    }
}
function alignDown(value, alignment) {
    return Math.floor(value / alignment) * alignment;
}
function alignUp(value, alignment) {
    return Math.ceil(value / alignment) * alignment;
}
//# sourceMappingURL=VertexBuffer.js.map