@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.
@@ -1,4 +1,5 @@
1
- import { MOST_BACK_BUFFER, SAMPLER_FILTERS, SAMPLER_MIPS, SAMPLER_WRAPS, ScissorBoundsError, screenPixels, TextureBindingError, VERTEX_FORMATS, VertexOffsetError, } from "../backend.js";
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
- textureSlots = new Array(8).fill(null);
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(8).fill(null);
93
+ vertices = new Array(VERTEX_SLOTS).fill(null);
70
94
  enabledAttributes = 0;
71
95
  indexBuffer = null;
72
- texturesApplied = new Uint8Array(8);
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(128);
103
- fragmentConstants = constants(28);
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
- gl.clearColor(red, green, blue, alpha);
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, "bgra");
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 === "bgra") {
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 += format === "bgra" ? w * h * 4 : blockBytes(w, h, format === "compressedAlpha");
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
- range(1, width - 1, 2048);
767
- range(1, height - 1, 2048);
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 > 1024) {
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 !== "bgra" && rectangle) {
819
+ if (compressedFormat(format) && rectangle) {
781
820
  throw new Context3DError("unsupported", "Context3D rectangle textures are not compressed");
782
821
  }
783
- if (format !== "bgra" && (width < 4 || height < 4)) {
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.RGBA8;
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 === "bgra") {
868
- throw new Context3DError("textureUpload", "Context3D experiment: BGRA texture given blocks");
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 !== "bgra") {
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 (!this.renderTexture) {
1176
- return;
1230
+ if (this.renderTexture) {
1231
+ this.finishTexture(this.renderTexture, this.renderFace);
1177
1232
  }
1178
- const texture = this.renderTexture;
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, this.renderFace, this.scissor);
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 + this.renderFace;
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[this.renderFace] = (1 << texture.levels) - 1;
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, 8);
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, (source.repeat ? 3 : 0) | (source.filter << 2) | (source.mip << 5));
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, 8);
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
- for (let i = 0; i < 8; i++) {
1418
- const bit = 1 << i;
1419
- if (this.enabledAttributes & bit & ~state.attributeMask) {
1420
- gl.disableVertexAttribArray(i);
1421
- this.enabledAttributes &= ~bit;
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 && texture === this.renderTexture) {
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
- else {
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);