VertexArrayFacade.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. import Check from "../Core/Check.js";
  2. import ComponentDatatype from "../Core/ComponentDatatype.js";
  3. import defaultValue from "../Core/defaultValue.js";
  4. import defined from "../Core/defined.js";
  5. import destroyObject from "../Core/destroyObject.js";
  6. import DeveloperError from "../Core/DeveloperError.js";
  7. import CesiumMath from "../Core/Math.js";
  8. import Buffer from "./Buffer.js";
  9. import BufferUsage from "./BufferUsage.js";
  10. import VertexArray from "./VertexArray.js";
  11. /**
  12. * @private
  13. */
  14. function VertexArrayFacade(context, attributes, sizeInVertices, instanced) {
  15. //>>includeStart('debug', pragmas.debug);
  16. Check.defined("context", context);
  17. if (!attributes || attributes.length === 0) {
  18. throw new DeveloperError("At least one attribute is required.");
  19. }
  20. //>>includeEnd('debug');
  21. const attrs = VertexArrayFacade._verifyAttributes(attributes);
  22. sizeInVertices = defaultValue(sizeInVertices, 0);
  23. const precreatedAttributes = [];
  24. const attributesByUsage = {};
  25. let attributesForUsage;
  26. let usage;
  27. // Bucket the attributes by usage.
  28. const length = attrs.length;
  29. for (let i = 0; i < length; ++i) {
  30. const attribute = attrs[i];
  31. // If the attribute already has a vertex buffer, we do not need
  32. // to manage a vertex buffer or typed array for it.
  33. if (attribute.vertexBuffer) {
  34. precreatedAttributes.push(attribute);
  35. continue;
  36. }
  37. usage = attribute.usage;
  38. attributesForUsage = attributesByUsage[usage];
  39. if (!defined(attributesForUsage)) {
  40. attributesForUsage = attributesByUsage[usage] = [];
  41. }
  42. attributesForUsage.push(attribute);
  43. }
  44. // A function to sort attributes by the size of their components. From left to right, a vertex
  45. // stores floats, shorts, and then bytes.
  46. function compare(left, right) {
  47. return (
  48. ComponentDatatype.getSizeInBytes(right.componentDatatype) -
  49. ComponentDatatype.getSizeInBytes(left.componentDatatype)
  50. );
  51. }
  52. this._allBuffers = [];
  53. for (usage in attributesByUsage) {
  54. if (attributesByUsage.hasOwnProperty(usage)) {
  55. attributesForUsage = attributesByUsage[usage];
  56. attributesForUsage.sort(compare);
  57. const vertexSizeInBytes = VertexArrayFacade._vertexSizeInBytes(
  58. attributesForUsage
  59. );
  60. const bufferUsage = attributesForUsage[0].usage;
  61. const buffer = {
  62. vertexSizeInBytes: vertexSizeInBytes,
  63. vertexBuffer: undefined,
  64. usage: bufferUsage,
  65. needsCommit: false,
  66. arrayBuffer: undefined,
  67. arrayViews: VertexArrayFacade._createArrayViews(
  68. attributesForUsage,
  69. vertexSizeInBytes
  70. ),
  71. };
  72. this._allBuffers.push(buffer);
  73. }
  74. }
  75. this._size = 0;
  76. this._instanced = defaultValue(instanced, false);
  77. this._precreated = precreatedAttributes;
  78. this._context = context;
  79. this.writers = undefined;
  80. this.va = undefined;
  81. this.resize(sizeInVertices);
  82. }
  83. VertexArrayFacade._verifyAttributes = function (attributes) {
  84. const attrs = [];
  85. for (let i = 0; i < attributes.length; ++i) {
  86. const attribute = attributes[i];
  87. const attr = {
  88. index: defaultValue(attribute.index, i),
  89. enabled: defaultValue(attribute.enabled, true),
  90. componentsPerAttribute: attribute.componentsPerAttribute,
  91. componentDatatype: defaultValue(
  92. attribute.componentDatatype,
  93. ComponentDatatype.FLOAT
  94. ),
  95. normalize: defaultValue(attribute.normalize, false),
  96. // There will be either a vertexBuffer or an [optional] usage.
  97. vertexBuffer: attribute.vertexBuffer,
  98. usage: defaultValue(attribute.usage, BufferUsage.STATIC_DRAW),
  99. };
  100. attrs.push(attr);
  101. //>>includeStart('debug', pragmas.debug);
  102. if (
  103. attr.componentsPerAttribute !== 1 &&
  104. attr.componentsPerAttribute !== 2 &&
  105. attr.componentsPerAttribute !== 3 &&
  106. attr.componentsPerAttribute !== 4
  107. ) {
  108. throw new DeveloperError(
  109. "attribute.componentsPerAttribute must be in the range [1, 4]."
  110. );
  111. }
  112. const datatype = attr.componentDatatype;
  113. if (!ComponentDatatype.validate(datatype)) {
  114. throw new DeveloperError(
  115. "Attribute must have a valid componentDatatype or not specify it."
  116. );
  117. }
  118. if (!BufferUsage.validate(attr.usage)) {
  119. throw new DeveloperError(
  120. "Attribute must have a valid usage or not specify it."
  121. );
  122. }
  123. //>>includeEnd('debug');
  124. }
  125. // Verify all attribute names are unique.
  126. const uniqueIndices = new Array(attrs.length);
  127. for (let j = 0; j < attrs.length; ++j) {
  128. const currentAttr = attrs[j];
  129. const index = currentAttr.index;
  130. //>>includeStart('debug', pragmas.debug);
  131. if (uniqueIndices[index]) {
  132. throw new DeveloperError(
  133. `Index ${index} is used by more than one attribute.`
  134. );
  135. }
  136. //>>includeEnd('debug');
  137. uniqueIndices[index] = true;
  138. }
  139. return attrs;
  140. };
  141. VertexArrayFacade._vertexSizeInBytes = function (attributes) {
  142. let sizeInBytes = 0;
  143. const length = attributes.length;
  144. for (let i = 0; i < length; ++i) {
  145. const attribute = attributes[i];
  146. sizeInBytes +=
  147. attribute.componentsPerAttribute *
  148. ComponentDatatype.getSizeInBytes(attribute.componentDatatype);
  149. }
  150. const maxComponentSizeInBytes =
  151. length > 0
  152. ? ComponentDatatype.getSizeInBytes(attributes[0].componentDatatype)
  153. : 0; // Sorted by size
  154. const remainder =
  155. maxComponentSizeInBytes > 0 ? sizeInBytes % maxComponentSizeInBytes : 0;
  156. const padding = remainder === 0 ? 0 : maxComponentSizeInBytes - remainder;
  157. sizeInBytes += padding;
  158. return sizeInBytes;
  159. };
  160. VertexArrayFacade._createArrayViews = function (attributes, vertexSizeInBytes) {
  161. const views = [];
  162. let offsetInBytes = 0;
  163. const length = attributes.length;
  164. for (let i = 0; i < length; ++i) {
  165. const attribute = attributes[i];
  166. const componentDatatype = attribute.componentDatatype;
  167. views.push({
  168. index: attribute.index,
  169. enabled: attribute.enabled,
  170. componentsPerAttribute: attribute.componentsPerAttribute,
  171. componentDatatype: componentDatatype,
  172. normalize: attribute.normalize,
  173. offsetInBytes: offsetInBytes,
  174. vertexSizeInComponentType:
  175. vertexSizeInBytes / ComponentDatatype.getSizeInBytes(componentDatatype),
  176. view: undefined,
  177. });
  178. offsetInBytes +=
  179. attribute.componentsPerAttribute *
  180. ComponentDatatype.getSizeInBytes(componentDatatype);
  181. }
  182. return views;
  183. };
  184. /**
  185. * Invalidates writers. Can't render again until commit is called.
  186. */
  187. VertexArrayFacade.prototype.resize = function (sizeInVertices) {
  188. this._size = sizeInVertices;
  189. const allBuffers = this._allBuffers;
  190. this.writers = [];
  191. for (let i = 0, len = allBuffers.length; i < len; ++i) {
  192. const buffer = allBuffers[i];
  193. VertexArrayFacade._resize(buffer, this._size);
  194. // Reserving invalidates the writers, so if client's cache them, they need to invalidate their cache.
  195. VertexArrayFacade._appendWriters(this.writers, buffer);
  196. }
  197. // VAs are recreated next time commit is called.
  198. destroyVA(this);
  199. };
  200. VertexArrayFacade._resize = function (buffer, size) {
  201. if (buffer.vertexSizeInBytes > 0) {
  202. // Create larger array buffer
  203. const arrayBuffer = new ArrayBuffer(size * buffer.vertexSizeInBytes);
  204. // Copy contents from previous array buffer
  205. if (defined(buffer.arrayBuffer)) {
  206. const destView = new Uint8Array(arrayBuffer);
  207. const sourceView = new Uint8Array(buffer.arrayBuffer);
  208. const sourceLength = sourceView.length;
  209. for (let j = 0; j < sourceLength; ++j) {
  210. destView[j] = sourceView[j];
  211. }
  212. }
  213. // Create typed views into the new array buffer
  214. const views = buffer.arrayViews;
  215. const length = views.length;
  216. for (let i = 0; i < length; ++i) {
  217. const view = views[i];
  218. view.view = ComponentDatatype.createArrayBufferView(
  219. view.componentDatatype,
  220. arrayBuffer,
  221. view.offsetInBytes
  222. );
  223. }
  224. buffer.arrayBuffer = arrayBuffer;
  225. }
  226. };
  227. const createWriters = [
  228. // 1 component per attribute
  229. function (buffer, view, vertexSizeInComponentType) {
  230. return function (index, attribute) {
  231. view[index * vertexSizeInComponentType] = attribute;
  232. buffer.needsCommit = true;
  233. };
  234. },
  235. // 2 component per attribute
  236. function (buffer, view, vertexSizeInComponentType) {
  237. return function (index, component0, component1) {
  238. const i = index * vertexSizeInComponentType;
  239. view[i] = component0;
  240. view[i + 1] = component1;
  241. buffer.needsCommit = true;
  242. };
  243. },
  244. // 3 component per attribute
  245. function (buffer, view, vertexSizeInComponentType) {
  246. return function (index, component0, component1, component2) {
  247. const i = index * vertexSizeInComponentType;
  248. view[i] = component0;
  249. view[i + 1] = component1;
  250. view[i + 2] = component2;
  251. buffer.needsCommit = true;
  252. };
  253. },
  254. // 4 component per attribute
  255. function (buffer, view, vertexSizeInComponentType) {
  256. return function (index, component0, component1, component2, component3) {
  257. const i = index * vertexSizeInComponentType;
  258. view[i] = component0;
  259. view[i + 1] = component1;
  260. view[i + 2] = component2;
  261. view[i + 3] = component3;
  262. buffer.needsCommit = true;
  263. };
  264. },
  265. ];
  266. VertexArrayFacade._appendWriters = function (writers, buffer) {
  267. const arrayViews = buffer.arrayViews;
  268. const length = arrayViews.length;
  269. for (let i = 0; i < length; ++i) {
  270. const arrayView = arrayViews[i];
  271. writers[arrayView.index] = createWriters[
  272. arrayView.componentsPerAttribute - 1
  273. ](buffer, arrayView.view, arrayView.vertexSizeInComponentType);
  274. }
  275. };
  276. VertexArrayFacade.prototype.commit = function (indexBuffer) {
  277. let recreateVA = false;
  278. const allBuffers = this._allBuffers;
  279. let buffer;
  280. let i;
  281. let length;
  282. for (i = 0, length = allBuffers.length; i < length; ++i) {
  283. buffer = allBuffers[i];
  284. recreateVA = commit(this, buffer) || recreateVA;
  285. }
  286. ///////////////////////////////////////////////////////////////////////
  287. if (recreateVA || !defined(this.va)) {
  288. destroyVA(this);
  289. const va = (this.va = []);
  290. const chunkSize = CesiumMath.SIXTY_FOUR_KILOBYTES - 4; // The 65535 index is reserved for primitive restart. Reserve the last 4 indices so that billboard quads are not broken up.
  291. const numberOfVertexArrays =
  292. defined(indexBuffer) && !this._instanced
  293. ? Math.ceil(this._size / chunkSize)
  294. : 1;
  295. for (let k = 0; k < numberOfVertexArrays; ++k) {
  296. let attributes = [];
  297. for (i = 0, length = allBuffers.length; i < length; ++i) {
  298. buffer = allBuffers[i];
  299. const offset = k * (buffer.vertexSizeInBytes * chunkSize);
  300. VertexArrayFacade._appendAttributes(
  301. attributes,
  302. buffer,
  303. offset,
  304. this._instanced
  305. );
  306. }
  307. attributes = attributes.concat(this._precreated);
  308. va.push({
  309. va: new VertexArray({
  310. context: this._context,
  311. attributes: attributes,
  312. indexBuffer: indexBuffer,
  313. }),
  314. indicesCount:
  315. 1.5 *
  316. (k !== numberOfVertexArrays - 1 ? chunkSize : this._size % chunkSize),
  317. // TODO: not hardcode 1.5, this assumes 6 indices per 4 vertices (as for Billboard quads).
  318. });
  319. }
  320. }
  321. };
  322. function commit(vertexArrayFacade, buffer) {
  323. if (buffer.needsCommit && buffer.vertexSizeInBytes > 0) {
  324. buffer.needsCommit = false;
  325. const vertexBuffer = buffer.vertexBuffer;
  326. const vertexBufferSizeInBytes =
  327. vertexArrayFacade._size * buffer.vertexSizeInBytes;
  328. const vertexBufferDefined = defined(vertexBuffer);
  329. if (
  330. !vertexBufferDefined ||
  331. vertexBuffer.sizeInBytes < vertexBufferSizeInBytes
  332. ) {
  333. if (vertexBufferDefined) {
  334. vertexBuffer.destroy();
  335. }
  336. buffer.vertexBuffer = Buffer.createVertexBuffer({
  337. context: vertexArrayFacade._context,
  338. typedArray: buffer.arrayBuffer,
  339. usage: buffer.usage,
  340. });
  341. buffer.vertexBuffer.vertexArrayDestroyable = false;
  342. return true; // Created new vertex buffer
  343. }
  344. buffer.vertexBuffer.copyFromArrayView(buffer.arrayBuffer);
  345. }
  346. return false; // Did not create new vertex buffer
  347. }
  348. VertexArrayFacade._appendAttributes = function (
  349. attributes,
  350. buffer,
  351. vertexBufferOffset,
  352. instanced
  353. ) {
  354. const arrayViews = buffer.arrayViews;
  355. const length = arrayViews.length;
  356. for (let i = 0; i < length; ++i) {
  357. const view = arrayViews[i];
  358. attributes.push({
  359. index: view.index,
  360. enabled: view.enabled,
  361. componentsPerAttribute: view.componentsPerAttribute,
  362. componentDatatype: view.componentDatatype,
  363. normalize: view.normalize,
  364. vertexBuffer: buffer.vertexBuffer,
  365. offsetInBytes: vertexBufferOffset + view.offsetInBytes,
  366. strideInBytes: buffer.vertexSizeInBytes,
  367. instanceDivisor: instanced ? 1 : 0,
  368. });
  369. }
  370. };
  371. VertexArrayFacade.prototype.subCommit = function (
  372. offsetInVertices,
  373. lengthInVertices
  374. ) {
  375. //>>includeStart('debug', pragmas.debug);
  376. if (offsetInVertices < 0 || offsetInVertices >= this._size) {
  377. throw new DeveloperError(
  378. "offsetInVertices must be greater than or equal to zero and less than the vertex array size."
  379. );
  380. }
  381. if (offsetInVertices + lengthInVertices > this._size) {
  382. throw new DeveloperError(
  383. "offsetInVertices + lengthInVertices cannot exceed the vertex array size."
  384. );
  385. }
  386. //>>includeEnd('debug');
  387. const allBuffers = this._allBuffers;
  388. for (let i = 0, len = allBuffers.length; i < len; ++i) {
  389. subCommit(allBuffers[i], offsetInVertices, lengthInVertices);
  390. }
  391. };
  392. function subCommit(buffer, offsetInVertices, lengthInVertices) {
  393. if (buffer.needsCommit && buffer.vertexSizeInBytes > 0) {
  394. const byteOffset = buffer.vertexSizeInBytes * offsetInVertices;
  395. const byteLength = buffer.vertexSizeInBytes * lengthInVertices;
  396. // PERFORMANCE_IDEA: If we want to get really crazy, we could consider updating
  397. // individual attributes instead of the entire (sub-)vertex.
  398. //
  399. // PERFORMANCE_IDEA: Does creating the typed view add too much GC overhead?
  400. buffer.vertexBuffer.copyFromArrayView(
  401. new Uint8Array(buffer.arrayBuffer, byteOffset, byteLength),
  402. byteOffset
  403. );
  404. }
  405. }
  406. VertexArrayFacade.prototype.endSubCommits = function () {
  407. const allBuffers = this._allBuffers;
  408. for (let i = 0, len = allBuffers.length; i < len; ++i) {
  409. allBuffers[i].needsCommit = false;
  410. }
  411. };
  412. function destroyVA(vertexArrayFacade) {
  413. const va = vertexArrayFacade.va;
  414. if (!defined(va)) {
  415. return;
  416. }
  417. const length = va.length;
  418. for (let i = 0; i < length; ++i) {
  419. va[i].va.destroy();
  420. }
  421. vertexArrayFacade.va = undefined;
  422. }
  423. VertexArrayFacade.prototype.isDestroyed = function () {
  424. return false;
  425. };
  426. VertexArrayFacade.prototype.destroy = function () {
  427. const allBuffers = this._allBuffers;
  428. for (let i = 0, len = allBuffers.length; i < len; ++i) {
  429. const buffer = allBuffers[i];
  430. buffer.vertexBuffer = buffer.vertexBuffer && buffer.vertexBuffer.destroy();
  431. }
  432. destroyVA(this);
  433. return destroyObject(this);
  434. };
  435. export default VertexArrayFacade;