Skip to content

Commit 729c813

Browse files
authored
feat: experimental WebGPU renderer (#652)
Summary: Experimental WebGPU renderer. Currently only supports the image layer. This PR replaces the renderer in first 3 examples to use the WebGPU renderer instead of WebGL.
1 parent 8112f51 commit 729c813

22 files changed

Lines changed: 1388 additions & 15 deletions

examples/ca_wave_dynamics/main.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ const timePointOverlay = {
7171
},
7272
};
7373

74-
new Idetik({
74+
const idetik = await Idetik.create({
75+
renderer: "webgpu-experimental",
7576
canvas: document.querySelector<HTMLCanvasElement>("#canvas")!,
7677
viewports: [
7778
{
@@ -82,7 +83,9 @@ new Idetik({
8283
],
8384
overlays: [timePointOverlay],
8485
showStats: true,
85-
}).start();
86+
});
87+
88+
idetik.start();
8689

8790
const controls = {
8891
showWireframes: imageLayer.debugMode,

examples/chunk_streaming/main.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ const scaleBar = new ScaleBar({
7575
unit: "μm",
7676
});
7777

78-
new Idetik({
78+
const idetik = await Idetik.create({
79+
renderer: "webgpu-experimental",
7980
canvas: document.querySelector<HTMLCanvasElement>("#canvas")!,
8081
viewports: [
8182
{
@@ -86,7 +87,9 @@ new Idetik({
8687
],
8788
overlays: [timePointOverlay, scaleBar],
8889
showStats: true,
89-
}).start();
90+
});
91+
92+
idetik.start();
9093

9194
const controls = {
9295
showWireframes: imageLayer.debugMode,

examples/image2d_from_omezarr4d_hcs/main.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,21 @@ wellPaths.forEach((path) => {
4242
const policy = createNoPrefetchPolicy();
4343

4444
const camera = new OrthographicCamera(0, xSize * xScale, 0, ySize * yScale);
45-
const app = new Idetik({
45+
46+
const idetik = await Idetik.create({
47+
renderer: "webgpu-experimental",
4648
canvas: document.querySelector<HTMLCanvasElement>("canvas")!,
4749
viewports: [
4850
{
4951
camera,
5052
cameraControls: new PanZoomControls(camera),
5153
},
5254
],
53-
}).start();
55+
});
56+
57+
idetik.start();
5458

55-
const viewport = app.viewports[0];
59+
const viewport = idetik.viewports[0];
5660
const imageSelector = document.querySelector("#image") as HTMLSelectElement;
5761

5862
const onImageChange = async () => {

generate-examples-manifest.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-env node */
21
/* global process */
32

43
import { readdirSync, existsSync, readFileSync, writeFileSync } from 'node:fs';

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"dependencies": {
4848
"gl-matrix": "^3.4.3",
4949
"zarrita": "^0.7.1",
50+
"webgpu-utils": "^2.1.0",
5051
"zod": "^3.24.1"
5152
},
5253
"devDependencies": {
@@ -58,11 +59,12 @@
5859
"@typescript-eslint/parser": "^8.1.0",
5960
"@vitest/browser": "^3.0.9",
6061
"@vitest/coverage-istanbul": "^3.0.9",
62+
"@webgpu/types": "^0.1.69",
6163
"conventional-changelog-conventionalcommits": "^9.1.0",
6264
"eslint": "^9.9.0",
6365
"globals": "^15.9.0",
64-
"json-schema-to-zod": "^2.6.1",
6566
"jsdom": "^24.1.1",
67+
"json-schema-to-zod": "^2.6.1",
6668
"lil-gui": "^0.20.0",
6769
"playwright": "1.56.1",
6870
"prettier": "^3.3.2",

src/core/layer.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { EventContext } from "./event_dispatcher";
66
import { Viewport } from "./viewport";
77

88
export type LayerState = "initialized" | "loading" | "ready";
9-
export type blendMode =
9+
export type BlendMode =
10+
| "none"
1011
| "normal"
1112
| "additive"
1213
| "subtractive"
@@ -21,7 +22,7 @@ type StateChangeCallback = (
2122
export interface LayerOptions {
2223
transparent?: boolean;
2324
opacity?: number;
24-
blendMode?: blendMode;
25+
blendMode?: BlendMode;
2526
}
2627

2728
export type RenderContext = {
@@ -37,7 +38,7 @@ export abstract class Layer {
3738

3839
public transparent: boolean;
3940
private opacity_: number;
40-
public blendMode: blendMode;
41+
public blendMode: BlendMode;
4142

4243
constructor({
4344
transparent = false,

src/core/renderer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export abstract class Renderer {
2323
this.updateRendererSize();
2424
}
2525

26+
public beginFrame(): void {}
27+
2628
public abstract render(viewport: Viewport): void;
2729

2830
public updateSize(): void {

src/idetik.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { WebGLRenderer } from "./renderers/webgl_renderer";
2+
import { createWebGPURenderer } from "./renderers/webgpu/webgpu_renderer";
23
import { Logger } from "./utilities/logger";
34
import { ChunkManager } from "./core/chunk_manager";
45
import { Renderer } from "./core/renderer";
@@ -87,10 +88,20 @@ export class Idetik {
8788
*
8889
* @throws {Error} If viewports have duplicate IDs or shared elements
8990
*/
90-
constructor(params: IdetikParams) {
91+
static async create(
92+
params: IdetikParams & { renderer?: "webgl" | "webgpu-experimental" }
93+
): Promise<Idetik> {
94+
const renderer =
95+
params.renderer === "webgpu-experimental"
96+
? await createWebGPURenderer(params.canvas)
97+
: new WebGLRenderer(params.canvas);
98+
return new Idetik(params, renderer);
99+
}
100+
101+
constructor(params: IdetikParams, renderer?: Renderer) {
91102
this.canvas = params.canvas;
92103

93-
this.renderer_ = new WebGLRenderer(this.canvas);
104+
this.renderer_ = renderer ?? new WebGLRenderer(this.canvas);
94105
this.chunkManager_ = new ChunkManager();
95106
this.context_ = {
96107
chunkManager: this.chunkManager_,
@@ -114,6 +125,7 @@ export class Idetik {
114125
}
115126
this.sizeObserver_ = new PixelSizeObserver(sizeDependents, () => {
116127
this.renderer_.updateSize();
128+
this.renderer_.beginFrame();
117129
for (const viewport of this.viewports_) {
118130
viewport.updateSize();
119131
this.renderer_.render(viewport);
@@ -219,6 +231,7 @@ export class Idetik {
219231

220232
this.lastTimestamp_ = timestamp;
221233

234+
this.renderer_.beginFrame();
222235
for (const viewport of this.viewports_) {
223236
viewport.cameraControls?.onUpdate(dt);
224237
this.renderer_.render(viewport);

src/objects/textures/texture.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,32 @@ export function bufferToDataType(
5353
throw new Error("Unsupported buffer type.");
5454
}
5555

56+
export function textureChannelCount(texture: Texture) {
57+
switch (texture.dataFormat) {
58+
case "scalar":
59+
return 1;
60+
case "rgb":
61+
return 3;
62+
case "rgba":
63+
return 4;
64+
}
65+
}
66+
67+
export function textureBytesPerChannel(texture: Texture) {
68+
switch (texture.dataType) {
69+
case "byte":
70+
case "unsigned_byte":
71+
return 1;
72+
case "short":
73+
case "unsigned_short":
74+
return 2;
75+
case "int":
76+
case "unsigned_int":
77+
case "float":
78+
return 4;
79+
}
80+
}
81+
5682
export function textureDefaultValueRange(texture: Texture): [number, number] {
5783
if (texture.dataFormat === "rgb" || texture.dataFormat === "rgba") {
5884
return [0, 1];

0 commit comments

Comments
 (0)