@aethervtt/shard-gpu-webgl2 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import type { Webgl2BindGroup } from './binding.js';
2
2
  import type { Webgl2RenderPipeline } from './pipeline.js';
3
+ import type { Webgl2QuerySet } from './queries.js';
3
4
  import type { Webgl2Buffer, Webgl2TextureView } from './resources.js';
4
5
  export declare const OP: {
5
6
  readonly BEGIN_PASS: 1;
@@ -19,6 +20,9 @@ export declare const OP: {
19
20
  readonly COPY_TEXTURE_TO_BUFFER: 15;
20
21
  readonly COPY_TEXTURE: 16;
21
22
  readonly CLEAR_BUFFER: 17;
23
+ readonly BEGIN_QUERY: 18;
24
+ readonly END_QUERY: 19;
25
+ readonly RESOLVE_QUERIES: 20;
22
26
  };
23
27
  export declare class CommandStream {
24
28
  nums: Float64Array<ArrayBuffer>;
@@ -41,6 +45,8 @@ export declare class Webgl2CommandBuffer {
41
45
  type Extent = GPUExtent3D;
42
46
  export declare class Webgl2CommandEncoder {
43
47
  label: string;
48
+ /** @internal The pass being recorded times itself (`timestampWrites`): its end ends the query. */
49
+ timedPass: boolean;
44
50
  private stream;
45
51
  private readonly pass;
46
52
  private readonly buffer;
@@ -56,7 +62,8 @@ export declare class Webgl2CommandEncoder {
56
62
  copyTextureToBuffer(source: GPUTexelCopyTextureInfo, destination: GPUTexelCopyBufferInfo, size: Extent): void;
57
63
  copyTextureToTexture(source: GPUTexelCopyTextureInfo, destination: GPUTexelCopyTextureInfo, size: Extent): void;
58
64
  clearBuffer(buffer: Webgl2Buffer, offset?: number, size?: number): void;
59
- resolveQuerySet(): never;
65
+ /** Pairs of the set to the buffer: written when the buffer is mapped (queries.ts). */
66
+ resolveQuerySet(querySet: Webgl2QuerySet, firstQuery: number, queryCount: number, destination: Webgl2Buffer, destinationOffset: number): void;
60
67
  writeTimestamp(): never;
61
68
  pushDebugGroup(): void;
62
69
  popDebugGroup(): void;
package/dist/commands.js CHANGED
@@ -16,7 +16,10 @@ const OP = {
16
16
  COPY_BUFFER_TO_TEXTURE: 14,
17
17
  COPY_TEXTURE_TO_BUFFER: 15,
18
18
  COPY_TEXTURE: 16,
19
- CLEAR_BUFFER: 17
19
+ CLEAR_BUFFER: 17,
20
+ BEGIN_QUERY: 18,
21
+ END_QUERY: 19,
22
+ RESOLVE_QUERIES: 20
20
23
  };
21
24
  class CommandStream {
22
25
  nums = new Float64Array(1024);
@@ -70,6 +73,8 @@ function colorOf(c, i) {
70
73
  }
71
74
  class Webgl2CommandEncoder {
72
75
  label = "";
76
+ /** @internal The pass being recorded times itself (`timestampWrites`): its end ends the query. */
77
+ timedPass = false;
73
78
  stream;
74
79
  pass;
75
80
  buffer;
@@ -92,6 +97,14 @@ class Webgl2CommandEncoder {
92
97
  beginRenderPass(d) {
93
98
  const s = this.recording;
94
99
  if (d.occlusionQuerySet) throw unsupported(`run occlusion queries ("${d.label ?? this.label}")`);
100
+ const writes = d.timestampWrites;
101
+ const index = writes?.beginningOfPassWriteIndex ?? writes?.endOfPassWriteIndex;
102
+ this.timedPass = writes !== void 0 && index !== void 0;
103
+ if (this.timedPass) {
104
+ s.num(OP.BEGIN_QUERY);
105
+ s.num(index >> 1);
106
+ s.ref(writes.querySet);
107
+ }
95
108
  s.num(OP.BEGIN_PASS);
96
109
  const colors = d.colorAttachments;
97
110
  s.num(colors.length);
@@ -211,11 +224,18 @@ class Webgl2CommandEncoder {
211
224
  s.num(size ?? buffer.size - offset);
212
225
  s.ref(buffer);
213
226
  }
214
- resolveQuerySet() {
215
- throw unsupported("resolve query sets", "timestamp-query is never offered on WebGL2.");
227
+ /** Pairs of the set to the buffer: written when the buffer is mapped (queries.ts). */
228
+ resolveQuerySet(querySet, firstQuery, queryCount, destination, destinationOffset) {
229
+ const s = this.recording;
230
+ s.num(OP.RESOLVE_QUERIES);
231
+ s.num(firstQuery);
232
+ s.num(queryCount);
233
+ s.num(destinationOffset);
234
+ s.ref(querySet);
235
+ s.ref(destination);
216
236
  }
217
237
  writeTimestamp() {
218
- throw unsupported("write timestamps", "timestamp-query is never offered on WebGL2.");
238
+ throw unsupported("write timestamps", "WebGL2 times passes (timestampWrites) only.");
219
239
  }
220
240
  pushDebugGroup() {
221
241
  }
@@ -345,7 +365,12 @@ class Webgl2RenderPassEncoder {
345
365
  insertDebugMarker() {
346
366
  }
347
367
  end() {
348
- this.encoder.recording.num(OP.END_PASS);
368
+ const s = this.encoder.recording;
369
+ s.num(OP.END_PASS);
370
+ if (this.encoder.timedPass) {
371
+ s.num(OP.END_QUERY);
372
+ this.encoder.timedPass = false;
373
+ }
349
374
  }
350
375
  }
351
376
  function layerOf(view, depthSlice) {
package/dist/copies.js CHANGED
@@ -232,6 +232,14 @@ class Copier {
232
232
  gl.bindBuffer(GL.COPY_READ_BUFFER, src.gl);
233
233
  gl.bindBuffer(GL.COPY_WRITE_BUFFER, dst.gl);
234
234
  gl.copyBufferSubData(GL.COPY_READ_BUFFER, GL.COPY_WRITE_BUFFER, srcOffset, dstOffset, size);
235
+ if (src.queryReads.length > 0) {
236
+ for (let i = src.queryReads.length - 1; i >= 0; i--) {
237
+ const read = src.queryReads[i];
238
+ if (read.offset < srcOffset || read.offset >= srcOffset + size) continue;
239
+ src.queryReads.splice(i, 1);
240
+ dst.queryReads.push({ offset: read.offset - srcOffset + dstOffset, queries: read.queries });
241
+ }
242
+ }
235
243
  if (dst.shadow) {
236
244
  if (src.shadow) dst.shadow.set(src.shadow.subarray(srcOffset, srcOffset + size), dstOffset);
237
245
  else gl.getBufferSubData(GL.COPY_WRITE_BUFFER, dstOffset, dst.shadow, dstOffset, size);
package/dist/device.d.ts CHANGED
@@ -5,6 +5,7 @@ import { Copier } from './copies.js';
5
5
  import { ShimError } from './errors.js';
6
6
  import { FramebufferCache } from './fbo.js';
7
7
  import { Webgl2RenderPipeline, Webgl2ShaderModule } from './pipeline.js';
8
+ import { QueryPool, Webgl2QuerySet } from './queries.js';
8
9
  import { Webgl2Queue } from './queue.js';
9
10
  import { Replayer } from './replay.js';
10
11
  import { Webgl2Buffer, Webgl2Sampler, Webgl2Texture } from './resources.js';
@@ -71,6 +72,8 @@ export interface Webgl2DeviceOptions {
71
72
  /** Extensions the device calls into, enabled by the adapter. */
72
73
  export interface Webgl2Extensions {
73
74
  clipControl: unknown;
75
+ /** EXT_disjoint_timer_query_webgl2: TIME_ELAPSED queries time passes (0074). */
76
+ timer: unknown;
74
77
  indexed: OES_draw_buffers_indexed | null;
75
78
  lose: WEBGL_lose_context | null;
76
79
  }
@@ -85,6 +88,10 @@ export declare class Webgl2Device extends EventTarget {
85
88
  readonly state: GlState;
86
89
  readonly fbos: FramebufferCache;
87
90
  readonly copier: Copier;
91
+ /** Query objects for timed passes (`timestamp-query`, 0074). */
92
+ readonly queries: QueryPool;
93
+ /** Where the next timed pass's stamps start: one timeline across frames, as GPU clocks are. */
94
+ timerAt: bigint;
88
95
  readonly replayer: Replayer;
89
96
  readonly queue: Webgl2Queue;
90
97
  readonly features: ReadonlySet<string>;
@@ -122,7 +129,8 @@ export declare class Webgl2Device extends EventTarget {
122
129
  createRenderPipelineAsync(descriptor: GPURenderPipelineDescriptor): Promise<Webgl2RenderPipeline>;
123
130
  createComputePipeline(descriptor: GPUComputePipelineDescriptor): never;
124
131
  createComputePipelineAsync(descriptor: GPUComputePipelineDescriptor): Promise<never>;
125
- createQuerySet(descriptor: GPUQuerySetDescriptor): never;
132
+ /** Timestamp query sets where the timer extension exists (see queries.ts). */
133
+ createQuerySet(descriptor: GPUQuerySetDescriptor): Webgl2QuerySet;
126
134
  createRenderBundleEncoder(): never;
127
135
  importExternalTexture(): never;
128
136
  createCommandEncoder(descriptor?: GPUCommandEncoderDescriptor): Webgl2CommandEncoder;
package/dist/device.js CHANGED
@@ -7,6 +7,7 @@ import { ShimError, unsupported } from "./errors.js";
7
7
  import { FramebufferCache } from "./fbo.js";
8
8
  import { CLIP, GL } from "./gl.js";
9
9
  import { Webgl2RenderPipeline, Webgl2ShaderModule } from "./pipeline.js";
10
+ import { finishQueryReads, QueryPool, Webgl2QuerySet } from "./queries.js";
10
11
  import { Webgl2Queue } from "./queue.js";
11
12
  import { Replayer } from "./replay.js";
12
13
  import { Webgl2Buffer, Webgl2Sampler, Webgl2Texture } from "./resources.js";
@@ -19,6 +20,10 @@ class Webgl2Device extends EventTarget {
19
20
  state;
20
21
  fbos;
21
22
  copier;
23
+ /** Query objects for timed passes (`timestamp-query`, 0074). */
24
+ queries;
25
+ /** Where the next timed pass's stamps start: one timeline across frames, as GPU clocks are. */
26
+ timerAt = 1n;
22
27
  replayer;
23
28
  queue;
24
29
  features;
@@ -62,6 +67,7 @@ class Webgl2Device extends EventTarget {
62
67
  this.fbos.incomplete = (status) => this.raise("validation", `A framebuffer is incomplete (0x${status.toString(16)})`);
63
68
  }
64
69
  this.copier = new Copier(this);
70
+ this.queries = new QueryPool(gl);
65
71
  this.replayer = new Replayer(this);
66
72
  this.queue = new Webgl2Queue(this);
67
73
  gl.pixelStorei(GL.UNPACK_ALIGNMENT, 1);
@@ -150,8 +156,12 @@ class Webgl2Device extends EventTarget {
150
156
  )
151
157
  );
152
158
  }
159
+ /** Timestamp query sets where the timer extension exists (see queries.ts). */
153
160
  createQuerySet(descriptor) {
154
- throw unsupported(`make a ${descriptor.type} query set`);
161
+ if (descriptor.type !== "timestamp" || !this.features.has("timestamp-query")) {
162
+ throw unsupported(`make a ${descriptor.type} query set`);
163
+ }
164
+ return new Webgl2QuerySet(this, descriptor);
155
165
  }
156
166
  createRenderBundleEncoder() {
157
167
  throw unsupported("record render bundles");
@@ -199,6 +209,7 @@ class Webgl2Device extends EventTarget {
199
209
  lose(reason, message) {
200
210
  if (this.isLost) return;
201
211
  this.isLost = true;
212
+ this.queries.clear();
202
213
  this.canvas?.removeEventListener("webglcontextlost", this.onContextLost);
203
214
  for (const f of this.fences.splice(0)) f.resolve();
204
215
  this.resolveLost({ reason, message });
@@ -282,7 +293,7 @@ class Webgl2Device extends EventTarget {
282
293
  return Promise.reject(error);
283
294
  }
284
295
  buffer.mapState = "pending";
285
- return this.afterGpu().then(() => {
296
+ return this.afterGpu().then(async () => {
286
297
  if (buffer.destroyed || buffer.mapState !== "pending") {
287
298
  const error = new Error(`Mapping "${buffer.label}" was aborted`);
288
299
  error.name = "AbortError";
@@ -304,6 +315,12 @@ class Webgl2Device extends EventTarget {
304
315
  }
305
316
  buffer.reads.length = 0;
306
317
  }
318
+ if (buffer.queryReads.length > 0) await finishQueryReads(this, buffer, bytes, offset);
319
+ if (buffer.destroyed || buffer.mapState !== "pending") {
320
+ const error = new Error(`Mapping "${buffer.label}" was aborted`);
321
+ error.name = "AbortError";
322
+ throw error;
323
+ }
307
324
  buffer.mapped = bytes.buffer;
308
325
  buffer.mappedOffset = offset;
309
326
  buffer.mapState = "mapped";
package/dist/fake-gl.d.ts CHANGED
@@ -116,6 +116,14 @@ export interface FakeGlOptions {
116
116
  linkPolls?: number;
117
117
  /** UNIFORM_BLOCK_DATA_SIZE by block name prefix (default 0: whatever is bound). */
118
118
  blockSizes?: Record<string, number>;
119
+ /**
120
+ * Offers EXT_disjoint_timer_query_webgl2 (0074): every TIME_ELAPSED query takes `elapsedNs`, and
121
+ * its result is available after `polls` reads of QUERY_RESULT_AVAILABLE (default 0).
122
+ */
123
+ timerQuery?: {
124
+ elapsedNs: number;
125
+ polls?: number;
126
+ };
119
127
  }
120
128
  export declare class FakeGl {
121
129
  readonly canvas: {
@@ -309,6 +317,20 @@ export declare class FakeGl {
309
317
  drawArraysInstanced(mode: number, first: number, count: number, instances: number): void;
310
318
  drawElementsInstanced(mode: number, count: number, type: number, offset: number, instances: number): void;
311
319
  private record;
320
+ /** Queries begun so far, and the one running. */
321
+ queriesBegun: number;
322
+ private activeQuery;
323
+ createQuery(): object;
324
+ deleteQuery(): void;
325
+ beginQuery(_target: number, query: {
326
+ polls: number;
327
+ ended: boolean;
328
+ }): void;
329
+ endQuery(): void;
330
+ getQueryParameter(query: {
331
+ polls: number;
332
+ ended: boolean;
333
+ }, p: number): number | boolean;
312
334
  fenceSync(): object;
313
335
  getSyncParameter(): number;
314
336
  deleteSync(): void;
package/dist/fake-gl.js CHANGED
@@ -155,6 +155,7 @@ class FakeGl {
155
155
  }
156
156
  }
157
157
  };
158
+ if (options.timerQuery) all.EXT_disjoint_timer_query_webgl2 = {};
158
159
  for (const [name, ext] of Object.entries(all)) {
159
160
  if (!options.without?.includes(name)) this.extensions.set(name, ext);
160
161
  }
@@ -893,6 +894,36 @@ class FakeGl {
893
894
  });
894
895
  }
895
896
  // --- sync -----------------------------------------------------------------------------
897
+ // --- timer queries (EXT_disjoint_timer_query_webgl2) --------------------------------
898
+ /** Queries begun so far, and the one running. */
899
+ queriesBegun = 0;
900
+ activeQuery = null;
901
+ createQuery() {
902
+ return { kind: "query", id: this.next++, polls: 0, ended: false };
903
+ }
904
+ deleteQuery() {
905
+ }
906
+ beginQuery(_target, query) {
907
+ query.polls = this.options.timerQuery?.polls ?? 0;
908
+ query.ended = false;
909
+ this.activeQuery = query;
910
+ this.queriesBegun++;
911
+ }
912
+ endQuery() {
913
+ if (this.activeQuery) this.activeQuery.ended = true;
914
+ this.activeQuery = null;
915
+ }
916
+ getQueryParameter(query, p) {
917
+ if (p === 34919) {
918
+ if (!query.ended) return false;
919
+ if (query.polls > 0) {
920
+ query.polls--;
921
+ return false;
922
+ }
923
+ return true;
924
+ }
925
+ return this.options.timerQuery?.elapsedNs ?? 0;
926
+ }
896
927
  fenceSync() {
897
928
  return { kind: "sync", id: this.next++ };
898
929
  }
package/dist/gpu.js CHANGED
@@ -70,7 +70,7 @@ class Webgl2Adapter {
70
70
  const { caps, extensions } = capsOf(opened.gl, options.profile ?? "native");
71
71
  this.caps = caps;
72
72
  this.extensions = extensions;
73
- this.features = featuresOf(opened.gl, caps);
73
+ this.features = featuresOf(opened.gl, caps, extensions.timer !== null);
74
74
  this.limits = limitsOf(caps);
75
75
  this.info = infoOf(opened.gl);
76
76
  }
@@ -113,6 +113,7 @@ function capsOf(gl, profile) {
113
113
  const floatLinear = ext("OES_texture_float_linear");
114
114
  const indexed = ext("OES_draw_buffers_indexed");
115
115
  const lose = gl.getExtension("WEBGL_lose_context");
116
+ const timer = ext("EXT_disjoint_timer_query_webgl2");
116
117
  const bc = ext("WEBGL_compressed_texture_s3tc") !== null && ext("WEBGL_compressed_texture_s3tc_srgb") !== null && ext("EXT_texture_compression_rgtc") !== null && ext("EXT_texture_compression_bptc") !== null;
117
118
  const caps = {
118
119
  maxSamples: minimum ? Math.min(4, p(GL.MAX_SAMPLES)) : p(GL.MAX_SAMPLES),
@@ -141,10 +142,11 @@ function capsOf(gl, profile) {
141
142
  etc2: ext("WEBGL_compressed_texture_etc") !== null
142
143
  }
143
144
  };
144
- return { caps, extensions: { clipControl, indexed, lose } };
145
+ return { caps, extensions: { clipControl, indexed, lose, timer } };
145
146
  }
146
- function featuresOf(gl, caps) {
147
+ function featuresOf(gl, caps, timer) {
147
148
  const features = /* @__PURE__ */ new Set(["depth32float-stencil8"]);
149
+ if (timer) features.add("timestamp-query");
148
150
  if (caps.compression.bc) features.add("texture-compression-bc");
149
151
  if (caps.compression.astc) features.add("texture-compression-astc");
150
152
  if (caps.compression.etc2) features.add("texture-compression-etc2");
@@ -0,0 +1,41 @@
1
+ import type { Webgl2Device } from './device.js';
2
+ import type { Webgl2Buffer } from './resources.js';
3
+ export declare const TIME_ELAPSED_EXT = 35007;
4
+ export declare const GPU_DISJOINT_EXT = 36795;
5
+ /** Queries resolved into a buffer, waiting for results: pair k's begin/end at `offset + 16k`. */
6
+ export interface QueryRead {
7
+ offset: number;
8
+ queries: (WebGLQuery | null)[];
9
+ }
10
+ export declare class Webgl2QuerySet {
11
+ readonly type: GPUQueryType;
12
+ readonly count: number;
13
+ label: string;
14
+ /** The query recording each pair this frame (one per pass), until resolved. */
15
+ private readonly pairs;
16
+ private readonly device;
17
+ private destroyed;
18
+ constructor(device: Webgl2Device, descriptor: GPUQuerySetDescriptor);
19
+ /** @internal Replay: starts timing pair `pair` (a pass with timestamp writes begins). */
20
+ begin(pair: number): void;
21
+ /** @internal Replay: the timed pass ended. */
22
+ end(): void;
23
+ /** @internal Replay: hands queries `first` to `first + count` to `buffer` at `offset`. */
24
+ resolve(first: number, count: number, buffer: Webgl2Buffer, offset: number): void;
25
+ destroy(): void;
26
+ }
27
+ /** Query objects, reused: a frame times a few dozen passes, every frame. */
28
+ export declare class QueryPool {
29
+ private readonly free;
30
+ private readonly gl;
31
+ constructor(gl: WebGL2RenderingContext);
32
+ take(): WebGLQuery | null;
33
+ give(query: WebGLQuery): void;
34
+ /** Forgets the pool (the context was lost: its queries went with it). */
35
+ clear(): void;
36
+ }
37
+ /**
38
+ * Waits for `buffer`'s pending query results and writes them into `bytes` (the mapped range, which
39
+ * starts at `mapOffset`), then returns the queries to the pool.
40
+ */
41
+ export declare function finishQueryReads(device: Webgl2Device, buffer: Webgl2Buffer, bytes: Uint8Array, mapOffset: number): Promise<void>;
@@ -0,0 +1,110 @@
1
+ const TIME_ELAPSED_EXT = 35007;
2
+ const GPU_DISJOINT_EXT = 36795;
3
+ const QUERY_RESULT = 34918;
4
+ const QUERY_RESULT_AVAILABLE = 34919;
5
+ const WAIT_MS = 2e3;
6
+ class Webgl2QuerySet {
7
+ type = "timestamp";
8
+ count;
9
+ label;
10
+ /** The query recording each pair this frame (one per pass), until resolved. */
11
+ pairs;
12
+ device;
13
+ destroyed = false;
14
+ constructor(device, descriptor) {
15
+ this.device = device;
16
+ this.count = descriptor.count;
17
+ this.label = descriptor.label ?? "";
18
+ this.pairs = new Array(Math.ceil(descriptor.count / 2)).fill(null);
19
+ }
20
+ /** @internal Replay: starts timing pair `pair` (a pass with timestamp writes begins). */
21
+ begin(pair) {
22
+ if (this.destroyed) return;
23
+ const query = this.device.queries.take();
24
+ if (!query) return;
25
+ this.device.gl.beginQuery(TIME_ELAPSED_EXT, query);
26
+ this.pairs[pair] = query;
27
+ }
28
+ /** @internal Replay: the timed pass ended. */
29
+ end() {
30
+ this.device.gl.endQuery(TIME_ELAPSED_EXT);
31
+ }
32
+ /** @internal Replay: hands queries `first` to `first + count` to `buffer` at `offset`. */
33
+ resolve(first, count, buffer, offset) {
34
+ const from = first >> 1;
35
+ const to = first + count + 1 >> 1;
36
+ const queries = [];
37
+ for (let p = from; p < to; p++) {
38
+ queries.push(this.pairs[p] ?? null);
39
+ this.pairs[p] = null;
40
+ }
41
+ buffer.queryReads.push({ offset, queries });
42
+ }
43
+ destroy() {
44
+ this.destroyed = true;
45
+ for (let p = 0; p < this.pairs.length; p++) {
46
+ const q = this.pairs[p];
47
+ if (q) this.device.queries.give(q);
48
+ this.pairs[p] = null;
49
+ }
50
+ }
51
+ }
52
+ class QueryPool {
53
+ free = [];
54
+ gl;
55
+ constructor(gl) {
56
+ this.gl = gl;
57
+ }
58
+ take() {
59
+ return this.free.pop() ?? this.gl.createQuery();
60
+ }
61
+ give(query) {
62
+ this.free.push(query);
63
+ }
64
+ /** Forgets the pool (the context was lost: its queries went with it). */
65
+ clear() {
66
+ this.free.length = 0;
67
+ }
68
+ }
69
+ const waitTick = () => new Promise((resolve) => setTimeout(resolve, 4));
70
+ async function finishQueryReads(device, buffer, bytes, mapOffset) {
71
+ const reads = buffer.queryReads.splice(0);
72
+ if (reads.length === 0) return;
73
+ const gl = device.gl;
74
+ const deadline = performance.now() + WAIT_MS;
75
+ const ready = () => reads.every(
76
+ (r) => r.queries.every((q) => !q || gl.getQueryParameter(q, QUERY_RESULT_AVAILABLE) === true)
77
+ );
78
+ while (!device.isLost && !ready() && performance.now() < deadline) await waitTick();
79
+ const lost = device.isLost;
80
+ const disjoint = !lost && gl.getParameter(GPU_DISJOINT_EXT) === true;
81
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
82
+ for (const read of reads) {
83
+ let at = device.timerAt;
84
+ for (let k = 0; k < read.queries.length; k++) {
85
+ const q = read.queries[k];
86
+ const pos = read.offset - mapOffset + k * 16;
87
+ let begin = 0n;
88
+ let end = 0n;
89
+ if (q && !lost && !disjoint && gl.getQueryParameter(q, QUERY_RESULT_AVAILABLE) === true) {
90
+ const elapsed = BigInt(gl.getQueryParameter(q, QUERY_RESULT));
91
+ begin = at;
92
+ end = at + elapsed;
93
+ at = end;
94
+ device.timerAt = end;
95
+ }
96
+ if (pos >= 0 && pos + 16 <= bytes.byteLength) {
97
+ view.setBigUint64(pos, begin, true);
98
+ view.setBigUint64(pos + 8, end, true);
99
+ }
100
+ if (q && !lost) device.queries.give(q);
101
+ }
102
+ }
103
+ }
104
+ export {
105
+ GPU_DISJOINT_EXT,
106
+ QueryPool,
107
+ TIME_ELAPSED_EXT,
108
+ Webgl2QuerySet,
109
+ finishQueryReads
110
+ };
package/dist/replay.d.ts CHANGED
@@ -10,6 +10,8 @@ export declare class Replayer {
10
10
  private stream;
11
11
  private i;
12
12
  private r;
13
+ /** The query set timing the pass being replayed. */
14
+ private timed;
13
15
  private passFbo;
14
16
  private passWidth;
15
17
  private passHeight;
package/dist/replay.js CHANGED
@@ -12,6 +12,8 @@ class Replayer {
12
12
  stream;
13
13
  i = 0;
14
14
  r = 0;
15
+ /** The query set timing the pass being replayed. */
16
+ timed;
15
17
  // The pass.
16
18
  passFbo = null;
17
19
  passWidth = 0;
@@ -245,6 +247,25 @@ class Replayer {
245
247
  this.device.copier.clearBuffer(this.ref(), offset, size);
246
248
  break;
247
249
  }
250
+ case OP.BEGIN_QUERY: {
251
+ const pair = this.num();
252
+ const set = this.ref();
253
+ set.begin(pair);
254
+ this.timed = set;
255
+ break;
256
+ }
257
+ case OP.END_QUERY:
258
+ this.timed?.end();
259
+ this.timed = void 0;
260
+ break;
261
+ case OP.RESOLVE_QUERIES: {
262
+ const first = this.num();
263
+ const count = this.num();
264
+ const offset = this.num();
265
+ const set = this.ref();
266
+ set.resolve(first, count, this.ref(), offset);
267
+ break;
268
+ }
248
269
  default:
249
270
  throw new Error(`Unknown recorded command ${op}`);
250
271
  }
@@ -1,6 +1,7 @@
1
1
  import type { ReadJob } from './copies.js';
2
2
  import type { Webgl2Device } from './device.js';
3
3
  import { type GlFormat } from './formats.js';
4
+ import type { QueryRead } from './queries.js';
4
5
  export declare class Webgl2Buffer {
5
6
  readonly id: number;
6
7
  readonly size: number;
@@ -28,6 +29,8 @@ export declare class Webgl2Buffer {
28
29
  mappedOffset: number;
29
30
  /** Texture reads into this buffer, repacked when it's mapped. */
30
31
  readonly reads: ReadJob[];
32
+ /** Timer queries resolved into this buffer, written when it's mapped (0074). */
33
+ readonly queryReads: QueryRead[];
31
34
  destroyed: boolean;
32
35
  private readonly device;
33
36
  constructor(device: Webgl2Device, descriptor: GPUBufferDescriptor);
package/dist/resources.js CHANGED
@@ -30,6 +30,8 @@ class Webgl2Buffer {
30
30
  mappedOffset = 0;
31
31
  /** Texture reads into this buffer, repacked when it's mapped. */
32
32
  reads = [];
33
+ /** Timer queries resolved into this buffer, written when it's mapped (0074). */
34
+ queryReads = [];
33
35
  destroyed = false;
34
36
  device;
35
37
  constructor(device, descriptor) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aethervtt/shard-gpu-webgl2",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -21,7 +21,7 @@
21
21
  "README.md"
22
22
  ],
23
23
  "dependencies": {
24
- "@aethervtt/shard-core": "0.1.0"
24
+ "@aethervtt/shard-core": "0.2.1"
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
@@ -33,6 +33,6 @@
33
33
  "provenance": true
34
34
  },
35
35
  "shard": {
36
- "buildSha": "8e14c9896f82a3554b156f1ffdd68f2cc3e7d78a"
36
+ "buildSha": "772eb0d2b8a5fc499105708f4ae92c58788cc9ea"
37
37
  }
38
38
  }