SOURCE / PINNED RELEASE
Made of little things.
Safe Sand · Material studies
- Release
- 9101a9cd9fb4…
- Author-recorded commit
- 75d98e1b8edb…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-7682e55a791
Archive hash verified: 6c8ddabeb31100e1…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Adapted from av/safe-sand, e27bafa. See THIRD_PARTY.md for provenance.
export function createContext(canvas) {
const gl = canvas.getContext('webgl2', {
antialias: true,
alpha: false,
depth: true,
stencil: false,
premultipliedAlpha: false,
preserveDrawingBuffer: false,
powerPreference: 'high-performance',
});
if (!gl) throw new Error('WebGL2 is not available on this device.');
const cbf = gl.getExtension('EXT_color_buffer_float');
if (!cbf) throw new Error('This GPU cannot render to float textures (EXT_color_buffer_float).');
const floatBlend = !!gl.getExtension('EXT_float_blend');
gl.getExtension('EXT_color_buffer_half_float');
return { gl, floatBlend };
}
function compile(gl, type, src, name) {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(s);
const numbered = src.split('\n').map((l, i) => `${i + 1}: ${l}`).join('\n');
console.error(numbered);
throw new Error(`Shader ${name} failed: ${log}`);
}
return s;
}
export function program(gl, vs, fs, name = 'program') {
const p = gl.createProgram();
const vertex = compile(gl, gl.VERTEX_SHADER, vs, name + '.vs');
const fragment = compile(gl, gl.FRAGMENT_SHADER, fs, name + '.fs');
gl.attachShader(p, vertex);
gl.attachShader(p, fragment);
gl.linkProgram(p);
gl.deleteShader(vertex); gl.deleteShader(fragment);
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
throw new Error(`Program ${name} link failed: ${gl.getProgramInfoLog(p)}`);
}
const uniforms = {};
const n = gl.getProgramParameter(p, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < n; i++) {
const info = gl.getActiveUniform(p, i);
const key = info.name.replace(/\[0\]$/, '');
uniforms[key] = gl.getUniformLocation(p, info.name);
}
return { p, u: uniforms, name };
}
export function texture(gl, w, h, internal, format, type, filter, data = null) {
const t = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, t);
gl.texImage2D(gl.TEXTURE_2D, 0, internal, w, h, 0, format, type, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
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);
return t;
}
export function framebuffer(gl, textures) {
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
const bufs = textures.map((t, i) => {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, t, 0);
return gl.COLOR_ATTACHMENT0 + i;
});
gl.drawBuffers(bufs);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) throw new Error('Framebuffer incomplete: 0x' + status.toString(16));
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return fb;
}
export function setUniforms(gl, prog, values) {
for (const [k, v] of Object.entries(values)) {
const loc = prog.u[k];
if (loc === undefined || loc === null) continue;
if (typeof v === 'number') gl.uniform1f(loc, v);
else if (v.type === 'i') gl.uniform1i(loc, v.v);
else if (v.type === 'u') gl.uniform1ui(loc, v.v);
else if (v.type === 'v4a') gl.uniform4fv(loc, v.v);
else if (v.length === 2) gl.uniform2fv(loc, v);
else if (v.length === 3) gl.uniform3fv(loc, v);
else if (v.length === 4) gl.uniform4fv(loc, v);
}
}
export function bindTex(gl, prog, name, tex, unit) {
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, tex);
const loc = prog.u[name];
if (loc) gl.uniform1i(loc, unit);
}
