@thi.ng/webgl
Version:
WebGL & GLSL abstraction layer
177 lines (176 loc) • 4.49 kB
JavaScript
import {
BIT_SHIFTS,
asGLType,
typedArrayType
} from "@thi.ng/api/typedarray";
import { isPlainObject } from "@thi.ng/checks/is-plain-object";
import {
DrawMode
} from "./api/model.js";
import { isGL2Context } from "./checks.js";
import { error } from "./error.js";
import { defShader } from "./shader.js";
class WebGLArrayBuffer {
gl;
buffer;
target;
mode;
data;
constructor(gl, data, target = gl.ARRAY_BUFFER, mode = gl.STATIC_DRAW, retain = false) {
this.gl = gl;
this.buffer = gl.createBuffer() || error("error creating WebGL buffer");
this.target = target;
this.mode = mode;
if (data) {
this.set(data);
if (retain) this.data = data;
}
}
bind() {
this.gl.bindBuffer(this.target, this.buffer);
return true;
}
unbind() {
this.gl.bindBuffer(this.target, null);
return true;
}
release() {
if (this.buffer) {
this.gl.deleteBuffer(this.buffer);
delete this.buffer;
}
return true;
}
/**
* Re-applies retained data (from ctor arg) using
* {@link WebGLArrayBuffer.set}. Presumably the underlying data has been
* updated elsewhere, but needs to be reflected to WebGL.
*
* @remarks
* If no data is retained, this method is a no-op.
*/
update() {
if (this.data) this.set(this.data);
}
set(data, mode = this.mode) {
this.bind();
this.gl.bufferData(this.target, data, mode);
if (this.data) this.data = data;
}
setChunk(data, byteOffset = 0) {
this.bind();
this.gl.bufferSubData(this.target, byteOffset, data);
if (this.data) {
this.data.set(
data,
byteOffset >>> BIT_SHIFTS[typedArrayType(data)]
);
}
}
}
const defBuffer = (gl, data, target = gl.ARRAY_BUFFER, mode = gl.STATIC_DRAW, retain = false) => new WebGLArrayBuffer(gl, data, target, mode, retain);
const compileModel = (gl, spec, mode = gl.STATIC_DRAW) => {
if (spec.attribPool) {
spec.attribs = compileAttribPool(
gl,
spec.attribPool,
void 0,
gl.ARRAY_BUFFER,
mode
);
} else {
__compileAttribs(gl, spec.attribs, mode);
}
if (spec.instancePool) {
spec.instances = {
attribs: compileAttribPool(
gl,
spec.instancePool,
void 0,
gl.ARRAY_BUFFER,
mode
),
num: spec.instancePool.capacity
};
} else if (spec.instances) {
__compileAttribs(gl, spec.instances.attribs, mode);
}
compileIndices(gl, spec.indices, mode);
spec.mode == null && (spec.mode = DrawMode.TRIANGLES);
if (isPlainObject(spec.shader)) {
spec.shader = defShader(gl, spec.shader);
}
return spec;
};
const __initBuffer = (gl, src, type, mode) => {
if (src.buffer) {
src.data && src.buffer.set(src.data);
} else {
src.buffer = new WebGLArrayBuffer(gl, src.data, type, mode, src.retain);
}
};
const __compileAttribs = (gl, attribs, mode) => {
if (attribs) {
for (let id in attribs) {
__initBuffer(gl, attribs[id], gl.ARRAY_BUFFER, mode);
}
}
return attribs;
};
const compileIndices = (gl, index, mode = gl.STATIC_DRAW) => {
if (index) {
__initBuffer(gl, index, gl.ELEMENT_ARRAY_BUFFER, mode);
}
return index;
};
const compileVAO = (gl, spec) => {
if (spec.shader) {
const isGL2 = isGL2Context(gl);
const ext = !isGL2 ? gl.getExtension("OES_vertex_array_object") : null;
if (isGL2 || ext) {
let vao;
if (isGL2) {
vao = gl.createVertexArray();
gl.bindVertexArray(vao);
} else {
vao = ext.createVertexArrayOES();
ext.bindVertexArrayOES(vao);
}
!!vao && error("error creating VAO");
spec.shader.bindAttribs(spec.attribs);
if (spec.indices) {
spec.indices.buffer.bind();
}
spec.shader.unbind(null);
if (isGL2) {
gl.bindVertexArray(null);
} else {
ext.bindVertexArrayOES(null);
}
return vao;
}
}
};
const compileAttribPool = (gl, pool, ids, target = gl.ARRAY_BUFFER, mode = gl.STATIC_DRAW) => {
const buf = defBuffer(gl, pool.bytes(), target, mode, true);
const spec = {};
for (let id of ids || Object.keys(pool.specs)) {
const attr = pool.specs[id];
spec[id] = {
buffer: buf,
size: attr.size,
type: asGLType(attr.type),
stride: pool.byteStride,
offset: attr.byteOffset
};
}
return spec;
};
export {
WebGLArrayBuffer,
compileAttribPool,
compileIndices,
compileModel,
compileVAO,
defBuffer
};