@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.
- package/README.md +8 -5
- package/dist/webgl/back-buffer.d.ts +6 -0
- package/dist/webgl/back-buffer.d.ts.map +1 -0
- package/dist/webgl/back-buffer.js +197 -0
- package/dist/webgl/back-buffer.js.map +1 -0
- package/dist/webgl/buffers.d.ts +2 -2
- package/dist/webgl/buffers.d.ts.map +1 -1
- package/dist/webgl/buffers.js +105 -2
- package/dist/webgl/buffers.js.map +1 -1
- package/dist/webgl/context.d.ts +5 -45
- package/dist/webgl/context.d.ts.map +1 -1
- package/dist/webgl/context.js +44 -322
- package/dist/webgl/context.js.map +1 -1
- package/dist/webgl/readback.d.ts +3 -0
- package/dist/webgl/readback.d.ts.map +1 -0
- package/dist/webgl/readback.js +40 -0
- package/dist/webgl/readback.js.map +1 -0
- package/package.json +1 -1
- package/src/webgl/back-buffer.ts +287 -0
- package/src/webgl/buffers.ts +114 -2
- package/src/webgl/context.ts +73 -451
- package/src/webgl/readback.ts +54 -0
package/src/webgl/context.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
+
/** @internal */
|
|
180
|
+
readonly vertices: (VertexBinding | null)[] = new Array(VERTEX_SLOTS).fill(null);
|
|
171
181
|
private enabledAttributes = 0;
|
|
172
|
-
|
|
173
|
-
|
|
182
|
+
/** @internal */
|
|
183
|
+
indexBuffer: WebGLBuffer | null = null;
|
|
184
|
+
/** @internal */
|
|
185
|
+
readonly texturesApplied = new Uint8Array(SAMPLER_SLOTS);
|
|
174
186
|
private selected: Program3D | null = null;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|