Skip to main content

GPU Trace Picking

Overview

getGPUTracePickingShader generates a small WebGPU compute shader that resolves a timeline coordinate into the lowest matching visible canonical span row. It belongs to the optional @luma.gl/experimental/lutrace submodule because its inputs understand execution-span timing, process/thread lane topology, hierarchy collapse, and trace-specific visibility policies.

The motivating use case is a dense execution timeline whose visible rows move whenever a process or thread expands. A generic screen-space object ID cannot recover the original timeline span without knowing which canonical spans survived filtering and how their original thread lanes were projected into the current display layout. The shader resolves those domain-specific details while keeping dispatch, command submission, result ownership, and asynchronous readback application-owned.

Click a span in the live trace explorer, collapse one of its neighboring processes, and click the updated row again. Picking follows the GPU-computed effective lane rather than the span's original uncompacted position, and hidden spans cannot steal the visible selection.

GitHub

Concepts

Timeline coordinates are different from source identity

Every canonical span has a stable source row, an application object ID, an original timeline lane, and a time interval. Display lanes are not stable: process/thread collapse changes exclusive-scanned thread offsets, while filtering removes rows from visibility. The generated shader compares the requested time and effective lane against canonical span data and publishes the original source row.

This distinction is important for overlapping spans. The shader uses atomicMin so competing matches resolve deterministically to the lowest canonical row, not whichever GPU invocation happens to complete first. Applications may translate that row to the stable application object ID through GPUTraceSceneView.objectIds when updating their inspector or selection state.

Picking consumes the existing interaction outputs

GPUTraceInteraction already produces exclusive-scanned thread offsets and a source-aligned final visibility mask. The picking shader consumes those buffers directly; it does not recompute hierarchy layout, traverse dependencies, or create its own filtering policy.

The effective lane is:

threadOffsets[threadId] + originalLane % lanesPerThread

A source row is eligible only when the pick request is active, its final visibility-mask entry is nonzero, its interval includes the requested time, and its effective lane contains the requested vertical coordinate.

The result is caller-owned and sentinel-based

Initialize the result buffer to 0xffffffff before dispatch. Matching rows atomically minimize that value to their canonical source index. An inactive request or a coordinate outside all visible spans leaves the sentinel unchanged. Reinitialize the result before every independent request.

The generated entry point uses 256 invocations per workgroup. Dispatch enough workgroups to cover the canonical source-row count; the shader bounds-checks the final partial workgroup automatically. The application owns command-graph ordering, queue submission, staging-buffer reuse, readback, and any UI synchronization.

Usage

import {getGPUTracePickingShader} from '@luma.gl/experimental/lutrace';

const lanesPerThread = 4;
const source = getGPUTracePickingShader(trace.stats.spanCount, lanesPerThread);
const shader = device.createShader({stage: 'compute', source});
const workgroupCount = Math.ceil(trace.stats.spanCount / 256);

The caller binds the generated shader into its own compute pipeline. Queue the pick pass after hierarchy and visibility generation so it observes the same effective rows that rendering uses.

Storage-binding contract

All five bindings belong to bind group zero:

BindingAccessContents
0Read-only storagePacked eight-word canonical span records from GPUTraceScene
1Read-only storageExclusive-scanned effective thread offsets from GPUTraceInteraction
2Read-only storageSource-aligned final visibility mask from GPUTraceInteraction
3Read-only storagePick request: time: f32, lane: f32, active: u32, padding: u32
4Read/write storageOne atomic<u32> result initialized to 0xffffffff

spanCount must be a nonnegative safe integer, and lanesPerThread must be a positive safe integer. Both values specialize the generated WGSL; changing either requires generating a new shader. Changing the request, expansion state, or visibility policy does not.

Ownership and scope

The helper returns WGSL source only. It does not allocate resources, compile pipelines, import buffers into a command graph, schedule dispatches, submit work, read results back, or destroy caller-owned buffers.

General-purpose picking infrastructure remains independent from the trace domain. Use this helper when a selection must interpret trace time, hierarchy-projected lanes, and canonical span identity; use a renderer-specific picking target when object selection has no execution-timeline semantics.