253 lines
7.6 KiB
JavaScript
253 lines
7.6 KiB
JavaScript
|
|
||
|
function assert(condition, message)
|
||
|
{
|
||
|
if (!condition)
|
||
|
{
|
||
|
throw new Error(message || "Assertion failed");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function glCompileShader(gl, type, name, source)
|
||
|
{
|
||
|
console.log("Compiling " + name);
|
||
|
|
||
|
// Compile the shader
|
||
|
let shader = gl.createShader(type);
|
||
|
gl.shaderSource(shader, source);
|
||
|
gl.compileShader(shader);
|
||
|
|
||
|
// Report any errors
|
||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
|
||
|
{
|
||
|
console.log("Error compiling " + name);
|
||
|
console.log(gl.getShaderInfoLog(shader));
|
||
|
console.trace();
|
||
|
}
|
||
|
|
||
|
return shader;
|
||
|
}
|
||
|
|
||
|
function glCreateProgram(gl, vshader, fshader)
|
||
|
{
|
||
|
// Attach shaders and link
|
||
|
let program = gl.createProgram();
|
||
|
gl.attachShader(program, vshader);
|
||
|
gl.attachShader(program, fshader);
|
||
|
gl.linkProgram(program);
|
||
|
|
||
|
// Report any errors
|
||
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
|
||
|
{
|
||
|
console.log("Failed to link program");
|
||
|
console.trace();
|
||
|
}
|
||
|
|
||
|
return program;
|
||
|
}
|
||
|
|
||
|
function glCreateProgramFromSource(gl, vshader_name, vshader_source, fshader_name, fshader_source)
|
||
|
{
|
||
|
const vshader = glCompileShader(gl, gl.VERTEX_SHADER, vshader_name, vshader_source);
|
||
|
const fshader = glCompileShader(gl, gl.FRAGMENT_SHADER, fshader_name, fshader_source);
|
||
|
return glCreateProgram(gl, vshader, fshader);
|
||
|
}
|
||
|
|
||
|
function glSetUniform(gl, program, name, value, index)
|
||
|
{
|
||
|
// Get location
|
||
|
const location = gl.getUniformLocation(program, name);
|
||
|
assert(location != null, "Can't find uniform " + name);
|
||
|
|
||
|
// Dispatch to uniform function by type
|
||
|
assert(value != null, "Value is null");
|
||
|
const type = Object.prototype.toString.call(value).slice(8, -1);
|
||
|
switch (type)
|
||
|
{
|
||
|
case "Number":
|
||
|
gl.uniform1f(location, value);
|
||
|
break;
|
||
|
|
||
|
case "WebGLTexture":
|
||
|
gl.activeTexture(gl.TEXTURE0 + index);
|
||
|
gl.bindTexture(gl.TEXTURE_2D, value);
|
||
|
gl.uniform1i(location, index);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert(false, "Unhandled type " + type);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function glCreateTexture(gl, width, height, data)
|
||
|
{
|
||
|
const texture = gl.createTexture();
|
||
|
|
||
|
// Set filtering/wrapping to nearest/clamp
|
||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||
|
|
||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
||
|
|
||
|
return texture;
|
||
|
}
|
||
|
|
||
|
const glDynamicBufferType = Object.freeze({
|
||
|
Buffer: 1,
|
||
|
Texture: 2
|
||
|
});
|
||
|
|
||
|
class glDynamicBuffer
|
||
|
{
|
||
|
constructor(gl, buffer_type, element_type, nb_elements, nb_entries = 1)
|
||
|
{
|
||
|
this.gl = gl;
|
||
|
this.elementType = element_type;
|
||
|
this.nbElements = nb_elements;
|
||
|
this.bufferType = buffer_type;
|
||
|
this.dirty = false;
|
||
|
|
||
|
this.Resize(nb_entries);
|
||
|
}
|
||
|
|
||
|
BindAsInstanceAttribute(program, attrib_name)
|
||
|
{
|
||
|
assert(this.bufferType == glDynamicBufferType.Buffer, "Can only call BindAsInstanceAttribute with Buffer types");
|
||
|
|
||
|
let gl = this.gl;
|
||
|
|
||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
||
|
|
||
|
// The attribute referenced in the program
|
||
|
const attrib_location = gl.getAttribLocation(program, attrib_name);
|
||
|
|
||
|
gl.enableVertexAttribArray(attrib_location);
|
||
|
gl.vertexAttribPointer(attrib_location, this.nbElements, this.elementType, false, 0, 0);
|
||
|
|
||
|
// One per instance
|
||
|
gl.vertexAttribDivisor(attrib_location, 1);
|
||
|
}
|
||
|
|
||
|
UploadData()
|
||
|
{
|
||
|
let gl = this.gl;
|
||
|
|
||
|
switch (this.bufferType)
|
||
|
{
|
||
|
case glDynamicBufferType.Buffer:
|
||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
||
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.cpuArray);
|
||
|
break;
|
||
|
|
||
|
case glDynamicBufferType.Texture:
|
||
|
assert(this.elementType == gl.UNSIGNED_BYTE || this.elementType == gl.FLOAT);
|
||
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||
|
|
||
|
// Very limited map from internal type to texture type
|
||
|
let internal_format, format, type;
|
||
|
if (this.elementType == gl.UNSIGNED_BYTE)
|
||
|
{
|
||
|
internal_format = this.nbElements == 1 ? gl.ALPHA : gl.RGBA8;
|
||
|
format = this.nbElements == 1 ? gl.ALPHA : gl.RGBA;
|
||
|
type = gl.UNSIGNED_BYTE;
|
||
|
}
|
||
|
else if (this.elementType == gl.FLOAT)
|
||
|
{
|
||
|
internal_format = this.nbElements == 1 ? gl.R32F : RGBA32F;
|
||
|
format = this.nbElements == 1 ? gl.RED : gl.RGBA;
|
||
|
type = gl.FLOAT;
|
||
|
}
|
||
|
|
||
|
gl.texImage2D(gl.TEXTURE_2D, 0, internal_format, this.nbEntries, 1, 0, format, type, this.cpuArray);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UploadDirtyData()
|
||
|
{
|
||
|
if (this.dirty)
|
||
|
{
|
||
|
this.UploadData();
|
||
|
this.dirty = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ResizeToFitNextPow2(target_count)
|
||
|
{
|
||
|
let nb_entries = this.nbEntries;
|
||
|
while (target_count > nb_entries)
|
||
|
{
|
||
|
nb_entries <<= 1;
|
||
|
}
|
||
|
|
||
|
if (nb_entries > this.nbEntries)
|
||
|
{
|
||
|
this.Resize(nb_entries);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Resize(nb_entries)
|
||
|
{
|
||
|
this.nbEntries = nb_entries;
|
||
|
|
||
|
let gl = this.gl;
|
||
|
|
||
|
// Create the CPU array
|
||
|
const old_array = this.cpuArray;
|
||
|
switch (this.elementType)
|
||
|
{
|
||
|
case gl.FLOAT:
|
||
|
this.nbElementBytes = 4;
|
||
|
this.cpuArray = new Float32Array(this.nbElements * this.nbEntries);
|
||
|
break;
|
||
|
|
||
|
case gl.UNSIGNED_BYTE:
|
||
|
this.nbElementBytes = 1;
|
||
|
this.cpuArray = new Uint8Array(this.nbElements * this.nbEntries);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert(false, "Unsupported dynamic buffer element type");
|
||
|
}
|
||
|
|
||
|
// Calculate byte size of the buffer
|
||
|
this.nbBytes = this.nbElementBytes * this.nbElements * this.nbEntries;
|
||
|
|
||
|
if (old_array != undefined)
|
||
|
{
|
||
|
// Copy the values of the previous array over
|
||
|
this.cpuArray.set(old_array);
|
||
|
}
|
||
|
|
||
|
// Create the GPU buffer
|
||
|
switch (this.bufferType)
|
||
|
{
|
||
|
case glDynamicBufferType.Buffer:
|
||
|
this.buffer = gl.createBuffer();
|
||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
||
|
gl.bufferData(gl.ARRAY_BUFFER, this.nbBytes, gl.DYNAMIC_DRAW);
|
||
|
break;
|
||
|
|
||
|
case glDynamicBufferType.Texture:
|
||
|
this.texture = gl.createTexture();
|
||
|
|
||
|
// Point sampling with clamp for indexing
|
||
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert(false, "Unsupported dynamic buffer type");
|
||
|
}
|
||
|
|
||
|
this.UploadData();
|
||
|
}
|
||
|
};
|
||
|
|