Skip to content

Commit e4e472b

Browse files
committed
Geometry package: circles
1 parent 86c916d commit e4e472b

File tree

13 files changed

+373
-0
lines changed

13 files changed

+373
-0
lines changed

apps/typegpu-docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@stackblitz/sdk": "^1.11.0",
2424
"@tailwindcss/vite": "^4.1.6",
2525
"@typegpu/color": "workspace:*",
26+
"@typegpu/geometry": "workspace:*",
2627
"@typegpu/noise": "workspace:*",
2728
"@typegpu/sdf": "workspace:*",
2829
"@types/dom-mediacapture-transform": "^0.1.9",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<canvas></canvas>
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import tgpu from 'typegpu';
2+
import * as d from 'typegpu/data';
3+
import * as s from 'typegpu/std';
4+
5+
import {
6+
circleFan,
7+
circleMaxArea,
8+
circleMaxAreaVertexCount,
9+
} from '@typegpu/geometry';
10+
11+
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
12+
const canvas = document.querySelector('canvas');
13+
const context = canvas?.getContext('webgpu');
14+
const multisample = true;
15+
16+
if (!canvas) {
17+
throw new Error('Could not find canvas');
18+
}
19+
if (!context) {
20+
throw new Error('Could not create WebGPU context');
21+
}
22+
23+
const adapter = await navigator.gpu.requestAdapter();
24+
console.log(`Using ${adapter?.info.vendor} adapter`);
25+
const device = await adapter?.requestDevice({
26+
requiredFeatures: ['timestamp-query'],
27+
});
28+
if (!device) {
29+
throw new Error('Could not get WebGPU device');
30+
}
31+
const root = tgpu.initFromDevice({ device });
32+
33+
context.configure({
34+
device: root.device,
35+
format: presentationFormat,
36+
alphaMode: 'premultiplied',
37+
});
38+
39+
// Textures
40+
let msaaTexture: GPUTexture;
41+
let msaaTextureView: GPUTextureView;
42+
43+
const createDepthAndMsaaTextures = () => {
44+
if (msaaTexture) {
45+
msaaTexture.destroy();
46+
}
47+
msaaTexture = device.createTexture({
48+
size: [canvas.width, canvas.height, 1],
49+
format: presentationFormat,
50+
sampleCount: 4,
51+
usage: GPUTextureUsage.RENDER_ATTACHMENT,
52+
});
53+
msaaTextureView = msaaTexture.createView();
54+
};
55+
56+
createDepthAndMsaaTextures();
57+
const resizeObserver = new ResizeObserver(createDepthAndMsaaTextures);
58+
resizeObserver.observe(canvas);
59+
60+
// const Uniforms = d.struct({});
61+
62+
const Circle = d.struct({
63+
position: d.vec2f,
64+
radius: d.f32,
65+
});
66+
67+
const bindGroupLayout = tgpu.bindGroupLayout({
68+
// uniforms: {
69+
// uniform: Uniforms,
70+
// },
71+
circles: {
72+
storage: (n: number) => d.arrayOf(Circle, n),
73+
},
74+
});
75+
76+
// const uniforms = root.createBuffer(Uniforms, {}).$usage(
77+
// 'uniform',
78+
// );
79+
80+
const circleCount = 1000;
81+
const circles = root.createBuffer(
82+
d.arrayOf(Circle, circleCount),
83+
Array.from({ length: circleCount }).map(() =>
84+
Circle({
85+
position: d.vec2f(Math.random() * 2 - 1, Math.random() * 2 - 1),
86+
radius: 0.05 * Math.random() + 0.01,
87+
})
88+
),
89+
).$usage('storage');
90+
91+
const uniformsBindGroup = root.createBindGroup(bindGroupLayout, {
92+
// uniforms,
93+
circles,
94+
});
95+
96+
const mainVertexMaxArea = tgpu['~unstable'].vertexFn({
97+
in: {
98+
instanceIndex: d.builtin.instanceIndex,
99+
vertexIndex: d.builtin.vertexIndex,
100+
},
101+
out: {
102+
outPos: d.builtin.position,
103+
uv: d.vec2f,
104+
instanceIndex: d.interpolate('flat', d.u32),
105+
},
106+
})(({ vertexIndex, instanceIndex }) => {
107+
const circle = bindGroupLayout.$.circles[instanceIndex];
108+
const unit = circleMaxArea(vertexIndex);
109+
const pos = s.add(circle.position, s.mul(unit, circle.radius));
110+
return {
111+
outPos: d.vec4f(pos, 0.0, 1.0),
112+
uv: unit,
113+
instanceIndex,
114+
};
115+
});
116+
117+
const mainVertexFan = tgpu['~unstable'].vertexFn({
118+
in: {
119+
instanceIndex: d.builtin.instanceIndex,
120+
vertexIndex: d.builtin.vertexIndex,
121+
},
122+
out: {
123+
outPos: d.builtin.position,
124+
uv: d.vec2f,
125+
instanceIndex: d.interpolate('flat', d.u32),
126+
},
127+
})(({ vertexIndex, instanceIndex }) => {
128+
const circle = bindGroupLayout.$.circles[instanceIndex];
129+
const unit = circleFan(vertexIndex, 10);
130+
const pos = s.add(circle.position, s.mul(unit, circle.radius));
131+
return {
132+
outPos: d.vec4f(pos, 0.0, 1.0),
133+
uv: unit,
134+
instanceIndex,
135+
};
136+
});
137+
138+
console.log(tgpu.resolve({ externals: { mainVertexFan } }));
139+
140+
const mainFragment = tgpu['~unstable'].fragmentFn({
141+
in: {
142+
uv: d.vec2f,
143+
instanceIndex: d.interpolate('flat', d.u32),
144+
},
145+
out: d.vec4f,
146+
})(({ uv, instanceIndex }) => {
147+
const color = d.vec3f(
148+
1,
149+
s.cos(d.f32(instanceIndex)),
150+
s.sin(5 * d.f32(instanceIndex)),
151+
);
152+
const r = s.length(uv);
153+
return d.vec4f(
154+
s.mix(color, d.vec3f(), s.clamp((r - 0.9) * 20, 0, 0.5)),
155+
1,
156+
);
157+
});
158+
159+
const pipeline = root['~unstable']
160+
.withVertex(mainVertexMaxArea, {})
161+
.withFragment(mainFragment, { format: presentationFormat })
162+
.withMultisample({ count: multisample ? 4 : 1 })
163+
.createPipeline();
164+
165+
setInterval(() => {
166+
pipeline
167+
.with(bindGroupLayout, uniformsBindGroup)
168+
.withColorAttachment({
169+
...(multisample
170+
? {
171+
view: msaaTextureView,
172+
resolveTarget: context.getCurrentTexture().createView(),
173+
}
174+
: {
175+
view: context.getCurrentTexture().createView(),
176+
}),
177+
clearValue: [0, 0, 0, 0],
178+
loadOp: 'clear',
179+
storeOp: 'store',
180+
})
181+
.withPerformanceCallback((a, b) => {
182+
console.log((Number(b - a) * 1e-6).toFixed(3), 'ms');
183+
})
184+
.draw(circleMaxAreaVertexCount(4), circleCount);
185+
}, 100);
186+
187+
export function onCleanup() {
188+
root.destroy();
189+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"title": "Circles",
3+
"category": "geometry",
4+
"tags": ["experimental"]
5+
}
Loading

