GltfVertexBufferLoader.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. import Check from "../Core/Check.js";
  2. import defaultValue from "../Core/defaultValue.js";
  3. import defined from "../Core/defined.js";
  4. import DeveloperError from "../Core/DeveloperError.js";
  5. import Buffer from "../Renderer/Buffer.js";
  6. import BufferUsage from "../Renderer/BufferUsage.js";
  7. import AttributeType from "./AttributeType.js";
  8. import JobType from "./JobType.js";
  9. import ModelComponents from "./ModelComponents.js";
  10. import ResourceLoader from "./ResourceLoader.js";
  11. import ResourceLoaderState from "./ResourceLoaderState.js";
  12. /**
  13. * Loads a vertex buffer from a glTF buffer view.
  14. * <p>
  15. * Implements the {@link ResourceLoader} interface.
  16. * </p>
  17. *
  18. * @alias GltfVertexBufferLoader
  19. * @constructor
  20. * @augments ResourceLoader
  21. *
  22. * @param {object} options Object with the following properties:
  23. * @param {ResourceCache} options.resourceCache The {@link ResourceCache} (to avoid circular dependencies).
  24. * @param {object} options.gltf The glTF JSON.
  25. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  26. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  27. * @param {number} [options.bufferViewId] The bufferView ID corresponding to the vertex buffer.
  28. * @param {object} [options.draco] The Draco extension object.
  29. * @param {string} [options.attributeSemantic] The attribute semantic, e.g. POSITION or NORMAL.
  30. * @param {number} [options.accessorId] The accessor id.
  31. * @param {string} [options.cacheKey] The cache key of the resource.
  32. * @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.
  33. * @param {boolean} [options.loadBuffer=false] Load vertex buffer as a GPU vertex buffer.
  34. * @param {boolean} [options.loadTypedArray=false] Load vertex buffer as a typed array.
  35. *
  36. * @exception {DeveloperError} One of options.bufferViewId and options.draco must be defined.
  37. * @exception {DeveloperError} When options.draco is defined options.attributeSemantic must also be defined.
  38. * @exception {DeveloperError} When options.draco is defined options.accessorId must also be defined.
  39. *
  40. * @private
  41. */
  42. function GltfVertexBufferLoader(options) {
  43. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  44. const resourceCache = options.resourceCache;
  45. const gltf = options.gltf;
  46. const gltfResource = options.gltfResource;
  47. const baseResource = options.baseResource;
  48. const bufferViewId = options.bufferViewId;
  49. const draco = options.draco;
  50. const attributeSemantic = options.attributeSemantic;
  51. const accessorId = options.accessorId;
  52. const cacheKey = options.cacheKey;
  53. const asynchronous = defaultValue(options.asynchronous, true);
  54. const loadBuffer = defaultValue(options.loadBuffer, false);
  55. const loadTypedArray = defaultValue(options.loadTypedArray, false);
  56. //>>includeStart('debug', pragmas.debug);
  57. Check.typeOf.func("options.resourceCache", resourceCache);
  58. Check.typeOf.object("options.gltf", gltf);
  59. Check.typeOf.object("options.gltfResource", gltfResource);
  60. Check.typeOf.object("options.baseResource", baseResource);
  61. if (!loadBuffer && !loadTypedArray) {
  62. throw new DeveloperError(
  63. "At least one of loadBuffer and loadTypedArray must be true."
  64. );
  65. }
  66. const hasBufferViewId = defined(bufferViewId);
  67. const hasDraco = hasDracoCompression(draco, attributeSemantic);
  68. const hasAttributeSemantic = defined(attributeSemantic);
  69. const hasAccessorId = defined(accessorId);
  70. if (hasBufferViewId === hasDraco) {
  71. throw new DeveloperError(
  72. "One of options.bufferViewId and options.draco must be defined."
  73. );
  74. }
  75. if (hasDraco && !hasAttributeSemantic) {
  76. throw new DeveloperError(
  77. "When options.draco is defined options.attributeSemantic must also be defined."
  78. );
  79. }
  80. if (hasDraco && !hasAccessorId) {
  81. throw new DeveloperError(
  82. "When options.draco is defined options.accessorId must also be defined."
  83. );
  84. }
  85. if (hasDraco) {
  86. Check.typeOf.object("options.draco", draco);
  87. Check.typeOf.string("options.attributeSemantic", attributeSemantic);
  88. Check.typeOf.number("options.accessorId", accessorId);
  89. }
  90. //>>includeEnd('debug');
  91. this._resourceCache = resourceCache;
  92. this._gltfResource = gltfResource;
  93. this._baseResource = baseResource;
  94. this._gltf = gltf;
  95. this._bufferViewId = bufferViewId;
  96. this._draco = draco;
  97. this._attributeSemantic = attributeSemantic;
  98. this._accessorId = accessorId;
  99. this._cacheKey = cacheKey;
  100. this._asynchronous = asynchronous;
  101. this._loadBuffer = loadBuffer;
  102. this._loadTypedArray = loadTypedArray;
  103. this._bufferViewLoader = undefined;
  104. this._dracoLoader = undefined;
  105. this._quantization = undefined;
  106. this._typedArray = undefined;
  107. this._buffer = undefined;
  108. this._state = ResourceLoaderState.UNLOADED;
  109. this._promise = undefined;
  110. }
  111. if (defined(Object.create)) {
  112. GltfVertexBufferLoader.prototype = Object.create(ResourceLoader.prototype);
  113. GltfVertexBufferLoader.prototype.constructor = GltfVertexBufferLoader;
  114. }
  115. Object.defineProperties(GltfVertexBufferLoader.prototype, {
  116. /**
  117. * The cache key of the resource.
  118. *
  119. * @memberof GltfVertexBufferLoader.prototype
  120. *
  121. * @type {string}
  122. * @readonly
  123. * @private
  124. */
  125. cacheKey: {
  126. get: function () {
  127. return this._cacheKey;
  128. },
  129. },
  130. /**
  131. * The vertex buffer. This is only defined when <code>loadAsTypedArray</code> is false.
  132. *
  133. * @memberof GltfVertexBufferLoader.prototype
  134. *
  135. * @type {Buffer}
  136. * @readonly
  137. * @private
  138. */
  139. buffer: {
  140. get: function () {
  141. return this._buffer;
  142. },
  143. },
  144. /**
  145. * The typed array containing vertex buffer data. This is only defined when <code>loadAsTypedArray</code> is true.
  146. *
  147. * @memberof GltfVertexBufferLoader.prototype
  148. *
  149. * @type {Uint8Array}
  150. * @readonly
  151. * @private
  152. */
  153. typedArray: {
  154. get: function () {
  155. return this._typedArray;
  156. },
  157. },
  158. /**
  159. * Information about the quantized vertex attribute after Draco decode.
  160. *
  161. * @memberof GltfVertexBufferLoader.prototype
  162. *
  163. * @type {ModelComponents.Quantization}
  164. * @readonly
  165. * @private
  166. */
  167. quantization: {
  168. get: function () {
  169. return this._quantization;
  170. },
  171. },
  172. });
  173. function hasDracoCompression(draco, semantic) {
  174. return (
  175. defined(draco) &&
  176. defined(draco.attributes) &&
  177. defined(draco.attributes[semantic])
  178. );
  179. }
  180. /**
  181. * Loads the resource.
  182. * @returns {Promise<GltfVertexBufferLoader>} A promise which resolves to the loader when the resource loading is completed.
  183. * @private
  184. */
  185. GltfVertexBufferLoader.prototype.load = async function () {
  186. if (defined(this._promise)) {
  187. return this._promise;
  188. }
  189. if (hasDracoCompression(this._draco, this._attributeSemantic)) {
  190. this._promise = loadFromDraco(this);
  191. return this._promise;
  192. }
  193. this._promise = loadFromBufferView(this);
  194. return this._promise;
  195. };
  196. function getQuantizationInformation(
  197. dracoQuantization,
  198. componentDatatype,
  199. componentCount,
  200. type
  201. ) {
  202. const quantizationBits = dracoQuantization.quantizationBits;
  203. const normalizationRange = (1 << quantizationBits) - 1;
  204. const normalizationDivisor = 1.0 / normalizationRange;
  205. const quantization = new ModelComponents.Quantization();
  206. quantization.componentDatatype = componentDatatype;
  207. quantization.octEncoded = dracoQuantization.octEncoded;
  208. quantization.octEncodedZXY = true;
  209. quantization.type = type;
  210. if (quantization.octEncoded) {
  211. quantization.type = AttributeType.VEC2;
  212. quantization.normalizationRange = normalizationRange;
  213. } else {
  214. const MathType = AttributeType.getMathType(type);
  215. if (MathType === Number) {
  216. const dimensions = dracoQuantization.range;
  217. quantization.quantizedVolumeOffset = dracoQuantization.minValues[0];
  218. quantization.quantizedVolumeDimensions = dimensions;
  219. quantization.normalizationRange = normalizationRange;
  220. quantization.quantizedVolumeStepSize = dimensions * normalizationDivisor;
  221. } else {
  222. quantization.quantizedVolumeOffset = MathType.unpack(
  223. dracoQuantization.minValues
  224. );
  225. quantization.normalizationRange = MathType.unpack(
  226. new Array(componentCount).fill(normalizationRange)
  227. );
  228. const packedDimensions = new Array(componentCount).fill(
  229. dracoQuantization.range
  230. );
  231. quantization.quantizedVolumeDimensions = MathType.unpack(
  232. packedDimensions
  233. );
  234. // Computing the step size
  235. const packedSteps = packedDimensions.map(function (dimension) {
  236. return dimension * normalizationDivisor;
  237. });
  238. quantization.quantizedVolumeStepSize = MathType.unpack(packedSteps);
  239. }
  240. }
  241. return quantization;
  242. }
  243. async function loadFromDraco(vertexBufferLoader) {
  244. vertexBufferLoader._state = ResourceLoaderState.LOADING;
  245. const resourceCache = vertexBufferLoader._resourceCache;
  246. try {
  247. const dracoLoader = resourceCache.getDracoLoader({
  248. gltf: vertexBufferLoader._gltf,
  249. draco: vertexBufferLoader._draco,
  250. gltfResource: vertexBufferLoader._gltfResource,
  251. baseResource: vertexBufferLoader._baseResource,
  252. });
  253. vertexBufferLoader._dracoLoader = dracoLoader;
  254. await dracoLoader.load();
  255. if (vertexBufferLoader.isDestroyed()) {
  256. return;
  257. }
  258. // Now wait for process() to run to finish loading
  259. vertexBufferLoader._state = ResourceLoaderState.LOADED;
  260. return vertexBufferLoader;
  261. } catch {
  262. if (vertexBufferLoader.isDestroyed()) {
  263. return;
  264. }
  265. handleError(vertexBufferLoader);
  266. }
  267. }
  268. function processDraco(vertexBufferLoader) {
  269. vertexBufferLoader._state = ResourceLoaderState.PROCESSING;
  270. const dracoLoader = vertexBufferLoader._dracoLoader;
  271. // Get the typed array and quantization information
  272. const decodedVertexAttributes = dracoLoader.decodedData.vertexAttributes;
  273. const attributeSemantic = vertexBufferLoader._attributeSemantic;
  274. const dracoAttribute = decodedVertexAttributes[attributeSemantic];
  275. const accessorId = vertexBufferLoader._accessorId;
  276. const accessor = vertexBufferLoader._gltf.accessors[accessorId];
  277. const type = accessor.type;
  278. const typedArray = dracoAttribute.array;
  279. const dracoQuantization = dracoAttribute.data.quantization;
  280. if (defined(dracoQuantization)) {
  281. vertexBufferLoader._quantization = getQuantizationInformation(
  282. dracoQuantization,
  283. dracoAttribute.data.componentDatatype,
  284. dracoAttribute.data.componentsPerAttribute,
  285. type
  286. );
  287. }
  288. vertexBufferLoader._typedArray = new Uint8Array(
  289. typedArray.buffer,
  290. typedArray.byteOffset,
  291. typedArray.byteLength
  292. );
  293. }
  294. async function loadFromBufferView(vertexBufferLoader) {
  295. vertexBufferLoader._state = ResourceLoaderState.LOADING;
  296. const resourceCache = vertexBufferLoader._resourceCache;
  297. try {
  298. const bufferViewLoader = resourceCache.getBufferViewLoader({
  299. gltf: vertexBufferLoader._gltf,
  300. bufferViewId: vertexBufferLoader._bufferViewId,
  301. gltfResource: vertexBufferLoader._gltfResource,
  302. baseResource: vertexBufferLoader._baseResource,
  303. });
  304. vertexBufferLoader._bufferViewLoader = bufferViewLoader;
  305. await bufferViewLoader.load();
  306. if (vertexBufferLoader.isDestroyed()) {
  307. return;
  308. }
  309. vertexBufferLoader._typedArray = bufferViewLoader.typedArray;
  310. vertexBufferLoader._state = ResourceLoaderState.PROCESSING;
  311. return vertexBufferLoader;
  312. } catch (error) {
  313. if (vertexBufferLoader.isDestroyed()) {
  314. return;
  315. }
  316. handleError(vertexBufferLoader, error);
  317. }
  318. }
  319. function handleError(vertexBufferLoader, error) {
  320. vertexBufferLoader.unload();
  321. vertexBufferLoader._state = ResourceLoaderState.FAILED;
  322. const errorMessage = "Failed to load vertex buffer";
  323. throw vertexBufferLoader.getError(errorMessage, error);
  324. }
  325. function CreateVertexBufferJob() {
  326. this.typedArray = undefined;
  327. this.context = undefined;
  328. this.buffer = undefined;
  329. }
  330. CreateVertexBufferJob.prototype.set = function (typedArray, context) {
  331. this.typedArray = typedArray;
  332. this.context = context;
  333. };
  334. CreateVertexBufferJob.prototype.execute = function () {
  335. this.buffer = createVertexBuffer(this.typedArray, this.context);
  336. };
  337. function createVertexBuffer(typedArray, context) {
  338. const buffer = Buffer.createVertexBuffer({
  339. typedArray: typedArray,
  340. context: context,
  341. usage: BufferUsage.STATIC_DRAW,
  342. });
  343. buffer.vertexArrayDestroyable = false;
  344. return buffer;
  345. }
  346. const scratchVertexBufferJob = new CreateVertexBufferJob();
  347. /**
  348. * Processes the resource until it becomes ready.
  349. *
  350. * @param {FrameState} frameState The frame state.
  351. * @private
  352. */
  353. GltfVertexBufferLoader.prototype.process = function (frameState) {
  354. //>>includeStart('debug', pragmas.debug);
  355. Check.typeOf.object("frameState", frameState);
  356. //>>includeEnd('debug');
  357. if (this._state === ResourceLoaderState.READY) {
  358. return true;
  359. }
  360. if (
  361. this._state !== ResourceLoaderState.LOADED &&
  362. this._state !== ResourceLoaderState.PROCESSING
  363. ) {
  364. return false;
  365. }
  366. if (defined(this._dracoLoader)) {
  367. try {
  368. const ready = this._dracoLoader.process(frameState);
  369. if (!ready) {
  370. return false;
  371. }
  372. } catch (error) {
  373. handleError(this, error);
  374. }
  375. processDraco(this);
  376. }
  377. let buffer;
  378. const typedArray = this._typedArray;
  379. if (this._loadBuffer && this._asynchronous) {
  380. const vertexBufferJob = scratchVertexBufferJob;
  381. vertexBufferJob.set(typedArray, frameState.context);
  382. const jobScheduler = frameState.jobScheduler;
  383. if (!jobScheduler.execute(vertexBufferJob, JobType.BUFFER)) {
  384. // Job scheduler is full. Try again next frame.
  385. return false;
  386. }
  387. buffer = vertexBufferJob.buffer;
  388. } else if (this._loadBuffer) {
  389. buffer = createVertexBuffer(typedArray, frameState.context);
  390. }
  391. // Unload everything except the vertex buffer
  392. this.unload();
  393. this._buffer = buffer;
  394. this._typedArray = this._loadTypedArray ? typedArray : undefined;
  395. this._state = ResourceLoaderState.READY;
  396. this._resourceCache.statistics.addGeometryLoader(this);
  397. return true;
  398. };
  399. /**
  400. * Unloads the resource.
  401. * @private
  402. */
  403. GltfVertexBufferLoader.prototype.unload = function () {
  404. if (defined(this._buffer)) {
  405. this._buffer.destroy();
  406. }
  407. const resourceCache = this._resourceCache;
  408. if (
  409. defined(this._bufferViewLoader) &&
  410. !this._bufferViewLoader.isDestroyed()
  411. ) {
  412. resourceCache.unload(this._bufferViewLoader);
  413. }
  414. if (defined(this._dracoLoader)) {
  415. resourceCache.unload(this._dracoLoader);
  416. }
  417. this._bufferViewLoader = undefined;
  418. this._dracoLoader = undefined;
  419. this._typedArray = undefined;
  420. this._buffer = undefined;
  421. this._gltf = undefined;
  422. };
  423. export default GltfVertexBufferLoader;