@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/dist/webgl/context.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INDIRECT_BANK } from "../agal.js";
|
|
2
|
+
import { compressedFormat, FRAGMENT_CONSTANTS, MOST_BACK_BUFFER, MOST_CUBE, profileLimits, SAMPLER_FILTERS, SAMPLER_MIPS, SAMPLER_SLOTS, SAMPLER_WRAPS, ScissorBoundsError, screenPixels, TEXEL_BYTES, TextureBindingError, VERTEX_CONSTANTS, VERTEX_FORMATS, VERTEX_SLOTS, VertexOffsetError, } from "../backend.js";
|
|
2
3
|
import { GlState } from "../device/state.js";
|
|
3
4
|
import { blockBytes } from "../dxt.js";
|
|
4
5
|
import { Context3DError } from "../errors.js";
|
|
@@ -7,14 +8,29 @@ import { resourceLimits } from "./limits.js";
|
|
|
7
8
|
import { createCopyProgram, uploadProgram, } from "./programs.js";
|
|
8
9
|
import { BLEND_FUNCTIONS, DEPTH_FUNCTIONS, range, STENCIL_FUNCTIONS, StencilState, } from "./state.js";
|
|
9
10
|
import { allocateMultisample, deleteMultisample, resolveTarget, selectSamples, targetFramebuffer, } from "./targets.js";
|
|
10
|
-
import { uploadCompressedLevel, uploadTexture, uploadTextureBytes, } from "./textures.js";
|
|
11
|
+
import { storageFormat, uploadCompressedLevel, uploadTexture, uploadTextureBytes, } from "./textures.js";
|
|
11
12
|
import { validateIndices, validateVertices } from "./validation.js";
|
|
12
13
|
/** What AGAL's sampler must name to read each texture format: RGBA, DXT1 or DXT5. */
|
|
13
14
|
const SAMPLER_FORMATS = {
|
|
14
15
|
bgra: 0,
|
|
15
16
|
compressed: 1,
|
|
16
17
|
compressedAlpha: 2,
|
|
18
|
+
bgrPacked565: 0,
|
|
19
|
+
bgraPacked4444: 0,
|
|
20
|
+
rgbaHalfFloat: 0,
|
|
17
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* A clear colour's channel as Direct3D's 8-bit clear colour holds it, which AIR fills by
|
|
24
|
+
* truncation (0.5 clears to 127): within 0 to 1 and cut to its byte, NaN kept.
|
|
25
|
+
*/
|
|
26
|
+
function clearChannel(value) {
|
|
27
|
+
if (!(value > 0)) {
|
|
28
|
+
return value < 0 ? 0 : value;
|
|
29
|
+
}
|
|
30
|
+
return value >= 1 ? 1 : Math.floor(value * 255 + 1e-3) / 255;
|
|
31
|
+
}
|
|
32
|
+
/** AGAL's wraps (clamp, repeat, clamp_u_repeat_v, repeat_u_clamp_v) as SAMPLER_WRAPS indices. */
|
|
33
|
+
const WRAPS = [0, 3, 2, 1];
|
|
18
34
|
function constants(registers) {
|
|
19
35
|
const data = new Float32Array(registers * 4);
|
|
20
36
|
return {
|
|
@@ -31,8 +47,16 @@ export class WebGLContext3D {
|
|
|
31
47
|
drawState;
|
|
32
48
|
width = 0;
|
|
33
49
|
height = 0;
|
|
50
|
+
/**
|
|
51
|
+
* The back buffer's storage: its size, or less where the GPU allows less. The SWF still
|
|
52
|
+
* sees the size it asked for, drawn at this one and stretched to it when presented.
|
|
53
|
+
*/
|
|
54
|
+
drawnWidth = 0;
|
|
55
|
+
drawnHeight = 0;
|
|
56
|
+
gpuMost = null;
|
|
34
57
|
disposed = false;
|
|
35
58
|
samplerFormats = true;
|
|
59
|
+
profile = "baseline";
|
|
36
60
|
buffers = new Map();
|
|
37
61
|
vertexBuffers = 0;
|
|
38
62
|
bufferBytes = 0;
|
|
@@ -51,7 +75,14 @@ export class WebGLContext3D {
|
|
|
51
75
|
renderTexture = null;
|
|
52
76
|
renderDepth = false;
|
|
53
77
|
renderFace = 0;
|
|
54
|
-
|
|
78
|
+
/** Colour outputs 1 to 3, drawn into with renderTexture, and their cube faces. */
|
|
79
|
+
outputs = [null, null, null];
|
|
80
|
+
outputFaces = new Uint8Array(3);
|
|
81
|
+
outputsChanged = false;
|
|
82
|
+
/** Whether any of outputs is bound, so that a draw alone into its target looks at none. */
|
|
83
|
+
outputsBound = false;
|
|
84
|
+
floatTargets;
|
|
85
|
+
textureSlots = new Array(SAMPLER_SLOTS).fill(null);
|
|
55
86
|
textureMask = 0;
|
|
56
87
|
samplers = new Array(96).fill(null);
|
|
57
88
|
// Two wrap bits, three filter bits and two mip bits fit each slot in one byte.
|
|
@@ -59,10 +90,10 @@ export class WebGLContext3D {
|
|
|
59
90
|
anisotropy;
|
|
60
91
|
s3tc;
|
|
61
92
|
maxAnisotropy = 1;
|
|
62
|
-
vertices = new Array(
|
|
93
|
+
vertices = new Array(VERTEX_SLOTS).fill(null);
|
|
63
94
|
enabledAttributes = 0;
|
|
64
95
|
indexBuffer = null;
|
|
65
|
-
texturesApplied = new Uint8Array(
|
|
96
|
+
texturesApplied = new Uint8Array(SAMPLER_SLOTS);
|
|
66
97
|
selected = null;
|
|
67
98
|
framebuffer = null;
|
|
68
99
|
texture = null;
|
|
@@ -71,6 +102,8 @@ export class WebGLContext3D {
|
|
|
71
102
|
resolveDirty = false;
|
|
72
103
|
depthBuffer = null;
|
|
73
104
|
antiAlias = 0;
|
|
105
|
+
/** The back buffer's pixels to a CSS pixel and to a scissor rectangle's unit. */
|
|
106
|
+
scale = 1;
|
|
74
107
|
depthWrite = true;
|
|
75
108
|
depthCompare = "less";
|
|
76
109
|
blendEnabled = false;
|
|
@@ -90,8 +123,8 @@ export class WebGLContext3D {
|
|
|
90
123
|
scissorRight = 0;
|
|
91
124
|
scissorBottom = 0;
|
|
92
125
|
scissorOutside = false;
|
|
93
|
-
vertexConstants = constants(
|
|
94
|
-
fragmentConstants = constants(
|
|
126
|
+
vertexConstants = constants(VERTEX_CONSTANTS);
|
|
127
|
+
fragmentConstants = constants(FRAGMENT_CONSTANTS);
|
|
95
128
|
pixels = new Uint8Array(0);
|
|
96
129
|
topDown = new Uint8Array(0);
|
|
97
130
|
vao;
|
|
@@ -150,17 +183,20 @@ export class WebGLContext3D {
|
|
|
150
183
|
}
|
|
151
184
|
}
|
|
152
185
|
this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
|
|
153
|
-
this.
|
|
186
|
+
if (this.outputsChanged || state?.outputs) {
|
|
187
|
+
this.attachOutputs(state);
|
|
188
|
+
}
|
|
189
|
+
this.drawState.viewport(0, 0, texture?.width ?? this.drawnWidth, texture?.height ?? this.drawnHeight);
|
|
154
190
|
}
|
|
155
191
|
/** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
|
|
156
192
|
bindFramebuffer(target, framebuffer) {
|
|
157
193
|
this.drawState.forget();
|
|
158
194
|
this.gl.bindFramebuffer(target, framebuffer);
|
|
159
195
|
}
|
|
160
|
-
configure(width, height, depth = false, antiAlias = 0) {
|
|
196
|
+
configure(width, height, depth = false, antiAlias = 0, scale = 1) {
|
|
161
197
|
this.alive();
|
|
162
|
-
range(32, width - 32,
|
|
163
|
-
range(32, height - 32,
|
|
198
|
+
range(32, width - 32, MOST_BACK_BUFFER);
|
|
199
|
+
range(32, height - 32, MOST_BACK_BUFFER);
|
|
164
200
|
if (this.renderTexture) {
|
|
165
201
|
throw new Context3DError("operation", "Context3D experiment: resize while rendering to texture");
|
|
166
202
|
}
|
|
@@ -171,10 +207,18 @@ export class WebGLContext3D {
|
|
|
171
207
|
height === this.height &&
|
|
172
208
|
depth === (this.depthBuffer !== null) &&
|
|
173
209
|
antiAlias === this.antiAlias) {
|
|
210
|
+
if (scale !== this.scale) {
|
|
211
|
+
this.scale = scale;
|
|
212
|
+
this.showAt();
|
|
213
|
+
this.applyScissor();
|
|
214
|
+
}
|
|
174
215
|
return;
|
|
175
216
|
}
|
|
176
217
|
const samples = selectSamples(this.gl, antiAlias, depth);
|
|
177
218
|
const gl = this.gl;
|
|
219
|
+
const most = this.storageLimit();
|
|
220
|
+
const drawnWidth = Math.min(width, most[0]);
|
|
221
|
+
const drawnHeight = Math.min(height, most[1]);
|
|
178
222
|
const texture = gl.createTexture();
|
|
179
223
|
const framebuffer = gl.createFramebuffer();
|
|
180
224
|
const depthBuffer = depth ? gl.createRenderbuffer() : null;
|
|
@@ -192,7 +236,7 @@ export class WebGLContext3D {
|
|
|
192
236
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
193
237
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
194
238
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
195
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8,
|
|
239
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, drawnWidth, drawnHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
196
240
|
this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
|
|
197
241
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
198
242
|
if (resolveFramebuffer) {
|
|
@@ -201,16 +245,16 @@ export class WebGLContext3D {
|
|
|
201
245
|
}
|
|
202
246
|
this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
203
247
|
gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
|
|
204
|
-
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8,
|
|
248
|
+
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, drawnWidth, drawnHeight);
|
|
205
249
|
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer);
|
|
206
250
|
}
|
|
207
251
|
if (depthBuffer) {
|
|
208
252
|
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
|
|
209
253
|
if (samples) {
|
|
210
|
-
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8,
|
|
254
|
+
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
|
|
211
255
|
}
|
|
212
256
|
else {
|
|
213
|
-
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8,
|
|
257
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
|
|
214
258
|
}
|
|
215
259
|
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
|
|
216
260
|
}
|
|
@@ -242,19 +286,75 @@ export class WebGLContext3D {
|
|
|
242
286
|
this.antiAlias = antiAlias;
|
|
243
287
|
this.width = width;
|
|
244
288
|
this.height = height;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.
|
|
248
|
-
|
|
289
|
+
this.drawnWidth = drawnWidth;
|
|
290
|
+
this.drawnHeight = drawnHeight;
|
|
291
|
+
this.scale = scale;
|
|
292
|
+
gl.canvas.width = drawnWidth;
|
|
293
|
+
gl.canvas.height = drawnHeight;
|
|
294
|
+
this.showAt();
|
|
295
|
+
// Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
|
|
296
|
+
this.pixels = new Uint8Array(0);
|
|
297
|
+
this.topDown = new Uint8Array(0);
|
|
249
298
|
this.applyScissor();
|
|
250
299
|
this.clear(0, 0, 0, 1);
|
|
251
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
|
|
303
|
+
* divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
|
|
304
|
+
*/
|
|
305
|
+
showAt() {
|
|
306
|
+
const style = this.gl.canvas.style;
|
|
307
|
+
if (!style) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const plain = this.scale === 1;
|
|
311
|
+
style.width = plain && this.drawnWidth === this.width ? "" : `${this.width / this.scale}px`;
|
|
312
|
+
style.height = plain && this.drawnHeight === this.height ? "" : `${this.height / this.scale}px`;
|
|
313
|
+
}
|
|
314
|
+
/** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
|
|
315
|
+
storageLimit() {
|
|
316
|
+
if (!this.gpuMost) {
|
|
317
|
+
const gl = this.gl;
|
|
318
|
+
const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
|
|
319
|
+
const side = Math.min(gl.getParameter(gl.MAX_TEXTURE_SIZE), gl.getParameter(gl.MAX_RENDERBUFFER_SIZE));
|
|
320
|
+
this.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
|
|
321
|
+
}
|
|
322
|
+
return this.gpuMost;
|
|
323
|
+
}
|
|
324
|
+
unconfigure() {
|
|
325
|
+
this.alive();
|
|
326
|
+
if (this.renderTexture) {
|
|
327
|
+
throw new Context3DError("operation", "Context3D experiment: resize while rendering to texture");
|
|
328
|
+
}
|
|
329
|
+
const gl = this.gl;
|
|
330
|
+
gl.deleteTexture(this.texture);
|
|
331
|
+
gl.deleteFramebuffer(this.framebuffer);
|
|
332
|
+
gl.deleteFramebuffer(this.resolveFramebuffer);
|
|
333
|
+
gl.deleteRenderbuffer(this.depthBuffer);
|
|
334
|
+
gl.deleteRenderbuffer(this.colorBuffer);
|
|
335
|
+
this.drawState.forget();
|
|
336
|
+
this.texture = null;
|
|
337
|
+
this.framebuffer = null;
|
|
338
|
+
this.resolveFramebuffer = null;
|
|
339
|
+
this.colorBuffer = null;
|
|
340
|
+
this.depthBuffer = null;
|
|
341
|
+
this.resolveDirty = false;
|
|
342
|
+
this.antiAlias = 0;
|
|
343
|
+
this.scale = 1;
|
|
344
|
+
this.width = 0;
|
|
345
|
+
this.height = 0;
|
|
346
|
+
this.drawnWidth = 0;
|
|
347
|
+
this.drawnHeight = 0;
|
|
348
|
+
this.pixels = new Uint8Array(0);
|
|
349
|
+
this.topDown = new Uint8Array(0);
|
|
350
|
+
}
|
|
252
351
|
clear(red, green, blue, alpha, depth = 1, mask = 0xffffffff, stencil = 0) {
|
|
253
352
|
this.target(false, (mask & 1) !== 0);
|
|
254
353
|
const gl = this.gl;
|
|
255
354
|
let bits = 0;
|
|
256
355
|
if (mask & 1) {
|
|
257
|
-
|
|
356
|
+
// A float target too: AIR clears through Direct3D's 8-bit colour.
|
|
357
|
+
gl.clearColor(clearChannel(red), clearChannel(green), clearChannel(blue), clearChannel(alpha));
|
|
258
358
|
bits |= gl.COLOR_BUFFER_BIT;
|
|
259
359
|
this.changedTarget();
|
|
260
360
|
}
|
|
@@ -401,13 +501,33 @@ export class WebGLContext3D {
|
|
|
401
501
|
}
|
|
402
502
|
const width = this.renderTexture?.width ?? this.width;
|
|
403
503
|
const height = this.renderTexture?.height ?? this.height;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
504
|
+
let left = Math.max(0, Math.min(width, this.scissorLeft));
|
|
505
|
+
let top = Math.max(0, Math.min(height, this.scissorTop));
|
|
506
|
+
let right = Math.max(0, Math.min(width, this.scissorRight));
|
|
507
|
+
let bottom = Math.max(0, Math.min(height, this.scissorBottom));
|
|
508
|
+
// AIR tells a rectangle outside from its own unit, then scales it to a back buffer
|
|
509
|
+
// configured for best resolution, whose pixels are the screen's.
|
|
408
510
|
this.scissorOutside = right <= left || bottom <= top;
|
|
511
|
+
if (!this.renderTexture && this.scale !== 1) {
|
|
512
|
+
const scale = this.scale;
|
|
513
|
+
left = Math.max(0, Math.min(width, screenPixels(this.scissorLeft, scale)));
|
|
514
|
+
top = Math.max(0, Math.min(height, screenPixels(this.scissorTop, scale)));
|
|
515
|
+
right = Math.max(0, Math.min(width, screenPixels(this.scissorRight, scale)));
|
|
516
|
+
bottom = Math.max(0, Math.min(height, screenPixels(this.scissorBottom, scale)));
|
|
517
|
+
}
|
|
518
|
+
let drawnHeight = height;
|
|
519
|
+
if (!this.renderTexture && (this.drawnWidth !== width || this.drawnHeight !== height)) {
|
|
520
|
+
// The back buffer's storage is smaller than its size: the rectangle shrinks with it.
|
|
521
|
+
const x = this.drawnWidth / width;
|
|
522
|
+
const y = this.drawnHeight / height;
|
|
523
|
+
left = Math.round(left * x);
|
|
524
|
+
right = Math.round(right * x);
|
|
525
|
+
top = Math.round(top * y);
|
|
526
|
+
bottom = Math.round(bottom * y);
|
|
527
|
+
drawnHeight = this.drawnHeight;
|
|
528
|
+
}
|
|
409
529
|
gl.enable(gl.SCISSOR_TEST);
|
|
410
|
-
gl.scissor(left, this.renderTexture ? top :
|
|
530
|
+
gl.scissor(left, this.renderTexture ? top : drawnHeight - bottom, Math.max(0, right - left), Math.max(0, bottom - top));
|
|
411
531
|
}
|
|
412
532
|
setBlend(source, destination) {
|
|
413
533
|
this.alive();
|
|
@@ -456,7 +576,7 @@ export class WebGLContext3D {
|
|
|
456
576
|
break;
|
|
457
577
|
}
|
|
458
578
|
}
|
|
459
|
-
createBuffer(count, stride) {
|
|
579
|
+
createBuffer(count, stride, dynamic = false) {
|
|
460
580
|
this.alive();
|
|
461
581
|
range(1, count - 1, stride === 0 ? 524287 : 65535);
|
|
462
582
|
range(0, stride, 64);
|
|
@@ -482,7 +602,7 @@ export class WebGLContext3D {
|
|
|
482
602
|
}
|
|
483
603
|
gl.bindVertexArray(this.vao);
|
|
484
604
|
gl.bindBuffer(target, handle);
|
|
485
|
-
gl.bufferData(target, bytes, gl.STATIC_DRAW);
|
|
605
|
+
gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
|
|
486
606
|
const state = {
|
|
487
607
|
handle,
|
|
488
608
|
indices: stride === 0 ? new Uint16Array(count) : null,
|
|
@@ -578,6 +698,7 @@ export class WebGLContext3D {
|
|
|
578
698
|
attributeMask: 0,
|
|
579
699
|
vertex: [],
|
|
580
700
|
vertexBank: null,
|
|
701
|
+
outputs: 1,
|
|
581
702
|
fragment: [],
|
|
582
703
|
samplers: [],
|
|
583
704
|
samplerMask: 0,
|
|
@@ -589,11 +710,17 @@ export class WebGLContext3D {
|
|
|
589
710
|
vertexVersion: -1,
|
|
590
711
|
};
|
|
591
712
|
const program = {
|
|
713
|
+
get outputs() {
|
|
714
|
+
return state.outputs;
|
|
715
|
+
},
|
|
716
|
+
get attributes() {
|
|
717
|
+
return state.attributeMask;
|
|
718
|
+
},
|
|
592
719
|
upload: (vertex, fragment) => {
|
|
593
720
|
this.alive();
|
|
594
721
|
this.program(program);
|
|
595
722
|
this.drawState.forget();
|
|
596
|
-
uploadProgram(this.gl, state, vertex, fragment);
|
|
723
|
+
uploadProgram(this.gl, state, vertex, fragment, this.profile);
|
|
597
724
|
if (this.selected === program) {
|
|
598
725
|
this.setProgram(program);
|
|
599
726
|
}
|
|
@@ -636,18 +763,18 @@ export class WebGLContext3D {
|
|
|
636
763
|
}
|
|
637
764
|
return state;
|
|
638
765
|
}
|
|
639
|
-
createTexture(width, height, cube = false, format = "bgra") {
|
|
640
|
-
return this.allocateTexture(width, height, cube, false, format);
|
|
766
|
+
createTexture(width, height, cube = false, format = "bgra", streaming = 0) {
|
|
767
|
+
return this.allocateTexture(width, height, cube, false, format, streaming);
|
|
641
768
|
}
|
|
642
|
-
createRectangleTexture(width, height) {
|
|
643
|
-
return this.allocateTexture(width, height, false, true,
|
|
769
|
+
createRectangleTexture(width, height, format = "bgra") {
|
|
770
|
+
return this.allocateTexture(width, height, false, true, format);
|
|
644
771
|
}
|
|
645
772
|
/** GL's internal format for a compressed texture's S3TC, or 0 where the context takes none. */
|
|
646
773
|
s3tcFormat(format) {
|
|
647
774
|
if (this.s3tc === undefined) {
|
|
648
775
|
this.s3tc = this.gl.getExtension("WEBGL_compressed_texture_s3tc");
|
|
649
776
|
}
|
|
650
|
-
if (!this.s3tc || format
|
|
777
|
+
if (!this.s3tc || !compressedFormat(format)) {
|
|
651
778
|
return 0;
|
|
652
779
|
}
|
|
653
780
|
// AIR's DXT1 has DXT1's one bit of alpha: a block of three colours has transparent black.
|
|
@@ -665,33 +792,38 @@ export class WebGLContext3D {
|
|
|
665
792
|
for (let level = 0; level < levels; level++) {
|
|
666
793
|
const w = Math.max(1, width >> level);
|
|
667
794
|
const h = Math.max(1, height >> level);
|
|
668
|
-
size +=
|
|
795
|
+
size += compressedFormat(format)
|
|
796
|
+
? blockBytes(w, h, format === "compressedAlpha")
|
|
797
|
+
: w * h * (TEXEL_BYTES[format] ?? 4);
|
|
669
798
|
}
|
|
670
799
|
return size * faces;
|
|
671
800
|
}
|
|
672
|
-
allocateTexture(width, height, cube, rectangle, format) {
|
|
801
|
+
allocateTexture(width, height, cube, rectangle, format, streaming = 0) {
|
|
673
802
|
this.alive();
|
|
674
|
-
|
|
675
|
-
|
|
803
|
+
const limits = profileLimits(this.profile);
|
|
804
|
+
const most = rectangle ? limits.rectangleSize : limits.textureSize;
|
|
805
|
+
range(1, width - 1, most);
|
|
806
|
+
range(1, height - 1, most);
|
|
676
807
|
if (cube && width !== height) {
|
|
677
808
|
throw new Context3DError("textureShape", "Context3D cube faces must be square");
|
|
678
809
|
}
|
|
679
|
-
if (cube && width >
|
|
810
|
+
if (cube && width > MOST_CUBE) {
|
|
680
811
|
throw new Context3DError("textureSize", "Context3D cube faces must be at most 1024");
|
|
681
812
|
}
|
|
682
813
|
const faces = cube ? 6 : 1;
|
|
683
814
|
const levels = rectangle ? 1 : 1 + Math.floor(Math.log2(Math.max(width, height)));
|
|
815
|
+
range(streaming, 1, levels);
|
|
684
816
|
if (!rectangle && ((width & (width - 1)) !== 0 || (height & (height - 1)) !== 0)) {
|
|
685
817
|
throw new Context3DError("texturePowerOfTwo", "Context3D texture size must be a power of two");
|
|
686
818
|
}
|
|
687
|
-
if (format
|
|
819
|
+
if (compressedFormat(format) && rectangle) {
|
|
688
820
|
throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
|
|
689
821
|
}
|
|
690
|
-
if (format
|
|
822
|
+
if (compressedFormat(format) && (width < 4 || height < 4)) {
|
|
691
823
|
throw new Context3DError("compressedSize", "Context3D compressed textures are at least 4x4");
|
|
692
824
|
}
|
|
693
825
|
const gl = this.gl;
|
|
694
|
-
const storage = this.s3tcFormat(format) || gl
|
|
826
|
+
const storage = this.s3tcFormat(format) || storageFormat(gl, format);
|
|
695
827
|
const size = this.chainBytes(width, height, levels, faces, format);
|
|
696
828
|
const kind = cube ? 2 : rectangle ? 1 : 0;
|
|
697
829
|
const countLimit = cube
|
|
@@ -706,7 +838,8 @@ export class WebGLContext3D {
|
|
|
706
838
|
: this.limits.max2DTextureBytes;
|
|
707
839
|
// AIR reserves half the base size for mips, independently of the exact
|
|
708
840
|
// chain storage, and charges DXT1 an eighth of BGRA and DXT5 a quarter.
|
|
709
|
-
const quotaBytes = (width * height * faces * (rectangle ? 4 : 6)) /
|
|
841
|
+
const quotaBytes = (width * height * faces * (rectangle ? 4 : 6) * (TEXEL_BYTES[format] ?? 4)) /
|
|
842
|
+
4 /
|
|
710
843
|
(format === "compressed" ? 8 : format === "compressedAlpha" ? 4 : 1);
|
|
711
844
|
if (this.textures.size >= this.limits.maxTextures ||
|
|
712
845
|
this.textureCounts[kind] >= countLimit ||
|
|
@@ -737,6 +870,8 @@ export class WebGLContext3D {
|
|
|
737
870
|
multisample: null,
|
|
738
871
|
samples: 0,
|
|
739
872
|
antiAlias: 0,
|
|
873
|
+
outputs: 0,
|
|
874
|
+
base: 0,
|
|
740
875
|
};
|
|
741
876
|
const texture = {
|
|
742
877
|
width,
|
|
@@ -745,6 +880,7 @@ export class WebGLContext3D {
|
|
|
745
880
|
cube,
|
|
746
881
|
rectangle,
|
|
747
882
|
format,
|
|
883
|
+
streaming,
|
|
748
884
|
upload: (pixels, level = 0, face = 0, sourceWidth, sourceHeight) => {
|
|
749
885
|
this.alive();
|
|
750
886
|
this.textureState(texture);
|
|
@@ -769,8 +905,8 @@ export class WebGLContext3D {
|
|
|
769
905
|
uploadCompressed: (blocks, level = 0, face = 0) => {
|
|
770
906
|
this.alive();
|
|
771
907
|
this.textureState(texture);
|
|
772
|
-
if (format
|
|
773
|
-
throw new Context3DError("textureUpload", "Context3D experiment:
|
|
908
|
+
if (!compressedFormat(format)) {
|
|
909
|
+
throw new Context3DError("textureUpload", "Context3D experiment: uncompressed texture given blocks");
|
|
774
910
|
}
|
|
775
911
|
try {
|
|
776
912
|
uploadCompressedLevel(gl, texture, state, blocks, level, face);
|
|
@@ -806,6 +942,13 @@ export class WebGLContext3D {
|
|
|
806
942
|
state.framebuffer = null;
|
|
807
943
|
state.mipFramebuffer = null;
|
|
808
944
|
state.depthBuffer = null;
|
|
945
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
946
|
+
if (this.outputs[i] === texture) {
|
|
947
|
+
this.outputs[i] = null;
|
|
948
|
+
this.outputsChanged = true;
|
|
949
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
809
952
|
for (let i = 0; i < this.textureSlots.length; i++) {
|
|
810
953
|
if (this.textureSlots[i] === texture) {
|
|
811
954
|
this.texturesApplied[i] = 0;
|
|
@@ -928,7 +1071,7 @@ export class WebGLContext3D {
|
|
|
928
1071
|
this.sampler(0);
|
|
929
1072
|
this.drawState.forget();
|
|
930
1073
|
this.texturesApplied[0] = 0;
|
|
931
|
-
const target = allocateMultisample(this.gl, texture, old, samples, depth, this.depthWrite, this.stencilWrite);
|
|
1074
|
+
const target = allocateMultisample(this.gl, texture, old, samples, depth, this.depthWrite, this.stencilWrite, state.storage);
|
|
932
1075
|
if (same) {
|
|
933
1076
|
if (newDepth) {
|
|
934
1077
|
this.targetDepthBytes += bytes;
|
|
@@ -960,9 +1103,10 @@ export class WebGLContext3D {
|
|
|
960
1103
|
const gl = this.gl;
|
|
961
1104
|
if (texture) {
|
|
962
1105
|
this.textureState(texture);
|
|
963
|
-
if (texture.format
|
|
1106
|
+
if (compressedFormat(texture.format)) {
|
|
964
1107
|
throw new Context3DError("operation", "Context3D experiment: compressed textures are no render targets");
|
|
965
1108
|
}
|
|
1109
|
+
this.renderFormat(texture.format);
|
|
966
1110
|
}
|
|
967
1111
|
const samples = texture ? this.multisamples(antiAlias, depth) : 0;
|
|
968
1112
|
this.finishTarget();
|
|
@@ -1060,6 +1204,12 @@ export class WebGLContext3D {
|
|
|
1060
1204
|
this.applyScissor();
|
|
1061
1205
|
}
|
|
1062
1206
|
changedTarget() {
|
|
1207
|
+
for (let i = 0; this.outputsBound && i < this.outputs.length; i++) {
|
|
1208
|
+
const output = this.outputs[i];
|
|
1209
|
+
if (output) {
|
|
1210
|
+
this.textureState(output).dirty = true;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1063
1213
|
if (this.renderTexture) {
|
|
1064
1214
|
const state = this.textureState(this.renderTexture);
|
|
1065
1215
|
state.dirty = true;
|
|
@@ -1077,22 +1227,33 @@ export class WebGLContext3D {
|
|
|
1077
1227
|
}
|
|
1078
1228
|
}
|
|
1079
1229
|
finishTarget() {
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
1230
|
+
if (this.renderTexture) {
|
|
1231
|
+
this.finishTexture(this.renderTexture, this.renderFace);
|
|
1082
1232
|
}
|
|
1083
|
-
|
|
1233
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1234
|
+
const output = this.outputs[i];
|
|
1235
|
+
if (output) {
|
|
1236
|
+
this.finishTexture(output, this.outputFaces[i]);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
/** A texture drawn into made whole again: resolved, and its mips regenerated. */
|
|
1241
|
+
finishTexture(texture, renderFace) {
|
|
1084
1242
|
const state = this.textureState(texture);
|
|
1085
1243
|
if (state.dirty) {
|
|
1086
1244
|
const gl = this.gl;
|
|
1087
1245
|
this.drawState.forget();
|
|
1088
1246
|
this.texturesApplied[0] = 0;
|
|
1089
|
-
resolveTarget(this.gl, texture, state,
|
|
1247
|
+
resolveTarget(this.gl, texture, state, renderFace, this.scissor);
|
|
1090
1248
|
gl.activeTexture(gl.TEXTURE0);
|
|
1091
1249
|
if (texture.cube) {
|
|
1250
|
+
// An extra colour output may never have been a target of its own.
|
|
1251
|
+
state.framebuffer ??= gl.createFramebuffer();
|
|
1252
|
+
state.mipFramebuffer ??= gl.createFramebuffer();
|
|
1092
1253
|
// generateMipmap(CUBE_MAP) would overwrite manually uploaded mips on
|
|
1093
1254
|
// untouched faces. AIR regenerates only the face just rendered.
|
|
1094
1255
|
gl.disable(gl.SCISSOR_TEST);
|
|
1095
|
-
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X +
|
|
1256
|
+
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderFace;
|
|
1096
1257
|
// The base-size depth attachment cannot accompany smaller mip images.
|
|
1097
1258
|
this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
1098
1259
|
if (state.depthAttached) {
|
|
@@ -1116,13 +1277,81 @@ export class WebGLContext3D {
|
|
|
1116
1277
|
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
1117
1278
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
1118
1279
|
}
|
|
1119
|
-
state.uploaded[
|
|
1280
|
+
state.uploaded[renderFace] = (1 << texture.levels) - 1;
|
|
1120
1281
|
state.dirty = false;
|
|
1121
1282
|
}
|
|
1122
1283
|
}
|
|
1284
|
+
/** Half floats are drawn into where the GPU's WebGL renders float colour, as every desktop one does. */
|
|
1285
|
+
renderFormat(format) {
|
|
1286
|
+
if (format !== "rgbaHalfFloat") {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
this.floatTargets ??= this.gl.getExtension("EXT_color_buffer_float") !== null;
|
|
1290
|
+
if (!this.floatTargets) {
|
|
1291
|
+
throw new Context3DError("unsupported", "Context3D: this GPU draws into no half floats");
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
setColorOutput(index, texture, face = 0) {
|
|
1295
|
+
this.alive();
|
|
1296
|
+
range(index - 1, 1, this.outputs.length);
|
|
1297
|
+
if (texture) {
|
|
1298
|
+
this.textureState(texture);
|
|
1299
|
+
range(face, 1, texture.cube ? 6 : 1);
|
|
1300
|
+
if (compressedFormat(texture.format)) {
|
|
1301
|
+
throw new Context3DError("operation", "Context3D experiment: compressed textures are no render targets");
|
|
1302
|
+
}
|
|
1303
|
+
this.renderFormat(texture.format);
|
|
1304
|
+
}
|
|
1305
|
+
if (this.outputs[index - 1] === texture && this.outputFaces[index - 1] === face) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
const previous = this.outputs[index - 1];
|
|
1309
|
+
if (previous) {
|
|
1310
|
+
this.finishTexture(previous, this.outputFaces[index - 1]);
|
|
1311
|
+
}
|
|
1312
|
+
this.outputs[index - 1] = texture;
|
|
1313
|
+
this.outputFaces[index - 1] = texture ? face : 0;
|
|
1314
|
+
this.outputsChanged = true;
|
|
1315
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* The current target's framebuffer with the extra colour outputs attached, or without
|
|
1319
|
+
* them: its draws write every output bound, and a program's unwritten ones are refused
|
|
1320
|
+
* before they draw.
|
|
1321
|
+
*/
|
|
1322
|
+
attachOutputs(state) {
|
|
1323
|
+
const want = state ? this.outputMask() : 0;
|
|
1324
|
+
if (!state || (!this.outputsChanged && state.outputs === want)) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (want && state.samples) {
|
|
1328
|
+
throw new Context3DError("unsupported", "Context3D: antialiased colour outputs");
|
|
1329
|
+
}
|
|
1330
|
+
const gl = this.gl;
|
|
1331
|
+
const buffers = [gl.COLOR_ATTACHMENT0];
|
|
1332
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1333
|
+
const output = this.outputs[i];
|
|
1334
|
+
const handle = output ? this.textureState(output).handle : null;
|
|
1335
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1 + i, output?.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.outputFaces[i] : gl.TEXTURE_2D, handle, 0);
|
|
1336
|
+
buffers.push(output ? gl.COLOR_ATTACHMENT1 + i : gl.NONE);
|
|
1337
|
+
}
|
|
1338
|
+
gl.drawBuffers(buffers);
|
|
1339
|
+
state.outputs = want;
|
|
1340
|
+
this.outputsChanged = false;
|
|
1341
|
+
}
|
|
1342
|
+
/** The extra colour outputs bound, a bit each from output 1. */
|
|
1343
|
+
outputMask() {
|
|
1344
|
+
let mask = 0;
|
|
1345
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1346
|
+
if (this.outputs[i]) {
|
|
1347
|
+
mask |= 1 << i;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return mask;
|
|
1351
|
+
}
|
|
1123
1352
|
setTexture(index, texture) {
|
|
1124
1353
|
this.alive();
|
|
1125
|
-
range(index, 1,
|
|
1354
|
+
range(index, 1, SAMPLER_SLOTS);
|
|
1126
1355
|
if (texture && !this.textureOwners.has(texture)) {
|
|
1127
1356
|
throw new Context3DError("disposed", "Context3D experiment: texture from another context");
|
|
1128
1357
|
}
|
|
@@ -1207,13 +1436,13 @@ export class WebGLContext3D {
|
|
|
1207
1436
|
for (let i = 0; i < state.samplers.length; i++) {
|
|
1208
1437
|
const source = state.samplers[i].source;
|
|
1209
1438
|
if (!source.ignore) {
|
|
1210
|
-
this.setSamplerState(source.index,
|
|
1439
|
+
this.setSamplerState(source.index, WRAPS[source.wrap] | (source.filter << 2) | (source.mip << 5));
|
|
1211
1440
|
}
|
|
1212
1441
|
}
|
|
1213
1442
|
}
|
|
1214
1443
|
setVertex(index, buffer, offset, format) {
|
|
1215
1444
|
this.alive();
|
|
1216
|
-
range(index, 1,
|
|
1445
|
+
range(index, 1, VERTEX_SLOTS);
|
|
1217
1446
|
if (buffer === null) {
|
|
1218
1447
|
this.vertices[index] = null;
|
|
1219
1448
|
return;
|
|
@@ -1319,13 +1548,14 @@ export class WebGLContext3D {
|
|
|
1319
1548
|
const maximum = validateIndices(index, first, count);
|
|
1320
1549
|
const gl = this.gl;
|
|
1321
1550
|
this.drawState.bindVertexArray(this.vao);
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
}
|
|
1551
|
+
// The streams enabled that the program does not read, highest first.
|
|
1552
|
+
let stale = this.enabledAttributes & ~state.attributeMask;
|
|
1553
|
+
while (stale) {
|
|
1554
|
+
const i = 31 - Math.clz32(stale);
|
|
1555
|
+
gl.disableVertexAttribArray(i);
|
|
1556
|
+
stale &= ~(1 << i);
|
|
1557
|
+
}
|
|
1558
|
+
this.enabledAttributes &= state.attributeMask;
|
|
1329
1559
|
for (let i = 0; i < state.attributes.length; i++) {
|
|
1330
1560
|
const slot = state.attributes[i];
|
|
1331
1561
|
const binding = this.vertices[slot];
|
|
@@ -1358,7 +1588,12 @@ export class WebGLContext3D {
|
|
|
1358
1588
|
const binding = state.samplers[i];
|
|
1359
1589
|
const slot = binding.source.index;
|
|
1360
1590
|
const texture = this.textureSlots[slot];
|
|
1361
|
-
if (texture &&
|
|
1591
|
+
if (texture &&
|
|
1592
|
+
(texture === this.renderTexture ||
|
|
1593
|
+
(this.outputsBound &&
|
|
1594
|
+
(texture === this.outputs[0] ||
|
|
1595
|
+
texture === this.outputs[1] ||
|
|
1596
|
+
texture === this.outputs[2])))) {
|
|
1362
1597
|
throw new TextureBindingError("feedback");
|
|
1363
1598
|
}
|
|
1364
1599
|
const resource = texture && this.textures.get(texture);
|
|
@@ -1383,7 +1618,10 @@ export class WebGLContext3D {
|
|
|
1383
1618
|
throw new TextureBindingError("unuploaded");
|
|
1384
1619
|
}
|
|
1385
1620
|
}
|
|
1386
|
-
|
|
1621
|
+
const base = texture.streaming
|
|
1622
|
+
? this.streamingBase(texture, resource, this.samplerStates[slot] >>> 5 !== 0)
|
|
1623
|
+
: 0;
|
|
1624
|
+
for (let face = 0; !texture.streaming && face < resource.uploaded.length; face++) {
|
|
1387
1625
|
if (!texture.cube && !(resource.uploaded[face] & 1)) {
|
|
1388
1626
|
throw new TextureBindingError("unuploaded");
|
|
1389
1627
|
}
|
|
@@ -1403,6 +1641,13 @@ export class WebGLContext3D {
|
|
|
1403
1641
|
gl.bindSampler(slot, this.sampler(this.samplerStates[slot]));
|
|
1404
1642
|
this.texturesApplied[slot] = 1;
|
|
1405
1643
|
}
|
|
1644
|
+
if (resource.base !== base) {
|
|
1645
|
+
const target = texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
|
|
1646
|
+
gl.activeTexture(gl.TEXTURE0 + slot);
|
|
1647
|
+
gl.bindTexture(target, resource.handle);
|
|
1648
|
+
gl.texParameteri(target, gl.TEXTURE_BASE_LEVEL, base);
|
|
1649
|
+
resource.base = base;
|
|
1650
|
+
}
|
|
1406
1651
|
if (!state.samplersSet) {
|
|
1407
1652
|
gl.uniform1i(binding.location, slot);
|
|
1408
1653
|
}
|
|
@@ -1410,11 +1655,12 @@ export class WebGLContext3D {
|
|
|
1410
1655
|
state.samplersSet = true;
|
|
1411
1656
|
if (state.vertexBank) {
|
|
1412
1657
|
if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
|
|
1413
|
-
gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
|
|
1658
|
+
gl.uniform4fv(state.vertexBank, this.vertexConstants.data, 0, INDIRECT_BANK * 4);
|
|
1414
1659
|
state.vertexVersion = this.vertexConstants.version;
|
|
1415
1660
|
}
|
|
1416
1661
|
}
|
|
1417
|
-
|
|
1662
|
+
// An indirect program's own uniforms are only the registers past its bank.
|
|
1663
|
+
if (state.vertex.length) {
|
|
1418
1664
|
this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions, state.vertexValues);
|
|
1419
1665
|
}
|
|
1420
1666
|
this.uniforms(state.fragment, this.fragmentConstants, state.fragmentVersions, state.fragmentValues);
|
|
@@ -1425,6 +1671,45 @@ export class WebGLContext3D {
|
|
|
1425
1671
|
gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, first * 2);
|
|
1426
1672
|
this.changedTarget();
|
|
1427
1673
|
}
|
|
1674
|
+
/**
|
|
1675
|
+
* The level a streaming texture is drawn from, as AIR 51 draws one: with mip filtering, the
|
|
1676
|
+
* finest whose levels down to the smallest are uploaded on every face, which must reach its
|
|
1677
|
+
* streaming level (#3700 before); without, its level 0 alone (#3713), as a texture that
|
|
1678
|
+
* does not stream.
|
|
1679
|
+
*/
|
|
1680
|
+
streamingBase(texture, resource, mip) {
|
|
1681
|
+
const faces = resource.uploaded.length;
|
|
1682
|
+
let uploaded = 0;
|
|
1683
|
+
for (let face = 0; face < faces; face++) {
|
|
1684
|
+
uploaded |= resource.uploaded[face];
|
|
1685
|
+
}
|
|
1686
|
+
if (!uploaded) {
|
|
1687
|
+
throw new TextureBindingError("unuploaded");
|
|
1688
|
+
}
|
|
1689
|
+
if (!mip) {
|
|
1690
|
+
for (let face = 0; face < faces; face++) {
|
|
1691
|
+
if (!(resource.uploaded[face] & 1)) {
|
|
1692
|
+
throw new TextureBindingError("streaming");
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
return 0;
|
|
1696
|
+
}
|
|
1697
|
+
let base = texture.levels;
|
|
1698
|
+
while (base > 0) {
|
|
1699
|
+
let whole = true;
|
|
1700
|
+
for (let face = 0; face < faces; face++) {
|
|
1701
|
+
whole &&= (resource.uploaded[face] & (1 << (base - 1))) !== 0;
|
|
1702
|
+
}
|
|
1703
|
+
if (!whole) {
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
base--;
|
|
1707
|
+
}
|
|
1708
|
+
if (base > texture.streaming) {
|
|
1709
|
+
throw new TextureBindingError("mipmaps");
|
|
1710
|
+
}
|
|
1711
|
+
return base;
|
|
1712
|
+
}
|
|
1428
1713
|
uniforms(locations, bank, previous, values) {
|
|
1429
1714
|
for (let i = 0; i < locations.length; i++) {
|
|
1430
1715
|
if (locations[i] && (bank.writing || previous[i] !== bank.versions[i])) {
|
|
@@ -1460,7 +1745,7 @@ export class WebGLContext3D {
|
|
|
1460
1745
|
if (this.scissor) {
|
|
1461
1746
|
gl.disable(gl.SCISSOR_TEST);
|
|
1462
1747
|
}
|
|
1463
|
-
gl.blitFramebuffer(0, 0, this.
|
|
1748
|
+
gl.blitFramebuffer(0, 0, this.drawnWidth, this.drawnHeight, 0, 0, this.drawnWidth, this.drawnHeight, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
1464
1749
|
if (this.scissor) {
|
|
1465
1750
|
gl.enable(gl.SCISSOR_TEST);
|
|
1466
1751
|
}
|
|
@@ -1474,12 +1759,34 @@ export class WebGLContext3D {
|
|
|
1474
1759
|
this.target(true);
|
|
1475
1760
|
this.resolve();
|
|
1476
1761
|
const gl = this.gl;
|
|
1762
|
+
const scaleX = this.drawnWidth / this.width;
|
|
1763
|
+
const scaleY = this.drawnHeight / this.height;
|
|
1764
|
+
const drawnWidth = Math.min(this.drawnWidth, Math.ceil(width * scaleX));
|
|
1765
|
+
const drawnHeight = Math.min(this.drawnHeight, Math.ceil(height * scaleY));
|
|
1766
|
+
if (this.pixels.length < drawnWidth * drawnHeight * 4) {
|
|
1767
|
+
this.pixels = new Uint8Array(drawnWidth * drawnHeight * 4);
|
|
1768
|
+
}
|
|
1769
|
+
if (this.topDown.length < width * height * 4) {
|
|
1770
|
+
this.topDown = new Uint8Array(width * height * 4);
|
|
1771
|
+
}
|
|
1477
1772
|
// GL's rows run bottom-up, so the top-left region starts height rows below the top.
|
|
1478
|
-
gl.readPixels(0, this.
|
|
1773
|
+
gl.readPixels(0, this.drawnHeight - drawnHeight, drawnWidth, drawnHeight, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
|
|
1479
1774
|
const row = width * 4;
|
|
1775
|
+
if (drawnWidth === width && drawnHeight === height) {
|
|
1776
|
+
for (let y = 0; y < height; y++) {
|
|
1777
|
+
const from = (height - y - 1) * row;
|
|
1778
|
+
this.topDown.set(this.pixels.subarray(from, from + row), y * row);
|
|
1779
|
+
}
|
|
1780
|
+
return this.topDown;
|
|
1781
|
+
}
|
|
1782
|
+
// Storage smaller than the back buffer: each pixel is the stored one it falls on.
|
|
1783
|
+
const source = new Uint32Array(this.pixels.buffer, 0, drawnWidth * drawnHeight);
|
|
1784
|
+
const target = new Uint32Array(this.topDown.buffer, 0, width * height);
|
|
1480
1785
|
for (let y = 0; y < height; y++) {
|
|
1481
|
-
const from = (
|
|
1482
|
-
|
|
1786
|
+
const from = (drawnHeight - 1 - Math.min(drawnHeight - 1, Math.floor(y * scaleY))) * drawnWidth;
|
|
1787
|
+
for (let x = 0; x < width; x++) {
|
|
1788
|
+
target[y * width + x] = source[from + Math.min(drawnWidth - 1, Math.floor(x * scaleX))];
|
|
1789
|
+
}
|
|
1483
1790
|
}
|
|
1484
1791
|
return this.topDown;
|
|
1485
1792
|
}
|
|
@@ -1492,7 +1799,8 @@ export class WebGLContext3D {
|
|
|
1492
1799
|
if (this.scissor) {
|
|
1493
1800
|
gl.disable(gl.SCISSOR_TEST);
|
|
1494
1801
|
}
|
|
1495
|
-
|
|
1802
|
+
// To the drawing buffer the browser gave, which it may have made smaller than the canvas.
|
|
1803
|
+
gl.blitFramebuffer(0, 0, this.drawnWidth, this.drawnHeight, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
1496
1804
|
if (this.scissor) {
|
|
1497
1805
|
gl.enable(gl.SCISSOR_TEST);
|
|
1498
1806
|
}
|