BatchTable.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import Cartesian4 from "../Core/Cartesian4.js";
  4. import combine from "../Core/combine.js";
  5. import ComponentDatatype from "../Core/ComponentDatatype.js";
  6. import defined from "../Core/defined.js";
  7. import destroyObject from "../Core/destroyObject.js";
  8. import DeveloperError from "../Core/DeveloperError.js";
  9. import PixelFormat from "../Core/PixelFormat.js";
  10. import ContextLimits from "../Renderer/ContextLimits.js";
  11. import PixelDatatype from "../Renderer/PixelDatatype.js";
  12. import Sampler from "../Renderer/Sampler.js";
  13. import Texture from "../Renderer/Texture.js";
  14. /**
  15. * Creates a texture to look up per instance attributes for batched primitives. For example, store each primitive's pick color in the texture.
  16. *
  17. * @alias BatchTable
  18. * @constructor
  19. * @private
  20. *
  21. * @param {Context} context The context in which the batch table is created.
  22. * @param {Object[]} attributes An array of objects describing a per instance attribute. Each object contains a datatype, components per attributes, whether it is normalized and a function name
  23. * to retrieve the value in the vertex shader.
  24. * @param {number} numberOfInstances The number of instances in a batch table.
  25. *
  26. * @example
  27. * // create the batch table
  28. * const attributes = [{
  29. * functionName : 'getShow',
  30. * componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
  31. * componentsPerAttribute : 1
  32. * }, {
  33. * functionName : 'getPickColor',
  34. * componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
  35. * componentsPerAttribute : 4,
  36. * normalize : true
  37. * }];
  38. * const batchTable = new BatchTable(context, attributes, 5);
  39. *
  40. * // when creating the draw commands, update the uniform map and the vertex shader
  41. * vertexShaderSource = batchTable.getVertexShaderCallback()(vertexShaderSource);
  42. * const shaderProgram = ShaderProgram.fromCache({
  43. * // ...
  44. * vertexShaderSource : vertexShaderSource,
  45. * });
  46. *
  47. * drawCommand.shaderProgram = shaderProgram;
  48. * drawCommand.uniformMap = batchTable.getUniformMapCallback()(uniformMap);
  49. *
  50. * // use the attribute function names in the shader to retrieve the instance values
  51. * // ...
  52. * attribute float batchId;
  53. *
  54. * void main() {
  55. * // ...
  56. * float show = getShow(batchId);
  57. * vec3 pickColor = getPickColor(batchId);
  58. * // ...
  59. * }
  60. */
  61. function BatchTable(context, attributes, numberOfInstances) {
  62. //>>includeStart('debug', pragmas.debug);
  63. if (!defined(context)) {
  64. throw new DeveloperError("context is required");
  65. }
  66. if (!defined(attributes)) {
  67. throw new DeveloperError("attributes is required");
  68. }
  69. if (!defined(numberOfInstances)) {
  70. throw new DeveloperError("numberOfInstances is required");
  71. }
  72. //>>includeEnd('debug');
  73. this._attributes = attributes;
  74. this._numberOfInstances = numberOfInstances;
  75. if (attributes.length === 0) {
  76. return;
  77. }
  78. // PERFORMANCE_IDEA: We may be able to arrange the attributes so they can be packing into fewer texels.
  79. // Right now, an attribute with one component uses an entire texel when 4 single component attributes can
  80. // be packed into a texel.
  81. //
  82. // Packing floats into unsigned byte textures makes the problem worse. A single component float attribute
  83. // will be packed into a single texel leaving 3 texels unused. 4 texels are reserved for each float attribute
  84. // regardless of how many components it has.
  85. const pixelDatatype = getDatatype(attributes);
  86. const textureFloatSupported = context.floatingPointTexture;
  87. const packFloats =
  88. pixelDatatype === PixelDatatype.FLOAT && !textureFloatSupported;
  89. const offsets = createOffsets(attributes, packFloats);
  90. const stride = getStride(offsets, attributes, packFloats);
  91. const maxNumberOfInstancesPerRow = Math.floor(
  92. ContextLimits.maximumTextureSize / stride
  93. );
  94. const instancesPerWidth = Math.min(
  95. numberOfInstances,
  96. maxNumberOfInstancesPerRow
  97. );
  98. const width = stride * instancesPerWidth;
  99. const height = Math.ceil(numberOfInstances / instancesPerWidth);
  100. const stepX = 1.0 / width;
  101. const centerX = stepX * 0.5;
  102. const stepY = 1.0 / height;
  103. const centerY = stepY * 0.5;
  104. this._textureDimensions = new Cartesian2(width, height);
  105. this._textureStep = new Cartesian4(stepX, centerX, stepY, centerY);
  106. this._pixelDatatype = !packFloats
  107. ? pixelDatatype
  108. : PixelDatatype.UNSIGNED_BYTE;
  109. this._packFloats = packFloats;
  110. this._offsets = offsets;
  111. this._stride = stride;
  112. this._texture = undefined;
  113. const batchLength = 4 * width * height;
  114. this._batchValues =
  115. pixelDatatype === PixelDatatype.FLOAT && !packFloats
  116. ? new Float32Array(batchLength)
  117. : new Uint8Array(batchLength);
  118. this._batchValuesDirty = false;
  119. }
  120. Object.defineProperties(BatchTable.prototype, {
  121. /**
  122. * The attribute descriptions.
  123. * @memberOf BatchTable.prototype
  124. * @type {Object[]}
  125. * @readonly
  126. */
  127. attributes: {
  128. get: function () {
  129. return this._attributes;
  130. },
  131. },
  132. /**
  133. * The number of instances.
  134. * @memberOf BatchTable.prototype
  135. * @type {number}
  136. * @readonly
  137. */
  138. numberOfInstances: {
  139. get: function () {
  140. return this._numberOfInstances;
  141. },
  142. },
  143. });
  144. function getDatatype(attributes) {
  145. let foundFloatDatatype = false;
  146. const length = attributes.length;
  147. for (let i = 0; i < length; ++i) {
  148. if (attributes[i].componentDatatype !== ComponentDatatype.UNSIGNED_BYTE) {
  149. foundFloatDatatype = true;
  150. break;
  151. }
  152. }
  153. return foundFloatDatatype ? PixelDatatype.FLOAT : PixelDatatype.UNSIGNED_BYTE;
  154. }
  155. function getAttributeType(attributes, attributeIndex) {
  156. const componentsPerAttribute =
  157. attributes[attributeIndex].componentsPerAttribute;
  158. if (componentsPerAttribute === 2) {
  159. return Cartesian2;
  160. } else if (componentsPerAttribute === 3) {
  161. return Cartesian3;
  162. } else if (componentsPerAttribute === 4) {
  163. return Cartesian4;
  164. }
  165. return Number;
  166. }
  167. function createOffsets(attributes, packFloats) {
  168. const offsets = new Array(attributes.length);
  169. let currentOffset = 0;
  170. const attributesLength = attributes.length;
  171. for (let i = 0; i < attributesLength; ++i) {
  172. const attribute = attributes[i];
  173. const componentDatatype = attribute.componentDatatype;
  174. offsets[i] = currentOffset;
  175. if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) {
  176. currentOffset += 4;
  177. } else {
  178. ++currentOffset;
  179. }
  180. }
  181. return offsets;
  182. }
  183. function getStride(offsets, attributes, packFloats) {
  184. const length = offsets.length;
  185. const lastOffset = offsets[length - 1];
  186. const lastAttribute = attributes[length - 1];
  187. const componentDatatype = lastAttribute.componentDatatype;
  188. if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) {
  189. return lastOffset + 4;
  190. }
  191. return lastOffset + 1;
  192. }
  193. const scratchPackedFloatCartesian4 = new Cartesian4();
  194. function getPackedFloat(array, index, result) {
  195. let packed = Cartesian4.unpack(array, index, scratchPackedFloatCartesian4);
  196. const x = Cartesian4.unpackFloat(packed);
  197. packed = Cartesian4.unpack(array, index + 4, scratchPackedFloatCartesian4);
  198. const y = Cartesian4.unpackFloat(packed);
  199. packed = Cartesian4.unpack(array, index + 8, scratchPackedFloatCartesian4);
  200. const z = Cartesian4.unpackFloat(packed);
  201. packed = Cartesian4.unpack(array, index + 12, scratchPackedFloatCartesian4);
  202. const w = Cartesian4.unpackFloat(packed);
  203. return Cartesian4.fromElements(x, y, z, w, result);
  204. }
  205. function setPackedAttribute(value, array, index) {
  206. let packed = Cartesian4.packFloat(value.x, scratchPackedFloatCartesian4);
  207. Cartesian4.pack(packed, array, index);
  208. packed = Cartesian4.packFloat(value.y, packed);
  209. Cartesian4.pack(packed, array, index + 4);
  210. packed = Cartesian4.packFloat(value.z, packed);
  211. Cartesian4.pack(packed, array, index + 8);
  212. packed = Cartesian4.packFloat(value.w, packed);
  213. Cartesian4.pack(packed, array, index + 12);
  214. }
  215. const scratchGetAttributeCartesian4 = new Cartesian4();
  216. /**
  217. * Gets the value of an attribute in the table.
  218. *
  219. * @param {number} instanceIndex The index of the instance.
  220. * @param {number} attributeIndex The index of the attribute.
  221. * @param {undefined|Cartesian2|Cartesian3|Cartesian4} [result] The object onto which to store the result. The type is dependent on the attribute's number of components.
  222. * @returns {number|Cartesian2|Cartesian3|Cartesian4} The attribute value stored for the instance.
  223. *
  224. * @exception {DeveloperError} instanceIndex is out of range.
  225. * @exception {DeveloperError} attributeIndex is out of range.
  226. */
  227. BatchTable.prototype.getBatchedAttribute = function (
  228. instanceIndex,
  229. attributeIndex,
  230. result
  231. ) {
  232. //>>includeStart('debug', pragmas.debug);
  233. if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) {
  234. throw new DeveloperError("instanceIndex is out of range.");
  235. }
  236. if (attributeIndex < 0 || attributeIndex >= this._attributes.length) {
  237. throw new DeveloperError("attributeIndex is out of range");
  238. }
  239. //>>includeEnd('debug');
  240. const attributes = this._attributes;
  241. const offset = this._offsets[attributeIndex];
  242. const stride = this._stride;
  243. const index = 4 * stride * instanceIndex + 4 * offset;
  244. let value;
  245. if (
  246. this._packFloats &&
  247. attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE
  248. ) {
  249. value = getPackedFloat(
  250. this._batchValues,
  251. index,
  252. scratchGetAttributeCartesian4
  253. );
  254. } else {
  255. value = Cartesian4.unpack(
  256. this._batchValues,
  257. index,
  258. scratchGetAttributeCartesian4
  259. );
  260. }
  261. const attributeType = getAttributeType(attributes, attributeIndex);
  262. if (defined(attributeType.fromCartesian4)) {
  263. return attributeType.fromCartesian4(value, result);
  264. } else if (defined(attributeType.clone)) {
  265. return attributeType.clone(value, result);
  266. }
  267. return value.x;
  268. };
  269. const setAttributeScratchValues = [
  270. undefined,
  271. undefined,
  272. new Cartesian2(),
  273. new Cartesian3(),
  274. new Cartesian4(),
  275. ];
  276. const setAttributeScratchCartesian4 = new Cartesian4();
  277. /**
  278. * Sets the value of an attribute in the table.
  279. *
  280. * @param {number} instanceIndex The index of the instance.
  281. * @param {number} attributeIndex The index of the attribute.
  282. * @param {number|Cartesian2|Cartesian3|Cartesian4} value The value to be stored in the table. The type of value will depend on the number of components of the attribute.
  283. *
  284. * @exception {DeveloperError} instanceIndex is out of range.
  285. * @exception {DeveloperError} attributeIndex is out of range.
  286. */
  287. BatchTable.prototype.setBatchedAttribute = function (
  288. instanceIndex,
  289. attributeIndex,
  290. value
  291. ) {
  292. //>>includeStart('debug', pragmas.debug);
  293. if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) {
  294. throw new DeveloperError("instanceIndex is out of range.");
  295. }
  296. if (attributeIndex < 0 || attributeIndex >= this._attributes.length) {
  297. throw new DeveloperError("attributeIndex is out of range");
  298. }
  299. if (!defined(value)) {
  300. throw new DeveloperError("value is required.");
  301. }
  302. //>>includeEnd('debug');
  303. const attributes = this._attributes;
  304. const result =
  305. setAttributeScratchValues[
  306. attributes[attributeIndex].componentsPerAttribute
  307. ];
  308. const currentAttribute = this.getBatchedAttribute(
  309. instanceIndex,
  310. attributeIndex,
  311. result
  312. );
  313. const attributeType = getAttributeType(this._attributes, attributeIndex);
  314. const entriesEqual = defined(attributeType.equals)
  315. ? attributeType.equals(currentAttribute, value)
  316. : currentAttribute === value;
  317. if (entriesEqual) {
  318. return;
  319. }
  320. const attributeValue = setAttributeScratchCartesian4;
  321. attributeValue.x = defined(value.x) ? value.x : value;
  322. attributeValue.y = defined(value.y) ? value.y : 0.0;
  323. attributeValue.z = defined(value.z) ? value.z : 0.0;
  324. attributeValue.w = defined(value.w) ? value.w : 0.0;
  325. const offset = this._offsets[attributeIndex];
  326. const stride = this._stride;
  327. const index = 4 * stride * instanceIndex + 4 * offset;
  328. if (
  329. this._packFloats &&
  330. attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE
  331. ) {
  332. setPackedAttribute(attributeValue, this._batchValues, index);
  333. } else {
  334. Cartesian4.pack(attributeValue, this._batchValues, index);
  335. }
  336. this._batchValuesDirty = true;
  337. };
  338. function createTexture(batchTable, context) {
  339. const dimensions = batchTable._textureDimensions;
  340. batchTable._texture = new Texture({
  341. context: context,
  342. pixelFormat: PixelFormat.RGBA,
  343. pixelDatatype: batchTable._pixelDatatype,
  344. width: dimensions.x,
  345. height: dimensions.y,
  346. sampler: Sampler.NEAREST,
  347. flipY: false,
  348. });
  349. }
  350. function updateTexture(batchTable) {
  351. const dimensions = batchTable._textureDimensions;
  352. batchTable._texture.copyFrom({
  353. source: {
  354. width: dimensions.x,
  355. height: dimensions.y,
  356. arrayBufferView: batchTable._batchValues,
  357. },
  358. });
  359. }
  360. /**
  361. * Creates/updates the batch table texture.
  362. * @param {FrameState} frameState The frame state.
  363. *
  364. * @exception {RuntimeError} The floating point texture extension is required but not supported.
  365. */
  366. BatchTable.prototype.update = function (frameState) {
  367. if (
  368. (defined(this._texture) && !this._batchValuesDirty) ||
  369. this._attributes.length === 0
  370. ) {
  371. return;
  372. }
  373. this._batchValuesDirty = false;
  374. if (!defined(this._texture)) {
  375. createTexture(this, frameState.context);
  376. }
  377. updateTexture(this);
  378. };
  379. /**
  380. * Gets a function that will update a uniform map to contain values for looking up values in the batch table.
  381. *
  382. * @returns {BatchTable.updateUniformMapCallback} A callback for updating uniform maps.
  383. */
  384. BatchTable.prototype.getUniformMapCallback = function () {
  385. const that = this;
  386. return function (uniformMap) {
  387. if (that._attributes.length === 0) {
  388. return uniformMap;
  389. }
  390. const batchUniformMap = {
  391. batchTexture: function () {
  392. return that._texture;
  393. },
  394. batchTextureDimensions: function () {
  395. return that._textureDimensions;
  396. },
  397. batchTextureStep: function () {
  398. return that._textureStep;
  399. },
  400. };
  401. return combine(uniformMap, batchUniformMap);
  402. };
  403. };
  404. function getGlslComputeSt(batchTable) {
  405. const stride = batchTable._stride;
  406. // GLSL batchId is zero-based: [0, numberOfInstances - 1]
  407. if (batchTable._textureDimensions.y === 1) {
  408. return (
  409. `${
  410. "uniform vec4 batchTextureStep; \n" +
  411. "vec2 computeSt(float batchId) \n" +
  412. "{ \n" +
  413. " float stepX = batchTextureStep.x; \n" +
  414. " float centerX = batchTextureStep.y; \n" +
  415. " float numberOfAttributes = float("
  416. }${stride}); \n` +
  417. ` return vec2(centerX + (batchId * numberOfAttributes * stepX), 0.5); \n` +
  418. `} \n`
  419. );
  420. }
  421. return (
  422. `${
  423. "uniform vec4 batchTextureStep; \n" +
  424. "uniform vec2 batchTextureDimensions; \n" +
  425. "vec2 computeSt(float batchId) \n" +
  426. "{ \n" +
  427. " float stepX = batchTextureStep.x; \n" +
  428. " float centerX = batchTextureStep.y; \n" +
  429. " float stepY = batchTextureStep.z; \n" +
  430. " float centerY = batchTextureStep.w; \n" +
  431. " float numberOfAttributes = float("
  432. }${stride}); \n` +
  433. ` float xId = mod(batchId * numberOfAttributes, batchTextureDimensions.x); \n` +
  434. ` float yId = floor(batchId * numberOfAttributes / batchTextureDimensions.x); \n` +
  435. ` return vec2(centerX + (xId * stepX), centerY + (yId * stepY)); \n` +
  436. `} \n`
  437. );
  438. }
  439. function getComponentType(componentsPerAttribute) {
  440. if (componentsPerAttribute === 1) {
  441. return "float";
  442. }
  443. return `vec${componentsPerAttribute}`;
  444. }
  445. function getComponentSwizzle(componentsPerAttribute) {
  446. if (componentsPerAttribute === 1) {
  447. return ".x";
  448. } else if (componentsPerAttribute === 2) {
  449. return ".xy";
  450. } else if (componentsPerAttribute === 3) {
  451. return ".xyz";
  452. }
  453. return "";
  454. }
  455. function getGlslAttributeFunction(batchTable, attributeIndex) {
  456. const attributes = batchTable._attributes;
  457. const attribute = attributes[attributeIndex];
  458. const componentsPerAttribute = attribute.componentsPerAttribute;
  459. const functionName = attribute.functionName;
  460. const functionReturnType = getComponentType(componentsPerAttribute);
  461. const functionReturnValue = getComponentSwizzle(componentsPerAttribute);
  462. const offset = batchTable._offsets[attributeIndex];
  463. let glslFunction =
  464. `${functionReturnType} ${functionName}(float batchId) \n` +
  465. `{ \n` +
  466. ` vec2 st = computeSt(batchId); \n` +
  467. ` st.x += batchTextureStep.x * float(${offset}); \n`;
  468. if (
  469. batchTable._packFloats &&
  470. attribute.componentDatatype !== PixelDatatype.UNSIGNED_BYTE
  471. ) {
  472. glslFunction +=
  473. "vec4 textureValue; \n" +
  474. "textureValue.x = czm_unpackFloat(texture(batchTexture, st)); \n" +
  475. "textureValue.y = czm_unpackFloat(texture(batchTexture, st + vec2(batchTextureStep.x, 0.0))); \n" +
  476. "textureValue.z = czm_unpackFloat(texture(batchTexture, st + vec2(batchTextureStep.x * 2.0, 0.0))); \n" +
  477. "textureValue.w = czm_unpackFloat(texture(batchTexture, st + vec2(batchTextureStep.x * 3.0, 0.0))); \n";
  478. } else {
  479. glslFunction += " vec4 textureValue = texture(batchTexture, st); \n";
  480. }
  481. glslFunction += ` ${functionReturnType} value = textureValue${functionReturnValue}; \n`;
  482. if (
  483. batchTable._pixelDatatype === PixelDatatype.UNSIGNED_BYTE &&
  484. attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE &&
  485. !attribute.normalize
  486. ) {
  487. glslFunction += "value *= 255.0; \n";
  488. } else if (
  489. batchTable._pixelDatatype === PixelDatatype.FLOAT &&
  490. attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE &&
  491. attribute.normalize
  492. ) {
  493. glslFunction += "value /= 255.0; \n";
  494. }
  495. glslFunction += " return value; \n" + "} \n";
  496. return glslFunction;
  497. }
  498. /**
  499. * Gets a function that will update a vertex shader to contain functions for looking up values in the batch table.
  500. *
  501. * @returns {BatchTable.updateVertexShaderSourceCallback} A callback for updating a vertex shader source.
  502. */
  503. BatchTable.prototype.getVertexShaderCallback = function () {
  504. const attributes = this._attributes;
  505. if (attributes.length === 0) {
  506. return function (source) {
  507. return source;
  508. };
  509. }
  510. let batchTableShader = "uniform highp sampler2D batchTexture; \n";
  511. batchTableShader += `${getGlslComputeSt(this)}\n`;
  512. const length = attributes.length;
  513. for (let i = 0; i < length; ++i) {
  514. batchTableShader += getGlslAttributeFunction(this, i);
  515. }
  516. return function (source) {
  517. const mainIndex = source.indexOf("void main");
  518. const beforeMain = source.substring(0, mainIndex);
  519. const afterMain = source.substring(mainIndex);
  520. return `${beforeMain}\n${batchTableShader}\n${afterMain}`;
  521. };
  522. };
  523. /**
  524. * Returns true if this object was destroyed; otherwise, false.
  525. * <br /><br />
  526. * If this object was destroyed, it should not be used; calling any function other than
  527. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  528. *
  529. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  530. *
  531. * @see BatchTable#destroy
  532. */
  533. BatchTable.prototype.isDestroyed = function () {
  534. return false;
  535. };
  536. /**
  537. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  538. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  539. * <br /><br />
  540. * Once an object is destroyed, it should not be used; calling any function other than
  541. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  542. * assign the return value (<code>undefined</code>) to the object as done in the example.
  543. *
  544. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  545. *
  546. * @see BatchTable#isDestroyed
  547. */
  548. BatchTable.prototype.destroy = function () {
  549. this._texture = this._texture && this._texture.destroy();
  550. return destroyObject(this);
  551. };
  552. /**
  553. * A callback for updating uniform maps.
  554. * @callback BatchTable.updateUniformMapCallback
  555. *
  556. * @param {object} uniformMap The uniform map.
  557. * @returns {object} The new uniform map with properties for retrieving values from the batch table.
  558. */
  559. /**
  560. * A callback for updating a vertex shader source.
  561. * @callback BatchTable.updateVertexShaderSourceCallback
  562. *
  563. * @param {string} vertexShaderSource The vertex shader source.
  564. * @returns {string} The new vertex shader source with the functions for retrieving batch table values injected.
  565. */
  566. export default BatchTable;