Better Hexells loading perf

This commit is contained in:
Dustin Brett 2024-02-13 15:56:28 -08:00
parent 96630293aa
commit e00aa5525d
10 changed files with 236 additions and 132 deletions

View file

@ -2,17 +2,18 @@ import { loadFiles } from "utils/functions";
declare global {
interface Window {
Demo: new (canvas: HTMLCanvasElement) => unknown;
Demo: new (canvas: HTMLCanvasElement, rootPath: string) => unknown;
Hexells: unknown;
}
}
export const ROOT_PATH = "/System/Hexells";
export const libs = [
"/System/Hexells/twgl-full.min.js",
"/System/Hexells/pako.min.js",
"/System/Hexells/UPNG.min.js",
"/System/Hexells/ca.js",
"/System/Hexells/demo.js",
`${ROOT_PATH}/twgl.min.js`,
`${ROOT_PATH}/UPNG.min.js`,
`${ROOT_PATH}/ca.js`,
`${ROOT_PATH}/demo.js`,
];
const hexells = async (el?: HTMLElement | null): Promise<void> => {
@ -25,7 +26,7 @@ const hexells = async (el?: HTMLElement | null): Promise<void> => {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
window.Hexells = new window.Demo(canvas);
window.Hexells = new window.Demo(canvas, ROOT_PATH);
el.append(canvas);
};

View file

@ -1,9 +1,9 @@
import { libs } from "components/system/Desktop/Wallpapers/hexells";
import { ROOT_PATH, libs } from "components/system/Desktop/Wallpapers/hexells";
import { type OffscreenRenderProps } from "components/system/Desktop/Wallpapers/types";
/* eslint-disable vars-on-top, no-var */
declare global {
var Demo: new (canvas: OffscreenCanvas) => unknown;
var Demo: new (canvas: OffscreenCanvas, rootPath: string) => unknown;
var Hexells: unknown;
var demoCanvasRect: DOMRect;
var devicePixelRatio: number;
@ -25,7 +25,7 @@ globalThis.addEventListener(
globalThis.devicePixelRatio = devicePixelRatio;
try {
globalThis.Hexells = new globalThis.Demo(canvas);
globalThis.Hexells = new globalThis.Demo(canvas, ROOT_PATH);
} catch (error) {
globalThis.postMessage({
message: (error as Error)?.message,

File diff suppressed because one or more lines are too long

View file

@ -12,20 +12,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Usage:
const gui = new dat.GUI();
const ca = new CA(gl, models_json, [W, H], gui); // gui is optional
ca.step();
ca.paint(x, y, radius, modelIndex);
ca.clearCircle(x, y, radius;
const stats = ca.benchmark();
ca.draw();
ca.draw(zoom);
*/
const vs_code = `
attribute vec4 position;
varying vec2 uv;
@ -509,40 +495,29 @@ function setTensorUniforms(uniforms, name, tensor) {
}
}
function decodeBase64(b64) {
const bin = atob(b64);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) {
bytes[i] = bin.charCodeAt(i);
}
return bytes;
}
function createDenseInfo(gl, params, onready) {
function createDenseInfo(gl, params, rootPath, onready) {
const coefs = [params.scale, 127.0 / 255.0];
const [in_n, out_n] = params.shape;
const info = { coefs, layout: params.layout, in_n: in_n - 1, out_n,
quantScaleZero: params.quant_scale_zero, ready: false };
// workaround against iOS WebKit bug (https://bugs.webkit.org/show_bug.cgi?id=138477)
// non-premultiplied PNG were decoded incorrectly
const img = UPNG.decode(decodeBase64(params.data.split(',')[1]));
const data = new Uint8Array(UPNG.toRGBA8(img)[0]);
info.tex = twgl.createTexture(gl, {
width: img.width, height: img.height,
minMag: gl.NEAREST, src: data,//, flipY: false, premultiplyAlpha: false,
}, ()=>{
//info.ready = true;
//onready();
});
setTimeout(()=>{
fetch(`${rootPath}/${params.data}`, { priority: "high" })
.then((response) => response.arrayBuffer())
.then((buffer) => {
const img = UPNG.decode(buffer);
const data = new Uint8Array(UPNG.toRGBA8(img)[0]);
info.tex = twgl.createTexture(gl, {
width: img.width, height: img.height,
minMag: gl.NEAREST, src: data,
});
info.ready = true;
onready();
}, 0);
});
return info;
}
class CA {
constructor(gl, models, gridSize, onready) {
constructor(gl, models, gridSize, rootPath, onready) {
this.rootPath = rootPath;
this.onready = onready || (()=>{});
this.gl = gl;
this.gridSize = gridSize || [96, 96];
@ -583,15 +558,6 @@ class CA {
});
}
disturbCircle(x, y, r, viewSize) {
viewSize = viewSize || [128, 128];
this.runLayer(this.progs.align, this.buf.align, {
u_input: this.buf.newAlign, u_hexGrid: this.hexGrid, u_init: Math.random()*1000+1,
u_pos: [x, y], u_r: r, u_viewSize: viewSize,
});
}
setupBuffers() {
const gl = this.gl;
const [gridW, gridH] = this.gridSize;
@ -644,20 +610,21 @@ class CA {
step(stage) {
stage = stage || 'all';
const isStageAll = stage == 'all';
if (!this.layers.every(l=>l.ready))
return;
if (stage == 'all') {
if (isStageAll) {
const [gridW, gridH] = this.gridSize;
this.shuffleOfs = [Math.floor(Math.random() * gridW), Math.floor(Math.random() * gridH)];
}
if (stage == 'all' || stage == 'align') {
if (isStageAll || stage == 'align') {
this.runLayer(this.progs.align, this.buf.newAlign, {
u_input: this.buf.align, u_hexGrid: this.hexGrid, u_init: 0.0
});
}
if (stage == 'all' || stage == 'perception') {
if (isStageAll || stage == 'perception') {
this.runLayer(this.progs.perception, this.buf.perception, {
u_input: this.buf.state, u_angle: this.rotationAngle / 180.0 * Math.PI,
u_alignTex: this.buf.newAlign,
@ -666,11 +633,11 @@ class CA {
}
let inputBuf = this.buf.perception;
for (let i=0; i<this.layers.length; ++i) {
if (stage == 'all' || stage == `layer${i}`)
if (isStageAll || stage == `layer${i}`)
this.runDense(this.buf[`layer${i}`], inputBuf, this.layers[i]);
inputBuf = this.buf[`layer${i}`];
}
if (stage == 'all' || stage == 'newState') {
if (isStageAll || stage == 'newState') {
this.runLayer(this.progs.update, this.buf.newState, {
u_input: this.buf.state, u_update: inputBuf,
u_unshuffleTex: this.unshuffleTex,
@ -678,55 +645,12 @@ class CA {
});
}
if (stage == 'all') {
if (isStageAll) {
[this.buf.state, this.buf.newState] = [this.buf.newState, this.buf.state];
[this.buf.align, this.buf.newAlign] = [this.buf.newAlign, this.buf.align];
}
}
benchmark() {
const gl = this.gl;
const flushBuf = new Uint8Array(4);
const flush = buf=>{
buf = buf || this.buf.state;
// gl.flush/finish don't seem to do anything, so reading a single
// pixel from the state buffer to flush the GPU command pipeline
twgl.bindFramebufferInfo(gl, buf.fbi);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, flushBuf);
}
flush();
const stepN = 100;
const start = Date.now();
for (let i = 0; i < stepN; ++i)
this.step();
flush();
const total = (Date.now() - start) / stepN;
const ops = ['align', 'perception'];
for (let i=0; i<this.layers.length; ++i)
ops.push(`layer${i}`);
ops.push('newState');
let perOpTotal = 0.0;
const perOp = [];
for (const op of ops) {
const start = Date.now();
for (let i = 0; i < stepN; ++i) {
this.step(op);
}
flush(this.buf[op]);
const dt = (Date.now() - start) / stepN;
perOpTotal += dt
perOp.push([op, dt]);
}
const perOpStr = perOp.map((p) => {
const [programName, dt] = p;
const percent = 100.0 * dt / perOpTotal;
return `${programName}: ${percent.toFixed(1)}%`;
}).join(', ');
return `${(total).toFixed(2)} ms/step, ${(1000.0 / total).toFixed(2)} step/sec\n` + perOpStr + '\n\n';
}
paint(x, y, r, brush, viewSize) {
viewSize = viewSize || [128, 128];
this.runLayer(this.progs.paint, this.buf.control, {
@ -734,17 +658,6 @@ class CA {
});
}
peek(x, y, viewSize) {
this.runLayer(this.progs.peek, this.buf.sonic, {
u_pos: [x, y], u_viewSize: viewSize, u_input: this.buf.state
});
const {width, height} = this.buf.sonic.fbi;
const gl = this.gl;
twgl.bindFramebufferInfo(gl, this.buf.sonic.fbi);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this.sonicBuf);
return {buf: this.sonicBuf, tex: this.buf.sonic.tex, pos: [x, y]};
}
clearCircle(x, y, r, viewSize) {
viewSize = viewSize || [128, 128];
this.runLayer(this.progs.paint, this.buf.state, {
@ -759,7 +672,7 @@ class CA {
if (this.layers.every(l=>l.ready))
this.onready();
}
this.layers = models.layers.map(layer=>createDenseInfo(gl, layer, onready));
this.layers = models.layers.map(layer=>createDenseInfo(gl, layer, this.rootPath, onready));
}
runLayer(program, output, inputs) {

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
public/System/Hexells/twgl.min.js vendored Normal file

File diff suppressed because one or more lines are too long