figureone 1.8.0 → 1.9.1

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/index.js CHANGED
@@ -7174,6 +7174,11 @@ var GLObject = /*#__PURE__*/function (_DrawingObject) {
7174
7174
  // is precomputed so the draw loop allocates nothing). A mask with an empty
7175
7175
  // src is a transparent placeholder - an index-preserving no-op slot.
7176
7176
 
7177
+ // The webgl texture id this object currently holds a base-texture reference to
7178
+ // (acquired by initTexture, or adopted from a shared atlas via
7179
+ // setBaseTextureRef). resetTextureBuffer releases exactly this id, so the
7180
+ // release can never be unbalanced with the acquire. null = no reference held.
7181
+
7177
7182
  function GLObject(webgl) {
7178
7183
  var _this;
7179
7184
  var vertexShader = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
@@ -7203,6 +7208,7 @@ var GLObject = /*#__PURE__*/function (_DrawingObject) {
7203
7208
  _this.numVertices = 0;
7204
7209
  _this.uniforms = {};
7205
7210
  _this.texture = null;
7211
+ _this.acquiredBaseTextureId = null;
7206
7212
  _this.maskTextures = [];
7207
7213
  // this.selectorProgramIndex = this.webgl.getProgram(selectorVertexShader, selectorFragShader);
7208
7214
  _this.initProgram();
@@ -7463,6 +7469,10 @@ var GLObject = /*#__PURE__*/function (_DrawingObject) {
7463
7469
  // gl.STATIC_DRAW,
7464
7470
  // );
7465
7471
  webgl.addTexture(id, data || src, loadColor, repeat, this.executeOnLoad.bind(this), force);
7472
+ // addTexture took/holds a reference to this id; record it so
7473
+ // resetTextureBuffer releases exactly what was acquired. (For a forced
7474
+ // update the id is unchanged, so this is idempotent.)
7475
+ this.acquiredBaseTextureId = id;
7466
7476
  this.state = webgl.textures[texture.id].state;
7467
7477
 
7468
7478
  // Register the optional mask textures (textureMap color mode). Each loads
@@ -7527,6 +7537,30 @@ var GLObject = /*#__PURE__*/function (_DrawingObject) {
7527
7537
  // }
7528
7538
  }
7529
7539
 
7540
+ /**
7541
+ * Adopt a base-texture reference to an already-registered texture (e.g. a
7542
+ * shared font atlas uploaded by the Atlas, whose id this object renders with
7543
+ * but does not upload itself).
7544
+ *
7545
+ * Releases any previously-held base reference first (so a font/atlas change
7546
+ * rebalances correctly) and is idempotent when the id is unchanged. If the
7547
+ * texture isn't registered yet, no reference is taken and acquiredBaseTextureId
7548
+ * is cleared, so the later resetTextureBuffer release stays balanced (it only
7549
+ * releases a reference that was actually acquired).
7550
+ */
7551
+ }, {
7552
+ key: "setBaseTextureRef",
7553
+ value: function setBaseTextureRef(id) {
7554
+ if (this.acquiredBaseTextureId === id) {
7555
+ return;
7556
+ }
7557
+ var webgl = this.webgl;
7558
+ if (this.acquiredBaseTextureId != null) {
7559
+ webgl.deleteTexture(this.acquiredBaseTextureId);
7560
+ }
7561
+ this.acquiredBaseTextureId = webgl.acquireTexture(id) ? id : null;
7562
+ }
7563
+
7530
7564
  // A texture map is a texture coords point that lines up with the texture
7531
7565
  // vertex point. So, if the vertex shape is rectangular, centered at the
7532
7566
  // origin and wants to incorporate the entire texture, then the map would
@@ -7707,14 +7741,15 @@ var GLObject = /*#__PURE__*/function (_DrawingObject) {
7707
7741
  maskTextures = this.maskTextures,
7708
7742
  webgl = this.webgl,
7709
7743
  gl = this.gl;
7710
- if (texture) {
7711
- if (deleteTexture && webgl.textures[texture.id] != null) {
7712
- webgl.deleteTexture(texture.id);
7713
- }
7714
- if (texture.buffer != null) {
7715
- gl.deleteBuffer(texture.buffer);
7716
- texture.buffer = null;
7717
- }
7744
+ if (deleteTexture && this.acquiredBaseTextureId != null) {
7745
+ // Release exactly the base reference this object acquired, so a failed
7746
+ // acquire (e.g. an atlas not registered yet) can never over-release.
7747
+ webgl.deleteTexture(this.acquiredBaseTextureId);
7748
+ this.acquiredBaseTextureId = null;
7749
+ }
7750
+ if (texture && texture.buffer != null) {
7751
+ gl.deleteBuffer(texture.buffer);
7752
+ texture.buffer = null;
7718
7753
  }
7719
7754
  if (deleteTexture) {
7720
7755
  maskTextures.forEach(function (maskTexture) {
@@ -7994,6 +8029,9 @@ var GLObject = /*#__PURE__*/function (_DrawingObject) {
7994
8029
  gl.uniform1f(locations.u_z, this.z);
7995
8030
  gl.uniform4f(locations.u_color, color[0], color[1], color[2], color[3]);
7996
8031
  var texture = this.texture;
8032
+ // Set when a textured object's base texture can't be bound, so we skip the
8033
+ // draw rather than sampling a stale/wrong texture (see below).
8034
+ var skipDraw = false;
7997
8035
  if (texture != null && targetTexture === false) {
7998
8036
  // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
7999
8037
  var texSize = 2; // 2 components per iteration
@@ -8007,27 +8045,49 @@ var GLObject = /*#__PURE__*/function (_DrawingObject) {
8007
8045
  gl.enableVertexAttribArray(locations.a_texcoord);
8008
8046
  gl.bindBuffer(gl.ARRAY_BUFFER, texture.buffer);
8009
8047
  gl.vertexAttribPointer(locations.a_texcoord, texSize, texType, texNormalize, texStride, texOffset);
8010
- gl.uniform1i(locations.u_use_texture, 1);
8011
- var index = webglInstance.textures[texture.id].index;
8012
- gl.uniform1i(locations.u_texture, index);
8013
-
8014
- // Bind the mask textures (textureMap color mode), each to its own texture
8015
- // unit and precomputed u_mask{i} sampler. They reuse the a_texcoord /
8016
- // v_texcoord bound above, so no extra attribute is needed. Uses an indexed
8017
- // loop with precomputed uniform names to avoid per-frame allocation.
8018
- var maskTextures = this.maskTextures;
8019
- for (var i = 0; i < maskTextures.length; i += 1) {
8020
- var maskTexture = maskTextures[i];
8021
- var maskLocation = locations[maskTexture.uniformName];
8022
- var maskRegistered = webglInstance.textures[maskTexture.id];
8023
- if (maskLocation != null && maskRegistered != null) {
8024
- gl.uniform1i(maskLocation, maskRegistered.index);
8048
+ // Assign content texture units for this draw from the shared pool. Unit 0
8049
+ // is reserved for the target/selector framebuffer texture, so content
8050
+ // starts at unit 1: the base texture, then each mask texture in turn.
8051
+ // bindTextureToUnit only issues a bindTexture when the unit isn't already
8052
+ // pointing at that texture, so runs of draws sharing a texture (e.g. text
8053
+ // sharing a font atlas) issue no bind calls.
8054
+ var textureUnit = 1;
8055
+ if (webglInstance.bindTextureToUnit(texture.id, textureUnit)) {
8056
+ gl.uniform1i(locations.u_use_texture, 1);
8057
+ gl.uniform1i(locations.u_texture, textureUnit);
8058
+ textureUnit += 1;
8059
+
8060
+ // Bind the mask textures (textureMap color mode), each to its own
8061
+ // texture unit and precomputed u_mask{i} sampler. They reuse the
8062
+ // a_texcoord / v_texcoord bound above, so no extra attribute is needed.
8063
+ // Only assign the sampler if the bind succeeds — otherwise the uniform
8064
+ // would default to sampler 0 (the reserved target texture) and recolor
8065
+ // with garbage. Uses an indexed loop with precomputed uniform names to
8066
+ // avoid per-frame allocation.
8067
+ var maskTextures = this.maskTextures;
8068
+ for (var i = 0; i < maskTextures.length; i += 1) {
8069
+ var maskTexture = maskTextures[i];
8070
+ var maskLocation = locations[maskTexture.uniformName];
8071
+ if (maskLocation != null && webglInstance.bindTextureToUnit(maskTexture.id, textureUnit)) {
8072
+ gl.uniform1i(maskLocation, textureUnit);
8073
+ textureUnit += 1;
8074
+ }
8025
8075
  }
8076
+ } else {
8077
+ // The base texture isn't available (e.g. a shared texture was deleted by
8078
+ // another element's cleanup while this one still references it). The
8079
+ // composed texture shaders sample u_texture unconditionally, so there is
8080
+ // no safe in-shader fallback — skip this object's draw this frame rather
8081
+ // than sampling whatever stale texture occupies the unit.
8082
+ gl.uniform1i(locations.u_use_texture, 0);
8083
+ skipDraw = true;
8026
8084
  }
8027
8085
  } else {
8028
8086
  gl.uniform1i(locations.u_use_texture, 0);
8029
8087
  }
8030
- gl.drawArrays(this.glPrimitive, 0, numDrawVertices);
8088
+ if (!skipDraw) {
8089
+ gl.drawArrays(this.glPrimitive, 0, numDrawVertices);
8090
+ }
8031
8091
  if (texture) {
8032
8092
  gl.disableVertexAttribArray(locations.a_texcoord);
8033
8093
  }
@@ -52888,11 +52948,20 @@ var FigureElementPrimitiveGLText = /*#__PURE__*/function (_FigureElementPrimiti)
52888
52948
  scene: scene,
52889
52949
  font: this.font
52890
52950
  });
52951
+ var textureID = this.atlas.font.getTextureID();
52891
52952
  if (this.drawingObject.texture == null) {
52892
- this.drawingObject.addTexture(this.atlas.font.getTextureID());
52953
+ this.drawingObject.addTexture(textureID);
52893
52954
  } else {
52894
- this.drawingObject.texture.id = this.atlas.font.getTextureID();
52895
- }
52955
+ this.drawingObject.texture.id = textureID;
52956
+ }
52957
+ // Take a webgl reference to the shared atlas texture so it survives other
52958
+ // elements' cleanup. The Atlas registers/uploads the texture; this element
52959
+ // only adopts its id (GLObject.addTexture above doesn't touch webgl), so
52960
+ // without this its resetTextureBuffer release would be unbalanced and the
52961
+ // first element's cleanup would free the atlas for the others.
52962
+ // setBaseTextureRef releases any previously-held id (createAtlas re-runs on
52963
+ // font changes) and is idempotent when the id is unchanged.
52964
+ this.drawingObject.setBaseTextureRef(textureID);
52896
52965
  // console.log(this.atlas)
52897
52966
  this.setText(this.text);
52898
52967
  this.atlasNotificationsID = this.atlas.notifications.add('updated', this.loaded.bind(this));
@@ -66580,13 +66649,31 @@ function composeFragShader() {
66580
66649
  // alpha controls how strongly that region is recolored (0 = leave the base
66581
66650
  // color unchanged). With one mask this is exactly four mixes and one extra
66582
66651
  // texture fetch; each additional mask adds one fetch and four mixes.
66652
+ //
66653
+ // Masks are uploaded with premultiplied alpha, so a region only carries full
66654
+ // weight where the mask pixel is opaque. Author each mask pixel as one of:
66655
+ // keep base : [0, 0, 0, 0] (transparent)
66656
+ // region 0 : [1, 0, 0, 1] -> u_tint0 (r channel)
66657
+ // region 1 : [0, 1, 0, 1] -> u_tint1 (g channel)
66658
+ // region 2 : [0, 0, 1, 1] -> u_tint2 (b channel)
66659
+ // region 3 : [0, 0, 0, 1] -> u_tint3 (a channel, opaque black)
66660
+ // With every painted region opaque, the alpha channel is 1 across all of them,
66661
+ // so it can't act as an independent region on its own. The a-channel mix below
66662
+ // gates on (1 - r - g - b) so region 3 is exactly the opaque-black pixels - the
66663
+ // ones not already claimed by an r/g/b region. Regions 0-2 support partial
66664
+ // weight by lowering their channel value (e.g. [0.5, 0, 0, 1]); region 3 is
66665
+ // full-weight only, with slight antialiasing softness at its borders.
66583
66666
  src += ' vec4 base = texture2D(u_texture, v_texcoord);\n';
66584
66667
  src += ' vec3 col = base.rgb;\n';
66585
66668
  for (var _m = 0; _m < numMasks; _m += 1) {
66586
66669
  src += " vec4 mask".concat(_m, " = texture2D(u_mask").concat(_m, ", v_texcoord);\n");
66587
66670
  for (var c = 0; c < CHANNELS_PER_MASK; c += 1) {
66588
66671
  var _t = _m * CHANNELS_PER_MASK + c;
66589
- src += " col = mix(col, u_tint".concat(_t, ".rgb, mask").concat(_m, ".").concat(channels[c], " * u_tint").concat(_t, ".a);\n");
66672
+ if (channels[c] === 'a') {
66673
+ src += " col = mix(col, u_tint".concat(_t, ".rgb, mask").concat(_m, ".a * (1.0 - clamp(mask").concat(_m, ".r + mask").concat(_m, ".g + mask").concat(_m, ".b, 0.0, 1.0)) * u_tint").concat(_t, ".a);\n");
66674
+ } else {
66675
+ src += " col = mix(col, u_tint".concat(_t, ".rgb, mask").concat(_m, ".").concat(channels[c], " * u_tint").concat(_t, ".a);\n");
66676
+ }
66590
66677
  }
66591
66678
  }
66592
66679
  src += ' gl_FragColor = vec4(col, base.a * u_color.a);\n';
@@ -67165,6 +67252,20 @@ var WebGLInstance = /*#__PURE__*/function () {
67165
67252
  value:
67166
67253
  // locations: Object;
67167
67254
 
67255
+ // Content texture units are a small shared pool reused across draws. Unit 0 is
67256
+ // reserved for the target/selector framebuffer texture, so content starts at
67257
+ // unit 1. boundUnits[u] tracks which texture id is currently bound to GL unit
67258
+ // u, so bindTextureToUnit can skip redundant binds (bind-on-change).
67259
+
67260
+ // Monotonic source for texture handles (never reused, so deleting a texture
67261
+ // can never cause a later texture to collide with a live one).
67262
+
67263
+ // gl.MAX_TEXTURE_IMAGE_UNITS (the fragment-shader sampler limit), queried once
67264
+ // for a diagnostic warning. Matches the per-object mask guard in
67265
+ // FigurePrimitives.gl().
67266
+
67267
+ // Set once the unit-budget warning has fired, so it isn't logged every frame.
67268
+
67168
67269
  /*
67169
67270
  Add, or update a texture. If the texture already exists, then do nothing.
67170
67271
  A texture is referenced with a unique id, and defined by either a url (string), Image or html canvas element.
@@ -67180,45 +67281,52 @@ var WebGLInstance = /*#__PURE__*/function () {
67180
67281
  var onLoad = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
67181
67282
  var force = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
67182
67283
  /*
67183
- If the texture already exits, then return its index. If the texture is
67184
- still loading, then add the onLoad callback to the list of callbacks to
67185
- be called once the texture loads.
67284
+ A texture id can be shared by multiple owners (e.g. several GLText elements
67285
+ using the same font atlas, plus the Atlas itself). Each addTexture call
67286
+ that isn't a forced content update acquires one reference; deleteTexture
67287
+ releases one, and the GL texture is freed only when the last owner releases
67288
+ it. This lets one element's cleanup() drop its reference without pulling a
67289
+ still-shared texture out from under the survivors.
67290
+ If the texture already exists and is still loading, the onLoad callback is
67291
+ added to the list to be called once the texture loads.
67186
67292
  */
67187
67293
  if (!force && this.textures[id] != null) {
67188
- if (this.textures[id].state === 'loaded') {
67189
- if (onLoad != null) {
67190
- this.textures[id].onLoad.push(onLoad);
67191
- }
67294
+ // Another owner (or the same owner re-acquiring) references an existing
67295
+ // texture: take a reference and (re)register the load callback.
67296
+ var existingTexture = this.textures[id];
67297
+ existingTexture.refCount += 1;
67298
+ if (onLoad != null) {
67299
+ existingTexture.onLoad.push(onLoad);
67300
+ }
67301
+ if (existingTexture.state === 'loaded') {
67192
67302
  this.onLoad(id);
67193
- return this.textures[id].index;
67194
67303
  }
67195
- // Otherwise loading
67304
+ return existingTexture.handle;
67305
+ }
67306
+ var gl = this.gl;
67307
+ if (this.textures[id] != null) {
67308
+ // Forced content update of an existing texture: keep its identity AND
67309
+ // reference count so other owners survive the update. setTextureData
67310
+ // (below) frees and replaces the old glTexture, so the entry — including
67311
+ // its glTexture pointer — is preserved here rather than recreated.
67312
+ // Append (don't replace) onLoad so pending callbacks other owners
67313
+ // registered while the texture was still loading aren't silently dropped.
67314
+ this.textures[id].state = 'loading';
67196
67315
  if (onLoad != null) {
67197
67316
  this.textures[id].onLoad.push(onLoad);
67198
67317
  }
67199
- return this.textures[id].index;
67200
- // removed by dead control flow
67201
-
67202
- }
67203
- var index = 0;
67204
- if (this.textures[id] != null) {
67205
- index = this.textures[id].index;
67206
67318
  } else {
67207
- index = Object.keys(this.textures).length + 1;
67319
+ // Brand new texture: the first owner holds the only reference.
67320
+ this.textures[id] = {
67321
+ id: id,
67322
+ state: 'loading',
67323
+ onLoad: onLoad != null ? [onLoad] : [],
67324
+ handle: this.nextTextureHandle,
67325
+ refCount: 1
67326
+ };
67327
+ this.nextTextureHandle += 1;
67208
67328
  }
67209
- // If a texture already exists, then unload it
67210
- this.deleteTexture(id);
67211
- var gl = this.gl;
67212
- this.textures[id] = {
67213
- id: id,
67214
- state: 'loading',
67215
- onLoad: [],
67216
- index: index
67217
- };
67218
67329
  var texture = this.textures[id];
67219
- if (onLoad != null) {
67220
- texture.onLoad.push(onLoad);
67221
- }
67222
67330
  // If the data is a url string, then load the data into an image
67223
67331
  if (typeof data === 'string') {
67224
67332
  this.setTextureData(id, loadColor);
@@ -67237,7 +67345,7 @@ var WebGLInstance = /*#__PURE__*/function () {
67237
67345
  this.onLoad(id);
67238
67346
  texture.state = 'loaded';
67239
67347
  }
67240
- return this.textures[id].index;
67348
+ return this.textures[id].handle;
67241
67349
  }
67242
67350
  }, {
67243
67351
  key: "getAtlas",
@@ -67273,6 +67381,27 @@ var WebGLInstance = /*#__PURE__*/function () {
67273
67381
  throw errors[0];
67274
67382
  }
67275
67383
  }
67384
+
67385
+ // Take an additional reference to an already-registered texture, without
67386
+ // re-uploading it or touching its load callbacks. Used when an element adopts
67387
+ // a texture another owner created and uploaded (e.g. a GLText element sharing
67388
+ // a font atlas registered by the Atlas). Returns false if the texture isn't
67389
+ // registered yet, in which case no reference was taken. Release with
67390
+ // deleteTexture.
67391
+ }, {
67392
+ key: "acquireTexture",
67393
+ value: function acquireTexture(id) {
67394
+ var texture = this.textures[id];
67395
+ if (texture == null) {
67396
+ return false;
67397
+ }
67398
+ texture.refCount += 1;
67399
+ return true;
67400
+ }
67401
+
67402
+ // Release one reference to a texture. The GL texture is freed only once the
67403
+ // last owner releases it, so one element's cleanup can't delete a texture
67404
+ // another element still shares (see addTexture for the rationale).
67276
67405
  }, {
67277
67406
  key: "deleteTexture",
67278
67407
  value: function deleteTexture(id) {
@@ -67280,13 +67409,27 @@ var WebGLInstance = /*#__PURE__*/function () {
67280
67409
  if (texture == null) {
67281
67410
  return;
67282
67411
  }
67283
- var gl = this.gl;
67412
+ texture.refCount -= 1;
67413
+ if (texture.refCount <= 0) {
67414
+ this.freeTexture(id);
67415
+ }
67416
+ }
67284
67417
 
67285
- // If texture exists, then delete it
67418
+ // Unconditionally free a texture regardless of reference count. Used for
67419
+ // teardown (cleanup), where every owner is going away at once.
67420
+ }, {
67421
+ key: "freeTexture",
67422
+ value: function freeTexture(id) {
67423
+ var texture = this.textures[id];
67424
+ if (texture == null) {
67425
+ return;
67426
+ }
67427
+ var gl = this.gl;
67428
+ // gl.deleteTexture unbinds it from any unit it was bound to, so we just drop
67429
+ // the stale cache entries.
67286
67430
  if (texture.glTexture != null) {
67287
- gl.activeTexture(gl.TEXTURE0 + texture.index);
67288
- gl.bindTexture(gl.TEXTURE_2D, null);
67289
67431
  gl.deleteTexture(texture.glTexture);
67432
+ this.clearBoundUnits(id);
67290
67433
  }
67291
67434
  this.cancel(id);
67292
67435
  delete this.textures[id];
@@ -67298,6 +67441,53 @@ var WebGLInstance = /*#__PURE__*/function () {
67298
67441
  Object.keys(this.textures).forEach(function (id) {
67299
67442
  _this3.textures[id].glTexture = null;
67300
67443
  });
67444
+ // The new context starts with nothing bound, so the bind cache is stale.
67445
+ this.boundUnits = [];
67446
+ }
67447
+
67448
+ // Drop any working-unit cache entries that point at this texture id, so a
67449
+ // future draw re-binds rather than trusting a stale/deleted glTexture.
67450
+ }, {
67451
+ key: "clearBoundUnits",
67452
+ value: function clearBoundUnits(id) {
67453
+ for (var u = 0; u < this.boundUnits.length; u += 1) {
67454
+ if (this.boundUnits[u] === id) {
67455
+ this.boundUnits[u] = null;
67456
+ }
67457
+ }
67458
+ }
67459
+
67460
+ /*
67461
+ Bind a registered texture to a content texture unit for the current draw.
67462
+ Content units are a small shared pool (unit 0 is reserved for the
67463
+ target/selector framebuffer texture). bindTexture is only issued when the
67464
+ unit is not already pointing at this texture, so runs of draws that share a
67465
+ texture (e.g. text sharing a font atlas) issue zero bind calls.
67466
+ */
67467
+ }, {
67468
+ key: "bindTextureToUnit",
67469
+ value: function bindTextureToUnit(id, unit) {
67470
+ var texture = this.textures[id];
67471
+ if (texture == null || texture.glTexture == null) {
67472
+ return false;
67473
+ }
67474
+ if (unit >= this.maxTextureUnits) {
67475
+ // Out of unit budget: warn once (not every frame) and don't issue an
67476
+ // out-of-range bind. The caller treats false as "not bound".
67477
+ if (!this.warnedUnitOverflow) {
67478
+ (0,_tools_tools__WEBPACK_IMPORTED_MODULE_2__.Console)("FigureOne WebGL warning: texture unit ".concat(unit, " exceeds this device's ") + "MAX_TEXTURE_IMAGE_UNITS (".concat(this.maxTextureUnits, "). Reduce the ") + 'number of simultaneous textures/masks.');
67479
+ this.warnedUnitOverflow = true;
67480
+ }
67481
+ return false;
67482
+ }
67483
+ if (this.boundUnits[unit] === id) {
67484
+ return true;
67485
+ }
67486
+ var gl = this.gl;
67487
+ gl.activeTexture(gl.TEXTURE0 + unit);
67488
+ gl.bindTexture(gl.TEXTURE_2D, texture.glTexture);
67489
+ this.boundUnits[unit] = id;
67490
+ return true;
67301
67491
  }
67302
67492
  }, {
67303
67493
  key: "cleanup",
@@ -67311,9 +67501,10 @@ var WebGLInstance = /*#__PURE__*/function () {
67311
67501
  }
67312
67502
  });
67313
67503
  this.programs = [];
67314
- // Delete all textures
67504
+ // Free all textures outright, ignoring reference counts — every owner is
67505
+ // going away in this teardown.
67315
67506
  Object.keys(this.textures).forEach(function (id) {
67316
- _this4.deleteTexture(id);
67507
+ _this4.freeTexture(id);
67317
67508
  });
67318
67509
  this.textures = {};
67319
67510
  // Clean up atlases
@@ -67334,20 +67525,27 @@ var WebGLInstance = /*#__PURE__*/function () {
67334
67525
  return (value & value - 1) === 0;
67335
67526
  }
67336
67527
  var texture = this.textures[id];
67337
- var index = texture.index;
67338
67528
  var gl = this.gl;
67529
+ // Upload happens on the first content unit as scratch. Any cache entry for
67530
+ // this id refers to the old glTexture we're about to replace, so invalidate
67531
+ // them all before binding the new one.
67532
+ var uploadUnit = 1;
67533
+ this.clearBoundUnits(id);
67339
67534
 
67340
67535
  // If texture exists, then delete it
67341
67536
  if (texture.glTexture != null) {
67342
- gl.activeTexture(gl.TEXTURE0 + index);
67537
+ gl.activeTexture(gl.TEXTURE0 + uploadUnit);
67343
67538
  gl.bindTexture(gl.TEXTURE_2D, null);
67344
67539
  gl.deleteTexture(texture.glTexture);
67345
67540
  }
67346
67541
  // Create a texture
67347
67542
  var glTexture = gl.createTexture();
67348
67543
  texture.glTexture = glTexture;
67349
- gl.activeTexture(gl.TEXTURE0 + index);
67544
+ gl.activeTexture(gl.TEXTURE0 + uploadUnit);
67350
67545
  gl.bindTexture(gl.TEXTURE_2D, glTexture);
67546
+ // The new glTexture is now bound to the upload unit; record it so a draw
67547
+ // that needs it on this unit can skip a redundant bind.
67548
+ this.boundUnits[uploadUnit] = id;
67351
67549
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
67352
67550
 
67353
67551
  // If image is a color, then create s ingle pixel image of that color
@@ -67411,7 +67609,7 @@ var WebGLInstance = /*#__PURE__*/function () {
67411
67609
  value: function onLoad(id) {
67412
67610
  var _this5 = this;
67413
67611
  this.textures[id].onLoad.forEach(function (f) {
67414
- return _this5.fnMap.exec(f, true, _this5.textures[id].index);
67612
+ return _this5.fnMap.exec(f, true, _this5.textures[id].handle);
67415
67613
  });
67416
67614
  this.textures[id].onLoad = [];
67417
67615
  }
@@ -67420,7 +67618,7 @@ var WebGLInstance = /*#__PURE__*/function () {
67420
67618
  value: function cancel(id) {
67421
67619
  var _this6 = this;
67422
67620
  this.textures[id].onLoad.forEach(function (f) {
67423
- return _this6.fnMap.exec(f, false, _this6.textures[id].index);
67621
+ return _this6.fnMap.exec(f, false, _this6.textures[id].handle);
67424
67622
  });
67425
67623
  this.textures[id].onLoad = [];
67426
67624
  }
@@ -67485,6 +67683,11 @@ var WebGLInstance = /*#__PURE__*/function () {
67485
67683
  value: function init(gl) {
67486
67684
  this.gl = gl;
67487
67685
  this.textures = {};
67686
+ this.boundUnits = [];
67687
+ this.nextTextureHandle = 1;
67688
+ this.warnedUnitOverflow = false;
67689
+ var maxUnits = gl.getParameter ? gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) : 0;
67690
+ this.maxTextureUnits = maxUnits || 8;
67488
67691
  this.programs = [];
67489
67692
  this.targetTexture = null;
67490
67693
  this.lastUsedProgram = null;
@@ -82098,8 +82301,8 @@ var tools = {
82098
82301
  */
82099
82302
 
82100
82303
  var Fig = {
82101
- version: "1.8.0",
82102
- gitHash: "4b64a8de5",
82304
+ version: "1.9.1",
82305
+ gitHash: "0adca1678",
82103
82306
  tools: tools,
82104
82307
  Figure: _js_figure_Figure__WEBPACK_IMPORTED_MODULE_5__["default"],
82105
82308
  Recorder: _js_figure_Recorder_Recorder__WEBPACK_IMPORTED_MODULE_7__.Recorder,
package/llms-full.txt CHANGED
@@ -115,6 +115,8 @@ Path to figure element(s) within a collection. Supports multiple formats:
115
115
  | loadColor | TypeColor | `[0, 0, 1, 0.5]` | Color while loading |
116
116
  | onLoad | () => void | | Callback after load |
117
117
 
118
+ A `texture` on the `gl` primitive can be recolored by region with one or more masks — see OBJ_TextureMask under OBJ_GenericGL.
119
+
118
120
  ### OBJ_FigurePrimitive (base for all primitives)
119
121
 
120
122
  | Property | Type | Default | Description |
@@ -365,12 +367,47 @@ Extends OBJ_Generic (without drawType).
365
367
  | attributes | OBJ_GLAttribute[] | | Shader attributes |
366
368
  | uniforms | OBJ_GLUniform[] | | Shader uniforms |
367
369
  | texture | OBJ_Texture | | Texture |
370
+ | mask | OBJ_TextureMask | | Single mask to recolor regions of `texture` |
371
+ | masks | OBJ_TextureMask[] | | Multiple masks (each adds 4 recolorable regions) |
372
+ | tints | (TypeColor \| null)[] | | Recolor per region; mask `m` uses `tints[4m]`…`tints[4m+3]` for its r,g,b,a channels (`null` = leave region unchanged) |
368
373
  | dimension | `2` \| `3` | `2` | Coordinate dimensions |
369
374
  | light | `'directional'` \| `'point'` \| null | null | Lighting |
370
375
  | vertices | number[] \| object | | Vertex data |
371
376
  | colors | number[] \| object | | Per-vertex colors |
372
377
  | normals | number[] \| object | | Normal vectors |
373
378
 
379
+ A texture can be recolored by region using one or more masks. A mask shares the base texture's coordinates (same dimensions, aligned to the base image); each of its r, g, b, a channels marks a region. Mask `m` is recolored by `tints[4m]` (r), `tints[4m + 1]` (g), `tints[4m + 2]` (b), `tints[4m + 3]` (a); unmasked pixels keep the base texture's color. A single mask costs one extra texture fetch and four mixes; each additional mask adds one fetch and four mixes.
380
+
381
+ ### OBJ_TextureMask
382
+
383
+ | Property | Type | Default | Description |
384
+ |---|---|---|---|
385
+ | src | string | | URL of the mask image |
386
+ | loadColor | TypeColor | `[0, 0, 0, 0]` | Color shown while the mask loads (transparent = nothing recolored until loaded) |
387
+
388
+ ```js
389
+ // Recolor three regions of a texture with one mask. The mask image's red,
390
+ // green and blue channels mark the regions tinted by tints 0, 1 and 2.
391
+ figure.add({
392
+ make: 'gl',
393
+ vertices: [-0.8, -0.8, 0.8, -0.8, -0.8, 0.8, 0.8, -0.8, 0.8, 0.8, -0.8, 0.8],
394
+ texture: { src: './image.png', coords: [0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1] },
395
+ mask: { src: './mask.png' },
396
+ tints: [[1, 0, 0, 1], [0, 0.6, 0, 1], [0, 0, 1, 1]],
397
+ });
398
+
399
+ // Two masks: mask 0 uses tints 0-3, mask 1 uses tints 4-7.
400
+ figure.add({
401
+ make: 'gl',
402
+ // ...vertices, texture...
403
+ masks: [{ src: './mask.png' }, { src: './mask1.png' }],
404
+ tints: [
405
+ [1, 0, 0, 1], [0, 0.6, 0, 1], [0, 0, 1, 1], null, // mask 0
406
+ [0.6, 0, 0.8, 1], // mask 1
407
+ ],
408
+ });
409
+ ```
410
+
374
411
  ### OBJ_Morph (`make: 'morph'`)
375
412
 
376
413
  Morphable shape with multiple point arrays that can be animated between.
package/llms.txt CHANGED
@@ -97,6 +97,16 @@ figure.add({
97
97
  make: 'rectangle', width: 1.8, height: 1.2,
98
98
  texture: { src: 'image.jpg', mapTo: [-0.9, -0.6, 1.8, 1.2] },
99
99
  });
100
+
101
+ // Recolor regions of a texture with masks (gl primitive). Each mask's r,g,b,a
102
+ // channels mark regions; mask m is tinted by tints[4m]..tints[4m+3].
103
+ figure.add({
104
+ make: 'gl',
105
+ vertices: [-0.8, -0.8, 0.8, -0.8, -0.8, 0.8, 0.8, -0.8, 0.8, 0.8, -0.8, 0.8],
106
+ texture: { src: 'image.png', coords: [0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1] },
107
+ masks: [{ src: 'mask.png' }, { src: 'mask1.png' }],
108
+ tints: [[1, 0, 0, 1], [0, 0.6, 0, 1], [0, 0, 1, 1], null, [0.6, 0, 0.8, 1]],
109
+ });
100
110
  ```
101
111
 
102
112
  ### Element Properties
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figureone",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "Draw, animate and interact with shapes, text, plots and equations in Javascript. Create interactive slide shows, and interactive videos.",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -76,6 +76,7 @@ declare class GLObject extends DrawingObject {
76
76
  fragmentShader: TypeFragmentShader;
77
77
  selectorVertexShader: TypeVertexShader;
78
78
  selectorFragmentShader: TypeFragmentShader;
79
+ acquiredBaseTextureId: string | null;
79
80
  constructor(webgl: WebGLInstance, vertexShader?: TypeVertexShader, fragmentShader?: TypeFragmentShader, selectorVertexShader?: TypeVertexShader, selectorFragShader?: TypeFragmentShader);
80
81
  init(webgl: WebGLInstance): void;
81
82
  initProgram(): void;
@@ -105,6 +106,18 @@ declare class GLObject extends DrawingObject {
105
106
  addMaskTexture(location?: string, loadColor?: TypeColor): void;
106
107
  updateTexture(data: HTMLImageElement): void;
107
108
  initTexture(force?: boolean): void;
109
+ /**
110
+ * Adopt a base-texture reference to an already-registered texture (e.g. a
111
+ * shared font atlas uploaded by the Atlas, whose id this object renders with
112
+ * but does not upload itself).
113
+ *
114
+ * Releases any previously-held base reference first (so a font/atlas change
115
+ * rebalances correctly) and is idempotent when the id is unchanged. If the
116
+ * texture isn't registered yet, no reference is taken and acquiredBaseTextureId
117
+ * is cleared, so the later resetTextureBuffer release stays balanced (it only
118
+ * releases a reference that was actually acquired).
119
+ */
120
+ setBaseTextureRef(id: string): void;
108
121
  createTextureMap(xMinGL?: number, xMaxGL?: number, yMinGL?: number, yMaxGL?: number, xMinTex?: number, xMaxTex?: number, yMinTex?: number, yMaxTex?: number): void;
109
122
  addVertices(vertices: Array<number>, dimension?: 2 | 3, usage?: TypeGLBufferUsage): void;
110
123
  addVertices3(vertices: Array<number>, usage?: TypeGLBufferUsage): void;