From 37a9c32b8d2fd0e1b6dc7d11c49d4ee2e948db32 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Thu, 8 Oct 2026 17:44:27 -0700 Subject: [PATCH] =?UTF-8?q?KidLisp:=20pixel-buffer=20profiler=20scaffold?= =?UTF-8?q?=20with=20AC's=20own=206=C3=9710=20glyphs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pixel-type.mjs writes text straight into RGBA pixels from AC's font_1 drawings (no browser font rasterizer) and carries PixelProfiler, which keeps an fps/cpu label as a small HUD buffer that paints onto any frame. pixel-profiler-gpu.mjs blits that HUD over a WebGPU target in a load/store pass. build-pixel-font.mjs regenerates pixel-font-data.mjs from the glyph drawings; the output is reproducible. Not yet wired into the roz preview. Also ignore pop/menuband/takes/, which holds takes pulled from the private @jeffrey backup. --- .gitignore | 3 +++ kidlisp/graph/pixel-profiler-gpu.mjs | 22 +++++++++++++++++ kidlisp/tools/build-pixel-font.mjs | 11 +++++++++ .../lib/pixel-font-data.mjs | 2 ++ .../aesthetic.computer/lib/pixel-type.mjs | 24 +++++++++++++++++++ 5 files changed, 62 insertions(+) create mode 100644 kidlisp/graph/pixel-profiler-gpu.mjs create mode 100644 kidlisp/tools/build-pixel-font.mjs create mode 100644 system/public/aesthetic.computer/lib/pixel-font-data.mjs create mode 100644 system/public/aesthetic.computer/lib/pixel-type.mjs diff --git a/.gitignore b/.gitignore index b2efc003c5..46f09fcfd3 100644 --- a/.gitignore +++ b/.gitignore @@ -615,3 +615,6 @@ xbox/live/oskiewar-release.json # Native app bundles are rebuilt from xbox/live, never a second source tree. xbox/builds/macos/ xbox/builds/ios/ + +# Pulled Menu Band takes (menuband_take_pull); backed up to @jeffrey, not git +pop/menuband/takes/ diff --git a/kidlisp/graph/pixel-profiler-gpu.mjs b/kidlisp/graph/pixel-profiler-gpu.mjs new file mode 100644 index 0000000000..8963500cb5 --- /dev/null +++ b/kidlisp/graph/pixel-profiler-gpu.mjs @@ -0,0 +1,22 @@ +import {PixelProfiler} from '../../system/public/aesthetic.computer/lib/pixel-type.mjs'; +const shader=`struct View {size:vec2u,hud:vec2u} +@group(0) @binding(0) var view:View; +@group(0) @binding(1) var pixels:array; +@vertex fn vertex(@builtin(vertex_index) i:u32)->@builtin(position) vec4f {let p=array(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3));return vec4f(p[i],0,1);} +@fragment fn fragment(@builtin(position) position:vec4f)->@location(0) vec4f { +let origin=max(vec2i(0),vec2i(view.size)-vec2i(view.hud)-vec2i(2));let p=vec2i(position.xy)-origin; +if(any(p=vec2i(view.hud))){discard;} +let c=pixels[u32(p.x)+u32(p.y)*view.hud.x];return vec4f(f32(c&255u),f32((c>>8u)&255u),f32((c>>16u)&255u),255)/255.0;}`; +export async function createPixelProfilerGPU(device,format){ + const profiler=new PixelProfiler(),module=device.createShaderModule({code:shader}); + const pipeline=await device.createRenderPipelineAsync({layout:'auto',vertex:{module,entryPoint:'vertex'},fragment:{module,entryPoint:'fragment',targets:[{format}]}}); + const pixels=device.createBuffer({size:200*14*4,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}); + const view=device.createBuffer({size:16,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}); + const bind=device.createBindGroup({layout:pipeline.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:view}},{binding:1,resource:{buffer:pixels}}]}); + let version=-1;const control=new Uint32Array(4); + return {profiler,draw(encoder,target,width,height){ + if(version!==profiler.version){device.queue.writeBuffer(pixels,0,profiler.hud.pixels);version=profiler.version;} + control.set([width,height,profiler.hud.width,profiler.hud.height]);device.queue.writeBuffer(view,0,control); + const pass=encoder.beginRenderPass({colorAttachments:[{view:target,loadOp:'load',storeOp:'store'}]});pass.setPipeline(pipeline);pass.setBindGroup(0,bind);pass.draw(3);pass.end(); + },dispose(){pixels.destroy();view.destroy();}}; +} diff --git a/kidlisp/tools/build-pixel-font.mjs b/kidlisp/tools/build-pixel-font.mjs new file mode 100644 index 0000000000..116cdd54e5 --- /dev/null +++ b/kidlisp/tools/build-pixel-font.mjs @@ -0,0 +1,11 @@ +#!/usr/bin/env node +import {font_1} from '../../system/public/aesthetic.computer/disks/common/fonts.mjs'; +import * as graph from '../../system/public/aesthetic.computer/lib/graph.mjs'; +import {readFile,writeFile} from 'node:fs/promises'; +const rows={}; +for(let i=32;i<127;i++) { + const char=String.fromCharCode(i),path=font_1[char];const buffer={width:6,height:10,pixels:new Uint8ClampedArray(240)}; + if(path){const drawing=JSON.parse(await readFile(new URL(`../../system/public/aesthetic.computer/disks/drawings/font_1/${path}.json`,import.meta.url),'utf8'));graph.setBuffer(buffer);graph.color(255,255,255,255);for(const c of drawing.commands){if(!['line','point'].includes(c.name))throw new Error(`Unknown glyph operation ${c.name}`);graph[c.name](...c.args);}} + rows[char]=Array.from({length:10},(_,y)=>{let row=0;for(let x=0;x<6;x++)if(buffer.pixels[(y*6+x)*4+3])row|=1<":[0,0,1,6,24,6,1,0,0,0],"?":[0,14,17,16,8,4,0,4,0,0],"@":[0,30,33,45,45,61,1,62,0,0],"A":[0,4,4,10,14,10,17,17,0,0],"B":[0,15,17,17,15,17,17,15,0,0],"C":[0,14,17,1,1,1,17,14,0,0],"D":[0,7,9,17,17,17,9,7,0,0],"E":[0,31,1,1,15,1,1,31,0,0],"F":[0,31,1,1,15,1,1,1,0,0],"G":[0,14,17,1,25,17,17,30,0,0],"H":[0,17,17,17,31,17,17,17,0,0],"I":[0,14,4,4,4,4,4,14,0,0],"J":[0,28,16,16,16,16,16,15,0,0],"K":[0,17,9,5,3,5,9,17,0,0],"L":[0,1,1,1,1,1,1,31,0,0],"M":[0,17,17,27,27,21,21,17,0,0],"N":[0,17,19,19,21,25,25,17,0,0],"O":[0,14,17,17,17,17,17,14,0,0],"P":[0,15,17,17,15,1,1,1,0,0],"Q":[0,14,17,17,17,17,25,30,0,0],"R":[0,15,17,17,15,17,17,17,0,0],"S":[0,14,17,1,14,16,17,14,0,0],"T":[0,31,4,4,4,4,4,4,0,0],"U":[0,17,17,17,17,17,17,14,0,0],"V":[0,17,17,10,10,10,4,4,0,0],"W":[0,17,21,21,14,10,10,10,0,0],"X":[0,17,17,10,4,10,17,17,0,0],"Y":[0,17,17,17,14,4,4,4,0,0],"Z":[0,31,16,8,4,2,1,31,0,0],"[":[14,2,2,2,2,2,2,2,14,0],"\\":[1,1,2,2,4,4,8,8,16,0],"]":[14,8,8,8,8,8,8,8,14,0],"^":[0,4,10,17,0,0,0,0,0,0],"_":[0,0,0,0,0,0,0,0,63,0],"`":[0,0,0,0,0,0,0,0,0,0],"a":[0,0,0,14,16,30,17,30,0,0],"b":[1,1,1,15,17,17,17,15,0,0],"c":[0,0,0,30,1,1,1,30,0,0],"d":[16,16,16,30,17,17,17,30,0,0],"e":[0,0,0,14,17,31,1,30,0,0],"f":[28,2,2,15,2,2,2,2,0,0],"g":[0,0,0,14,17,17,17,30,16,14],"h":[1,1,1,15,17,17,17,17,0,0],"i":[0,4,0,6,4,4,4,4,0,0],"j":[0,8,0,12,8,8,8,8,8,7],"k":[2,2,2,18,10,6,10,18,0,0],"l":[6,4,4,4,4,4,4,4,0,0],"m":[0,0,0,11,21,21,21,21,0,0],"n":[0,0,0,15,17,17,17,17,0,0],"o":[0,0,0,14,17,17,17,14,0,0],"p":[0,0,0,15,17,17,17,15,1,1],"q":[0,0,0,30,17,17,17,30,16,16],"r":[0,0,0,13,19,1,1,1,0,0],"s":[0,0,0,30,1,14,16,15,0,0],"t":[0,2,2,14,2,2,2,28,0,0],"u":[0,0,0,17,17,17,17,30,0,0],"v":[0,0,0,17,17,10,10,4,0,0],"w":[0,0,0,17,21,21,10,10,0,0],"x":[0,0,0,17,10,4,10,17,0,0],"y":[0,0,0,17,17,17,17,30,16,14],"z":[0,0,0,31,8,4,2,31,0,0],"{":[8,4,4,4,2,4,4,4,8,0],"|":[4,4,4,4,4,4,4,4,4,0],"}":[2,4,4,4,8,4,4,4,2,0],"~":[0,0,0,0,38,25,0,0,0,0]}; diff --git a/system/public/aesthetic.computer/lib/pixel-type.mjs b/system/public/aesthetic.computer/lib/pixel-type.mjs new file mode 100644 index 0000000000..38c1ef3c64 --- /dev/null +++ b/system/public/aesthetic.computer/lib/pixel-type.mjs @@ -0,0 +1,24 @@ +import {glyphRows} from './pixel-font-data.mjs'; +// AC's 6×10 glyphs, written directly into RGBA pixels. No browser font rasterizer. +export function pixelWrite(buffer,text,x,y,{color=[240,242,250,255],scale=1}={}) { + scale=Math.max(1,Math.min(8,Math.floor(scale)));x=Math.floor(x);y=Math.floor(y);const start=x; + for(const char of String(text)) { + if(char==='\n'){x=start;y+=12*scale;continue;} + const rows=glyphRows[char]||glyphRows['?']; + for(let row=0;row<10;row++)for(let col=0;col<6;col++)if(rows[row]&(1<=0&&py>=0&&px=500){this.fps=Math.round(this.frames*1000/elapsed);this.cpuMs=this.work/this.frames;this.label(`${this.fps}fps`);this.start=now;this.frames=this.work=0;return true;}return false;} + paint(buffer){const {width,height,pixels}=this.hud,x=Math.max(0,buffer.width-width-2),y=Math.max(0,buffer.height-height-2);for(let row=0;row