(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.
That is as far as the document goes. A payload cannot consume its own readback,
so the numbers reach the page through the JavaScript runtime: after each submit
it maps every map-read destination the frame wrote and calls the host’s
onQueryResult with one number per 64-bit lane. See
Buffer readback.
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.
Where these words come from
Section titled “Where these words come from”Every key and value on this page traced to the WebGPU name it stands for, with a link to the definition. How the tracing is made, and what keeps it from rotting, is the subject of Where the Words Come From.
(query-set …) mirrors GPUQuerySetDescriptor (MDN).
| Key | WebGPU | Note |
|---|---|---|
:name |
GPUObjectDescriptorBase.label |
the label of GPUObjectDescriptorBase, and the name every cross-reference resolves |
:type |
GPUQuerySetDescriptor.type |
|
:count |
GPUQuerySetDescriptor.count |
Values
Section titled “Values”query-type (:type on (query-set …)) spells the GPUQueryType enum.
| Value | WebGPU | Note |
|---|---|---|
occlusion |
"occlusion" |
|
timestamp |
"timestamp" |
Checked against the WebGPU specification at revision b8c0fa9; the links go to the current draft.
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