GltfIndexBufferLoader.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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 deprecationWarning from "../Core/deprecationWarning.js";
  6. import DeveloperError from "../Core/DeveloperError.js";
  7. import IndexDatatype from "../Core/IndexDatatype.js";
  8. import Buffer from "../Renderer/Buffer.js";
  9. import BufferUsage from "../Renderer/BufferUsage.js";
  10. import JobType from "./JobType.js";
  11. import ResourceLoader from "./ResourceLoader.js";
  12. import ResourceLoaderState from "./ResourceLoaderState.js";
  13. /**
  14. * Loads an index buffer from a glTF accessor.
  15. * <p>
  16. * Implements the {@link ResourceLoader} interface.
  17. * </p>
  18. *
  19. * @alias GltfIndexBufferLoader
  20. * @constructor
  21. * @augments ResourceLoader
  22. *
  23. * @param {object} options Object with the following properties:
  24. * @param {ResourceCache} options.resourceCache The {@link ResourceCache} (to avoid circular dependencies).
  25. * @param {object} options.gltf The glTF JSON.
  26. * @param {number} options.accessorId The accessor ID corresponding to the index buffer.
  27. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  28. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  29. * @param {object} [options.draco] The Draco extension object.
  30. * @param {string} [options.cacheKey] The cache key of the resource.
  31. * @param {boolean} [options.asynchronous=true] Determines if WebGL resource creation will be spread out over several frames or block until all WebGL resources are created.
  32. * @param {boolean} [options.loadBuffer=false] Load the index buffer as a GPU index buffer.
  33. * @param {boolean} [options.loadTypedArray=false] Load the index buffer as a typed array.
  34. * @private
  35. */
  36. function GltfIndexBufferLoader(options) {
  37. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  38. const resourceCache = options.resourceCache;
  39. const gltf = options.gltf;
  40. const accessorId = options.accessorId;
  41. const gltfResource = options.gltfResource;
  42. const baseResource = options.baseResource;
  43. const draco = options.draco;
  44. const cacheKey = options.cacheKey;
  45. const asynchronous = defaultValue(options.asynchronous, true);
  46. const loadBuffer = defaultValue(options.loadBuffer, false);
  47. const loadTypedArray = defaultValue(options.loadTypedArray, false);
  48. //>>includeStart('debug', pragmas.debug);
  49. Check.typeOf.func("options.resourceCache", resourceCache);
  50. Check.typeOf.object("options.gltf", gltf);
  51. Check.typeOf.number("options.accessorId", accessorId);
  52. Check.typeOf.object("options.gltfResource", gltfResource);
  53. Check.typeOf.object("options.baseResource", baseResource);
  54. if (!loadBuffer && !loadTypedArray) {
  55. throw new DeveloperError(
  56. "At least one of loadBuffer and loadTypedArray must be true."
  57. );
  58. }
  59. //>>includeEnd('debug');
  60. const indexDatatype = gltf.accessors[accessorId].componentType;
  61. this._resourceCache = resourceCache;
  62. this._gltfResource = gltfResource;
  63. this._baseResource = baseResource;
  64. this._gltf = gltf;
  65. this._accessorId = accessorId;
  66. this._indexDatatype = indexDatatype;
  67. this._draco = draco;
  68. this._cacheKey = cacheKey;
  69. this._asynchronous = asynchronous;
  70. this._loadBuffer = loadBuffer;
  71. this._loadTypedArray = loadTypedArray;
  72. this._bufferViewLoader = undefined;
  73. this._dracoLoader = undefined;
  74. this._typedArray = undefined;
  75. this._buffer = undefined;
  76. this._state = ResourceLoaderState.UNLOADED;
  77. this._promise = undefined;
  78. }
  79. if (defined(Object.create)) {
  80. GltfIndexBufferLoader.prototype = Object.create(ResourceLoader.prototype);
  81. GltfIndexBufferLoader.prototype.constructor = GltfIndexBufferLoader;
  82. }
  83. Object.defineProperties(GltfIndexBufferLoader.prototype, {
  84. /**
  85. * The cache key of the resource.
  86. *
  87. * @memberof GltfIndexBufferLoader.prototype
  88. *
  89. * @type {string}
  90. * @readonly
  91. * @private
  92. */
  93. cacheKey: {
  94. get: function () {
  95. return this._cacheKey;
  96. },
  97. },
  98. /**
  99. * The index buffer. This is only defined when <code>loadBuffer</code> is true.
  100. *
  101. * @memberof GltfIndexBufferLoader.prototype
  102. *
  103. * @type {Buffer}
  104. * @readonly
  105. * @private
  106. */
  107. buffer: {
  108. get: function () {
  109. return this._buffer;
  110. },
  111. },
  112. /**
  113. * The typed array containing indices. This is only defined when <code>loadTypedArray</code> is true.
  114. *
  115. * @memberof GltfIndexBufferLoader.prototype
  116. *
  117. * @type {Uint8Array|Uint16Array|Uint32Array}
  118. * @readonly
  119. * @private
  120. */
  121. typedArray: {
  122. get: function () {
  123. return this._typedArray;
  124. },
  125. },
  126. /**
  127. * The index datatype after decode.
  128. *
  129. * @memberof GltfIndexBufferLoader.prototype
  130. *
  131. * @type {IndexDatatype}
  132. * @readonly
  133. * @private
  134. */
  135. indexDatatype: {
  136. get: function () {
  137. return this._indexDatatype;
  138. },
  139. },
  140. });
  141. const scratchIndexBufferJob = new CreateIndexBufferJob();
  142. /**
  143. * Loads the resource.
  144. * @returns {Promise<GltfIndexBufferLoader>} A promise which resolves to the loader when the resource loading is completed.
  145. * @private
  146. */
  147. GltfIndexBufferLoader.prototype.load = async function () {
  148. if (defined(this._promise)) {
  149. return this._promise;
  150. }
  151. if (defined(this._draco)) {
  152. this._promise = loadFromDraco(this);
  153. return this._promise;
  154. }
  155. this._promise = loadFromBufferView(this);
  156. return this._promise;
  157. };
  158. async function loadFromDraco(indexBufferLoader) {
  159. indexBufferLoader._state = ResourceLoaderState.LOADING;
  160. const resourceCache = indexBufferLoader._resourceCache;
  161. try {
  162. const dracoLoader = resourceCache.getDracoLoader({
  163. gltf: indexBufferLoader._gltf,
  164. draco: indexBufferLoader._draco,
  165. gltfResource: indexBufferLoader._gltfResource,
  166. baseResource: indexBufferLoader._baseResource,
  167. });
  168. indexBufferLoader._dracoLoader = dracoLoader;
  169. await dracoLoader.load();
  170. if (indexBufferLoader.isDestroyed()) {
  171. return;
  172. }
  173. // Now wait for process() to run to finish loading
  174. indexBufferLoader._state = ResourceLoaderState.LOADED;
  175. return indexBufferLoader;
  176. } catch (error) {
  177. if (indexBufferLoader.isDestroyed()) {
  178. return;
  179. }
  180. handleError(indexBufferLoader, error);
  181. }
  182. }
  183. async function loadFromBufferView(indexBufferLoader) {
  184. const gltf = indexBufferLoader._gltf;
  185. const accessorId = indexBufferLoader._accessorId;
  186. const accessor = gltf.accessors[accessorId];
  187. const bufferViewId = accessor.bufferView;
  188. indexBufferLoader._state = ResourceLoaderState.LOADING;
  189. const resourceCache = indexBufferLoader._resourceCache;
  190. try {
  191. const bufferViewLoader = resourceCache.getBufferViewLoader({
  192. gltf: gltf,
  193. bufferViewId: bufferViewId,
  194. gltfResource: indexBufferLoader._gltfResource,
  195. baseResource: indexBufferLoader._baseResource,
  196. });
  197. indexBufferLoader._bufferViewLoader = bufferViewLoader;
  198. await bufferViewLoader.load();
  199. if (indexBufferLoader.isDestroyed()) {
  200. return;
  201. }
  202. const bufferViewTypedArray = bufferViewLoader.typedArray;
  203. indexBufferLoader._typedArray = createIndicesTypedArray(
  204. indexBufferLoader,
  205. bufferViewTypedArray
  206. );
  207. indexBufferLoader._state = ResourceLoaderState.PROCESSING;
  208. return indexBufferLoader;
  209. } catch (error) {
  210. if (indexBufferLoader.isDestroyed()) {
  211. return;
  212. }
  213. handleError(indexBufferLoader, error);
  214. }
  215. }
  216. function createIndicesTypedArray(indexBufferLoader, bufferViewTypedArray) {
  217. const gltf = indexBufferLoader._gltf;
  218. const accessorId = indexBufferLoader._accessorId;
  219. const accessor = gltf.accessors[accessorId];
  220. const count = accessor.count;
  221. const indexDatatype = accessor.componentType;
  222. const indexSize = IndexDatatype.getSizeInBytes(indexDatatype);
  223. let arrayBuffer = bufferViewTypedArray.buffer;
  224. let byteOffset = bufferViewTypedArray.byteOffset + accessor.byteOffset;
  225. if (byteOffset % indexSize !== 0) {
  226. const byteLength = count * indexSize;
  227. const view = new Uint8Array(arrayBuffer, byteOffset, byteLength);
  228. const copy = new Uint8Array(view);
  229. arrayBuffer = copy.buffer;
  230. byteOffset = 0;
  231. deprecationWarning(
  232. "index-buffer-unaligned",
  233. `The index array is not aligned to a ${indexSize}-byte boundary.`
  234. );
  235. }
  236. let typedArray;
  237. if (indexDatatype === IndexDatatype.UNSIGNED_BYTE) {
  238. typedArray = new Uint8Array(arrayBuffer, byteOffset, count);
  239. } else if (indexDatatype === IndexDatatype.UNSIGNED_SHORT) {
  240. typedArray = new Uint16Array(arrayBuffer, byteOffset, count);
  241. } else if (indexDatatype === IndexDatatype.UNSIGNED_INT) {
  242. typedArray = new Uint32Array(arrayBuffer, byteOffset, count);
  243. }
  244. return typedArray;
  245. }
  246. function handleError(indexBufferLoader, error) {
  247. indexBufferLoader.unload();
  248. indexBufferLoader._state = ResourceLoaderState.FAILED;
  249. const errorMessage = "Failed to load index buffer";
  250. throw indexBufferLoader.getError(errorMessage, error);
  251. }
  252. function CreateIndexBufferJob() {
  253. this.typedArray = undefined;
  254. this.indexDatatype = undefined;
  255. this.context = undefined;
  256. this.buffer = undefined;
  257. }
  258. CreateIndexBufferJob.prototype.set = function (
  259. typedArray,
  260. indexDatatype,
  261. context
  262. ) {
  263. this.typedArray = typedArray;
  264. this.indexDatatype = indexDatatype;
  265. this.context = context;
  266. };
  267. CreateIndexBufferJob.prototype.execute = function () {
  268. this.buffer = createIndexBuffer(
  269. this.typedArray,
  270. this.indexDatatype,
  271. this.context
  272. );
  273. };
  274. function createIndexBuffer(typedArray, indexDatatype, context) {
  275. const buffer = Buffer.createIndexBuffer({
  276. typedArray: typedArray,
  277. context: context,
  278. usage: BufferUsage.STATIC_DRAW,
  279. indexDatatype: indexDatatype,
  280. });
  281. buffer.vertexArrayDestroyable = false;
  282. return buffer;
  283. }
  284. /**
  285. * Processes the resource until it becomes ready.
  286. *
  287. * @param {FrameState} frameState The frame state.
  288. * @private
  289. */
  290. GltfIndexBufferLoader.prototype.process = function (frameState) {
  291. //>>includeStart('debug', pragmas.debug);
  292. Check.typeOf.object("frameState", frameState);
  293. //>>includeEnd('debug');
  294. if (this._state === ResourceLoaderState.READY) {
  295. return true;
  296. }
  297. if (
  298. this._state !== ResourceLoaderState.LOADED &&
  299. this._state !== ResourceLoaderState.PROCESSING
  300. ) {
  301. return false;
  302. }
  303. let typedArray = this._typedArray;
  304. let indexDatatype = this._indexDatatype;
  305. if (defined(this._dracoLoader)) {
  306. try {
  307. const ready = this._dracoLoader.process(frameState);
  308. if (ready) {
  309. const dracoLoader = this._dracoLoader;
  310. typedArray = dracoLoader.decodedData.indices.typedArray;
  311. this._typedArray = typedArray;
  312. // The index datatype may be a smaller datatype after draco decode
  313. indexDatatype = ComponentDatatype.fromTypedArray(typedArray);
  314. this._indexDatatype = indexDatatype;
  315. }
  316. } catch (error) {
  317. handleError(this, error);
  318. }
  319. }
  320. if (!defined(typedArray)) {
  321. // Buffer view hasn't been loaded yet
  322. return false;
  323. }
  324. let buffer;
  325. if (this._loadBuffer && this._asynchronous) {
  326. const indexBufferJob = scratchIndexBufferJob;
  327. indexBufferJob.set(typedArray, indexDatatype, frameState.context);
  328. const jobScheduler = frameState.jobScheduler;
  329. if (!jobScheduler.execute(indexBufferJob, JobType.BUFFER)) {
  330. // Job scheduler is full. Try again next frame.
  331. return false;
  332. }
  333. buffer = indexBufferJob.buffer;
  334. } else if (this._loadBuffer) {
  335. buffer = createIndexBuffer(typedArray, indexDatatype, frameState.context);
  336. }
  337. // Unload everything except the index buffer and/or typed array.
  338. this.unload();
  339. this._buffer = buffer;
  340. this._typedArray = this._loadTypedArray ? typedArray : undefined;
  341. this._state = ResourceLoaderState.READY;
  342. this._resourceCache.statistics.addGeometryLoader(this);
  343. return true;
  344. };
  345. /**
  346. * Unloads the resource.
  347. * @private
  348. */
  349. GltfIndexBufferLoader.prototype.unload = function () {
  350. if (defined(this._buffer)) {
  351. this._buffer.destroy();
  352. }
  353. const resourceCache = this._resourceCache;
  354. if (
  355. defined(this._bufferViewLoader) &&
  356. !this._bufferViewLoader.isDestroyed()
  357. ) {
  358. resourceCache.unload(this._bufferViewLoader);
  359. }
  360. if (defined(this._dracoLoader)) {
  361. resourceCache.unload(this._dracoLoader);
  362. }
  363. this._bufferViewLoader = undefined;
  364. this._dracoLoader = undefined;
  365. this._typedArray = undefined;
  366. this._buffer = undefined;
  367. this._gltf = undefined;
  368. };
  369. export default GltfIndexBufferLoader;