@swf2es/context3d 0.1.19 → 0.1.21

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 (56) hide show
  1. package/README.md +120 -27
  2. package/dist/agal.d.ts +11 -2
  3. package/dist/agal.d.ts.map +1 -1
  4. package/dist/agal.js +318 -47
  5. package/dist/agal.js.map +1 -1
  6. package/dist/backend.d.ts +50 -3
  7. package/dist/backend.d.ts.map +1 -1
  8. package/dist/backend.js +74 -2
  9. package/dist/backend.js.map +1 -1
  10. package/dist/errors.d.ts +1 -1
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/webgl/back-buffer.d.ts +6 -0
  18. package/dist/webgl/back-buffer.d.ts.map +1 -0
  19. package/dist/webgl/back-buffer.js +197 -0
  20. package/dist/webgl/back-buffer.js.map +1 -0
  21. package/dist/webgl/buffers.d.ts +2 -2
  22. package/dist/webgl/buffers.d.ts.map +1 -1
  23. package/dist/webgl/buffers.js +105 -2
  24. package/dist/webgl/buffers.js.map +1 -1
  25. package/dist/webgl/context.d.ts +29 -48
  26. package/dist/webgl/context.d.ts.map +1 -1
  27. package/dist/webgl/context.js +227 -364
  28. package/dist/webgl/context.js.map +1 -1
  29. package/dist/webgl/programs.d.ts +4 -1
  30. package/dist/webgl/programs.d.ts.map +1 -1
  31. package/dist/webgl/programs.js +7 -6
  32. package/dist/webgl/programs.js.map +1 -1
  33. package/dist/webgl/readback.d.ts +3 -0
  34. package/dist/webgl/readback.d.ts.map +1 -0
  35. package/dist/webgl/readback.js +40 -0
  36. package/dist/webgl/readback.js.map +1 -0
  37. package/dist/webgl/targets.d.ts +3 -1
  38. package/dist/webgl/targets.d.ts.map +1 -1
  39. package/dist/webgl/targets.js +5 -3
  40. package/dist/webgl/targets.js.map +1 -1
  41. package/dist/webgl/textures.d.ts +9 -1
  42. package/dist/webgl/textures.d.ts.map +1 -1
  43. package/dist/webgl/textures.js +161 -7
  44. package/dist/webgl/textures.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/agal.ts +375 -51
  47. package/src/backend.ts +113 -3
  48. package/src/errors.ts +27 -1
  49. package/src/index.ts +12 -1
  50. package/src/webgl/back-buffer.ts +287 -0
  51. package/src/webgl/buffers.ts +114 -2
  52. package/src/webgl/context.ts +291 -489
  53. package/src/webgl/programs.ts +10 -5
  54. package/src/webgl/readback.ts +54 -0
  55. package/src/webgl/targets.ts +4 -2
  56. package/src/webgl/textures.ts +183 -9
