@swf2es/context3d 0.0.0-stage → 0.1.0
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/LICENSE +202 -0
- package/README.md +219 -2
- package/dist/agal.d.ts +23 -0
- package/dist/agal.d.ts.map +1 -0
- package/dist/agal.js +410 -0
- package/dist/agal.js.map +1 -0
- package/dist/backend.d.ts +122 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +63 -0
- package/dist/backend.js.map +1 -0
- package/dist/device/index.d.ts +3 -0
- package/dist/device/index.d.ts.map +1 -0
- package/dist/device/index.js +6 -0
- package/dist/device/index.js.map +1 -0
- package/dist/device/programs.d.ts +13 -0
- package/dist/device/programs.d.ts.map +1 -0
- package/dist/device/programs.js +59 -0
- package/dist/device/programs.js.map +1 -0
- package/dist/device/state.d.ts +53 -0
- package/dist/device/state.d.ts.map +1 -0
- package/dist/device/state.js +196 -0
- package/dist/device/state.js.map +1 -0
- package/dist/dxt.d.ts +22 -0
- package/dist/dxt.d.ts.map +1 -0
- package/dist/dxt.js +304 -0
- package/dist/dxt.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +8 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/webgl/buffers.d.ts +27 -0
- package/dist/webgl/buffers.d.ts.map +1 -0
- package/dist/webgl/buffers.js +53 -0
- package/dist/webgl/buffers.js.map +1 -0
- package/dist/webgl/context.d.ts +123 -0
- package/dist/webgl/context.d.ts.map +1 -0
- package/dist/webgl/context.js +1368 -0
- package/dist/webgl/context.js.map +1 -0
- package/dist/webgl/index.d.ts +3 -0
- package/dist/webgl/index.d.ts.map +1 -0
- package/dist/webgl/index.js +2 -0
- package/dist/webgl/index.js.map +1 -0
- package/dist/webgl/limits.d.ts +23 -0
- package/dist/webgl/limits.d.ts.map +1 -0
- package/dist/webgl/limits.js +35 -0
- package/dist/webgl/limits.js.map +1 -0
- package/dist/webgl/programs.d.ts +22 -0
- package/dist/webgl/programs.d.ts.map +1 -0
- package/dist/webgl/programs.js +110 -0
- package/dist/webgl/programs.js.map +1 -0
- package/dist/webgl/state.d.ts +42 -0
- package/dist/webgl/state.d.ts.map +1 -0
- package/dist/webgl/state.js +55 -0
- package/dist/webgl/state.js.map +1 -0
- package/dist/webgl/targets.d.ts +20 -0
- package/dist/webgl/targets.d.ts.map +1 -0
- package/dist/webgl/targets.js +138 -0
- package/dist/webgl/targets.js.map +1 -0
- package/dist/webgl/textures.d.ts +28 -0
- package/dist/webgl/textures.d.ts.map +1 -0
- package/dist/webgl/textures.js +119 -0
- package/dist/webgl/textures.js.map +1 -0
- package/dist/webgl/validation.d.ts +4 -0
- package/dist/webgl/validation.d.ts.map +1 -0
- package/dist/webgl/validation.js +52 -0
- package/dist/webgl/validation.js.map +1 -0
- package/package.json +37 -3
- package/src/agal.ts +496 -0
- package/src/backend.ts +210 -0
- package/src/device/index.ts +5 -0
- package/src/device/programs.ts +66 -0
- package/src/device/state.ts +214 -0
- package/src/dxt.ts +349 -0
- package/src/errors.ts +68 -0
- package/src/index.ts +35 -0
- package/src/webgl/buffers.ts +102 -0
- package/src/webgl/context.ts +1803 -0
- package/src/webgl/index.ts +2 -0
- package/src/webgl/limits.ts +61 -0
- package/src/webgl/programs.ts +145 -0
- package/src/webgl/state.ts +65 -0
- package/src/webgl/targets.ts +229 -0
- package/src/webgl/textures.ts +200 -0
- package/src/webgl/validation.ts +72 -0
|
@@ -0,0 +1,1368 @@
|
|
|
1
|
+
import { SAMPLER_FILTERS, SAMPLER_MIPS, SAMPLER_WRAPS, ScissorBoundsError, TextureBindingError, VERTEX_FORMATS, VertexOffsetError, } from "../backend.js";
|
|
2
|
+
import { blockBytes } from "../dxt.js";
|
|
3
|
+
import { Context3DError } from "../errors.js";
|
|
4
|
+
import { uploadBuffer, uploadBufferBytes, } from "./buffers.js";
|
|
5
|
+
import { resourceLimits } from "./limits.js";
|
|
6
|
+
import { createCopyProgram, uploadProgram, } from "./programs.js";
|
|
7
|
+
import { BLEND_FUNCTIONS, DEPTH_FUNCTIONS, range, STENCIL_FUNCTIONS, StencilState, } from "./state.js";
|
|
8
|
+
import { allocateMultisample, deleteMultisample, resolveTarget, selectSamples, targetFramebuffer, } from "./targets.js";
|
|
9
|
+
import { uploadCompressedLevel, uploadTexture, uploadTextureBytes, } from "./textures.js";
|
|
10
|
+
import { validateIndices, validateVertices } from "./validation.js";
|
|
11
|
+
/** What AGAL's sampler must name to read each texture format: RGBA, DXT1 or DXT5. */
|
|
12
|
+
const SAMPLER_FORMATS = {
|
|
13
|
+
bgra: 0,
|
|
14
|
+
compressed: 1,
|
|
15
|
+
compressedAlpha: 2,
|
|
16
|
+
};
|
|
17
|
+
/** An owned WebGL2 context, deliberately independent of the 2D renderer. */
|
|
18
|
+
export class WebGLContext3D {
|
|
19
|
+
gl;
|
|
20
|
+
width = 0;
|
|
21
|
+
height = 0;
|
|
22
|
+
disposed = false;
|
|
23
|
+
samplerFormats = true;
|
|
24
|
+
buffers = new Map();
|
|
25
|
+
vertexBuffers = 0;
|
|
26
|
+
bufferBytes = 0;
|
|
27
|
+
vertexBufferBytes = 0;
|
|
28
|
+
programs = new Map();
|
|
29
|
+
textures = new Map();
|
|
30
|
+
textureCounts = new Float64Array(3);
|
|
31
|
+
textureQuotaBytes = new Float64Array(3);
|
|
32
|
+
textureOwners = new WeakSet();
|
|
33
|
+
textureBytes = 0;
|
|
34
|
+
targetDepthBytes = 0;
|
|
35
|
+
targetColorBytes = 0;
|
|
36
|
+
copy2D = null;
|
|
37
|
+
copyCube = null;
|
|
38
|
+
targetSamples = new Int8Array(10).fill(-1);
|
|
39
|
+
renderTexture = null;
|
|
40
|
+
renderDepth = false;
|
|
41
|
+
renderFace = 0;
|
|
42
|
+
textureSlots = new Array(8).fill(null);
|
|
43
|
+
textureMask = 0;
|
|
44
|
+
samplers = new Array(96).fill(null);
|
|
45
|
+
// Two wrap bits, three filter bits and two mip bits fit each slot in one byte.
|
|
46
|
+
samplerStates = new Uint8Array(16);
|
|
47
|
+
anisotropy;
|
|
48
|
+
s3tc;
|
|
49
|
+
maxAnisotropy = 1;
|
|
50
|
+
vertices = new Array(8).fill(null);
|
|
51
|
+
selected = null;
|
|
52
|
+
framebuffer = null;
|
|
53
|
+
texture = null;
|
|
54
|
+
resolveFramebuffer = null;
|
|
55
|
+
colorBuffer = null;
|
|
56
|
+
resolveDirty = false;
|
|
57
|
+
depthBuffer = null;
|
|
58
|
+
antiAlias = 0;
|
|
59
|
+
depthWrite = true;
|
|
60
|
+
blendEnabled = false;
|
|
61
|
+
cullEnabled = false;
|
|
62
|
+
colorWrite = 15;
|
|
63
|
+
stencilFront = new StencilState();
|
|
64
|
+
stencilBack = new StencilState();
|
|
65
|
+
stencilEnabled = false;
|
|
66
|
+
stencilReference = 0;
|
|
67
|
+
stencilRead = 255;
|
|
68
|
+
stencilWrite = 255;
|
|
69
|
+
scissor = false;
|
|
70
|
+
scissorLeft = 0;
|
|
71
|
+
scissorTop = 0;
|
|
72
|
+
scissorRight = 0;
|
|
73
|
+
scissorBottom = 0;
|
|
74
|
+
scissorOutside = false;
|
|
75
|
+
vertexConstants = new Float32Array(128 * 4);
|
|
76
|
+
fragmentConstants = new Float32Array(28 * 4);
|
|
77
|
+
pixels = new Uint8Array(0);
|
|
78
|
+
topDown = new Uint8Array(0);
|
|
79
|
+
vao;
|
|
80
|
+
limits;
|
|
81
|
+
dxtEncoder = null;
|
|
82
|
+
/** The host's whole-level encoder; a refusal retains the TypeScript path. */
|
|
83
|
+
setDxtEncoder(encoder) {
|
|
84
|
+
this.dxtEncoder = encoder;
|
|
85
|
+
}
|
|
86
|
+
constructor(gl, limits = {}) {
|
|
87
|
+
this.limits = resourceLimits(limits);
|
|
88
|
+
this.gl = gl;
|
|
89
|
+
if (gl.getContextAttributes()?.antialias) {
|
|
90
|
+
throw new Context3DError("unsupported", "Context3D requires a WebGL2 context with antialias: false");
|
|
91
|
+
}
|
|
92
|
+
const vao = gl.createVertexArray();
|
|
93
|
+
if (!vao) {
|
|
94
|
+
throw new Context3DError("operation", "Context3D experiment: cannot create vertex array");
|
|
95
|
+
}
|
|
96
|
+
this.vao = vao;
|
|
97
|
+
gl.bindVertexArray(vao);
|
|
98
|
+
gl.enable(gl.DEPTH_TEST);
|
|
99
|
+
gl.depthFunc(gl.LESS);
|
|
100
|
+
gl.depthMask(true);
|
|
101
|
+
gl.colorMask(true, true, true, true);
|
|
102
|
+
gl.disable(gl.STENCIL_TEST);
|
|
103
|
+
gl.stencilFunc(gl.ALWAYS, 0, 255);
|
|
104
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
|
105
|
+
gl.stencilMask(255);
|
|
106
|
+
gl.disable(gl.BLEND);
|
|
107
|
+
gl.disable(gl.CULL_FACE);
|
|
108
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
109
|
+
// Stage3D front faces are clockwise in clip-space XY.
|
|
110
|
+
gl.frontFace(gl.CW);
|
|
111
|
+
gl.disable(gl.DITHER);
|
|
112
|
+
}
|
|
113
|
+
alive() {
|
|
114
|
+
if (this.disposed || this.gl.isContextLost()) {
|
|
115
|
+
throw new Context3DError("disposed", "Context3D experiment: context disposed or lost");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
target(back = false, discardColor = false) {
|
|
119
|
+
this.alive();
|
|
120
|
+
if (!this.framebuffer) {
|
|
121
|
+
throw new Context3DError("backBuffer", "Context3D experiment: configure the back buffer first");
|
|
122
|
+
}
|
|
123
|
+
const texture = back ? null : this.renderTexture;
|
|
124
|
+
const state = texture && this.textureState(texture);
|
|
125
|
+
if (texture && state?.samples && state.multisample && !state.multisample.valid) {
|
|
126
|
+
if (discardColor) {
|
|
127
|
+
state.multisample.valid = true;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.seedTarget(texture, state);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, state ? targetFramebuffer(state) : this.framebuffer);
|
|
134
|
+
this.gl.viewport(0, 0, texture?.width ?? this.width, texture?.height ?? this.height);
|
|
135
|
+
}
|
|
136
|
+
configure(width, height, depth = false, antiAlias = 0) {
|
|
137
|
+
this.alive();
|
|
138
|
+
range(32, width - 32, 2048);
|
|
139
|
+
range(32, height - 32, 2048);
|
|
140
|
+
if (this.renderTexture) {
|
|
141
|
+
throw new Context3DError("operation", "Context3D experiment: resize while rendering to texture");
|
|
142
|
+
}
|
|
143
|
+
// AIR preserves color, depth and bindings when the request is unchanged.
|
|
144
|
+
// Shadow passes can issue this every frame when returning to the back buffer.
|
|
145
|
+
if (this.framebuffer &&
|
|
146
|
+
width === this.width &&
|
|
147
|
+
height === this.height &&
|
|
148
|
+
depth === (this.depthBuffer !== null) &&
|
|
149
|
+
antiAlias === this.antiAlias) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const samples = selectSamples(this.gl, antiAlias, depth);
|
|
153
|
+
const gl = this.gl;
|
|
154
|
+
const texture = gl.createTexture();
|
|
155
|
+
const framebuffer = gl.createFramebuffer();
|
|
156
|
+
const depthBuffer = depth ? gl.createRenderbuffer() : null;
|
|
157
|
+
const resolveFramebuffer = samples ? gl.createFramebuffer() : null;
|
|
158
|
+
const colorBuffer = samples ? gl.createRenderbuffer() : null;
|
|
159
|
+
try {
|
|
160
|
+
if (!texture ||
|
|
161
|
+
!framebuffer ||
|
|
162
|
+
(depth && !depthBuffer) ||
|
|
163
|
+
(samples && (!resolveFramebuffer || !colorBuffer))) {
|
|
164
|
+
throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
|
|
165
|
+
}
|
|
166
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
167
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
168
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
169
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
170
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
|
|
171
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
172
|
+
if (resolveFramebuffer) {
|
|
173
|
+
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
174
|
+
throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
|
|
175
|
+
}
|
|
176
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
177
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
|
|
178
|
+
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, width, height);
|
|
179
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer);
|
|
180
|
+
}
|
|
181
|
+
if (depthBuffer) {
|
|
182
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
|
|
183
|
+
if (samples) {
|
|
184
|
+
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, width, height);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, width, height);
|
|
188
|
+
}
|
|
189
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
|
|
190
|
+
}
|
|
191
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
192
|
+
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
193
|
+
throw new Context3DError("operation", "Context3D experiment: incomplete back buffer");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
gl.deleteTexture(texture);
|
|
198
|
+
gl.deleteFramebuffer(framebuffer);
|
|
199
|
+
gl.deleteFramebuffer(resolveFramebuffer);
|
|
200
|
+
gl.deleteRenderbuffer(depthBuffer);
|
|
201
|
+
gl.deleteRenderbuffer(colorBuffer);
|
|
202
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
203
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
gl.deleteTexture(this.texture);
|
|
207
|
+
gl.deleteFramebuffer(this.framebuffer);
|
|
208
|
+
gl.deleteFramebuffer(this.resolveFramebuffer);
|
|
209
|
+
gl.deleteRenderbuffer(this.depthBuffer);
|
|
210
|
+
gl.deleteRenderbuffer(this.colorBuffer);
|
|
211
|
+
this.texture = texture;
|
|
212
|
+
this.framebuffer = framebuffer;
|
|
213
|
+
this.resolveFramebuffer = resolveFramebuffer;
|
|
214
|
+
this.colorBuffer = colorBuffer;
|
|
215
|
+
this.depthBuffer = depthBuffer;
|
|
216
|
+
this.antiAlias = antiAlias;
|
|
217
|
+
this.width = width;
|
|
218
|
+
this.height = height;
|
|
219
|
+
gl.canvas.width = width;
|
|
220
|
+
gl.canvas.height = height;
|
|
221
|
+
this.pixels = new Uint8Array(width * height * 4);
|
|
222
|
+
this.topDown = new Uint8Array(this.pixels.length);
|
|
223
|
+
this.applyScissor();
|
|
224
|
+
this.clear(0, 0, 0, 1);
|
|
225
|
+
}
|
|
226
|
+
clear(red, green, blue, alpha, depth = 1, mask = 0xffffffff, stencil = 0) {
|
|
227
|
+
this.target(false, (mask & 1) !== 0);
|
|
228
|
+
const gl = this.gl;
|
|
229
|
+
let bits = 0;
|
|
230
|
+
if (mask & 1) {
|
|
231
|
+
gl.clearColor(red, green, blue, alpha);
|
|
232
|
+
bits |= gl.COLOR_BUFFER_BIT;
|
|
233
|
+
this.changedTarget();
|
|
234
|
+
}
|
|
235
|
+
if (mask & 2) {
|
|
236
|
+
gl.clearDepth(depth);
|
|
237
|
+
bits |= gl.DEPTH_BUFFER_BIT;
|
|
238
|
+
}
|
|
239
|
+
if (mask & 4) {
|
|
240
|
+
gl.clearStencil(Math.min(stencil >>> 0, 255));
|
|
241
|
+
bits |= gl.STENCIL_BUFFER_BIT;
|
|
242
|
+
}
|
|
243
|
+
// Flash clear writes depth even while draw-time depth writes are masked.
|
|
244
|
+
const hasDepth = this.renderTexture ? this.renderDepth : this.depthBuffer !== null;
|
|
245
|
+
const overrideDepth = hasDepth && (mask & 2) !== 0 && !this.depthWrite;
|
|
246
|
+
if (overrideDepth) {
|
|
247
|
+
gl.depthMask(true);
|
|
248
|
+
}
|
|
249
|
+
const overrideStencil = hasDepth && (mask & 4) !== 0 && this.stencilWrite !== 255;
|
|
250
|
+
if (overrideStencil) {
|
|
251
|
+
gl.stencilMask(255);
|
|
252
|
+
}
|
|
253
|
+
// AIR clears every color channel regardless of the draw-time mask.
|
|
254
|
+
const overrideColor = (mask & 1) !== 0 && this.colorWrite !== 15;
|
|
255
|
+
if (overrideColor) {
|
|
256
|
+
gl.colorMask(true, true, true, true);
|
|
257
|
+
}
|
|
258
|
+
if (this.scissor) {
|
|
259
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
260
|
+
}
|
|
261
|
+
gl.clear(bits);
|
|
262
|
+
if (this.scissor) {
|
|
263
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
264
|
+
}
|
|
265
|
+
if (overrideDepth) {
|
|
266
|
+
gl.depthMask(false);
|
|
267
|
+
}
|
|
268
|
+
if (overrideColor) {
|
|
269
|
+
this.applyColorMask();
|
|
270
|
+
}
|
|
271
|
+
if (overrideStencil) {
|
|
272
|
+
gl.stencilMask(this.stencilWrite);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
setStencilActions(face, compare, pass, depthFail, stencilFail) {
|
|
276
|
+
this.alive();
|
|
277
|
+
const gl = this.gl;
|
|
278
|
+
if (face === "front" || face === "frontAndBack") {
|
|
279
|
+
this.stencilFace(gl.FRONT, this.stencilFront, compare, pass, depthFail, stencilFail);
|
|
280
|
+
}
|
|
281
|
+
if (face === "back" || face === "frontAndBack") {
|
|
282
|
+
this.stencilFace(gl.BACK, this.stencilBack, compare, pass, depthFail, stencilFail);
|
|
283
|
+
}
|
|
284
|
+
const enabled = this.stencilFront.active || this.stencilBack.active;
|
|
285
|
+
if (enabled !== this.stencilEnabled) {
|
|
286
|
+
this.stencilEnabled = enabled;
|
|
287
|
+
if (enabled) {
|
|
288
|
+
gl.enable(gl.STENCIL_TEST);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
gl.disable(gl.STENCIL_TEST);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
stencilFace(face, state, compare, pass, depthFail, stencilFail) {
|
|
296
|
+
const gl = this.gl;
|
|
297
|
+
if (state.compare !== compare) {
|
|
298
|
+
state.compare = compare;
|
|
299
|
+
gl.stencilFuncSeparate(face, gl[DEPTH_FUNCTIONS[compare]], this.stencilReference, this.stencilRead);
|
|
300
|
+
}
|
|
301
|
+
if (state.pass !== pass || state.depthFail !== depthFail || state.stencilFail !== stencilFail) {
|
|
302
|
+
state.pass = pass;
|
|
303
|
+
state.depthFail = depthFail;
|
|
304
|
+
state.stencilFail = stencilFail;
|
|
305
|
+
gl.stencilOpSeparate(face, gl[STENCIL_FUNCTIONS[stencilFail]], gl[STENCIL_FUNCTIONS[depthFail]], gl[STENCIL_FUNCTIONS[pass]]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
setStencilReference(reference, readMask, writeMask) {
|
|
309
|
+
this.alive();
|
|
310
|
+
reference &= 255;
|
|
311
|
+
readMask &= 255;
|
|
312
|
+
writeMask &= 255;
|
|
313
|
+
const gl = this.gl;
|
|
314
|
+
if (reference !== this.stencilReference || readMask !== this.stencilRead) {
|
|
315
|
+
this.stencilReference = reference;
|
|
316
|
+
this.stencilRead = readMask;
|
|
317
|
+
gl.stencilFuncSeparate(gl.FRONT, gl[DEPTH_FUNCTIONS[this.stencilFront.compare]], reference, readMask);
|
|
318
|
+
gl.stencilFuncSeparate(gl.BACK, gl[DEPTH_FUNCTIONS[this.stencilBack.compare]], reference, readMask);
|
|
319
|
+
}
|
|
320
|
+
if (writeMask !== this.stencilWrite) {
|
|
321
|
+
this.stencilWrite = writeMask;
|
|
322
|
+
gl.stencilMask(writeMask);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
setColorMask(red, green, blue, alpha) {
|
|
326
|
+
this.alive();
|
|
327
|
+
const mask = (red ? 1 : 0) | (green ? 2 : 0) | (blue ? 4 : 0) | (alpha ? 8 : 0);
|
|
328
|
+
if (mask === this.colorWrite) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
this.colorWrite = mask;
|
|
332
|
+
this.applyColorMask();
|
|
333
|
+
}
|
|
334
|
+
applyColorMask() {
|
|
335
|
+
const mask = this.colorWrite;
|
|
336
|
+
this.gl.colorMask((mask & 1) !== 0, (mask & 2) !== 0, (mask & 4) !== 0, (mask & 8) !== 0);
|
|
337
|
+
}
|
|
338
|
+
setDepth(write, compare) {
|
|
339
|
+
this.alive();
|
|
340
|
+
this.depthWrite = write;
|
|
341
|
+
this.gl.depthMask(write);
|
|
342
|
+
this.gl.depthFunc(this.gl[DEPTH_FUNCTIONS[compare]]);
|
|
343
|
+
}
|
|
344
|
+
setScissor(x, y = 0, width = 0, height = 0) {
|
|
345
|
+
this.alive();
|
|
346
|
+
if (x !== null &&
|
|
347
|
+
(!Number.isFinite(x) ||
|
|
348
|
+
!Number.isFinite(y) ||
|
|
349
|
+
!Number.isFinite(width) ||
|
|
350
|
+
!Number.isFinite(height))) {
|
|
351
|
+
throw new Context3DError("operation", "Context3D experiment: non-finite scissor rectangle");
|
|
352
|
+
}
|
|
353
|
+
this.scissor = x !== null;
|
|
354
|
+
if (x !== null) {
|
|
355
|
+
// AIR rounds the edges, not the width/height, and copies the rectangle.
|
|
356
|
+
this.scissorLeft = Math.floor(x + 0.5);
|
|
357
|
+
this.scissorTop = Math.floor(y + 0.5);
|
|
358
|
+
this.scissorRight = Math.floor(x + width + 0.5);
|
|
359
|
+
this.scissorBottom = Math.floor(y + height + 0.5);
|
|
360
|
+
this.scissor = this.scissorRight > this.scissorLeft && this.scissorBottom > this.scissorTop;
|
|
361
|
+
}
|
|
362
|
+
this.applyScissor();
|
|
363
|
+
}
|
|
364
|
+
applyScissor() {
|
|
365
|
+
const gl = this.gl;
|
|
366
|
+
if (!this.scissor) {
|
|
367
|
+
this.scissorOutside = false;
|
|
368
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const width = this.renderTexture?.width ?? this.width;
|
|
372
|
+
const height = this.renderTexture?.height ?? this.height;
|
|
373
|
+
const left = Math.max(0, Math.min(width, this.scissorLeft));
|
|
374
|
+
const top = Math.max(0, Math.min(height, this.scissorTop));
|
|
375
|
+
const right = Math.max(0, Math.min(width, this.scissorRight));
|
|
376
|
+
const bottom = Math.max(0, Math.min(height, this.scissorBottom));
|
|
377
|
+
this.scissorOutside = right <= left || bottom <= top;
|
|
378
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
379
|
+
gl.scissor(left, this.renderTexture ? top : height - bottom, Math.max(0, right - left), Math.max(0, bottom - top));
|
|
380
|
+
}
|
|
381
|
+
setBlend(source, destination) {
|
|
382
|
+
this.alive();
|
|
383
|
+
const gl = this.gl;
|
|
384
|
+
this.blendEnabled = source !== "one" || destination !== "zero";
|
|
385
|
+
if (source === "one" && destination === "zero") {
|
|
386
|
+
gl.disable(gl.BLEND);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
gl.blendFunc(gl[BLEND_FUNCTIONS[source]], gl[BLEND_FUNCTIONS[destination]]);
|
|
390
|
+
gl.enable(gl.BLEND);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
setCulling(face) {
|
|
394
|
+
this.alive();
|
|
395
|
+
const gl = this.gl;
|
|
396
|
+
this.cullEnabled = face !== "none";
|
|
397
|
+
if (face === "none") {
|
|
398
|
+
gl.disable(gl.CULL_FACE);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
gl.enable(gl.CULL_FACE);
|
|
402
|
+
switch (face) {
|
|
403
|
+
case "back":
|
|
404
|
+
gl.cullFace(gl.BACK);
|
|
405
|
+
break;
|
|
406
|
+
case "front":
|
|
407
|
+
gl.cullFace(gl.FRONT);
|
|
408
|
+
break;
|
|
409
|
+
case "frontAndBack":
|
|
410
|
+
gl.cullFace(gl.FRONT_AND_BACK);
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
createBuffer(count, stride) {
|
|
415
|
+
this.alive();
|
|
416
|
+
range(1, count - 1, stride === 0 ? 524287 : 65535);
|
|
417
|
+
range(0, stride, 64);
|
|
418
|
+
const countLimit = stride === 0 ? this.limits.maxIndexBuffers : this.limits.maxVertexBuffers;
|
|
419
|
+
const used = stride === 0 ? this.buffers.size - this.vertexBuffers : this.vertexBuffers;
|
|
420
|
+
if (this.buffers.size >= this.limits.maxBuffers || used >= countLimit) {
|
|
421
|
+
throw new Context3DError("limit", "Context3D experiment: buffer limit");
|
|
422
|
+
}
|
|
423
|
+
const bytes = count * (stride === 0 ? 2 : stride * 4);
|
|
424
|
+
const usedBytes = stride === 0 ? this.bufferBytes - this.vertexBufferBytes : this.vertexBufferBytes;
|
|
425
|
+
const byteLimit = stride === 0 ? this.limits.maxIndexBufferBytes : this.limits.maxVertexBufferBytes;
|
|
426
|
+
if (this.bufferBytes + bytes > this.limits.maxBufferBytes || usedBytes + bytes > byteLimit) {
|
|
427
|
+
throw new Context3DError("limit", "Context3D experiment: buffer memory limit");
|
|
428
|
+
}
|
|
429
|
+
const gl = this.gl;
|
|
430
|
+
const handle = gl.createBuffer();
|
|
431
|
+
if (!handle) {
|
|
432
|
+
throw new Context3DError("bufferAllocation", "Context3D experiment: cannot allocate buffer");
|
|
433
|
+
}
|
|
434
|
+
const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
|
|
435
|
+
gl.bindVertexArray(this.vao);
|
|
436
|
+
gl.bindBuffer(target, handle);
|
|
437
|
+
gl.bufferData(target, bytes, gl.STATIC_DRAW);
|
|
438
|
+
const state = {
|
|
439
|
+
handle,
|
|
440
|
+
indices: stride === 0 ? new Uint16Array(count) : null,
|
|
441
|
+
uploaded: new Uint8Array(count),
|
|
442
|
+
prefix: 0,
|
|
443
|
+
version: 0,
|
|
444
|
+
checkedVersion: -1,
|
|
445
|
+
checkedFirst: 0,
|
|
446
|
+
checkedCount: 0,
|
|
447
|
+
maximum: -1,
|
|
448
|
+
};
|
|
449
|
+
const buffer = {
|
|
450
|
+
count,
|
|
451
|
+
stride,
|
|
452
|
+
upload: (values, start, amount) => {
|
|
453
|
+
this.alive();
|
|
454
|
+
this.buffer(buffer);
|
|
455
|
+
uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
|
|
456
|
+
},
|
|
457
|
+
uploadBytes: (data, offset, start, amount) => {
|
|
458
|
+
this.alive();
|
|
459
|
+
this.buffer(buffer);
|
|
460
|
+
uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
|
|
461
|
+
},
|
|
462
|
+
dispose: () => {
|
|
463
|
+
if (!this.buffers.delete(buffer)) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
gl.deleteBuffer(handle);
|
|
467
|
+
this.bufferBytes -= bytes;
|
|
468
|
+
if (stride !== 0) {
|
|
469
|
+
this.vertexBuffers--;
|
|
470
|
+
this.vertexBufferBytes -= bytes;
|
|
471
|
+
}
|
|
472
|
+
state.indices = null;
|
|
473
|
+
state.uploaded = new Uint8Array(0);
|
|
474
|
+
for (let i = 0; i < this.vertices.length; i++) {
|
|
475
|
+
const binding = this.vertices[i];
|
|
476
|
+
if (binding?.buffer === buffer) {
|
|
477
|
+
this.vertices[i] = null;
|
|
478
|
+
}
|
|
479
|
+
else if (binding?.checkedIndex === state) {
|
|
480
|
+
binding.checkedIndex = null;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
this.buffers.set(buffer, state);
|
|
486
|
+
if (stride !== 0) {
|
|
487
|
+
this.vertexBuffers++;
|
|
488
|
+
this.vertexBufferBytes += bytes;
|
|
489
|
+
}
|
|
490
|
+
this.bufferBytes += bytes;
|
|
491
|
+
return buffer;
|
|
492
|
+
}
|
|
493
|
+
buffer(buffer) {
|
|
494
|
+
const state = this.buffers.get(buffer);
|
|
495
|
+
if (!state) {
|
|
496
|
+
throw new Context3DError("disposed", "Context3D experiment: buffer disposed or from another context");
|
|
497
|
+
}
|
|
498
|
+
return state;
|
|
499
|
+
}
|
|
500
|
+
createProgram() {
|
|
501
|
+
this.alive();
|
|
502
|
+
if (this.programs.size >= this.limits.maxPrograms) {
|
|
503
|
+
throw new Context3DError("limit", "Context3D experiment: program limit");
|
|
504
|
+
}
|
|
505
|
+
const state = {
|
|
506
|
+
handle: null,
|
|
507
|
+
targetY: null,
|
|
508
|
+
attributes: [],
|
|
509
|
+
vertex: [],
|
|
510
|
+
vertexBank: null,
|
|
511
|
+
fragment: [],
|
|
512
|
+
samplers: [],
|
|
513
|
+
samplerMask: 0,
|
|
514
|
+
};
|
|
515
|
+
const program = {
|
|
516
|
+
upload: (vertex, fragment) => {
|
|
517
|
+
this.alive();
|
|
518
|
+
this.program(program);
|
|
519
|
+
uploadProgram(this.gl, state, vertex, fragment);
|
|
520
|
+
if (this.selected === program) {
|
|
521
|
+
this.setProgram(program);
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
dispose: () => {
|
|
525
|
+
if (!this.programs.delete(program)) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
// WebGL keeps a deleted program alive while it is still current.
|
|
529
|
+
if (this.gl.getParameter(this.gl.CURRENT_PROGRAM) === state.handle) {
|
|
530
|
+
this.gl.useProgram(null);
|
|
531
|
+
}
|
|
532
|
+
this.gl.deleteProgram(state.handle);
|
|
533
|
+
state.handle = null;
|
|
534
|
+
state.targetY = null;
|
|
535
|
+
state.attributes.length = 0;
|
|
536
|
+
state.vertex.length = 0;
|
|
537
|
+
state.vertexBank = null;
|
|
538
|
+
state.fragment.length = 0;
|
|
539
|
+
state.samplers.length = 0;
|
|
540
|
+
state.samplerMask = 0;
|
|
541
|
+
if (this.selected === program) {
|
|
542
|
+
this.selected = null;
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
this.programs.set(program, state);
|
|
547
|
+
return program;
|
|
548
|
+
}
|
|
549
|
+
program(program) {
|
|
550
|
+
const state = this.programs.get(program);
|
|
551
|
+
if (!state) {
|
|
552
|
+
throw new Context3DError("disposed", "Context3D experiment: program disposed or from another context");
|
|
553
|
+
}
|
|
554
|
+
return state;
|
|
555
|
+
}
|
|
556
|
+
createTexture(width, height, cube = false, format = "bgra") {
|
|
557
|
+
return this.allocateTexture(width, height, cube, false, format);
|
|
558
|
+
}
|
|
559
|
+
createRectangleTexture(width, height) {
|
|
560
|
+
return this.allocateTexture(width, height, false, true, "bgra");
|
|
561
|
+
}
|
|
562
|
+
/** GL's internal format for a compressed texture's S3TC, or 0 where the context takes none. */
|
|
563
|
+
s3tcFormat(format) {
|
|
564
|
+
if (this.s3tc === undefined) {
|
|
565
|
+
this.s3tc = this.gl.getExtension("WEBGL_compressed_texture_s3tc");
|
|
566
|
+
}
|
|
567
|
+
if (!this.s3tc || format === "bgra") {
|
|
568
|
+
return 0;
|
|
569
|
+
}
|
|
570
|
+
// AIR's DXT1 has DXT1's one bit of alpha: a block of three colours has transparent black.
|
|
571
|
+
return format === "compressed"
|
|
572
|
+
? this.s3tc.COMPRESSED_RGBA_S3TC_DXT1_EXT
|
|
573
|
+
: this.s3tc.COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Bytes a mip chain of `format` takes, its faces' every level: S3TC's
|
|
577
|
+
* blocks for a compressed texture whatever its storage, as AIR counts
|
|
578
|
+
* them, so that a GPU without S3TC allocates no less than one with it.
|
|
579
|
+
*/
|
|
580
|
+
chainBytes(width, height, levels, faces, format) {
|
|
581
|
+
let size = 0;
|
|
582
|
+
for (let level = 0; level < levels; level++) {
|
|
583
|
+
const w = Math.max(1, width >> level);
|
|
584
|
+
const h = Math.max(1, height >> level);
|
|
585
|
+
size += format === "bgra" ? w * h * 4 : blockBytes(w, h, format === "compressedAlpha");
|
|
586
|
+
}
|
|
587
|
+
return size * faces;
|
|
588
|
+
}
|
|
589
|
+
allocateTexture(width, height, cube, rectangle, format) {
|
|
590
|
+
this.alive();
|
|
591
|
+
range(1, width - 1, 2048);
|
|
592
|
+
range(1, height - 1, 2048);
|
|
593
|
+
if (cube && width !== height) {
|
|
594
|
+
throw new Context3DError("textureShape", "Context3D cube faces must be square");
|
|
595
|
+
}
|
|
596
|
+
if (cube && width > 1024) {
|
|
597
|
+
throw new Context3DError("textureSize", "Context3D cube faces must be at most 1024");
|
|
598
|
+
}
|
|
599
|
+
const faces = cube ? 6 : 1;
|
|
600
|
+
const levels = rectangle ? 1 : 1 + Math.floor(Math.log2(Math.max(width, height)));
|
|
601
|
+
if (!rectangle && ((width & (width - 1)) !== 0 || (height & (height - 1)) !== 0)) {
|
|
602
|
+
throw new Context3DError("texturePowerOfTwo", "Context3D texture size must be a power of two");
|
|
603
|
+
}
|
|
604
|
+
if (format !== "bgra" && rectangle) {
|
|
605
|
+
throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
|
|
606
|
+
}
|
|
607
|
+
if (format !== "bgra" && (width < 4 || height < 4)) {
|
|
608
|
+
throw new Context3DError("compressedSize", "Context3D compressed textures are at least 4x4");
|
|
609
|
+
}
|
|
610
|
+
const gl = this.gl;
|
|
611
|
+
const storage = this.s3tcFormat(format) || gl.RGBA8;
|
|
612
|
+
const size = this.chainBytes(width, height, levels, faces, format);
|
|
613
|
+
const kind = cube ? 2 : rectangle ? 1 : 0;
|
|
614
|
+
const countLimit = cube
|
|
615
|
+
? this.limits.maxCubeTextures
|
|
616
|
+
: rectangle
|
|
617
|
+
? this.limits.maxRectangleTextures
|
|
618
|
+
: this.limits.max2DTextures;
|
|
619
|
+
const byteLimit = cube
|
|
620
|
+
? this.limits.maxCubeTextureBytes
|
|
621
|
+
: rectangle
|
|
622
|
+
? this.limits.maxRectangleTextureBytes
|
|
623
|
+
: this.limits.max2DTextureBytes;
|
|
624
|
+
// AIR reserves half the base size for mips, independently of the exact
|
|
625
|
+
// chain storage, and charges DXT1 an eighth of BGRA and DXT5 a quarter.
|
|
626
|
+
const quotaBytes = (width * height * faces * (rectangle ? 4 : 6)) /
|
|
627
|
+
(format === "compressed" ? 8 : format === "compressedAlpha" ? 4 : 1);
|
|
628
|
+
if (this.textures.size >= this.limits.maxTextures ||
|
|
629
|
+
this.textureCounts[kind] >= countLimit ||
|
|
630
|
+
this.textureQuotaBytes[kind] + quotaBytes > byteLimit ||
|
|
631
|
+
this.textureBytes + size > this.limits.maxTextureBytes) {
|
|
632
|
+
throw new Context3DError("limit", "Context3D experiment: texture count or memory limit");
|
|
633
|
+
}
|
|
634
|
+
const handle = gl.createTexture();
|
|
635
|
+
if (!handle) {
|
|
636
|
+
throw new Context3DError("textureAllocation", "Context3D experiment: cannot create texture");
|
|
637
|
+
}
|
|
638
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
639
|
+
const target = cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
|
|
640
|
+
gl.bindTexture(target, handle);
|
|
641
|
+
gl.texStorage2D(target, levels, storage, width, height);
|
|
642
|
+
const state = {
|
|
643
|
+
handle,
|
|
644
|
+
storage,
|
|
645
|
+
bytes: new Uint8Array(0),
|
|
646
|
+
uploaded: new Uint16Array(faces),
|
|
647
|
+
framebuffer: null,
|
|
648
|
+
depthBuffer: null,
|
|
649
|
+
dirty: false,
|
|
650
|
+
face: 0,
|
|
651
|
+
mipFramebuffer: null,
|
|
652
|
+
depthAttached: false,
|
|
653
|
+
multisample: null,
|
|
654
|
+
samples: 0,
|
|
655
|
+
antiAlias: 0,
|
|
656
|
+
};
|
|
657
|
+
const texture = {
|
|
658
|
+
width,
|
|
659
|
+
height,
|
|
660
|
+
levels,
|
|
661
|
+
cube,
|
|
662
|
+
rectangle,
|
|
663
|
+
format,
|
|
664
|
+
upload: (pixels, level = 0, face = 0, sourceWidth, sourceHeight) => {
|
|
665
|
+
this.alive();
|
|
666
|
+
this.textureState(texture);
|
|
667
|
+
uploadTexture(gl, texture, state, pixels, level, face, sourceWidth, sourceHeight, this.dxtEncoder);
|
|
668
|
+
},
|
|
669
|
+
uploadBytes: (data, offset, level = 0, face = 0) => {
|
|
670
|
+
this.alive();
|
|
671
|
+
this.textureState(texture);
|
|
672
|
+
uploadTextureBytes(gl, texture, state, data, offset, level, face, this.dxtEncoder);
|
|
673
|
+
},
|
|
674
|
+
uploadCompressed: (blocks, level = 0, face = 0) => {
|
|
675
|
+
this.alive();
|
|
676
|
+
this.textureState(texture);
|
|
677
|
+
if (format === "bgra") {
|
|
678
|
+
throw new Context3DError("textureUpload", "Context3D experiment: BGRA texture given blocks");
|
|
679
|
+
}
|
|
680
|
+
uploadCompressedLevel(gl, texture, state, blocks, level, face);
|
|
681
|
+
},
|
|
682
|
+
dispose: () => {
|
|
683
|
+
if (!this.textures.delete(texture)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
this.textureCounts[kind]--;
|
|
687
|
+
this.textureQuotaBytes[kind] -= quotaBytes;
|
|
688
|
+
if (this.renderTexture === texture) {
|
|
689
|
+
this.renderTexture = null;
|
|
690
|
+
this.renderDepth = false;
|
|
691
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
692
|
+
gl.frontFace(gl.CW);
|
|
693
|
+
this.applyScissor();
|
|
694
|
+
}
|
|
695
|
+
gl.deleteFramebuffer(state.framebuffer);
|
|
696
|
+
gl.deleteFramebuffer(state.mipFramebuffer);
|
|
697
|
+
gl.deleteRenderbuffer(state.depthBuffer);
|
|
698
|
+
if (state.multisample) {
|
|
699
|
+
this.deleteMultisample(texture, state.multisample);
|
|
700
|
+
state.multisample = null;
|
|
701
|
+
}
|
|
702
|
+
if (state.depthBuffer) {
|
|
703
|
+
this.targetDepthBytes -= width * height * 4;
|
|
704
|
+
}
|
|
705
|
+
state.framebuffer = null;
|
|
706
|
+
state.mipFramebuffer = null;
|
|
707
|
+
state.depthBuffer = null;
|
|
708
|
+
for (let i = 0; i < this.textureSlots.length; i++) {
|
|
709
|
+
if (this.textureSlots[i] === texture) {
|
|
710
|
+
this.textureSlots[i] = null;
|
|
711
|
+
this.textureMask &= ~(1 << i);
|
|
712
|
+
gl.activeTexture(gl.TEXTURE0 + i);
|
|
713
|
+
gl.bindTexture(target, null);
|
|
714
|
+
gl.bindSampler(i, null);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
gl.deleteTexture(handle);
|
|
718
|
+
state.bytes = new Uint8Array(0);
|
|
719
|
+
this.textureBytes -= size;
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
this.textures.set(texture, state);
|
|
723
|
+
this.textureCounts[kind]++;
|
|
724
|
+
this.textureQuotaBytes[kind] += quotaBytes;
|
|
725
|
+
this.textureOwners.add(texture);
|
|
726
|
+
this.textureBytes += size;
|
|
727
|
+
return texture;
|
|
728
|
+
}
|
|
729
|
+
textureState(texture) {
|
|
730
|
+
const state = this.textures.get(texture);
|
|
731
|
+
if (!state) {
|
|
732
|
+
throw new Context3DError("disposed", "Context3D experiment: texture disposed or from another context");
|
|
733
|
+
}
|
|
734
|
+
return state;
|
|
735
|
+
}
|
|
736
|
+
copyProgram(cube) {
|
|
737
|
+
const cached = cube ? this.copyCube : this.copy2D;
|
|
738
|
+
if (cached) {
|
|
739
|
+
return cached;
|
|
740
|
+
}
|
|
741
|
+
const program = createCopyProgram(this.gl, cube);
|
|
742
|
+
if (cube) {
|
|
743
|
+
this.copyCube = program;
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
this.copy2D = program;
|
|
747
|
+
}
|
|
748
|
+
return program;
|
|
749
|
+
}
|
|
750
|
+
seedTarget(texture, state) {
|
|
751
|
+
const target = state.multisample;
|
|
752
|
+
if (!target) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const gl = this.gl;
|
|
756
|
+
const program = this.copyProgram(texture.cube);
|
|
757
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
|
|
758
|
+
gl.viewport(0, 0, texture.width, texture.height);
|
|
759
|
+
gl.disable(gl.DEPTH_TEST);
|
|
760
|
+
gl.disable(gl.STENCIL_TEST);
|
|
761
|
+
gl.disable(gl.BLEND);
|
|
762
|
+
gl.disable(gl.CULL_FACE);
|
|
763
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
764
|
+
gl.colorMask(true, true, true, true);
|
|
765
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
766
|
+
gl.bindTexture(texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D, state.handle);
|
|
767
|
+
gl.bindSampler(0, this.sampler(0));
|
|
768
|
+
gl.bindVertexArray(this.vao);
|
|
769
|
+
gl.useProgram(program.handle);
|
|
770
|
+
if (texture.cube) {
|
|
771
|
+
gl.uniform2f(program.size, texture.width, texture.height);
|
|
772
|
+
gl.uniform1i(program.face, this.renderFace);
|
|
773
|
+
}
|
|
774
|
+
// WebGL cannot blit single-sample color into a multisample framebuffer.
|
|
775
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
776
|
+
gl.enable(gl.DEPTH_TEST);
|
|
777
|
+
if (this.stencilEnabled) {
|
|
778
|
+
gl.enable(gl.STENCIL_TEST);
|
|
779
|
+
}
|
|
780
|
+
if (this.blendEnabled) {
|
|
781
|
+
gl.enable(gl.BLEND);
|
|
782
|
+
}
|
|
783
|
+
if (this.cullEnabled) {
|
|
784
|
+
gl.enable(gl.CULL_FACE);
|
|
785
|
+
}
|
|
786
|
+
if (this.scissor) {
|
|
787
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
788
|
+
}
|
|
789
|
+
this.applyColorMask();
|
|
790
|
+
target.valid = true;
|
|
791
|
+
}
|
|
792
|
+
multisamples(quality, depth) {
|
|
793
|
+
if (!Number.isInteger(quality)) {
|
|
794
|
+
throw new Context3DError("operation", "Context3D experiment: invalid antialiasing quality");
|
|
795
|
+
}
|
|
796
|
+
const key = Math.max(0, Math.min(quality, 4)) * 2 + Number(depth);
|
|
797
|
+
if (this.targetSamples[key] < 0) {
|
|
798
|
+
this.targetSamples[key] = selectSamples(this.gl, quality, depth);
|
|
799
|
+
}
|
|
800
|
+
return this.targetSamples[key];
|
|
801
|
+
}
|
|
802
|
+
deleteMultisample(texture, target) {
|
|
803
|
+
deleteMultisample(this.gl, target);
|
|
804
|
+
const bytes = texture.width * texture.height * 4 * target.samples;
|
|
805
|
+
this.targetColorBytes -= bytes + (target.texture ? texture.width * texture.height * 4 : 0);
|
|
806
|
+
if (target.depth) {
|
|
807
|
+
this.targetDepthBytes -= bytes;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
prepareMultisample(texture, state, samples, depth) {
|
|
811
|
+
const old = state.multisample;
|
|
812
|
+
const same = old?.samples === samples;
|
|
813
|
+
const bytes = texture.width * texture.height * 4 * samples;
|
|
814
|
+
const previous = old ? texture.width * texture.height * 4 * old.samples : 0;
|
|
815
|
+
const resolveBytes = texture.cube ? texture.width * texture.height * 4 : 0;
|
|
816
|
+
const newDepth = depth && (!same || !old.depth);
|
|
817
|
+
if (this.targetColorBytes + (same ? 0 : bytes - previous + (old ? 0 : resolveBytes)) >
|
|
818
|
+
this.limits.maxTargetColorBytes ||
|
|
819
|
+
this.targetDepthBytes + (newDepth ? bytes : 0) - (!same && old?.depth ? previous : 0) >
|
|
820
|
+
this.limits.maxTargetDepthBytes) {
|
|
821
|
+
throw new Context3DError("limit", "Context3D experiment: multisample target memory limit");
|
|
822
|
+
}
|
|
823
|
+
this.copyProgram(texture.cube);
|
|
824
|
+
this.sampler(0);
|
|
825
|
+
const target = allocateMultisample(this.gl, texture, old, samples, depth, this.depthWrite, this.stencilWrite);
|
|
826
|
+
if (same) {
|
|
827
|
+
if (newDepth) {
|
|
828
|
+
this.targetDepthBytes += bytes;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
if (old) {
|
|
833
|
+
this.deleteMultisample(texture, old);
|
|
834
|
+
}
|
|
835
|
+
state.multisample = target;
|
|
836
|
+
this.targetColorBytes += bytes + resolveBytes;
|
|
837
|
+
if (target.depth) {
|
|
838
|
+
this.targetDepthBytes += bytes;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
setRenderTarget(texture, depth = false, face = 0, antiAlias = 0) {
|
|
843
|
+
this.alive();
|
|
844
|
+
if (texture) {
|
|
845
|
+
range(face, 1, texture.cube ? 6 : 1);
|
|
846
|
+
}
|
|
847
|
+
if (texture === this.renderTexture &&
|
|
848
|
+
(!texture ||
|
|
849
|
+
(depth === this.renderDepth &&
|
|
850
|
+
face === this.renderFace &&
|
|
851
|
+
antiAlias === this.textureState(texture).antiAlias))) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const gl = this.gl;
|
|
855
|
+
if (texture) {
|
|
856
|
+
this.textureState(texture);
|
|
857
|
+
if (texture.format !== "bgra") {
|
|
858
|
+
throw new Context3DError("operation", "Context3D experiment: compressed textures are no render targets");
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const samples = texture ? this.multisamples(antiAlias, depth) : 0;
|
|
862
|
+
this.finishTarget();
|
|
863
|
+
if (texture) {
|
|
864
|
+
const state = this.textureState(texture);
|
|
865
|
+
const singleDepth = depth && samples === 0;
|
|
866
|
+
let framebuffer = state.framebuffer;
|
|
867
|
+
let mipFramebuffer = state.mipFramebuffer;
|
|
868
|
+
let depthBuffer = state.depthBuffer;
|
|
869
|
+
try {
|
|
870
|
+
framebuffer ??= gl.createFramebuffer();
|
|
871
|
+
if (!framebuffer) {
|
|
872
|
+
throw new Context3DError("operation", "Context3D experiment: cannot create texture framebuffer");
|
|
873
|
+
}
|
|
874
|
+
if (texture.cube && !mipFramebuffer) {
|
|
875
|
+
mipFramebuffer = gl.createFramebuffer();
|
|
876
|
+
if (!mipFramebuffer) {
|
|
877
|
+
throw new Context3DError("operation", "Context3D experiment: cannot create cube mip framebuffer");
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
881
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + face : gl.TEXTURE_2D, state.handle, 0);
|
|
882
|
+
if (singleDepth && !depthBuffer) {
|
|
883
|
+
if (this.targetDepthBytes + texture.width * texture.height * 4 >
|
|
884
|
+
this.limits.maxTargetDepthBytes) {
|
|
885
|
+
throw new Context3DError("limit", "Context3D experiment: target depth memory limit");
|
|
886
|
+
}
|
|
887
|
+
depthBuffer = gl.createRenderbuffer();
|
|
888
|
+
if (!depthBuffer) {
|
|
889
|
+
throw new Context3DError("operation", "Context3D experiment: cannot create texture depth buffer");
|
|
890
|
+
}
|
|
891
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
|
|
892
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, texture.width, texture.height);
|
|
893
|
+
}
|
|
894
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, singleDepth ? depthBuffer : null);
|
|
895
|
+
if ((!state.framebuffer || (singleDepth && !state.depthBuffer)) &&
|
|
896
|
+
gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
897
|
+
throw new Context3DError("operation", "Context3D experiment: incomplete texture framebuffer");
|
|
898
|
+
}
|
|
899
|
+
if (samples) {
|
|
900
|
+
this.prepareMultisample(texture, state, samples, depth);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
if (framebuffer !== state.framebuffer) {
|
|
905
|
+
gl.deleteFramebuffer(framebuffer);
|
|
906
|
+
}
|
|
907
|
+
else if (framebuffer) {
|
|
908
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
909
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + state.face : gl.TEXTURE_2D, state.handle, 0);
|
|
910
|
+
// Restore the attachment if this was the currently selected target.
|
|
911
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, state.depthAttached ? state.depthBuffer : null);
|
|
912
|
+
}
|
|
913
|
+
if (mipFramebuffer !== state.mipFramebuffer) {
|
|
914
|
+
gl.deleteFramebuffer(mipFramebuffer);
|
|
915
|
+
}
|
|
916
|
+
if (depthBuffer !== state.depthBuffer) {
|
|
917
|
+
gl.deleteRenderbuffer(depthBuffer);
|
|
918
|
+
}
|
|
919
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
920
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.renderTexture
|
|
921
|
+
? targetFramebuffer(this.textureState(this.renderTexture))
|
|
922
|
+
: this.framebuffer);
|
|
923
|
+
this.applyScissor();
|
|
924
|
+
throw error;
|
|
925
|
+
}
|
|
926
|
+
if (depthBuffer && !state.depthBuffer) {
|
|
927
|
+
this.targetDepthBytes += texture.width * texture.height * 4;
|
|
928
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
929
|
+
gl.depthMask(true);
|
|
930
|
+
gl.clearDepth(1);
|
|
931
|
+
gl.clearStencil(0);
|
|
932
|
+
gl.stencilMask(255);
|
|
933
|
+
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
|
|
934
|
+
gl.depthMask(this.depthWrite);
|
|
935
|
+
gl.stencilMask(this.stencilWrite);
|
|
936
|
+
}
|
|
937
|
+
state.framebuffer = framebuffer;
|
|
938
|
+
state.mipFramebuffer = mipFramebuffer;
|
|
939
|
+
state.face = face;
|
|
940
|
+
state.depthAttached = singleDepth;
|
|
941
|
+
state.depthBuffer = depthBuffer;
|
|
942
|
+
state.samples = samples;
|
|
943
|
+
state.antiAlias = antiAlias;
|
|
944
|
+
if (samples && state.multisample && state.multisample.face !== face) {
|
|
945
|
+
state.multisample.face = face;
|
|
946
|
+
state.multisample.valid = false;
|
|
947
|
+
}
|
|
948
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
|
949
|
+
}
|
|
950
|
+
this.renderTexture = texture;
|
|
951
|
+
this.renderFace = texture ? face : 0;
|
|
952
|
+
this.renderDepth = texture !== null && depth;
|
|
953
|
+
gl.frontFace(texture ? gl.CCW : gl.CW);
|
|
954
|
+
this.applyScissor();
|
|
955
|
+
}
|
|
956
|
+
changedTarget() {
|
|
957
|
+
if (this.renderTexture) {
|
|
958
|
+
const state = this.textureState(this.renderTexture);
|
|
959
|
+
state.dirty = true;
|
|
960
|
+
if (state.multisample) {
|
|
961
|
+
if (state.samples) {
|
|
962
|
+
state.multisample.dirty = true;
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
state.multisample.valid = false;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
this.resolveDirty = true;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
finishTarget() {
|
|
974
|
+
if (!this.renderTexture) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const texture = this.renderTexture;
|
|
978
|
+
const state = this.textureState(texture);
|
|
979
|
+
if (state.dirty) {
|
|
980
|
+
const gl = this.gl;
|
|
981
|
+
resolveTarget(this.gl, texture, state, this.renderFace, this.scissor);
|
|
982
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
983
|
+
if (texture.cube) {
|
|
984
|
+
// generateMipmap(CUBE_MAP) would overwrite manually uploaded mips on
|
|
985
|
+
// untouched faces. AIR regenerates only the face just rendered.
|
|
986
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
987
|
+
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.renderFace;
|
|
988
|
+
// The base-size depth attachment cannot accompany smaller mip images.
|
|
989
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
990
|
+
if (state.depthAttached) {
|
|
991
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, null);
|
|
992
|
+
}
|
|
993
|
+
for (let level = 1; level < texture.levels; level++) {
|
|
994
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, state.framebuffer);
|
|
995
|
+
gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, face, state.handle, level - 1);
|
|
996
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, state.mipFramebuffer);
|
|
997
|
+
gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, face, state.handle, level);
|
|
998
|
+
const size = texture.width >> level;
|
|
999
|
+
gl.blitFramebuffer(0, 0, size * 2, size * 2, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.LINEAR);
|
|
1000
|
+
}
|
|
1001
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
1002
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + state.face, state.handle, 0);
|
|
1003
|
+
if (state.depthAttached) {
|
|
1004
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, state.depthBuffer);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
else if (!texture.rectangle) {
|
|
1008
|
+
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
1009
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
1010
|
+
}
|
|
1011
|
+
state.uploaded[this.renderFace] = (1 << texture.levels) - 1;
|
|
1012
|
+
state.dirty = false;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
setTexture(index, texture) {
|
|
1016
|
+
this.alive();
|
|
1017
|
+
range(index, 1, 8);
|
|
1018
|
+
if (texture && !this.textureOwners.has(texture)) {
|
|
1019
|
+
throw new Context3DError("disposed", "Context3D experiment: texture from another context");
|
|
1020
|
+
}
|
|
1021
|
+
// AIR accepts a disposed texture binding and reports 3663 when drawing.
|
|
1022
|
+
this.textureSlots[index] = texture;
|
|
1023
|
+
this.textureMask = (this.textureMask & ~(1 << index)) | (texture ? 1 << index : 0);
|
|
1024
|
+
if (!texture) {
|
|
1025
|
+
this.gl.activeTexture(this.gl.TEXTURE0 + index);
|
|
1026
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, null);
|
|
1027
|
+
this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, null);
|
|
1028
|
+
this.gl.bindSampler(index, null);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
setSampler(index, wrap, filter, mip) {
|
|
1032
|
+
this.alive();
|
|
1033
|
+
range(index, 1, this.samplerStates.length);
|
|
1034
|
+
const w = SAMPLER_WRAPS.indexOf(wrap);
|
|
1035
|
+
const f = SAMPLER_FILTERS.indexOf(filter);
|
|
1036
|
+
const m = SAMPLER_MIPS.indexOf(mip);
|
|
1037
|
+
if (w < 0 || f < 0 || m < 0) {
|
|
1038
|
+
throw new Context3DError("operation", "Context3D experiment: invalid sampler state");
|
|
1039
|
+
}
|
|
1040
|
+
this.samplerStates[index] = w | (f << 2) | (m << 5);
|
|
1041
|
+
}
|
|
1042
|
+
sampler(index) {
|
|
1043
|
+
let sampler = this.samplers[index];
|
|
1044
|
+
if (!sampler) {
|
|
1045
|
+
const gl = this.gl;
|
|
1046
|
+
sampler = gl.createSampler();
|
|
1047
|
+
if (!sampler) {
|
|
1048
|
+
throw new Context3DError("operation", "Context3D experiment: cannot create sampler");
|
|
1049
|
+
}
|
|
1050
|
+
const mode = (index >>> 2) & 7;
|
|
1051
|
+
const linear = mode !== 0;
|
|
1052
|
+
const filter = linear ? gl.LINEAR : gl.NEAREST;
|
|
1053
|
+
let minFilter = filter;
|
|
1054
|
+
switch (index >>> 5) {
|
|
1055
|
+
case 1:
|
|
1056
|
+
minFilter = linear ? gl.LINEAR_MIPMAP_NEAREST : gl.NEAREST_MIPMAP_NEAREST;
|
|
1057
|
+
break;
|
|
1058
|
+
case 2:
|
|
1059
|
+
minFilter = linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_LINEAR;
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
gl.samplerParameteri(sampler, gl.TEXTURE_MIN_FILTER, minFilter);
|
|
1063
|
+
gl.samplerParameteri(sampler, gl.TEXTURE_MAG_FILTER, filter);
|
|
1064
|
+
if (mode > 1) {
|
|
1065
|
+
if (this.anisotropy === undefined) {
|
|
1066
|
+
this.anisotropy = gl.getExtension("EXT_texture_filter_anisotropic");
|
|
1067
|
+
if (this.anisotropy) {
|
|
1068
|
+
this.maxAnisotropy = gl.getParameter(this.anisotropy.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (this.anisotropy) {
|
|
1072
|
+
gl.samplerParameterf(sampler, this.anisotropy.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(1 << (mode - 1), this.maxAnisotropy));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
gl.samplerParameteri(sampler, gl.TEXTURE_WRAP_S, index & 1 ? gl.REPEAT : gl.CLAMP_TO_EDGE);
|
|
1076
|
+
gl.samplerParameteri(sampler, gl.TEXTURE_WRAP_T, index & 2 ? gl.REPEAT : gl.CLAMP_TO_EDGE);
|
|
1077
|
+
gl.samplerParameteri(sampler, gl.TEXTURE_WRAP_R, (index & 3) === 3 ? gl.REPEAT : gl.CLAMP_TO_EDGE);
|
|
1078
|
+
this.samplers[index] = sampler;
|
|
1079
|
+
}
|
|
1080
|
+
return sampler;
|
|
1081
|
+
}
|
|
1082
|
+
setProgram(program) {
|
|
1083
|
+
this.alive();
|
|
1084
|
+
const state = this.program(program);
|
|
1085
|
+
if (!state.handle) {
|
|
1086
|
+
throw new Context3DError("program", "Context3D experiment: program not uploaded");
|
|
1087
|
+
}
|
|
1088
|
+
this.selected = program;
|
|
1089
|
+
for (let i = 0; i < state.samplers.length; i++) {
|
|
1090
|
+
const source = state.samplers[i].source;
|
|
1091
|
+
if (!source.ignore) {
|
|
1092
|
+
this.samplerStates[source.index] =
|
|
1093
|
+
(source.repeat ? 3 : 0) | (source.filter << 2) | (source.mip << 5);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
setVertex(index, buffer, offset, format) {
|
|
1098
|
+
this.alive();
|
|
1099
|
+
range(index, 1, 8);
|
|
1100
|
+
if (buffer === null) {
|
|
1101
|
+
this.vertices[index] = null;
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
this.buffer(buffer);
|
|
1105
|
+
const packed = format === "bytes4";
|
|
1106
|
+
const size = packed ? 4 : VERTEX_FORMATS.indexOf(format) + 1;
|
|
1107
|
+
range(1, size - 1, 4);
|
|
1108
|
+
if (!Number.isInteger(offset)) {
|
|
1109
|
+
throw new Context3DError("operation", "Context3D experiment: vertex offset must be an integer");
|
|
1110
|
+
}
|
|
1111
|
+
// AIR reports stride overruns at draw time, including on an existing binding.
|
|
1112
|
+
const valid = offset >= 0 && offset <= buffer.stride - (packed ? 1 : size);
|
|
1113
|
+
const previous = this.vertices[index];
|
|
1114
|
+
if (previous) {
|
|
1115
|
+
if (previous.buffer !== buffer) {
|
|
1116
|
+
previous.checkedIndex = null;
|
|
1117
|
+
}
|
|
1118
|
+
previous.buffer = buffer;
|
|
1119
|
+
previous.offset = offset;
|
|
1120
|
+
previous.size = size;
|
|
1121
|
+
previous.packed = packed;
|
|
1122
|
+
previous.valid = valid;
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
this.vertices[index] = {
|
|
1126
|
+
buffer,
|
|
1127
|
+
offset,
|
|
1128
|
+
size,
|
|
1129
|
+
packed,
|
|
1130
|
+
valid,
|
|
1131
|
+
checkedIndex: null,
|
|
1132
|
+
checkedIndexVersion: -1,
|
|
1133
|
+
checkedVertexVersion: -1,
|
|
1134
|
+
checkedFirst: 0,
|
|
1135
|
+
checkedCount: 0,
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
setConstants(stage, first, values, count) {
|
|
1140
|
+
this.alive();
|
|
1141
|
+
const dest = stage === "vertex" ? this.vertexConstants : this.fragmentConstants;
|
|
1142
|
+
range(first, count, dest.length / 4);
|
|
1143
|
+
range(0, count * 4, values.length);
|
|
1144
|
+
for (let i = 0; i < count * 4; i++) {
|
|
1145
|
+
dest[first * 4 + i] = values[i];
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
draw(indices, first, triangles) {
|
|
1149
|
+
this.target();
|
|
1150
|
+
if (this.scissorOutside) {
|
|
1151
|
+
throw new ScissorBoundsError("Scissor rectangle does not intersect the target");
|
|
1152
|
+
}
|
|
1153
|
+
const index = this.buffer(indices);
|
|
1154
|
+
if (!index.indices) {
|
|
1155
|
+
throw new Context3DError("indexBuffer", "Context3D draw needs an index buffer");
|
|
1156
|
+
}
|
|
1157
|
+
if (!this.selected) {
|
|
1158
|
+
throw new Context3DError("program", "Context3D draw needs a program");
|
|
1159
|
+
}
|
|
1160
|
+
const state = this.program(this.selected);
|
|
1161
|
+
if (this.textureMask & ~state.samplerMask) {
|
|
1162
|
+
throw new TextureBindingError("unused");
|
|
1163
|
+
}
|
|
1164
|
+
for (let i = 0; i < state.samplers.length; i++) {
|
|
1165
|
+
const source = state.samplers[i].source;
|
|
1166
|
+
const texture = this.textureSlots[source.index];
|
|
1167
|
+
for (let j = i + 1; texture && j < state.samplers.length; j++) {
|
|
1168
|
+
const other = state.samplers[j].source;
|
|
1169
|
+
if (this.textureSlots[other.index] === texture &&
|
|
1170
|
+
this.samplerStates[source.index] !== this.samplerStates[other.index]) {
|
|
1171
|
+
throw new TextureBindingError("conflicting");
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
const count = triangles === -1 ? Math.floor((indices.count - first) / 3) * 3 : triangles * 3;
|
|
1176
|
+
if (!Number.isInteger(first) || !Number.isInteger(count) || first < 0 || count < 0) {
|
|
1177
|
+
throw new Context3DError("triangleCount", "Context3D draw has invalid triangle parameters");
|
|
1178
|
+
}
|
|
1179
|
+
if (first > indices.count - count) {
|
|
1180
|
+
throw new Context3DError("drawRange", "Context3D draw exceeds its index buffer");
|
|
1181
|
+
}
|
|
1182
|
+
if (count % 3 !== 0) {
|
|
1183
|
+
throw new Context3DError("triangleCount", "Context3D experiment: incomplete triangle");
|
|
1184
|
+
}
|
|
1185
|
+
const maximum = validateIndices(index, first, count);
|
|
1186
|
+
const gl = this.gl;
|
|
1187
|
+
gl.bindVertexArray(this.vao);
|
|
1188
|
+
for (let i = 0; i < 8; i++) {
|
|
1189
|
+
gl.disableVertexAttribArray(i);
|
|
1190
|
+
}
|
|
1191
|
+
for (let i = 0; i < state.attributes.length; i++) {
|
|
1192
|
+
const slot = state.attributes[i];
|
|
1193
|
+
const binding = this.vertices[slot];
|
|
1194
|
+
if (!binding) {
|
|
1195
|
+
throw new Context3DError("vertexBinding", "Context3D experiment: missing vertex binding");
|
|
1196
|
+
}
|
|
1197
|
+
const buffer = this.buffer(binding.buffer);
|
|
1198
|
+
if (!binding.valid) {
|
|
1199
|
+
throw new VertexOffsetError("Vertex attribute exceeds its stride");
|
|
1200
|
+
}
|
|
1201
|
+
validateVertices(buffer, index, binding, first, count, maximum);
|
|
1202
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer.handle);
|
|
1203
|
+
gl.enableVertexAttribArray(slot);
|
|
1204
|
+
gl.vertexAttribPointer(slot, binding.size, binding.packed ? gl.UNSIGNED_BYTE : gl.FLOAT, binding.packed, binding.buffer.stride * 4, binding.offset * 4);
|
|
1205
|
+
}
|
|
1206
|
+
gl.useProgram(state.handle);
|
|
1207
|
+
gl.uniform1f(state.targetY, this.renderTexture ? -1 : 1);
|
|
1208
|
+
for (let i = 0; i < state.samplers.length; i++) {
|
|
1209
|
+
const binding = state.samplers[i];
|
|
1210
|
+
const slot = binding.source.index;
|
|
1211
|
+
const texture = this.textureSlots[slot];
|
|
1212
|
+
if (texture && texture === this.renderTexture) {
|
|
1213
|
+
throw new TextureBindingError("feedback");
|
|
1214
|
+
}
|
|
1215
|
+
const resource = texture && this.textures.get(texture);
|
|
1216
|
+
if (!resource || !texture) {
|
|
1217
|
+
throw new TextureBindingError("missing");
|
|
1218
|
+
}
|
|
1219
|
+
if (texture.cube !== binding.source.cube) {
|
|
1220
|
+
throw new TextureBindingError("dimension");
|
|
1221
|
+
}
|
|
1222
|
+
if ((texture.cube || texture.rectangle) && (this.samplerStates[slot] & 3) !== 0) {
|
|
1223
|
+
throw new TextureBindingError("wrap");
|
|
1224
|
+
}
|
|
1225
|
+
if (texture.rectangle && this.samplerStates[slot] >> 5 !== 0) {
|
|
1226
|
+
throw new TextureBindingError("rectangleMip");
|
|
1227
|
+
}
|
|
1228
|
+
if (texture.cube) {
|
|
1229
|
+
let uploaded = 0;
|
|
1230
|
+
for (let face = 0; face < 6; face++) {
|
|
1231
|
+
uploaded |= resource.uploaded[face];
|
|
1232
|
+
}
|
|
1233
|
+
if (!uploaded) {
|
|
1234
|
+
throw new TextureBindingError("unuploaded");
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
for (let face = 0; face < resource.uploaded.length; face++) {
|
|
1238
|
+
if (!texture.cube && !(resource.uploaded[face] & 1)) {
|
|
1239
|
+
throw new TextureBindingError("unuploaded");
|
|
1240
|
+
}
|
|
1241
|
+
// AIR requires every cube face and mip even with mip filtering disabled.
|
|
1242
|
+
if ((texture.cube || this.samplerStates[slot] >>> 5 !== 0) &&
|
|
1243
|
+
resource.uploaded[face] !== (1 << texture.levels) - 1) {
|
|
1244
|
+
throw new TextureBindingError("mipmaps");
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
// AGAL must say a compressed texture is one, and which: AIR reads nothing otherwise.
|
|
1248
|
+
if (this.samplerFormats && binding.source.format !== SAMPLER_FORMATS[texture.format]) {
|
|
1249
|
+
throw new TextureBindingError("format", slot);
|
|
1250
|
+
}
|
|
1251
|
+
gl.activeTexture(gl.TEXTURE0 + slot);
|
|
1252
|
+
gl.bindTexture(texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D, resource.handle);
|
|
1253
|
+
gl.bindSampler(slot, this.sampler(this.samplerStates[slot]));
|
|
1254
|
+
gl.uniform1i(binding.location, slot);
|
|
1255
|
+
}
|
|
1256
|
+
if (state.vertexBank) {
|
|
1257
|
+
gl.uniform4fv(state.vertexBank, this.vertexConstants);
|
|
1258
|
+
}
|
|
1259
|
+
else {
|
|
1260
|
+
this.uniforms(state.vertex, this.vertexConstants);
|
|
1261
|
+
}
|
|
1262
|
+
this.uniforms(state.fragment, this.fragmentConstants);
|
|
1263
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index.handle);
|
|
1264
|
+
gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, first * 2);
|
|
1265
|
+
this.changedTarget();
|
|
1266
|
+
}
|
|
1267
|
+
uniforms(locations, data) {
|
|
1268
|
+
for (let i = 0; i < locations.length; i++) {
|
|
1269
|
+
if (locations[i]) {
|
|
1270
|
+
this.gl.uniform4fv(locations[i], data, i * 4, 4);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
resolve() {
|
|
1275
|
+
if (!this.resolveFramebuffer) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
const gl = this.gl;
|
|
1279
|
+
if (this.resolveDirty) {
|
|
1280
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
|
|
1281
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
|
|
1282
|
+
if (this.scissor) {
|
|
1283
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
1284
|
+
}
|
|
1285
|
+
gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
1286
|
+
if (this.scissor) {
|
|
1287
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
1288
|
+
}
|
|
1289
|
+
this.resolveDirty = false;
|
|
1290
|
+
}
|
|
1291
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
|
|
1292
|
+
}
|
|
1293
|
+
read(width = this.width, height = this.height) {
|
|
1294
|
+
width = Math.max(0, Math.min(this.width, Math.floor(width) || 0));
|
|
1295
|
+
height = Math.max(0, Math.min(this.height, Math.floor(height) || 0));
|
|
1296
|
+
this.target(true);
|
|
1297
|
+
this.resolve();
|
|
1298
|
+
const gl = this.gl;
|
|
1299
|
+
// GL's rows run bottom-up, so the top-left region starts height rows below the top.
|
|
1300
|
+
gl.readPixels(0, this.height - height, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
|
|
1301
|
+
const row = width * 4;
|
|
1302
|
+
for (let y = 0; y < height; y++) {
|
|
1303
|
+
const from = (height - y - 1) * row;
|
|
1304
|
+
this.topDown.set(this.pixels.subarray(from, from + row), y * row);
|
|
1305
|
+
}
|
|
1306
|
+
return this.topDown;
|
|
1307
|
+
}
|
|
1308
|
+
present() {
|
|
1309
|
+
this.setRenderTarget(null);
|
|
1310
|
+
this.target();
|
|
1311
|
+
this.resolve();
|
|
1312
|
+
const gl = this.gl;
|
|
1313
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
|
|
1314
|
+
if (this.scissor) {
|
|
1315
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
1316
|
+
}
|
|
1317
|
+
gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
|
|
1318
|
+
if (this.scissor) {
|
|
1319
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
1320
|
+
}
|
|
1321
|
+
gl.flush();
|
|
1322
|
+
}
|
|
1323
|
+
dispose() {
|
|
1324
|
+
if (this.disposed) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
this.disposed = true;
|
|
1328
|
+
for (const buffer of this.buffers.keys()) {
|
|
1329
|
+
buffer.dispose();
|
|
1330
|
+
}
|
|
1331
|
+
for (const program of this.programs.keys()) {
|
|
1332
|
+
program.dispose();
|
|
1333
|
+
}
|
|
1334
|
+
for (const texture of this.textures.keys()) {
|
|
1335
|
+
texture.dispose();
|
|
1336
|
+
}
|
|
1337
|
+
this.textureSlots.fill(null);
|
|
1338
|
+
this.textureMask = 0;
|
|
1339
|
+
for (let i = 0; i < this.samplers.length; i++) {
|
|
1340
|
+
this.gl.deleteSampler(this.samplers[i]);
|
|
1341
|
+
this.samplers[i] = null;
|
|
1342
|
+
}
|
|
1343
|
+
this.gl.bindVertexArray(null);
|
|
1344
|
+
this.gl.useProgram(null);
|
|
1345
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
|
|
1346
|
+
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
|
|
1347
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
|
1348
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, null);
|
|
1349
|
+
this.gl.deleteVertexArray(this.vao);
|
|
1350
|
+
this.gl.deleteFramebuffer(this.framebuffer);
|
|
1351
|
+
this.gl.deleteFramebuffer(this.resolveFramebuffer);
|
|
1352
|
+
this.gl.deleteRenderbuffer(this.colorBuffer);
|
|
1353
|
+
this.gl.deleteProgram(this.copy2D?.handle ?? null);
|
|
1354
|
+
this.gl.deleteProgram(this.copyCube?.handle ?? null);
|
|
1355
|
+
this.copy2D = null;
|
|
1356
|
+
this.copyCube = null;
|
|
1357
|
+
this.gl.deleteTexture(this.texture);
|
|
1358
|
+
this.gl.deleteRenderbuffer(this.depthBuffer);
|
|
1359
|
+
this.framebuffer = null;
|
|
1360
|
+
this.resolveFramebuffer = null;
|
|
1361
|
+
this.colorBuffer = null;
|
|
1362
|
+
this.texture = null;
|
|
1363
|
+
this.depthBuffer = null;
|
|
1364
|
+
this.pixels = new Uint8Array(0);
|
|
1365
|
+
this.topDown = new Uint8Array(0);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
//# sourceMappingURL=context.js.map
|