@aethervtt/shard-gpu-webgl2 0.0.0-stage → 0.2.0

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/binding.d.ts +32 -0
  4. package/dist/binding.js +81 -0
  5. package/dist/cache.d.ts +73 -0
  6. package/dist/cache.js +184 -0
  7. package/dist/commands.d.ts +101 -0
  8. package/dist/commands.js +391 -0
  9. package/dist/constants.d.ts +41 -0
  10. package/dist/constants.js +38 -0
  11. package/dist/copies.d.ts +61 -0
  12. package/dist/copies.js +629 -0
  13. package/dist/device.d.ts +168 -0
  14. package/dist/device.js +401 -0
  15. package/dist/errors.d.ts +8 -0
  16. package/dist/errors.js +18 -0
  17. package/dist/fake-gl.d.ts +338 -0
  18. package/dist/fake-gl.js +980 -0
  19. package/dist/fbo.d.ts +30 -0
  20. package/dist/fbo.js +142 -0
  21. package/dist/formats.d.ts +25 -0
  22. package/dist/formats.js +106 -0
  23. package/dist/gl.d.ts +216 -0
  24. package/dist/gl.js +232 -0
  25. package/dist/gpu.d.ts +58 -0
  26. package/dist/gpu.js +215 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.js +42 -0
  29. package/dist/naga.d.ts +37 -0
  30. package/dist/naga.js +99 -0
  31. package/dist/pipeline.d.ts +113 -0
  32. package/dist/pipeline.js +294 -0
  33. package/dist/present.d.ts +18 -0
  34. package/dist/present.js +108 -0
  35. package/dist/queries.d.ts +41 -0
  36. package/dist/queries.js +110 -0
  37. package/dist/queue.d.ts +13 -0
  38. package/dist/queue.js +50 -0
  39. package/dist/readback.d.ts +17 -0
  40. package/dist/readback.js +104 -0
  41. package/dist/replay.d.ts +78 -0
  42. package/dist/replay.js +678 -0
  43. package/dist/resources.d.ts +96 -0
  44. package/dist/resources.js +355 -0
  45. package/dist/state.d.ts +43 -0
  46. package/dist/state.js +169 -0
  47. package/package.json +36 -4
  48. package/wasm/shard_naga.wasm +0 -0
