@@ -430,9 +430,34 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
430430 throw new Error (" pipeline is undefined" );
431431 }
432432 pass .setPipeline (pipeline .pipeline );
433+
433434 // Determine the workgroup size based on the size of the buffer or texture.
434435 let size: [number , number , number ];
435- if (command .type == " RESOURCE_BASED" ) {
436+ if (command .type == " INDIRECT" ) {
437+ // validate buffer
438+ if (! allocatedResources .has (command .bufferName )) {
439+ emit (" logError" , " Error when dispatching " + command .fnName + " . Indirect buffer not found: " + command .bufferName );
440+ pass .end ();
441+ return false ;
442+ }
443+ const indirectBuffer = allocatedResources .get (command .bufferName );
444+ if (! (indirectBuffer instanceof GPUBuffer )) {
445+ emit (" logError" , " Error when dispatching " + command .fnName + " . Indirect resource is not a buffer: " + command .bufferName );
446+ pass .end ();
447+ return false ;
448+ }
449+
450+ try {
451+ pass .dispatchWorkgroupsIndirect (indirectBuffer , command .offset );
452+ } catch (e ) {
453+ emit (" logError" , " Failed to perform indirect dispatch for " + command .fnName + " : " + (e as Error ).message );
454+ pass .end ();
455+ return false ;
456+ }
457+
458+ pass .end ();
459+ continue ; // Exit early since indirect dispatches are handled specially
460+ } else if (command .type == " RESOURCE_BASED" ) {
436461 if (! allocatedResources .has (command .resourceName )) {
437462 console .error (" Error when dispatching " + command .fnName + " . Resource not found: " + command .resourceName );
438463 pass .end ();
@@ -556,7 +581,12 @@ function safeSet<T extends GPUObjectBase>(map: Map<string, T>, key: string, valu
556581 map .set (key , value );
557582};
558583
559- async function processResourceCommands(resourceBindings : Bindings , resourceCommands : ResourceCommand [], uniformSize : number ) {
584+ async function processResourceCommands(
585+ resourceBindings : Bindings ,
586+ resourceCommands : ResourceCommand [],
587+ resourceMetadata : { [k : string ]: ResourceMetadata },
588+ uniformSize : number
589+ ) {
560590 let allocatedResources: Map <string , GPUObjectBase > = new Map ();
561591
562592 safeSet (allocatedResources , " uniformInput" , device .createBuffer ({ size: uniformSize , usage: GPUBufferUsage .UNIFORM | GPUBufferUsage .COPY_DST }));
@@ -575,7 +605,7 @@ async function processResourceCommands(resourceBindings: Bindings, resourceComma
575605
576606 const buffer = device .createBuffer ({
577607 size: parsedCommand .count * elementSize ,
578- usage: GPUBufferUsage .STORAGE | GPUBufferUsage .COPY_DST ,
608+ usage: GPUBufferUsage .STORAGE | GPUBufferUsage .COPY_DST | ( resourceMetadata [ resourceName ]?. indirect ? GPUBufferUsage . INDIRECT : 0 ) ,
579609 });
580610
581611 safeSet (allocatedResources , resourceName , buffer );
@@ -749,7 +779,7 @@ async function processResourceCommands(resourceBindings: Bindings, resourceComma
749779 // Create GPU buffer
750780 const buffer = device .createBuffer ({
751781 size: bufferSize ,
752- usage: GPUBufferUsage .STORAGE | GPUBufferUsage .COPY_DST ,
782+ usage: GPUBufferUsage .STORAGE | GPUBufferUsage .COPY_DST | ( resourceMetadata [ resourceName ]?. indirect ? GPUBufferUsage . INDIRECT : 0 ) ,
753783 });
754784
755785 // Upload data to GPU buffer (only the aligned portion)
@@ -773,7 +803,7 @@ async function processResourceCommands(resourceBindings: Bindings, resourceComma
773803
774804 const buffer = device .createBuffer ({
775805 size: parsedCommand .count * elementSize ,
776- usage: GPUBufferUsage .STORAGE | GPUBufferUsage .COPY_DST ,
806+ usage: GPUBufferUsage .STORAGE | GPUBufferUsage .COPY_DST | ( resourceMetadata [ resourceName ]?. indirect ? GPUBufferUsage . INDIRECT : 0 ) ,
777807 });
778808
779809 const floatArray = new Float32Array (parsedCommand .count );
@@ -858,6 +888,31 @@ async function processResourceCommands(resourceBindings: Bindings, resourceComma
858888 return allocatedResources ;
859889}
860890
891+ type ResourceMetadata = {
892+ indirect? : boolean ,
893+ excludeBinding: string [],
894+ }
895+
896+ function getResourceMetadata(compiledCode : CompiledPlayground ): { [k : string ]: ResourceMetadata } {
897+ const metadata = {};
898+
899+ for (const resourceName of Object .keys (compiledCode .shader .layout )) {
900+ metadata [resourceName ] = {
901+ indirect: false ,
902+ excludeBinding: [],
903+ };
904+ }
905+
906+ for (const callCommand of compiledCode .callCommands ) {
907+ if (callCommand .type === ' INDIRECT' ) {
908+ metadata [callCommand .bufferName ].indirect = true ;
909+ metadata [callCommand .bufferName ].excludeBinding .push (callCommand .fnName );
910+ }
911+ }
912+
913+ return metadata ;
914+ }
915+
861916function onRun(runCompiledCode : CompiledPlayground ) {
862917 if (! device ) return ;
863918
@@ -886,17 +941,41 @@ function onRun(runCompiledCode: CompiledPlayground) {
886941
887942 const module = device .createShaderModule ({ code: compiledCode .shader .code });
888943
944+ const resourceMetadata = getResourceMetadata (compiledCode );
945+
889946 for (const callCommand of compiledCode .callCommands ) {
890947 const entryPoint = callCommand .fnName ;
891948 const pipeline = new ComputePipeline (device );
949+
950+ const entryPointReflection = compiledCode .shader .reflection .entryPoints .find (e => e .name === entryPoint );
951+ if (! entryPointReflection ) {
952+ throw new Error (` Entry point ${entryPoint } not found in reflection data ` );
953+ }
954+
955+ const pipelineBindings: Bindings = {};
956+ for (const param in compiledCode .shader .layout ) {
957+ if (resourceMetadata [param ]?.excludeBinding .includes (entryPoint )) {
958+ continue ;
959+ }
960+ pipelineBindings [param ] = compiledCode .shader .layout [param ];
961+ }
962+
892963 // create a pipeline resource 'signature' based on the bindings found in the program.
893- pipeline .createPipelineLayout (compiledCode .shader .layout );
894- pipeline .createPipeline (module , entryPoint );
964+ pipeline .createPipelineLayout (pipelineBindings );
965+ let pipelineCreationResult = await pipeline .createPipeline (module , entryPoint );
966+ if (pipelineCreationResult .succ == false ) {
967+ throw new Error (` Failed to create pipeline for entry point "${entryPoint }":\n ${pipelineCreationResult .message } ` );
968+ }
895969 pipeline .setThreadGroupSize (compiledCode .shader .threadGroupSizes [entryPoint ]);
896970 computePipelines .push (pipeline );
897971 }
898972
899- allocatedResources = await processResourceCommands (compiledCode .shader .layout , compiledCode .resourceCommands , compiledCode .uniformSize );
973+ allocatedResources = await processResourceCommands (
974+ compiledCode .shader .layout ,
975+ compiledCode .resourceCommands ,
976+ resourceMetadata ,
977+ compiledCode .uniformSize
978+ );
900979
901980 if (! passThroughPipeline ) {
902981 passThroughPipeline = new GraphicsPipeline (device );
0 commit comments