apps/typegpu-docs/src/utils/examples/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const exampleCategories = [
1313
{ key: 'image-processing', label: 'Image processing' },
1414
{ key: 'simulation', label: 'Simulation' },
1515
{ key: 'algorithms', label: 'Algorithms' },
16+
{ key: 'geometry', label: 'Geometry' },
1617
{ key: 'tests', label: 'Tests' },
1718
];
1819

packages/typegpu-geometry/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div align="center">
2+
3+
# @typegpu/geometry
4+
5+
🚧 **Under Construction** 🚧
6+
7+
</div>
8+
9+
A set of geometry helper functions for use in WebGPU/TypeGPU apps.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { type BuildConfig, defineBuildConfig } from 'unbuild';
2+
import typegpu from 'unplugin-typegpu/rollup';
3+
4+
const Config: BuildConfig[] = defineBuildConfig({
5+
hooks: {
6+
'rollup:options': (_options, config) => {
7+
config.plugins.push(typegpu({ include: [/\.ts$/] }));
8+
},
9+
},
10+
});
11+
12+
export default Config;

packages/typegpu-geometry/deno.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"exclude": ["."],
3+
"fmt": {
4+
"exclude": ["!."],
5+
"singleQuote": true
6+
}
7+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "@typegpu/geometry",
3+
"type": "module",
4+
"version": "0.0.1",
5+
"description": "A set of geometry helper functions for use in WebGPU/TypeGPU apps.",
6+
"exports": {
7+
".": "./src/index.ts",
8+
"./package.json": "./package.json"
9+
},
10+
"publishConfig": {
11+
"directory": "dist",
12+
"linkDirectory": false,
13+
"main": "./dist/index.mjs",
14+
"types": "./dist/index.d.ts",
15+
"exports": {
16+
"./package.json": "./dist/package.json",
17+
".": {
18+
"types": "./dist/index.d.ts",
19+
"module": "./dist/index.mjs",
20+
"import": "./dist/index.mjs",
21+
"default": "./dist/index.cjs"
22+
}
23+
}
24+
},
25+
"sideEffects": false,
26+
"scripts": {
27+
"build": "unbuild",
28+
"test:types": "pnpm tsc --p ./tsconfig.json --noEmit",
29+
"prepublishOnly": "tgpu-dev-cli prepack"
30+
},
31+
"keywords": [],
32+
"license": "MIT",
33+
"peerDependencies": {
34+
"typegpu": "^0.5.9"
35+
},
36+
"devDependencies": {
37+
"@typegpu/tgpu-dev-cli": "workspace:*",
38+
"@webgpu/types": "catalog:",
39+
"typegpu": "workspace:*",
40+
"typescript": "catalog:",
41+
"unbuild": "catalog:",
42+
"unplugin-typegpu": "workspace:*"
43+
}
44+
}

0 commit comments

Comments
 (0)