(query-set …)
Declares a pool of :count GPU queries of one :type. A render pass writes
into the set, either by bracketing draws in an
(occlusion-query …) or by attaching
(timestamp-writes …) to the pass, and a
(resolve-query-set …) queue action copies the
results into a buffer, 8 bytes per query.
Syntax
Section titled “Syntax”(query-set :name occ :type occlusion :count 6)(query-set :name ts :type timestamp :count 2)| Key | Type | Required | Description |
|---|---|---|---|
:name |
symbol | Yes | Unique query-set name |
:type |
symbol | Yes | occlusion or timestamp |
:count |
number | Yes | Number of queries in the set (WebGPU’s ceiling is 4096) |
:count, like every numeric slot, takes a literal, a bare (define …)
constant, or a bounded expression over those constants.
| Value | Measures |
|---|---|
occlusion |
Samples that passed depth/stencil within an (occlusion-query …) bracket |
timestamp |
A GPU timestamp in nanoseconds (64-bit) |
Occlusion queries
Section titled “Occlusion queries”Set :occlusion-query-set on the (render-pass …),
then bracket the draws you want counted. (occlusion-query …) is not a
sibling of the draws; the draws are its positional children:
(query-set :name occ :type occlusion :count 6)(buffer :name results :size 48 :usage [query-resolve copy-src])
(render-pass :name draw (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) :pipeline pipe :occlusion-query-set occ (occlusion-query :query-index 0 (draw :vertex-count 36 :first-instance 0)) (occlusion-query :query-index 1 (draw :vertex-count 36 :first-instance 1)))
(queue :name readback (resolve-query-set :query-set occ :first-query 0 :query-count 6 :destination results :destination-offset 0))(occlusion-query …)
Section titled “(occlusion-query …)”| Key / Sub-form | Type | Default | Description |
|---|---|---|---|
:query-index |
number | 0 |
Index within the pass’s :occlusion-query-set |
(draw …) / (draw-indexed …) |
sub-form | - | The bracketed draw calls |
Those two are the only heads a bracket accepts; anything else is a
not_head_member. A bracket takes its place among the pass’s other commands,
in the order everything is written.
The pass must state :occlusion-query-set once it holds a bracket. The key is
optional only because a pass may hold none, so the compiler checks the pairing
and reports it on the pass:
render-pass 'draw' brackets draws in an (occlusion-query …) but states no:occlusion-query-setTimestamp queries
Section titled “Timestamp queries”Timestamps attach to the pass rather than bracketing draws. One query index is written at pass start, another at pass end:
(query-set :name ts :type timestamp :count 2)
(render-pass :name draw (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) (timestamp-writes :query-set ts :beginning-of-pass-write-index 0 :end-of-pass-write-index 1) :pipeline pipe (draw :vertex-count 3))(timestamp-writes …)
Section titled “(timestamp-writes …)”| Key | Type | Required | Default | Description |
|---|---|---|---|---|
:query-set |
reference | Yes | - | The (query-set :type timestamp) written into |
:beginning-of-pass-write-index |
number | No | 0 |
Query index written at pass start (the IDL leaves it absent, meaning not written; pngine bakes 0) |
:end-of-pass-write-index |
number | No | 1 |
Query index written at pass end (pngine bakes 1) |
A pass carries at most one (timestamp-writes …); a second is a
positional_too_many.
Timestamp queries need the timestamp-query device feature. The browser
runtime requests it at device creation wherever the adapter reports it, along
with the other optional features a payload might use.
Reading results back
Section titled “Reading results back”Query results live on the GPU. A (resolve-query-set …) queue action copies
them into a buffer that carries query-resolve usage; to read them on the CPU,
copy that buffer into a map-read buffer with (copy-buffer-to-buffer …):
(buffer :name resolveBuf :size 48 :usage [query-resolve copy-src])(buffer :name readbackBuf :size 48 :usage [map-read copy-dst])
(queue :name readback (resolve-query-set :query-set occ :first-query 0 :query-count 6 :destination resolveBuf :destination-offset 0) (copy-buffer-to-buffer :source resolveBuf :source-offset 0 :destination readbackBuf :destination-offset 0 :size 48))WebGPU requires :destination-offset to be a multiple of 256 on
resolve-query-set, and multiples of 4 on copy-buffer-to-buffer.
Validation Rules
Section titled “Validation Rules”| Rule | Error |
|---|---|
| A name is unique across every form kind | duplicate_cross_ref_target |
:type and :count are required |
missing_required_key |
:type must be occlusion or timestamp |
not_member |
| A referenced query set must exist | not_cross_ref |
An (occlusion-query …) child other than a draw |
not_head_member |
A second (timestamp-writes …) in one pass |
positional_too_many |
resolve-query-set’s :destination-offset is a multiple of 256 |
number_not_multiple |
Two more are the compiler’s, reading values across forms, so they carry a
located message and no code: a bracket without the pass’s
:occlusion-query-set (quoted above), and a (copy-buffer-to-buffer …)
endpoint whose (buffer …) lacks the usage the copy needs:
buffer 'readbackBuf' is the :destination of a copy but its :usage has no copy-dstThe query-resolve usage a resolve-query-set destination needs is WebGPU’s
rule, not one the compiler checks: get it wrong and the error arrives from the
device at run time.
WebGPU Mapping
Section titled “WebGPU Mapping”const occ = device.createQuerySet({ type: 'occlusion', count: 6 });
const pass = encoder.beginRenderPass({ colorAttachments, occlusionQuerySet: occ });pass.beginOcclusionQuery(0);pass.draw(36, 1, 0, 0);pass.endOcclusionQuery();pass.end();
encoder.resolveQuerySet(occ, 0, 6, resolveBuf, 0);Platform notes
Section titled “Platform notes”The whole query family is browser-only: the native --frame renderer treats
occlusion and timestamp queries as no-ops, so a payload that depends on query
results renders identically without them.
Related
Section titled “Related”(render-pass …)-:occlusion-query-set,(timestamp-writes …),(occlusion-query …)(queue …)-(resolve-query-set …)and(copy-buffer-to-buffer …)(buffer …)-query-resolve/map-readdestinations