@@ -0,0 +1,113 @@
1
+ import type { Webgl2PipelineLayout } from './binding.js';
2
+ import type { Webgl2Device } from './device.js';
3
+ import type { GlslTranslation } from './naga.js';
4
+ export declare class Webgl2ShaderModule {
5
+ readonly id: number;
6
+ readonly code: string;
7
+ readonly label: string;
8
+ constructor(d: GPUShaderModuleDescriptor);
9
+ /** Answered without translating: pipelines report what doesn't translate or link. */
10
+ getCompilationInfo(): Promise<{
11
+ messages: readonly unknown[];
12
+ }>;
13
+ }
14
+ /** One stage's WGSL and entry point. */
15
+ export interface StageSource {
16
+ code: string;
17
+ entry: string;
18
+ }
19
+ /** A uniform block of the program, and the group binding that feeds it. */
20
+ export interface ProgramBlock {
21
+ group: number;
22
+ binding: number;
23
+ point: number;
24
+ /**
25
+ * The block's size in GLSL (std140 rounds a block up to 16 bytes; WGSL doesn't round a uniform
26
+ * struct): a draw fails unless at least this much is bound.
27
+ */
28
+ size: number;
29
+ }
30
+ /** A combined sampler of the program: its texture unit, texture binding and sampler binding. */
31
+ export interface ProgramSampler {
32
+ unit: number;
33
+ group: number;
34
+ binding: number;
35
+ /** The sampler's group and binding, or -1 for `texelFetch` reads (no sampler). */
36
+ samplerGroup: number;
37
+ samplerBinding: number;
38
+ }
39
+ export interface VertexAttribute {
40
+ location: number;
41
+ size: number;
42
+ type: number;
43
+ normalized: boolean;
44
+ integer: boolean;
45
+ offset: number;
46
+ }
47
+ export interface VertexBufferLayout {
48
+ stride: number;
49
+ instanced: boolean;
50
+ attributes: VertexAttribute[];
51
+ }
52
+ interface BlendState {
53
+ enabled: boolean;
54
+ colorOp: number;
55
+ colorSrc: number;
56
+ colorDst: number;
57
+ alphaOp: number;
58
+ alphaSrc: number;
59
+ alphaDst: number;
60
+ }
61
+ export declare class Webgl2RenderPipeline {
62
+ readonly id: number;
63
+ readonly label: string;
64
+ readonly layout: Webgl2PipelineLayout;
65
+ program: WebGLProgram | null;
66
+ readonly blocks: ProgramBlock[];
67
+ readonly samplers: ProgramSampler[];
68
+ firstInstance: WebGLUniformLocation | null;
69
+ /** The first instance the program's uniform holds (-1: not set yet). */
70
+ firstInstanceValue: number;
71
+ /** Reads `@builtin(vertex_index)`: a nonzero baseVertex would shift it. */
72
+ readonly vertexIndex: boolean;
73
+ readonly vertexBuffers: VertexBufferLayout[];
74
+ readonly topology: number;
75
+ readonly cull: number;
76
+ readonly frontFace: number;
77
+ readonly depthTest: boolean;
78
+ readonly depthWrite: boolean;
79
+ readonly depthFunc: number;
80
+ readonly depthBias: number;
81
+ readonly depthSlope: number;
82
+ readonly stencil: boolean;
83
+ readonly stencilFront: [number, number, number, number];
84
+ readonly stencilBack: [number, number, number, number];
85
+ readonly stencilReadMask: number;
86
+ readonly stencilWriteMask: number;
87
+ readonly blends: BlendState[];
88
+ readonly masks: number[];
89
+ /** Every target blends and masks alike: one set of GL calls covers them all. */
90
+ readonly uniformTargets: boolean;
91
+ readonly targets: number;
92
+ readonly sampleCount: number;
93
+ private readonly device;
94
+ private readonly descriptor;
95
+ constructor(device: Webgl2Device, d: GPURenderPipelineDescriptor);
96
+ /** The WGSL and entry point of each stage, to translate. */
97
+ stages(): {
98
+ vertex: StageSource;
99
+ fragment: StageSource | undefined;
100
+ };
101
+ /** Compiles both stages' GLSL and starts linking the program. */
102
+ build(v: GlslTranslation, f: GlslTranslation | undefined): WebGLProgram;
103
+ /** The shaders and translations of a program still linking. */
104
+ private link;
105
+ /** Whether the driver has finished linking (KHR_parallel_shader_compile), or can't say. */
106
+ linked(): boolean;
107
+ /** After linking: the status, and the binding points and units from the reflection. */
108
+ finish(): void;
109
+ getBindGroupLayout(index: number): unknown;
110
+ }
111
+ /** The entry point a stage runs when the descriptor names none: the first of that stage. */
112
+ export declare function entryPointOf(code: string, stage: 'vertex' | 'fragment'): string;
113
+ export {};
@@ -0,0 +1,294 @@
1
+ import { unsupported } from "./errors.js";
2
+ import { GL } from "./gl.js";
3
+ import { COMPARE } from "./resources.js";
4
+ let nextId = 1;
5
+ class Webgl2ShaderModule {
6
+ id = nextId++;
7
+ code;
8
+ label;
9
+ constructor(d) {
10
+ this.code = d.code;
11
+ this.label = d.label ?? "";
12
+ }
13
+ /** Answered without translating: pipelines report what doesn't translate or link. */
14
+ getCompilationInfo() {
15
+ return Promise.resolve({ messages: [] });
16
+ }
17
+ }
18
+ const FACTOR = {
19
+ zero: GL.ZERO,
20
+ one: GL.ONE,
21
+ src: GL.SRC_COLOR,
22
+ "one-minus-src": GL.ONE_MINUS_SRC_COLOR,
23
+ "src-alpha": GL.SRC_ALPHA,
24
+ "one-minus-src-alpha": GL.ONE_MINUS_SRC_ALPHA,
25
+ dst: GL.DST_COLOR,
26
+ "one-minus-dst": GL.ONE_MINUS_DST_COLOR,
27
+ "dst-alpha": GL.DST_ALPHA,
28
+ "one-minus-dst-alpha": GL.ONE_MINUS_DST_ALPHA,
29
+ "src-alpha-saturated": GL.SRC_ALPHA_SATURATE,
30
+ constant: GL.CONSTANT_COLOR,
31
+ "one-minus-constant": GL.ONE_MINUS_CONSTANT_COLOR
32
+ };
33
+ const OPERATION = {
34
+ add: GL.FUNC_ADD,
35
+ subtract: GL.FUNC_SUBTRACT,
36
+ "reverse-subtract": GL.FUNC_REVERSE_SUBTRACT,
37
+ min: GL.MIN,
38
+ max: GL.MAX
39
+ };
40
+ const STENCIL_OP = {
41
+ keep: GL.KEEP,
42
+ zero: GL.ZERO,
43
+ replace: GL.REPLACE,
44
+ invert: GL.INVERT,
45
+ "increment-clamp": GL.INCR,
46
+ "decrement-clamp": GL.DECR,
47
+ "increment-wrap": GL.INCR_WRAP,
48
+ "decrement-wrap": GL.DECR_WRAP
49
+ };
50
+ const VERTEX_FORMAT = {
51
+ float32: [1, GL.FLOAT, false, false],
52
+ float32x2: [2, GL.FLOAT, false, false],
53
+ float32x3: [3, GL.FLOAT, false, false],
54
+ float32x4: [4, GL.FLOAT, false, false],
55
+ float16x2: [2, GL.HALF_FLOAT, false, false],
56
+ float16x4: [4, GL.HALF_FLOAT, false, false],
57
+ uint32: [1, GL.UNSIGNED_INT, false, true],
58
+ uint32x2: [2, GL.UNSIGNED_INT, false, true],
59
+ uint32x3: [3, GL.UNSIGNED_INT, false, true],
60
+ uint32x4: [4, GL.UNSIGNED_INT, false, true],
61
+ sint32: [1, GL.INT, false, true],
62
+ sint32x2: [2, GL.INT, false, true],
63
+ sint32x3: [3, GL.INT, false, true],
64
+ sint32x4: [4, GL.INT, false, true],
65
+ uint16x2: [2, GL.UNSIGNED_SHORT, false, true],
66
+ uint16x4: [4, GL.UNSIGNED_SHORT, false, true],
67
+ unorm16x2: [2, GL.UNSIGNED_SHORT, true, false],
68
+ unorm16x4: [4, GL.UNSIGNED_SHORT, true, false],
69
+ uint8x2: [2, GL.UNSIGNED_BYTE, false, true],
70
+ uint8x4: [4, GL.UNSIGNED_BYTE, false, true],
71
+ unorm8x2: [2, GL.UNSIGNED_BYTE, true, false],
72
+ unorm8x4: [4, GL.UNSIGNED_BYTE, true, false],
73
+ snorm8x4: [4, GL.BYTE, true, false]
74
+ };
75
+ const EMPTY_FRAGMENT = "#version 300 es\nprecision highp float;\nvoid main() {}\n";
76
+ class Webgl2RenderPipeline {
77
+ id = nextId++;
78
+ label;
79
+ layout;
80
+ program = null;
81
+ blocks = [];
82
+ samplers = [];
83
+ firstInstance = null;
84
+ /** The first instance the program's uniform holds (-1: not set yet). */
85
+ firstInstanceValue = -1;
86
+ /** Reads `@builtin(vertex_index)`: a nonzero baseVertex would shift it. */
87
+ vertexIndex;
88
+ vertexBuffers;
89
+ topology;
90
+ cull;
91
+ frontFace;
92
+ depthTest;
93
+ depthWrite;
94
+ depthFunc;
95
+ depthBias;
96
+ depthSlope;
97
+ stencil;
98
+ stencilFront;
99
+ stencilBack;
100
+ stencilReadMask;
101
+ stencilWriteMask;
102
+ blends;
103
+ masks;
104
+ /** Every target blends and masks alike: one set of GL calls covers them all. */
105
+ uniformTargets;
106
+ targets;
107
+ sampleCount;
108
+ device;
109
+ descriptor;
110
+ constructor(device, d) {
111
+ this.device = device;
112
+ this.descriptor = d;
113
+ this.label = d.label ?? "";
114
+ if (d.layout === "auto") throw unsupported(`make "${this.label}" with layout 'auto'`);
115
+ this.layout = d.layout;
116
+ const vs = d.vertex.module;
117
+ this.vertexIndex = /@builtin\(\s*vertex_index\s*\)/.test(vs.code);
118
+ this.vertexBuffers = (d.vertex.buffers ?? []).map((b) => ({
119
+ stride: b?.arrayStride ?? 0,
120
+ instanced: b?.stepMode === "instance",
121
+ attributes: (b ? [...b.attributes] : []).map((a) => {
122
+ const f = VERTEX_FORMAT[a.format];
123
+ if (!f) throw unsupported(`read the vertex format ${a.format}`);
124
+ return {
125
+ location: a.shaderLocation,
126
+ size: f[0],
127
+ type: f[1],
128
+ normalized: f[2],
129
+ integer: f[3],
130
+ offset: a.offset
131
+ };
132
+ })
133
+ }));
134
+ const p = d.primitive ?? {};
135
+ const topology = p.topology ?? "triangle-list";
136
+ this.topology = topology === "triangle-list" ? GL.TRIANGLES : topology === "triangle-strip" ? GL.TRIANGLE_STRIP : topology === "line-list" ? GL.LINES : topology === "line-strip" ? GL.LINE_STRIP : GL.POINTS;
137
+ this.cull = p.cullMode === "front" ? GL.FRONT : p.cullMode === "back" ? GL.BACK : 0;
138
+ this.frontFace = (p.frontFace ?? "ccw") === "ccw" ? GL.CW : GL.CCW;
139
+ const ds = d.depthStencil;
140
+ this.depthTest = ds !== void 0 && ((ds.depthCompare ?? "always") !== "always" || ds.depthWriteEnabled === true);
141
+ this.depthWrite = ds?.depthWriteEnabled === true;
142
+ this.depthFunc = COMPARE[ds?.depthCompare ?? "always"];
143
+ this.depthBias = ds?.depthBias ?? 0;
144
+ this.depthSlope = ds?.depthBiasSlopeScale ?? 0;
145
+ const face = (s) => [
146
+ COMPARE[s?.compare ?? "always"],
147
+ STENCIL_OP[s?.failOp ?? "keep"],
148
+ STENCIL_OP[s?.depthFailOp ?? "keep"],
149
+ STENCIL_OP[s?.passOp ?? "keep"]
150
+ ];
151
+ this.stencilFront = face(ds?.stencilFront);
152
+ this.stencilBack = face(ds?.stencilBack);
153
+ this.stencilReadMask = ds?.stencilReadMask ?? 255;
154
+ this.stencilWriteMask = ds?.stencilWriteMask ?? 255;
155
+ this.stencil = ds !== void 0 && (ds.format === "stencil8" || ds.format.includes("stencil")) && (ds.stencilFront !== void 0 || ds.stencilBack !== void 0);
156
+ if (d.multisample?.alphaToCoverageEnabled)
157
+ throw unsupported(`use alpha to coverage ("${this.label}")`);
158
+ this.sampleCount = d.multisample?.count ?? 1;
159
+ const targets = d.fragment ? [...d.fragment.targets] : [];
160
+ this.targets = targets.length;
161
+ this.blends = targets.map((t) => {
162
+ const b = t?.blend;
163
+ return {
164
+ enabled: b !== void 0,
165
+ colorOp: OPERATION[b?.color.operation ?? "add"],
166
+ colorSrc: FACTOR[b?.color.srcFactor ?? "one"],
167
+ colorDst: FACTOR[b?.color.dstFactor ?? "zero"],
168
+ alphaOp: OPERATION[b?.alpha.operation ?? "add"],
169
+ alphaSrc: FACTOR[b?.alpha.srcFactor ?? "one"],
170
+ alphaDst: FACTOR[b?.alpha.dstFactor ?? "zero"]
171
+ };
172
+ });
173
+ this.masks = targets.map((t) => t?.writeMask ?? 15);
174
+ const same = (a, b) => a.enabled === b.enabled && a.colorOp === b.colorOp && a.colorSrc === b.colorSrc && a.colorDst === b.colorDst && a.alphaOp === b.alphaOp && a.alphaSrc === b.alphaSrc && a.alphaDst === b.alphaDst;
175
+ this.uniformTargets = this.blends.every(
176
+ (b, i) => same(b, this.blends[0]) && this.masks[i] === this.masks[0]
177
+ );
178
+ }
179
+ /** The WGSL and entry point of each stage, to translate. */
180
+ stages() {
181
+ const d = this.descriptor;
182
+ const vs = d.vertex.module;
183
+ const fs = d.fragment?.module;
184
+ return {
185
+ vertex: { code: vs.code, entry: d.vertex.entryPoint ?? entryPointOf(vs.code, "vertex") },
186
+ fragment: fs && d.fragment ? { code: fs.code, entry: d.fragment.entryPoint ?? entryPointOf(fs.code, "fragment") } : void 0
187
+ };
188
+ }
189
+ /** Compiles both stages' GLSL and starts linking the program. */
190
+ build(v, f) {
191
+ const gl = this.device.gl;
192
+ const program = gl.createProgram();
193
+ const shader = (type, source) => {
194
+ const s = gl.createShader(type);
195
+ gl.shaderSource(s, source);
196
+ gl.compileShader(s);
197
+ gl.attachShader(program, s);
198
+ return s;
199
+ };
200
+ const vsShader = shader(GL.VERTEX_SHADER, v.glsl);
201
+ const fsShader = shader(GL.FRAGMENT_SHADER, f?.glsl ?? EMPTY_FRAGMENT);
202
+ gl.linkProgram(program);
203
+ this.program = program;
204
+ this.link = { vs: vsShader, fs: fsShader, v, f };
205
+ return program;
206
+ }
207
+ /** The shaders and translations of a program still linking. */
208
+ link;
209
+ /** Whether the driver has finished linking (KHR_parallel_shader_compile), or can't say. */
210
+ linked() {
211
+ if (!this.device.caps.parallelCompile || !this.program) return true;
212
+ return this.device.gl.getProgramParameter(this.program, GL.COMPLETION_STATUS_KHR);
213
+ }
214
+ /** After linking: the status, and the binding points and units from the reflection. */
215
+ finish() {
216
+ const gl = this.device.gl;
217
+ const program = this.program;
218
+ const link = this.link;
219
+ this.link = void 0;
220
+ if (!gl.getProgramParameter(program, GL.LINK_STATUS)) {
221
+ const log = [
222
+ gl.getShaderInfoLog(link.vs),
223
+ gl.getShaderInfoLog(link.fs),
224
+ gl.getProgramInfoLog(program)
225
+ ].filter(Boolean).join("\n");
226
+ gl.deleteProgram(program);
227
+ this.program = null;
228
+ throw new Error(`GLSL for "${this.label}" didn't link: ${log}`);
229
+ }
230
+ gl.deleteShader(link.vs);
231
+ gl.deleteShader(link.fs);
232
+ const points = /* @__PURE__ */ new Map();
233
+ for (const u of [...link.v.uniforms, ...link.f?.uniforms ?? []]) {
234
+ const index = gl.getUniformBlockIndex(program, u.name);
235
+ if (index === 4294967295) continue;
236
+ const key = `${u.group}:${u.binding}`;
237
+ const size = gl.getActiveUniformBlockParameter(
238
+ program,
239
+ index,
240
+ GL.UNIFORM_BLOCK_DATA_SIZE
241
+ );
242
+ let point = points.get(key);
243
+ if (point === void 0) {
244
+ point = points.size;
245
+ points.set(key, point);
246
+ this.blocks.push({ group: u.group, binding: u.binding, point, size });
247
+ } else {
248
+ const block = this.blocks[point];
249
+ block.size = Math.max(block.size, size);
250
+ }
251
+ gl.uniformBlockBinding(program, index, point);
252
+ }
253
+ gl.useProgram(program);
254
+ const units = /* @__PURE__ */ new Map();
255
+ for (const t of [...link.v.textures, ...link.f?.textures ?? []]) {
256
+ const location = gl.getUniformLocation(program, t.name);
257
+ if (!location) continue;
258
+ const key = `${t.group}:${t.binding}:${t.sampler ? t.sampler.join(":") : "-"}`;
259
+ let unit = units.get(key);
260
+ if (unit === void 0) {
261
+ unit = units.size;
262
+ units.set(key, unit);
263
+ this.samplers.push({
264
+ unit,
265
+ group: t.group,
266
+ binding: t.binding,
267
+ samplerGroup: t.sampler ? t.sampler[0] : -1,
268
+ samplerBinding: t.sampler ? t.sampler[1] : -1
269
+ });
270
+ }
271
+ gl.uniform1i(location, unit);
272
+ }
273
+ if (units.size > this.device.caps.combinedUnits - 1) {
274
+ throw new Error(
275
+ `"${this.label}" samples ${units.size} textures; this device binds ${this.device.caps.combinedUnits - 1}`
276
+ );
277
+ }
278
+ this.firstInstance = link.v.firstInstance ? gl.getUniformLocation(program, "naga_vs_first_instance") : null;
279
+ this.device.state.forgetProgram();
280
+ }
281
+ getBindGroupLayout(index) {
282
+ return this.layout.groups[index];
283
+ }
284
+ }
285
+ function entryPointOf(code, stage) {
286
+ const m = new RegExp(`@${stage}\\s+fn\\s+([A-Za-z_]\\w*)`).exec(code);
287
+ if (!m) throw new Error(`No @${stage} entry point`);
288
+ return m[1];
289
+ }
290
+ export {
291
+ Webgl2RenderPipeline,
292
+ Webgl2ShaderModule,
293
+ entryPointOf
294
+ };
@@ -0,0 +1,18 @@
1
+ import { Webgl2Texture } from './resources.js';
2
+ /** The canvas context for `canvas`: one per canvas, as `getContext` gives. */
3
+ export declare function canvasContext(canvas: HTMLCanvasElement | OffscreenCanvas): Webgl2CanvasContext;
4
+ export declare class Webgl2CanvasContext {
5
+ readonly canvas: HTMLCanvasElement | OffscreenCanvas;
6
+ private device;
7
+ private configuration;
8
+ private texture;
9
+ private target;
10
+ private readonly read;
11
+ constructor(canvas: HTMLCanvasElement | OffscreenCanvas);
12
+ configure(configuration: GPUCanvasConfiguration): void;
13
+ getConfiguration(): GPUCanvasConfiguration | null;
14
+ unconfigure(): void;
15
+ getCurrentTexture(): Webgl2Texture;
16
+ /** @internal Shows `texture` on the canvas. */
17
+ present(texture: Webgl2Texture): void;
18
+ }
@@ -0,0 +1,108 @@
1
+ import { TextureUsage } from "./constants.js";
2
+ import { unsupported } from "./errors.js";
3
+ import { Attachments } from "./fbo.js";
4
+ import { GL } from "./gl.js";
5
+ import { Webgl2Texture } from "./resources.js";
6
+ const contexts = /* @__PURE__ */ new WeakMap();
7
+ function canvasContext(canvas) {
8
+ let context = contexts.get(canvas);
9
+ if (!context) {
10
+ context = new Webgl2CanvasContext(canvas);
11
+ contexts.set(canvas, context);
12
+ }
13
+ return context;
14
+ }
15
+ class Webgl2CanvasContext {
16
+ canvas;
17
+ device;
18
+ configuration;
19
+ texture;
20
+ target;
21
+ read = new Attachments();
22
+ constructor(canvas) {
23
+ this.canvas = canvas;
24
+ }
25
+ configure(configuration) {
26
+ if (configuration.format !== "rgba8unorm") {
27
+ throw unsupported(
28
+ `present ${configuration.format}`,
29
+ "Configure the canvas with getPreferredCanvasFormat() ('rgba8unorm' on WebGL2)."
30
+ );
31
+ }
32
+ this.texture?.destroy();
33
+ this.texture = void 0;
34
+ this.device = configuration.device;
35
+ this.configuration = configuration;
36
+ }
37
+ getConfiguration() {
38
+ return this.configuration ?? null;
39
+ }
40
+ unconfigure() {
41
+ this.texture?.destroy();
42
+ this.texture = void 0;
43
+ this.device = void 0;
44
+ this.configuration = void 0;
45
+ }
46
+ getCurrentTexture() {
47
+ const device = this.device;
48
+ const configuration = this.configuration;
49
+ if (!device || !configuration)
50
+ throw new Error("getCurrentTexture: the canvas context is not configured");
51
+ const width = Math.max(1, this.canvas.width);
52
+ const height = Math.max(1, this.canvas.height);
53
+ let texture = this.texture;
54
+ if (!texture || texture.destroyed || texture.width !== width || texture.height !== height) {
55
+ texture?.destroy();
56
+ texture = new Webgl2Texture(device, {
57
+ label: "canvas",
58
+ size: [width, height],
59
+ format: "rgba8unorm",
60
+ usage: (configuration.usage ?? TextureUsage.RENDER_ATTACHMENT) | TextureUsage.RENDER_ATTACHMENT
61
+ });
62
+ texture.surface = this;
63
+ this.texture = texture;
64
+ }
65
+ return texture;
66
+ }
67
+ /** @internal Shows `texture` on the canvas. */
68
+ present(texture) {
69
+ const device = this.device;
70
+ if (!device || device.isLost || texture !== this.texture) return;
71
+ const gl = device.gl;
72
+ const glCanvas = device.canvas;
73
+ if (!glCanvas) return;
74
+ const w = texture.width;
75
+ const h = texture.height;
76
+ const own = glCanvas === this.canvas;
77
+ if (!own) {
78
+ if (glCanvas.width !== w) glCanvas.width = w;
79
+ if (glCanvas.height !== h) glCanvas.height = h;
80
+ }
81
+ this.read.reset();
82
+ this.read.color(0, texture, 0, 0);
83
+ gl.bindFramebuffer(GL.READ_FRAMEBUFFER, device.fbos.get(this.read));
84
+ gl.bindFramebuffer(GL.DRAW_FRAMEBUFFER, null);
85
+ device.state.enable(GL.SCISSOR_TEST, false);
86
+ gl.blitFramebuffer(0, 0, w, h, 0, h, w, 0, GL.COLOR_BUFFER_BIT, GL.NEAREST);
87
+ const opaque = this.configuration?.alphaMode !== "premultiplied";
88
+ if (opaque) {
89
+ gl.bindFramebuffer(GL.FRAMEBUFFER, null);
90
+ device.state.prepareClear();
91
+ gl.colorMask(false, false, false, true);
92
+ gl.clearColor(0, 0, 0, 1);
93
+ gl.clear(GL.COLOR_BUFFER_BIT);
94
+ gl.colorMask(true, true, true, true);
95
+ }
96
+ if (own) return;
97
+ if (this.target === void 0) {
98
+ this.target = this.canvas.getContext("2d", { alpha: !opaque });
99
+ }
100
+ if (!this.target) return;
101
+ this.target.globalCompositeOperation = "copy";
102
+ this.target.drawImage(glCanvas, 0, 0);
103
+ }
104
+ }
105
+ export {
106
+ Webgl2CanvasContext,
107
+ canvasContext
108
+ };
@@ -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
+ };
@@ -0,0 +1,13 @@
1
+ import type { Webgl2CommandBuffer } from './commands.js';
2
+ import type { Webgl2Device } from './device.js';
3
+ import type { Webgl2Buffer } from './resources.js';
4
+ export declare class Webgl2Queue {
5
+ label: string;
6
+ private readonly device;
7
+ constructor(device: Webgl2Device);
8
+ submit(buffers: Iterable<Webgl2CommandBuffer>): void;
9
+ onSubmittedWorkDone(): Promise<undefined>;
10
+ writeBuffer(buffer: Webgl2Buffer, offset: number, data: BufferSource, dataOffset?: number, size?: number): void;
11
+ writeTexture(destination: GPUTexelCopyTextureInfo, data: BufferSource, layout: GPUTexelCopyBufferLayout, size: GPUExtent3D): void;
12
+ copyExternalImageToTexture(source: GPUCopyExternalImageSourceInfo, destination: GPUCopyExternalImageDestInfo, size: GPUExtent3D): void;
13
+ }