Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6b57bba
thinking about what it would be like to use webGPU here for scatterbr…
froyo-np Apr 22, 2026
0b0d594
switch to vite (library mode) because it supports typeGPU's wgsl tran…
froyo-np Apr 22, 2026
a64a9d8
minor import cleanup
froyo-np Apr 23, 2026
71db373
try typegpu until I go crazy, but it does (almost) work
froyo-np Apr 23, 2026
8dd215c
fix the uint16 mystery. implement the raw webGPU scatterplot renderer…
froyo-np Apr 23, 2026
376e96c
WIP total reorg
froyo-np Apr 23, 2026
b04f894
fix oh so many mistakes
froyo-np Apr 24, 2026
8a7ab4f
continue to fight with stuff to make the examples work...
froyo-np Apr 24, 2026
80532be
lots of cleanup
froyo-np Apr 24, 2026
0051cd3
more cleanup
froyo-np Apr 24, 2026
e93aa02
fmt
froyo-np May 1, 2026
3eaf74d
tsconfig webgpu types
froyo-np May 1, 2026
b04849f
webgpu types
froyo-np May 1, 2026
855c371
put everything back to regl until we can figure out what is going on …
froyo-np May 1, 2026
c5bed41
clean it up
froyo-np May 1, 2026
3f12b87
lint & fmt
froyo-np May 1, 2026
da81118
fix a very silly hack that was totally ruining astro's build and it w…
froyo-np May 1, 2026
47afe9a
oh also this has float16array so thats good
froyo-np May 1, 2026
086a838
cleanup experimental stuff
froyo-np May 1, 2026
24d78a0
update lockfile
froyo-np May 1, 2026
9b3cda9
Merge remote-tracking branch 'origin/main' into noah/webgpu-scatterbr…
froyo-np May 4, 2026
b582440
install
froyo-np May 4, 2026
82d8fa3
use node 24 in ci actions, so that it doesnt choke on Float16Array in…
froyo-np May 4, 2026
43d3ae7
for some fun reason lodash does not play nice with parcel...
froyo-np May 4, 2026
26b03df
TIL lodash-es, hooray!
froyo-np May 5, 2026
46c7a1a
beat stuff up until it works
froyo-np May 5, 2026
8a4c235
make it build, confirm working webgpu example... discover little bug...
froyo-np May 5, 2026
8804591
a note about per-draw uniform data...
froyo-np May 5, 2026
c66c468
Merge remote-tracking branch 'origin/main' into noah/webgpu-scatterbr…
froyo-np May 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@
"pnpm": "10.33.0"
},
"packageManager": "pnpm@10.33.0"
}
}
15 changes: 9 additions & 6 deletions packages/scatterbrain/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alleninstitute/vis-scatterbrain",
"version": "0.0.3",
"version": "0.0.4",
"contributors": [
{
"name": "Lane Sawyer",
Expand Down Expand Up @@ -38,8 +38,7 @@
"scripts": {
"typecheck": "tsc --noEmit",
"build": "parcel build --no-cache",
"dev": "parcel watch --port 1239",
"demo": "vite",
"dev": "parcel watch --no-cache --port 1239",
"test": "vitest --watch",
"test:ci": "vitest run",
"coverage": "vitest run --coverage",
Expand All @@ -52,9 +51,11 @@
"dependencies": {
"@alleninstitute/vis-core": "workspace:*",
"@alleninstitute/vis-geometry": "workspace:*",
"lodash": "4.17.23",
"lodash-es": "4.18.1",
"regl": "2.1.0",
"ts-pattern": "5.9.0",
"typegpu": "0.11.2",
"webgpu-utils": "2.0.2",
"zod": "4.3.6"
},
"publishConfig": {
Expand All @@ -63,6 +64,8 @@
},
"packageManager": "pnpm@9.14.2",
"devDependencies": {
"@types/lodash": "4.17.24"
"@types/lodash-es": "4.17.12",
"@types/node": "22.19.15",
"@webgpu/types": "0.1.69"
}
}
}
4 changes: 2 additions & 2 deletions packages/scatterbrain/src/cache-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Cacheable, SharedPriorityCache } from '@alleninstitute/vis-core';
import reduce from 'lodash/reduce';
import reduce from 'lodash-es/reduce';
import type { WebGLSafeBasicType } from './typed-array';
import type { ColumnRequest, Item } from './types';