@@ -1,14 +1,19 @@
1
- import type { ProgramType } from "../agal.js";
1
+ import { INDIRECT_BANK, type ProgramType } from "../agal.js";
2
2
  import {
3
3
  type BlendFactor,
4
4
  type Buffer3D,
5
5
  type CompareMode,
6
6
  type Context3D,
7
7
  type CullFace,
8
- MOST_BACK_BUFFER,
8
+ compressedFormat,
9
+ FRAGMENT_CONSTANTS,
10
+ MOST_CUBE,
11
+ type Profile,
9
12
  type Program3D,
13
+ profileLimits,
10
14
  SAMPLER_FILTERS,
11
15
  SAMPLER_MIPS,
16
+ SAMPLER_SLOTS,
12
17
  SAMPLER_WRAPS,
13
18
  type SamplerFilter,
14
19
  type SamplerMip,
@@ -16,10 +21,13 @@ import {
16
21
  ScissorBoundsError,
17
22
  type StencilAction,
18
23
  screenPixels,
24
+ TEXEL_BYTES,
19
25
  type Texture3D,
20
26
  TextureBindingError,
21
27
  type TextureFormat,
28
+ VERTEX_CONSTANTS,
22
29
  VERTEX_FORMATS,
30
+ VERTEX_SLOTS,
23
31
  type VertexFormat,
24
32
  VertexOffsetError,
25
33
  } from "../backend.js";
@@ -27,12 +35,8 @@ import { GlState } from "../device/state.js";
27
35
  import { blockBytes, type DxtEncoder } from "../dxt.js";
28
36
  import { Context3DError } from "../errors.js";
29
37
 
30
- import {
31
- type BufferState,
32
- uploadBuffer,
33
- uploadBufferBytes,
34
- type VertexBinding,
35
- } from "./buffers.js";
38
+ import { configureBackBuffer, presentBackBuffer, unconfigureBackBuffer } from "./back-buffer.js";
39
+ import { allocateBuffer, type BufferState, type VertexBinding } from "./buffers.js";
36
40
  import { resourceLimits, type WebGLContext3DLimits } from "./limits.js";
37
41
  import {
38
42
  type CopyProgram,
@@ -40,6 +44,7 @@ import {
40
44
  type ProgramState,
41
45
  uploadProgram,
42
46
  } from "./programs.js";
47
+ import { readBackBuffer } from "./readback.js";
43
48
  import {
44
49
  BLEND_FUNCTIONS,
45
50
  DEPTH_FUNCTIONS,
@@ -56,6 +61,7 @@ import {
56
61
  targetFramebuffer,
57
62
  } from "./targets.js";
58
63
  import {
64
+ storageFormat,
59
65
  type TextureState,
60
66
  uploadCompressedLevel,
61
67
  uploadTexture,
@@ -68,8 +74,26 @@ const SAMPLER_FORMATS: Record<TextureFormat, number> = {
68
74
  bgra: 0,
69
75
  compressed: 1,
70
76
  compressedAlpha: 2,
77
+ bgrPacked565: 0,
78
+ bgraPacked4444: 0,
79
+ rgbaHalfFloat: 0,
71
80
  };
72
81
 
82
+ /**
83
+ * A clear colour's channel as Direct3D's 8-bit clear colour holds it, which AIR fills by
84
+ * truncation (0.5 clears to 127): within 0 to 1 and cut to its byte, NaN kept.
85
+ */
86
+ function clearChannel(value: number): number {
87
+ if (!(value > 0)) {
88
+ return value < 0 ? 0 : value;
89
+ }
90
+
91
+ return value >= 1 ? 1 : Math.floor(value * 255 + 1e-3) / 255;
92
+ }
93
+
94
+ /** AGAL's wraps (clamp, repeat, clamp_u_repeat_v, repeat_u_clamp_v) as SAMPLER_WRAPS indices. */
95
+ const WRAPS = [0, 3, 2, 1];
96
+
73
97
  interface Constants {
74
98
  data: Float32Array;
75
99
  bits: Uint32Array;
@@ -90,25 +114,38 @@ function constants(registers: number): Constants {
90
114
  };
91
115
  }
92
116
 
93
- /** An owned WebGL2 context, deliberately independent of the 2D renderer. */
117
+ /**
118
+ * An owned WebGL2 context, deliberately independent of the 2D renderer. Its members that are
119
+ * neither private nor Context3D's are for back-buffer.ts, buffers.ts and readback.ts, which
120
+ * hold parts of it as functions over it.
121
+ */
94
122
  export class WebGLContext3D implements Context3D {
95
123
  readonly gl: WebGL2RenderingContext;
96
- private readonly drawState: GlState;
124
+ /** @internal */
125
+ readonly drawState: GlState;
97
126
  width = 0;
98
127
  height = 0;
99
128
  /**
100
129
  * The back buffer's storage: its size, or less where the GPU allows less. The SWF still
101
130
  * sees the size it asked for, drawn at this one and stretched to it when presented.
102
131
  */
103
- private drawnWidth = 0;
104
- private drawnHeight = 0;
105
- private gpuMost: Int32Array | null = null;
132
+ /** @internal */
133
+ drawnWidth = 0;
134
+ /** @internal */
135
+ drawnHeight = 0;
136
+ /** @internal */
137
+ gpuMost: Int32Array | null = null;
106
138
  disposed = false;
107
139
  samplerFormats = true;
108
- private readonly buffers = new Map<Buffer3D, BufferState>();
109
- private vertexBuffers = 0;
110
- private bufferBytes = 0;
111
- private vertexBufferBytes = 0;
140
+ profile: Profile = "baseline";
141
+ /** @internal */
142
+ readonly buffers = new Map<Buffer3D, BufferState>();
143
+ /** @internal */
144
+ vertexBuffers = 0;
145
+ /** @internal */
146
+ bufferBytes = 0;
147
+ /** @internal */
148
+ vertexBufferBytes = 0;
112
149
  private readonly programs = new Map<Program3D, ProgramState>();
113
150
  private readonly textures = new Map<Texture3D, TextureState>();
114
151
  private readonly textureCounts = new Float64Array(3);
@@ -120,10 +157,18 @@ export class WebGLContext3D implements Context3D {
120
157
  private copy2D: CopyProgram | null = null;
121
158
  private copyCube: CopyProgram | null = null;
122
159
  private readonly targetSamples = new Int8Array(10).fill(-1);
123
- private renderTexture: Texture3D | null = null;
160
+ /** @internal */
161
+ renderTexture: Texture3D | null = null;
124
162
  private renderDepth = false;
125
163
  private renderFace = 0;
126
- private readonly textureSlots: (Texture3D | null)[] = new Array(8).fill(null);
164
+ /** Colour outputs 1 to 3, drawn into with renderTexture, and their cube faces. */
165
+ private readonly outputs: (Texture3D | null)[] = [null, null, null];
166
+ private readonly outputFaces = new Uint8Array(3);
167
+ private outputsChanged = false;
168
+ /** Whether any of outputs is bound, so that a draw alone into its target looks at none. */
169
+ private outputsBound = false;
170
+ private floatTargets: boolean | undefined;
171
+ private readonly textureSlots: (Texture3D | null)[] = new Array(SAMPLER_SLOTS).fill(null);
127
172
  private textureMask = 0;
128
173
  private readonly samplers: (WebGLSampler | null)[] = new Array(96).fill(null);
129
174
  // Two wrap bits, three filter bits and two mip bits fit each slot in one byte.
@@ -131,20 +176,31 @@ export class WebGLContext3D implements Context3D {
131
176
  private anisotropy: EXT_texture_filter_anisotropic | null | undefined;
132
177
  private s3tc: WEBGL_compressed_texture_s3tc | null | undefined;
133
178
  private maxAnisotropy = 1;
134
- private readonly vertices: (VertexBinding | null)[] = new Array(8).fill(null);
179
+ /** @internal */
180
+ readonly vertices: (VertexBinding | null)[] = new Array(VERTEX_SLOTS).fill(null);
135
181
  private enabledAttributes = 0;
136
- private indexBuffer: WebGLBuffer | null = null;
137
- private readonly texturesApplied = new Uint8Array(8);
182
+ /** @internal */
183
+ indexBuffer: WebGLBuffer | null = null;
184
+ /** @internal */
185
+ readonly texturesApplied = new Uint8Array(SAMPLER_SLOTS);
138
186
  private selected: Program3D | null = null;
139
- private framebuffer: WebGLFramebuffer | null = null;
140
- private texture: WebGLTexture | null = null;
141
- private resolveFramebuffer: WebGLFramebuffer | null = null;
142
- private colorBuffer: WebGLRenderbuffer | null = null;
143
- private resolveDirty = false;
144
- private depthBuffer: WebGLRenderbuffer | null = null;
145
- private antiAlias = 0;
187
+ /** @internal */
188
+ framebuffer: WebGLFramebuffer | null = null;
189
+ /** @internal */
190
+ texture: WebGLTexture | null = null;
191
+ /** @internal */
192
+ resolveFramebuffer: WebGLFramebuffer | null = null;
193
+ /** @internal */
194
+ colorBuffer: WebGLRenderbuffer | null = null;
195
+ /** @internal */
196
+ resolveDirty = false;
197
+ /** @internal */
198
+ depthBuffer: WebGLRenderbuffer | null = null;
199
+ /** @internal */
200
+ antiAlias = 0;
146
201
  /** The back buffer's pixels to a CSS pixel and to a scissor rectangle's unit. */
147
- private scale = 1;
202
+ /** @internal */
203
+ scale = 1;
148
204
  private depthWrite = true;
149
205
  private depthCompare: CompareMode = "less";
150
206
  private blendEnabled = false;
@@ -158,18 +214,23 @@ export class WebGLContext3D implements Context3D {
158
214
  private stencilReference = 0;
159
215
  private stencilRead = 255;
160
216
  private stencilWrite = 255;
161
- private scissor = false;
217
+ /** @internal */
218
+ scissor = false;
162
219
  private scissorLeft = 0;
163
220
  private scissorTop = 0;
164
221
  private scissorRight = 0;
165
222
  private scissorBottom = 0;
166
223
  private scissorOutside = false;
167
- private readonly vertexConstants = constants(128);
168
- private readonly fragmentConstants = constants(28);
169
- private pixels = new Uint8Array(0);
170
- private topDown = new Uint8Array(0);
171
- private readonly vao: WebGLVertexArrayObject;
172
- private readonly limits: Readonly<WebGLContext3DLimits>;
224
+ private readonly vertexConstants = constants(VERTEX_CONSTANTS);
225
+ private readonly fragmentConstants = constants(FRAGMENT_CONSTANTS);
226
+ /** @internal */
227
+ pixels = new Uint8Array(0);
228
+ /** @internal */
229
+ topDown = new Uint8Array(0);
230
+ /** @internal */
231
+ readonly vao: WebGLVertexArrayObject;
232
+ /** @internal */
233
+ readonly limits: Readonly<WebGLContext3DLimits>;
173
234
  private dxtEncoder: DxtEncoder | null = null;
174
235
 
175
236
  /** The host's whole-level encoder; a refusal retains the TypeScript path. */
@@ -211,13 +272,15 @@ export class WebGLContext3D implements Context3D {
211
272
  gl.disable(gl.DITHER);
212
273
  }
213
274
 
214
- private alive(): void {
275
+ /** @internal */
276
+ alive(): void {
215
277
  if (this.disposed || this.gl.isContextLost()) {
216
278
  throw new Context3DError("disposed", "Context3D experiment: context disposed or lost");
217
279
  }
218
280
  }
219
281
 
220
- private target(back = false, discardColor = false): void {
282
+ /** @internal */
283
+ target(back = false, discardColor = false): void {
221
284
  this.alive();
222
285
  if (!this.framebuffer) {
223
286
  throw new Context3DError(
@@ -236,6 +299,9 @@ export class WebGLContext3D implements Context3D {
236
299
  }
237
300
  }
238
301
  this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
302
+ if (this.outputsChanged || state?.outputs) {
303
+ this.attachOutputs(state);
304
+ }
239
305
  this.drawState.viewport(
240
306
  0,
241
307
  0,
@@ -245,220 +311,18 @@ export class WebGLContext3D implements Context3D {
245
311
  }
246
312
 
247
313
  /** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
248
- private bindFramebuffer(target: number, framebuffer: WebGLFramebuffer | null): void {
314
+ /** @internal */
315
+ bindFramebuffer(target: number, framebuffer: WebGLFramebuffer | null): void {
249
316
  this.drawState.forget();
250
317
  this.gl.bindFramebuffer(target, framebuffer);
251
318
  }
252
319
 
253
320
  configure(width: number, height: number, depth = false, antiAlias = 0, scale = 1): void {
254
- this.alive();
255
- range(32, width - 32, MOST_BACK_BUFFER);
256
- range(32, height - 32, MOST_BACK_BUFFER);
257
- if (this.renderTexture) {
258
- throw new Context3DError(
259
- "operation",
260
- "Context3D experiment: resize while rendering to texture",
261
- );
262
- }
263
-
264
- // AIR preserves color, depth and bindings when the request is unchanged.
265
- // Shadow passes can issue this every frame when returning to the back buffer.
266
- if (
267
- this.framebuffer &&
268
- width === this.width &&
269
- height === this.height &&
270
- depth === (this.depthBuffer !== null) &&
271
- antiAlias === this.antiAlias
272
- ) {
273
- if (scale !== this.scale) {
274
- this.scale = scale;
275
- this.showAt();
276
- this.applyScissor();
277
- }
278
-
279
- return;
280
- }
281
-
282
- const samples = selectSamples(this.gl, antiAlias, depth);
283
- const gl = this.gl;
284
- const most = this.storageLimit();
285
- const drawnWidth = Math.min(width, most[0]);
286
- const drawnHeight = Math.min(height, most[1]);
287
- const texture = gl.createTexture();
288
- const framebuffer = gl.createFramebuffer();
289
- const depthBuffer = depth ? gl.createRenderbuffer() : null;
290
- const resolveFramebuffer = samples ? gl.createFramebuffer() : null;
291
- const colorBuffer = samples ? gl.createRenderbuffer() : null;
292
- try {
293
- if (
294
- !texture ||
295
- !framebuffer ||
296
- (depth && !depthBuffer) ||
297
- (samples && (!resolveFramebuffer || !colorBuffer))
298
- ) {
299
- throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
300
- }
301
-
302
- this.texturesApplied[0] = 0;
303
- gl.activeTexture(gl.TEXTURE0);
304
- gl.bindTexture(gl.TEXTURE_2D, texture);
305
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
306
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
307
- gl.texImage2D(
308
- gl.TEXTURE_2D,
309
- 0,
310
- gl.RGBA8,
311
- drawnWidth,
312
- drawnHeight,
313
- 0,
314
- gl.RGBA,
315
- gl.UNSIGNED_BYTE,
316
- null,
317
- );
318
- this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
319
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
320
- if (resolveFramebuffer) {
321
- if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
322
- throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
323
- }
324
-
325
- this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
326
- gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
327
- gl.renderbufferStorageMultisample(
328
- gl.RENDERBUFFER,
329
- samples,
330
- gl.RGBA8,
331
- drawnWidth,
332
- drawnHeight,
333
- );
334
- gl.framebufferRenderbuffer(
335
- gl.FRAMEBUFFER,
336
- gl.COLOR_ATTACHMENT0,
337
- gl.RENDERBUFFER,
338
- colorBuffer,
339
- );
340
- }
341
- if (depthBuffer) {
342
- gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
343
- if (samples) {
344
- gl.renderbufferStorageMultisample(
345
- gl.RENDERBUFFER,
346
- samples,
347
- gl.DEPTH24_STENCIL8,
348
- drawnWidth,
349
- drawnHeight,
350
- );
351
- } else {
352
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
353
- }
354
- gl.framebufferRenderbuffer(
355
- gl.FRAMEBUFFER,
356
- gl.DEPTH_STENCIL_ATTACHMENT,
357
- gl.RENDERBUFFER,
358
- depthBuffer,
359
- );
360
- }
361
- gl.bindRenderbuffer(gl.RENDERBUFFER, null);
362
- if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
363
- throw new Context3DError("operation", "Context3D experiment: incomplete back buffer");
364
- }
365
- } catch (error) {
366
- gl.deleteTexture(texture);
367
- gl.deleteFramebuffer(framebuffer);
368
- gl.deleteFramebuffer(resolveFramebuffer);
369
- gl.deleteRenderbuffer(depthBuffer);
370
- gl.deleteRenderbuffer(colorBuffer);
371
- gl.bindRenderbuffer(gl.RENDERBUFFER, null);
372
- this.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
373
- throw error;
374
- }
375
-
376
- gl.deleteTexture(this.texture);
377
- gl.deleteFramebuffer(this.framebuffer);
378
- gl.deleteFramebuffer(this.resolveFramebuffer);
379
- gl.deleteRenderbuffer(this.depthBuffer);
380
- gl.deleteRenderbuffer(this.colorBuffer);
381
- this.texture = texture;
382
- this.framebuffer = framebuffer;
383
- this.resolveFramebuffer = resolveFramebuffer;
384
- this.colorBuffer = colorBuffer;
385
- this.depthBuffer = depthBuffer;
386
- this.antiAlias = antiAlias;
387
- this.width = width;
388
- this.height = height;
389
- this.drawnWidth = drawnWidth;
390
- this.drawnHeight = drawnHeight;
391
- this.scale = scale;
392
- gl.canvas.width = drawnWidth;
393
- gl.canvas.height = drawnHeight;
394
- this.showAt();
395
- // Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
396
- this.pixels = new Uint8Array(0);
397
- this.topDown = new Uint8Array(0);
398
- this.applyScissor();
399
- this.clear(0, 0, 0, 1);
400
- }
401
-
402
- /**
403
- * Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
404
- * divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
405
- */
406
- private showAt(): void {
407
- const style = (this.gl.canvas as Partial<HTMLCanvasElement>).style;
408
- if (!style) {
409
- return;
410
- }
411
-
412
- const plain = this.scale === 1;
413
- style.width = plain && this.drawnWidth === this.width ? "" : `${this.width / this.scale}px`;
414
- style.height = plain && this.drawnHeight === this.height ? "" : `${this.height / this.scale}px`;
415
- }
416
-
417
- /** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
418
- private storageLimit(): Int32Array {
419
- if (!this.gpuMost) {
420
- const gl = this.gl;
421
- const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS) as Int32Array;
422
- const side = Math.min(
423
- gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
424
- gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
425
- );
426
- this.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
427
- }
428
-
429
- return this.gpuMost;
321
+ configureBackBuffer(this, width, height, depth, antiAlias, scale);
430
322
  }
431
323
 
432
324
  unconfigure(): void {
433
- this.alive();
434
- if (this.renderTexture) {
435
- throw new Context3DError(
436
- "operation",
437
- "Context3D experiment: resize while rendering to texture",
438
- );
439
- }
440
-
441
- const gl = this.gl;
442
- gl.deleteTexture(this.texture);
443
- gl.deleteFramebuffer(this.framebuffer);
444
- gl.deleteFramebuffer(this.resolveFramebuffer);
445
- gl.deleteRenderbuffer(this.depthBuffer);
446
- gl.deleteRenderbuffer(this.colorBuffer);
447
- this.drawState.forget();
448
- this.texture = null;
449
- this.framebuffer = null;
450
- this.resolveFramebuffer = null;
451
- this.colorBuffer = null;
452
- this.depthBuffer = null;
453
- this.resolveDirty = false;
454
- this.antiAlias = 0;
455
- this.scale = 1;
456
- this.width = 0;
457
- this.height = 0;
458
- this.drawnWidth = 0;
459
- this.drawnHeight = 0;
460
- this.pixels = new Uint8Array(0);
461
- this.topDown = new Uint8Array(0);
325
+ unconfigureBackBuffer(this);
462
326
  }
463
327
 
464
328
  clear(
@@ -474,7 +338,13 @@ export class WebGLContext3D implements Context3D {
474
338
  const gl = this.gl;
475
339
  let bits = 0;
476
340
  if (mask & 1) {
477
- gl.clearColor(red, green, blue, alpha);
341
+ // A float target too: AIR clears through Direct3D's 8-bit colour.
342
+ gl.clearColor(
343
+ clearChannel(red),
344
+ clearChannel(green),
345
+ clearChannel(blue),
346
+ clearChannel(alpha),
347
+ );
478
348
  bits |= gl.COLOR_BUFFER_BIT;
479
349
  this.changedTarget();
480
350
  }
@@ -663,7 +533,8 @@ export class WebGLContext3D implements Context3D {
663
533
  this.applyScissor();
664
534
  }
665
535
 
666
- private applyScissor(): void {
536
+ /** @internal */
537
+ applyScissor(): void {
667
538
  const gl = this.gl;
668
539
  if (!this.scissor) {
669
540
  this.scissorOutside = false;
@@ -760,112 +631,11 @@ export class WebGLContext3D implements Context3D {
760
631
  }
761
632
 
762
633
  createBuffer(count: number, stride: number, dynamic = false): Buffer3D {
763
- this.alive();
764
- range(1, count - 1, stride === 0 ? 524287 : 65535);
765
- range(0, stride, 64);
766
- const countLimit = stride === 0 ? this.limits.maxIndexBuffers : this.limits.maxVertexBuffers;
767
- const used = stride === 0 ? this.buffers.size - this.vertexBuffers : this.vertexBuffers;
768
- if (this.buffers.size >= this.limits.maxBuffers || used >= countLimit) {
769
- throw new Context3DError("limit", "Context3D experiment: buffer limit");
770
- }
771
-
772
- const bytes = count * (stride === 0 ? 2 : stride * 4);
773
- const usedBytes =
774
- stride === 0 ? this.bufferBytes - this.vertexBufferBytes : this.vertexBufferBytes;
775
- const byteLimit =
776
- stride === 0 ? this.limits.maxIndexBufferBytes : this.limits.maxVertexBufferBytes;
777
- if (this.bufferBytes + bytes > this.limits.maxBufferBytes || usedBytes + bytes > byteLimit) {
778
- throw new Context3DError("limit", "Context3D experiment: buffer memory limit");
779
- }
780
-
781
- const gl = this.gl;
782
- const handle = gl.createBuffer();
783
- if (!handle) {
784
- throw new Context3DError("bufferAllocation", "Context3D experiment: cannot allocate buffer");
785
- }
786
-
787
- const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
788
- if (stride === 0) {
789
- this.indexBuffer = null;
790
- }
791
- gl.bindVertexArray(this.vao);
792
- gl.bindBuffer(target, handle);
793
- gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
794
- const state: BufferState = {
795
- handle,
796
- indices: stride === 0 ? new Uint16Array(count) : null,
797
- uploaded: new Uint8Array(count),
798
- prefix: 0,
799
- version: 0,
800
- checkedVersion: -1,
801
- checkedFirst: 0,
802
- checkedCount: 0,
803
- maximum: -1,
804
- ranges: null,
805
- };
806
- const buffer: Buffer3D = {
807
- count,
808
- stride,
809
- upload: (values, start, amount) => {
810
- this.alive();
811
- this.buffer(buffer);
812
- try {
813
- uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
814
- } finally {
815
- // Source getters can draw before the upload replaces the VAO's index binding.
816
- if (stride === 0) {
817
- this.indexBuffer = null;
818
- }
819
- }
820
- },
821
- uploadBytes: (data, offset, start, amount) => {
822
- this.alive();
823
- this.buffer(buffer);
824
- try {
825
- uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
826
- } finally {
827
- if (stride === 0) {
828
- this.indexBuffer = null;
829
- }
830
- }
831
- },
832
- dispose: () => {
833
- if (!this.buffers.delete(buffer)) {
834
- return;
835
- }
836
-
837
- if (this.indexBuffer === handle) {
838
- this.indexBuffer = null;
839
- }
840
- gl.deleteBuffer(handle);
841
- this.bufferBytes -= bytes;
842
- if (stride !== 0) {
843
- this.vertexBuffers--;
844
- this.vertexBufferBytes -= bytes;
845
- }
846
- state.indices = null;
847
- state.ranges = null;
848
- state.uploaded = new Uint8Array(0);
849
- for (let i = 0; i < this.vertices.length; i++) {
850
- const binding = this.vertices[i];
851
- if (binding?.buffer === buffer) {
852
- this.vertices[i] = null;
853
- } else if (binding?.checkedIndex === state) {
854
- binding.checkedIndex = null;
855
- }
856
- }
857
- },
858
- };
859
- this.buffers.set(buffer, state);
860
- if (stride !== 0) {
861
- this.vertexBuffers++;
862
- this.vertexBufferBytes += bytes;
863
- }
864
- this.bufferBytes += bytes;
865
- return buffer;
634
+ return allocateBuffer(this, count, stride, dynamic);
866
635
  }
867
636
 
868
- private buffer(buffer: Buffer3D): BufferState {
637
+ /** @internal */
638
+ buffer(buffer: Buffer3D): BufferState {
869
639
  const state = this.buffers.get(buffer);
870
640
  if (!state) {
871
641
  throw new Context3DError(
@@ -891,6 +661,7 @@ export class WebGLContext3D implements Context3D {
891
661
  attributeMask: 0,
892
662
  vertex: [],
893
663
  vertexBank: null,
664
+ outputs: 1,
894
665
  fragment: [],
895
666
  samplers: [],
896
667
  samplerMask: 0,
@@ -902,11 +673,17 @@ export class WebGLContext3D implements Context3D {
902
673
  vertexVersion: -1,
903
674
  };
904
675
  const program: Program3D = {
676
+ get outputs() {
677
+ return state.outputs;
678
+ },
679
+ get attributes() {
680
+ return state.attributeMask;
681
+ },
905
682
  upload: (vertex, fragment) => {
906
683
  this.alive();
907
684
  this.program(program);
908
685
  this.drawState.forget();
909
- uploadProgram(this.gl, state, vertex, fragment);
686
+ uploadProgram(this.gl, state, vertex, fragment, this.profile);
910
687
 
911
688
  if (this.selected === program) {
912
689
  this.setProgram(program);
@@ -968,8 +745,8 @@ export class WebGLContext3D implements Context3D {
968
745
  return this.allocateTexture(width, height, cube, false, format, streaming);
969
746
  }
970
747
 
971
- createRectangleTexture(width: number, height: number): Texture3D {
972
- return this.allocateTexture(width, height, false, true, "bgra");
748
+ createRectangleTexture(width: number, height: number, format: TextureFormat = "bgra"): Texture3D {
749
+ return this.allocateTexture(width, height, false, true, format);
973
750
  }
974
751
 
975
752
  /** GL's internal format for a compressed texture's S3TC, or 0 where the context takes none. */
@@ -977,7 +754,7 @@ export class WebGLContext3D implements Context3D {
977
754
  if (this.s3tc === undefined) {
978
755
  this.s3tc = this.gl.getExtension("WEBGL_compressed_texture_s3tc");
979
756
  }
980
- if (!this.s3tc || format === "bgra") {
757
+ if (!this.s3tc || !compressedFormat(format)) {
981
758
  return 0;
982
759
  }
983
760
 
@@ -1003,7 +780,9 @@ export class WebGLContext3D implements Context3D {
1003
780
  for (let level = 0; level < levels; level++) {
1004
781
  const w = Math.max(1, width >> level);
1005
782
  const h = Math.max(1, height >> level);
1006
- size += format === "bgra" ? w * h * 4 : blockBytes(w, h, format === "compressedAlpha");
783
+ size += compressedFormat(format)
784
+ ? blockBytes(w, h, format === "compressedAlpha")
785
+ : w * h * (TEXEL_BYTES[format] ?? 4);
1007
786
  }
1008
787
 
1009
788
  return size * faces;
@@ -1018,12 +797,14 @@ export class WebGLContext3D implements Context3D {
1018
797
  streaming = 0,
1019
798
  ): Texture3D {
1020
799
  this.alive();
1021
- range(1, width - 1, 2048);
1022
- range(1, height - 1, 2048);
800
+ const limits = profileLimits(this.profile);
801
+ const most = rectangle ? limits.rectangleSize : limits.textureSize;
802
+ range(1, width - 1, most);
803
+ range(1, height - 1, most);
1023
804
  if (cube && width !== height) {
1024
805
  throw new Context3DError("textureShape", "Context3D cube faces must be square");
1025
806
  }
1026
- if (cube && width > 1024) {
807
+ if (cube && width > MOST_CUBE) {
1027
808
  throw new Context3DError("textureSize", "Context3D cube faces must be at most 1024");
1028
809
  }
1029
810
 
@@ -1036,15 +817,15 @@ export class WebGLContext3D implements Context3D {
1036
817
  "Context3D texture size must be a power of two",
1037
818
  );
1038
819
  }
1039
- if (format !== "bgra" && rectangle) {
820
+ if (compressedFormat(format) && rectangle) {
1040
821
  throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
1041
822
  }
1042
- if (format !== "bgra" && (width < 4 || height < 4)) {
823
+ if (compressedFormat(format) && (width < 4 || height < 4)) {
1043
824
  throw new Context3DError("compressedSize", "Context3D compressed textures are at least 4x4");
1044
825
  }
1045
826
 
1046
827
  const gl = this.gl;
1047
- const storage = this.s3tcFormat(format) || gl.RGBA8;
828
+ const storage = this.s3tcFormat(format) || storageFormat(gl, format);
1048
829
  const size = this.chainBytes(width, height, levels, faces, format);
1049
830
  const kind = cube ? 2 : rectangle ? 1 : 0;
1050
831
  const countLimit = cube
@@ -1060,7 +841,8 @@ export class WebGLContext3D implements Context3D {
1060
841
  // AIR reserves half the base size for mips, independently of the exact
1061
842
  // chain storage, and charges DXT1 an eighth of BGRA and DXT5 a quarter.
1062
843
  const quotaBytes =
1063
- (width * height * faces * (rectangle ? 4 : 6)) /
844
+ (width * height * faces * (rectangle ? 4 : 6) * (TEXEL_BYTES[format] ?? 4)) /
845
+ 4 /
1064
846
  (format === "compressed" ? 8 : format === "compressedAlpha" ? 4 : 1);
1065
847
  if (
1066
848
  this.textures.size >= this.limits.maxTextures ||
@@ -1095,6 +877,7 @@ export class WebGLContext3D implements Context3D {
1095
877
  multisample: null,
1096
878
  samples: 0,
1097
879
  antiAlias: 0,
880
+ outputs: 0,
1098
881
  base: 0,
1099
882
  };
1100
883
  const texture: Texture3D = {
@@ -1137,10 +920,10 @@ export class WebGLContext3D implements Context3D {
1137
920
  uploadCompressed: (blocks, level = 0, face = 0) => {
1138
921
  this.alive();
1139
922
  this.textureState(texture);
1140
- if (format === "bgra") {
923
+ if (!compressedFormat(format)) {
1141
924
  throw new Context3DError(
1142
925
  "textureUpload",
1143
- "Context3D experiment: BGRA texture given blocks",
926
+ "Context3D experiment: uncompressed texture given blocks",
1144
927
  );
1145
928
  }
1146
929
 
@@ -1180,6 +963,13 @@ export class WebGLContext3D implements Context3D {
1180
963
  state.mipFramebuffer = null;
1181
964
  state.depthBuffer = null;
1182
965
 
966
+ for (let i = 0; i < this.outputs.length; i++) {
967
+ if (this.outputs[i] === texture) {
968
+ this.outputs[i] = null;
969
+ this.outputsChanged = true;
970
+ this.outputsBound = this.outputMask() !== 0;
971
+ }
972
+ }
1183
973
  for (let i = 0; i < this.textureSlots.length; i++) {
1184
974
  if (this.textureSlots[i] === texture) {
1185
975
  this.texturesApplied[i] = 0;
@@ -1335,6 +1125,7 @@ export class WebGLContext3D implements Context3D {
1335
1125
  depth,
1336
1126
  this.depthWrite,
1337
1127
  this.stencilWrite,
1128
+ state.storage,
1338
1129
  );
1339
1130
  if (same) {
1340
1131
  if (newDepth) {
@@ -1370,12 +1161,13 @@ export class WebGLContext3D implements Context3D {
1370
1161
  const gl = this.gl;
1371
1162
  if (texture) {
1372
1163
  this.textureState(texture);
1373
- if (texture.format !== "bgra") {
1164
+ if (compressedFormat(texture.format)) {
1374
1165
  throw new Context3DError(
1375
1166
  "operation",
1376
1167
  "Context3D experiment: compressed textures are no render targets",
1377
1168
  );
1378
1169
  }
1170
+ this.renderFormat(texture.format);
1379
1171
  }
1380
1172
  const samples = texture ? this.multisamples(antiAlias, depth) : 0;
1381
1173
  this.finishTarget();
@@ -1521,6 +1313,12 @@ export class WebGLContext3D implements Context3D {
1521
1313
  }
1522
1314
 
1523
1315
  private changedTarget(): void {
1316
+ for (let i = 0; this.outputsBound && i < this.outputs.length; i++) {
1317
+ const output = this.outputs[i];
1318
+ if (output) {
1319
+ this.textureState(output).dirty = true;
1320
+ }
1321
+ }
1524
1322
  if (this.renderTexture) {
1525
1323
  const state = this.textureState(this.renderTexture);
1526
1324
  state.dirty = true;
@@ -1537,22 +1335,34 @@ export class WebGLContext3D implements Context3D {
1537
1335
  }
1538
1336
 
1539
1337
  private finishTarget(): void {
1540
- if (!this.renderTexture) {
1541
- return;
1338
+ if (this.renderTexture) {
1339
+ this.finishTexture(this.renderTexture, this.renderFace);
1340
+ }
1341
+ for (let i = 0; i < this.outputs.length; i++) {
1342
+ const output = this.outputs[i];
1343
+ if (output) {
1344
+ this.finishTexture(output, this.outputFaces[i]);
1345
+ }
1542
1346
  }
1543
- const texture = this.renderTexture;
1347
+ }
1348
+
1349
+ /** A texture drawn into made whole again: resolved, and its mips regenerated. */
1350
+ private finishTexture(texture: Texture3D, renderFace: number): void {
1544
1351
  const state = this.textureState(texture);
1545
1352
  if (state.dirty) {
1546
1353
  const gl = this.gl;
1547
1354
  this.drawState.forget();
1548
1355
  this.texturesApplied[0] = 0;
1549
- resolveTarget(this.gl, texture, state, this.renderFace, this.scissor);
1356
+ resolveTarget(this.gl, texture, state, renderFace, this.scissor);
1550
1357
  gl.activeTexture(gl.TEXTURE0);
1551
1358
  if (texture.cube) {
1359
+ // An extra colour output may never have been a target of its own.
1360
+ state.framebuffer ??= gl.createFramebuffer();
1361
+ state.mipFramebuffer ??= gl.createFramebuffer();
1552
1362
  // generateMipmap(CUBE_MAP) would overwrite manually uploaded mips on
1553
1363
  // untouched faces. AIR regenerates only the face just rendered.
1554
1364
  gl.disable(gl.SCISSOR_TEST);
1555
- const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.renderFace;
1365
+ const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderFace;
1556
1366
  // The base-size depth attachment cannot accompany smaller mip images.
1557
1367
  this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
1558
1368
  if (state.depthAttached) {
@@ -1614,14 +1424,99 @@ export class WebGLContext3D implements Context3D {
1614
1424
  gl.bindTexture(gl.TEXTURE_2D, state.handle);
1615
1425
  gl.generateMipmap(gl.TEXTURE_2D);
1616
1426
  }
1617
- state.uploaded[this.renderFace] = (1 << texture.levels) - 1;
1427
+ state.uploaded[renderFace] = (1 << texture.levels) - 1;
1618
1428
  state.dirty = false;
1619
1429
  }
1620
1430
  }
1621
1431
 
1432
+ /** Half floats are drawn into where the GPU's WebGL renders float colour, as every desktop one does. */
1433
+ private renderFormat(format: TextureFormat): void {
1434
+ if (format !== "rgbaHalfFloat") {
1435
+ return;
1436
+ }
1437
+
1438
+ this.floatTargets ??= this.gl.getExtension("EXT_color_buffer_float") !== null;
1439
+ if (!this.floatTargets) {
1440
+ throw new Context3DError("unsupported", "Context3D: this GPU draws into no half floats");
1441
+ }
1442
+ }
1443
+
1444
+ setColorOutput(index: number, texture: Texture3D | null, face = 0): void {
1445
+ this.alive();
1446
+ range(index - 1, 1, this.outputs.length);
1447
+ if (texture) {
1448
+ this.textureState(texture);
1449
+ range(face, 1, texture.cube ? 6 : 1);
1450
+ if (compressedFormat(texture.format)) {
1451
+ throw new Context3DError(
1452
+ "operation",
1453
+ "Context3D experiment: compressed textures are no render targets",
1454
+ );
1455
+ }
1456
+ this.renderFormat(texture.format);
1457
+ }
1458
+ if (this.outputs[index - 1] === texture && this.outputFaces[index - 1] === face) {
1459
+ return;
1460
+ }
1461
+
1462
+ const previous = this.outputs[index - 1];
1463
+ if (previous) {
1464
+ this.finishTexture(previous, this.outputFaces[index - 1]);
1465
+ }
1466
+ this.outputs[index - 1] = texture;
1467
+ this.outputFaces[index - 1] = texture ? face : 0;
1468
+ this.outputsChanged = true;
1469
+ this.outputsBound = this.outputMask() !== 0;
1470
+ }
1471
+
1472
+ /**
1473
+ * The current target's framebuffer with the extra colour outputs attached, or without
1474
+ * them: its draws write every output bound, and a program's unwritten ones are refused
1475
+ * before they draw.
1476
+ */
1477
+ private attachOutputs(state: TextureState | null): void {
1478
+ const want = state ? this.outputMask() : 0;
1479
+ if (!state || (!this.outputsChanged && state.outputs === want)) {
1480
+ return;
1481
+ }
1482
+ if (want && state.samples) {
1483
+ throw new Context3DError("unsupported", "Context3D: antialiased colour outputs");
1484
+ }
1485
+
1486
+ const gl = this.gl;
1487
+ const buffers: number[] = [gl.COLOR_ATTACHMENT0];
1488
+ for (let i = 0; i < this.outputs.length; i++) {
1489
+ const output = this.outputs[i];
1490
+ const handle = output ? this.textureState(output).handle : null;
1491
+ gl.framebufferTexture2D(
1492
+ gl.FRAMEBUFFER,
1493
+ gl.COLOR_ATTACHMENT1 + i,
1494
+ output?.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.outputFaces[i] : gl.TEXTURE_2D,
1495
+ handle,
1496
+ 0,
1497
+ );
1498
+ buffers.push(output ? gl.COLOR_ATTACHMENT1 + i : gl.NONE);
1499
+ }
1500
+ gl.drawBuffers(buffers);
1501
+ state.outputs = want;
1502
+ this.outputsChanged = false;
1503
+ }
1504
+
1505
+ /** The extra colour outputs bound, a bit each from output 1. */
1506
+ private outputMask(): number {
1507
+ let mask = 0;
1508
+ for (let i = 0; i < this.outputs.length; i++) {
1509
+ if (this.outputs[i]) {
1510
+ mask |= 1 << i;
1511
+ }
1512
+ }
1513
+
1514
+ return mask;
1515
+ }
1516
+
1622
1517
  setTexture(index: number, texture: Texture3D | null): void {
1623
1518
  this.alive();
1624
- range(index, 1, 8);
1519
+ range(index, 1, SAMPLER_SLOTS);
1625
1520
  if (texture && !this.textureOwners.has(texture)) {
1626
1521
  throw new Context3DError("disposed", "Context3D experiment: texture from another context");
1627
1522
  }
@@ -1726,7 +1621,7 @@ export class WebGLContext3D implements Context3D {
1726
1621
  if (!source.ignore) {
1727
1622
  this.setSamplerState(
1728
1623
  source.index,
1729
- (source.repeat ? 3 : 0) | (source.filter << 2) | (source.mip << 5),
1624
+ WRAPS[source.wrap] | (source.filter << 2) | (source.mip << 5),
1730
1625
  );
1731
1626
  }
1732
1627
  }
@@ -1734,7 +1629,7 @@ export class WebGLContext3D implements Context3D {
1734
1629
 
1735
1630
  setVertex(index: number, buffer: Buffer3D | null, offset: number, format: VertexFormat): void {
1736
1631
  this.alive();
1737
- range(index, 1, 8);
1632
+ range(index, 1, VERTEX_SLOTS);
1738
1633
  if (buffer === null) {
1739
1634
  this.vertices[index] = null;
1740
1635
  return;
@@ -1857,13 +1752,14 @@ export class WebGLContext3D implements Context3D {
1857
1752
 
1858
1753
  const gl = this.gl;
1859
1754
  this.drawState.bindVertexArray(this.vao);
1860
- for (let i = 0; i < 8; i++) {
1861
- const bit = 1 << i;
1862
- if (this.enabledAttributes & bit & ~state.attributeMask) {
1863
- gl.disableVertexAttribArray(i);
1864
- this.enabledAttributes &= ~bit;
1865
- }
1755
+ // The streams enabled that the program does not read, highest first.
1756
+ let stale = this.enabledAttributes & ~state.attributeMask;
1757
+ while (stale) {
1758
+ const i = 31 - Math.clz32(stale);
1759
+ gl.disableVertexAttribArray(i);
1760
+ stale &= ~(1 << i);
1866
1761
  }
1762
+ this.enabledAttributes &= state.attributeMask;
1867
1763
 
1868
1764
  for (let i = 0; i < state.attributes.length; i++) {
1869
1765
  const slot = state.attributes[i];
@@ -1909,7 +1805,14 @@ export class WebGLContext3D implements Context3D {
1909
1805
  const binding = state.samplers[i];
1910
1806
  const slot = binding.source.index;
1911
1807
  const texture = this.textureSlots[slot];
1912
- if (texture && texture === this.renderTexture) {
1808
+ if (
1809
+ texture &&
1810
+ (texture === this.renderTexture ||
1811
+ (this.outputsBound &&
1812
+ (texture === this.outputs[0] ||
1813
+ texture === this.outputs[1] ||
1814
+ texture === this.outputs[2])))
1815
+ ) {
1913
1816
  throw new TextureBindingError("feedback");
1914
1817
  }
1915
1818
  const resource = texture && this.textures.get(texture);
@@ -1976,10 +1879,12 @@ export class WebGLContext3D implements Context3D {
1976
1879
 
1977
1880
  if (state.vertexBank) {
1978
1881
  if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
1979
- gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
1882
+ gl.uniform4fv(state.vertexBank, this.vertexConstants.data, 0, INDIRECT_BANK * 4);
1980
1883
  state.vertexVersion = this.vertexConstants.version;
1981
1884
  }
1982
- } else {
1885
+ }
1886
+ // An indirect program's own uniforms are only the registers past its bank.
1887
+ if (state.vertex.length) {
1983
1888
  this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions, state.vertexValues);
1984
1889
  }
1985
1890
  this.uniforms(
@@ -2072,115 +1977,12 @@ export class WebGLContext3D implements Context3D {
2072
1977
  }
2073
1978
  }
2074
1979
 
2075
- private resolve(): void {
2076
- if (!this.resolveFramebuffer) {
2077
- return;
2078
- }
2079
-
2080
- const gl = this.gl;
2081
- if (this.resolveDirty) {
2082
- this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
2083
- this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
2084
- if (this.scissor) {
2085
- gl.disable(gl.SCISSOR_TEST);
2086
- }
2087
- gl.blitFramebuffer(
2088
- 0,
2089
- 0,
2090
- this.drawnWidth,
2091
- this.drawnHeight,
2092
- 0,
2093
- 0,
2094
- this.drawnWidth,
2095
- this.drawnHeight,
2096
- gl.COLOR_BUFFER_BIT,
2097
- gl.NEAREST,
2098
- );
2099
- if (this.scissor) {
2100
- gl.enable(gl.SCISSOR_TEST);
2101
- }
2102
- this.resolveDirty = false;
2103
- }
2104
- this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
2105
- }
2106
-
2107
1980
  read(width = this.width, height = this.height): Uint8Array {
2108
- width = Math.max(0, Math.min(this.width, Math.floor(width) || 0));
2109
- height = Math.max(0, Math.min(this.height, Math.floor(height) || 0));
2110
- this.target(true);
2111
- this.resolve();
2112
- const gl = this.gl;
2113
- const scaleX = this.drawnWidth / this.width;
2114
- const scaleY = this.drawnHeight / this.height;
2115
- const drawnWidth = Math.min(this.drawnWidth, Math.ceil(width * scaleX));
2116
- const drawnHeight = Math.min(this.drawnHeight, Math.ceil(height * scaleY));
2117
- if (this.pixels.length < drawnWidth * drawnHeight * 4) {
2118
- this.pixels = new Uint8Array(drawnWidth * drawnHeight * 4);
2119
- }
2120
- if (this.topDown.length < width * height * 4) {
2121
- this.topDown = new Uint8Array(width * height * 4);
2122
- }
2123
-
2124
- // GL's rows run bottom-up, so the top-left region starts height rows below the top.
2125
- gl.readPixels(
2126
- 0,
2127
- this.drawnHeight - drawnHeight,
2128
- drawnWidth,
2129
- drawnHeight,
2130
- gl.RGBA,
2131
- gl.UNSIGNED_BYTE,
2132
- this.pixels,
2133
- );
2134
- const row = width * 4;
2135
- if (drawnWidth === width && drawnHeight === height) {
2136
- for (let y = 0; y < height; y++) {
2137
- const from = (height - y - 1) * row;
2138
- this.topDown.set(this.pixels.subarray(from, from + row), y * row);
2139
- }
2140
-
2141
- return this.topDown;
2142
- }
2143
-
2144
- // Storage smaller than the back buffer: each pixel is the stored one it falls on.
2145
- const source = new Uint32Array(this.pixels.buffer, 0, drawnWidth * drawnHeight);
2146
- const target = new Uint32Array(this.topDown.buffer, 0, width * height);
2147
- for (let y = 0; y < height; y++) {
2148
- const from =
2149
- (drawnHeight - 1 - Math.min(drawnHeight - 1, Math.floor(y * scaleY))) * drawnWidth;
2150
- for (let x = 0; x < width; x++) {
2151
- target[y * width + x] = source[from + Math.min(drawnWidth - 1, Math.floor(x * scaleX))];
2152
- }
2153
- }
2154
-
2155
- return this.topDown;
1981
+ return readBackBuffer(this, width, height);
2156
1982
  }
2157
1983
 
2158
1984
  present(): void {
2159
- this.setRenderTarget(null);
2160
- this.target();
2161
- this.resolve();
2162
- const gl = this.gl;
2163
- this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
2164
- if (this.scissor) {
2165
- gl.disable(gl.SCISSOR_TEST);
2166
- }
2167
- // To the drawing buffer the browser gave, which it may have made smaller than the canvas.
2168
- gl.blitFramebuffer(
2169
- 0,
2170
- 0,
2171
- this.drawnWidth,
2172
- this.drawnHeight,
2173
- 0,
2174
- 0,
2175
- gl.drawingBufferWidth,
2176
- gl.drawingBufferHeight,
2177
- gl.COLOR_BUFFER_BIT,
2178
- gl.NEAREST,
2179
- );
2180
- if (this.scissor) {
2181
- gl.enable(gl.SCISSOR_TEST);
2182
- }
2183
- gl.flush();
1985
+ presentBackBuffer(this);
2184
1986
  }
2185
1987
 
2186
1988
  dispose(): void {