Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 73 additions & 27 deletions modules/experimental/src/gpu-primitives/gpu-compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

import {Computation} from '@luma.gl/engine';
import {GPUCommandGraph, GraphVectorView, type GraphDataView} from './gpu-command-graph';
import {GPUScan} from './gpu-scan';
import {
getBoundedDispatchLayout,
getBoundedInvocationIndexSource,
type GPUBoundedDispatchLayout
} from './gpu-dispatch-utils';
import {addGPUScanToGraphWithDispatchLimit, GPUScan} from './gpu-scan';
import {
createTransientVectorView,
createTransientView,
Expand Down Expand Up @@ -98,29 +103,56 @@ export class GPUCompaction {
* commands.
*/
addToGraph<Parameters>(graph: GPUCommandGraph<Parameters>): void {
for (const view of [
...getCompactionChunks(this.input),
...getCompactionChunks(this.flags),
...getCompactionChunks(this.output),
this.count
]) {
if (view.buffer.graph !== graph) {
throw new Error(`${this.id} views must belong to the target graph`);
}
}
addGPUCompactionToGraphWithDispatchLimit(
this,
graph,
graph.device.limits.maxComputeWorkgroupsPerDimension
);
}
}

if (this.input.length === 0) {
addClearCountPass(graph, this.id, this.count);
return;
/** Adds stable scan and scatter passes with an explicit dispatch limit. @internal */
export function addGPUCompactionToGraphWithDispatchLimit<Parameters>(
compaction: GPUCompaction,
graph: GPUCommandGraph<Parameters>,
maxComputeWorkgroupsPerDimension: number
): void {
for (const view of [
...getCompactionChunks(compaction.input),
...getCompactionChunks(compaction.flags),
...getCompactionChunks(compaction.output),
compaction.count
]) {
if (view.buffer.graph !== graph) {
throw new Error(`${compaction.id} views must belong to the target graph`);
}
}

const offsets =
this.flags instanceof GraphVectorView
? createTransientVectorView(graph, `${this.id}-offsets`, this.flags)
: createTransientView(graph, `${this.id}-offsets`, 'uint32', this.flags.length);
new GPUScan({id: `${this.id}-scan`, input: this.flags, output: offsets}).addToGraph(graph);
addScatterPasses(graph, this.id, this.input, this.flags, offsets, this.output, this.count);
if (compaction.input.length === 0) {
addClearCountPass(graph, compaction.id, compaction.count);
return;
}

const offsets =
compaction.flags instanceof GraphVectorView
? createTransientVectorView(graph, `${compaction.id}-offsets`, compaction.flags)
: createTransientView(graph, `${compaction.id}-offsets`, 'uint32', compaction.flags.length);
const scan = new GPUScan({
id: `${compaction.id}-scan`,
input: compaction.flags,
output: offsets
});
addGPUScanToGraphWithDispatchLimit(scan, graph, maxComputeWorkgroupsPerDimension);
addScatterPasses(
graph,
compaction.id,
compaction.input,
compaction.flags,
offsets,
compaction.output,
compaction.count,
maxComputeWorkgroupsPerDimension
);
}

/** Writes the required zero count for an empty input. */
Expand Down Expand Up @@ -162,7 +194,8 @@ function addScatterPasses<Parameters>(
flags: GPUCompactionInput,
offsets: GPUCompactionInput,
output: GPUCompactionInput,
count: GraphDataView<'uint32'>
count: GraphDataView<'uint32'>,
maxComputeWorkgroupsPerDimension: number
): void {
const inputChunks = getCompactionChunks(input);
const flagChunks = getCompactionChunks(flags);
Expand Down Expand Up @@ -196,7 +229,13 @@ function addScatterPasses<Parameters>(
output: outputChunk,
outputStart,
outputEnd,
count: writesCount ? count : undefined
count: writesCount ? count : undefined,
dispatchLayout: getBoundedDispatchLayout(
'GPUCompaction',
inputChunks[inputChunkIndex].length,
COMPACTION_WORKGROUP_SIZE,
maxComputeWorkgroupsPerDimension
)
});
}
}
Expand All @@ -216,6 +255,7 @@ function addScatterPass<Parameters>(
outputStart: number;
outputEnd: number;
count?: GraphDataView<'uint32'>;
dispatchLayout: GPUBoundedDispatchLayout;
}
): void {
const countBinding = props.count
Expand All @@ -242,9 +282,10 @@ ${props.count ? `const COUNT_OFFSET: u32 = ${getViewElementOffset(props.count)}u
${countBinding}

@compute @workgroup_size(${COMPACTION_WORKGROUP_SIZE}) fn main(
@builtin(global_invocation_id) globalId: vec3<u32>
@builtin(local_invocation_index) localInvocationIndex: u32,
@builtin(workgroup_id) workgroupId: vec3<u32>
) {
let index = globalId.x;
${getBoundedInvocationIndexSource(props.dispatchLayout, COMPACTION_WORKGROUP_SIZE)}
if (index >= ELEMENT_COUNT) { return; }
let flag = min(flags[FLAGS_OFFSET + index], 1u);
let outputIndex = offsets[OFFSETS_OFFSET + index];
Expand All @@ -270,7 +311,7 @@ ${countBinding}
outputValues: props.output,
...(props.count ? {outputCount: props.count} : {})
},
dispatchCount: Math.ceil(props.input.length / COMPACTION_WORKGROUP_SIZE)
dispatchLayout: props.dispatchLayout
});
}

Expand All @@ -285,7 +326,7 @@ function addCompactionPass<Parameters>(
usage: 'storage-read' | 'storage-write' | 'storage-read-write';
}>;
bindings: Record<string, GraphDataView>;
dispatchCount: number;
dispatchLayout: GPUBoundedDispatchLayout;
}
): void {
graph.addComputePass({
Expand All @@ -311,7 +352,12 @@ function addCompactionPass<Parameters>(
bindings[name] = getViewBinding(view, getBuffer);
}
computation.setBindings(bindings);
computation.dispatch(computePass, props.dispatchCount);
computation.dispatch(
computePass,
props.dispatchLayout.x,
props.dispatchLayout.y,
props.dispatchLayout.z
);
},
destroy: () => computation.destroy()
};
Expand Down
73 changes: 53 additions & 20 deletions modules/experimental/src/gpu-primitives/gpu-mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import {
type GraphBufferUse,
type GraphDataView
} from './gpu-command-graph';
import {
getBoundedDispatchLayout,
getBoundedInvocationIndexSource,
type GPUBoundedDispatchLayout
} from './gpu-dispatch-utils';
import {
getViewBinding,
getViewElementOffset,
Expand Down Expand Up @@ -97,26 +102,45 @@ export class GPUMask {
* The caller remains responsible for graph compilation, command submission, and readback.
*/
addToGraph<Parameters>(graph: GPUCommandGraph<Parameters>): void {
const outputChunks = getMaskChunks(this.output);
const inputChunks = this.inputs.map(getMaskChunks);
for (const chunk of [...outputChunks, ...inputChunks.flat()]) {
if (chunk.buffer.graph !== graph) {
throw new Error(`${this.id} masks must belong to the target graph`);
}
addGPUMaskToGraphWithDispatchLimit(
this,
graph,
graph.device.limits.maxComputeWorkgroupsPerDimension
);
}
}

/** Adds source-aligned mask composition with an explicit dispatch limit. @internal */
export function addGPUMaskToGraphWithDispatchLimit<Parameters>(
mask: GPUMask,
graph: GPUCommandGraph<Parameters>,
maxComputeWorkgroupsPerDimension: number
): void {
const outputChunks = getMaskChunks(mask.output);
const inputChunks = mask.inputs.map(getMaskChunks);
for (const chunk of [...outputChunks, ...inputChunks.flat()]) {
if (chunk.buffer.graph !== graph) {
throw new Error(`${mask.id} masks must belong to the target graph`);
}
}

for (const [chunkIndex, output] of outputChunks.entries()) {
if (output.length === 0) {
continue;
}
const inputs = inputChunks.map(chunks => chunks[chunkIndex]);
addMaskPass(graph, {
id: this.output instanceof GraphVectorView ? `${this.id}-chunk-${chunkIndex}` : this.id,
inputs,
output,
operation: this.operation
});
for (const [chunkIndex, output] of outputChunks.entries()) {
if (output.length === 0) {
continue;
}
const inputs = inputChunks.map(chunks => chunks[chunkIndex]);
addMaskPass(graph, {
id: mask.output instanceof GraphVectorView ? `${mask.id}-chunk-${chunkIndex}` : mask.id,
inputs,
output,
operation: mask.operation,
dispatchLayout: getBoundedDispatchLayout(
'GPUMask',
output.length,
MASK_WORKGROUP_SIZE,
maxComputeWorkgroupsPerDimension
)
});
}
}

Expand All @@ -133,6 +157,7 @@ function addMaskPass<Parameters>(
inputs: readonly GraphDataView<'uint32'>[];
output: GraphDataView<'uint32'>;
operation: GPUMaskOperation;
dispatchLayout: GPUBoundedDispatchLayout;
}
): void {
const inputDeclarations = props.inputs
Expand All @@ -154,8 +179,11 @@ ${inputDeclarations}
@group(0) @binding(${outputBinding}) var<storage, read_write> outputMask: array<u32>;

@compute @workgroup_size(${MASK_WORKGROUP_SIZE})
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
let index = globalId.x;
fn main(
@builtin(local_invocation_index) localInvocationIndex: u32,
@builtin(workgroup_id) workgroupId: vec3<u32>
) {
${getBoundedInvocationIndexSource(props.dispatchLayout, MASK_WORKGROUP_SIZE)}
if (index >= ELEMENT_COUNT) {
return;
}
Expand Down Expand Up @@ -193,7 +221,12 @@ fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
resolvedBindings[name] = getViewBinding(view, getBuffer);
}
computation.setBindings(resolvedBindings);
computation.dispatch(computePass, Math.ceil(props.output.length / MASK_WORKGROUP_SIZE));
computation.dispatch(
computePass,
props.dispatchLayout.x,
props.dispatchLayout.y,
props.dispatchLayout.z
);
},
destroy: () => computation.destroy()
};
Expand Down
Loading
Loading