Expand All @@ -19,7 +19,7 @@ export function buildScatterbrainCacheClient<V extends Cacheable>(
...acc,
[key]: `${dataset.metadata.metadataFileEndpoint}/${node.file}/${col.name}`,
}),
{}
{},
);
},
fetch: (item) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/scatterbrain/src/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
visitBFSMaybe,
} from '@alleninstitute/vis-geometry';
import type { PointAttribute, ScatterbrainDataset, SlideviewScatterbrainDataset, TreeNode, volumeBound } from './types';
import reduce from 'lodash/reduce';
import reduce from 'lodash-es/reduce';
import * as z from 'zod';

type Dataset = ScatterbrainDataset | SlideviewScatterbrainDataset;
Expand Down
32 changes: 32 additions & 0 deletions packages/scatterbrain/src/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the demo I used to debug my webGPU implementation - we should remove it, but for now starlight seems to have trouble with our webGPU utils (a 3rd party helper)

<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=yes">
<title>hello webgpu</title>
<style>
:root {
color-scheme: light dark;
}

html,
body {
margin: 0;
/* remove default margin */
height: 100%;
overflow: hidden;
/* make body fill the browser window */
display: flex;
touch-action: none;
place-content: center center;
}
</style>
<script defer src="../dist/main.js" type="module"></script>
</head>

<body>
<canvas id="canvas"></canvas>
</body>

</html>
111 changes: 111 additions & 0 deletions packages/scatterbrain/src/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/** biome-ignore-all lint/suspicious/noNonNullAssertedOptionalChain: <its a demo> */
/** biome-ignore-all lint/style/noNonNullAssertion: <its a demo> */

import { SharedPriorityCache } from '@alleninstitute/vis-core';
import { Box2D, type vec4 } from '@alleninstitute/vis-geometry';
import { loadDataset } from './dataset';
import { buildRenderFrameFn, type ShaderSettings } from './render/webgpu/renderer';
import type { ScatterbrainDataset } from './types';

const tenx =
'https://bkp-2d-visualizations-stage.s3.amazonaws.com/wmb_tenx_01172024_stage-20240128193624/G4I4GFJXJB9ATZ3PTX1/ScatterBrain.json';

async function loadRawJson() {
return await (await fetch(tenx)).json();
}
const makeFakeColors = (n: number) => {
const stuff: Record<number, { color: vec4; filteredIn: boolean }> = {};
for (let i = 0; i < n; i++) {
stuff[i] = {
color: [Math.random(), Math.random(), Math.random(), 1],
// 80% of either category are filtered in, at random:
filteredIn: Math.random() > 0.2,
};
}
return stuff;
};

