@swf2es/context3d 0.1.20 → 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.
@@ -7,7 +7,6 @@ import {
7
7
  type CullFace,
8
8
  compressedFormat,
9
9
  FRAGMENT_CONSTANTS,
10
- MOST_BACK_BUFFER,
11
10
  MOST_CUBE,
12
11
  type Profile,
13
12
  type Program3D,
@@ -36,12 +35,8 @@ import { GlState } from "../device/state.js";
36
35
  import { blockBytes, type DxtEncoder } from "../dxt.js";
37
36
  import { Context3DError } from "../errors.js";
38
37
 
39
- import {
40
- type BufferState,
41
- uploadBuffer,
42
- uploadBufferBytes,
43
- type VertexBinding,
44
- } from "./buffers.js";
38
+ import { configureBackBuffer, presentBackBuffer, unconfigureBackBuffer } from "./back-buffer.js";
39
+ import { allocateBuffer, type BufferState, type VertexBinding } from "./buffers.js";
45
40
  import { resourceLimits, type WebGLContext3DLimits } from "./limits.js";
46
41
  import {
47
42
  type CopyProgram,
@@ -49,6 +44,7 @@ import {
49
44
  type ProgramState,
50
45
  uploadProgram,
51
46
  } from "./programs.js";
47
+ import { readBackBuffer } from "./readback.js";
52
48
  import {
53
49
  BLEND_FUNCTIONS,
54
50
  DEPTH_FUNCTIONS,
@@ -118,26 +114,38 @@ function constants(registers: number): Constants {
118
114
  };
119
115
  }
120
116
 
121
- /** 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
+ */
122
122
  export class WebGLContext3D implements Context3D {
123
123
  readonly gl: WebGL2RenderingContext;
124
- private readonly drawState: GlState;
124
+ /** @internal */
125
+ readonly drawState: GlState;
125
126
  width = 0;
126
127
  height = 0;
127
128
  /**
128
129
  * The back buffer's storage: its size, or less where the GPU allows less. The SWF still
129
130
  * sees the size it asked for, drawn at this one and stretched to it when presented.
130
131
  */
131
- private drawnWidth = 0;
132
- private drawnHeight = 0;
133
- private gpuMost: Int32Array | null = null;
132
+ /** @internal */
133
+ drawnWidth = 0;
134
+ /** @internal */
135
+ drawnHeight = 0;
136
+ /** @internal */
137
+ gpuMost: Int32Array | null = null;
134
138
  disposed = false;
135
139
  samplerFormats = true;
136
140
  profile: Profile = "baseline";
137
- private readonly buffers = new Map<Buffer3D, BufferState>();
138
- private vertexBuffers = 0;
139
- private bufferBytes = 0;
140
- private vertexBufferBytes = 0;
141
+ /** @internal */
142
+ readonly buffers = new Map<Buffer3D, BufferState>();
143
+ /** @internal */
144
+ vertexBuffers = 0;
145
+ /** @internal */
146
+ bufferBytes = 0;
147
+ /** @internal */
148
+ vertexBufferBytes = 0;
141
149
  private readonly programs = new Map<Program3D, ProgramState>();
142
150
  private readonly textures = new Map<Texture3D, TextureState>();
143
151
  private readonly textureCounts = new Float64Array(3);
@@ -149,7 +157,8 @@ export class WebGLContext3D implements Context3D {
149
157
  private copy2D: CopyProgram | null = null;
150
158
  private copyCube: CopyProgram | null = null;
151
159
  private readonly targetSamples = new Int8Array(10).fill(-1);
152
- private renderTexture: Texture3D | null = null;
160
+ /** @internal */
161
+ renderTexture: Texture3D | null = null;
153
162
  private renderDepth = false;
154
163
  private renderFace = 0;
155
164
  /** Colour outputs 1 to 3, drawn into with renderTexture, and their cube faces. */
@@ -167,20 +176,31 @@ export class WebGLContext3D implements Context3D {
167
176
  private anisotropy: EXT_texture_filter_anisotropic | null | undefined;
168
177
  private s3tc: WEBGL_compressed_texture_s3tc | null | undefined;
169
178
  private maxAnisotropy = 1;
170
- private readonly vertices: (VertexBinding | null)[] = new Array(VERTEX_SLOTS).fill(null);
179
+ /** @internal */
180
+ readonly vertices: (VertexBinding | null)[] = new Array(VERTEX_SLOTS).fill(null);
171
181
  private enabledAttributes = 0;
172
- private indexBuffer: WebGLBuffer | null = null;
173
- private readonly texturesApplied = new Uint8Array(SAMPLER_SLOTS);
182
+ /** @internal */
183
+ indexBuffer: WebGLBuffer | null = null;
184
+ /** @internal */
185
+ readonly texturesApplied = new Uint8Array(SAMPLER_SLOTS);
174
186
  private selected: Program3D | null = null;
175
- private framebuffer: WebGLFramebuffer | null = null;
176
- private texture: WebGLTexture | null = null;
177
- private resolveFramebuffer: WebGLFramebuffer | null = null;
178
- private colorBuffer: WebGLRenderbuffer | null = null;
179
- private resolveDirty = false;
180
- private depthBuffer: WebGLRenderbuffer | null = null;
181
- 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;
182
201
  /** The back buffer's pixels to a CSS pixel and to a scissor rectangle's unit. */
183
- private scale = 1;
202
+ /** @internal */
203
+ scale = 1;
184
204
  private depthWrite = true;
185
205
  private depthCompare: CompareMode = "less";
186
206
  private blendEnabled = false;
@@ -194,7 +214,8 @@ export class WebGLContext3D implements Context3D {
194
214
  private stencilReference = 0;
195
215
  private stencilRead = 255;
196
216
  private stencilWrite = 255;
197
- private scissor = false;
217
+ /** @internal */
218
+ scissor = false;
198
219
  private scissorLeft = 0;
199
220
  private scissorTop = 0;
200
221
  private scissorRight = 0;
@@ -202,10 +223,14 @@ export class WebGLContext3D implements Context3D {
202
223
  private scissorOutside = false;
203
224
  private readonly vertexConstants = constants(VERTEX_CONSTANTS);
204
225
  private readonly fragmentConstants = constants(FRAGMENT_CONSTANTS);
205
- private pixels = new Uint8Array(0);
206
- private topDown = new Uint8Array(0);
207
- private readonly vao: WebGLVertexArrayObject;
208
- private readonly limits: Readonly<WebGLContext3DLimits>;
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>;
209
234
  private dxtEncoder: DxtEncoder | null = null;
210
235
 
211
236
  /** The host's whole-level encoder; a refusal retains the TypeScript path. */
@@ -247,13 +272,15 @@ export class WebGLContext3D implements Context3D {
247
272
  gl.disable(gl.DITHER);
248
273
  }
249
274
 
250
- private alive(): void {
275
+ /** @internal */
276
+ alive(): void {
251
277
  if (this.disposed || this.gl.isContextLost()) {
252
278
  throw new Context3DError("disposed", "Context3D experiment: context disposed or lost");
253
279
  }
254
280
  }
255
281
 
256
- private target(back = false, discardColor = false): void {
282
+ /** @internal */
283
+ target(back = false, discardColor = false): void {
257
284
  this.alive();
258
285
  if (!this.framebuffer) {
259
286
  throw new Context3DError(
@@ -284,220 +311,18 @@ export class WebGLContext3D implements Context3D {
284
311
  }
285
312
 
286
313
  /** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
287
- private bindFramebuffer(target: number, framebuffer: WebGLFramebuffer | null): void {
314
+ /** @internal */
315
+ bindFramebuffer(target: number, framebuffer: WebGLFramebuffer | null): void {
288
316
  this.drawState.forget();
289
317
  this.gl.bindFramebuffer(target, framebuffer);
290
318
  }
291
319
 
292
320
  configure(width: number, height: number, depth = false, antiAlias = 0, scale = 1): void {
293
- this.alive();
294
- range(32, width - 32, MOST_BACK_BUFFER);
295
- range(32, height - 32, MOST_BACK_BUFFER);
296
- if (this.renderTexture) {
297
- throw new Context3DError(
298
- "operation",
299
- "Context3D experiment: resize while rendering to texture",
300
- );
301
- }
302
-
303
- // AIR preserves color, depth and bindings when the request is unchanged.
304
- // Shadow passes can issue this every frame when returning to the back buffer.
305
- if (
306
- this.framebuffer &&
307
- width === this.width &&
308
- height === this.height &&
309
- depth === (this.depthBuffer !== null) &&
310
- antiAlias === this.antiAlias
311
- ) {
312
- if (scale !== this.scale) {
313
- this.scale = scale;
314
- this.showAt();
315
- this.applyScissor();
316
- }
317
-
318
- return;
319
- }
320
-
321
- const samples = selectSamples(this.gl, antiAlias, depth);
322
- const gl = this.gl;
323
- const most = this.storageLimit();
324
- const drawnWidth = Math.min(width, most[0]);
325
- const drawnHeight = Math.min(height, most[1]);
326
- const texture = gl.createTexture();
327
- const framebuffer = gl.createFramebuffer();
328
- const depthBuffer = depth ? gl.createRenderbuffer() : null;
329
- const resolveFramebuffer = samples ? gl.createFramebuffer() : null;
330
- const colorBuffer = samples ? gl.createRenderbuffer() : null;
331
- try {
332
- if (
333
- !texture ||
334
- !framebuffer ||
335
- (depth && !depthBuffer) ||
336
- (samples && (!resolveFramebuffer || !colorBuffer))
337
- ) {
338
- throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
339
- }
340
-
341
- this.texturesApplied[0] = 0;
342
- gl.activeTexture(gl.TEXTURE0);
343
- gl.bindTexture(gl.TEXTURE_2D, texture);
344
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
345
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
346
- gl.texImage2D(
347
- gl.TEXTURE_2D,
348
- 0,
349
- gl.RGBA8,
350
- drawnWidth,
351
- drawnHeight,
352
- 0,
353
- gl.RGBA,
354
- gl.UNSIGNED_BYTE,
355
- null,
356
- );
357
- this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
358
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
359
- if (resolveFramebuffer) {
360
- if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
361
- throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
362
- }
363
-
364
- this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
365
- gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
366
- gl.renderbufferStorageMultisample(
367
- gl.RENDERBUFFER,
368
- samples,
369
- gl.RGBA8,
370
- drawnWidth,
371
- drawnHeight,
372
- );
373
- gl.framebufferRenderbuffer(
374
- gl.FRAMEBUFFER,
375
- gl.COLOR_ATTACHMENT0,
376
- gl.RENDERBUFFER,
377
- colorBuffer,
378
- );
379
- }
380
- if (depthBuffer) {
381
- gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
382
- if (samples) {
383
- gl.renderbufferStorageMultisample(
384
- gl.RENDERBUFFER,
385
- samples,
386
- gl.DEPTH24_STENCIL8,
387
- drawnWidth,
388
- drawnHeight,
389
- );
390
- } else {
391
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
392
- }
393
- gl.framebufferRenderbuffer(
394
- gl.FRAMEBUFFER,
395
- gl.DEPTH_STENCIL_ATTACHMENT,
396
- gl.RENDERBUFFER,
397
- depthBuffer,
398
- );
399
- }
400
- gl.bindRenderbuffer(gl.RENDERBUFFER, null);
401
- if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
402
- throw new Context3DError("operation", "Context3D experiment: incomplete back buffer");
403
- }
404
- } catch (error) {
405
- gl.deleteTexture(texture);
406
- gl.deleteFramebuffer(framebuffer);
407
- gl.deleteFramebuffer(resolveFramebuffer);
408
- gl.deleteRenderbuffer(depthBuffer);
409
- gl.deleteRenderbuffer(colorBuffer);
410
- gl.bindRenderbuffer(gl.RENDERBUFFER, null);
411
- this.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
412
- throw error;
413
- }
414
-
415
- gl.deleteTexture(this.texture);
416
- gl.deleteFramebuffer(this.framebuffer);
417
- gl.deleteFramebuffer(this.resolveFramebuffer);
418
- gl.deleteRenderbuffer(this.depthBuffer);
419
- gl.deleteRenderbuffer(this.colorBuffer);
420
- this.texture = texture;
421
- this.framebuffer = framebuffer;
422
- this.resolveFramebuffer = resolveFramebuffer;
423
- this.colorBuffer = colorBuffer;
424
- this.depthBuffer = depthBuffer;
425
- this.antiAlias = antiAlias;
426
- this.width = width;
427
- this.height = height;
428
- this.drawnWidth = drawnWidth;
429
- this.drawnHeight = drawnHeight;
430
- this.scale = scale;
431
- gl.canvas.width = drawnWidth;
432
- gl.canvas.height = drawnHeight;
433
- this.showAt();
434
- // Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
435
- this.pixels = new Uint8Array(0);
436
- this.topDown = new Uint8Array(0);
437
- this.applyScissor();
438
- this.clear(0, 0, 0, 1);
439
- }
440
-
441
- /**
442
- * Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
443
- * divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
444
- */
445
- private showAt(): void {
446
- const style = (this.gl.canvas as Partial<HTMLCanvasElement>).style;
447
- if (!style) {
448
- return;
449
- }
450
-
451
- const plain = this.scale === 1;
452
- style.width = plain && this.drawnWidth === this.width ? "" : `${this.width / this.scale}px`;
453
- style.height = plain && this.drawnHeight === this.height ? "" : `${this.height / this.scale}px`;
454
- }
455
-
456
- /** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
457
- private storageLimit(): Int32Array {
458
- if (!this.gpuMost) {
459
- const gl = this.gl;
460
- const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS) as Int32Array;
461
- const side = Math.min(
462
- gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
463
- gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
464
- );
465
- this.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
466
- }
467
-
468
- return this.gpuMost;
321
+ configureBackBuffer(this, width, height, depth, antiAlias, scale);
469
322
  }
470
323
 
471
324
  unconfigure(): void {
472
- this.alive();
473
- if (this.renderTexture) {
474
- throw new Context3DError(
475
- "operation",
476
- "Context3D experiment: resize while rendering to texture",
477
- );
478
- }
479
-
480
- const gl = this.gl;
481
- gl.deleteTexture(this.texture);
482
- gl.deleteFramebuffer(this.framebuffer);
483
- gl.deleteFramebuffer(this.resolveFramebuffer);
484
- gl.deleteRenderbuffer(this.depthBuffer);
485
- gl.deleteRenderbuffer(this.colorBuffer);
486
- this.drawState.forget();
487
- this.texture = null;
488
- this.framebuffer = null;
489
- this.resolveFramebuffer = null;
490
- this.colorBuffer = null;
491
- this.depthBuffer = null;
492
- this.resolveDirty = false;
493
- this.antiAlias = 0;
494
- this.scale = 1;
495
- this.width = 0;
496
- this.height = 0;
497
- this.drawnWidth = 0;
498
- this.drawnHeight = 0;
499
- this.pixels = new Uint8Array(0);
500
- this.topDown = new Uint8Array(0);
325
+ unconfigureBackBuffer(this);
501
326
  }
502
327
 
503
328
  clear(
@@ -708,7 +533,8 @@ export class WebGLContext3D implements Context3D {
708
533
  this.applyScissor();
709
534
  }
710
535
 
711
- private applyScissor(): void {
536
+ /** @internal */
537
+ applyScissor(): void {
712
538
  const gl = this.gl;
713
539
  if (!this.scissor) {
714
540
  this.scissorOutside = false;
@@ -805,112 +631,11 @@ export class WebGLContext3D implements Context3D {
805
631
  }
806
632
 
807
633
  createBuffer(count: number, stride: number, dynamic = false): Buffer3D {
808
- this.alive();
809
- range(1, count - 1, stride === 0 ? 524287 : 65535);
810
- range(0, stride, 64);
811
- const countLimit = stride === 0 ? this.limits.maxIndexBuffers : this.limits.maxVertexBuffers;
812
- const used = stride === 0 ? this.buffers.size - this.vertexBuffers : this.vertexBuffers;
813
- if (this.buffers.size >= this.limits.maxBuffers || used >= countLimit) {
814
- throw new Context3DError("limit", "Context3D experiment: buffer limit");
815
- }
816
-
817
- const bytes = count * (stride === 0 ? 2 : stride * 4);
818
- const usedBytes =
819
- stride === 0 ? this.bufferBytes - this.vertexBufferBytes : this.vertexBufferBytes;
820
- const byteLimit =
821
- stride === 0 ? this.limits.maxIndexBufferBytes : this.limits.maxVertexBufferBytes;
822
- if (this.bufferBytes + bytes > this.limits.maxBufferBytes || usedBytes + bytes > byteLimit) {
823
- throw new Context3DError("limit", "Context3D experiment: buffer memory limit");
824
- }
825
-
826
- const gl = this.gl;
827
- const handle = gl.createBuffer();
828
- if (!handle) {
829
- throw new Context3DError("bufferAllocation", "Context3D experiment: cannot allocate buffer");
830
- }
831
-
832
- const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
833
- if (stride === 0) {
834
- this.indexBuffer = null;
835
- }
836
- gl.bindVertexArray(this.vao);
837
- gl.bindBuffer(target, handle);
838
- gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
839
- const state: BufferState = {
840
- handle,
841
- indices: stride === 0 ? new Uint16Array(count) : null,
842
- uploaded: new Uint8Array(count),
843
- prefix: 0,
844
- version: 0,
845
- checkedVersion: -1,
846
- checkedFirst: 0,
847
- checkedCount: 0,
848
- maximum: -1,
849
- ranges: null,
850
- };
851
- const buffer: Buffer3D = {
852
- count,
853
- stride,
854
- upload: (values, start, amount) => {
855
- this.alive();
856
- this.buffer(buffer);
857
- try {
858
- uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
859
- } finally {
860
- // Source getters can draw before the upload replaces the VAO's index binding.
861
- if (stride === 0) {
862
- this.indexBuffer = null;
863
- }
864
- }
865
- },
866
- uploadBytes: (data, offset, start, amount) => {
867
- this.alive();
868
- this.buffer(buffer);
869
- try {
870
- uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
871
- } finally {
872
- if (stride === 0) {
873
- this.indexBuffer = null;
874
- }
875
- }
876
- },
877
- dispose: () => {
878
- if (!this.buffers.delete(buffer)) {
879
- return;
880
- }
881
-
882
- if (this.indexBuffer === handle) {
883
- this.indexBuffer = null;
884
- }
885
- gl.deleteBuffer(handle);
886
- this.bufferBytes -= bytes;
887
- if (stride !== 0) {
888
- this.vertexBuffers--;
889
- this.vertexBufferBytes -= bytes;
890
- }
891
- state.indices = null;
892
- state.ranges = null;
893
- state.uploaded = new Uint8Array(0);
894
- for (let i = 0; i < this.vertices.length; i++) {
895
- const binding = this.vertices[i];
896
- if (binding?.buffer === buffer) {
897
- this.vertices[i] = null;
898
- } else if (binding?.checkedIndex === state) {
899
- binding.checkedIndex = null;
900
- }
901
- }
902
- },
903
- };
904
- this.buffers.set(buffer, state);
905
- if (stride !== 0) {
906
- this.vertexBuffers++;
907
- this.vertexBufferBytes += bytes;
908
- }
909
- this.bufferBytes += bytes;
910
- return buffer;
634
+ return allocateBuffer(this, count, stride, dynamic);
911
635
  }
912
636
 
913
- private buffer(buffer: Buffer3D): BufferState {
637
+ /** @internal */
638
+ buffer(buffer: Buffer3D): BufferState {
914
639
  const state = this.buffers.get(buffer);
915
640
  if (!state) {
916
641
  throw new Context3DError(
@@ -2252,115 +1977,12 @@ export class WebGLContext3D implements Context3D {
2252
1977
  }
2253
1978
  }
2254
1979
 
2255
- private resolve(): void {
2256
- if (!this.resolveFramebuffer) {
2257
- return;
2258
- }
2259
-
2260
- const gl = this.gl;
2261
- if (this.resolveDirty) {
2262
- this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
2263
- this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
2264
- if (this.scissor) {
2265
- gl.disable(gl.SCISSOR_TEST);
2266
- }
2267
- gl.blitFramebuffer(
2268
- 0,
2269
- 0,
2270
- this.drawnWidth,
2271
- this.drawnHeight,
2272
- 0,
2273
- 0,
2274
- this.drawnWidth,
2275
- this.drawnHeight,
2276
- gl.COLOR_BUFFER_BIT,
2277
- gl.NEAREST,
2278
- );
2279
- if (this.scissor) {
2280
- gl.enable(gl.SCISSOR_TEST);
2281
- }
2282
- this.resolveDirty = false;
2283
- }
2284
- this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
2285
- }
2286
-
2287
1980
  read(width = this.width, height = this.height): Uint8Array {
2288
- width = Math.max(0, Math.min(this.width, Math.floor(width) || 0));
2289
- height = Math.max(0, Math.min(this.height, Math.floor(height) || 0));
2290
- this.target(true);
2291
- this.resolve();
2292
- const gl = this.gl;
2293
- const scaleX = this.drawnWidth / this.width;
2294
- const scaleY = this.drawnHeight / this.height;
2295
- const drawnWidth = Math.min(this.drawnWidth, Math.ceil(width * scaleX));
2296
- const drawnHeight = Math.min(this.drawnHeight, Math.ceil(height * scaleY));
2297
- if (this.pixels.length < drawnWidth * drawnHeight * 4) {
2298
- this.pixels = new Uint8Array(drawnWidth * drawnHeight * 4);
2299
- }
2300
- if (this.topDown.length < width * height * 4) {
2301
- this.topDown = new Uint8Array(width * height * 4);
2302
- }
2303
-
2304
- // GL's rows run bottom-up, so the top-left region starts height rows below the top.
2305
- gl.readPixels(
2306
- 0,
2307
- this.drawnHeight - drawnHeight,
2308
- drawnWidth,
2309
- drawnHeight,
2310
- gl.RGBA,
2311
- gl.UNSIGNED_BYTE,
2312
- this.pixels,
2313
- );
2314
- const row = width * 4;
2315
- if (drawnWidth === width && drawnHeight === height) {
2316
- for (let y = 0; y < height; y++) {
2317
- const from = (height - y - 1) * row;
2318
- this.topDown.set(this.pixels.subarray(from, from + row), y * row);
2319
- }
2320
-
2321
- return this.topDown;
2322
- }
2323
-
2324
- // Storage smaller than the back buffer: each pixel is the stored one it falls on.
2325
- const source = new Uint32Array(this.pixels.buffer, 0, drawnWidth * drawnHeight);
2326
- const target = new Uint32Array(this.topDown.buffer, 0, width * height);
2327
- for (let y = 0; y < height; y++) {
2328
- const from =
2329
- (drawnHeight - 1 - Math.min(drawnHeight - 1, Math.floor(y * scaleY))) * drawnWidth;
2330
- for (let x = 0; x < width; x++) {
2331
- target[y * width + x] = source[from + Math.min(drawnWidth - 1, Math.floor(x * scaleX))];
2332
- }
2333
- }
2334
-
2335
- return this.topDown;
1981
+ return readBackBuffer(this, width, height);
2336
1982
  }
2337
1983
 
2338
1984
  present(): void {
2339
- this.setRenderTarget(null);
2340
- this.target();
2341
- this.resolve();
2342
- const gl = this.gl;
2343
- this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
2344
- if (this.scissor) {
2345
- gl.disable(gl.SCISSOR_TEST);
2346
- }
2347
- // To the drawing buffer the browser gave, which it may have made smaller than the canvas.
2348
- gl.blitFramebuffer(
2349
- 0,
2350
- 0,
2351
- this.drawnWidth,
2352
- this.drawnHeight,
2353
- 0,
2354
- 0,
2355
- gl.drawingBufferWidth,
2356
- gl.drawingBufferHeight,
2357
- gl.COLOR_BUFFER_BIT,
2358
- gl.NEAREST,
2359
- );
2360
- if (this.scissor) {
2361
- gl.enable(gl.SCISSOR_TEST);
2362
- }
2363
- gl.flush();
1985
+ presentBackBuffer(this);
2364
1986
  }
2365
1987
 
2366
1988
  dispose(): void {
@@ -0,0 +1,54 @@
1
+ // The back buffer read back for drawToBitmapData: its rows top-down, at the size asked for.
2
+
3
+ import { resolveBackBuffer } from "./back-buffer.js";
4
+ import type { WebGLContext3D } from "./context.js";
5
+
6
+ export function readBackBuffer(ctx: WebGLContext3D, width: number, height: number): Uint8Array {
7
+ width = Math.max(0, Math.min(ctx.width, Math.floor(width) || 0));
8
+ height = Math.max(0, Math.min(ctx.height, Math.floor(height) || 0));
9
+ ctx.target(true);
10
+ resolveBackBuffer(ctx);
11
+ const gl = ctx.gl;
12
+ const scaleX = ctx.drawnWidth / ctx.width;
13
+ const scaleY = ctx.drawnHeight / ctx.height;
14
+ const drawnWidth = Math.min(ctx.drawnWidth, Math.ceil(width * scaleX));
15
+ const drawnHeight = Math.min(ctx.drawnHeight, Math.ceil(height * scaleY));
16
+ if (ctx.pixels.length < drawnWidth * drawnHeight * 4) {
17
+ ctx.pixels = new Uint8Array(drawnWidth * drawnHeight * 4);
18
+ }
19
+ if (ctx.topDown.length < width * height * 4) {
20
+ ctx.topDown = new Uint8Array(width * height * 4);
21
+ }
22
+
23
+ // GL's rows run bottom-up, so the top-left region starts height rows below the top.
24
+ gl.readPixels(
25
+ 0,
26
+ ctx.drawnHeight - drawnHeight,
27
+ drawnWidth,
28
+ drawnHeight,
29
+ gl.RGBA,
30
+ gl.UNSIGNED_BYTE,
31
+ ctx.pixels,
32
+ );
33
+ const row = width * 4;
34
+ if (drawnWidth === width && drawnHeight === height) {
35
+ for (let y = 0; y < height; y++) {
36
+ const from = (height - y - 1) * row;
37
+ ctx.topDown.set(ctx.pixels.subarray(from, from + row), y * row);
38
+ }
39
+
40
+ return ctx.topDown;
41
+ }
42
+
43
+ // Storage smaller than the back buffer: each pixel is the stored one it falls on.
44
+ const source = new Uint32Array(ctx.pixels.buffer, 0, drawnWidth * drawnHeight);
45
+ const target = new Uint32Array(ctx.topDown.buffer, 0, width * height);
46
+ for (let y = 0; y < height; y++) {
47
+ const from = (drawnHeight - 1 - Math.min(drawnHeight - 1, Math.floor(y * scaleY))) * drawnWidth;
48
+ for (let x = 0; x < width; x++) {
49
+ target[y * width + x] = source[from + Math.min(drawnWidth - 1, Math.floor(x * scaleX))];
50
+ }
51
+ }
52
+
53
+ return ctx.topDown;
54
+ }