Files
matrix/js/regl/utils.js

196 lines
4.0 KiB
JavaScript

const makePassTexture = (regl, halfFloat) =>
regl.texture({
width: 1,
height: 1,
type: halfFloat ? "half float" : "uint8",
wrap: "clamp",
min: "linear",
mag: "linear",
});
const makePassFBO = (regl, halfFloat) =>
regl.framebuffer({ color: makePassTexture(regl, halfFloat) });
const makeDoubleBuffer = (regl, props) => {
const state = Array(2)
.fill()
.map(() =>
regl.framebuffer({
color: regl.texture(props),
depthStencil: false,
}),
);
return {
front: ({ tick }) => state[tick % 2],
back: ({ tick }) => state[(tick + 1) % 2],
};
};
const isPowerOfTwo = (x) => Math.log2(x) % 1 == 0;
const loadImage = (cache, regl, url, mipmap) => {
const key = `${url}_${mipmap}`;
if (cache.has(key)) {
return cache.get(key);
}
let texture = regl.texture([[0]]);
let loaded = false;
const resource = {
texture: () => {
if (!loaded && url != null) {
console.warn(`texture still loading: ${url}`);
}
return texture;
},
width: () => {
if (!loaded && url != null) {
console.warn(`texture still loading: ${url}`);
}
return loaded ? texture.width : 1;
},
height: () => {
if (!loaded && url != null) {
console.warn(`texture still loading: ${url}`);
}
return loaded ? texture.height : 1;
},
loaded: (async () => {
if (url != null) {
const data = new Image();
data.crossOrigin = "anonymous";
let imageURL;
if (typeof cache.get(`import::${url}`) === "function") {
imageURL = (await cache.get(`import::${url}`)()).default;
} else {
imageURL = url;
}
data.src = imageURL;
await data.decode();
loaded = true;
if (mipmap) {
if (!isPowerOfTwo(data.width) || !isPowerOfTwo(data.height)) {
console.warn(`Can't mipmap a non-power-of-two image: ${url}`);
}
mipmap = false;
}
texture = regl.texture({
data,
mag: "linear",
min: mipmap ? "mipmap" : "linear",
flipY: true,
});
}
})(),
};
cache.set(key, resource);
return resource;
};
const loadText = (cache, url) => {
const key = url;
if (cache.has(key)) {
return cache.get(key);
}
let text = "";
let loaded = false;
const resource = {
text: () => {
if (!loaded) {
console.warn(`text still loading: ${url}`);
}
return text;
},
loaded: (async () => {
if (url != null) {
let textURL;
if (typeof cache.get(`import::${url}`) === "function") {
textURL = (await cache.get(`import::${url}`)()).default;
} else {
textURL = url;
}
text = await (await fetch(textURL)).text();
loaded = true;
}
})(),
};
cache.set(key, resource);
return resource;
};
const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
regl({
vert: `
precision mediump float;
attribute vec2 aPosition;
varying vec2 vUV;
void main() {
vUV = 0.5 * (aPosition + 1.0);
gl_Position = vec4(aPosition, 0, 1);
}
`,
frag: `
precision mediump float;
varying vec2 vUV;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, vUV);
}
`,
attributes: {
aPosition: [-4, -4, 4, -4, 0, 4],
},
count: 3,
uniforms: {
...uniforms,
time: regl.context("time"),
tick: regl.context("tick"),
},
context,
depth: { enable: false },
});
const make1DTexture = (regl, rgbas) => {
const data = rgbas.map((rgba) => rgba.map((f) => Math.floor(f * 0xff))).flat();
return regl.texture({
data,
width: data.length / 4,
height: 1,
format: "rgba",
mag: "linear",
min: "linear",
});
};
const makePass = (outputs, ready, setSize, execute) => ({
outputs: outputs ?? {},
ready: ready ?? Promise.resolve(),
setSize: setSize ?? (() => {}),
execute: execute ?? (() => {}),
});
const makePipeline = (context, steps) =>
steps
.filter((f) => f != null)
.reduce(
(pipeline, f, i) => [...pipeline, f(context, i == 0 ? null : pipeline[i - 1].outputs)],
[],
);
export {
makePassTexture,
makePassFBO,
makeDoubleBuffer,
loadImage,
loadText,
makeFullScreenQuad,
make1DTexture,
makePass,
makePipeline,
};