export async function whatever() {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the bootstrap function for my demo - remote eventually

const gradientData = new Uint8Array(256 * 4);
for (let i = 0; i < 256; i += 4) {
gradientData[i * 4 + 0] = i;
gradientData[i * 4 + 1] = i;
gradientData[i * 4 + 2] = i;
gradientData[i * 4 + 3] = 255;
}
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter?.requestDevice()!;
// buildTest(root.device)

const categories = {
'4MV7HA5DG2XJZ3UD8G9': makeFakeColors(40), // nt type
FS00DXV0T9R1X9FJ4QE: makeFakeColors(40), // class
};

const settings: Omit<ShaderSettings, 'dataset'> = {
categoricalFilters: { '4MV7HA5DG2XJZ3UD8G9': 40, FS00DXV0T9R1X9FJ4QE: 40 },
colorBy: { kind: 'metadata', column: 'FS00DXV0T9R1X9FJ4QE' },
// an alternative color-by setting, swap it to see quantitative coloring
// colorBy: { kind: 'quantitative', column: '27683', gradient: 'viridis', range: { min: 0, max: 10 } },
mode: 'color',
quantitativeFilters: [],
highlightByColumn: { kind: 'metadata', column: 'FS00DXV0T9R1X9FJ4QE' },
};

const dataset = await loadDataset(await loadRawJson());
if (!dataset) {
throw new Error('blerg this data is toast');
}
const cache = new SharedPriorityCache(new Map(), 1024 * 1024 * 2000);
const { render, connectToCache } = buildRenderFrameFn(device, { ...settings, dataset });

const cnvs: HTMLCanvasElement = document.getElementById('canvas') as HTMLCanvasElement;
cnvs.width = 1500;
cnvs.height = 1500;
const ctx = cnvs.getContext('webgpu');
ctx?.configure({
device: device,
format: navigator.gpu.getPreferredCanvasFormat(),
alphaMode: 'premultiplied',
});

const bound = (dataset as ScatterbrainDataset).metadata.tightBoundingBox;
const view = Box2D.create([bound.lx, bound.ly], [bound.ux, bound.uy]);
const client = connectToCache(cache, () => {
// redraw?
// console.log('new data arrived...')
requestAnimationFrame(() => {
// biome-ignore lint/suspicious/noConsole: <this is a demo>
console.log('re render!');

render({
categories,
client,
gradient: gradientData,
target: ctx!.getCurrentTexture().createView(),
uniforms: {
camera: { view, screenResolution: [1500, 1500] },
filteredOutColor: [0.5, 0.5, 0.5, 1.0],
highlightedValue: 22,
offset: [0, 0],
quantitativeRangeFilters: {},
spatialFilterBox: view,
},
});
});
});
render({
categories,
client,
gradient: gradientData,
target: ctx!.getCurrentTexture().createView(),
uniforms: {
camera: { view, screenResolution: [1500, 1500] },
filteredOutColor: [0.5, 0.5, 0.5, 1.0],
highlightedValue: 22,
offset: [0, 0],
quantitativeRangeFilters: {},
spatialFilterBox: view,
},
});
}
8 changes: 3 additions & 5 deletions packages/scatterbrain/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
export { buildScatterbrainCacheClient } from './cache-client';
export { getVisibleItems, loadDataset as loadScatterbrainDataset } from './dataset';
export {
buildRenderFrameFn as buildScatterbrainRenderFn,
setCategoricalLookupTableValues,
updateCategoricalValue,
} from './renderer';
export * from './render/webgl/index';
export * from './render/webgpu/index';
export * from './types';

8 changes: 8 additions & 0 deletions packages/scatterbrain/src/render/webgl/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

