@swf2es/context3d 0.1.8 → 0.1.11

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.
@@ -21,6 +21,7 @@ import {
21
21
  type VertexFormat,
22
22
  VertexOffsetError,
23
23
  } from "../backend.js";
24
+ import { GlState } from "../device/state.js";
24
25
  import { blockBytes, type DxtEncoder } from "../dxt.js";
25
26
  import { Context3DError } from "../errors.js";
26
27
 
@@ -67,9 +68,17 @@ const SAMPLER_FORMATS: Record<TextureFormat, number> = {
67
68
  compressedAlpha: 2,
68
69
  };
69
70
 
71
+ interface Constants {
72
+ data: Float32Array;
73
+ versions: Float64Array;
74
+ version: number;
75
+ writing: number;
76
+ }
77
+
70
78
  /** An owned WebGL2 context, deliberately independent of the 2D renderer. */
71
79
  export class WebGLContext3D implements Context3D {
72
80
  readonly gl: WebGL2RenderingContext;
81
+ private readonly drawState: GlState;
73
82
  width = 0;
74
83
  height = 0;
75
84
  disposed = false;
@@ -101,6 +110,9 @@ export class WebGLContext3D implements Context3D {
101
110
  private s3tc: WEBGL_compressed_texture_s3tc | null | undefined;
102
111
  private maxAnisotropy = 1;
103
112
  private readonly vertices: (VertexBinding | null)[] = new Array(8).fill(null);
113
+ private enabledAttributes = 0;
114
+ private indexBuffer: WebGLBuffer | null = null;
115
+ private readonly texturesApplied = new Uint8Array(8);
104
116
  private selected: Program3D | null = null;
105
117
  private framebuffer: WebGLFramebuffer | null = null;
106
118
  private texture: WebGLTexture | null = null;
@@ -110,8 +122,11 @@ export class WebGLContext3D implements Context3D {
110
122
  private depthBuffer: WebGLRenderbuffer | null = null;
111
123
  private antiAlias = 0;
112
124
  private depthWrite = true;
125
+ private depthCompare: CompareMode = "less";
113
126
  private blendEnabled = false;
114
- private cullEnabled = false;
127
+ private blendSource: BlendFactor = "one";
128
+ private blendDestination: BlendFactor = "zero";
129
+ private cullFace: CullFace = "none";
115
130
  private colorWrite = 15;
116
131
  private readonly stencilFront = new StencilState();
117
132
  private readonly stencilBack = new StencilState();
@@ -125,8 +140,18 @@ export class WebGLContext3D implements Context3D {
125
140
  private scissorRight = 0;
126
141
  private scissorBottom = 0;
127
142
  private scissorOutside = false;
128
- private readonly vertexConstants = new Float32Array(128 * 4);
129
- private readonly fragmentConstants = new Float32Array(28 * 4);
143
+ private readonly vertexConstants: Constants = {
144
+ data: new Float32Array(128 * 4),
145
+ versions: new Float64Array(128),
146
+ version: 0,
147
+ writing: 0,
148
+ };
149
+ private readonly fragmentConstants: Constants = {
150
+ data: new Float32Array(28 * 4),
151
+ versions: new Float64Array(28),
152
+ version: 0,
153
+ writing: 0,
154
+ };
130
155
  private pixels = new Uint8Array(0);
131
156
  private topDown = new Uint8Array(0);
132
157
  private readonly vao: WebGLVertexArrayObject;
@@ -148,6 +173,7 @@ export class WebGLContext3D implements Context3D {
148
173
  );
149
174
  }
150
175
 
176
+ this.drawState = new GlState(gl);
151
177
  const vao = gl.createVertexArray();
152
178
  if (!vao) {
153
179
  throw new Context3DError("operation", "Context3D experiment: cannot create vertex array");
@@ -195,11 +221,14 @@ export class WebGLContext3D implements Context3D {
195
221
  this.seedTarget(texture, state);
196
222
  }
197
223
  }
198
- this.gl.bindFramebuffer(
199
- this.gl.FRAMEBUFFER,
200
- state ? targetFramebuffer(state) : this.framebuffer,
201
- );
202
- this.gl.viewport(0, 0, texture?.width ?? this.width, texture?.height ?? this.height);
224
+ this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
225
+ this.drawState.viewport(0, 0, texture?.width ?? this.width, texture?.height ?? this.height);
226
+ }
227
+
228
+ /** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
229
+ private bindFramebuffer(target: number, framebuffer: WebGLFramebuffer | null): void {
230
+ this.drawState.forget();
231
+ this.gl.bindFramebuffer(target, framebuffer);
203
232
  }
204
233
 
205
234
  configure(width: number, height: number, depth = false, antiAlias = 0): void {
@@ -242,18 +271,20 @@ export class WebGLContext3D implements Context3D {
242
271
  throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
243
272
  }
244
273
 
274
+ this.texturesApplied[0] = 0;
275
+ gl.activeTexture(gl.TEXTURE0);
245
276
  gl.bindTexture(gl.TEXTURE_2D, texture);
246
277
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
247
278
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
248
279
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
249
- gl.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
280
+ this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
250
281
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
251
282
  if (resolveFramebuffer) {
252
283
  if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
253
284
  throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
254
285
  }
255
286
 
256
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
287
+ this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
257
288
  gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
258
289
  gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, width, height);
259
290
  gl.framebufferRenderbuffer(
@@ -294,7 +325,7 @@ export class WebGLContext3D implements Context3D {
294
325
  gl.deleteRenderbuffer(depthBuffer);
295
326
  gl.deleteRenderbuffer(colorBuffer);
296
327
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
297
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
328
+ this.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
298
329
  throw error;
299
330
  }
300
331
 
@@ -485,9 +516,15 @@ export class WebGLContext3D implements Context3D {
485
516
 
486
517
  setDepth(write: boolean, compare: CompareMode): void {
487
518
  this.alive();
488
- this.depthWrite = write;
489
- this.gl.depthMask(write);
490
- this.gl.depthFunc(this.gl[DEPTH_FUNCTIONS[compare]]);
519
+ if (this.depthWrite !== write) {
520
+ this.depthWrite = write;
521
+ this.gl.depthMask(write);
522
+ }
523
+
524
+ if (this.depthCompare !== compare) {
525
+ this.depthCompare = compare;
526
+ this.gl.depthFunc(this.gl[DEPTH_FUNCTIONS[compare]]);
527
+ }
491
528
  }
492
529
 
493
530
  setScissor(x: number | null, y = 0, width = 0, height = 0): void {
@@ -541,26 +578,42 @@ export class WebGLContext3D implements Context3D {
541
578
 
542
579
  setBlend(source: BlendFactor, destination: BlendFactor): void {
543
580
  this.alive();
581
+ if (this.blendSource === source && this.blendDestination === destination) {
582
+ return;
583
+ }
584
+
544
585
  const gl = this.gl;
586
+ const enabled = this.blendEnabled;
587
+ this.blendSource = source;
588
+ this.blendDestination = destination;
545
589
  this.blendEnabled = source !== "one" || destination !== "zero";
546
- if (source === "one" && destination === "zero") {
590
+ if (!this.blendEnabled) {
547
591
  gl.disable(gl.BLEND);
548
592
  } else {
549
593
  gl.blendFunc(gl[BLEND_FUNCTIONS[source]], gl[BLEND_FUNCTIONS[destination]]);
550
- gl.enable(gl.BLEND);
594
+ if (!enabled) {
595
+ gl.enable(gl.BLEND);
596
+ }
551
597
  }
552
598
  }
553
599
 
554
600
  setCulling(face: CullFace): void {
555
601
  this.alive();
602
+ const previous = this.cullFace;
603
+ if (previous === face) {
604
+ return;
605
+ }
606
+
556
607
  const gl = this.gl;
557
- this.cullEnabled = face !== "none";
608
+ this.cullFace = face;
558
609
  if (face === "none") {
559
610
  gl.disable(gl.CULL_FACE);
560
611
  return;
561
612
  }
562
613
 
563
- gl.enable(gl.CULL_FACE);
614
+ if (previous === "none") {
615
+ gl.enable(gl.CULL_FACE);
616
+ }
564
617
  switch (face) {
565
618
  case "back":
566
619
  gl.cullFace(gl.BACK);
@@ -600,6 +653,9 @@ export class WebGLContext3D implements Context3D {
600
653
  }
601
654
 
602
655
  const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
656
+ if (stride === 0) {
657
+ this.indexBuffer = null;
658
+ }
603
659
  gl.bindVertexArray(this.vao);
604
660
  gl.bindBuffer(target, handle);
605
661
  gl.bufferData(target, bytes, gl.STATIC_DRAW);
@@ -620,18 +676,34 @@ export class WebGLContext3D implements Context3D {
620
676
  upload: (values, start, amount) => {
621
677
  this.alive();
622
678
  this.buffer(buffer);
623
- uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
679
+ try {
680
+ uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
681
+ } finally {
682
+ // Source getters can draw before the upload replaces the VAO's index binding.
683
+ if (stride === 0) {
684
+ this.indexBuffer = null;
685
+ }
686
+ }
624
687
  },
625
688
  uploadBytes: (data, offset, start, amount) => {
626
689
  this.alive();
627
690
  this.buffer(buffer);
628
- uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
691
+ try {
692
+ uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
693
+ } finally {
694
+ if (stride === 0) {
695
+ this.indexBuffer = null;
696
+ }
697
+ }
629
698
  },
630
699
  dispose: () => {
631
700
  if (!this.buffers.delete(buffer)) {
632
701
  return;
633
702
  }
634
703
 
704
+ if (this.indexBuffer === handle) {
705
+ this.indexBuffer = null;
706
+ }
635
707
  gl.deleteBuffer(handle);
636
708
  this.bufferBytes -= bytes;
637
709
  if (stride !== 0) {
@@ -680,17 +752,24 @@ export class WebGLContext3D implements Context3D {
680
752
  const state: ProgramState = {
681
753
  handle: null,
682
754
  targetY: null,
755
+ targetYValue: Number.NaN,
683
756
  attributes: [],
757
+ attributeMask: 0,
684
758
  vertex: [],
685
759
  vertexBank: null,
686
760
  fragment: [],
687
761
  samplers: [],
688
762
  samplerMask: 0,
763
+ samplersSet: false,
764
+ vertexVersions: new Float64Array(0),
765
+ fragmentVersions: new Float64Array(0),
766
+ vertexVersion: -1,
689
767
  };
690
768
  const program: Program3D = {
691
769
  upload: (vertex, fragment) => {
692
770
  this.alive();
693
771
  this.program(program);
772
+ this.drawState.forget();
694
773
  uploadProgram(this.gl, state, vertex, fragment);
695
774
 
696
775
  if (this.selected === program) {
@@ -703,6 +782,7 @@ export class WebGLContext3D implements Context3D {
703
782
  }
704
783
 
705
784
  // WebGL keeps a deleted program alive while it is still current.
785
+ this.drawState.forget();
706
786
  if (this.gl.getParameter(this.gl.CURRENT_PROGRAM) === state.handle) {
707
787
  this.gl.useProgram(null);
708
788
  }
@@ -711,11 +791,14 @@ export class WebGLContext3D implements Context3D {
711
791
  state.handle = null;
712
792
  state.targetY = null;
713
793
  state.attributes.length = 0;
794
+ state.attributeMask = 0;
714
795
  state.vertex.length = 0;
715
796
  state.vertexBank = null;
716
797
  state.fragment.length = 0;
717
798
  state.samplers.length = 0;
718
799
  state.samplerMask = 0;
800
+ state.vertexVersions = new Float64Array(0);
801
+ state.fragmentVersions = new Float64Array(0);
719
802
  if (this.selected === program) {
720
803
  this.selected = null;
721
804
  }
@@ -852,6 +935,7 @@ export class WebGLContext3D implements Context3D {
852
935
  throw new Context3DError("textureAllocation", "Context3D experiment: cannot create texture");
853
936
  }
854
937
 
938
+ this.texturesApplied[0] = 0;
855
939
  gl.activeTexture(gl.TEXTURE0);
856
940
  const target = cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
857
941
  gl.bindTexture(target, handle);
@@ -881,22 +965,31 @@ export class WebGLContext3D implements Context3D {
881
965
  upload: (pixels, level = 0, face = 0, sourceWidth, sourceHeight) => {
882
966
  this.alive();
883
967
  this.textureState(texture);
884
- uploadTexture(
885
- gl,
886
- texture,
887
- state,
888
- pixels,
889
- level,
890
- face,
891
- sourceWidth,
892
- sourceHeight,
893
- this.dxtEncoder,
894
- );
968
+ try {
969
+ uploadTexture(
970
+ gl,
971
+ texture,
972
+ state,
973
+ pixels,
974
+ level,
975
+ face,
976
+ sourceWidth,
977
+ sourceHeight,
978
+ this.dxtEncoder,
979
+ );
980
+ } finally {
981
+ // An encoder can draw before the upload replaces unit zero's binding.
982
+ this.texturesApplied[0] = 0;
983
+ }
895
984
  },
896
985
  uploadBytes: (data, offset, level = 0, face = 0) => {
897
986
  this.alive();
898
987
  this.textureState(texture);
899
- uploadTextureBytes(gl, texture, state, data, offset, level, face, this.dxtEncoder);
988
+ try {
989
+ uploadTextureBytes(gl, texture, state, data, offset, level, face, this.dxtEncoder);
990
+ } finally {
991
+ this.texturesApplied[0] = 0;
992
+ }
900
993
  },
901
994
  uploadCompressed: (blocks, level = 0, face = 0) => {
902
995
  this.alive();
@@ -908,7 +1001,11 @@ export class WebGLContext3D implements Context3D {
908
1001
  );
909
1002
  }
910
1003
 
911
- uploadCompressedLevel(gl, texture, state, blocks, level, face);
1004
+ try {
1005
+ uploadCompressedLevel(gl, texture, state, blocks, level, face);
1006
+ } finally {
1007
+ this.texturesApplied[0] = 0;
1008
+ }
912
1009
  },
913
1010
  dispose: () => {
914
1011
  if (!this.textures.delete(texture)) {
@@ -916,12 +1013,13 @@ export class WebGLContext3D implements Context3D {
916
1013
  }
917
1014
 
918
1015
  this.textureCounts[kind]--;
1016
+ this.drawState.forget();
919
1017
  this.textureQuotaBytes[kind] -= quotaBytes;
920
1018
 
921
1019
  if (this.renderTexture === texture) {
922
1020
  this.renderTexture = null;
923
1021
  this.renderDepth = false;
924
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
1022
+ this.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
925
1023
  gl.frontFace(gl.CW);
926
1024
  this.applyScissor();
927
1025
  }
@@ -941,6 +1039,7 @@ export class WebGLContext3D implements Context3D {
941
1039
 
942
1040
  for (let i = 0; i < this.textureSlots.length; i++) {
943
1041
  if (this.textureSlots[i] === texture) {
1042
+ this.texturesApplied[i] = 0;
944
1043
  this.textureSlots[i] = null;
945
1044
  this.textureMask &= ~(1 << i);
946
1045
  gl.activeTexture(gl.TEXTURE0 + i);
@@ -999,7 +1098,7 @@ export class WebGLContext3D implements Context3D {
999
1098
 
1000
1099
  const gl = this.gl;
1001
1100
  const program = this.copyProgram(texture.cube);
1002
- gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
1101
+ this.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
1003
1102
  gl.viewport(0, 0, texture.width, texture.height);
1004
1103
  gl.disable(gl.DEPTH_TEST);
1005
1104
  gl.disable(gl.STENCIL_TEST);
@@ -1007,6 +1106,7 @@ export class WebGLContext3D implements Context3D {
1007
1106
  gl.disable(gl.CULL_FACE);
1008
1107
  gl.disable(gl.SCISSOR_TEST);
1009
1108
  gl.colorMask(true, true, true, true);
1109
+ this.texturesApplied[0] = 0;
1010
1110
  gl.activeTexture(gl.TEXTURE0);
1011
1111
  gl.bindTexture(texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D, state.handle);
1012
1112
  gl.bindSampler(0, this.sampler(0));
@@ -1025,7 +1125,7 @@ export class WebGLContext3D implements Context3D {
1025
1125
  if (this.blendEnabled) {
1026
1126
  gl.enable(gl.BLEND);
1027
1127
  }
1028
- if (this.cullEnabled) {
1128
+ if (this.cullFace !== "none") {
1029
1129
  gl.enable(gl.CULL_FACE);
1030
1130
  }
1031
1131
  if (this.scissor) {
@@ -1049,6 +1149,7 @@ export class WebGLContext3D implements Context3D {
1049
1149
  }
1050
1150
 
1051
1151
  private deleteMultisample(texture: Texture3D, target: MultisampleTarget): void {
1152
+ this.drawState.forget();
1052
1153
  deleteMultisample(this.gl, target);
1053
1154
 
1054
1155
  const bytes = texture.width * texture.height * 4 * target.samples;
@@ -1081,6 +1182,8 @@ export class WebGLContext3D implements Context3D {
1081
1182
 
1082
1183
  this.copyProgram(texture.cube);
1083
1184
  this.sampler(0);
1185
+ this.drawState.forget();
1186
+ this.texturesApplied[0] = 0;
1084
1187
  const target = allocateMultisample(
1085
1188
  this.gl,
1086
1189
  texture,
@@ -1157,7 +1260,7 @@ export class WebGLContext3D implements Context3D {
1157
1260
  );
1158
1261
  }
1159
1262
  }
1160
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
1263
+ this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
1161
1264
  gl.framebufferTexture2D(
1162
1265
  gl.FRAMEBUFFER,
1163
1266
  gl.COLOR_ATTACHMENT0,
@@ -1209,7 +1312,7 @@ export class WebGLContext3D implements Context3D {
1209
1312
  if (framebuffer !== state.framebuffer) {
1210
1313
  gl.deleteFramebuffer(framebuffer);
1211
1314
  } else if (framebuffer) {
1212
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
1315
+ this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
1213
1316
  gl.framebufferTexture2D(
1214
1317
  gl.FRAMEBUFFER,
1215
1318
  gl.COLOR_ATTACHMENT0,
@@ -1232,7 +1335,7 @@ export class WebGLContext3D implements Context3D {
1232
1335
  gl.deleteRenderbuffer(depthBuffer);
1233
1336
  }
1234
1337
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
1235
- gl.bindFramebuffer(
1338
+ this.bindFramebuffer(
1236
1339
  gl.FRAMEBUFFER,
1237
1340
  this.renderTexture
1238
1341
  ? targetFramebuffer(this.textureState(this.renderTexture))
@@ -1298,6 +1401,8 @@ export class WebGLContext3D implements Context3D {
1298
1401
  const state = this.textureState(texture);
1299
1402
  if (state.dirty) {
1300
1403
  const gl = this.gl;
1404
+ this.drawState.forget();
1405
+ this.texturesApplied[0] = 0;
1301
1406
  resolveTarget(this.gl, texture, state, this.renderFace, this.scissor);
1302
1407
  gl.activeTexture(gl.TEXTURE0);
1303
1408
  if (texture.cube) {
@@ -1306,7 +1411,7 @@ export class WebGLContext3D implements Context3D {
1306
1411
  gl.disable(gl.SCISSOR_TEST);
1307
1412
  const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.renderFace;
1308
1413
  // The base-size depth attachment cannot accompany smaller mip images.
1309
- gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
1414
+ this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
1310
1415
  if (state.depthAttached) {
1311
1416
  gl.framebufferRenderbuffer(
1312
1417
  gl.FRAMEBUFFER,
@@ -1316,7 +1421,7 @@ export class WebGLContext3D implements Context3D {
1316
1421
  );
1317
1422
  }
1318
1423
  for (let level = 1; level < texture.levels; level++) {
1319
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, state.framebuffer);
1424
+ this.bindFramebuffer(gl.READ_FRAMEBUFFER, state.framebuffer);
1320
1425
  gl.framebufferTexture2D(
1321
1426
  gl.READ_FRAMEBUFFER,
1322
1427
  gl.COLOR_ATTACHMENT0,
@@ -1324,7 +1429,7 @@ export class WebGLContext3D implements Context3D {
1324
1429
  state.handle,
1325
1430
  level - 1,
1326
1431
  );
1327
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, state.mipFramebuffer);
1432
+ this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, state.mipFramebuffer);
1328
1433
  gl.framebufferTexture2D(
1329
1434
  gl.DRAW_FRAMEBUFFER,
1330
1435
  gl.COLOR_ATTACHMENT0,
@@ -1346,7 +1451,7 @@ export class WebGLContext3D implements Context3D {
1346
1451
  gl.LINEAR,
1347
1452
  );
1348
1453
  }
1349
- gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
1454
+ this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
1350
1455
  gl.framebufferTexture2D(
1351
1456
  gl.FRAMEBUFFER,
1352
1457
  gl.COLOR_ATTACHMENT0,
@@ -1379,9 +1484,13 @@ export class WebGLContext3D implements Context3D {
1379
1484
  }
1380
1485
 
1381
1486
  // AIR accepts a disposed texture binding and reports 3663 when drawing.
1487
+ if (this.textureSlots[index] !== texture) {
1488
+ this.texturesApplied[index] = 0;
1489
+ }
1382
1490
  this.textureSlots[index] = texture;
1383
1491
  this.textureMask = (this.textureMask & ~(1 << index)) | (texture ? 1 << index : 0);
1384
1492
  if (!texture) {
1493
+ this.texturesApplied[index] = 0;
1385
1494
  this.gl.activeTexture(this.gl.TEXTURE0 + index);
1386
1495
  this.gl.bindTexture(this.gl.TEXTURE_2D, null);
1387
1496
  this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, null);
@@ -1399,7 +1508,14 @@ export class WebGLContext3D implements Context3D {
1399
1508
  throw new Context3DError("operation", "Context3D experiment: invalid sampler state");
1400
1509
  }
1401
1510
 
1402
- this.samplerStates[index] = w | (f << 2) | (m << 5);
1511
+ this.setSamplerState(index, w | (f << 2) | (m << 5));
1512
+ }
1513
+
1514
+ private setSamplerState(index: number, state: number): void {
1515
+ if (this.samplerStates[index] !== state) {
1516
+ this.samplerStates[index] = state;
1517
+ this.texturesApplied[index] = 0;
1518
+ }
1403
1519
  }
1404
1520
 
1405
1521
  private sampler(index: number): WebGLSampler {
@@ -1465,8 +1581,10 @@ export class WebGLContext3D implements Context3D {
1465
1581
  for (let i = 0; i < state.samplers.length; i++) {
1466
1582
  const source = state.samplers[i].source;
1467
1583
  if (!source.ignore) {
1468
- this.samplerStates[source.index] =
1469
- (source.repeat ? 3 : 0) | (source.filter << 2) | (source.mip << 5);
1584
+ this.setSamplerState(
1585
+ source.index,
1586
+ (source.repeat ? 3 : 0) | (source.filter << 2) | (source.mip << 5),
1587
+ );
1470
1588
  }
1471
1589
  }
1472
1590
  }
@@ -1494,6 +1612,15 @@ export class WebGLContext3D implements Context3D {
1494
1612
  const valid = offset >= 0 && offset <= buffer.stride - (packed ? 1 : size);
1495
1613
  const previous = this.vertices[index];
1496
1614
  if (previous) {
1615
+ if (
1616
+ previous.buffer !== buffer ||
1617
+ previous.offset !== offset ||
1618
+ previous.size !== size ||
1619
+ previous.packed !== packed
1620
+ ) {
1621
+ previous.applied = false;
1622
+ }
1623
+
1497
1624
  if (previous.buffer !== buffer) {
1498
1625
  previous.checkedIndex = null;
1499
1626
  }
@@ -1509,6 +1636,7 @@ export class WebGLContext3D implements Context3D {
1509
1636
  size,
1510
1637
  packed,
1511
1638
  valid,
1639
+ applied: false,
1512
1640
  checkedIndex: null,
1513
1641
  checkedIndexVersion: -1,
1514
1642
  checkedVertexVersion: -1,
@@ -1521,10 +1649,20 @@ export class WebGLContext3D implements Context3D {
1521
1649
  setConstants(stage: ProgramType, first: number, values: ArrayLike<number>, count: number): void {
1522
1650
  this.alive();
1523
1651
  const dest = stage === "vertex" ? this.vertexConstants : this.fragmentConstants;
1524
- range(first, count, dest.length / 4);
1652
+ range(first, count, dest.data.length / 4);
1525
1653
  range(0, count * 4, values.length);
1526
- for (let i = 0; i < count * 4; i++) {
1527
- dest[first * 4 + i] = values[i];
1654
+
1655
+ dest.writing++;
1656
+ try {
1657
+ for (let i = 0; i < count * 4; i++) {
1658
+ dest.data[first * 4 + i] = values[i];
1659
+ }
1660
+ } finally {
1661
+ // Source getters can draw, nest setters or throw after partially writing the bank.
1662
+ dest.writing--;
1663
+ if (count) {
1664
+ dest.versions.fill(++dest.version, first, first + count);
1665
+ }
1528
1666
  }
1529
1667
  }
1530
1668
 
@@ -1575,9 +1713,13 @@ export class WebGLContext3D implements Context3D {
1575
1713
  const maximum = validateIndices(index, first, count);
1576
1714
 
1577
1715
  const gl = this.gl;
1578
- gl.bindVertexArray(this.vao);
1716
+ this.drawState.bindVertexArray(this.vao);
1579
1717
  for (let i = 0; i < 8; i++) {
1580
- gl.disableVertexAttribArray(i);
1718
+ const bit = 1 << i;
1719
+ if (this.enabledAttributes & bit & ~state.attributeMask) {
1720
+ gl.disableVertexAttribArray(i);
1721
+ this.enabledAttributes &= ~bit;
1722
+ }
1581
1723
  }
1582
1724
 
1583
1725
  for (let i = 0; i < state.attributes.length; i++) {
@@ -1594,20 +1736,32 @@ export class WebGLContext3D implements Context3D {
1594
1736
 
1595
1737
  validateVertices(buffer, index, binding, first, count, maximum);
1596
1738
 
1597
- gl.bindBuffer(gl.ARRAY_BUFFER, buffer.handle);
1598
- gl.enableVertexAttribArray(slot);
1599
- gl.vertexAttribPointer(
1600
- slot,
1601
- binding.size,
1602
- binding.packed ? gl.UNSIGNED_BYTE : gl.FLOAT,
1603
- binding.packed,
1604
- binding.buffer.stride * 4,
1605
- binding.offset * 4,
1606
- );
1739
+ if (!binding.applied) {
1740
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer.handle);
1741
+ gl.vertexAttribPointer(
1742
+ slot,
1743
+ binding.size,
1744
+ binding.packed ? gl.UNSIGNED_BYTE : gl.FLOAT,
1745
+ binding.packed,
1746
+ binding.buffer.stride * 4,
1747
+ binding.offset * 4,
1748
+ );
1749
+ binding.applied = true;
1750
+ }
1751
+
1752
+ const bit = 1 << slot;
1753
+ if (!(this.enabledAttributes & bit)) {
1754
+ gl.enableVertexAttribArray(slot);
1755
+ this.enabledAttributes |= bit;
1756
+ }
1607
1757
  }
1608
1758
 
1609
- gl.useProgram(state.handle);
1610
- gl.uniform1f(state.targetY, this.renderTexture ? -1 : 1);
1759
+ this.drawState.useProgram(state.handle);
1760
+ const targetY = this.renderTexture ? -1 : 1;
1761
+ if (state.targetYValue !== targetY) {
1762
+ gl.uniform1f(state.targetY, targetY);
1763
+ state.targetYValue = targetY;
1764
+ }
1611
1765
  for (let i = 0; i < state.samplers.length; i++) {
1612
1766
  const binding = state.samplers[i];
1613
1767
  const slot = binding.source.index;
@@ -1654,27 +1808,45 @@ export class WebGLContext3D implements Context3D {
1654
1808
  throw new TextureBindingError("format", slot);
1655
1809
  }
1656
1810
 
1657
- gl.activeTexture(gl.TEXTURE0 + slot);
1658
- gl.bindTexture(texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D, resource.handle);
1659
- gl.bindSampler(slot, this.sampler(this.samplerStates[slot]));
1660
- gl.uniform1i(binding.location, slot);
1811
+ if (!this.texturesApplied[slot]) {
1812
+ gl.activeTexture(gl.TEXTURE0 + slot);
1813
+ gl.bindTexture(texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D, resource.handle);
1814
+ gl.bindSampler(slot, this.sampler(this.samplerStates[slot]));
1815
+ this.texturesApplied[slot] = 1;
1816
+ }
1817
+ if (!state.samplersSet) {
1818
+ gl.uniform1i(binding.location, slot);
1819
+ }
1661
1820
  }
1662
1821
 
1822
+ state.samplersSet = true;
1823
+
1663
1824
  if (state.vertexBank) {
1664
- gl.uniform4fv(state.vertexBank, this.vertexConstants);
1825
+ if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
1826
+ gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
1827
+ state.vertexVersion = this.vertexConstants.version;
1828
+ }
1665
1829
  } else {
1666
- this.uniforms(state.vertex, this.vertexConstants);
1830
+ this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions);
1831
+ }
1832
+ this.uniforms(state.fragment, this.fragmentConstants, state.fragmentVersions);
1833
+ if (this.indexBuffer !== index.handle) {
1834
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index.handle);
1835
+ this.indexBuffer = index.handle;
1667
1836
  }
1668
- this.uniforms(state.fragment, this.fragmentConstants);
1669
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index.handle);
1670
1837
  gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, first * 2);
1671
1838
  this.changedTarget();
1672
1839
  }
1673
1840
 
1674
- private uniforms(locations: (WebGLUniformLocation | null)[], data: Float32Array): void {
1841
+ private uniforms(
1842
+ locations: (WebGLUniformLocation | null)[],
1843
+ bank: Constants,
1844
+ previous: Float64Array,
1845
+ ): void {
1675
1846
  for (let i = 0; i < locations.length; i++) {
1676
- if (locations[i]) {
1677
- this.gl.uniform4fv(locations[i], data, i * 4, 4);
1847
+ if (locations[i] && (bank.writing || previous[i] !== bank.versions[i])) {
1848
+ this.gl.uniform4fv(locations[i], bank.data, i * 4, 4);
1849
+ previous[i] = bank.versions[i];
1678
1850
  }
1679
1851
  }
1680
1852
  }
@@ -1686,8 +1858,8 @@ export class WebGLContext3D implements Context3D {
1686
1858
 
1687
1859
  const gl = this.gl;
1688
1860
  if (this.resolveDirty) {
1689
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
1690
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
1861
+ this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
1862
+ this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
1691
1863
  if (this.scissor) {
1692
1864
  gl.disable(gl.SCISSOR_TEST);
1693
1865
  }
@@ -1708,7 +1880,7 @@ export class WebGLContext3D implements Context3D {
1708
1880
  }
1709
1881
  this.resolveDirty = false;
1710
1882
  }
1711
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
1883
+ this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
1712
1884
  }
1713
1885
 
1714
1886
  read(width = this.width, height = this.height): Uint8Array {
@@ -1733,7 +1905,7 @@ export class WebGLContext3D implements Context3D {
1733
1905
  this.target();
1734
1906
  this.resolve();
1735
1907
  const gl = this.gl;
1736
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
1908
+ this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
1737
1909
  if (this.scissor) {
1738
1910
  gl.disable(gl.SCISSOR_TEST);
1739
1911
  }
@@ -1780,7 +1952,7 @@ export class WebGLContext3D implements Context3D {
1780
1952
  this.gl.useProgram(null);
1781
1953
  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
1782
1954
  this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
1783
- this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
1955
+ this.bindFramebuffer(this.gl.FRAMEBUFFER, null);
1784
1956
  this.gl.bindTexture(this.gl.TEXTURE_2D, null);
1785
1957
  this.gl.deleteVertexArray(this.vao);
1786
1958
  this.gl.deleteFramebuffer(this.framebuffer);
@@ -1799,5 +1971,8 @@ export class WebGLContext3D implements Context3D {
1799
1971
  this.depthBuffer = null;
1800
1972
  this.pixels = new Uint8Array(0);
1801
1973
  this.topDown = new Uint8Array(0);
1974
+ this.texturesApplied.fill(0);
1975
+ this.indexBuffer = null;
1976
+ this.drawState.forget();
1802
1977
  }
1803
1978
  }