pex-context
Version:
Modern WebGL state wrapper for PEX: allocate GPU resources (textures, buffers), setup state pipelines and passes, and combine them into commands.
133 lines (113 loc) • 3.74 kB
JavaScript
import { checkProps } from "./utils.js";
/**
* @typedef {import("./types.js").PexResource} BufferOptions
* @property {Array | import("./types.js").TypedArray | ArrayBuffer} data
* @property {ctx.DataType} [type]
* @property {ctx.Usage} [usage=ctx.Usage.StaticDraw]
* @property {number} offset
*/
const allowedProps = [
"target", // Note: only at creation
"data",
"usage",
"type",
"offset",
];
function createBuffer(ctx, opts) {
checkProps(allowedProps, opts);
const gl = ctx.gl;
console.assert(
opts.target === gl.ARRAY_BUFFER || opts.target === gl.ELEMENT_ARRAY_BUFFER,
"Invalid buffer target",
);
const buffer = {
class: opts.target === gl.ARRAY_BUFFER ? "vertexBuffer" : "indexBuffer",
handle: gl.createBuffer(),
target: opts.target,
usage: opts.usage || gl.STATIC_DRAW,
_update: updateBuffer,
_dispose() {
gl.deleteBuffer(this.handle);
this.handle = null;
},
};
updateBuffer(ctx, buffer, opts);
return buffer;
}
function updateBuffer(ctx, buffer, opts) {
checkProps(allowedProps, opts);
const gl = ctx.gl;
let data = opts.data || opts;
let type = opts.type;
let offset = opts.offset || 0;
if (Array.isArray(data)) {
if (!type) {
if (buffer.target === gl.ARRAY_BUFFER) {
type = ctx.DataType.Float32;
} else if (buffer.target === gl.ELEMENT_ARRAY_BUFFER) {
type = ctx.DataType.Uint16;
}
}
const sourceData = data;
const elemSize = Array.isArray(sourceData[0]) ? sourceData[0].length : 1;
const size = elemSize * sourceData.length;
if (type === ctx.DataType.Float32) {
data = new Float32Array(elemSize === 1 ? sourceData : size);
} else if (type === ctx.DataType.Uint8) {
data = new Uint8Array(elemSize === 1 ? sourceData : size);
} else if (type === ctx.DataType.Uint16) {
data = new Uint16Array(elemSize === 1 ? sourceData : size);
} else if (type === ctx.DataType.Uint32) {
data = new Uint32Array(elemSize === 1 ? sourceData : size);
} else if (type === ctx.DataType.Int8) {
data = new Int8Array(elemSize === 1 ? sourceData : size);
}
if (elemSize > 1) {
for (let i = 0; i < sourceData.length; i++) {
for (let j = 0; j < elemSize; j++) {
const index = i * elemSize + j;
data[index] = sourceData[i][j];
}
}
}
} else if (data instanceof Float32Array) {
type = ctx.DataType.Float32;
} else if (data instanceof Uint8Array) {
type = ctx.DataType.Uint8;
} else if (data instanceof Uint16Array) {
type = ctx.DataType.Uint16;
} else if (data instanceof Uint32Array) {
type = ctx.DataType.Uint32;
} else if (data instanceof Int8Array) {
type = ctx.DataType.Int8;
} else if (data instanceof ArrayBuffer) {
// assuming type was provided
if (!type) {
if (buffer.target === gl.ARRAY_BUFFER) {
type = ctx.DataType.Float32;
} else if (buffer.target === gl.ELEMENT_ARRAY_BUFFER) {
type = ctx.DataType.Uint16;
}
}
} else {
throw new Error(`Unknown buffer data type: ${data.constructor}`);
}
buffer.type = type;
// TODO: is this a valid guess?
buffer.length =
data.length ??
data.byteLength / ctx.DataTypeConstructor[type].BYTES_PER_ELEMENT;
if (ctx.state.vertexArray) {
ctx.state.vertexArray = undefined;
gl.bindVertexArray(null);
}
// TODO: push state, and pop as this can modify existing VBO?
gl.bindBuffer(buffer.target, buffer.handle);
if (offset) {
gl.bufferSubData(buffer.target, offset, data);
} else {
gl.bufferData(buffer.target, data, buffer.usage);
}
buffer.info = ctx.DataTypeConstructor[type].name;
}
export default createBuffer;