@swf2es/context3d 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -22
- package/dist/agal.d.ts +11 -2
- package/dist/agal.d.ts.map +1 -1
- package/dist/agal.js +318 -47
- package/dist/agal.js.map +1 -1
- package/dist/backend.d.ts +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/context.d.ts +24 -3
- package/dist/webgl/context.d.ts.map +1 -1
- package/dist/webgl/context.js +184 -43
- package/dist/webgl/context.js.map +1 -1
- package/dist/webgl/programs.d.ts +4 -1
- package/dist/webgl/programs.d.ts.map +1 -1
- package/dist/webgl/programs.js +7 -6
- package/dist/webgl/programs.js.map +1 -1
- package/dist/webgl/targets.d.ts +3 -1
- package/dist/webgl/targets.d.ts.map +1 -1
- package/dist/webgl/targets.js +5 -3
- package/dist/webgl/targets.js.map +1 -1
- package/dist/webgl/textures.d.ts +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/context.ts +220 -40
- package/src/webgl/programs.ts +10 -5
- package/src/webgl/targets.ts +4 -2
- package/src/webgl/textures.ts +183 -9
package/dist/webgl/context.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INDIRECT_BANK } from "../agal.js";
|
|
2
|
+
import { compressedFormat, FRAGMENT_CONSTANTS, MOST_BACK_BUFFER, MOST_CUBE, profileLimits, SAMPLER_FILTERS, SAMPLER_MIPS, SAMPLER_SLOTS, SAMPLER_WRAPS, ScissorBoundsError, screenPixels, TEXEL_BYTES, TextureBindingError, VERTEX_CONSTANTS, VERTEX_FORMATS, VERTEX_SLOTS, VertexOffsetError, } from "../backend.js";
|
|
2
3
|
import { GlState } from "../device/state.js";
|
|
3
4
|
import { blockBytes } from "../dxt.js";
|
|
4
5
|
import { Context3DError } from "../errors.js";
|
|
@@ -7,14 +8,29 @@ import { resourceLimits } from "./limits.js";
|
|
|
7
8
|
import { createCopyProgram, uploadProgram, } from "./programs.js";
|
|
8
9
|
import { BLEND_FUNCTIONS, DEPTH_FUNCTIONS, range, STENCIL_FUNCTIONS, StencilState, } from "./state.js";
|
|
9
10
|
import { allocateMultisample, deleteMultisample, resolveTarget, selectSamples, targetFramebuffer, } from "./targets.js";
|
|
10
|
-
import { uploadCompressedLevel, uploadTexture, uploadTextureBytes, } from "./textures.js";
|
|
11
|
+
import { storageFormat, uploadCompressedLevel, uploadTexture, uploadTextureBytes, } from "./textures.js";
|
|
11
12
|
import { validateIndices, validateVertices } from "./validation.js";
|
|
12
13
|
/** What AGAL's sampler must name to read each texture format: RGBA, DXT1 or DXT5. */
|
|
13
14
|
const SAMPLER_FORMATS = {
|
|
14
15
|
bgra: 0,
|
|
15
16
|
compressed: 1,
|
|
16
17
|
compressedAlpha: 2,
|
|
18
|
+
bgrPacked565: 0,
|
|
19
|
+
bgraPacked4444: 0,
|
|
20
|
+
rgbaHalfFloat: 0,
|
|
17
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* A clear colour's channel as Direct3D's 8-bit clear colour holds it, which AIR fills by
|
|
24
|
+
* truncation (0.5 clears to 127): within 0 to 1 and cut to its byte, NaN kept.
|
|
25
|
+
*/
|
|
26
|
+
function clearChannel(value) {
|
|
27
|
+
if (!(value > 0)) {
|
|
28
|
+
return value < 0 ? 0 : value;
|
|
29
|
+
}
|
|
30
|
+
return value >= 1 ? 1 : Math.floor(value * 255 + 1e-3) / 255;
|
|
31
|
+
}
|
|
32
|
+
/** AGAL's wraps (clamp, repeat, clamp_u_repeat_v, repeat_u_clamp_v) as SAMPLER_WRAPS indices. */
|
|
33
|
+
const WRAPS = [0, 3, 2, 1];
|
|
18
34
|
function constants(registers) {
|
|
19
35
|
const data = new Float32Array(registers * 4);
|
|
20
36
|
return {
|
|
@@ -40,6 +56,7 @@ export class WebGLContext3D {
|
|
|
40
56
|
gpuMost = null;
|
|
41
57
|
disposed = false;
|
|
42
58
|
samplerFormats = true;
|
|
59
|
+
profile = "baseline";
|
|
43
60
|
buffers = new Map();
|
|
44
61
|
vertexBuffers = 0;
|
|
45
62
|
bufferBytes = 0;
|
|
@@ -58,7 +75,14 @@ export class WebGLContext3D {
|
|
|
58
75
|
renderTexture = null;
|
|
59
76
|
renderDepth = false;
|
|
60
77
|
renderFace = 0;
|
|
61
|
-
|
|
78
|
+
/** Colour outputs 1 to 3, drawn into with renderTexture, and their cube faces. */
|
|
79
|
+
outputs = [null, null, null];
|
|
80
|
+
outputFaces = new Uint8Array(3);
|
|
81
|
+
outputsChanged = false;
|
|
82
|
+
/** Whether any of outputs is bound, so that a draw alone into its target looks at none. */
|
|
83
|
+
outputsBound = false;
|
|
84
|
+
floatTargets;
|
|
85
|
+
textureSlots = new Array(SAMPLER_SLOTS).fill(null);
|
|
62
86
|
textureMask = 0;
|
|
63
87
|
samplers = new Array(96).fill(null);
|
|
64
88
|
// Two wrap bits, three filter bits and two mip bits fit each slot in one byte.
|
|
@@ -66,10 +90,10 @@ export class WebGLContext3D {
|
|
|
66
90
|
anisotropy;
|
|
67
91
|
s3tc;
|
|
68
92
|
maxAnisotropy = 1;
|
|
69
|
-
vertices = new Array(
|
|
93
|
+
vertices = new Array(VERTEX_SLOTS).fill(null);
|
|
70
94
|
enabledAttributes = 0;
|
|
71
95
|
indexBuffer = null;
|
|
72
|
-
texturesApplied = new Uint8Array(
|
|
96
|
+
texturesApplied = new Uint8Array(SAMPLER_SLOTS);
|
|
73
97
|
selected = null;
|
|
74
98
|
framebuffer = null;
|
|
75
99
|
texture = null;
|
|
@@ -99,8 +123,8 @@ export class WebGLContext3D {
|
|
|
99
123
|
scissorRight = 0;
|
|
100
124
|
scissorBottom = 0;
|
|
101
125
|
scissorOutside = false;
|
|
102
|
-
vertexConstants = constants(
|
|
103
|
-
fragmentConstants = constants(
|
|
126
|
+
vertexConstants = constants(VERTEX_CONSTANTS);
|
|
127
|
+
fragmentConstants = constants(FRAGMENT_CONSTANTS);
|
|
104
128
|
pixels = new Uint8Array(0);
|
|
105
129
|
topDown = new Uint8Array(0);
|
|
106
130
|
vao;
|
|
@@ -159,6 +183,9 @@ export class WebGLContext3D {
|
|
|
159
183
|
}
|
|
160
184
|
}
|
|
161
185
|
this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
|
|
186
|
+
if (this.outputsChanged || state?.outputs) {
|
|
187
|
+
this.attachOutputs(state);
|
|
188
|
+
}
|
|
162
189
|
this.drawState.viewport(0, 0, texture?.width ?? this.drawnWidth, texture?.height ?? this.drawnHeight);
|
|
163
190
|
}
|
|
164
191
|
/** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
|
|
@@ -326,7 +353,8 @@ export class WebGLContext3D {
|
|
|
326
353
|
const gl = this.gl;
|
|
327
354
|
let bits = 0;
|
|
328
355
|
if (mask & 1) {
|
|
329
|
-
|
|
356
|
+
// A float target too: AIR clears through Direct3D's 8-bit colour.
|
|
357
|
+
gl.clearColor(clearChannel(red), clearChannel(green), clearChannel(blue), clearChannel(alpha));
|
|
330
358
|
bits |= gl.COLOR_BUFFER_BIT;
|
|
331
359
|
this.changedTarget();
|
|
332
360
|
}
|
|
@@ -670,6 +698,7 @@ export class WebGLContext3D {
|
|
|
670
698
|
attributeMask: 0,
|
|
671
699
|
vertex: [],
|
|
672
700
|
vertexBank: null,
|
|
701
|
+
outputs: 1,
|
|
673
702
|
fragment: [],
|
|
674
703
|
samplers: [],
|
|
675
704
|
samplerMask: 0,
|
|
@@ -681,11 +710,17 @@ export class WebGLContext3D {
|
|
|
681
710
|
vertexVersion: -1,
|
|
682
711
|
};
|
|
683
712
|
const program = {
|
|
713
|
+
get outputs() {
|
|
714
|
+
return state.outputs;
|
|
715
|
+
},
|
|
716
|
+
get attributes() {
|
|
717
|
+
return state.attributeMask;
|
|
718
|
+
},
|
|
684
719
|
upload: (vertex, fragment) => {
|
|
685
720
|
this.alive();
|
|
686
721
|
this.program(program);
|
|
687
722
|
this.drawState.forget();
|
|
688
|
-
uploadProgram(this.gl, state, vertex, fragment);
|
|
723
|
+
uploadProgram(this.gl, state, vertex, fragment, this.profile);
|
|
689
724
|
if (this.selected === program) {
|
|
690
725
|
this.setProgram(program);
|
|
691
726
|
}
|
|
@@ -731,15 +766,15 @@ export class WebGLContext3D {
|
|
|
731
766
|
createTexture(width, height, cube = false, format = "bgra", streaming = 0) {
|
|
732
767
|
return this.allocateTexture(width, height, cube, false, format, streaming);
|
|
733
768
|
}
|
|
734
|
-
createRectangleTexture(width, height) {
|
|
735
|
-
return this.allocateTexture(width, height, false, true,
|
|
769
|
+
createRectangleTexture(width, height, format = "bgra") {
|
|
770
|
+
return this.allocateTexture(width, height, false, true, format);
|
|
736
771
|
}
|
|
737
772
|
/** GL's internal format for a compressed texture's S3TC, or 0 where the context takes none. */
|
|
738
773
|
s3tcFormat(format) {
|
|
739
774
|
if (this.s3tc === undefined) {
|
|
740
775
|
this.s3tc = this.gl.getExtension("WEBGL_compressed_texture_s3tc");
|
|
741
776
|
}
|
|
742
|
-
if (!this.s3tc || format
|
|
777
|
+
if (!this.s3tc || !compressedFormat(format)) {
|
|
743
778
|
return 0;
|
|
744
779
|
}
|
|
745
780
|
// AIR's DXT1 has DXT1's one bit of alpha: a block of three colours has transparent black.
|
|
@@ -757,18 +792,22 @@ export class WebGLContext3D {
|
|
|
757
792
|
for (let level = 0; level < levels; level++) {
|
|
758
793
|
const w = Math.max(1, width >> level);
|
|
759
794
|
const h = Math.max(1, height >> level);
|
|
760
|
-
size +=
|
|
795
|
+
size += compressedFormat(format)
|
|
796
|
+
? blockBytes(w, h, format === "compressedAlpha")
|
|
797
|
+
: w * h * (TEXEL_BYTES[format] ?? 4);
|
|
761
798
|
}
|
|
762
799
|
return size * faces;
|
|
763
800
|
}
|
|
764
801
|
allocateTexture(width, height, cube, rectangle, format, streaming = 0) {
|
|
765
802
|
this.alive();
|
|
766
|
-
|
|
767
|
-
|
|
803
|
+
const limits = profileLimits(this.profile);
|
|
804
|
+
const most = rectangle ? limits.rectangleSize : limits.textureSize;
|
|
805
|
+
range(1, width - 1, most);
|
|
806
|
+
range(1, height - 1, most);
|
|
768
807
|
if (cube && width !== height) {
|
|
769
808
|
throw new Context3DError("textureShape", "Context3D cube faces must be square");
|
|
770
809
|
}
|
|
771
|
-
if (cube && width >
|
|
810
|
+
if (cube && width > MOST_CUBE) {
|
|
772
811
|
throw new Context3DError("textureSize", "Context3D cube faces must be at most 1024");
|
|
773
812
|
}
|
|
774
813
|
const faces = cube ? 6 : 1;
|
|
@@ -777,14 +816,14 @@ export class WebGLContext3D {
|
|
|
777
816
|
if (!rectangle && ((width & (width - 1)) !== 0 || (height & (height - 1)) !== 0)) {
|
|
778
817
|
throw new Context3DError("texturePowerOfTwo", "Context3D texture size must be a power of two");
|
|
779
818
|
}
|
|
780
|
-
if (format
|
|
819
|
+
if (compressedFormat(format) && rectangle) {
|
|
781
820
|
throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
|
|
782
821
|
}
|
|
783
|
-
if (format
|
|
822
|
+
if (compressedFormat(format) && (width < 4 || height < 4)) {
|
|
784
823
|
throw new Context3DError("compressedSize", "Context3D compressed textures are at least 4x4");
|
|
785
824
|
}
|
|
786
825
|
const gl = this.gl;
|
|
787
|
-
const storage = this.s3tcFormat(format) || gl
|
|
826
|
+
const storage = this.s3tcFormat(format) || storageFormat(gl, format);
|
|
788
827
|
const size = this.chainBytes(width, height, levels, faces, format);
|
|
789
828
|
const kind = cube ? 2 : rectangle ? 1 : 0;
|
|
790
829
|
const countLimit = cube
|
|
@@ -799,7 +838,8 @@ export class WebGLContext3D {
|
|
|
799
838
|
: this.limits.max2DTextureBytes;
|
|
800
839
|
// AIR reserves half the base size for mips, independently of the exact
|
|
801
840
|
// chain storage, and charges DXT1 an eighth of BGRA and DXT5 a quarter.
|
|
802
|
-
const quotaBytes = (width * height * faces * (rectangle ? 4 : 6)) /
|
|
841
|
+
const quotaBytes = (width * height * faces * (rectangle ? 4 : 6) * (TEXEL_BYTES[format] ?? 4)) /
|
|
842
|
+
4 /
|
|
803
843
|
(format === "compressed" ? 8 : format === "compressedAlpha" ? 4 : 1);
|
|
804
844
|
if (this.textures.size >= this.limits.maxTextures ||
|
|
805
845
|
this.textureCounts[kind] >= countLimit ||
|
|
@@ -830,6 +870,7 @@ export class WebGLContext3D {
|
|
|
830
870
|
multisample: null,
|
|
831
871
|
samples: 0,
|
|
832
872
|
antiAlias: 0,
|
|
873
|
+
outputs: 0,
|
|
833
874
|
base: 0,
|
|
834
875
|
};
|
|
835
876
|
const texture = {
|
|
@@ -864,8 +905,8 @@ export class WebGLContext3D {
|
|
|
864
905
|
uploadCompressed: (blocks, level = 0, face = 0) => {
|
|
865
906
|
this.alive();
|
|
866
907
|
this.textureState(texture);
|
|
867
|
-
if (format
|
|
868
|
-
throw new Context3DError("textureUpload", "Context3D experiment:
|
|
908
|
+
if (!compressedFormat(format)) {
|
|
909
|
+
throw new Context3DError("textureUpload", "Context3D experiment: uncompressed texture given blocks");
|
|
869
910
|
}
|
|
870
911
|
try {
|
|
871
912
|
uploadCompressedLevel(gl, texture, state, blocks, level, face);
|
|
@@ -901,6 +942,13 @@ export class WebGLContext3D {
|
|
|
901
942
|
state.framebuffer = null;
|
|
902
943
|
state.mipFramebuffer = null;
|
|
903
944
|
state.depthBuffer = null;
|
|
945
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
946
|
+
if (this.outputs[i] === texture) {
|
|
947
|
+
this.outputs[i] = null;
|
|
948
|
+
this.outputsChanged = true;
|
|
949
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
904
952
|
for (let i = 0; i < this.textureSlots.length; i++) {
|
|
905
953
|
if (this.textureSlots[i] === texture) {
|
|
906
954
|
this.texturesApplied[i] = 0;
|
|
@@ -1023,7 +1071,7 @@ export class WebGLContext3D {
|
|
|
1023
1071
|
this.sampler(0);
|
|
1024
1072
|
this.drawState.forget();
|
|
1025
1073
|
this.texturesApplied[0] = 0;
|
|
1026
|
-
const target = allocateMultisample(this.gl, texture, old, samples, depth, this.depthWrite, this.stencilWrite);
|
|
1074
|
+
const target = allocateMultisample(this.gl, texture, old, samples, depth, this.depthWrite, this.stencilWrite, state.storage);
|
|
1027
1075
|
if (same) {
|
|
1028
1076
|
if (newDepth) {
|
|
1029
1077
|
this.targetDepthBytes += bytes;
|
|
@@ -1055,9 +1103,10 @@ export class WebGLContext3D {
|
|
|
1055
1103
|
const gl = this.gl;
|
|
1056
1104
|
if (texture) {
|
|
1057
1105
|
this.textureState(texture);
|
|
1058
|
-
if (texture.format
|
|
1106
|
+
if (compressedFormat(texture.format)) {
|
|
1059
1107
|
throw new Context3DError("operation", "Context3D experiment: compressed textures are no render targets");
|
|
1060
1108
|
}
|
|
1109
|
+
this.renderFormat(texture.format);
|
|
1061
1110
|
}
|
|
1062
1111
|
const samples = texture ? this.multisamples(antiAlias, depth) : 0;
|
|
1063
1112
|
this.finishTarget();
|
|
@@ -1155,6 +1204,12 @@ export class WebGLContext3D {
|
|
|
1155
1204
|
this.applyScissor();
|
|
1156
1205
|
}
|
|
1157
1206
|
changedTarget() {
|
|
1207
|
+
for (let i = 0; this.outputsBound && i < this.outputs.length; i++) {
|
|
1208
|
+
const output = this.outputs[i];
|
|
1209
|
+
if (output) {
|
|
1210
|
+
this.textureState(output).dirty = true;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1158
1213
|
if (this.renderTexture) {
|
|
1159
1214
|
const state = this.textureState(this.renderTexture);
|
|
1160
1215
|
state.dirty = true;
|
|
@@ -1172,22 +1227,33 @@ export class WebGLContext3D {
|
|
|
1172
1227
|
}
|
|
1173
1228
|
}
|
|
1174
1229
|
finishTarget() {
|
|
1175
|
-
if (
|
|
1176
|
-
|
|
1230
|
+
if (this.renderTexture) {
|
|
1231
|
+
this.finishTexture(this.renderTexture, this.renderFace);
|
|
1177
1232
|
}
|
|
1178
|
-
|
|
1233
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1234
|
+
const output = this.outputs[i];
|
|
1235
|
+
if (output) {
|
|
1236
|
+
this.finishTexture(output, this.outputFaces[i]);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
/** A texture drawn into made whole again: resolved, and its mips regenerated. */
|
|
1241
|
+
finishTexture(texture, renderFace) {
|
|
1179
1242
|
const state = this.textureState(texture);
|
|
1180
1243
|
if (state.dirty) {
|
|
1181
1244
|
const gl = this.gl;
|
|
1182
1245
|
this.drawState.forget();
|
|
1183
1246
|
this.texturesApplied[0] = 0;
|
|
1184
|
-
resolveTarget(this.gl, texture, state,
|
|
1247
|
+
resolveTarget(this.gl, texture, state, renderFace, this.scissor);
|
|
1185
1248
|
gl.activeTexture(gl.TEXTURE0);
|
|
1186
1249
|
if (texture.cube) {
|
|
1250
|
+
// An extra colour output may never have been a target of its own.
|
|
1251
|
+
state.framebuffer ??= gl.createFramebuffer();
|
|
1252
|
+
state.mipFramebuffer ??= gl.createFramebuffer();
|
|
1187
1253
|
// generateMipmap(CUBE_MAP) would overwrite manually uploaded mips on
|
|
1188
1254
|
// untouched faces. AIR regenerates only the face just rendered.
|
|
1189
1255
|
gl.disable(gl.SCISSOR_TEST);
|
|
1190
|
-
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X +
|
|
1256
|
+
const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderFace;
|
|
1191
1257
|
// The base-size depth attachment cannot accompany smaller mip images.
|
|
1192
1258
|
this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
1193
1259
|
if (state.depthAttached) {
|
|
@@ -1211,13 +1277,81 @@ export class WebGLContext3D {
|
|
|
1211
1277
|
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
1212
1278
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
1213
1279
|
}
|
|
1214
|
-
state.uploaded[
|
|
1280
|
+
state.uploaded[renderFace] = (1 << texture.levels) - 1;
|
|
1215
1281
|
state.dirty = false;
|
|
1216
1282
|
}
|
|
1217
1283
|
}
|
|
1284
|
+
/** Half floats are drawn into where the GPU's WebGL renders float colour, as every desktop one does. */
|
|
1285
|
+
renderFormat(format) {
|
|
1286
|
+
if (format !== "rgbaHalfFloat") {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
this.floatTargets ??= this.gl.getExtension("EXT_color_buffer_float") !== null;
|
|
1290
|
+
if (!this.floatTargets) {
|
|
1291
|
+
throw new Context3DError("unsupported", "Context3D: this GPU draws into no half floats");
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
setColorOutput(index, texture, face = 0) {
|
|
1295
|
+
this.alive();
|
|
1296
|
+
range(index - 1, 1, this.outputs.length);
|
|
1297
|
+
if (texture) {
|
|
1298
|
+
this.textureState(texture);
|
|
1299
|
+
range(face, 1, texture.cube ? 6 : 1);
|
|
1300
|
+
if (compressedFormat(texture.format)) {
|
|
1301
|
+
throw new Context3DError("operation", "Context3D experiment: compressed textures are no render targets");
|
|
1302
|
+
}
|
|
1303
|
+
this.renderFormat(texture.format);
|
|
1304
|
+
}
|
|
1305
|
+
if (this.outputs[index - 1] === texture && this.outputFaces[index - 1] === face) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
const previous = this.outputs[index - 1];
|
|
1309
|
+
if (previous) {
|
|
1310
|
+
this.finishTexture(previous, this.outputFaces[index - 1]);
|
|
1311
|
+
}
|
|
1312
|
+
this.outputs[index - 1] = texture;
|
|
1313
|
+
this.outputFaces[index - 1] = texture ? face : 0;
|
|
1314
|
+
this.outputsChanged = true;
|
|
1315
|
+
this.outputsBound = this.outputMask() !== 0;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* The current target's framebuffer with the extra colour outputs attached, or without
|
|
1319
|
+
* them: its draws write every output bound, and a program's unwritten ones are refused
|
|
1320
|
+
* before they draw.
|
|
1321
|
+
*/
|
|
1322
|
+
attachOutputs(state) {
|
|
1323
|
+
const want = state ? this.outputMask() : 0;
|
|
1324
|
+
if (!state || (!this.outputsChanged && state.outputs === want)) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (want && state.samples) {
|
|
1328
|
+
throw new Context3DError("unsupported", "Context3D: antialiased colour outputs");
|
|
1329
|
+
}
|
|
1330
|
+
const gl = this.gl;
|
|
1331
|
+
const buffers = [gl.COLOR_ATTACHMENT0];
|
|
1332
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1333
|
+
const output = this.outputs[i];
|
|
1334
|
+
const handle = output ? this.textureState(output).handle : null;
|
|
1335
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1 + i, output?.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.outputFaces[i] : gl.TEXTURE_2D, handle, 0);
|
|
1336
|
+
buffers.push(output ? gl.COLOR_ATTACHMENT1 + i : gl.NONE);
|
|
1337
|
+
}
|
|
1338
|
+
gl.drawBuffers(buffers);
|
|
1339
|
+
state.outputs = want;
|
|
1340
|
+
this.outputsChanged = false;
|
|
1341
|
+
}
|
|
1342
|
+
/** The extra colour outputs bound, a bit each from output 1. */
|
|
1343
|
+
outputMask() {
|
|
1344
|
+
let mask = 0;
|
|
1345
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
1346
|
+
if (this.outputs[i]) {
|
|
1347
|
+
mask |= 1 << i;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return mask;
|
|
1351
|
+
}
|
|
1218
1352
|
setTexture(index, texture) {
|
|
1219
1353
|
this.alive();
|
|
1220
|
-
range(index, 1,
|
|
1354
|
+
range(index, 1, SAMPLER_SLOTS);
|
|
1221
1355
|
if (texture && !this.textureOwners.has(texture)) {
|
|
1222
1356
|
throw new Context3DError("disposed", "Context3D experiment: texture from another context");
|
|
1223
1357
|
}
|
|
@@ -1302,13 +1436,13 @@ export class WebGLContext3D {
|
|
|
1302
1436
|
for (let i = 0; i < state.samplers.length; i++) {
|
|
1303
1437
|
const source = state.samplers[i].source;
|
|
1304
1438
|
if (!source.ignore) {
|
|
1305
|
-
this.setSamplerState(source.index,
|
|
1439
|
+
this.setSamplerState(source.index, WRAPS[source.wrap] | (source.filter << 2) | (source.mip << 5));
|
|
1306
1440
|
}
|
|
1307
1441
|
}
|
|
1308
1442
|
}
|
|
1309
1443
|
setVertex(index, buffer, offset, format) {
|
|
1310
1444
|
this.alive();
|
|
1311
|
-
range(index, 1,
|
|
1445
|
+
range(index, 1, VERTEX_SLOTS);
|
|
1312
1446
|
if (buffer === null) {
|
|
1313
1447
|
this.vertices[index] = null;
|
|
1314
1448
|
return;
|
|
@@ -1414,13 +1548,14 @@ export class WebGLContext3D {
|
|
|
1414
1548
|
const maximum = validateIndices(index, first, count);
|
|
1415
1549
|
const gl = this.gl;
|
|
1416
1550
|
this.drawState.bindVertexArray(this.vao);
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
}
|
|
1551
|
+
// The streams enabled that the program does not read, highest first.
|
|
1552
|
+
let stale = this.enabledAttributes & ~state.attributeMask;
|
|
1553
|
+
while (stale) {
|
|
1554
|
+
const i = 31 - Math.clz32(stale);
|
|
1555
|
+
gl.disableVertexAttribArray(i);
|
|
1556
|
+
stale &= ~(1 << i);
|
|
1557
|
+
}
|
|
1558
|
+
this.enabledAttributes &= state.attributeMask;
|
|
1424
1559
|
for (let i = 0; i < state.attributes.length; i++) {
|
|
1425
1560
|
const slot = state.attributes[i];
|
|
1426
1561
|
const binding = this.vertices[slot];
|
|
@@ -1453,7 +1588,12 @@ export class WebGLContext3D {
|
|
|
1453
1588
|
const binding = state.samplers[i];
|
|
1454
1589
|
const slot = binding.source.index;
|
|
1455
1590
|
const texture = this.textureSlots[slot];
|
|
1456
|
-
if (texture &&
|
|
1591
|
+
if (texture &&
|
|
1592
|
+
(texture === this.renderTexture ||
|
|
1593
|
+
(this.outputsBound &&
|
|
1594
|
+
(texture === this.outputs[0] ||
|
|
1595
|
+
texture === this.outputs[1] ||
|
|
1596
|
+
texture === this.outputs[2])))) {
|
|
1457
1597
|
throw new TextureBindingError("feedback");
|
|
1458
1598
|
}
|
|
1459
1599
|
const resource = texture && this.textures.get(texture);
|
|
@@ -1515,11 +1655,12 @@ export class WebGLContext3D {
|
|
|
1515
1655
|
state.samplersSet = true;
|
|
1516
1656
|
if (state.vertexBank) {
|
|
1517
1657
|
if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
|
|
1518
|
-
gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
|
|
1658
|
+
gl.uniform4fv(state.vertexBank, this.vertexConstants.data, 0, INDIRECT_BANK * 4);
|
|
1519
1659
|
state.vertexVersion = this.vertexConstants.version;
|
|
1520
1660
|
}
|
|
1521
1661
|
}
|
|
1522
|
-
|
|
1662
|
+
// An indirect program's own uniforms are only the registers past its bank.
|
|
1663
|
+
if (state.vertex.length) {
|
|
1523
1664
|
this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions, state.vertexValues);
|
|
1524
1665
|
}
|
|
1525
1666
|
this.uniforms(state.fragment, this.fragmentConstants, state.fragmentVersions, state.fragmentValues);
|