@swf2es/context3d 0.1.10 → 0.1.12

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
1
  import { SAMPLER_FILTERS, SAMPLER_MIPS, SAMPLER_WRAPS, ScissorBoundsError, TextureBindingError, VERTEX_FORMATS, VertexOffsetError, } from "../backend.js";
2
+ import { GlState } from "../device/state.js";
2
3
  import { blockBytes } from "../dxt.js";
3
4
  import { Context3DError } from "../errors.js";
4
5
  import { uploadBuffer, uploadBufferBytes, } from "./buffers.js";
@@ -17,6 +18,7 @@ const SAMPLER_FORMATS = {
17
18
  /** An owned WebGL2 context, deliberately independent of the 2D renderer. */
18
19
  export class WebGLContext3D {
19
20
  gl;
21
+ drawState;
20
22
  width = 0;
21
23
  height = 0;
22
24
  disposed = false;
@@ -48,6 +50,9 @@ export class WebGLContext3D {
48
50
  s3tc;
49
51
  maxAnisotropy = 1;
50
52
  vertices = new Array(8).fill(null);
53
+ enabledAttributes = 0;
54
+ indexBuffer = null;
55
+ texturesApplied = new Uint8Array(8);
51
56
  selected = null;
52
57
  framebuffer = null;
53
58
  texture = null;
@@ -57,8 +62,11 @@ export class WebGLContext3D {
57
62
  depthBuffer = null;
58
63
  antiAlias = 0;
59
64
  depthWrite = true;
65
+ depthCompare = "less";
60
66
  blendEnabled = false;
61
- cullEnabled = false;
67
+ blendSource = "one";
68
+ blendDestination = "zero";
69
+ cullFace = "none";
62
70
  colorWrite = 15;
63
71
  stencilFront = new StencilState();
64
72
  stencilBack = new StencilState();
@@ -72,8 +80,18 @@ export class WebGLContext3D {
72
80
  scissorRight = 0;
73
81
  scissorBottom = 0;
74
82
  scissorOutside = false;
75
- vertexConstants = new Float32Array(128 * 4);
76
- fragmentConstants = new Float32Array(28 * 4);
83
+ vertexConstants = {
84
+ data: new Float32Array(128 * 4),
85
+ versions: new Float64Array(128),
86
+ version: 0,
87
+ writing: 0,
88
+ };
89
+ fragmentConstants = {
90
+ data: new Float32Array(28 * 4),
91
+ versions: new Float64Array(28),
92
+ version: 0,
93
+ writing: 0,
94
+ };
77
95
  pixels = new Uint8Array(0);
78
96
  topDown = new Uint8Array(0);
79
97
  vao;
@@ -89,6 +107,7 @@ export class WebGLContext3D {
89
107
  if (gl.getContextAttributes()?.antialias) {
90
108
  throw new Context3DError("unsupported", "Context3D requires a WebGL2 context with antialias: false");
91
109
  }
110
+ this.drawState = new GlState(gl);
92
111
  const vao = gl.createVertexArray();
93
112
  if (!vao) {
94
113
  throw new Context3DError("operation", "Context3D experiment: cannot create vertex array");
@@ -130,8 +149,13 @@ export class WebGLContext3D {
130
149
  this.seedTarget(texture, state);
131
150
  }
132
151
  }
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);
152
+ this.drawState.bindFramebuffer(state ? targetFramebuffer(state) : this.framebuffer);
153
+ this.drawState.viewport(0, 0, texture?.width ?? this.width, texture?.height ?? this.height);
154
+ }
155
+ /** Temporary attachments and separate READ/DRAW bindings bypass the hot target cache. */
156
+ bindFramebuffer(target, framebuffer) {
157
+ this.drawState.forget();
158
+ this.gl.bindFramebuffer(target, framebuffer);
135
159
  }
136
160
  configure(width, height, depth = false, antiAlias = 0) {
137
161
  this.alive();
@@ -163,17 +187,19 @@ export class WebGLContext3D {
163
187
  (samples && (!resolveFramebuffer || !colorBuffer))) {
164
188
  throw new Context3DError("operation", "Context3D experiment: cannot allocate back buffer");
165
189
  }
190
+ this.texturesApplied[0] = 0;
191
+ gl.activeTexture(gl.TEXTURE0);
166
192
  gl.bindTexture(gl.TEXTURE_2D, texture);
167
193
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
168
194
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
169
195
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
170
- gl.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
196
+ this.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer ?? framebuffer);
171
197
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
172
198
  if (resolveFramebuffer) {
173
199
  if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
174
200
  throw new Context3DError("operation", "Context3D experiment: incomplete resolve buffer");
175
201
  }
176
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
202
+ this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
177
203
  gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
178
204
  gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, width, height);
179
205
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer);
@@ -200,7 +226,7 @@ export class WebGLContext3D {
200
226
  gl.deleteRenderbuffer(depthBuffer);
201
227
  gl.deleteRenderbuffer(colorBuffer);
202
228
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
203
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
229
+ this.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
204
230
  throw error;
205
231
  }
206
232
  gl.deleteTexture(this.texture);
@@ -337,9 +363,14 @@ export class WebGLContext3D {
337
363
  }
338
364
  setDepth(write, compare) {
339
365
  this.alive();
340
- this.depthWrite = write;
341
- this.gl.depthMask(write);
342
- this.gl.depthFunc(this.gl[DEPTH_FUNCTIONS[compare]]);
366
+ if (this.depthWrite !== write) {
367
+ this.depthWrite = write;
368
+ this.gl.depthMask(write);
369
+ }
370
+ if (this.depthCompare !== compare) {
371
+ this.depthCompare = compare;
372
+ this.gl.depthFunc(this.gl[DEPTH_FUNCTIONS[compare]]);
373
+ }
343
374
  }
344
375
  setScissor(x, y = 0, width = 0, height = 0) {
345
376
  this.alive();
@@ -380,25 +411,39 @@ export class WebGLContext3D {
380
411
  }
381
412
  setBlend(source, destination) {
382
413
  this.alive();
414
+ if (this.blendSource === source && this.blendDestination === destination) {
415
+ return;
416
+ }
383
417
  const gl = this.gl;
418
+ const enabled = this.blendEnabled;
419
+ this.blendSource = source;
420
+ this.blendDestination = destination;
384
421
  this.blendEnabled = source !== "one" || destination !== "zero";
385
- if (source === "one" && destination === "zero") {
422
+ if (!this.blendEnabled) {
386
423
  gl.disable(gl.BLEND);
387
424
  }
388
425
  else {
389
426
  gl.blendFunc(gl[BLEND_FUNCTIONS[source]], gl[BLEND_FUNCTIONS[destination]]);
390
- gl.enable(gl.BLEND);
427
+ if (!enabled) {
428
+ gl.enable(gl.BLEND);
429
+ }
391
430
  }
392
431
  }
393
432
  setCulling(face) {
394
433
  this.alive();
434
+ const previous = this.cullFace;
435
+ if (previous === face) {
436
+ return;
437
+ }
395
438
  const gl = this.gl;
396
- this.cullEnabled = face !== "none";
439
+ this.cullFace = face;
397
440
  if (face === "none") {
398
441
  gl.disable(gl.CULL_FACE);
399
442
  return;
400
443
  }
401
- gl.enable(gl.CULL_FACE);
444
+ if (previous === "none") {
445
+ gl.enable(gl.CULL_FACE);
446
+ }
402
447
  switch (face) {
403
448
  case "back":
404
449
  gl.cullFace(gl.BACK);
@@ -432,6 +477,9 @@ export class WebGLContext3D {
432
477
  throw new Context3DError("bufferAllocation", "Context3D experiment: cannot allocate buffer");
433
478
  }
434
479
  const target = stride === 0 ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
480
+ if (stride === 0) {
481
+ this.indexBuffer = null;
482
+ }
435
483
  gl.bindVertexArray(this.vao);
436
484
  gl.bindBuffer(target, handle);
437
485
  gl.bufferData(target, bytes, gl.STATIC_DRAW);
@@ -452,17 +500,35 @@ export class WebGLContext3D {
452
500
  upload: (values, start, amount) => {
453
501
  this.alive();
454
502
  this.buffer(buffer);
455
- uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
503
+ try {
504
+ uploadBuffer(gl, this.vao, buffer, state, values, start, amount);
505
+ }
506
+ finally {
507
+ // Source getters can draw before the upload replaces the VAO's index binding.
508
+ if (stride === 0) {
509
+ this.indexBuffer = null;
510
+ }
511
+ }
456
512
  },
457
513
  uploadBytes: (data, offset, start, amount) => {
458
514
  this.alive();
459
515
  this.buffer(buffer);
460
- uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
516
+ try {
517
+ uploadBufferBytes(gl, this.vao, buffer, state, data, offset, start, amount);
518
+ }
519
+ finally {
520
+ if (stride === 0) {
521
+ this.indexBuffer = null;
522
+ }
523
+ }
461
524
  },
462
525
  dispose: () => {
463
526
  if (!this.buffers.delete(buffer)) {
464
527
  return;
465
528
  }
529
+ if (this.indexBuffer === handle) {
530
+ this.indexBuffer = null;
531
+ }
466
532
  gl.deleteBuffer(handle);
467
533
  this.bufferBytes -= bytes;
468
534
  if (stride !== 0) {
@@ -505,17 +571,24 @@ export class WebGLContext3D {
505
571
  const state = {
506
572
  handle: null,
507
573
  targetY: null,
574
+ targetYValue: Number.NaN,
508
575
  attributes: [],
576
+ attributeMask: 0,
509
577
  vertex: [],
510
578
  vertexBank: null,
511
579
  fragment: [],
512
580
  samplers: [],
513
581
  samplerMask: 0,
582
+ samplersSet: false,
583
+ vertexVersions: new Float64Array(0),
584
+ fragmentVersions: new Float64Array(0),
585
+ vertexVersion: -1,
514
586
  };
515
587
  const program = {
516
588
  upload: (vertex, fragment) => {
517
589
  this.alive();
518
590
  this.program(program);
591
+ this.drawState.forget();
519
592
  uploadProgram(this.gl, state, vertex, fragment);
520
593
  if (this.selected === program) {
521
594
  this.setProgram(program);
@@ -526,6 +599,7 @@ export class WebGLContext3D {
526
599
  return;
527
600
  }
528
601
  // WebGL keeps a deleted program alive while it is still current.
602
+ this.drawState.forget();
529
603
  if (this.gl.getParameter(this.gl.CURRENT_PROGRAM) === state.handle) {
530
604
  this.gl.useProgram(null);
531
605
  }
@@ -533,11 +607,14 @@ export class WebGLContext3D {
533
607
  state.handle = null;
534
608
  state.targetY = null;
535
609
  state.attributes.length = 0;
610
+ state.attributeMask = 0;
536
611
  state.vertex.length = 0;
537
612
  state.vertexBank = null;
538
613
  state.fragment.length = 0;
539
614
  state.samplers.length = 0;
540
615
  state.samplerMask = 0;
616
+ state.vertexVersions = new Float64Array(0);
617
+ state.fragmentVersions = new Float64Array(0);
541
618
  if (this.selected === program) {
542
619
  this.selected = null;
543
620
  }
@@ -635,6 +712,7 @@ export class WebGLContext3D {
635
712
  if (!handle) {
636
713
  throw new Context3DError("textureAllocation", "Context3D experiment: cannot create texture");
637
714
  }
715
+ this.texturesApplied[0] = 0;
638
716
  gl.activeTexture(gl.TEXTURE0);
639
717
  const target = cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
640
718
  gl.bindTexture(target, handle);
@@ -664,12 +742,23 @@ export class WebGLContext3D {
664
742
  upload: (pixels, level = 0, face = 0, sourceWidth, sourceHeight) => {
665
743
  this.alive();
666
744
  this.textureState(texture);
667
- uploadTexture(gl, texture, state, pixels, level, face, sourceWidth, sourceHeight, this.dxtEncoder);
745
+ try {
746
+ uploadTexture(gl, texture, state, pixels, level, face, sourceWidth, sourceHeight, this.dxtEncoder);
747
+ }
748
+ finally {
749
+ // An encoder can draw before the upload replaces unit zero's binding.
750
+ this.texturesApplied[0] = 0;
751
+ }
668
752
  },
669
753
  uploadBytes: (data, offset, level = 0, face = 0) => {
670
754
  this.alive();
671
755
  this.textureState(texture);
672
- uploadTextureBytes(gl, texture, state, data, offset, level, face, this.dxtEncoder);
756
+ try {
757
+ uploadTextureBytes(gl, texture, state, data, offset, level, face, this.dxtEncoder);
758
+ }
759
+ finally {
760
+ this.texturesApplied[0] = 0;
761
+ }
673
762
  },
674
763
  uploadCompressed: (blocks, level = 0, face = 0) => {
675
764
  this.alive();
@@ -677,18 +766,24 @@ export class WebGLContext3D {
677
766
  if (format === "bgra") {
678
767
  throw new Context3DError("textureUpload", "Context3D experiment: BGRA texture given blocks");
679
768
  }
680
- uploadCompressedLevel(gl, texture, state, blocks, level, face);
769
+ try {
770
+ uploadCompressedLevel(gl, texture, state, blocks, level, face);
771
+ }
772
+ finally {
773
+ this.texturesApplied[0] = 0;
774
+ }
681
775
  },
682
776
  dispose: () => {
683
777
  if (!this.textures.delete(texture)) {
684
778
  return;
685
779
  }
686
780
  this.textureCounts[kind]--;
781
+ this.drawState.forget();
687
782
  this.textureQuotaBytes[kind] -= quotaBytes;
688
783
  if (this.renderTexture === texture) {
689
784
  this.renderTexture = null;
690
785
  this.renderDepth = false;
691
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
786
+ this.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
692
787
  gl.frontFace(gl.CW);
693
788
  this.applyScissor();
694
789
  }
@@ -707,6 +802,7 @@ export class WebGLContext3D {
707
802
  state.depthBuffer = null;
708
803
  for (let i = 0; i < this.textureSlots.length; i++) {
709
804
  if (this.textureSlots[i] === texture) {
805
+ this.texturesApplied[i] = 0;
710
806
  this.textureSlots[i] = null;
711
807
  this.textureMask &= ~(1 << i);
712
808
  gl.activeTexture(gl.TEXTURE0 + i);
@@ -754,7 +850,7 @@ export class WebGLContext3D {
754
850
  }
755
851
  const gl = this.gl;
756
852
  const program = this.copyProgram(texture.cube);
757
- gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
853
+ this.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
758
854
  gl.viewport(0, 0, texture.width, texture.height);
759
855
  gl.disable(gl.DEPTH_TEST);
760
856
  gl.disable(gl.STENCIL_TEST);
@@ -762,6 +858,7 @@ export class WebGLContext3D {
762
858
  gl.disable(gl.CULL_FACE);
763
859
  gl.disable(gl.SCISSOR_TEST);
764
860
  gl.colorMask(true, true, true, true);
861
+ this.texturesApplied[0] = 0;
765
862
  gl.activeTexture(gl.TEXTURE0);
766
863
  gl.bindTexture(texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D, state.handle);
767
864
  gl.bindSampler(0, this.sampler(0));
@@ -780,7 +877,7 @@ export class WebGLContext3D {
780
877
  if (this.blendEnabled) {
781
878
  gl.enable(gl.BLEND);
782
879
  }
783
- if (this.cullEnabled) {
880
+ if (this.cullFace !== "none") {
784
881
  gl.enable(gl.CULL_FACE);
785
882
  }
786
883
  if (this.scissor) {
@@ -800,6 +897,7 @@ export class WebGLContext3D {
800
897
  return this.targetSamples[key];
801
898
  }
802
899
  deleteMultisample(texture, target) {
900
+ this.drawState.forget();
803
901
  deleteMultisample(this.gl, target);
804
902
  const bytes = texture.width * texture.height * 4 * target.samples;
805
903
  this.targetColorBytes -= bytes + (target.texture ? texture.width * texture.height * 4 : 0);
@@ -822,6 +920,8 @@ export class WebGLContext3D {
822
920
  }
823
921
  this.copyProgram(texture.cube);
824
922
  this.sampler(0);
923
+ this.drawState.forget();
924
+ this.texturesApplied[0] = 0;
825
925
  const target = allocateMultisample(this.gl, texture, old, samples, depth, this.depthWrite, this.stencilWrite);
826
926
  if (same) {
827
927
  if (newDepth) {
@@ -877,7 +977,7 @@ export class WebGLContext3D {
877
977
  throw new Context3DError("operation", "Context3D experiment: cannot create cube mip framebuffer");
878
978
  }
879
979
  }
880
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
980
+ this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
881
981
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + face : gl.TEXTURE_2D, state.handle, 0);
882
982
  if (singleDepth && !depthBuffer) {
883
983
  if (this.targetDepthBytes + texture.width * texture.height * 4 >
@@ -905,7 +1005,7 @@ export class WebGLContext3D {
905
1005
  gl.deleteFramebuffer(framebuffer);
906
1006
  }
907
1007
  else if (framebuffer) {
908
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
1008
+ this.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
909
1009
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture.cube ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + state.face : gl.TEXTURE_2D, state.handle, 0);
910
1010
  // Restore the attachment if this was the currently selected target.
911
1011
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, state.depthAttached ? state.depthBuffer : null);
@@ -917,7 +1017,7 @@ export class WebGLContext3D {
917
1017
  gl.deleteRenderbuffer(depthBuffer);
918
1018
  }
919
1019
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
920
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.renderTexture
1020
+ this.bindFramebuffer(gl.FRAMEBUFFER, this.renderTexture
921
1021
  ? targetFramebuffer(this.textureState(this.renderTexture))
922
1022
  : this.framebuffer);
923
1023
  this.applyScissor();
@@ -978,6 +1078,8 @@ export class WebGLContext3D {
978
1078
  const state = this.textureState(texture);
979
1079
  if (state.dirty) {
980
1080
  const gl = this.gl;
1081
+ this.drawState.forget();
1082
+ this.texturesApplied[0] = 0;
981
1083
  resolveTarget(this.gl, texture, state, this.renderFace, this.scissor);
982
1084
  gl.activeTexture(gl.TEXTURE0);
983
1085
  if (texture.cube) {
@@ -986,19 +1088,19 @@ export class WebGLContext3D {
986
1088
  gl.disable(gl.SCISSOR_TEST);
987
1089
  const face = gl.TEXTURE_CUBE_MAP_POSITIVE_X + this.renderFace;
988
1090
  // The base-size depth attachment cannot accompany smaller mip images.
989
- gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
1091
+ this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
990
1092
  if (state.depthAttached) {
991
1093
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, null);
992
1094
  }
993
1095
  for (let level = 1; level < texture.levels; level++) {
994
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, state.framebuffer);
1096
+ this.bindFramebuffer(gl.READ_FRAMEBUFFER, state.framebuffer);
995
1097
  gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, face, state.handle, level - 1);
996
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, state.mipFramebuffer);
1098
+ this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, state.mipFramebuffer);
997
1099
  gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, face, state.handle, level);
998
1100
  const size = texture.width >> level;
999
1101
  gl.blitFramebuffer(0, 0, size * 2, size * 2, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.LINEAR);
1000
1102
  }
1001
- gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
1103
+ this.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
1002
1104
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + state.face, state.handle, 0);
1003
1105
  if (state.depthAttached) {
1004
1106
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, state.depthBuffer);
@@ -1019,9 +1121,13 @@ export class WebGLContext3D {
1019
1121
  throw new Context3DError("disposed", "Context3D experiment: texture from another context");
1020
1122
  }
1021
1123
  // AIR accepts a disposed texture binding and reports 3663 when drawing.
1124
+ if (this.textureSlots[index] !== texture) {
1125
+ this.texturesApplied[index] = 0;
1126
+ }
1022
1127
  this.textureSlots[index] = texture;
1023
1128
  this.textureMask = (this.textureMask & ~(1 << index)) | (texture ? 1 << index : 0);
1024
1129
  if (!texture) {
1130
+ this.texturesApplied[index] = 0;
1025
1131
  this.gl.activeTexture(this.gl.TEXTURE0 + index);
1026
1132
  this.gl.bindTexture(this.gl.TEXTURE_2D, null);
1027
1133
  this.gl.bindTexture(this.gl.TEXTURE_CUBE_MAP, null);
@@ -1037,7 +1143,13 @@ export class WebGLContext3D {
1037
1143
  if (w < 0 || f < 0 || m < 0) {
1038
1144
  throw new Context3DError("operation", "Context3D experiment: invalid sampler state");
1039
1145
  }
1040
- this.samplerStates[index] = w | (f << 2) | (m << 5);
1146
+ this.setSamplerState(index, w | (f << 2) | (m << 5));
1147
+ }
1148
+ setSamplerState(index, state) {
1149
+ if (this.samplerStates[index] !== state) {
1150
+ this.samplerStates[index] = state;
1151
+ this.texturesApplied[index] = 0;
1152
+ }
1041
1153
  }
1042
1154
  sampler(index) {
1043
1155
  let sampler = this.samplers[index];
@@ -1089,8 +1201,7 @@ export class WebGLContext3D {
1089
1201
  for (let i = 0; i < state.samplers.length; i++) {
1090
1202
  const source = state.samplers[i].source;
1091
1203
  if (!source.ignore) {
1092
- this.samplerStates[source.index] =
1093
- (source.repeat ? 3 : 0) | (source.filter << 2) | (source.mip << 5);
1204
+ this.setSamplerState(source.index, (source.repeat ? 3 : 0) | (source.filter << 2) | (source.mip << 5));
1094
1205
  }
1095
1206
  }
1096
1207
  }
@@ -1112,6 +1223,12 @@ export class WebGLContext3D {
1112
1223
  const valid = offset >= 0 && offset <= buffer.stride - (packed ? 1 : size);
1113
1224
  const previous = this.vertices[index];
1114
1225
  if (previous) {
1226
+ if (previous.buffer !== buffer ||
1227
+ previous.offset !== offset ||
1228
+ previous.size !== size ||
1229
+ previous.packed !== packed) {
1230
+ previous.applied = false;
1231
+ }
1115
1232
  if (previous.buffer !== buffer) {
1116
1233
  previous.checkedIndex = null;
1117
1234
  }
@@ -1128,6 +1245,7 @@ export class WebGLContext3D {
1128
1245
  size,
1129
1246
  packed,
1130
1247
  valid,
1248
+ applied: false,
1131
1249
  checkedIndex: null,
1132
1250
  checkedIndexVersion: -1,
1133
1251
  checkedVertexVersion: -1,
@@ -1139,10 +1257,20 @@ export class WebGLContext3D {
1139
1257
  setConstants(stage, first, values, count) {
1140
1258
  this.alive();
1141
1259
  const dest = stage === "vertex" ? this.vertexConstants : this.fragmentConstants;
1142
- range(first, count, dest.length / 4);
1260
+ range(first, count, dest.data.length / 4);
1143
1261
  range(0, count * 4, values.length);
1144
- for (let i = 0; i < count * 4; i++) {
1145
- dest[first * 4 + i] = values[i];
1262
+ dest.writing++;
1263
+ try {
1264
+ for (let i = 0; i < count * 4; i++) {
1265
+ dest.data[first * 4 + i] = values[i];
1266
+ }
1267
+ }
1268
+ finally {
1269
+ // Source getters can draw, nest setters or throw after partially writing the bank.
1270
+ dest.writing--;
1271
+ if (count) {
1272
+ dest.versions.fill(++dest.version, first, first + count);
1273
+ }
1146
1274
  }
1147
1275
  }
1148
1276
  draw(indices, first, triangles) {
@@ -1184,9 +1312,13 @@ export class WebGLContext3D {
1184
1312
  }
1185
1313
  const maximum = validateIndices(index, first, count);
1186
1314
  const gl = this.gl;
1187
- gl.bindVertexArray(this.vao);
1315
+ this.drawState.bindVertexArray(this.vao);
1188
1316
  for (let i = 0; i < 8; i++) {
1189
- gl.disableVertexAttribArray(i);
1317
+ const bit = 1 << i;
1318
+ if (this.enabledAttributes & bit & ~state.attributeMask) {
1319
+ gl.disableVertexAttribArray(i);
1320
+ this.enabledAttributes &= ~bit;
1321
+ }
1190
1322
  }
1191
1323
  for (let i = 0; i < state.attributes.length; i++) {
1192
1324
  const slot = state.attributes[i];
@@ -1199,12 +1331,23 @@ export class WebGLContext3D {
1199
1331
  throw new VertexOffsetError("Vertex attribute exceeds its stride");
1200
1332
  }
1201
1333
  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);
1334
+ if (!binding.applied) {
1335
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer.handle);
1336
+ gl.vertexAttribPointer(slot, binding.size, binding.packed ? gl.UNSIGNED_BYTE : gl.FLOAT, binding.packed, binding.buffer.stride * 4, binding.offset * 4);
1337
+ binding.applied = true;
1338
+ }
1339
+ const bit = 1 << slot;
1340
+ if (!(this.enabledAttributes & bit)) {
1341
+ gl.enableVertexAttribArray(slot);
1342
+ this.enabledAttributes |= bit;
1343
+ }
1344
+ }
1345
+ this.drawState.useProgram(state.handle);
1346
+ const targetY = this.renderTexture ? -1 : 1;
1347
+ if (state.targetYValue !== targetY) {
1348
+ gl.uniform1f(state.targetY, targetY);
1349
+ state.targetYValue = targetY;
1205
1350
  }
1206
- gl.useProgram(state.handle);
1207
- gl.uniform1f(state.targetY, this.renderTexture ? -1 : 1);
1208
1351
  for (let i = 0; i < state.samplers.length; i++) {
1209
1352
  const binding = state.samplers[i];
1210
1353
  const slot = binding.source.index;
@@ -1248,26 +1391,39 @@ export class WebGLContext3D {
1248
1391
  if (this.samplerFormats && binding.source.format !== SAMPLER_FORMATS[texture.format]) {
1249
1392
  throw new TextureBindingError("format", slot);
1250
1393
  }
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);
1394
+ if (!this.texturesApplied[slot]) {
1395
+ gl.activeTexture(gl.TEXTURE0 + slot);
1396
+ gl.bindTexture(texture.cube ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D, resource.handle);
1397
+ gl.bindSampler(slot, this.sampler(this.samplerStates[slot]));
1398
+ this.texturesApplied[slot] = 1;
1399
+ }
1400
+ if (!state.samplersSet) {
1401
+ gl.uniform1i(binding.location, slot);
1402
+ }
1255
1403
  }
1404
+ state.samplersSet = true;
1256
1405
  if (state.vertexBank) {
1257
- gl.uniform4fv(state.vertexBank, this.vertexConstants);
1406
+ if (this.vertexConstants.writing || state.vertexVersion !== this.vertexConstants.version) {
1407
+ gl.uniform4fv(state.vertexBank, this.vertexConstants.data);
1408
+ state.vertexVersion = this.vertexConstants.version;
1409
+ }
1258
1410
  }
1259
1411
  else {
1260
- this.uniforms(state.vertex, this.vertexConstants);
1412
+ this.uniforms(state.vertex, this.vertexConstants, state.vertexVersions);
1413
+ }
1414
+ this.uniforms(state.fragment, this.fragmentConstants, state.fragmentVersions);
1415
+ if (this.indexBuffer !== index.handle) {
1416
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index.handle);
1417
+ this.indexBuffer = index.handle;
1261
1418
  }
1262
- this.uniforms(state.fragment, this.fragmentConstants);
1263
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index.handle);
1264
1419
  gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, first * 2);
1265
1420
  this.changedTarget();
1266
1421
  }
1267
- uniforms(locations, data) {
1422
+ uniforms(locations, bank, previous) {
1268
1423
  for (let i = 0; i < locations.length; i++) {
1269
- if (locations[i]) {
1270
- this.gl.uniform4fv(locations[i], data, i * 4, 4);
1424
+ if (locations[i] && (bank.writing || previous[i] !== bank.versions[i])) {
1425
+ this.gl.uniform4fv(locations[i], bank.data, i * 4, 4);
1426
+ previous[i] = bank.versions[i];
1271
1427
  }
1272
1428
  }
1273
1429
  }
@@ -1277,8 +1433,8 @@ export class WebGLContext3D {
1277
1433
  }
1278
1434
  const gl = this.gl;
1279
1435
  if (this.resolveDirty) {
1280
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
1281
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
1436
+ this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.framebuffer);
1437
+ this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
1282
1438
  if (this.scissor) {
1283
1439
  gl.disable(gl.SCISSOR_TEST);
1284
1440
  }
@@ -1288,7 +1444,7 @@ export class WebGLContext3D {
1288
1444
  }
1289
1445
  this.resolveDirty = false;
1290
1446
  }
1291
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
1447
+ this.bindFramebuffer(gl.READ_FRAMEBUFFER, this.resolveFramebuffer);
1292
1448
  }
1293
1449
  read(width = this.width, height = this.height) {
1294
1450
  width = Math.max(0, Math.min(this.width, Math.floor(width) || 0));
@@ -1310,7 +1466,7 @@ export class WebGLContext3D {
1310
1466
  this.target();
1311
1467
  this.resolve();
1312
1468
  const gl = this.gl;
1313
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
1469
+ this.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
1314
1470
  if (this.scissor) {
1315
1471
  gl.disable(gl.SCISSOR_TEST);
1316
1472
  }
@@ -1344,7 +1500,7 @@ export class WebGLContext3D {
1344
1500
  this.gl.useProgram(null);
1345
1501
  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
1346
1502
  this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
1347
- this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
1503
+ this.bindFramebuffer(this.gl.FRAMEBUFFER, null);
1348
1504
  this.gl.bindTexture(this.gl.TEXTURE_2D, null);
1349
1505
  this.gl.deleteVertexArray(this.vao);
1350
1506
  this.gl.deleteFramebuffer(this.framebuffer);
@@ -1363,6 +1519,9 @@ export class WebGLContext3D {
1363
1519
  this.depthBuffer = null;
1364
1520
  this.pixels = new Uint8Array(0);
1365
1521
  this.topDown = new Uint8Array(0);
1522
+ this.texturesApplied.fill(0);
1523
+ this.indexBuffer = null;
1524
+ this.drawState.forget();
1366
1525
  }
1367
1526
  }
1368
1527
  //# sourceMappingURL=context.js.map