@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/src/webgl/context.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
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
|
-
|
|
8
|
+
compressedFormat,
|
|
9
|
+
FRAGMENT_CONSTANTS,
|
|
10
|
+
MOST_CUBE,
|
|
11
|
+
type Profile,
|
|
9
12
|
type Program3D,
|
|
13
|
+
profileLimits,
|
|
10
14
|
SAMPLER_FILTERS,
|
|
11
15
|
SAMPLER_MIPS,
|
|
16
|
+
SAMPLER_SLOTS,
|
|
12
17
|
SAMPLER_WRAPS,
|
|
13
18
|
type SamplerFilter,
|
|
14
19
|
type SamplerMip,
|
|
@@ -16,10 +21,13 @@ import {
|
|
|
16
21
|
ScissorBoundsError,
|
|
17
22
|
type StencilAction,
|
|
18
23
|
screenPixels,
|
|
24
|
+
TEXEL_BYTES,
|
|
19
25
|
type Texture3D,
|
|
20
26
|
TextureBindingError,
|
|
21
27
|
type TextureFormat,
|
|
28
|
+
VERTEX_CONSTANTS,
|
|
22
29
|
VERTEX_FORMATS,
|
|
30
|
+
VERTEX_SLOTS,
|
|
23
31
|
type VertexFormat,
|
|
24
32
|
VertexOffsetError,
|
|
25
33
|
} from "../backend.js";
|
|
@@ -27,12 +35,8 @@ import { GlState } from "../device/state.js";
|
|
|
27
35
|
import { blockBytes, type DxtEncoder } from "../dxt.js";
|
|
28
36
|
import { Context3DError } from "../errors.js";
|
|
29
37
|
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
uploadBuffer,
|
|
33
|
-
uploadBufferBytes,
|
|
34
|
-
type VertexBinding,
|
|
35
|
-
} from "./buffers.js";
|
|
38
|
+
import { configureBackBuffer, presentBackBuffer, unconfigureBackBuffer } from "./back-buffer.js";
|
|
39
|
+
import { allocateBuffer, type BufferState, type VertexBinding } from "./buffers.js";
|
|
36
40
|
import { resourceLimits, type WebGLContext3DLimits } from "./limits.js";
|
|
37
41
|
import {
|
|
38
42
|
type CopyProgram,
|
|
@@ -40,6 +44,7 @@ import {
|
|
|
40
44
|
type ProgramState,
|
|
41
45
|
uploadProgram,
|
|
42
46
|
} from "./programs.js";
|
|
47
|
+
import { readBackBuffer } from "./readback.js";
|
|
43
48
|
import {
|
|
44
49
|
BLEND_FUNCTIONS,
|
|
45
50
|
DEPTH_FUNCTIONS,
|
|
@@ -56,6 +61,7 @@ import {
|
|
|
56
61
|
targetFramebuffer,
|
|
57
62
|
} from "./targets.js";
|
|
58
63
|
import {
|
|
64
|
+
storageFormat,
|
|
59
65
|
type TextureState,
|
|
60
66
|
uploadCompressedLevel,
|
|
61
67
|
uploadTexture,
|
|
@@ -68,8 +74,26 @@ const SAMPLER_FORMATS: Record<TextureFormat, number> = {
|
|
|
68
74
|
bgra: 0,
|
|
69
75
|
compressed: 1,
|
|
70
76
|
compressedAlpha: 2,
|
|
77
|
+
bgrPacked565: 0,
|
|
78
|
+
bgraPacked4444: 0,
|
|
79
|
+
rgbaHalfFloat: 0,
|
|
71
80
|
};
|
|
72
81
|
|
|
82
|
+
/**
|
|
83
|
+
* A clear colour's channel as Direct3D's 8-bit clear colour holds it, which AIR fills by
|
|
84
|
+
* truncation (0.5 clears to 127): within 0 to 1 and cut to its byte, NaN kept.
|
|
85
|
+
*/
|
|
86
|
+
function clearChannel(value: number): number {
|
|
87
|
+
if (!(value > 0)) {
|
|
88
|
+
return value < 0 ? 0 : value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return value >= 1 ? 1 : Math.floor(value * 255 + 1e-3) / 255;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** AGAL's wraps (clamp, repeat, clamp_u_repeat_v, repeat_u_clamp_v) as SAMPLER_WRAPS indices. */
|
|
95
|
+
const WRAPS = [0, 3, 2, 1];
|
|
96
|
+
|
|
73
97
|
interface Constants {
|
|
74
98
|
data: Float32Array;
|
|
75
99
|
bits: Uint32Array;
|
|
@@ -90,25 +114,38 @@ function constants(registers: number): Constants {
|
|
|
90
114
|
};
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* An owned WebGL2 context, deliberately independent of the 2D renderer. Its members that are
|
|
119
|
+
* neither private nor Context3D's are for back-buffer.ts, buffers.ts and readback.ts, which
|
|
120
|
+
* hold parts of it as functions over it.
|
|
121
|
+
*/
|
|
94
122
|
export class WebGLContext3D implements Context3D {
|
|
95
123
|
readonly gl: WebGL2RenderingContext;
|
|
96
|
-
|
|
124
|
+
/** @internal */
|
|
125
|
+
readonly drawState: GlState;
|
|
97
126
|
width = 0;
|
|
98
127
|
height = 0;
|
|
99
128
|
/**
|
|
100
129
|
* The back buffer's storage: its size, or less where the GPU allows less. The SWF still
|
|
101
130
|
* sees the size it asked for, drawn at this one and stretched to it when presented.
|
|
102
131
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
132
|
+
/** @internal */
|
|
133
|
+
drawnWidth = 0;
|
|
134
|
+
/** @internal */
|
|
135
|
+
drawnHeight = 0;
|
|
136
|
+
/** @internal */
|
|
137
|
+
gpuMost: Int32Array | null = null;
|
|
106
138
|
disposed = false;
|
|
107
139
|
samplerFormats = true;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
140
|
+
profile: Profile = "baseline";
|
|
141
|
+
/** @internal */
|
|
142
|
+
readonly buffers = new Map<Buffer3D, BufferState>();
|
|
143
|
+
/** @internal */
|
|
144
|
+
vertexBuffers = 0;
|
|
145
|
+
/** @internal */
|
|
146
|
+
bufferBytes = 0;
|
|
147
|
+
/** @internal */
|
|
148
|
+
vertexBufferBytes = 0;
|
|
112
149
|
private readonly programs = new Map<Program3D, ProgramState>();
|
|
113
150
|
private readonly textures = new Map<Texture3D, TextureState>();
|
|
114
151
|
private readonly textureCounts = new Float64Array(3);
|
|
@@ -120,10 +157,18 @@ export class WebGLContext3D implements Context3D {
|
|
|
120
157
|
private copy2D: CopyProgram | null = null;
|
|
121
158
|
private copyCube: CopyProgram | null = null;
|
|
122
159
|
private readonly targetSamples = new Int8Array(10).fill(-1);
|
|
123
|
-
|
|
160
|
+
/** @internal */
|
|
161
|
+
renderTexture: Texture3D | null = null;
|
|
124
162
|
private renderDepth = false;
|
|
125
163
|
private renderFace = 0;
|
|
126
|
-
|
|
164
|
+
/** Colour outputs 1 to 3, drawn into with renderTexture, and their cube faces. */
|
|
165
|
+
private readonly outputs: (Texture3D | null)[] = [null, null, null];
|
|
166
|
+
private readonly outputFaces = new Uint8Array(3);
|
|
167
|
+
private outputsChanged = false;
|
|
168
|
+
/** Whether any of outputs is bound, so that a draw alone into its target looks at none. */
|
|
169
|
+
private outputsBound = false;
|
|
170
|
+
private floatTargets: boolean | undefined;
|
|
171
|
+
private readonly textureSlots: (Texture3D | null)[] = new Array(SAMPLER_SLOTS).fill(null);
|
|
127
172
|
private textureMask = 0;
|
|
128
173
|
private readonly samplers: (WebGLSampler | null)[] = new Array(96).fill(null);
|
|
129
174
|
// Two wrap bits, three filter bits and two mip bits fit each slot in one byte.
|
|
@@ -131,20 +176,31 @@ export class WebGLContext3D implements Context3D {
|
|
|
131
176
|
private anisotropy: EXT_texture_filter_anisotropic | null | undefined;
|
|
132
177
|
private s3tc: WEBGL_compressed_texture_s3tc | null | undefined;
|
|
133
178
|
private maxAnisotropy = 1;
|
|
134
|
-
|
|
179
|
+
/** @internal */
|
|
180
|
+
readonly vertices: (VertexBinding | null)[] = new Array(VERTEX_SLOTS).fill(null);
|
|
135
181
|
private enabledAttributes = 0;
|
|
136
|
-
|
|
137
|
-
|
|
182
|
+
/** @internal */
|
|
183
|
+
indexBuffer: WebGLBuffer | null = null;
|
|
184
|
+
/** @internal */
|
|
185
|
+
readonly texturesApplied = new Uint8Array(SAMPLER_SLOTS);
|
|
138
186
|
private selected: Program3D | null = null;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
187
|
+
/** @internal */
|
|
188
|
+
framebuffer: WebGLFramebuffer | null = null;
|
|
189
|
+
/** @internal */
|
|
190
|
+
texture: WebGLTexture | null = null;
|
|
191
|
+
/** @internal */
|
|
192
|
+
resolveFramebuffer: WebGLFramebuffer | null = null;
|
|
193
|
+
/** @internal */
|
|
194
|
+
colorBuffer: WebGLRenderbuffer | null = null;
|
|
195
|
+
/** @internal */
|
|
196
|
+
resolveDirty = false;
|
|
197
|
+
/** @internal */
|
|
198
|
+
depthBuffer: WebGLRenderbuffer | null = null;
|
|
199
|
+
/** @internal */
|
|
200
|
+
antiAlias = 0;
|
|
146
201
|
/** The back buffer's pixels to a CSS pixel and to a scissor rectangle's unit. */
|
|
147
|
-
|
|
202
|
+
/** @internal */
|
|
203
|
+
scale = 1;
|
|
148
204
|
private depthWrite = true;
|
|
149
205
|
private depthCompare: CompareMode = "less";
|
|
150
206
|
private blendEnabled = false;
|
|
@@ -158,18 +214,23 @@ export class WebGLContext3D implements Context3D {
|
|
|
158
214
|
private stencilReference = 0;
|
|
159
215
|
private stencilRead = 255;
|
|
160
216
|
private stencilWrite = 255;
|
|
161
|
-
|
|
217
|
+
/** @internal */
|
|
218
|
+
scissor = false;
|
|
162
219
|
private scissorLeft = 0;
|
|
163
220
|
private scissorTop = 0;
|
|
164
221
|
private scissorRight = 0;
|
|
165
222
|
private scissorBottom = 0;
|
|
166
223
|
private scissorOutside = false;
|
|
167
|
-
private readonly vertexConstants = constants(
|
|
168
|
-
private readonly fragmentConstants = constants(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
224
|
+
private readonly vertexConstants = constants(VERTEX_CONSTANTS);
|
|
225
|
+
private readonly fragmentConstants = constants(FRAGMENT_CONSTANTS);
|
|
226
|
+
/** @internal */
|
|
227
|
+
pixels = new Uint8Array(0);
|
|
228
|
+
/** @internal */
|
|
229
|
+
topDown = new Uint8Array(0);
|
|
230
|
+
/** @internal */
|
|
231
|
+
readonly vao: WebGLVertexArrayObject;
|
|
232
|
+
/** @internal */
|
|
233
|
+
readonly limits: Readonly<WebGLContext3DLimits>;
|
|
173
234
|
private dxtEncoder: DxtEncoder | null = null;
|
|
174
235
|
|
|
175
236
|
/** The host's whole-level encoder; a refusal retains the TypeScript path. */
|
|
@@ -211,13 +272,15 @@ export class WebGLContext3D implements Context3D {
|
|
|
211
272
|
gl.disable(gl.DITHER);
|
|
212
273
|
}
|
|
213
274
|
|
|
214
|
-
|
|
275
|
+
/** @internal */
|
|
276
|
+
alive(): void {
|
|
215
277
|
if (this.disposed || this.gl.isContextLost()) {
|
|
216
278
|
throw new Context3DError("disposed", "Context3D experiment: context disposed or lost");
|
|
217
279
|
}
|
|
218
280
|
}
|
|
219
281
|
|
|
220
|
-
|
|
282
|
+
/** @internal */
|
|
283
|
+
target(back = false, discardColor = false): void {
|
|
221
284
|
this.alive();
|
|
222
285
|
if (!this.framebuffer) {
|
|
223
286
|
throw new Context3DError(
|
|
@@ -236,6 +299,9 @@ export class WebGLContext3D implements Context3D {
|
|
|
236
299
|
}
|
|
237
300
|
}
|
|
238
301
|
this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
|
|
302
|
+
if (this.outputsChanged || state?.outputs) {
|
|
303
|
+
this.attachOutputs(state);
|
|
304
|
+
}
|
|
239
305
|
this.drawState.viewport(
|
|
240
306
|
0,
|
|
241
307
|
0,
|
|
@@ -245,220 +311,18 @@ export class WebGLContext3D implements Context3D {
|
|
|
245
311
|
}
|
|
246
312
|
|
|
247
313
|
/** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
|
|
248
|
-
|
|
314
|
+
/** @internal */
|
|
315
|
+
bindFramebuffer(target: number, framebuffer: WebGLFramebuffer | null): void {
|
|
249
316
|
this.drawState.forget();
|
|
250
317
|
this.gl.bindFramebuffer(target, framebuffer);
|
|
251
318
|
}
|
|
252
319
|
|
|
253
320
|
configure(width: number, height: number, depth = false, antiAlias = 0, scale = 1): void {
|
|
254
|
-
this
|
|
255
|
-
range(32, width - 32, MOST_BACK_BUFFER);
|
|
256
|
-
range(32, height - 32, MOST_BACK_BUFFER);
|
|
257
|
-
if (this.renderTexture) {
|
|
258
|
-
throw new Context3DError(
|
|
259
|
-
"operation",
|
|
260
|
-
"Context3D experiment: resize while rendering to texture",
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// AIR preserves color, depth and bindings when the request is unchanged.
|
|
265
|
-
// Shadow passes can issue this every frame when returning to the back buffer.
|
|
266
|
-
if (
|
|
267
|
-
this.framebuffer &&
|
|
268
|
-
width === this.width &&
|
|
269
|
-
height === this.height &&
|
|
270
|
-
depth === (this.depthBuffer !== null) &&
|
|
271
|
-
antiAlias === this.antiAlias
|
|
272
|
-
) {
|
|
273
|
-
if (scale !== this.scale) {
|
|
274
|
-
this.scale = scale;
|
|
275
|
-
this.showAt();
|
|
276
|
-
this.applyScissor();
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const samples = selectSamples(this.gl, antiAlias, depth);
|
|
283
|
-
const gl = this.gl;
|
|
284
|
-
const most = this.storageLimit();
|
|
285
|
-
const drawnWidth = Math.min(width, most[0]);
|
|
286
|
-
const drawnHeight = Math.min(height, most[1]);
|
|
287
|
-
const texture = gl.createTexture();
|
|
288
|
-
const framebuffer = gl.createFramebuffer();
|
|
289
|
-
const depthBuffer = depth ? gl.createRenderbuffer() : null;
|
|
290
|
-
const resolveFramebuffer = samples ? gl.createFramebuffer() : null;
|
|
291
|
-
const colorBuffer = samples ? gl.createRenderbuffer() : null;
|
|
292
|
-
try {
|
|
293
|
-
if (
|
|
294
|
-
!texture ||
|
|
295
|
-
!framebuffer ||
|
|
296
|
-
(depth && !depthBuffer) ||
|
|
297
|
-
(samples && (!resolveFramebuffer || !colorBuffer))
|
|
298
|
-
) {
|
|
299
|
-
throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
this.texturesApplied[0] = 0;
|
|
303
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
304
|
-
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
305
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
306
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
307
|
-
gl.texImage2D(
|
|
308
|
-
gl.TEXTURE_2D,
|
|
309
|
-
0,
|
|
310
|
-
gl.RGBA8,
|
|
311
|
-
drawnWidth,
|
|
312
|
-
drawnHeight,
|
|
313
|
-
0,
|
|
314
|
-
gl.RGBA,
|
|
315
|
-
gl.UNSIGNED_BYTE,
|
|
316
|
-
null,
|
|
317
|
-
);
|
|
318
|
-
this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
|
|
319
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
320
|
-
if (resolveFramebuffer) {
|
|
321
|
-
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
322
|
-
throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
326
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
|
|
327
|
-
gl.renderbufferStorageMultisample(
|
|
328
|
-
gl.RENDERBUFFER,
|
|
329
|
-
samples,
|
|
330
|
-
gl.RGBA8,
|
|
331
|
-
drawnWidth,
|
|
332
|
-
drawnHeight,
|
|
333
|
-
);
|
|
334
|
-
gl.framebufferRenderbuffer(
|
|
335
|
-
gl.FRAMEBUFFER,
|
|
336
|
-
gl.COLOR_ATTACHMENT0,
|
|
337
|
-
gl.RENDERBUFFER,
|
|
338
|
-
colorBuffer,
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
if (depthBuffer) {
|
|
342
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
|
|
343
|
-
if (samples) {
|
|
344
|
-
gl.renderbufferStorageMultisample(
|
|
345
|
-
gl.RENDERBUFFER,
|
|
346
|
-
samples,
|
|
347
|
-
gl.DEPTH24_STENCIL8,
|
|
348
|
-
drawnWidth,
|
|
349
|
-
drawnHeight,
|
|
350
|
-
);
|
|
351
|
-
} else {
|
|
352
|
-
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, drawnWidth, drawnHeight);
|
|
353
|
-
}
|
|
354
|
-
gl.framebufferRenderbuffer(
|
|
355
|
-
gl.FRAMEBUFFER,
|
|
356
|
-
gl.DEPTH_STENCIL_ATTACHMENT,
|
|
357
|
-
gl.RENDERBUFFER,
|
|
358
|
-
depthBuffer,
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
362
|
-
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
363
|
-
throw new Context3DError("operation", "Context3D experiment: incomplete back buffer");
|
|
364
|
-
}
|
|
365
|
-
} catch (error) {
|
|
366
|
-
gl.deleteTexture(texture);
|
|
367
|
-
gl.deleteFramebuffer(framebuffer);
|
|
368
|
-
gl.deleteFramebuffer(resolveFramebuffer);
|
|
369
|
-
gl.deleteRenderbuffer(depthBuffer);
|
|
370
|
-
gl.deleteRenderbuffer(colorBuffer);
|
|
371
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
372
|
-
this.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
373
|
-
throw error;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
gl.deleteTexture(this.texture);
|
|
377
|
-
gl.deleteFramebuffer(this.framebuffer);
|
|
378
|
-
gl.deleteFramebuffer(this.resolveFramebuffer);
|
|
379
|
-
gl.deleteRenderbuffer(this.depthBuffer);
|
|
380
|
-
gl.deleteRenderbuffer(this.colorBuffer);
|
|
381
|
-
this.texture = texture;
|
|
382
|
-
this.framebuffer = framebuffer;
|
|
383
|
-
this.resolveFramebuffer = resolveFramebuffer;
|
|
384
|
-
this.colorBuffer = colorBuffer;
|
|
385
|
-
this.depthBuffer = depthBuffer;
|
|
386
|
-
this.antiAlias = antiAlias;
|
|
387
|
-
this.width = width;
|
|
388
|
-
this.height = height;
|
|
389
|
-
this.drawnWidth = drawnWidth;
|
|
390
|
-
this.drawnHeight = drawnHeight;
|
|
391
|
-
this.scale = scale;
|
|
392
|
-
gl.canvas.width = drawnWidth;
|
|
393
|
-
gl.canvas.height = drawnHeight;
|
|
394
|
-
this.showAt();
|
|
395
|
-
// Readback's storage waits for a read: a back buffer as large as a 4K screen is 33 MB each.
|
|
396
|
-
this.pixels = new Uint8Array(0);
|
|
397
|
-
this.topDown = new Uint8Array(0);
|
|
398
|
-
this.applyScissor();
|
|
399
|
-
this.clear(0, 0, 0, 1);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Shown at the size asked for, in CSS pixels, as an unstyled canvas of that size would be,
|
|
404
|
-
* divided by its scale: a best-resolution back buffer shows its pixels at the screen's.
|
|
405
|
-
*/
|
|
406
|
-
private showAt(): void {
|
|
407
|
-
const style = (this.gl.canvas as Partial<HTMLCanvasElement>).style;
|
|
408
|
-
if (!style) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const plain = this.scale === 1;
|
|
413
|
-
style.width = plain && this.drawnWidth === this.width ? "" : `${this.width / this.scale}px`;
|
|
414
|
-
style.height = plain && this.drawnHeight === this.height ? "" : `${this.height / this.scale}px`;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/** The largest back buffer storage the GPU gives: a texture, a renderbuffer and a viewport. */
|
|
418
|
-
private storageLimit(): Int32Array {
|
|
419
|
-
if (!this.gpuMost) {
|
|
420
|
-
const gl = this.gl;
|
|
421
|
-
const viewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS) as Int32Array;
|
|
422
|
-
const side = Math.min(
|
|
423
|
-
gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
|
|
424
|
-
gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
|
|
425
|
-
);
|
|
426
|
-
this.gpuMost = Int32Array.of(Math.min(side, viewport[0]), Math.min(side, viewport[1]));
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return this.gpuMost;
|
|
321
|
+
configureBackBuffer(this, width, height, depth, antiAlias, scale);
|
|
430
322
|
}
|
|
431
323
|
|
|
432
324
|
unconfigure(): void {
|
|
433
|
-
this
|
|
434
|
-
if (this.renderTexture) {
|
|
435
|
-
throw new Context3DError(
|
|
436
|
-
"operation",
|
|
437
|
-
"Context3D experiment: resize while rendering to texture",
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const gl = this.gl;
|
|
442
|
-
gl.deleteTexture(this.texture);
|
|
443
|
-
gl.deleteFramebuffer(this.framebuffer);
|
|
444
|
-
gl.deleteFramebuffer(this.resolveFramebuffer);
|
|
445
|
-
gl.deleteRenderbuffer(this.depthBuffer);
|
|
446
|
-
gl.deleteRenderbuffer(this.colorBuffer);
|
|
447
|
-
this.drawState.forget();
|
|
448
|
-
this.texture = null;
|
|
449
|
-
this.framebuffer = null;
|
|
450
|
-
this.resolveFramebuffer = null;
|
|
451
|
-
this.colorBuffer = null;
|
|
452
|
-
this.depthBuffer = null;
|
|
453
|
-
this.resolveDirty = false;
|
|
454
|
-
this.antiAlias = 0;
|
|
455
|
-
this.scale = 1;
|
|
456
|
-
this.width = 0;
|
|
457
|
-
this.height = 0;
|
|
458
|
-
this.drawnWidth = 0;
|
|
459
|
-
this.drawnHeight = 0;
|
|
460
|
-
this.pixels = new Uint8Array(0);
|
|
461
|
-
this.topDown = new Uint8Array(0);
|
|
325
|
+
unconfigureBackBuffer(this);
|
|
462
326
|
}
|
|
463
327
|
|
|
464
328
|
clear(
|
|
@@ -474,7 +338,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
474
338
|
const gl = this.gl;
|
|
475
339
|
let bits = 0;
|
|
476
340
|
if (mask & 1) {
|
|
477
|
-
|
|
341
|
+
// A float target too: AIR clears through Direct3D's 8-bit colour.
|
|
342
|
+
gl.clearColor(
|
|
343
|
+
clearChannel(red),
|
|
344
|
+
clearChannel(green),
|
|
345
|
+
clearChannel(blue),
|
|
346
|
+
clearChannel(alpha),
|
|
347
|
+
);
|
|
478
348
|
bits |= gl.COLOR_BUFFER_BIT;
|
|
479
349
|
this.changedTarget();
|
|
480
350
|
}
|
|
@@ -663,7 +533,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
663
533
|
this.applyScissor();
|
|
664
534
|
}
|
|
665
535
|
|
|
666
|
-
|
|
536
|
+
/** @internal */
|
|
537
|
+
applyScissor(): void {
|
|
667
538
|
const gl = this.gl;
|
|
668
539
|
if (!this.scissor) {
|
|
669
540
|
this.scissorOutside = false;
|
|
@@ -760,112 +631,11 @@ export class WebGLContext3D implements Context3D {
|
|
|
760
631
|
}
|
|
761
632
|
|
|
762
633
|
createBuffer(count: number, stride: number, dynamic = false): Buffer3D {
|
|
763
|
-
this
|
|
764
|
-
range(1, count - 1, stride === 0 ? 524287 : 65535);
|
|
765
|
-
range(0, stride, 64);
|
|
766
|
-
const countLimit = stride === 0 ? this.limits.maxIndexBuffers : this.limits.maxVertexBuffers;
|
|
767
|
-
const used = stride === 0 ? this.buffers.size - this.vertexBuffers : this.vertexBuffers;
|
|
768
|
-
if (this.buffers.size >= this.limits.maxBuffers || used >= countLimit) {
|
|
769
|
-
throw new Context3DError("limit", "Context3D experiment: buffer limit");
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
const bytes = count * (stride === 0 ? 2 : stride * 4);
|
|
773
|
-
const usedBytes =
|
|
774
|
-
stride === 0 ? this.bufferBytes - this.vertexBufferBytes : this.vertexBufferBytes;
|
|
775
|
-
const byteLimit =
|
|
776
|
-
stride === 0 ? this.limits.maxIndexBufferBytes : this.limits.maxVertexBufferBytes;
|
|
777
|
-
if (this.bufferBytes + bytes > this.limits.maxBufferBytes || usedBytes + bytes > byteLimit) {
|
|
778
|
-
throw new Context3DError("limit", "Context3D experiment: buffer memory limit");
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
const gl = this.gl;
|
|
782
|
-
const handle = gl.createBuffer();
|
|
783
|
-
if (!handle) {
|
|
784
|
-
throw new Context3DError("bufferAllocation", "Context3D experiment: cannot allocate buffer");
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
|
|
788
|
-
if (stride === 0) {
|
|
789
|
-
this.indexBuffer = null;
|
|
790
|
-
}
|
|
791
|
-
gl.bindVertexArray(this.vao);
|
|
792
|
-
gl.bindBuffer(target, handle);
|
|
793
|
-
gl.bufferData(target, bytes, dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);
|
|
794
|
-
const state: BufferState = {
|
|
795
|
-
handle,
|
|
796
|
-
indices: stride === 0 ? new Uint16Array(count) : null,
|
|
797
|
-
uploaded: new Uint8Array(count),
|
|
798
|
-
prefix: 0,
|
|
799
|
-
version: 0,
|
|
800
|
-
checkedVersion: -1,
|
|
801
|
-
checkedFirst: 0,
|
|
802
|
-
checkedCount: 0,
|
|
803
|
-
maximum: -1,
|
|
804
|
-
ranges: null,
|
|
805
|
-
};
|
|
806
|
-
const buffer: Buffer3D = {
|
|
807
|
-
count,
|
|
808
|
-
stride,
|
|
809
|
-
upload: (values, start, amount) => {
|
|
810
|
-
this.alive();
|
|
811
|
-
this.buffer(buffer);
|
|
812
|
-
try {
|
|
813
|
-
uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
|
|
814
|
-
} finally {
|
|
815
|
-
// Source getters can draw before the upload replaces the VAO's index binding.
|
|
816
|
-
if (stride === 0) {
|
|
817
|
-
this.indexBuffer = null;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
},
|
|
821
|
-
uploadBytes: (data, offset, start, amount) => {
|
|
822
|
-
this.alive();
|
|
823
|
-
this.buffer(buffer);
|
|
824
|
-
try {
|
|
825
|
-
uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
|
|
826
|
-
} finally {
|
|
827
|
-
if (stride === 0) {
|
|
828
|
-
this.indexBuffer = null;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
},
|
|
832
|
-
dispose: () => {
|
|
833
|
-
if (!this.buffers.delete(buffer)) {
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (this.indexBuffer === handle) {
|
|
838
|
-
this.indexBuffer = null;
|
|
839
|
-
}
|
|
840
|
-
gl.deleteBuffer(handle);
|
|
841
|
-
this.bufferBytes -= bytes;
|
|
842
|
-
if (stride !== 0) {
|
|
843
|
-
this.vertexBuffers--;
|
|
844
|
-
this.vertexBufferBytes -= bytes;
|
|
845
|
-
}
|
|
846
|
-
state.indices = null;
|
|
847
|
-
state.ranges = null;
|
|
848
|
-
state.uploaded = new Uint8Array(0);
|
|
849
|
-
for (let i = 0; i < this.vertices.length; i++) {
|
|
850
|
-
const binding = this.vertices[i];
|
|
851
|
-
if (binding?.buffer === buffer) {
|
|
852
|
-
this.vertices[i] = null;
|
|
853
|
-
} else if (binding?.checkedIndex === state) {
|
|
854
|
-
binding.checkedIndex = null;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
},
|
|
858
|
-
};
|
|
859
|
-
this.buffers.set(buffer, state);
|
|
860
|
-
if (stride !== 0) {
|
|
861
|
-
this.vertexBuffers++;
|
|
862
|
-
this.vertexBufferBytes += bytes;
|
|
863
|
-
}
|
|
864
|
-
this.bufferBytes += bytes;
|
|
865
|
-
return buffer;
|
|
634
|
+
return allocateBuffer(this, count, stride, dynamic);
|
|
866
635
|
}
|
|
867
636
|
|
|
868
|
-
|
|
637
|
+
/** @internal */
|
|
638
|
+
buffer(buffer: Buffer3D): BufferState {
|
|
869
639
|
const state = this.buffers.get(buffer);
|
|
870
640
|
if (!state) {
|
|
871
641
|
throw new Context3DError(
|
|
@@ -891,6 +661,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
891
661
|
attributeMask: 0,
|
|
892
662
|
vertex: [],
|
|
893
663
|
vertexBank: null,
|
|
664
|
+
outputs: 1,
|
|
894
665
|
fragment: [],
|
|
895
666
|
samplers: [],
|
|
896
667
|
samplerMask: 0,
|
|
@@ -902,11 +673,17 @@ export class WebGLContext3D implements Context3D {
|
|
|
902
673
|
vertexVersion: -1,
|
|
903
674
|
};
|
|
904
675
|
const program: Program3D = {
|
|
676
|
+
get outputs() {
|
|
677
|
+
return state.outputs;
|
|
678
|
+
},
|
|
679
|
+
get attributes() {
|
|
680
|
+
return state.attributeMask;
|
|
681
|
+
},
|
|
905
682
|
upload: (vertex, fragment) => {
|
|
906
683
|
this.alive();
|
|
907
684
|
this.program(program);
|
|
908
685
|
this.drawState.forget();
|
|
909
|
-
uploadProgram(this.gl, state, vertex, fragment);
|
|
686
|
+
uploadProgram(this.gl, state, vertex, fragment, this.profile);
|
|
910
687
|
|
|
911
688
|
if (this.selected === program) {
|
|
912
689
|
this.setProgram(program);
|
|
@@ -968,8 +745,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
968
745
|
return this.allocateTexture(width, height, cube, false, format, streaming);
|
|
969
746
|
}
|
|
970
747
|
|
|
971
|
-
createRectangleTexture(width: number, height: number): Texture3D {
|
|
972
|
-
return this.allocateTexture(width, height, false, true,
|
|
748
|
+
createRectangleTexture(width: number, height: number, format: TextureFormat = "bgra"): Texture3D {
|
|
749
|
+
return this.allocateTexture(width, height, false, true, format);
|
|
973
750
|
}
|
|
974
751
|
|
|
975
752
|
/** GL's internal format for a compressed texture's S3TC, or 0 where the context takes none. */
|
|
@@ -977,7 +754,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
977
754
|
if (this.s3tc === undefined) {
|
|
978
755
|
this.s3tc = this.gl.getExtension("WEBGL_compressed_texture_s3tc");
|
|
979
756
|
}
|
|
980
|
-
if (!this.s3tc || format
|
|
757
|
+
if (!this.s3tc || !compressedFormat(format)) {
|
|
981
758
|
return 0;
|
|
982
759
|
}
|
|
983
760
|
|
|
@@ -1003,7 +780,9 @@ export class WebGLContext3D implements Context3D {
|
|
|
1003
780
|
for (let level = 0; level < levels; level++) {
|
|
1004
781
|
const w = Math.max(1, width >> level);
|
|
1005
782
|
const h = Math.max(1, height >> level);
|
|
1006
|
-
size +=
|
|
783
|
+
size += compressedFormat(format)
|
|
784
|
+
? blockBytes(w, h, format === "compressedAlpha")
|
|
785
|
+
: w * h * (TEXEL_BYTES[format] ?? 4);
|
|
1007
786
|
}
|
|
1008
787
|
|
|
1009
788
|
return size * faces;
|
|
@@ -1018,12 +797,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
1018
797
|
streaming = 0,
|
|
1019
798
|
): Texture3D {
|
|
1020
799
|
this.alive();
|
|
1021
|
-
|
|
1022
|
-
|
|
800
|
+
const limits = profileLimits(this.profile);
|
|
801
|
+
const most = rectangle ? limits.rectangleSize : limits.textureSize;
|
|
802
|
+
range(1, width - 1, most);
|
|
803
|
+
range(1, height - 1, most);
|
|
1023
804
|
if (cube && width !== height) {
|
|
1024
805
|
throw new Context3DError("textureShape", "Context3D cube faces must be square");
|
|
1025
806
|
}
|
|
1026
|
-
if (cube && width >
|
|
807
|
+
if (cube && width > MOST_CUBE) {
|
|
1027
808
|
throw new Context3DError("textureSize", "Context3D cube faces must be at most 1024");
|
|
1028
809
|
}
|
|
1029
810
|
|
|
@@ -1036,15 +817,15 @@ export class WebGLContext3D implements Context3D {
|
|
|
1036
817
|
"Context3D texture size must be a power of two",
|
|
1037
818
|
);
|
|
1038
819
|
}
|
|
1039
|
-
if (format
|
|
820
|
+
if (compressedFormat(format) && rectangle) {
|
|
1040
821
|
throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
|
|
1041
822
|
}
|
|
1042
|
-
if (format
|
|
823
|
+
if (compressedFormat(format) && (width < 4 || height < 4)) {
|
|
1043
824
|
throw new Context3DError("compressedSize", "Context3D compressed textures are at least 4x4");
|
|
1044
825
|
}
|
|
1045
826
|
|
|
1046
827
|
const gl = this.gl;
|
|
1047
|
-
const storage = this.s3tcFormat(format) || gl
|
|
828
|
+
const storage = this.s3tcFormat(format) || storageFormat(gl, format);
|
|
1048
829
|
const size = this.chainBytes(width, height, levels, faces, format);
|
|
1049
830
|
const kind = cube ? 2 : rectangle ? 1 : 0;
|
|
1050
831
|
const countLimit = cube
|
|
@@ -1060,7 +841,8 @@ export class WebGLContext3D implements Context3D {
|
|
|
1060
841
|
// AIR reserves half the base size for mips, independently of the exact
|
|
1061
842
|
// chain storage, and charges DXT1 an eighth of BGRA and DXT5 a quarter.
|
|
1062
843
|
const quotaBytes =
|
|
1063
|
-
(width * height * faces * (rectangle ? 4 : 6)) /
|
|
844
|
+
(width * height * faces * (rectangle ? 4 : 6) * (TEXEL_BYTES[format] ?? 4)) /
|
|
845
|
+
4 /
|
|
1064
846
|
(format === "compressed" ? 8 : format === "compressedAlpha" ? 4 : 1);
|
|
1065
847
|
if (
|
|
1066
848
|
this.textures.size >= this.limits.maxTextures ||
|
|
@@ -1095,6 +877,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1095
877
|
multisample: null,
|
|
1096
878
|
samples: 0,
|
|
1097
879
|
antiAlias: 0,
|
|
880
|
+
outputs: 0,
|
|
1098
881
|
base: 0,
|
|
1099
882
|
};
|
|
1100
883
|
const texture: Texture3D = {
|
|
@@ -1137,10 +920,10 @@ export class WebGLContext3D implements Context3D {
|
|
|
1137
920
|
uploadCompressed: (blocks, level = 0, face = 0) => {
|
|
1138
921
|
this.alive();
|
|
1139
922
|
this.textureState(texture);
|
|
1140
|
-
if (format
|
|
923
|
+
if (!compressedFormat(format)) {
|
|
1141
924
|
throw new Context3DError(
|
|
1142
925
|
"textureUpload",
|
|
1143
|
-
"Context3D experiment:
|
|
926
|
+
"Context3D experiment: uncompressed texture given blocks",
|
|
1144
927
|
);
|
|
1145
928
|
}
|
|
1146
929
|
|
|
@@ -1180,6 +963,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
1180
963
|
state.mipFramebuffer = null;
|
|
1181
964
|
state.depthBuffer = null;
|
|
1182
965
|
|
|
966
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
967
|
+
if (this.outputs[i] === texture) {
|
|
968
|
+
this.outputs[i] = null;
|
|
969
|
+
this.outputsChanged = true;
|
|
970
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
1183
973
|
for (let i = 0; i < this.textureSlots.length; i++) {
|
|
1184
974
|
if (this.textureSlots[i] === texture) {
|
|
1185
975
|
this.texturesApplied[i] = 0;
|
|
@@ -1335,6 +1125,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1335
1125
|
depth,
|
|
1336
1126
|
this.depthWrite,
|
|
1337
1127
|
this.stencilWrite,
|
|
1128
|
+
state.storage,
|
|
1338
1129
|
);
|
|
1339
1130
|
if (same) {
|
|
1340
1131
|
if (newDepth) {
|
|
@@ -1370,12 +1161,13 @@ export class WebGLContext3D implements Context3D {
|
|
|
1370
1161
|
const gl = this.gl;
|
|
1371
1162
|
if (texture) {
|
|
1372
1163
|
this.textureState(texture);
|
|
1373
|
-
if (texture.format
|
|
1164
|
+
if (compressedFormat(texture.format)) {
|
|
1374
1165
|
throw new Context3DError(
|
|
1375
1166
|
"operation",
|
|
1376
1167
|
"Context3D experiment: compressed textures are no render targets",
|
|
1377
1168
|
);
|
|
1378
1169
|
}
|
|
1170
|
+
this.renderFormat(texture.format);
|
|
1379
1171
|
}
|
|
1380
1172
|
const samples = texture ? this.multisamples(antiAlias, depth) : 0;
|
|
1381
1173
|
this.finishTarget();
|
|
@@ -1521,6 +1313,12 @@ export class WebGLContext3D implements Context3D {
|
|
|
1521
1313
|
}
|
|
1522
1314
|
|
|
1523
1315
|
private changedTarget(): void {
|
|
1316
|
+
for (let i = 0; this.outputsBound && i < this.outputs.length; i++) {
|
|
1317
|
+
const output = this.outputs[i];
|
|
1318
|
+
if (output) {
|
|
1319
|
+
this.textureState(output).dirty = true;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1524
1322
|
if (this.renderTexture) {
|
|
1525
1323
|
const state = this.textureState(this.renderTexture);
|
|
1526
1324
|
state.dirty = true;
|
|
@@ -1537,22 +1335,34 @@ export class WebGLContext3D implements Context3D {
|
|
|
1537
1335
|
}
|
|
1538
1336
|
|
|
1539
1337
|
private finishTarget(): void {
|
|
1540
|
-
if (
|
|
1541
|
-
|
|
1338
|
+
if (this.renderTexture) {
|
|
1339
|
+
this.finishTexture(this.renderTexture, this.renderFace);
|
|
1340
|
+
}
|
|
1341
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1342
|
+
const output = this.outputs[i];
|
|
1343
|
+
if (output) {
|
|
1344
|
+
this.finishTexture(output, this.outputFaces[i]);
|
|
1345
|
+
}
|
|
1542
1346
|
}
|
|
1543
|
-
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
/** A texture drawn into made whole again: resolved, and its mips regenerated. */
|
|
1350
|
+
private finishTexture(texture: Texture3D, renderFace: number): void {
|
|
1544
1351
|
const state = this.textureState(texture);
|
|
1545
1352
|
if (state.dirty) {
|
|
1546
1353
|
const gl = this.gl;
|
|
1547
1354
|
this.drawState.forget();
|
|
1548
1355
|
this.texturesApplied[0] = 0;
|
|
1549
|
-
resolveTarget(this.gl, texture, state,
|
|
1356
|
+
resolveTarget(this.gl, texture, state, renderFace, this.scissor);
|
|
1550
1357
|
gl.activeTexture(gl.TEXTURE0);
|
|
1551
1358
|
if (texture.cube) {
|
|
1359
|
+
// An extra colour output may never have been a target of its own.
|
|
1360
|
+
state.framebuffer ??= gl.createFramebuffer();
|
|
1361
|
+
state.mipFramebuffer ??= gl.createFramebuffer();
|
|
1552
1362
|
// generateMipmap(CUBE_MAP) would overwrite manually uploaded mips on
|
|
1553
1363
|
// untouched faces. AIR regenerates only the face just rendered.
|
|
1554
1364
|
gl.disable(gl.SCISSOR_TEST);
|
|
1555
|
-
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X +
|
|
1365
|
+
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderFace;
|
|
1556
1366
|
// The base-size depth attachment cannot accompany smaller mip images.
|
|
1557
1367
|
this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
1558
1368
|
if (state.depthAttached) {
|
|
@@ -1614,14 +1424,99 @@ export class WebGLContext3D implements Context3D {
|
|
|
1614
1424
|
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
1615
1425
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
1616
1426
|
}
|
|
1617
|
-
state.uploaded[
|
|
1427
|
+
state.uploaded[renderFace] = (1 << texture.levels) - 1;
|
|
1618
1428
|
state.dirty = false;
|
|
1619
1429
|
}
|
|
1620
1430
|
}
|
|
1621
1431
|
|
|
1432
|
+
/** Half floats are drawn into where the GPU's WebGL renders float colour, as every desktop one does. */
|
|
1433
|
+
private renderFormat(format: TextureFormat): void {
|
|
1434
|
+
if (format !== "rgbaHalfFloat") {
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
this.floatTargets ??= this.gl.getExtension("EXT_color_buffer_float") !== null;
|
|
1439
|
+
if (!this.floatTargets) {
|
|
1440
|
+
throw new Context3DError("unsupported", "Context3D: this GPU draws into no half floats");
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
setColorOutput(index: number, texture: Texture3D | null, face = 0): void {
|
|
1445
|
+
this.alive();
|
|
1446
|
+
range(index - 1, 1, this.outputs.length);
|
|
1447
|
+
if (texture) {
|
|
1448
|
+
this.textureState(texture);
|
|
1449
|
+
range(face, 1, texture.cube ? 6 : 1);
|
|
1450
|
+
if (compressedFormat(texture.format)) {
|
|
1451
|
+
throw new Context3DError(
|
|
1452
|
+
"operation",
|
|
1453
|
+
"Context3D experiment: compressed textures are no render targets",
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
this.renderFormat(texture.format);
|
|
1457
|
+
}
|
|
1458
|
+
if (this.outputs[index - 1] === texture && this.outputFaces[index - 1] === face) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
const previous = this.outputs[index - 1];
|
|
1463
|
+
if (previous) {
|
|
1464
|
+
this.finishTexture(previous, this.outputFaces[index - 1]);
|
|
1465
|
+
}
|
|
1466
|
+
this.outputs[index - 1] = texture;
|
|
1467
|
+
this.outputFaces[index - 1] = texture ? face : 0;
|
|
1468
|
+
this.outputsChanged = true;
|
|
1469
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* The current target's framebuffer with the extra colour outputs attached, or without
|
|
1474
|
+
* them: its draws write every output bound, and a program's unwritten ones are refused
|
|
1475
|
+
* before they draw.
|
|
1476
|
+
*/
|
|
1477
|
+
private attachOutputs(state: TextureState | null): void {
|
|
1478
|
+
const want = state ? this.outputMask() : 0;
|
|
1479
|
+
if (!state || (!this.outputsChanged && state.outputs === want)) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
if (want && state.samples) {
|
|
1483
|
+
throw new Context3DError("unsupported", "Context3D: antialiased colour outputs");
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
const gl = this.gl;
|
|
1487
|
+
const buffers: number[] = [gl.COLOR_ATTACHMENT0];
|
|
1488
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1489
|
+
const output = this.outputs[i];
|
|
1490
|
+
const handle = output ? this.textureState(output).handle : null;
|
|
1491
|
+
gl.framebufferTexture2D(
|
|
1492
|
+
gl.FRAMEBUFFER,
|
|
1493
|
+
gl.COLOR_ATTACHMENT1 + i,
|
|
1494
|
+
output?.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.outputFaces[i] : gl.TEXTURE_2D,
|
|
1495
|
+
handle,
|
|
1496
|
+
0,
|
|
1497
|
+
);
|
|
1498
|
+
buffers.push(output ? gl.COLOR_ATTACHMENT1 + i : gl.NONE);
|
|
1499
|
+
}
|
|
1500
|
+
gl.drawBuffers(buffers);
|
|
1501
|
+
state.outputs = want;
|
|
1502
|
+
this.outputsChanged = false;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
/** The extra colour outputs bound, a bit each from output 1. */
|
|
1506
|
+
private outputMask(): number {
|
|
1507
|
+
let mask = 0;
|
|
1508
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1509
|
+
if (this.outputs[i]) {
|
|
1510
|
+
mask |= 1 << i;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
return mask;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1622
1517
|
setTexture(index: number, texture: Texture3D | null): void {
|
|
1623
1518
|
this.alive();
|
|
1624
|
-
range(index, 1,
|
|
1519
|
+
range(index, 1, SAMPLER_SLOTS);
|
|
1625
1520
|
if (texture && !this.textureOwners.has(texture)) {
|
|
1626
1521
|
throw new Context3DError("disposed", "Context3D experiment: texture from another context");
|
|
1627
1522
|
}
|
|
@@ -1726,7 +1621,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1726
1621
|
if (!source.ignore) {
|
|
1727
1622
|
this.setSamplerState(
|
|
1728
1623
|
source.index,
|
|
1729
|
-
|
|
1624
|
+
WRAPS[source.wrap] | (source.filter << 2) | (source.mip << 5),
|
|
1730
1625
|
);
|
|
1731
1626
|
}
|
|
1732
1627
|
}
|
|
@@ -1734,7 +1629,7 @@ export class WebGLContext3D implements Context3D {
|
|
|
1734
1629
|
|
|
1735
1630
|
setVertex(index: number, buffer: Buffer3D | null, offset: number, format: VertexFormat): void {
|
|
1736
1631
|
this.alive();
|
|
1737
|
-
range(index, 1,
|
|
1632
|
+
range(index, 1, VERTEX_SLOTS);
|
|
1738
1633
|
if (buffer === null) {
|
|
1739
1634
|
this.vertices[index] = null;
|
|
1740
1635
|
return;
|
|
@@ -1857,13 +1752,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
1857
1752
|
|
|
1858
1753
|
const gl = this.gl;
|
|
1859
1754
|
this.drawState.bindVertexArray(this.vao);
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1755
|
+
// The streams enabled that the program does not read, highest first.
|
|
1756
|
+
let stale = this.enabledAttributes & ~state.attributeMask;
|
|
1757
|
+
while (stale) {
|
|
1758
|
+
const i = 31 - Math.clz32(stale);
|
|
1759
|
+
gl.disableVertexAttribArray(i);
|
|
1760
|
+
stale &= ~(1 << i);
|
|
1866
1761
|
}
|
|
1762
|
+
this.enabledAttributes &= state.attributeMask;
|
|
1867
1763
|
|
|
1868
1764
|
for (let i = 0; i < state.attributes.length; i++) {
|
|
1869
1765
|
const slot = state.attributes[i];
|
|
@@ -1909,7 +1805,14 @@ export class WebGLContext3D implements Context3D {
|
|
|
1909
1805
|
const binding = state.samplers[i];
|
|
1910
1806
|
const slot = binding.source.index;
|
|
1911
1807
|
const texture = this.textureSlots[slot];
|
|
1912
|
-
if (
|
|
1808
|
+
if (
|
|
1809
|
+
texture &&
|
|
1810
|
+
(texture === this.renderTexture ||
|
|
1811
|
+
(this.outputsBound &&
|
|
1812
|
+
(texture === this.outputs[0] ||
|
|
1813
|
+
texture === this.outputs[1] ||
|
|
1814
|
+
texture === this.outputs[2])))
|
|
1815
|
+
) {
|
|
1913
1816
|
throw new TextureBindingError("feedback");
|
|
1914
1817
|
}
|
|
1915
1818
|
const resource = texture && this.textures.get(texture);
|
|
@@ -1976,10 +1879,12 @@ export class WebGLContext3D implements Context3D {
|
|
|
1976
1879
|
|
|
1977
1880
|
if (state.vertexBank) {
|
|
1978
1881
|
if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
|
|
1979
|
-
gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
|
|
1882
|
+
gl.uniform4fv(state.vertexBank, this.vertexConstants.data, 0, INDIRECT_BANK * 4);
|
|
1980
1883
|
state.vertexVersion = this.vertexConstants.version;
|
|
1981
1884
|
}
|
|
1982
|
-
}
|
|
1885
|
+
}
|
|
1886
|
+
// An indirect program's own uniforms are only the registers past its bank.
|
|
1887
|
+
if (state.vertex.length) {
|
|
1983
1888
|
this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions, state.vertexValues);
|
|
1984
1889
|
}
|
|
1985
1890
|
this.uniforms(
|
|
@@ -2072,115 +1977,12 @@ export class WebGLContext3D implements Context3D {
|
|
|
2072
1977
|
}
|
|
2073
1978
|
}
|
|
2074
1979
|
|
|
2075
|
-
private resolve(): void {
|
|
2076
|
-
if (!this.resolveFramebuffer) {
|
|
2077
|
-
return;
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
const gl = this.gl;
|
|
2081
|
-
if (this.resolveDirty) {
|
|
2082
|
-
this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
|
|
2083
|
-
this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
|
|
2084
|
-
if (this.scissor) {
|
|
2085
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
2086
|
-
}
|
|
2087
|
-
gl.blitFramebuffer(
|
|
2088
|
-
0,
|
|
2089
|
-
0,
|
|
2090
|
-
this.drawnWidth,
|
|
2091
|
-
this.drawnHeight,
|
|
2092
|
-
0,
|
|
2093
|
-
0,
|
|
2094
|
-
this.drawnWidth,
|
|
2095
|
-
this.drawnHeight,
|
|
2096
|
-
gl.COLOR_BUFFER_BIT,
|
|
2097
|
-
gl.NEAREST,
|
|
2098
|
-
);
|
|
2099
|
-
if (this.scissor) {
|
|
2100
|
-
gl.enable(gl.SCISSOR_TEST);
|
|
2101
|
-
}
|
|
2102
|
-
this.resolveDirty = false;
|
|
2103
|
-
}
|
|
2104
|
-
this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
1980
|
read(width = this.width, height = this.height): Uint8Array {
|
|
2108
|
-
|
|
2109
|
-
height = Math.max(0, Math.min(this.height, Math.floor(height) || 0));
|
|
2110
|
-
this.target(true);
|
|
2111
|
-
this.resolve();
|
|
2112
|
-
const gl = this.gl;
|
|
2113
|
-
const scaleX = this.drawnWidth / this.width;
|
|
2114
|
-
const scaleY = this.drawnHeight / this.height;
|
|
2115
|
-
const drawnWidth = Math.min(this.drawnWidth, Math.ceil(width * scaleX));
|
|
2116
|
-
const drawnHeight = Math.min(this.drawnHeight, Math.ceil(height * scaleY));
|
|
2117
|
-
if (this.pixels.length < drawnWidth * drawnHeight * 4) {
|
|
2118
|
-
this.pixels = new Uint8Array(drawnWidth * drawnHeight * 4);
|
|
2119
|
-
}
|
|
2120
|
-
if (this.topDown.length < width * height * 4) {
|
|
2121
|
-
this.topDown = new Uint8Array(width * height * 4);
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
// GL's rows run bottom-up, so the top-left region starts height rows below the top.
|
|
2125
|
-
gl.readPixels(
|
|
2126
|
-
0,
|
|
2127
|
-
this.drawnHeight - drawnHeight,
|
|
2128
|
-
drawnWidth,
|
|
2129
|
-
drawnHeight,
|
|
2130
|
-
gl.RGBA,
|
|
2131
|
-
gl.UNSIGNED_BYTE,
|
|
2132
|
-
this.pixels,
|
|
2133
|
-
);
|
|
2134
|
-
const row = width * 4;
|
|
2135
|
-
if (drawnWidth === width && drawnHeight === height) {
|
|
2136
|
-
for (let y = 0; y < height; y++) {
|
|
2137
|
-
const from = (height - y - 1) * row;
|
|
2138
|
-
this.topDown.set(this.pixels.subarray(from, from + row), y * row);
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
return this.topDown;
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
// Storage smaller than the back buffer: each pixel is the stored one it falls on.
|
|
2145
|
-
const source = new Uint32Array(this.pixels.buffer, 0, drawnWidth * drawnHeight);
|
|
2146
|
-
const target = new Uint32Array(this.topDown.buffer, 0, width * height);
|
|
2147
|
-
for (let y = 0; y < height; y++) {
|
|
2148
|
-
const from =
|
|
2149
|
-
(drawnHeight - 1 - Math.min(drawnHeight - 1, Math.floor(y * scaleY))) * drawnWidth;
|
|
2150
|
-
for (let x = 0; x < width; x++) {
|
|
2151
|
-
target[y * width + x] = source[from + Math.min(drawnWidth - 1, Math.floor(x * scaleX))];
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
return this.topDown;
|
|
1981
|
+
return readBackBuffer(this, width, height);
|
|
2156
1982
|
}
|
|
2157
1983
|
|
|
2158
1984
|
present(): void {
|
|
2159
|
-
this
|
|
2160
|
-
this.target();
|
|
2161
|
-
this.resolve();
|
|
2162
|
-
const gl = this.gl;
|
|
2163
|
-
this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
|
|
2164
|
-
if (this.scissor) {
|
|
2165
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
2166
|
-
}
|
|
2167
|
-
// To the drawing buffer the browser gave, which it may have made smaller than the canvas.
|
|
2168
|
-
gl.blitFramebuffer(
|
|
2169
|
-
0,
|
|
2170
|
-
0,
|
|
2171
|
-
this.drawnWidth,
|
|
2172
|
-
this.drawnHeight,
|
|
2173
|
-
0,
|
|
2174
|
-
0,
|
|
2175
|
-
gl.drawingBufferWidth,
|
|
2176
|
-
gl.drawingBufferHeight,
|
|
2177
|
-
gl.COLOR_BUFFER_BIT,
|
|
2178
|
-
gl.NEAREST,
|
|
2179
|
-
);
|
|
2180
|
-
if (this.scissor) {
|
|
2181
|
-
gl.enable(gl.SCISSOR_TEST);
|
|
2182
|
-
}
|
|
2183
|
-
gl.flush();
|
|
1985
|
+
presentBackBuffer(this);
|
|
2184
1986
|
}
|
|
2185
1987
|
|
|
2186
1988
|
dispose(): void {
|