Skip to content

Commit dcf6123

Browse files
committed
Implemented stuff
1 parent ad56c8f commit dcf6123

12 files changed

Lines changed: 188 additions & 50 deletions

File tree

apps/typegpu-docs/src/content/examples/react/triangle/index.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
import { vec4f } from 'typegpu/data';
2+
import { useRender } from '@typegpu/react';
3+
14
function App() {
2-
// TODO: Use useRender to draw a full-screen gradient
5+
const { ref } = useRender({
6+
fragment: ({ uv }) => {
7+
'kernel';
8+
return vec4f(uv.x, uv.y, 1, 1);
9+
},
10+
});
11+
312
// TODO: Provide a time variable to the shader with useUniformValue
413
// TODO: Make the gradient shift colors over time using hsvToRgb from @typegpu/color
5-
return <main>Hello</main>;
14+
15+
return (
16+
<main>
17+
<canvas ref={ref} width='256' height='256' />
18+
</main>
19+
);
620
}
721

822
// #region Example controls and cleanup

packages/typegpu-react/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
import { hsvToRgb } from '@typegpu/color';
1313
import { useFrame, useRender, useUniformValue } from '@typegpu/react';
1414

15-
const App = () => {
16-
const time = useUniformValue(0);
15+
const App = (props: Props) => {
16+
const time = useUniformValue(d.f32, 0);
17+
const color = useMirroredUniform(d.vec3f, props.color);
1718

1819
// Runs each frame on the CPU 🤖
1920
useFrame(() => {
@@ -22,9 +23,9 @@ const App = () => {
2223

2324
const { ref } = useRender({
2425
// Runs each frame on the GPU 🌈
25-
fragment: () => {
26+
fragment: ({ uv }) => {
2627
'kernel';
27-
return hsvToRgb(time.value, 1, 1);
28+
return hsvToRgb(time.$, uv.x, uv.y) * color.$;
2829
},
2930
});
3031

packages/typegpu-react/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
"license": "MIT",
3232
"peerDependencies": {
3333
"typegpu": "^0.7.0",
34-
"react": "^18.0.0"
34+
"react": "^19.0.0"
3535
},
3636
"devDependencies": {
3737
"@typegpu/tgpu-dev-cli": "workspace:*",
3838
"@webgpu/types": "catalog:types",
39-
"@types/react": "^18.0.0",
39+
"@types/react": "^19.0.0",
4040
"tsdown": "catalog:build",
4141
"typegpu": "workspace:*",
4242
"typescript": "catalog:types",
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export { useFrame } from './useFrame.ts';
2-
export { useRender } from './useRender.ts';
3-
export { useUniformValue } from './useUniformValue.ts';
1+
export { useFrame } from './use-frame.ts';
2+
export { useRender } from './use-render.ts';
3+
export { useUniformValue } from './use-uniform-value.ts';
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
createContext,
3+
type ReactNode,
4+
use,
5+
useContext,
6+
useState,
7+
} from 'react';
8+
import tgpu, { type TgpuRoot } from 'typegpu';
9+
10+
class RootContext {
11+
#root: TgpuRoot | undefined;
12+
#rootPromise: Promise<TgpuRoot> | undefined;
13+
14+
initOrGetRoot(): Promise<TgpuRoot> | TgpuRoot {
15+
if (this.#root) {
16+
return this.#root;
17+
}
18+
19+
if (!this.#rootPromise) {
20+
this.#rootPromise = tgpu.init().then((root) => {
21+
this.#root = root;
22+
return root;
23+
});
24+
}
25+
26+
return this.#rootPromise;
27+
}
28+
}
29+
30+
/**
31+
* Used in case no provider is mounted
32+
*/
33+
const globalRootContextValue = new RootContext();
34+
35+
const rootContext = createContext<RootContext | null>(null);
36+
37+
export interface RootProps {
38+
children?: ReactNode | undefined;
39+
}
40+
41+
export const Root = ({ children }: RootProps) => {
42+
const [ctx] = useState(() => new RootContext());
43+
44+
return (
45+
<rootContext.Provider value={ctx}>
46+
{children}
47+
</rootContext.Provider>
48+
);
49+
};
50+
51+
export function useRoot(): TgpuRoot {
52+
const context = useContext(rootContext) ?? globalRootContextValue;
53+
54+
const maybeRoot = context.initOrGetRoot();
55+
return maybeRoot instanceof Promise ? use(maybeRoot) : maybeRoot;
56+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as d from 'typegpu/data';
2+
import tgpu from 'typegpu';
3+
import { useRoot } from './root-context.tsx';
4+
import { useMemo, useRef } from 'react';
5+
import { useFrame } from './use-frame.ts';
6+
7+
type InferRecord<T> = {
8+
[K in keyof T]: d.Infer<T[K]>;
9+
};
10+
11+
export interface UseRenderOptions {
12+
vertex?: () => void;
13+
14+
/**
15+
* A kernel function that runs per-pixel on the GPU.
16+
*/
17+
fragment: (input: InferRecord<typeof DefaultVarying>) => d.v4f;
18+
}
19+
20+
const DefaultVarying = {
21+
uv: d.vec2f,
22+
};
23+
24+
const fullScreenTriangle = tgpu['~unstable'].vertexFn({
25+
in: { vertexIndex: d.builtin.vertexIndex },
26+
out: { pos: d.builtin.position, ...DefaultVarying },
27+
})((input) => {
28+
const pos = [d.vec2f(-1, -1), d.vec2f(3, -1), d.vec2f(-1, 3)];
29+
const uv = [d.vec2f(0, 1), d.vec2f(2, 1), d.vec2f(0, -1)];
30+
31+
return {
32+
pos: d.vec4f(pos[input.vertexIndex] as d.v2f, 0, 1),
33+
uv: uv[input.vertexIndex] as d.v2f,
34+
};
35+
});
36+
37+
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
38+
39+
export function useRender(options: UseRenderOptions) {
40+
const ref = useRef<HTMLCanvasElement>(null);
41+
const ctxRef = useRef<GPUCanvasContext>(null);
42+
const root = useRoot();
43+
44+
// Only considering the first passed-in fragment function.
45+
// This assumes that users won't swap shaders in the same useRender call,
46+
// but we can make this more robust by computing a hash with unplugin-typegpu.
47+
// TODO: You can also use the React Nook trick to track functions based on their
48+
// place in the code. Simpler and more reliable? ((x)=>x)``
49+
const fragmentRef = useRef(options.fragment);
50+
51+
const fragmentFn = useMemo(() => {
52+
return tgpu['~unstable'].fragmentFn({
53+
in: { ...DefaultVarying },
54+
out: d.vec4f,
55+
})(fragmentRef.current);
56+
}, []);
57+
58+
const pipeline = useMemo(() => {
59+
return root['~unstable']
60+
.withVertex(fullScreenTriangle, {})
61+
.withFragment(fragmentFn, { format: presentationFormat })
62+
.createPipeline();
63+
}, [root, fragmentFn]);
64+
65+
useFrame(() => {
66+
const canvas = ref.current;
67+
if (!canvas) return;
68+
if (ctxRef.current === null) {
69+
ctxRef.current = canvas.getContext('webgpu') as GPUCanvasContext;
70+
ctxRef.current.configure({
71+
device: root.device,
72+
format: presentationFormat,
73+
});
74+
}
75+
76+
pipeline
77+
.withColorAttachment({
78+
view: ctxRef.current.getCurrentTexture().createView(),
79+
loadOp: 'load',
80+
storeOp: 'store',
81+
})
82+
.draw(3);
83+
});
84+
85+
return { ref };
86+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type * as d from 'typegpu/data';
2+
3+
interface UniformValue<TSchema, TValue extends d.Infer<TSchema>> {
4+
schema: TSchema;
5+
value: TValue;
6+
}
7+
8+
export function useUniformValue<TSchema, TValue extends d.Infer<TSchema>>(
9+
schema: d.AnyWgslData,
10+
initialValue?: TValue | undefined,
11+
): UniformValue<TSchema, TValue> {
12+
// TODO: Implement
13+
}

packages/typegpu-react/src/useRender.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/typegpu-react/src/useUniformValue.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)