11import { randf , randomGeneratorSlot } from '@typegpu/noise' ;
2- import tgpu , { common , d , std , type TgpuRenderPipeline } from 'typegpu' ;
2+ import tgpu , { common , d , std , type TgpuGuardedComputePipeline } from 'typegpu' ;
33
44import * as c from './constants.ts' ;
55import { initialPRNG , prngKeys , prngs , type PRNGKey } from './prngs.ts' ;
66import { defineControls } from '../../common/defineControls.ts' ;
77
8- const root = await tgpu . init ( ) ;
8+ const root = await tgpu . init ( { device : { requiredFeatures : [ 'timestamp-query' ] } } ) ;
99
1010const canvas = document . querySelector ( 'canvas' ) as HTMLCanvasElement ;
1111const context = root . configureContext ( { canvas, alphaMode : 'premultiplied' } ) ;
@@ -15,47 +15,102 @@ const Config = d.struct({
1515 gridSize : d . f32 ,
1616 canvasRatio : d . f32 ,
1717 useSeed2 : d . u32 ,
18+ samplesPerThread : d . u32 ,
19+ takeAverage : d . u32 ,
1820} ) ;
1921
2022const configUniform = root . createUniform ( Config , {
2123 gridSize : c . initialGridSize ,
2224 canvasRatio : canvas . width / canvas . height ,
2325 useSeed2 : d . u32 ( prngs [ initialPRNG ] . useSeed2 ) ,
26+ samplesPerThread : c . initialSamplesPerThread ,
27+ takeAverage : d . u32 ( c . initialTakeAverage ) ,
2428} ) ;
2529
26- const fragmentShader = tgpu . fragmentFn ( {
27- in : { uv : d . vec2f } ,
28- out : d . vec4f ,
29- } ) ( ( input ) => {
30+ const layouts = {
31+ compute : tgpu . bindGroupLayout ( {
32+ texture : { storageTexture : d . textureStorage2d ( 'r32float' , 'write-only' ) } ,
33+ } ) ,
34+ display : tgpu . bindGroupLayout ( {
35+ texture : { storageTexture : d . textureStorage2d ( 'r32float' , 'read-only' ) } ,
36+ } ) ,
37+ } ;
38+
39+ const bindGroups = Object . fromEntries (
40+ c . gridSizes . map ( ( size ) => {
41+ const texture = root [ '~unstable' ]
42+ . createTexture ( { size : [ size , size ] , format : 'r32float' } )
43+ . $usage ( 'storage' , 'sampled' ) ;
44+ return [
45+ size ,
46+ {
47+ compute : root . createBindGroup ( layouts . compute , { texture } ) ,
48+ display : root . createBindGroup ( layouts . display , { texture } ) ,
49+ } ,
50+ ] ;
51+ } ) ,
52+ ) ;
53+
54+ const displayPipeline = root . createRenderPipeline ( {
55+ vertex : common . fullScreenTriangle ,
56+ fragment : ( { uv } ) => {
57+ 'use gpu' ;
58+ const adjustedUv = uv * d . vec2f ( configUniform . $ . canvasRatio , 1 ) ;
59+ const gridSize = configUniform . $ . gridSize ;
60+ const coords = d . vec2u ( std . floor ( adjustedUv * gridSize ) ) ;
61+ const value = std . textureLoad ( layouts . display . $ . texture , coords ) . r ;
62+ return d . vec4f ( d . vec3f ( value ) , 1 ) ;
63+ } ,
64+ targets : { format : presentationFormat } ,
65+ } ) ;
66+
67+ const computeFn = ( x : number , y : number ) => {
3068 'use gpu' ;
3169 const gridSize = configUniform . $ . gridSize ;
32- const uv = input . uv * d . vec2f ( configUniform . $ . canvasRatio , 1 ) ;
33- const gridedUV = std . floor ( uv * gridSize ) ;
3470
3571 if ( configUniform . $ . useSeed2 === 1 ) {
36- randf . seed2 ( gridedUV ) ;
72+ randf . seed2 ( d . vec2f ( x , y ) ) ;
3773 } else {
38- randf . seed ( gridedUV . x * gridSize + gridedUV . y ) ;
74+ randf . seed ( d . f32 ( x ) * gridSize + d . f32 ( y ) ) ;
3975 }
4076
41- return d . vec4f ( d . vec3f ( randf . sample ( ) ) , 1 ) ;
42- } ) ;
77+ let i = d . u32 ( 0 ) ;
78+ const samplesPerThread = configUniform . $ . samplesPerThread ;
79+ let samples = d . f32 ( 0 ) ;
80+ while ( i < samplesPerThread - 1 ) {
81+ samples += randf . sample ( ) ;
82+ i += 1 ;
83+ }
4384
44- const pipelineCache = new Map < PRNGKey , TgpuRenderPipeline < d . Vec4f > > ( ) ;
45- let prng : PRNGKey = initialPRNG ;
85+ let result = randf . sample ( ) ;
86+ if ( configUniform . $ . takeAverage === 1 ) {
87+ result = ( result + samples ) / samplesPerThread ;
88+ }
4689
47- const redraw = ( ) => {
48- let pipeline = pipelineCache . get ( prng ) ;
90+ std . textureStore ( layouts . compute . $ . texture , d . vec2u ( x , y ) , d . vec4f ( result , 0 , 0 , 0 ) ) ;
91+ } ;
92+
93+ const computePipelineCache = new Map < PRNGKey , TgpuGuardedComputePipeline < [ number , number ] > > ( ) ;
94+ const getComputePipeline = ( key : PRNGKey ) => {
95+ let pipeline = computePipelineCache . get ( key ) ;
4996 if ( ! pipeline ) {
50- pipeline = root . with ( randomGeneratorSlot , prngs [ prng ] . generator ) . createRenderPipeline ( {
51- vertex : common . fullScreenTriangle ,
52- fragment : fragmentShader ,
53- targets : { format : presentationFormat } ,
54- } ) ;
55- pipelineCache . set ( prng , pipeline ) ;
97+ pipeline = root
98+ . with ( randomGeneratorSlot , prngs [ key ] . generator )
99+ . createGuardedComputePipeline ( computeFn )
100+ . withPerformanceCallback ( ( start , end ) => {
101+ console . log ( `[${ key } ] - ${ Number ( end - start ) / 1000 } ms.` ) ;
102+ } ) ;
103+ computePipelineCache . set ( key , pipeline ) ;
56104 }
105+ return pipeline ;
106+ } ;
107+
108+ let prng = initialPRNG ;
109+ let gridSize = c . initialGridSize ;
57110
58- pipeline . withColorAttachment ( { view : context } ) . draw ( 3 ) ;
111+ const redraw = ( ) => {
112+ getComputePipeline ( prng ) . with ( bindGroups [ gridSize ] . compute ) . dispatchThreads ( gridSize , gridSize ) ;
113+ displayPipeline . withColorAttachment ( { view : context } ) . with ( bindGroups [ gridSize ] . display ) . draw ( 3 ) ;
59114} ;
60115
61116// #region Example controls & Cleanup
@@ -69,26 +124,35 @@ export const controls = defineControls({
69124 redraw ( ) ;
70125 } ,
71126 } ,
127+ 'Samples per thread' : {
128+ initial : c . initialSamplesPerThread ,
129+ options : c . samplesPerThread ,
130+ onSelectChange : ( value ) => {
131+ configUniform . writePartial ( { samplesPerThread : value } ) ;
132+ redraw ( ) ;
133+ } ,
134+ } ,
135+ 'Take Average' : {
136+ initial : c . initialTakeAverage ,
137+ onToggleChange : ( value ) => {
138+ configUniform . writePartial ( { takeAverage : d . u32 ( value ) } ) ;
139+ redraw ( ) ;
140+ } ,
141+ } ,
72142 'Grid Size' : {
73143 initial : c . initialGridSize ,
74144 options : c . gridSizes ,
75145 onSelectChange : ( value ) => {
76- configUniform . writePartial ( { gridSize : value } ) ;
146+ gridSize = value ;
147+ configUniform . writePartial ( { gridSize } ) ;
77148 redraw ( ) ;
78149 } ,
79150 } ,
80151 // this is the only place where some niche prngs are tested
81152 'Test Resolution' : import . meta. env . DEV && {
82153 onButtonClick : ( ) => {
83154 prngKeys
84- . map ( ( key ) =>
85- tgpu . resolve ( [
86- root . with ( randomGeneratorSlot , prngs [ key ] . generator ) . createRenderPipeline ( {
87- vertex : common . fullScreenTriangle ,
88- fragment : fragmentShader ,
89- } ) ,
90- ] ) ,
91- )
155+ . map ( ( key ) => tgpu . resolve ( [ getComputePipeline ( key ) . pipeline ] ) )
92156 . forEach ( ( r ) => root . device . createShaderModule ( { code : r } ) ) ;
93157 } ,
94158 } ,
0 commit comments