@swf2es/context3d 0.1.18 → 0.1.20
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 +112 -22
- package/dist/agal.d.ts +11 -2
- package/dist/agal.d.ts.map +1 -1
- package/dist/agal.js +318 -47
- package/dist/agal.js.map +1 -1
- package/dist/backend.d.ts +80 -7
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +91 -2
- package/dist/backend.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/webgl/context.d.ts +51 -6
- package/dist/webgl/context.d.ts.map +1 -1
- package/dist/webgl/context.js +379 -71
- package/dist/webgl/context.js.map +1 -1
- package/dist/webgl/programs.d.ts +4 -1
- package/dist/webgl/programs.d.ts.map +1 -1
- package/dist/webgl/programs.js +7 -6
- package/dist/webgl/programs.js.map +1 -1
- package/dist/webgl/targets.d.ts +3 -1
- package/dist/webgl/targets.d.ts.map +1 -1
- package/dist/webgl/targets.js +5 -3
- package/dist/webgl/targets.js.map +1 -1
- package/dist/webgl/textures.d.ts +11 -1
- package/dist/webgl/textures.d.ts.map +1 -1
- package/dist/webgl/textures.js +161 -7
- package/dist/webgl/textures.js.map +1 -1
- package/package.json +1 -1
- package/src/agal.ts +375 -51
- package/src/backend.ts +164 -7
- package/src/errors.ts +27 -1
- package/src/index.ts +14 -1
- package/src/webgl/context.ts +476 -73
- package/src/webgl/programs.ts +10 -5
- package/src/webgl/targets.ts +4 -2
- package/src/webgl/textures.ts +185 -9
package/src/webgl/context.ts
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
|
-
import type
|
|
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
|
+
compressedFormat,
|
|
9
|
+
FRAGMENT_CONSTANTS,
|
|
10
|
+
MOST_BACK_BUFFER,
|
|
11
|
+
MOST_CUBE,
|
|
12
|
+
type Profile,
|
|
8
13
|
type Program3D,
|
|
14
|
+
profileLimits,
|
|
9
15
|
SAMPLER_FILTERS,
|
|
10
16
|
SAMPLER_MIPS,
|
|
17
|
+
SAMPLER_SLOTS,
|
|
11
18
|
SAMPLER_WRAPS,
|
|
12
19
|
type SamplerFilter,
|
|
13
20
|
type SamplerMip,
|
|
14
21
|
type SamplerWrap,
|
|
15
22
|
ScissorBoundsError,
|
|
16
23
|
type StencilAction,
|
|
24
|
+
screenPixels,
|
|
25
|
+
TEXEL_BYTES,
|
|
17
26
|
type Texture3D,
|
|
18
27
|
TextureBindingError,
|
|
19
28
|
type TextureFormat,
|
|
29
|
+
VERTEX_CONSTANTS,
|
|
20
30
|
VERTEX_FORMATS,
|
|
31
|
+
VERTEX_SLOTS,
|
|
21
32
|
type VertexFormat,
|
|
22
33
|
VertexOffsetError,
|
|
23
34
|
} from "../backend.js";
|
|
@@ -54,6 +65,7 @@ import {
|
|
|
54
65
|
targetFramebuffer,
|
|
55
66
|
} from "./targets.js";
|
|
56
67
|
import {
|
|
68
|
+
storageFormat,
|
|
57
69
|
type TextureState,
|
|
58
70
|
uploadCompressedLevel,
|
|
59
71
|
uploadTexture,
|
|
@@ -66,8 +78,26 @@ const SAMPLER_FORMATS: Record<TextureFormat, number> = {
|
|
|
66
78
|
bgra: 0,
|
|
67
79
|
compressed: 1,
|
|
68
80
|
compressedAlpha: 2,
|
|
81
|
+
bgrPacked565: 0,
|
|
82
|
+
bgraPacked4444: 0,
|
|
83
|
+
rgbaHalfFloat: 0,
|
|
69
84
|
};
|
|
70
85
|
|
|
86
|
+
/**
|
|
87
|
+
* A clear colour's channel as Direct3D's 8-bit clear colour holds it, which AIR fills by
|
|
88
|
+
* truncation (0.5 clears to 127): within 0 to 1 and cut to its byte, NaN kept.
|
|
89
|
+
*/
|
|
90
|
+
function clearChannel(value: number): number {
|
|
91
|
+
if (!(value > 0)) {
|
|
92
|
+
return value < 0 ? 0 : value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value >= 1 ? 1 : Math.floor(value * 255 + 1e-3) / 255;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** AGAL's wraps (clamp, repeat, clamp_u_repeat_v, repeat_u_clamp_v) as SAMPLER_WRAPS indices. */
|
|
99
|
+
const WRAPS = [0, 3, 2, 1];
|
|
100
|
+
|
|
71
101
|
interface Constants {
|
|
72
102
|
data: Float32Array;
|
|
73
103
|
bits: Uint32Array;
|
|
@@ -94,8 +124,16 @@ export class WebGLContext3D implements Context3D {
|
|
|
94
124
|
private readonly drawState: GlState;
|
|
95
125
|
width = 0;
|
|
96
126
|
height = 0;
|
|
127
|
+
/**
|
|
128
|
+
* The back buffer's storage: its size, or less where the GPU allows less. The SWF still
|
|
129
|
+
* sees the size it asked for, drawn at this one and stretched to it when presented.
|
|
130
|
+
*/
|
|
131
|
+
private drawnWidth = 0;
|
|
132
|
+
private drawnHeight = 0;
|
|
133
|
+
private gpuMost: Int32Array | null = null;
|
|
97
134
|
disposed = false;
|
|
98
135
|
samplerFormats = true;
|
|
136
|
+
profile: Profile = "baseline";
|
|
99
137
|
private readonly buffers = new Map<Buffer3D, BufferState>();
|
|
100
138
|
private vertexBuffers = 0;
|
|
101
139
|
private bufferBytes = 0;
|
|
@@ -114,7 +152,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
114
152
|
private renderTexture: Texture3D | null = null;
|
|
115
153
|
private renderDepth = false;
|
|
116
154
|
private renderFace = 0;
|
|
117
|
-
|
|
155
|
+
/** Colour outputs 1 to 3, drawn into with renderTexture, and their cube faces. */
|
|
156
|
+
private readonly outputs: (Texture3D | null)[] = [null, null, null];
|
|
157
|
+
private readonly outputFaces = new Uint8Array(3);
|
|
158
|
+
private outputsChanged = false;
|
|
159
|
+
/** Whether any of outputs is bound, so that a draw alone into its target looks at none. */
|
|
160
|
+
private outputsBound = false;
|
|
161
|
+
private floatTargets: boolean | undefined;
|
|
162
|
+
private readonly textureSlots: (Texture3D | null)[] = new Array(SAMPLER_SLOTS).fill(null);
|
|
118
163
|
private textureMask = 0;
|
|
119
164
|
private readonly samplers: (WebGLSampler | null)[] = new Array(96).fill(null);
|
|
120
165
|
// Two wrap bits, three filter bits and two mip bits fit each slot in one byte.
|
|
@@ -122,10 +167,10 @@ export class WebGLContext3D implements Context3D {
|
|
|
122
167
|
private anisotropy: EXT_texture_filter_anisotropic | null | undefined;
|
|
123
168
|
private s3tc: WEBGL_compressed_texture_s3tc | null | undefined;
|
|
124
169
|
private maxAnisotropy = 1;
|
|
125
|
-
private readonly vertices: (VertexBinding | null)[] = new Array(
|
|
170
|
+
private readonly vertices: (VertexBinding | null)[] = new Array(VERTEX_SLOTS).fill(null);
|
|
126
171
|
private enabledAttributes = 0;
|
|
127
172
|
private indexBuffer: WebGLBuffer | null = null;
|
|
128
|
-
private readonly texturesApplied = new Uint8Array(
|
|
173
|
+
private readonly texturesApplied = new Uint8Array(SAMPLER_SLOTS);
|
|
129
174
|
private selected: Program3D | null = null;
|
|
130
175
|
private framebuffer: WebGLFramebuffer | null = null;
|
|
131
176
|
private texture: WebGLTexture | null = null;
|
|
@@ -134,6 +179,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
134
179
|
private resolveDirty = false;
|
|
135
180
|
private depthBuffer: WebGLRenderbuffer | null = null;
|
|
136
181
|
private antiAlias = 0;
|
|
182
|
+
/** The back buffer's pixels to a CSS pixel and to a scissor rectangle's unit. */
|
|
183
|
+
private scale = 1;
|
|
137
184
|
private depthWrite = true;
|
|
138
185
|
private depthCompare: CompareMode = "less";
|
|
139
186
|
private blendEnabled = false;
|
|
@@ -153,8 +200,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
153
200
|
private scissorRight = 0;
|
|
154
201
|
private scissorBottom = 0;
|
|
155
202
|
private scissorOutside = false;
|
|
156
|
-
private readonly vertexConstants = constants(
|
|
157
|
-
private readonly fragmentConstants = constants(
|
|
203
|
+
private readonly vertexConstants = constants(VERTEX_CONSTANTS);
|
|
204
|
+
private readonly fragmentConstants = constants(FRAGMENT_CONSTANTS);
|
|
158
205
|
private pixels = new Uint8Array(0);
|
|
159
206
|
private topDown = new Uint8Array(0);
|
|
160
207
|
private readonly vao: WebGLVertexArrayObject;
|
|
@@ -225,7 +272,15 @@ export class WebGLContext3D implements Context3D {
|
|
|
225
272
|
}
|
|
226
273
|
}
|
|
227
274
|
this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
|
|
228
|
-
this.
|
|
275
|
+
if (this.outputsChanged || state?.outputs) {
|
|
276
|
+
this.attachOutputs(state);
|
|
277
|
+
}
|
|
278
|
+
this.drawState.viewport(
|
|
279
|
+
0,
|
|
280
|
+
0,
|
|
281
|
+
texture?.width ?? this.drawnWidth,
|
|
282
|
+
texture?.height ?? this.drawnHeight,
|
|
283
|
+
);
|
|
229
284
|
}
|
|
230
285
|
|
|
231
286
|
/** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
|
|
@@ -234,10 +289,10 @@ export class WebGLContext3D implements Context3D {
|
|
|
234
289
|
this.gl.bindFramebuffer(target, framebuffer);
|
|
235
290
|
}
|
|
236
291
|
|
|
237
|
-
configure(width: number, height: number, depth = false, antiAlias = 0): void {
|
|
292
|
+
configure(width: number, height: number, depth = false, antiAlias = 0, scale = 1): void {
|
|
238
293
|
this.alive();
|
|
239
|
-
range(32, width - 32,
|
|
240
|
-
range(32, height - 32,
|
|
294
|
+
range(32, width - 32, MOST_BACK_BUFFER);
|
|
295
|
+
range(32, height - 32, MOST_BACK_BUFFER);
|
|
241
296
|
if (this.renderTexture) {
|
|
242
297
|
throw new Context3DError(
|
|
243
298
|
"operation",
|
|
@@ -254,11 +309,20 @@ export class WebGLContext3D implements Context3D {
|
|
|
254
309
|
depth === (this.depthBuffer !== null) &&
|
|
255
310
|
antiAlias === this.antiAlias
|
|
256
311
|
) {
|
|
312
|
+
if (scale !== this.scale) {
|
|
313
|
+
this.scale = scale;
|
|
314
|
+
this.showAt();
|
|
315
|
+
this.applyScissor();
|
|
316
|
+
}
|
|
317
|
+
|
|
257
318
|
return;
|
|
258
319
|
}
|
|
259
320
|
|
|
260
321
|
const samples = selectSamples(this.gl, antiAlias, depth);
|
|
261
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]);
|
|
262
326
|
const texture = gl.createTexture();
|
|
263
327
|
const framebuffer = gl.createFramebuffer();
|
|
264
328
|
const depthBuffer = depth ? gl.createRenderbuffer() : null;
|
|
@@ -279,7 +343,17 @@ export class WebGLContext3D implements Context3D {
|
|
|
279
343
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
280
344
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
281
345
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
282
|
-
gl.texImage2D(
|
|
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
|
+
);
|
|
283
357
|
this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
|
|
284
358
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
285
359
|
if (resolveFramebuffer) {
|
|
@@ -289,7 +363,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
289
363
|
|
|
290
364
|
this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
291
365
|
gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
|
|
292
|
-
gl.renderbufferStorageMultisample(
|
|
366
|
+
gl.renderbufferStorageMultisample(
|
|
367
|
+
gl.RENDERBUFFER,
|
|
368
|
+
samples,
|
|
369
|
+
gl.RGBA8,
|
|
370
|
+
drawnWidth,
|
|
371
|
+
drawnHeight,
|
|
372
|
+
);
|
|
293
373
|
gl.framebufferRenderbuffer(
|
|
294
374
|
gl.FRAMEBUFFER,
|
|
295
375
|
gl.COLOR_ATTACHMENT0,
|
|
@@ -304,11 +384,11 @@ export class WebGLContext3D implements Context3D {
|
|
|
304
384
|
gl.RENDERBUFFER,
|
|
305
385
|
samples,
|
|
306
386
|
gl.DEPTH24_STENCIL8,
|
|
307
|
-
|
|
308
|
-
|
|
387
|
+
drawnWidth,
|
|
388
|
+
drawnHeight,
|
|
309
389
|
);
|
|
310
390
|
} else {
|
|
311
|
-
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8,
|
|
391
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
|
|
312
392
|
}
|
|
313
393
|
gl.framebufferRenderbuffer(
|
|
314
394
|
gl.FRAMEBUFFER,
|
|
@@ -345,14 +425,81 @@ export class WebGLContext3D implements Context3D {
|
|
|
345
425
|
this.antiAlias = antiAlias;
|
|
346
426
|
this.width = width;
|
|
347
427
|
this.height = height;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.
|
|
351
|
-
|
|
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);
|
|
352
437
|
this.applyScissor();
|
|
353
438
|
this.clear(0, 0, 0, 1);
|
|
354
439
|
}
|
|
355
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;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
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);
|
|
501
|
+
}
|
|
502
|
+
|
|
356
503
|
clear(
|
|
357
504
|
red: number,
|
|
358
505
|
green: number,
|
|
@@ -366,7 +513,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
366
513
|
const gl = this.gl;
|
|
367
514
|
let bits = 0;
|
|
368
515
|
if (mask & 1) {
|
|
369
|
-
|
|
516
|
+
// A float target too: AIR clears through Direct3D's 8-bit colour.
|
|
517
|
+
gl.clearColor(
|
|
518
|
+
clearChannel(red),
|
|
519
|
+
clearChannel(green),
|
|
520
|
+
clearChannel(blue),
|
|
521
|
+
clearChannel(alpha),
|
|
522
|
+
);
|
|
370
523
|
bits |= gl.COLOR_BUFFER_BIT;
|
|
371
524
|
this.changedTarget();
|
|
372
525
|
}
|
|
@@ -565,15 +718,36 @@ export class WebGLContext3D implements Context3D {
|
|
|
565
718
|
|
|
566
719
|
const width = this.renderTexture?.width ?? this.width;
|
|
567
720
|
const height = this.renderTexture?.height ?? this.height;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
721
|
+
let left = Math.max(0, Math.min(width, this.scissorLeft));
|
|
722
|
+
let top = Math.max(0, Math.min(height, this.scissorTop));
|
|
723
|
+
let right = Math.max(0, Math.min(width, this.scissorRight));
|
|
724
|
+
let bottom = Math.max(0, Math.min(height, this.scissorBottom));
|
|
725
|
+
// AIR tells a rectangle outside from its own unit, then scales it to a back buffer
|
|
726
|
+
// configured for best resolution, whose pixels are the screen's.
|
|
572
727
|
this.scissorOutside = right <= left || bottom <= top;
|
|
728
|
+
if (!this.renderTexture && this.scale !== 1) {
|
|
729
|
+
const scale = this.scale;
|
|
730
|
+
left = Math.max(0, Math.min(width, screenPixels(this.scissorLeft, scale)));
|
|
731
|
+
top = Math.max(0, Math.min(height, screenPixels(this.scissorTop, scale)));
|
|
732
|
+
right = Math.max(0, Math.min(width, screenPixels(this.scissorRight, scale)));
|
|
733
|
+
bottom = Math.max(0, Math.min(height, screenPixels(this.scissorBottom, scale)));
|
|
734
|
+
}
|
|
735
|
+
let drawnHeight = height;
|
|
736
|
+
if (!this.renderTexture && (this.drawnWidth !== width || this.drawnHeight !== height)) {
|
|
737
|
+
// The back buffer's storage is smaller than its size: the rectangle shrinks with it.
|
|
738
|
+
const x = this.drawnWidth / width;
|
|
739
|
+
const y = this.drawnHeight / height;
|
|
740
|
+
left = Math.round(left * x);
|
|
741
|
+
right = Math.round(right * x);
|
|
742
|
+
top = Math.round(top * y);
|
|
743
|
+
bottom = Math.round(bottom * y);
|
|
744
|
+
drawnHeight = this.drawnHeight;
|
|
745
|
+
}
|
|
746
|
+
|
|
573
747
|
gl.enable(gl.SCISSOR_TEST);
|
|
574
748
|
gl.scissor(
|
|
575
749
|
left,
|
|
576
|
-
this.renderTexture ? top :
|
|
750
|
+
this.renderTexture ? top : drawnHeight - bottom,
|
|
577
751
|
Math.max(0, right - left),
|
|
578
752
|
Math.max(0, bottom - top),
|
|
579
753
|
);
|
|
@@ -630,7 +804,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
630
804
|
}
|
|
631
805
|
}
|
|
632
806
|
|
|
633
|
-
createBuffer(count: number, stride: number): Buffer3D {
|
|
807
|
+
createBuffer(count: number, stride: number, dynamic = false): Buffer3D {
|
|
634
808
|
this.alive();
|
|
635
809
|
range(1, count - 1, stride === 0 ? 524287 : 65535);
|
|
636
810
|
range(0, stride, 64);
|
|
@@ -661,7 +835,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
661
835
|
}
|
|
662
836
|
gl.bindVertexArray(this.vao);
|
|
663
837
|
gl.bindBuffer(target, handle);
|
|
664
|
-
gl.bufferData(target, bytes, gl.STATIC_DRAW);
|
|
838
|
+
gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
|
|
665
839
|
const state: BufferState = {
|
|
666
840
|
handle,
|
|
667
841
|
indices: stride === 0 ? new Uint16Array(count) : null,
|
|
@@ -762,6 +936,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
762
936
|
attributeMask: 0,
|
|
763
937
|
vertex: [],
|
|
764
938
|
vertexBank: null,
|
|
939
|
+
outputs: 1,
|
|
765
940
|
fragment: [],
|
|
766
941
|
samplers: [],
|
|
767
942
|
samplerMask: 0,
|
|
@@ -773,11 +948,17 @@ export class WebGLContext3D implements Context3D {
|
|
|
773
948
|
vertexVersion: -1,
|
|
774
949
|
};
|
|
775
950
|
const program: Program3D = {
|
|
951
|
+
get outputs() {
|
|
952
|
+
return state.outputs;
|
|
953
|
+
},
|
|
954
|
+
get attributes() {
|
|
955
|
+
return state.attributeMask;
|
|
956
|
+
},
|
|
776
957
|
upload: (vertex, fragment) => {
|
|
777
958
|
this.alive();
|
|
778
959
|
this.program(program);
|
|
779
960
|
this.drawState.forget();
|
|
780
|
-
uploadProgram(this.gl, state, vertex, fragment);
|
|
961
|
+
uploadProgram(this.gl, state, vertex, fragment, this.profile);
|
|
781
962
|
|
|
782
963
|
if (this.selected === program) {
|
|
783
964
|
this.setProgram(program);
|
|
@@ -834,12 +1015,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
834
1015
|
height: number,
|
|
835
1016
|
cube = false,
|
|
836
1017
|
format: TextureFormat = "bgra",
|
|
1018
|
+
streaming = 0,
|
|
837
1019
|
): Texture3D {
|
|
838
|
-
return this.allocateTexture(width, height, cube, false, format);
|
|
1020
|
+
return this.allocateTexture(width, height, cube, false, format, streaming);
|
|
839
1021
|
}
|
|
840
1022
|
|
|
841
|
-
createRectangleTexture(width: number, height: number): Texture3D {
|
|
842
|
-
return this.allocateTexture(width, height, false, true,
|
|
1023
|
+
createRectangleTexture(width: number, height: number, format: TextureFormat = "bgra"): Texture3D {
|
|
1024
|
+
return this.allocateTexture(width, height, false, true, format);
|
|
843
1025
|
}
|
|
844
1026
|
|
|
845
1027
|
/** GL's internal format for a compressed texture's S3TC, or 0 where the context takes none. */
|
|
@@ -847,7 +1029,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
847
1029
|
if (this.s3tc === undefined) {
|
|
848
1030
|
this.s3tc = this.gl.getExtension("WEBGL_compressed_texture_s3tc");
|
|
849
1031
|
}
|
|
850
|
-
if (!this.s3tc || format
|
|
1032
|
+
if (!this.s3tc || !compressedFormat(format)) {
|
|
851
1033
|
return 0;
|
|
852
1034
|
}
|
|
853
1035
|
|
|
@@ -873,7 +1055,9 @@ export class WebGLContext3D implements Context3D {
|
|
|
873
1055
|
for (let level = 0; level < levels; level++) {
|
|
874
1056
|
const w = Math.max(1, width >> level);
|
|
875
1057
|
const h = Math.max(1, height >> level);
|
|
876
|
-
size +=
|
|
1058
|
+
size += compressedFormat(format)
|
|
1059
|
+
? blockBytes(w, h, format === "compressedAlpha")
|
|
1060
|
+
: w * h * (TEXEL_BYTES[format] ?? 4);
|
|
877
1061
|
}
|
|
878
1062
|
|
|
879
1063
|
return size * faces;
|
|
@@ -885,34 +1069,38 @@ export class WebGLContext3D implements Context3D {
|
|
|
885
1069
|
cube: boolean,
|
|
886
1070
|
rectangle: boolean,
|
|
887
1071
|
format: TextureFormat,
|
|
1072
|
+
streaming = 0,
|
|
888
1073
|
): Texture3D {
|
|
889
1074
|
this.alive();
|
|
890
|
-
|
|
891
|
-
|
|
1075
|
+
const limits = profileLimits(this.profile);
|
|
1076
|
+
const most = rectangle ? limits.rectangleSize : limits.textureSize;
|
|
1077
|
+
range(1, width - 1, most);
|
|
1078
|
+
range(1, height - 1, most);
|
|
892
1079
|
if (cube && width !== height) {
|
|
893
1080
|
throw new Context3DError("textureShape", "Context3D cube faces must be square");
|
|
894
1081
|
}
|
|
895
|
-
if (cube && width >
|
|
1082
|
+
if (cube && width > MOST_CUBE) {
|
|
896
1083
|
throw new Context3DError("textureSize", "Context3D cube faces must be at most 1024");
|
|
897
1084
|
}
|
|
898
1085
|
|
|
899
1086
|
const faces = cube ? 6 : 1;
|
|
900
1087
|
const levels = rectangle ? 1 : 1 + Math.floor(Math.log2(Math.max(width, height)));
|
|
1088
|
+
range(streaming, 1, levels);
|
|
901
1089
|
if (!rectangle && ((width & (width - 1)) !== 0 || (height & (height - 1)) !== 0)) {
|
|
902
1090
|
throw new Context3DError(
|
|
903
1091
|
"texturePowerOfTwo",
|
|
904
1092
|
"Context3D texture size must be a power of two",
|
|
905
1093
|
);
|
|
906
1094
|
}
|
|
907
|
-
if (format
|
|
1095
|
+
if (compressedFormat(format) && rectangle) {
|
|
908
1096
|
throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
|
|
909
1097
|
}
|
|
910
|
-
if (format
|
|
1098
|
+
if (compressedFormat(format) && (width < 4 || height < 4)) {
|
|
911
1099
|
throw new Context3DError("compressedSize", "Context3D compressed textures are at least 4x4");
|
|
912
1100
|
}
|
|
913
1101
|
|
|
914
1102
|
const gl = this.gl;
|
|
915
|
-
const storage = this.s3tcFormat(format) || gl
|
|
1103
|
+
const storage = this.s3tcFormat(format) || storageFormat(gl, format);
|
|
916
1104
|
const size = this.chainBytes(width, height, levels, faces, format);
|
|
917
1105
|
const kind = cube ? 2 : rectangle ? 1 : 0;
|
|
918
1106
|
const countLimit = cube
|
|
@@ -928,7 +1116,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
928
1116
|
// AIR reserves half the base size for mips, independently of the exact
|
|
929
1117
|
// chain storage, and charges DXT1 an eighth of BGRA and DXT5 a quarter.
|
|
930
1118
|
const quotaBytes =
|
|
931
|
-
(width * height * faces * (rectangle ? 4 : 6)) /
|
|
1119
|
+
(width * height * faces * (rectangle ? 4 : 6) * (TEXEL_BYTES[format] ?? 4)) /
|
|
1120
|
+
4 /
|
|
932
1121
|
(format === "compressed" ? 8 : format === "compressedAlpha" ? 4 : 1);
|
|
933
1122
|
if (
|
|
934
1123
|
this.textures.size >= this.limits.maxTextures ||
|
|
@@ -963,6 +1152,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
963
1152
|
multisample: null,
|
|
964
1153
|
samples: 0,
|
|
965
1154
|
antiAlias: 0,
|
|
1155
|
+
outputs: 0,
|
|
1156
|
+
base: 0,
|
|
966
1157
|
};
|
|
967
1158
|
const texture: Texture3D = {
|
|
968
1159
|
width,
|
|
@@ -971,6 +1162,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
971
1162
|
cube,
|
|
972
1163
|
rectangle,
|
|
973
1164
|
format,
|
|
1165
|
+
streaming,
|
|
974
1166
|
upload: (pixels, level = 0, face = 0, sourceWidth, sourceHeight) => {
|
|
975
1167
|
this.alive();
|
|
976
1168
|
this.textureState(texture);
|
|
@@ -1003,10 +1195,10 @@ export class WebGLContext3D implements Context3D {
|
|
|
1003
1195
|
uploadCompressed: (blocks, level = 0, face = 0) => {
|
|
1004
1196
|
this.alive();
|
|
1005
1197
|
this.textureState(texture);
|
|
1006
|
-
if (format
|
|
1198
|
+
if (!compressedFormat(format)) {
|
|
1007
1199
|
throw new Context3DError(
|
|
1008
1200
|
"textureUpload",
|
|
1009
|
-
"Context3D experiment:
|
|
1201
|
+
"Context3D experiment: uncompressed texture given blocks",
|
|
1010
1202
|
);
|
|
1011
1203
|
}
|
|
1012
1204
|
|
|
@@ -1046,6 +1238,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
1046
1238
|
state.mipFramebuffer = null;
|
|
1047
1239
|
state.depthBuffer = null;
|
|
1048
1240
|
|
|
1241
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1242
|
+
if (this.outputs[i] === texture) {
|
|
1243
|
+
this.outputs[i] = null;
|
|
1244
|
+
this.outputsChanged = true;
|
|
1245
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1049
1248
|
for (let i = 0; i < this.textureSlots.length; i++) {
|
|
1050
1249
|
if (this.textureSlots[i] === texture) {
|
|
1051
1250
|
this.texturesApplied[i] = 0;
|
|
@@ -1201,6 +1400,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1201
1400
|
depth,
|
|
1202
1401
|
this.depthWrite,
|
|
1203
1402
|
this.stencilWrite,
|
|
1403
|
+
state.storage,
|
|
1204
1404
|
);
|
|
1205
1405
|
if (same) {
|
|
1206
1406
|
if (newDepth) {
|
|
@@ -1236,12 +1436,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
1236
1436
|
const gl = this.gl;
|
|
1237
1437
|
if (texture) {
|
|
1238
1438
|
this.textureState(texture);
|
|
1239
|
-
if (texture.format
|
|
1439
|
+
if (compressedFormat(texture.format)) {
|
|
1240
1440
|
throw new Context3DError(
|
|
1241
1441
|
"operation",
|
|
1242
1442
|
"Context3D experiment: compressed textures are no render targets",
|
|
1243
1443
|
);
|
|
1244
1444
|
}
|
|
1445
|
+
this.renderFormat(texture.format);
|
|
1245
1446
|
}
|
|
1246
1447
|
const samples = texture ? this.multisamples(antiAlias, depth) : 0;
|
|
1247
1448
|
this.finishTarget();
|
|
@@ -1387,6 +1588,12 @@ export class WebGLContext3D implements Context3D {
|
|
|
1387
1588
|
}
|
|
1388
1589
|
|
|
1389
1590
|
private changedTarget(): void {
|
|
1591
|
+
for (let i = 0; this.outputsBound && i < this.outputs.length; i++) {
|
|
1592
|
+
const output = this.outputs[i];
|
|
1593
|
+
if (output) {
|
|
1594
|
+
this.textureState(output).dirty = true;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1390
1597
|
if (this.renderTexture) {
|
|
1391
1598
|
const state = this.textureState(this.renderTexture);
|
|
1392
1599
|
state.dirty = true;
|
|
@@ -1403,22 +1610,34 @@ export class WebGLContext3D implements Context3D {
|
|
|
1403
1610
|
}
|
|
1404
1611
|
|
|
1405
1612
|
private finishTarget(): void {
|
|
1406
|
-
if (
|
|
1407
|
-
|
|
1613
|
+
if (this.renderTexture) {
|
|
1614
|
+
this.finishTexture(this.renderTexture, this.renderFace);
|
|
1408
1615
|
}
|
|
1409
|
-
|
|
1616
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1617
|
+
const output = this.outputs[i];
|
|
1618
|
+
if (output) {
|
|
1619
|
+
this.finishTexture(output, this.outputFaces[i]);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
/** A texture drawn into made whole again: resolved, and its mips regenerated. */
|
|
1625
|
+
private finishTexture(texture: Texture3D, renderFace: number): void {
|
|
1410
1626
|
const state = this.textureState(texture);
|
|
1411
1627
|
if (state.dirty) {
|
|
1412
1628
|
const gl = this.gl;
|
|
1413
1629
|
this.drawState.forget();
|
|
1414
1630
|
this.texturesApplied[0] = 0;
|
|
1415
|
-
resolveTarget(this.gl, texture, state,
|
|
1631
|
+
resolveTarget(this.gl, texture, state, renderFace, this.scissor);
|
|
1416
1632
|
gl.activeTexture(gl.TEXTURE0);
|
|
1417
1633
|
if (texture.cube) {
|
|
1634
|
+
// An extra colour output may never have been a target of its own.
|
|
1635
|
+
state.framebuffer ??= gl.createFramebuffer();
|
|
1636
|
+
state.mipFramebuffer ??= gl.createFramebuffer();
|
|
1418
1637
|
// generateMipmap(CUBE_MAP) would overwrite manually uploaded mips on
|
|
1419
1638
|
// untouched faces. AIR regenerates only the face just rendered.
|
|
1420
1639
|
gl.disable(gl.SCISSOR_TEST);
|
|
1421
|
-
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X +
|
|
1640
|
+
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderFace;
|
|
1422
1641
|
// The base-size depth attachment cannot accompany smaller mip images.
|
|
1423
1642
|
this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
1424
1643
|
if (state.depthAttached) {
|
|
@@ -1480,14 +1699,99 @@ export class WebGLContext3D implements Context3D {
|
|
|
1480
1699
|
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
1481
1700
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
1482
1701
|
}
|
|
1483
|
-
state.uploaded[
|
|
1702
|
+
state.uploaded[renderFace] = (1 << texture.levels) - 1;
|
|
1484
1703
|
state.dirty = false;
|
|
1485
1704
|
}
|
|
1486
1705
|
}
|
|
1487
1706
|
|
|
1707
|
+
/** Half floats are drawn into where the GPU's WebGL renders float colour, as every desktop one does. */
|
|
1708
|
+
private renderFormat(format: TextureFormat): void {
|
|
1709
|
+
if (format !== "rgbaHalfFloat") {
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
this.floatTargets ??= this.gl.getExtension("EXT_color_buffer_float") !== null;
|
|
1714
|
+
if (!this.floatTargets) {
|
|
1715
|
+
throw new Context3DError("unsupported", "Context3D: this GPU draws into no half floats");
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
setColorOutput(index: number, texture: Texture3D | null, face = 0): void {
|
|
1720
|
+
this.alive();
|
|
1721
|
+
range(index - 1, 1, this.outputs.length);
|
|
1722
|
+
if (texture) {
|
|
1723
|
+
this.textureState(texture);
|
|
1724
|
+
range(face, 1, texture.cube ? 6 : 1);
|
|
1725
|
+
if (compressedFormat(texture.format)) {
|
|
1726
|
+
throw new Context3DError(
|
|
1727
|
+
"operation",
|
|
1728
|
+
"Context3D experiment: compressed textures are no render targets",
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
this.renderFormat(texture.format);
|
|
1732
|
+
}
|
|
1733
|
+
if (this.outputs[index - 1] === texture && this.outputFaces[index - 1] === face) {
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
const previous = this.outputs[index - 1];
|
|
1738
|
+
if (previous) {
|
|
1739
|
+
this.finishTexture(previous, this.outputFaces[index - 1]);
|
|
1740
|
+
}
|
|
1741
|
+
this.outputs[index - 1] = texture;
|
|
1742
|
+
this.outputFaces[index - 1] = texture ? face : 0;
|
|
1743
|
+
this.outputsChanged = true;
|
|
1744
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
/**
|
|
1748
|
+
* The current target's framebuffer with the extra colour outputs attached, or without
|
|
1749
|
+
* them: its draws write every output bound, and a program's unwritten ones are refused
|
|
1750
|
+
* before they draw.
|
|
1751
|
+
*/
|
|
1752
|
+
private attachOutputs(state: TextureState | null): void {
|
|
1753
|
+
const want = state ? this.outputMask() : 0;
|
|
1754
|
+
if (!state || (!this.outputsChanged && state.outputs === want)) {
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (want && state.samples) {
|
|
1758
|
+
throw new Context3DError("unsupported", "Context3D: antialiased colour outputs");
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
const gl = this.gl;
|
|
1762
|
+
const buffers: number[] = [gl.COLOR_ATTACHMENT0];
|
|
1763
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1764
|
+
const output = this.outputs[i];
|
|
1765
|
+
const handle = output ? this.textureState(output).handle : null;
|
|
1766
|
+
gl.framebufferTexture2D(
|
|
1767
|
+
gl.FRAMEBUFFER,
|
|
1768
|
+
gl.COLOR_ATTACHMENT1 + i,
|
|
1769
|
+
output?.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.outputFaces[i] : gl.TEXTURE_2D,
|
|
1770
|
+
handle,
|
|
1771
|
+
0,
|
|
1772
|
+
);
|
|
1773
|
+
buffers.push(output ? gl.COLOR_ATTACHMENT1 + i : gl.NONE);
|
|
1774
|
+
}
|
|
1775
|
+
gl.drawBuffers(buffers);
|
|
1776
|
+
state.outputs = want;
|
|
1777
|
+
this.outputsChanged = false;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
/** The extra colour outputs bound, a bit each from output 1. */
|
|
1781
|
+
private outputMask(): number {
|
|
1782
|
+
let mask = 0;
|
|
1783
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1784
|
+
if (this.outputs[i]) {
|
|
1785
|
+
mask |= 1 << i;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
return mask;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1488
1792
|
setTexture(index: number, texture: Texture3D | null): void {
|
|
1489
1793
|
this.alive();
|
|
1490
|
-
range(index, 1,
|
|
1794
|
+
range(index, 1, SAMPLER_SLOTS);
|
|
1491
1795
|
if (texture && !this.textureOwners.has(texture)) {
|
|
1492
1796
|
throw new Context3DError("disposed", "Context3D experiment: texture from another context");
|
|
1493
1797
|
}
|
|
@@ -1592,7 +1896,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1592
1896
|
if (!source.ignore) {
|
|
1593
1897
|
this.setSamplerState(
|
|
1594
1898
|
source.index,
|
|
1595
|
-
|
|
1899
|
+
WRAPS[source.wrap] | (source.filter << 2) | (source.mip << 5),
|
|
1596
1900
|
);
|
|
1597
1901
|
}
|
|
1598
1902
|
}
|
|
@@ -1600,7 +1904,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1600
1904
|
|
|
1601
1905
|
setVertex(index: number, buffer: Buffer3D | null, offset: number, format: VertexFormat): void {
|
|
1602
1906
|
this.alive();
|
|
1603
|
-
range(index, 1,
|
|
1907
|
+
range(index, 1, VERTEX_SLOTS);
|
|
1604
1908
|
if (buffer === null) {
|
|
1605
1909
|
this.vertices[index] = null;
|
|
1606
1910
|
return;
|
|
@@ -1723,13 +2027,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
1723
2027
|
|
|
1724
2028
|
const gl = this.gl;
|
|
1725
2029
|
this.drawState.bindVertexArray(this.vao);
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
2030
|
+
// The streams enabled that the program does not read, highest first.
|
|
2031
|
+
let stale = this.enabledAttributes & ~state.attributeMask;
|
|
2032
|
+
while (stale) {
|
|
2033
|
+
const i = 31 - Math.clz32(stale);
|
|
2034
|
+
gl.disableVertexAttribArray(i);
|
|
2035
|
+
stale &= ~(1 << i);
|
|
1732
2036
|
}
|
|
2037
|
+
this.enabledAttributes &= state.attributeMask;
|
|
1733
2038
|
|
|
1734
2039
|
for (let i = 0; i < state.attributes.length; i++) {
|
|
1735
2040
|
const slot = state.attributes[i];
|
|
@@ -1775,7 +2080,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
1775
2080
|
const binding = state.samplers[i];
|
|
1776
2081
|
const slot = binding.source.index;
|
|
1777
2082
|
const texture = this.textureSlots[slot];
|
|
1778
|
-
if (
|
|
2083
|
+
if (
|
|
2084
|
+
texture &&
|
|
2085
|
+
(texture === this.renderTexture ||
|
|
2086
|
+
(this.outputsBound &&
|
|
2087
|
+
(texture === this.outputs[0] ||
|
|
2088
|
+
texture === this.outputs[1] ||
|
|
2089
|
+
texture === this.outputs[2])))
|
|
2090
|
+
) {
|
|
1779
2091
|
throw new TextureBindingError("feedback");
|
|
1780
2092
|
}
|
|
1781
2093
|
const resource = texture && this.textures.get(texture);
|
|
@@ -1800,7 +2112,10 @@ export class WebGLContext3D implements Context3D {
|
|
|
1800
2112
|
throw new TextureBindingError("unuploaded");
|
|
1801
2113
|
}
|
|
1802
2114
|
}
|
|
1803
|
-
|
|
2115
|
+
const base = texture.streaming
|
|
2116
|
+
? this.streamingBase(texture, resource, this.samplerStates[slot] >>> 5 !== 0)
|
|
2117
|
+
: 0;
|
|
2118
|
+
for (let face = 0; !texture.streaming && face < resource.uploaded.length; face++) {
|
|
1804
2119
|
if (!texture.cube && !(resource.uploaded[face] & 1)) {
|
|
1805
2120
|
throw new TextureBindingError("unuploaded");
|
|
1806
2121
|
}
|
|
@@ -1823,6 +2138,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
1823
2138
|
gl.bindSampler(slot, this.sampler(this.samplerStates[slot]));
|
|
1824
2139
|
this.texturesApplied[slot] = 1;
|
|
1825
2140
|
}
|
|
2141
|
+
if (resource.base !== base) {
|
|
2142
|
+
const target = texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
|
|
2143
|
+
gl.activeTexture(gl.TEXTURE0 + slot);
|
|
2144
|
+
gl.bindTexture(target, resource.handle);
|
|
2145
|
+
gl.texParameteri(target, gl.TEXTURE_BASE_LEVEL, base);
|
|
2146
|
+
resource.base = base;
|
|
2147
|
+
}
|
|
1826
2148
|
if (!state.samplersSet) {
|
|
1827
2149
|
gl.uniform1i(binding.location, slot);
|
|
1828
2150
|
}
|
|
@@ -1832,10 +2154,12 @@ export class WebGLContext3D implements Context3D {
|
|
|
1832
2154
|
|
|
1833
2155
|
if (state.vertexBank) {
|
|
1834
2156
|
if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
|
|
1835
|
-
gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
|
|
2157
|
+
gl.uniform4fv(state.vertexBank, this.vertexConstants.data, 0, INDIRECT_BANK * 4);
|
|
1836
2158
|
state.vertexVersion = this.vertexConstants.version;
|
|
1837
2159
|
}
|
|
1838
|
-
}
|
|
2160
|
+
}
|
|
2161
|
+
// An indirect program's own uniforms are only the registers past its bank.
|
|
2162
|
+
if (state.vertex.length) {
|
|
1839
2163
|
this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions, state.vertexValues);
|
|
1840
2164
|
}
|
|
1841
2165
|
this.uniforms(
|
|
@@ -1852,6 +2176,50 @@ export class WebGLContext3D implements Context3D {
|
|
|
1852
2176
|
this.changedTarget();
|
|
1853
2177
|
}
|
|
1854
2178
|
|
|
2179
|
+
/**
|
|
2180
|
+
* The level a streaming texture is drawn from, as AIR 51 draws one: with mip filtering, the
|
|
2181
|
+
* finest whose levels down to the smallest are uploaded on every face, which must reach its
|
|
2182
|
+
* streaming level (#3700 before); without, its level 0 alone (#3713), as a texture that
|
|
2183
|
+
* does not stream.
|
|
2184
|
+
*/
|
|
2185
|
+
private streamingBase(texture: Texture3D, resource: TextureState, mip: boolean): number {
|
|
2186
|
+
const faces = resource.uploaded.length;
|
|
2187
|
+
let uploaded = 0;
|
|
2188
|
+
for (let face = 0; face < faces; face++) {
|
|
2189
|
+
uploaded |= resource.uploaded[face];
|
|
2190
|
+
}
|
|
2191
|
+
if (!uploaded) {
|
|
2192
|
+
throw new TextureBindingError("unuploaded");
|
|
2193
|
+
}
|
|
2194
|
+
if (!mip) {
|
|
2195
|
+
for (let face = 0; face < faces; face++) {
|
|
2196
|
+
if (!(resource.uploaded[face] & 1)) {
|
|
2197
|
+
throw new TextureBindingError("streaming");
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
return 0;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
let base = texture.levels;
|
|
2205
|
+
while (base > 0) {
|
|
2206
|
+
let whole = true;
|
|
2207
|
+
for (let face = 0; face < faces; face++) {
|
|
2208
|
+
whole &&= (resource.uploaded[face] & (1 << (base - 1))) !== 0;
|
|
2209
|
+
}
|
|
2210
|
+
if (!whole) {
|
|
2211
|
+
break;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
base--;
|
|
2215
|
+
}
|
|
2216
|
+
if (base > texture.streaming) {
|
|
2217
|
+
throw new TextureBindingError("mipmaps");
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
return base;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
1855
2223
|
private uniforms(
|
|
1856
2224
|
locations: (WebGLUniformLocation | null)[],
|
|
1857
2225
|
bank: Constants,
|
|
@@ -1899,12 +2267,12 @@ export class WebGLContext3D implements Context3D {
|
|
|
1899
2267
|
gl.blitFramebuffer(
|
|
1900
2268
|
0,
|
|
1901
2269
|
0,
|
|
1902
|
-
this.
|
|
1903
|
-
this.
|
|
2270
|
+
this.drawnWidth,
|
|
2271
|
+
this.drawnHeight,
|
|
1904
2272
|
0,
|
|
1905
2273
|
0,
|
|
1906
|
-
this.
|
|
1907
|
-
this.
|
|
2274
|
+
this.drawnWidth,
|
|
2275
|
+
this.drawnHeight,
|
|
1908
2276
|
gl.COLOR_BUFFER_BIT,
|
|
1909
2277
|
gl.NEAREST,
|
|
1910
2278
|
);
|
|
@@ -1922,12 +2290,46 @@ export class WebGLContext3D implements Context3D {
|
|
|
1922
2290
|
this.target(true);
|
|
1923
2291
|
this.resolve();
|
|
1924
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
|
+
|
|
1925
2304
|
// GL's rows run bottom-up, so the top-left region starts height rows below the top.
|
|
1926
|
-
gl.readPixels(
|
|
2305
|
+
gl.readPixels(
|
|
2306
|
+
0,
|
|
2307
|
+
this.drawnHeight - drawnHeight,
|
|
2308
|
+
drawnWidth,
|
|
2309
|
+
drawnHeight,
|
|
2310
|
+
gl.RGBA,
|
|
2311
|
+
gl.UNSIGNED_BYTE,
|
|
2312
|
+
this.pixels,
|
|
2313
|
+
);
|
|
1927
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);
|
|
1928
2327
|
for (let y = 0; y < height; y++) {
|
|
1929
|
-
const from =
|
|
1930
|
-
|
|
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
|
+
}
|
|
1931
2333
|
}
|
|
1932
2334
|
|
|
1933
2335
|
return this.topDown;
|
|
@@ -1942,15 +2344,16 @@ export class WebGLContext3D implements Context3D {
|
|
|
1942
2344
|
if (this.scissor) {
|
|
1943
2345
|
gl.disable(gl.SCISSOR_TEST);
|
|
1944
2346
|
}
|
|
2347
|
+
// To the drawing buffer the browser gave, which it may have made smaller than the canvas.
|
|
1945
2348
|
gl.blitFramebuffer(
|
|
1946
2349
|
0,
|
|
1947
2350
|
0,
|
|
1948
|
-
this.
|
|
1949
|
-
this.
|
|
2351
|
+
this.drawnWidth,
|
|
2352
|
+
this.drawnHeight,
|
|
1950
2353
|
0,
|
|
1951
2354
|
0,
|
|
1952
|
-
|
|
1953
|
-
|
|
2355
|
+
gl.drawingBufferWidth,
|
|
2356
|
+
gl.drawingBufferHeight,
|
|
1954
2357
|
gl.COLOR_BUFFER_BIT,
|
|
1955
2358
|
gl.NEAREST,
|
|
1956
2359
|
);
|