1+ import styles from './styles.module.css'
2+ import { Head } from "#components/Head"
3+ import type { RouteMeta } from "#router"
4+ import { use , useEffect , useRef , useState } from "react"
5+ import { getFormValue } from "#components/getFormValue"
6+ import { makeFrameCounter } from "#components/makeFrameCounter"
7+ import shader from './foo.wgsl?raw'
8+ import compute from './compute.wgsl?raw'
9+
10+ export const meta : RouteMeta = {
11+ title : 'Particle Life GPU' ,
12+ tags : [ 'simulation' , 'webgpu' , 'particles' , 'wip' ] ,
13+ }
14+
15+ export default function ParticleLifeGPUPage ( ) {
16+ const canvasRef = useRef < HTMLCanvasElement > ( null )
17+ const [ supported ] = useState ( ( ) => Boolean ( navigator . gpu ) )
18+ const [ fps , setFps ] = useState ( 0 )
19+
20+ useEffect ( ( ) => {
21+ if ( ! supported ) return
22+ const canvas = canvasRef . current !
23+ const controller = new AbortController ( )
24+
25+ canvas . width = window . innerWidth * devicePixelRatio
26+ canvas . height = window . innerHeight * devicePixelRatio
27+
28+ const frameCounter = makeFrameCounter ( 60 )
29+ start ( controller . signal , canvas , ( dt ) => setFps ( Math . round ( frameCounter ( dt / 1000 ) ) ) )
30+
31+ return ( ) => {
32+ controller . abort ( )
33+ }
34+ } , [ supported ] )
35+
36+ return (
37+ < div className = { styles . main } >
38+ < div className = { styles . head } >
39+ < Head />
40+ { supported && < pre > { fps } FPS</ pre > }
41+ { ! supported && < pre > Your browser does not support WebGPU.</ pre > }
42+ </ div >
43+ < canvas ref = { canvasRef } />
44+ </ div >
45+ )
46+ }
47+
48+ async function start (
49+ signal : AbortSignal ,
50+ canvas : HTMLCanvasElement ,
51+ onFrame : ( dt : number ) => void ,
52+ ) {
53+ const adapter = await navigator . gpu . requestAdapter ( { powerPreference : 'high-performance' } )
54+ if ( ! adapter ) throw new Error ( 'No GPU adapter found' )
55+ if ( signal . aborted ) return
56+
57+ const device = await adapter . requestDevice ( )
58+ if ( ! device ) throw new Error ( 'No GPU device found' )
59+ if ( signal . aborted ) return
60+ signal . addEventListener ( 'abort' , ( ) => device . destroy ( ) , { once : true } )
61+
62+ const ctx = canvas . getContext ( 'webgpu' ) !
63+ if ( ! ctx ) throw new Error ( 'No WebGPU context found' )
64+ const format = navigator . gpu . getPreferredCanvasFormat ( )
65+ ctx . configure ( { device, format, alphaMode : 'opaque' } )
66+ signal . addEventListener ( 'abort' , ( ) => ctx . unconfigure ( ) , { once : true } )
67+
68+ const module = device . createShaderModule ( { code : shader , label : 'our hardcoded red triangle shaders' } )
69+
70+ const pipeline = device . createRenderPipeline ( {
71+ label : 'our hardcoded red triangle pipeline' ,
72+ layout : 'auto' ,
73+ vertex : {
74+ entryPoint : 'vs' ,
75+ module,
76+ } ,
77+ fragment : {
78+ entryPoint : 'fs' ,
79+ module,
80+ targets : [ { format } ] ,
81+ } ,
82+ } )
83+
84+
85+ const renderPassDescriptor = {
86+ label : 'our basic canvas renderPass' ,
87+ colorAttachments : [
88+ {
89+ view : null ! as GPUTextureView ,
90+ clearValue : [ 0.3 , 0.3 , 0.3 , 1 ] ,
91+ loadOp : 'clear' ,
92+ storeOp : 'store' ,
93+ } ,
94+ ] ,
95+ } satisfies GPURenderPassDescriptor
96+
97+ function render ( ) {
98+ // Get the current texture from the canvas context and
99+ // set it as the texture to render to.
100+ renderPassDescriptor . colorAttachments [ 0 ] ! . view =
101+ ctx . getCurrentTexture ( ) . createView ( )
102+
103+ const encoder = device . createCommandEncoder ( { label : 'our encoder' } )
104+ const pass = encoder . beginRenderPass ( renderPassDescriptor )
105+ pass . setPipeline ( pipeline )
106+ pass . draw ( 3 )
107+ pass . end ( )
108+
109+ const commandBuffer = encoder . finish ( )
110+ device . queue . submit ( [ commandBuffer ] )
111+ }
112+
113+ let lastTime = performance . now ( )
114+ let rafId = requestAnimationFrame ( function frame ( time ) {
115+ if ( signal . aborted ) return
116+ rafId = requestAnimationFrame ( frame )
117+ const dt = time - lastTime
118+ lastTime = time
119+ onFrame ( dt )
120+ render ( )
121+ } )
122+ signal . addEventListener ( 'abort' , ( ) => cancelAnimationFrame ( rafId ) , { once : true } )
123+
124+ const width = ctx . canvas . width
125+ const height = ctx . canvas . height
126+ }
0 commit comments