@swf2es/context3d 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -27
- 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 +50 -3
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +74 -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/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 +29 -48
- package/dist/webgl/context.d.ts.map +1 -1
- package/dist/webgl/context.js +227 -364
- 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/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/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 +9 -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 +113 -3
- package/src/errors.ts +27 -1
- package/src/index.ts +12 -1
- package/src/webgl/back-buffer.ts +287 -0
- package/src/webgl/buffers.ts +114 -2
- package/src/webgl/context.ts +291 -489
- package/src/webgl/programs.ts +10 -5
- package/src/webgl/readback.ts +54 -0
- package/src/webgl/targets.ts +4 -2
- package/src/webgl/textures.ts +183 -9
package/dist/webgl/context.js
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INDIRECT_BANK } from "../agal.js";
|
|
2
|
+
import { compressedFormat, FRAGMENT_CONSTANTS, 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";
|
|
5
|
-
import {
|
|
6
|
+
import { configureBackBuffer, presentBackBuffer, unconfigureBackBuffer } from "./back-buffer.js";
|
|
7
|
+
import { allocateBuffer } from "./buffers.js";
|
|
6
8
|
import { resourceLimits } from "./limits.js";
|
|
7
9
|
import { createCopyProgram, uploadProgram, } from "./programs.js";
|
|
10
|
+
import { readBackBuffer } from "./readback.js";
|
|
8
11
|
import { BLEND_FUNCTIONS, DEPTH_FUNCTIONS, range, STENCIL_FUNCTIONS, StencilState, } from "./state.js";
|
|
9
12
|
import { allocateMultisample, deleteMultisample, resolveTarget, selectSamples, targetFramebuffer, } from "./targets.js";
|
|
10
|
-
import { uploadCompressedLevel, uploadTexture, uploadTextureBytes, } from "./textures.js";
|
|
13
|
+
import { storageFormat, uploadCompressedLevel, uploadTexture, uploadTextureBytes, } from "./textures.js";
|
|
11
14
|
import { validateIndices, validateVertices } from "./validation.js";
|
|
12
15
|
/** What AGAL's sampler must name to read each texture format: RGBA, DXT1 or DXT5. */
|
|
13
16
|
const SAMPLER_FORMATS = {
|
|
14
17
|
bgra: 0,
|
|
15
18
|
compressed: 1,
|
|
16
19
|
compressedAlpha: 2,
|
|
20
|
+
bgrPacked565: 0,
|
|
21
|
+
bgraPacked4444: 0,
|
|
22
|
+
rgbaHalfFloat: 0,
|
|
17
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* A clear colour's channel as Direct3D's 8-bit clear colour holds it, which AIR fills by
|
|
26
|
+
* truncation (0.5 clears to 127): within 0 to 1 and cut to its byte, NaN kept.
|
|
27
|
+
*/
|
|
28
|
+
function clearChannel(value) {
|
|
29
|
+
if (!(value > 0)) {
|
|
30
|
+
return value < 0 ? 0 : value;
|
|
31
|
+
}
|
|
32
|
+
return value >= 1 ? 1 : Math.floor(value * 255 + 1e-3) / 255;
|
|
33
|
+
}
|
|
34
|
+
/** AGAL's wraps (clamp, repeat, clamp_u_repeat_v, repeat_u_clamp_v) as SAMPLER_WRAPS indices. */
|
|
35
|
+
const WRAPS = [0, 3, 2, 1];
|
|
18
36
|
function constants(registers) {
|
|
19
37
|
const data = new Float32Array(registers * 4);
|
|
20
38
|
return {
|
|
@@ -25,9 +43,14 @@ function constants(registers) {
|
|
|
25
43
|
writing: 0,
|
|
26
44
|
};
|
|
27
45
|
}
|
|
28
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* An owned WebGL2 context, deliberately independent of the 2D renderer. Its members that are
|
|
48
|
+
* neither private nor Context3D's are for back-buffer.ts, buffers.ts and readback.ts, which
|
|
49
|
+
* hold parts of it as functions over it.
|
|
50
|
+
*/
|
|
29
51
|
export class WebGLContext3D {
|
|
30
52
|
gl;
|
|
53
|
+
/** @internal */
|
|
31
54
|
drawState;
|
|
32
55
|
width = 0;
|
|
33
56
|
height = 0;
|
|
@@ -35,14 +58,22 @@ export class WebGLContext3D {
|
|
|
35
58
|
* The back buffer's storage: its size, or less where the GPU allows less. The SWF still
|
|
36
59
|
* sees the size it asked for, drawn at this one and stretched to it when presented.
|
|
37
60
|
*/
|
|
61
|
+
/** @internal */
|
|
38
62
|
drawnWidth = 0;
|
|
63
|
+
/** @internal */
|
|
39
64
|
drawnHeight = 0;
|
|
65
|
+
/** @internal */
|
|
40
66
|
gpuMost = null;
|
|
41
67
|
disposed = false;
|
|
42
68
|
samplerFormats = true;
|
|
69
|
+
profile = "baseline";
|
|
70
|
+
/** @internal */
|
|
43
71
|
buffers = new Map();
|
|
72
|
+
/** @internal */
|
|
44
73
|
vertexBuffers = 0;
|
|
74
|
+
/** @internal */
|
|
45
75
|
bufferBytes = 0;
|
|
76
|
+
/** @internal */
|
|
46
77
|
vertexBufferBytes = 0;
|
|
47
78
|
programs = new Map();
|
|
48
79
|
textures = new Map();
|
|
@@ -55,10 +86,18 @@ export class WebGLContext3D {
|
|
|
55
86
|
copy2D = null;
|
|
56
87
|
copyCube = null;
|
|
57
88
|
targetSamples = new Int8Array(10).fill(-1);
|
|
89
|
+
/** @internal */
|
|
58
90
|
renderTexture = null;
|
|
59
91
|
renderDepth = false;
|
|
60
92
|
renderFace = 0;
|
|
61
|
-
|
|
93
|
+
/** Colour outputs 1 to 3, drawn into with renderTexture, and their cube faces. */
|
|
94
|
+
outputs = [null, null, null];
|
|
95
|
+
outputFaces = new Uint8Array(3);
|
|
96
|
+
outputsChanged = false;
|
|
97
|
+
/** Whether any of outputs is bound, so that a draw alone into its target looks at none. */
|
|
98
|
+
outputsBound = false;
|
|
99
|
+
floatTargets;
|
|
100
|
+
textureSlots = new Array(SAMPLER_SLOTS).fill(null);
|
|
62
101
|
textureMask = 0;
|
|
63
102
|
samplers = new Array(96).fill(null);
|
|
64
103
|
// Two wrap bits, three filter bits and two mip bits fit each slot in one byte.
|
|
@@ -66,19 +105,30 @@ export class WebGLContext3D {
|
|
|
66
105
|
anisotropy;
|
|
67
106
|
s3tc;
|
|
68
107
|
maxAnisotropy = 1;
|
|
69
|
-
|
|
108
|
+
/** @internal */
|
|
109
|
+
vertices = new Array(VERTEX_SLOTS).fill(null);
|
|
70
110
|
enabledAttributes = 0;
|
|
111
|
+
/** @internal */
|
|
71
112
|
indexBuffer = null;
|
|
72
|
-
|
|
113
|
+
/** @internal */
|
|
114
|
+
texturesApplied = new Uint8Array(SAMPLER_SLOTS);
|
|
73
115
|
selected = null;
|
|
116
|
+
/** @internal */
|
|
74
117
|
framebuffer = null;
|
|
118
|
+
/** @internal */
|
|
75
119
|
texture = null;
|
|
120
|
+
/** @internal */
|
|
76
121
|
resolveFramebuffer = null;
|
|
122
|
+
/** @internal */
|
|
77
123
|
colorBuffer = null;
|
|
124
|
+
/** @internal */
|
|
78
125
|
resolveDirty = false;
|
|
126
|
+
/** @internal */
|
|
79
127
|
depthBuffer = null;
|
|
128
|
+
/** @internal */
|
|
80
129
|
antiAlias = 0;
|
|
81
130
|
/** The back buffer's pixels to a CSS pixel and to a scissor rectangle's unit. */
|
|
131
|
+
/** @internal */
|
|
82
132
|
scale = 1;
|
|
83
133
|
depthWrite = true;
|
|
84
134
|
depthCompare = "less";
|
|
@@ -93,17 +143,22 @@ export class WebGLContext3D {
|
|
|
93
143
|
stencilReference = 0;
|
|
94
144
|
stencilRead = 255;
|
|
95
145
|
stencilWrite = 255;
|
|
146
|
+
/** @internal */
|
|
96
147
|
scissor = false;
|
|
97
148
|
scissorLeft = 0;
|
|
98
149
|
scissorTop = 0;
|
|
99
150
|
scissorRight = 0;
|
|
100
151
|
scissorBottom = 0;
|
|
101
152
|
scissorOutside = false;
|
|
102
|
-
vertexConstants = constants(
|
|
103
|
-
fragmentConstants = constants(
|
|
153
|
+
vertexConstants = constants(VERTEX_CONSTANTS);
|
|
154
|
+
fragmentConstants = constants(FRAGMENT_CONSTANTS);
|
|
155
|
+
/** @internal */
|
|
104
156
|
pixels = new Uint8Array(0);
|
|
157
|
+
/** @internal */
|
|
105
158
|
topDown = new Uint8Array(0);
|
|
159
|
+
/** @internal */
|
|
106
160
|
vao;
|
|
161
|
+
/** @internal */
|
|
107
162
|
limits;
|
|
108
163
|
dxtEncoder = null;
|
|
109
164
|
/** The host's whole-level encoder; a refusal retains the TypeScript path. */
|
|
@@ -138,11 +193,13 @@ export class WebGLContext3D {
|
|
|
138
193
|
gl.frontFace(gl.CW);
|
|
139
194
|
gl.disable(gl.DITHER);
|
|
140
195
|
}
|
|
196
|
+
/** @internal */
|
|
141
197
|
alive() {
|
|
142
198
|
if (this.disposed || this.gl.isContextLost()) {
|
|
143
199
|
throw new Context3DError("disposed", "Context3D experiment: context disposed or lost");
|
|
144
200
|
}
|
|
145
201
|
}
|
|
202
|
+
/** @internal */
|
|
146
203
|
target(back = false, discardColor = false) {
|
|
147
204
|
this.alive();
|
|
148
205
|
if (!this.framebuffer) {
|
|
@@ -159,174 +216,30 @@ export class WebGLContext3D {
|
|
|
159
216
|
}
|
|
160
217
|
}
|
|
161
218
|
this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
|
|
219
|
+
if (this.outputsChanged || state?.outputs) {
|
|
220
|
+
this.attachOutputs(state);
|
|
221
|
+
}
|
|
162
222
|
this.drawState.viewport(0, 0, texture?.width ?? this.drawnWidth, texture?.height ?? this.drawnHeight);
|
|
163
223
|
}
|
|
164
224
|
/** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
|
|
225
|
+
/** @internal */
|
|
165
226
|
bindFramebuffer(target, framebuffer) {
|
|
166
227
|
this.drawState.forget();
|
|
167
228
|
this.gl.bindFramebuffer(target, framebuffer);
|
|
168
229
|
}
|
|
169
230
|
configure(width, height, depth = false, antiAlias = 0, scale = 1) {
|
|
170
|
-
this
|
|
171
|
-
range(32, width - 32, MOST_BACK_BUFFER);
|
|
172
|
-
range(32, height - 32, MOST_BACK_BUFFER);
|
|
173
|
-
if (this.renderTexture) {
|
|
174
|
-
throw new Context3DError("operation", "Context3D experiment: resize while rendering to texture");
|
|
175
|
-
}
|
|
176
|
-
// AIR preserves color, depth and bindings when the request is unchanged.
|
|
177
|
-
// Shadow passes can issue this every frame when returning to the back buffer.
|
|
178
|
-
if (this.framebuffer &&
|
|
179
|
-
width === this.width &&
|
|
180
|
-
height === this.height &&
|
|
181
|
-
depth === (this.depthBuffer !== null) &&
|
|
182
|
-
antiAlias === this.antiAlias) {
|
|
183
|
-
if (scale !== this.scale) {
|
|
184
|
-
this.scale = scale;
|
|
185
|
-
this.showAt();
|
|
186
|
-
this.applyScissor();
|
|
187
|
-
}
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const samples = selectSamples(this.gl, antiAlias, depth);
|
|
191
|
-
const gl = this.gl;
|
|
192
|
-
const most = this.storageLimit();
|
|
193
|
-
const drawnWidth = Math.min(width, most[0]);
|
|
194
|
-
const drawnHeight = Math.min(height, most[1]);
|
|
195
|
-
const texture = gl.createTexture();
|
|
196
|
-
const framebuffer = gl.createFramebuffer();
|
|
197
|
-
const depthBuffer = depth ? gl.createRenderbuffer() : null;
|
|
198
|
-
const resolveFramebuffer = samples ? gl.createFramebuffer() : null;
|
|
199
|
-
const colorBuffer = samples ? gl.createRenderbuffer() : null;
|
|
200
|
-
try {
|
|
201
|
-
if (!texture ||
|
|
202
|
-
!framebuffer ||
|
|
203
|
-
(depth && !depthBuffer) ||
|
|
204
|
-
(samples && (!resolveFramebuffer || !colorBuffer))) {
|
|
205
|
-
throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
|
|
206
|
-
}
|
|
207
|
-
this.texturesApplied[0] = 0;
|
|
208
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
209
|
-
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
210
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
211
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
212
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, drawnWidth, drawnHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
213
|
-
this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
|
|
214
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
215
|
-
if (resolveFramebuffer) {
|
|
216
|
-
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
217
|
-
throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
|
|
218
|
-
}
|
|
219
|
-
this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
220
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
|
|
221
|
-
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, drawnWidth, drawnHeight);
|
|
222
|
-
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer);
|
|
223
|
-
}
|
|
224
|
-
if (depthBuffer) {
|
|
225
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
|
|
226
|
-
if (samples) {
|
|
227
|
-
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
|
|
231
|
-
}
|
|
232
|
-
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
|
|
233
|
-
}
|
|
234
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
235
|
-
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
236
|
-
throw new Context3DError("operation", "Context3D experiment: incomplete back buffer");
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
gl.deleteTexture(texture);
|
|
241
|
-
gl.deleteFramebuffer(framebuffer);
|
|
242
|
-
gl.deleteFramebuffer(resolveFramebuffer);
|
|
243
|
-
gl.deleteRenderbuffer(depthBuffer);
|
|
244
|
-
gl.deleteRenderbuffer(colorBuffer);
|
|
245
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
246
|
-
this.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
247
|
-
throw error;
|
|
248
|
-
}
|
|
249
|
-
gl.deleteTexture(this.texture);
|
|
250
|
-
gl.deleteFramebuffer(this.framebuffer);
|
|
251
|
-
gl.deleteFramebuffer(this.resolveFramebuffer);
|
|
252
|
-
gl.deleteRenderbuffer(this.depthBuffer);
|
|
253
|
-
gl.deleteRenderbuffer(this.colorBuffer);
|
|
254
|
-
this.texture = texture;
|
|
255
|
-
this.framebuffer = framebuffer;
|
|
256
|
-
this.resolveFramebuffer = resolveFramebuffer;
|
|
257
|
-
this.colorBuffer = colorBuffer;
|
|
258
|
-
this.depthBuffer = depthBuffer;
|
|
259
|
-
this.antiAlias = antiAlias;
|
|
260
|
-
this.width = width;
|
|
261
|
-
this.height = height;
|
|
262
|
-
this.drawnWidth = drawnWidth;
|
|
263
|
-
this.drawnHeight = drawnHeight;
|
|
264
|
-
this.scale = scale;
|
|
265
|
-
gl.canvas.width = drawnWidth;
|
|
266
|
-
gl.canvas.height = drawnHeight;
|
|
267
|
-
this.showAt();
|
|
268
|
-
// Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
|
|
269
|
-
this.pixels = new Uint8Array(0);
|
|
270
|
-
this.topDown = new Uint8Array(0);
|
|
271
|
-
this.applyScissor();
|
|
272
|
-
this.clear(0, 0, 0, 1);
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
|
|
276
|
-
* divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
|
|
277
|
-
*/
|
|
278
|
-
showAt() {
|
|
279
|
-
const style = this.gl.canvas.style;
|
|
280
|
-
if (!style) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
const plain = this.scale === 1;
|
|
284
|
-
style.width = plain && this.drawnWidth === this.width ? "" : `${this.width / this.scale}px`;
|
|
285
|
-
style.height = plain && this.drawnHeight === this.height ? "" : `${this.height / this.scale}px`;
|
|
286
|
-
}
|
|
287
|
-
/** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
|
|
288
|
-
storageLimit() {
|
|
289
|
-
if (!this.gpuMost) {
|
|
290
|
-
const gl = this.gl;
|
|
291
|
-
const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
|
|
292
|
-
const side = Math.min(gl.getParameter(gl.MAX_TEXTURE_SIZE), gl.getParameter(gl.MAX_RENDERBUFFER_SIZE));
|
|
293
|
-
this.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
|
|
294
|
-
}
|
|
295
|
-
return this.gpuMost;
|
|
231
|
+
configureBackBuffer(this, width, height, depth, antiAlias, scale);
|
|
296
232
|
}
|
|
297
233
|
unconfigure() {
|
|
298
|
-
this
|
|
299
|
-
if (this.renderTexture) {
|
|
300
|
-
throw new Context3DError("operation", "Context3D experiment: resize while rendering to texture");
|
|
301
|
-
}
|
|
302
|
-
const gl = this.gl;
|
|
303
|
-
gl.deleteTexture(this.texture);
|
|
304
|
-
gl.deleteFramebuffer(this.framebuffer);
|
|
305
|
-
gl.deleteFramebuffer(this.resolveFramebuffer);
|
|
306
|
-
gl.deleteRenderbuffer(this.depthBuffer);
|
|
307
|
-
gl.deleteRenderbuffer(this.colorBuffer);
|
|
308
|
-
this.drawState.forget();
|
|
309
|
-
this.texture = null;
|
|
310
|
-
this.framebuffer = null;
|
|
311
|
-
this.resolveFramebuffer = null;
|
|
312
|
-
this.colorBuffer = null;
|
|
313
|
-
this.depthBuffer = null;
|
|
314
|
-
this.resolveDirty = false;
|
|
315
|
-
this.antiAlias = 0;
|
|
316
|
-
this.scale = 1;
|
|
317
|
-
this.width = 0;
|
|
318
|
-
this.height = 0;
|
|
319
|
-
this.drawnWidth = 0;
|
|
320
|
-
this.drawnHeight = 0;
|
|
321
|
-
this.pixels = new Uint8Array(0);
|
|
322
|
-
this.topDown = new Uint8Array(0);
|
|
234
|
+
unconfigureBackBuffer(this);
|
|
323
235
|
}
|
|
324
236
|
clear(red, green, blue, alpha, depth = 1, mask = 0xffffffff, stencil = 0) {
|
|
325
237
|
this.target(false, (mask & 1) !== 0);
|
|
326
238
|
const gl = this.gl;
|
|
327
239
|
let bits = 0;
|
|
328
240
|
if (mask & 1) {
|
|
329
|
-
|
|
241
|
+
// A float target too: AIR clears through Direct3D's 8-bit colour.
|
|
242
|
+
gl.clearColor(clearChannel(red), clearChannel(green), clearChannel(blue), clearChannel(alpha));
|
|
330
243
|
bits |= gl.COLOR_BUFFER_BIT;
|
|
331
244
|
this.changedTarget();
|
|
332
245
|
}
|
|
@@ -464,6 +377,7 @@ export class WebGLContext3D {
|
|
|
464
377
|
}
|
|
465
378
|
this.applyScissor();
|
|
466
379
|
}
|
|
380
|
+
/** @internal */
|
|
467
381
|
applyScissor() {
|
|
468
382
|
const gl = this.gl;
|
|
469
383
|
if (!this.scissor) {
|
|
@@ -549,107 +463,9 @@ export class WebGLContext3D {
|
|
|
549
463
|
}
|
|
550
464
|
}
|
|
551
465
|
createBuffer(count, stride, dynamic = false) {
|
|
552
|
-
this
|
|
553
|
-
range(1, count - 1, stride === 0 ? 524287 : 65535);
|
|
554
|
-
range(0, stride, 64);
|
|
555
|
-
const countLimit = stride === 0 ? this.limits.maxIndexBuffers : this.limits.maxVertexBuffers;
|
|
556
|
-
const used = stride === 0 ? this.buffers.size - this.vertexBuffers : this.vertexBuffers;
|
|
557
|
-
if (this.buffers.size >= this.limits.maxBuffers || used >= countLimit) {
|
|
558
|
-
throw new Context3DError("limit", "Context3D experiment: buffer limit");
|
|
559
|
-
}
|
|
560
|
-
const bytes = count * (stride === 0 ? 2 : stride * 4);
|
|
561
|
-
const usedBytes = stride === 0 ? this.bufferBytes - this.vertexBufferBytes : this.vertexBufferBytes;
|
|
562
|
-
const byteLimit = stride === 0 ? this.limits.maxIndexBufferBytes : this.limits.maxVertexBufferBytes;
|
|
563
|
-
if (this.bufferBytes + bytes > this.limits.maxBufferBytes || usedBytes + bytes > byteLimit) {
|
|
564
|
-
throw new Context3DError("limit", "Context3D experiment: buffer memory limit");
|
|
565
|
-
}
|
|
566
|
-
const gl = this.gl;
|
|
567
|
-
const handle = gl.createBuffer();
|
|
568
|
-
if (!handle) {
|
|
569
|
-
throw new Context3DError("bufferAllocation", "Context3D experiment: cannot allocate buffer");
|
|
570
|
-
}
|
|
571
|
-
const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
|
|
572
|
-
if (stride === 0) {
|
|
573
|
-
this.indexBuffer = null;
|
|
574
|
-
}
|
|
575
|
-
gl.bindVertexArray(this.vao);
|
|
576
|
-
gl.bindBuffer(target, handle);
|
|
577
|
-
gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
|
|
578
|
-
const state = {
|
|
579
|
-
handle,
|
|
580
|
-
indices: stride === 0 ? new Uint16Array(count) : null,
|
|
581
|
-
uploaded: new Uint8Array(count),
|
|
582
|
-
prefix: 0,
|
|
583
|
-
version: 0,
|
|
584
|
-
checkedVersion: -1,
|
|
585
|
-
checkedFirst: 0,
|
|
586
|
-
checkedCount: 0,
|
|
587
|
-
maximum: -1,
|
|
588
|
-
ranges: null,
|
|
589
|
-
};
|
|
590
|
-
const buffer = {
|
|
591
|
-
count,
|
|
592
|
-
stride,
|
|
593
|
-
upload: (values, start, amount) => {
|
|
594
|
-
this.alive();
|
|
595
|
-
this.buffer(buffer);
|
|
596
|
-
try {
|
|
597
|
-
uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
|
|
598
|
-
}
|
|
599
|
-
finally {
|
|
600
|
-
// Source getters can draw before the upload replaces the VAO's index binding.
|
|
601
|
-
if (stride === 0) {
|
|
602
|
-
this.indexBuffer = null;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
},
|
|
606
|
-
uploadBytes: (data, offset, start, amount) => {
|
|
607
|
-
this.alive();
|
|
608
|
-
this.buffer(buffer);
|
|
609
|
-
try {
|
|
610
|
-
uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
|
|
611
|
-
}
|
|
612
|
-
finally {
|
|
613
|
-
if (stride === 0) {
|
|
614
|
-
this.indexBuffer = null;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
},
|
|
618
|
-
dispose: () => {
|
|
619
|
-
if (!this.buffers.delete(buffer)) {
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (this.indexBuffer === handle) {
|
|
623
|
-
this.indexBuffer = null;
|
|
624
|
-
}
|
|
625
|
-
gl.deleteBuffer(handle);
|
|
626
|
-
this.bufferBytes -= bytes;
|
|
627
|
-
if (stride !== 0) {
|
|
628
|
-
this.vertexBuffers--;
|
|
629
|
-
this.vertexBufferBytes -= bytes;
|
|
630
|
-
}
|
|
631
|
-
state.indices = null;
|
|
632
|
-
state.ranges = null;
|
|
633
|
-
state.uploaded = new Uint8Array(0);
|
|
634
|
-
for (let i = 0; i < this.vertices.length; i++) {
|
|
635
|
-
const binding = this.vertices[i];
|
|
636
|
-
if (binding?.buffer === buffer) {
|
|
637
|
-
this.vertices[i] = null;
|
|
638
|
-
}
|
|
639
|
-
else if (binding?.checkedIndex === state) {
|
|
640
|
-
binding.checkedIndex = null;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
},
|
|
644
|
-
};
|
|
645
|
-
this.buffers.set(buffer, state);
|
|
646
|
-
if (stride !== 0) {
|
|
647
|
-
this.vertexBuffers++;
|
|
648
|
-
this.vertexBufferBytes += bytes;
|
|
649
|
-
}
|
|
650
|
-
this.bufferBytes += bytes;
|
|
651
|
-
return buffer;
|
|
466
|
+
return allocateBuffer(this, count, stride, dynamic);
|
|
652
467
|
}
|
|
468
|
+
/** @internal */
|
|
653
469
|
buffer(buffer) {
|
|
654
470
|
const state = this.buffers.get(buffer);
|
|
655
471
|
if (!state) {
|
|
@@ -670,6 +486,7 @@ export class WebGLContext3D {
|
|
|
670
486
|
attributeMask: 0,
|
|
671
487
|
vertex: [],
|
|
672
488
|
vertexBank: null,
|
|
489
|
+
outputs: 1,
|
|
673
490
|
fragment: [],
|
|
674
491
|
samplers: [],
|
|
675
492
|
samplerMask: 0,
|
|
@@ -681,11 +498,17 @@ export class WebGLContext3D {
|
|
|
681
498
|
vertexVersion: -1,
|
|
682
499
|
};
|
|
683
500
|
const program = {
|
|
501
|
+
get outputs() {
|
|
502
|
+
return state.outputs;
|
|
503
|
+
},
|
|
504
|
+
get attributes() {
|
|
505
|
+
return state.attributeMask;
|
|
506
|
+
},
|
|
684
507
|
upload: (vertex, fragment) => {
|
|
685
508
|
this.alive();
|
|
686
509
|
this.program(program);
|
|
687
510
|
this.drawState.forget();
|
|
688
|
-
uploadProgram(this.gl, state, vertex, fragment);
|
|
511
|
+
uploadProgram(this.gl, state, vertex, fragment, this.profile);
|
|
689
512
|
if (this.selected === program) {
|
|
690
513
|
this.setProgram(program);
|
|
691
514
|
}
|
|
@@ -731,15 +554,15 @@ export class WebGLContext3D {
|
|
|
731
554
|
createTexture(width, height, cube = false, format = "bgra", streaming = 0) {
|
|
732
555
|
return this.allocateTexture(width, height, cube, false, format, streaming);
|
|
733
556
|
}
|
|
734
|
-
createRectangleTexture(width, height) {
|
|
735
|
-
return this.allocateTexture(width, height, false, true,
|
|
557
|
+
createRectangleTexture(width, height, format = "bgra") {
|
|
558
|
+
return this.allocateTexture(width, height, false, true, format);
|
|
736
559
|
}
|
|
737
560
|
/** GL's internal format for a compressed texture's S3TC, or 0 where the context takes none. */
|
|
738
561
|
s3tcFormat(format) {
|
|
739
562
|
if (this.s3tc === undefined) {
|
|
740
563
|
this.s3tc = this.gl.getExtension("WEBGL_compressed_texture_s3tc");
|
|
741
564
|
}
|
|
742
|
-
if (!this.s3tc || format
|
|
565
|
+
if (!this.s3tc || !compressedFormat(format)) {
|
|
743
566
|
return 0;
|
|
744
567
|
}
|
|
745
568
|
// AIR's DXT1 has DXT1's one bit of alpha: a block of three colours has transparent black.
|
|
@@ -757,18 +580,22 @@ export class WebGLContext3D {
|
|
|
757
580
|
for (let level = 0; level < levels; level++) {
|
|
758
581
|
const w = Math.max(1, width >> level);
|
|
759
582
|
const h = Math.max(1, height >> level);
|
|
760
|
-
size +=
|
|
583
|
+
size += compressedFormat(format)
|
|
584
|
+
? blockBytes(w, h, format === "compressedAlpha")
|
|
585
|
+
: w * h * (TEXEL_BYTES[format] ?? 4);
|
|
761
586
|
}
|
|
762
587
|
return size * faces;
|
|
763
588
|
}
|
|
764
589
|
allocateTexture(width, height, cube, rectangle, format, streaming = 0) {
|
|
765
590
|
this.alive();
|
|
766
|
-
|
|
767
|
-
|
|
591
|
+
const limits = profileLimits(this.profile);
|
|
592
|
+
const most = rectangle ? limits.rectangleSize : limits.textureSize;
|
|
593
|
+
range(1, width - 1, most);
|
|
594
|
+
range(1, height - 1, most);
|
|
768
595
|
if (cube && width !== height) {
|
|
769
596
|
throw new Context3DError("textureShape", "Context3D cube faces must be square");
|
|
770
597
|
}
|
|
771
|
-
if (cube && width >
|
|
598
|
+
if (cube && width > MOST_CUBE) {
|
|
772
599
|
throw new Context3DError("textureSize", "Context3D cube faces must be at most 1024");
|
|
773
600
|
}
|
|
774
601
|
const faces = cube ? 6 : 1;
|
|
@@ -777,14 +604,14 @@ export class WebGLContext3D {
|
|
|
777
604
|
if (!rectangle && ((width & (width - 1)) !== 0 || (height & (height - 1)) !== 0)) {
|
|
778
605
|
throw new Context3DError("texturePowerOfTwo", "Context3D texture size must be a power of two");
|
|
779
606
|
}
|
|
780
|
-
if (format
|
|
607
|
+
if (compressedFormat(format) && rectangle) {
|
|
781
608
|
throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
|
|
782
609
|
}
|
|
783
|
-
if (format
|
|
610
|
+
if (compressedFormat(format) && (width < 4 || height < 4)) {
|
|
784
611
|
throw new Context3DError("compressedSize", "Context3D compressed textures are at least 4x4");
|
|
785
612
|
}
|
|
786
613
|
const gl = this.gl;
|
|
787
|
-
const storage = this.s3tcFormat(format) || gl
|
|
614
|
+
const storage = this.s3tcFormat(format) || storageFormat(gl, format);
|
|
788
615
|
const size = this.chainBytes(width, height, levels, faces, format);
|
|
789
616
|
const kind = cube ? 2 : rectangle ? 1 : 0;
|
|
790
617
|
const countLimit = cube
|
|
@@ -799,7 +626,8 @@ export class WebGLContext3D {
|
|
|
799
626
|
: this.limits.max2DTextureBytes;
|
|
800
627
|
// AIR reserves half the base size for mips, independently of the exact
|
|
801
628
|
// chain storage, and charges DXT1 an eighth of BGRA and DXT5 a quarter.
|
|
802
|
-
const quotaBytes = (width * height * faces * (rectangle ? 4 : 6)) /
|
|
629
|
+
const quotaBytes = (width * height * faces * (rectangle ? 4 : 6) * (TEXEL_BYTES[format] ?? 4)) /
|
|
630
|
+
4 /
|
|
803
631
|
(format === "compressed" ? 8 : format === "compressedAlpha" ? 4 : 1);
|
|
804
632
|
if (this.textures.size >= this.limits.maxTextures ||
|
|
805
633
|
this.textureCounts[kind] >= countLimit ||
|
|
@@ -830,6 +658,7 @@ export class WebGLContext3D {
|
|
|
830
658
|
multisample: null,
|
|
831
659
|
samples: 0,
|
|
832
660
|
antiAlias: 0,
|
|
661
|
+
outputs: 0,
|
|
833
662
|
base: 0,
|
|
834
663
|
};
|
|
835
664
|
const texture = {
|
|
@@ -864,8 +693,8 @@ export class WebGLContext3D {
|
|
|
864
693
|
uploadCompressed: (blocks, level = 0, face = 0) => {
|
|
865
694
|
this.alive();
|
|
866
695
|
this.textureState(texture);
|
|
867
|
-
if (format
|
|
868
|
-
throw new Context3DError("textureUpload", "Context3D experiment:
|
|
696
|
+
if (!compressedFormat(format)) {
|
|
697
|
+
throw new Context3DError("textureUpload", "Context3D experiment: uncompressed texture given blocks");
|
|
869
698
|
}
|
|
870
699
|
try {
|
|
871
700
|
uploadCompressedLevel(gl, texture, state, blocks, level, face);
|
|
@@ -901,6 +730,13 @@ export class WebGLContext3D {
|
|
|
901
730
|
state.framebuffer = null;
|
|
902
731
|
state.mipFramebuffer = null;
|
|
903
732
|
state.depthBuffer = null;
|
|
733
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
734
|
+
if (this.outputs[i] === texture) {
|
|
735
|
+
this.outputs[i] = null;
|
|
736
|
+
this.outputsChanged = true;
|
|
737
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
904
740
|
for (let i = 0; i < this.textureSlots.length; i++) {
|
|
905
741
|
if (this.textureSlots[i] === texture) {
|
|
906
742
|
this.texturesApplied[i] = 0;
|
|
@@ -1023,7 +859,7 @@ export class WebGLContext3D {
|
|
|
1023
859
|
this.sampler(0);
|
|
1024
860
|
this.drawState.forget();
|
|
1025
861
|
this.texturesApplied[0] = 0;
|
|
1026
|
-
const target = allocateMultisample(this.gl, texture, old, samples, depth, this.depthWrite, this.stencilWrite);
|
|
862
|
+
const target = allocateMultisample(this.gl, texture, old, samples, depth, this.depthWrite, this.stencilWrite, state.storage);
|
|
1027
863
|
if (same) {
|
|
1028
864
|
if (newDepth) {
|
|
1029
865
|
this.targetDepthBytes += bytes;
|
|
@@ -1055,9 +891,10 @@ export class WebGLContext3D {
|
|
|
1055
891
|
const gl = this.gl;
|
|
1056
892
|
if (texture) {
|
|
1057
893
|
this.textureState(texture);
|
|
1058
|
-
if (texture.format
|
|
894
|
+
if (compressedFormat(texture.format)) {
|
|
1059
895
|
throw new Context3DError("operation", "Context3D experiment: compressed textures are no render targets");
|
|
1060
896
|
}
|
|
897
|
+
this.renderFormat(texture.format);
|
|
1061
898
|
}
|
|
1062
899
|
const samples = texture ? this.multisamples(antiAlias, depth) : 0;
|
|
1063
900
|
this.finishTarget();
|
|
@@ -1155,6 +992,12 @@ export class WebGLContext3D {
|
|
|
1155
992
|
this.applyScissor();
|
|
1156
993
|
}
|
|
1157
994
|
changedTarget() {
|
|
995
|
+
for (let i = 0; this.outputsBound && i < this.outputs.length; i++) {
|
|
996
|
+
const output = this.outputs[i];
|
|
997
|
+
if (output) {
|
|
998
|
+
this.textureState(output).dirty = true;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1158
1001
|
if (this.renderTexture) {
|
|
1159
1002
|
const state = this.textureState(this.renderTexture);
|
|
1160
1003
|
state.dirty = true;
|
|
@@ -1172,22 +1015,33 @@ export class WebGLContext3D {
|
|
|
1172
1015
|
}
|
|
1173
1016
|
}
|
|
1174
1017
|
finishTarget() {
|
|
1175
|
-
if (
|
|
1176
|
-
|
|
1018
|
+
if (this.renderTexture) {
|
|
1019
|
+
this.finishTexture(this.renderTexture, this.renderFace);
|
|
1177
1020
|
}
|
|
1178
|
-
|
|
1021
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1022
|
+
const output = this.outputs[i];
|
|
1023
|
+
if (output) {
|
|
1024
|
+
this.finishTexture(output, this.outputFaces[i]);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/** A texture drawn into made whole again: resolved, and its mips regenerated. */
|
|
1029
|
+
finishTexture(texture, renderFace) {
|
|
1179
1030
|
const state = this.textureState(texture);
|
|
1180
1031
|
if (state.dirty) {
|
|
1181
1032
|
const gl = this.gl;
|
|
1182
1033
|
this.drawState.forget();
|
|
1183
1034
|
this.texturesApplied[0] = 0;
|
|
1184
|
-
resolveTarget(this.gl, texture, state,
|
|
1035
|
+
resolveTarget(this.gl, texture, state, renderFace, this.scissor);
|
|
1185
1036
|
gl.activeTexture(gl.TEXTURE0);
|
|
1186
1037
|
if (texture.cube) {
|
|
1038
|
+
// An extra colour output may never have been a target of its own.
|
|
1039
|
+
state.framebuffer ??= gl.createFramebuffer();
|
|
1040
|
+
state.mipFramebuffer ??= gl.createFramebuffer();
|
|
1187
1041
|
// generateMipmap(CUBE_MAP) would overwrite manually uploaded mips on
|
|
1188
1042
|
// untouched faces. AIR regenerates only the face just rendered.
|
|
1189
1043
|
gl.disable(gl.SCISSOR_TEST);
|
|
1190
|
-
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X +
|
|
1044
|
+
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderFace;
|
|
1191
1045
|
// The base-size depth attachment cannot accompany smaller mip images.
|
|
1192
1046
|
this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
1193
1047
|
if (state.depthAttached) {
|
|
@@ -1211,13 +1065,81 @@ export class WebGLContext3D {
|
|
|
1211
1065
|
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
1212
1066
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
1213
1067
|
}
|
|
1214
|
-
state.uploaded[
|
|
1068
|
+
state.uploaded[renderFace] = (1 << texture.levels) - 1;
|
|
1215
1069
|
state.dirty = false;
|
|
1216
1070
|
}
|
|
1217
1071
|
}
|
|
1072
|
+
/** Half floats are drawn into where the GPU's WebGL renders float colour, as every desktop one does. */
|
|
1073
|
+
renderFormat(format) {
|
|
1074
|
+
if (format !== "rgbaHalfFloat") {
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
this.floatTargets ??= this.gl.getExtension("EXT_color_buffer_float") !== null;
|
|
1078
|
+
if (!this.floatTargets) {
|
|
1079
|
+
throw new Context3DError("unsupported", "Context3D: this GPU draws into no half floats");
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
setColorOutput(index, texture, face = 0) {
|
|
1083
|
+
this.alive();
|
|
1084
|
+
range(index - 1, 1, this.outputs.length);
|
|
1085
|
+
if (texture) {
|
|
1086
|
+
this.textureState(texture);
|
|
1087
|
+
range(face, 1, texture.cube ? 6 : 1);
|
|
1088
|
+
if (compressedFormat(texture.format)) {
|
|
1089
|
+
throw new Context3DError("operation", "Context3D experiment: compressed textures are no render targets");
|
|
1090
|
+
}
|
|
1091
|
+
this.renderFormat(texture.format);
|
|
1092
|
+
}
|
|
1093
|
+
if (this.outputs[index - 1] === texture && this.outputFaces[index - 1] === face) {
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const previous = this.outputs[index - 1];
|
|
1097
|
+
if (previous) {
|
|
1098
|
+
this.finishTexture(previous, this.outputFaces[index - 1]);
|
|
1099
|
+
}
|
|
1100
|
+
this.outputs[index - 1] = texture;
|
|
1101
|
+
this.outputFaces[index - 1] = texture ? face : 0;
|
|
1102
|
+
this.outputsChanged = true;
|
|
1103
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* The current target's framebuffer with the extra colour outputs attached, or without
|
|
1107
|
+
* them: its draws write every output bound, and a program's unwritten ones are refused
|
|
1108
|
+
* before they draw.
|
|
1109
|
+
*/
|
|
1110
|
+
attachOutputs(state) {
|
|
1111
|
+
const want = state ? this.outputMask() : 0;
|
|
1112
|
+
if (!state || (!this.outputsChanged && state.outputs === want)) {
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (want && state.samples) {
|
|
1116
|
+
throw new Context3DError("unsupported", "Context3D: antialiased colour outputs");
|
|
1117
|
+
}
|
|
1118
|
+
const gl = this.gl;
|
|
1119
|
+
const buffers = [gl.COLOR_ATTACHMENT0];
|
|
1120
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1121
|
+
const output = this.outputs[i];
|
|
1122
|
+
const handle = output ? this.textureState(output).handle : null;
|
|
1123
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1 + i, output?.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.outputFaces[i] : gl.TEXTURE_2D, handle, 0);
|
|
1124
|
+
buffers.push(output ? gl.COLOR_ATTACHMENT1 + i : gl.NONE);
|
|
1125
|
+
}
|
|
1126
|
+
gl.drawBuffers(buffers);
|
|
1127
|
+
state.outputs = want;
|
|
1128
|
+
this.outputsChanged = false;
|
|
1129
|
+
}
|
|
1130
|
+
/** The extra colour outputs bound, a bit each from output 1. */
|
|
1131
|
+
outputMask() {
|
|
1132
|
+
let mask = 0;
|
|
1133
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1134
|
+
if (this.outputs[i]) {
|
|
1135
|
+
mask |= 1 << i;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return mask;
|
|
1139
|
+
}
|
|
1218
1140
|
setTexture(index, texture) {
|
|
1219
1141
|
this.alive();
|
|
1220
|
-
range(index, 1,
|
|
1142
|
+
range(index, 1, SAMPLER_SLOTS);
|
|
1221
1143
|
if (texture && !this.textureOwners.has(texture)) {
|
|
1222
1144
|
throw new Context3DError("disposed", "Context3D experiment: texture from another context");
|
|
1223
1145
|
}
|
|
@@ -1302,13 +1224,13 @@ export class WebGLContext3D {
|
|
|
1302
1224
|
for (let i = 0; i < state.samplers.length; i++) {
|
|
1303
1225
|
const source = state.samplers[i].source;
|
|
1304
1226
|
if (!source.ignore) {
|
|
1305
|
-
this.setSamplerState(source.index,
|
|
1227
|
+
this.setSamplerState(source.index, WRAPS[source.wrap] | (source.filter << 2) | (source.mip << 5));
|
|
1306
1228
|
}
|
|
1307
1229
|
}
|
|
1308
1230
|
}
|
|
1309
1231
|
setVertex(index, buffer, offset, format) {
|
|
1310
1232
|
this.alive();
|
|
1311
|
-
range(index, 1,
|
|
1233
|
+
range(index, 1, VERTEX_SLOTS);
|
|
1312
1234
|
if (buffer === null) {
|
|
1313
1235
|
this.vertices[index] = null;
|
|
1314
1236
|
return;
|
|
@@ -1414,13 +1336,14 @@ export class WebGLContext3D {
|
|
|
1414
1336
|
const maximum = validateIndices(index, first, count);
|
|
1415
1337
|
const gl = this.gl;
|
|
1416
1338
|
this.drawState.bindVertexArray(this.vao);
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
}
|
|
1339
|
+
// The streams enabled that the program does not read, highest first.
|
|
1340
|
+
let stale = this.enabledAttributes & ~state.attributeMask;
|
|
1341
|
+
while (stale) {
|
|
1342
|
+
const i = 31 - Math.clz32(stale);
|
|
1343
|
+
gl.disableVertexAttribArray(i);
|
|
1344
|
+
stale &= ~(1 << i);
|
|
1345
|
+
}
|
|
1346
|
+
this.enabledAttributes &= state.attributeMask;
|
|
1424
1347
|
for (let i = 0; i < state.attributes.length; i++) {
|
|
1425
1348
|
const slot = state.attributes[i];
|
|
1426
1349
|
const binding = this.vertices[slot];
|
|
@@ -1453,7 +1376,12 @@ export class WebGLContext3D {
|
|
|
1453
1376
|
const binding = state.samplers[i];
|
|
1454
1377
|
const slot = binding.source.index;
|
|
1455
1378
|
const texture = this.textureSlots[slot];
|
|
1456
|
-
if (texture &&
|
|
1379
|
+
if (texture &&
|
|
1380
|
+
(texture === this.renderTexture ||
|
|
1381
|
+
(this.outputsBound &&
|
|
1382
|
+
(texture === this.outputs[0] ||
|
|
1383
|
+
texture === this.outputs[1] ||
|
|
1384
|
+
texture === this.outputs[2])))) {
|
|
1457
1385
|
throw new TextureBindingError("feedback");
|
|
1458
1386
|
}
|
|
1459
1387
|
const resource = texture && this.textures.get(texture);
|
|
@@ -1515,11 +1443,12 @@ export class WebGLContext3D {
|
|
|
1515
1443
|
state.samplersSet = true;
|
|
1516
1444
|
if (state.vertexBank) {
|
|
1517
1445
|
if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
|
|
1518
|
-
gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
|
|
1446
|
+
gl.uniform4fv(state.vertexBank, this.vertexConstants.data, 0, INDIRECT_BANK * 4);
|
|
1519
1447
|
state.vertexVersion = this.vertexConstants.version;
|
|
1520
1448
|
}
|
|
1521
1449
|
}
|
|
1522
|
-
|
|
1450
|
+
// An indirect program's own uniforms are only the registers past its bank.
|
|
1451
|
+
if (state.vertex.length) {
|
|
1523
1452
|
this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions, state.vertexValues);
|
|
1524
1453
|
}
|
|
1525
1454
|
this.uniforms(state.fragment, this.fragmentConstants, state.fragmentVersions, state.fragmentValues);
|
|
@@ -1593,77 +1522,11 @@ export class WebGLContext3D {
|
|
|
1593
1522
|
}
|
|
1594
1523
|
}
|
|
1595
1524
|
}
|
|
1596
|
-
resolve() {
|
|
1597
|
-
if (!this.resolveFramebuffer) {
|
|
1598
|
-
return;
|
|
1599
|
-
}
|
|
1600
|
-
const gl = this.gl;
|
|
1601
|
-
if (this.resolveDirty) {
|
|
1602
|
-
this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
|
|
1603
|
-
this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
|
|
1604
|
-
if (this.scissor) {
|
|
1605
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
1606
|
-
}
|
|
1607
|
-
gl.blitFramebuffer(0, 0, this.drawnWidth, this.drawnHeight, 0, 0, this.drawnWidth, this.drawnHeight, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
1608
|
-
if (this.scissor) {
|
|
1609
|
-
gl.enable(gl.SCISSOR_TEST);
|
|
1610
|
-
}
|
|
1611
|
-
this.resolveDirty = false;
|
|
1612
|
-
}
|
|
1613
|
-
this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
|
|
1614
|
-
}
|
|
1615
1525
|
read(width = this.width, height = this.height) {
|
|
1616
|
-
|
|
1617
|
-
height = Math.max(0, Math.min(this.height, Math.floor(height) || 0));
|
|
1618
|
-
this.target(true);
|
|
1619
|
-
this.resolve();
|
|
1620
|
-
const gl = this.gl;
|
|
1621
|
-
const scaleX = this.drawnWidth / this.width;
|
|
1622
|
-
const scaleY = this.drawnHeight / this.height;
|
|
1623
|
-
const drawnWidth = Math.min(this.drawnWidth, Math.ceil(width * scaleX));
|
|
1624
|
-
const drawnHeight = Math.min(this.drawnHeight, Math.ceil(height * scaleY));
|
|
1625
|
-
if (this.pixels.length < drawnWidth * drawnHeight * 4) {
|
|
1626
|
-
this.pixels = new Uint8Array(drawnWidth * drawnHeight * 4);
|
|
1627
|
-
}
|
|
1628
|
-
if (this.topDown.length < width * height * 4) {
|
|
1629
|
-
this.topDown = new Uint8Array(width * height * 4);
|
|
1630
|
-
}
|
|
1631
|
-
// GL's rows run bottom-up, so the top-left region starts height rows below the top.
|
|
1632
|
-
gl.readPixels(0, this.drawnHeight - drawnHeight, drawnWidth, drawnHeight, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
|
|
1633
|
-
const row = width * 4;
|
|
1634
|
-
if (drawnWidth === width && drawnHeight === height) {
|
|
1635
|
-
for (let y = 0; y < height; y++) {
|
|
1636
|
-
const from = (height - y - 1) * row;
|
|
1637
|
-
this.topDown.set(this.pixels.subarray(from, from + row), y * row);
|
|
1638
|
-
}
|
|
1639
|
-
return this.topDown;
|
|
1640
|
-
}
|
|
1641
|
-
// Storage smaller than the back buffer: each pixel is the stored one it falls on.
|
|
1642
|
-
const source = new Uint32Array(this.pixels.buffer, 0, drawnWidth * drawnHeight);
|
|
1643
|
-
const target = new Uint32Array(this.topDown.buffer, 0, width * height);
|
|
1644
|
-
for (let y = 0; y < height; y++) {
|
|
1645
|
-
const from = (drawnHeight - 1 - Math.min(drawnHeight - 1, Math.floor(y * scaleY))) * drawnWidth;
|
|
1646
|
-
for (let x = 0; x < width; x++) {
|
|
1647
|
-
target[y * width + x] = source[from + Math.min(drawnWidth - 1, Math.floor(x * scaleX))];
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
return this.topDown;
|
|
1526
|
+
return readBackBuffer(this, width, height);
|
|
1651
1527
|
}
|
|
1652
1528
|
present() {
|
|
1653
|
-
this
|
|
1654
|
-
this.target();
|
|
1655
|
-
this.resolve();
|
|
1656
|
-
const gl = this.gl;
|
|
1657
|
-
this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
|
|
1658
|
-
if (this.scissor) {
|
|
1659
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
1660
|
-
}
|
|
1661
|
-
// To the drawing buffer the browser gave, which it may have made smaller than the canvas.
|
|
1662
|
-
gl.blitFramebuffer(0, 0, this.drawnWidth, this.drawnHeight, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
1663
|
-
if (this.scissor) {
|
|
1664
|
-
gl.enable(gl.SCISSOR_TEST);
|
|
1665
|
-
}
|
|
1666
|
-
gl.flush();
|
|
1529
|
+
presentBackBuffer(this);
|
|
1667
1530
|
}
|
|
1668
1531
|
dispose() {
|
|
1669
1532
|
if (this.disposed) {
|