// because the webGL and webGPU implementations of these renderers are very similar,
// they end up having identical names for the same conceptual parts -
// so lets export them namespaced
import * as WGL from './renderer'
export const WebGL = {
...WGL
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { SharedPriorityCache } from '@alleninstitute/vis-core';
import { Box2D, type box2D, type vec4 } from '@alleninstitute/vis-geometry';
import keys from 'lodash/keys';
import reduce from 'lodash/reduce';
import keys from 'lodash-es/keys';
import reduce from 'lodash-es/reduce';
import type REGL from 'regl';
import { buildScatterbrainCacheClient } from './cache-client';
import { getVisibleItems, type NodeWithBounds } from './dataset';
import { buildScatterbrainCacheClient } from '../../cache-client';
import { getVisibleItems, type NodeWithBounds } from '../../dataset';
import { buildScatterbrainRenderCommand, type Config, configureShader, type ShaderSettings, VBO } from './shader';
import { MakeTaggedBufferView } from './typed-array';
import type { ColumnRequest, ScatterbrainDataset, SlideviewScatterbrainDataset, TreeNode } from './types';
import { MakeTaggedBufferView } from '../../typed-array';
import type { ColumnRequest, ScatterbrainDataset, SlideviewScatterbrainDataset, TreeNode } from '../../types';

export type Item = Readonly<{
dataset: SlideviewScatterbrainDataset | ScatterbrainDataset;
Expand Down Expand Up @@ -149,7 +149,7 @@ export function buildRenderFrameFn(regl: REGL.Regl, settings: ShaderSettings) {
}
};
const connectToCache = (cache: SharedPriorityCache, onDataArrived: () => void) => {
const client = buildScatterbrainCacheClient(
const client = buildScatterbrainCacheClient<VBO>(
cache,
(buff, type) => {
const typed = MakeTaggedBufferView(type, buff);
Expand All @@ -159,7 +159,7 @@ export function buildRenderFrameFn(regl: REGL.Regl, settings: ShaderSettings) {
type: 'buffer',
});
},
onDataArrived
onDataArrived,
);
return client;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest';
import { buildShaders, type Config, configureShader } from './shader';
import type { ScatterbrainDataset } from './types';
import type { ScatterbrainDataset } from '../../types';

const tenx: ScatterbrainDataset = {
type: 'normal',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/** biome-ignore-all lint/style/noUnusedTemplateLiteral: not at all helpful*/

import type REGL from 'regl';
import type { ScatterbrainDataset, SlideviewScatterbrainDataset } from './types';
import type { Cacheable, CachedVertexBuffer } from '@alleninstitute/vis-core';
import type { ScatterbrainDataset, SlideviewScatterbrainDataset } from '../../types';
import type { CachedVertexBuffer, Cacheable } from '@alleninstitute/vis-core';
import { Box2D, type box2D, type Interval, type vec2, type vec4 } from '@alleninstitute/vis-geometry';
import * as lodash from 'lodash';
import * as lodash from 'lodash-es';
const { keys, mapValues, reduce } = lodash;

// the set of columns and what to do with them can vary
Expand All @@ -27,7 +27,7 @@ const { keys, mapValues, reduce } = lodash;
// patterns we've seen in our shaders so far! you could easily generate your own
// totally custom shaders!

type ScatterbrainShaderUtils = {
export type ScatterbrainShaderUtils = {
uniforms: string; // the GLSL declarations of the uniforms for this shader
attributes: string; // the GLSL declarations of the vertex attributes for this shader
commonUtilsGLSL: string; // prepend any GLSL to the final vertex shader
Expand Down Expand Up @@ -309,8 +309,8 @@ export function generate(config: Config): ScatterbrainShaderUtils {
return mix(filteredOutColor,${colorize},isFilteredIn());
`
: categoryColumnIndex === -1
? colorByQuantitativeValue
: colorByCategoricalId;
? colorByQuantitativeValue
: colorByCategoricalId;
return {
attributes,
uniforms,
Expand All @@ -332,8 +332,8 @@ export type ShaderSettings = {
quantitativeFilters: readonly string[]; // the names of quantitative variables
mode: 'color' | 'info';
colorBy:
| { kind: 'metadata'; column: string }
| { kind: 'quantitative'; column: string; gradient: 'viridis' | 'inferno'; range: Interval };
| { kind: 'metadata'; column: string }
| { kind: 'quantitative'; column: string; gradient: 'viridis' | 'inferno'; range: Interval };
};

export function configureShader(settings: ShaderSettings): {
Expand Down
8 changes: 8 additions & 0 deletions packages/scatterbrain/src/render/webgpu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

// because the webGL and webGPU implementations of these renderers are very similar,
// they end up having identical names for the same conceptual parts -
// so lets export them namespaced
import * as WGPU from './renderer'
export const WebGPU = {
...WGPU
}
Loading
Loading