DracoLoader.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import arraySlice from "../Core/arraySlice.js";
  2. import ComponentDatatype from "../Core/ComponentDatatype.js";
  3. import defined from "../Core/defined.js";
  4. import FeatureDetection from "../Core/FeatureDetection.js";
  5. import TaskProcessor from "../Core/TaskProcessor.js";
  6. import ForEach from "./GltfPipeline/ForEach.js";
  7. /**
  8. * @private
  9. */
  10. function DracoLoader() {}
  11. // Maximum concurrency to use when decoding draco models
  12. DracoLoader._maxDecodingConcurrency = Math.max(
  13. FeatureDetection.hardwareConcurrency - 1,
  14. 1
  15. );
  16. // Exposed for testing purposes
  17. DracoLoader._decoderTaskProcessor = undefined;
  18. DracoLoader._taskProcessorReady = false;
  19. DracoLoader._getDecoderTaskProcessor = function () {
  20. if (!defined(DracoLoader._decoderTaskProcessor)) {
  21. const processor = new TaskProcessor(
  22. "decodeDraco",
  23. DracoLoader._maxDecodingConcurrency
  24. );
  25. processor
  26. .initWebAssemblyModule({
  27. modulePath: "ThirdParty/Workers/draco_decoder_nodejs.js",
  28. wasmBinaryFile: "ThirdParty/draco_decoder.wasm",
  29. })
  30. .then(function () {
  31. DracoLoader._taskProcessorReady = true;
  32. });
  33. DracoLoader._decoderTaskProcessor = processor;
  34. }
  35. return DracoLoader._decoderTaskProcessor;
  36. };
  37. /**
  38. * Returns true if the model uses or requires KHR_draco_mesh_compression.
  39. *
  40. * @private
  41. */
  42. DracoLoader.hasExtension = function (model) {
  43. return (
  44. defined(model.extensionsRequired.KHR_draco_mesh_compression) ||
  45. defined(model.extensionsUsed.KHR_draco_mesh_compression)
  46. );
  47. };
  48. function addBufferToLoadResources(loadResources, typedArray) {
  49. // Create a new id to differentiate from original glTF bufferViews
  50. const bufferViewId = `runtime.${
  51. Object.keys(loadResources.createdBufferViews).length
  52. }`;
  53. const loadResourceBuffers = loadResources.buffers;
  54. const id = Object.keys(loadResourceBuffers).length;
  55. loadResourceBuffers[id] = typedArray;
  56. loadResources.createdBufferViews[bufferViewId] = {
  57. buffer: id,
  58. byteOffset: 0,
  59. byteLength: typedArray.byteLength,
  60. };
  61. return bufferViewId;
  62. }
  63. function addNewVertexBuffer(typedArray, model, context) {
  64. const loadResources = model._loadResources;
  65. const id = addBufferToLoadResources(loadResources, typedArray);
  66. loadResources.vertexBuffersToCreate.enqueue(id);
  67. return id;
  68. }
  69. function addNewIndexBuffer(indexArray, model, context) {
  70. const typedArray = indexArray.typedArray;
  71. const loadResources = model._loadResources;
  72. const id = addBufferToLoadResources(loadResources, typedArray);
  73. loadResources.indexBuffersToCreate.enqueue({
  74. id: id,
  75. componentType: ComponentDatatype.fromTypedArray(typedArray),
  76. });
  77. return {
  78. bufferViewId: id,
  79. numberOfIndices: indexArray.numberOfIndices,
  80. };
  81. }
  82. function scheduleDecodingTask(
  83. decoderTaskProcessor,
  84. model,
  85. loadResources,
  86. context
  87. ) {
  88. if (!DracoLoader._taskProcessorReady) {
  89. // The task processor is not ready to schedule tasks
  90. return;
  91. }
  92. const taskData = loadResources.primitivesToDecode.peek();
  93. if (!defined(taskData)) {
  94. // All primitives are processing
  95. return;
  96. }
  97. const promise = decoderTaskProcessor.scheduleTask(taskData, [
  98. taskData.array.buffer,
  99. ]);
  100. if (!defined(promise)) {
  101. // Cannot schedule another task this frame
  102. return;
  103. }
  104. loadResources.activeDecodingTasks++;
  105. loadResources.primitivesToDecode.dequeue();
  106. return promise.then(function (result) {
  107. loadResources.activeDecodingTasks--;
  108. const decodedIndexBuffer = addNewIndexBuffer(
  109. result.indexArray,
  110. model,
  111. context
  112. );
  113. const attributes = {};
  114. const decodedAttributeData = result.attributeData;
  115. for (const attributeName in decodedAttributeData) {
  116. if (decodedAttributeData.hasOwnProperty(attributeName)) {
  117. const attribute = decodedAttributeData[attributeName];
  118. const vertexArray = attribute.array;
  119. const vertexBufferView = addNewVertexBuffer(
  120. vertexArray,
  121. model,
  122. context
  123. );
  124. const data = attribute.data;
  125. data.bufferView = vertexBufferView;
  126. attributes[attributeName] = data;
  127. }
  128. }
  129. model._decodedData[`${taskData.mesh}.primitive.${taskData.primitive}`] = {
  130. bufferView: decodedIndexBuffer.bufferViewId,
  131. numberOfIndices: decodedIndexBuffer.numberOfIndices,
  132. attributes: attributes,
  133. };
  134. });
  135. }
  136. DracoLoader._decodedModelResourceCache = undefined;
  137. /**
  138. * Parses draco extension on model primitives and
  139. * adds the decoding data to the model's load resources.
  140. *
  141. * @private
  142. */
  143. DracoLoader.parse = function (model, context) {
  144. if (!DracoLoader.hasExtension(model)) {
  145. return;
  146. }
  147. const loadResources = model._loadResources;
  148. const cacheKey = model.cacheKey;
  149. if (defined(cacheKey)) {
  150. if (!defined(DracoLoader._decodedModelResourceCache)) {
  151. if (!defined(context.cache.modelDecodingCache)) {
  152. context.cache.modelDecodingCache = {};
  153. }
  154. DracoLoader._decodedModelResourceCache = context.cache.modelDecodingCache;
  155. }
  156. // Decoded data for model will be loaded from cache
  157. const cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
  158. if (defined(cachedData)) {
  159. cachedData.count++;
  160. loadResources.pendingDecodingCache = true;
  161. return;
  162. }
  163. }
  164. const dequantizeInShader = model._dequantizeInShader;
  165. const gltf = model.gltf;
  166. ForEach.mesh(gltf, function (mesh, meshId) {
  167. ForEach.meshPrimitive(mesh, function (primitive, primitiveId) {
  168. if (!defined(primitive.extensions)) {
  169. return;
  170. }
  171. const compressionData = primitive.extensions.KHR_draco_mesh_compression;
  172. if (!defined(compressionData)) {
  173. return;
  174. }
  175. const bufferView = gltf.bufferViews[compressionData.bufferView];
  176. const typedArray = arraySlice(
  177. gltf.buffers[bufferView.buffer].extras._pipeline.source,
  178. bufferView.byteOffset,
  179. bufferView.byteOffset + bufferView.byteLength
  180. );
  181. loadResources.primitivesToDecode.enqueue({
  182. mesh: meshId,
  183. primitive: primitiveId,
  184. array: typedArray,
  185. bufferView: bufferView,
  186. compressedAttributes: compressionData.attributes,
  187. dequantizeInShader: dequantizeInShader,
  188. });
  189. });
  190. });
  191. };
  192. /**
  193. * Schedules decoding tasks available this frame.
  194. * @private
  195. */
  196. DracoLoader.decodeModel = function (model, context) {
  197. if (!DracoLoader.hasExtension(model)) {
  198. return Promise.resolve();
  199. }
  200. const loadResources = model._loadResources;
  201. const cacheKey = model.cacheKey;
  202. if (defined(cacheKey) && defined(DracoLoader._decodedModelResourceCache)) {
  203. const cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
  204. // Load decoded data for model when cache is ready
  205. if (defined(cachedData) && loadResources.pendingDecodingCache) {
  206. return Promise.resolve(cachedData.ready).then(function () {
  207. model._decodedData = cachedData.data;
  208. loadResources.pendingDecodingCache = false;
  209. });
  210. }
  211. // Decoded data for model should be cached when ready
  212. DracoLoader._decodedModelResourceCache[cacheKey] = {
  213. ready: false,
  214. count: 1,
  215. data: undefined,
  216. };
  217. }
  218. if (loadResources.primitivesToDecode.length === 0) {
  219. // No more tasks to schedule
  220. return Promise.resolve();
  221. }
  222. const decoderTaskProcessor = DracoLoader._getDecoderTaskProcessor();
  223. const decodingPromises = [];
  224. let promise = scheduleDecodingTask(
  225. decoderTaskProcessor,
  226. model,
  227. loadResources,
  228. context
  229. );
  230. while (defined(promise)) {
  231. decodingPromises.push(promise);
  232. promise = scheduleDecodingTask(
  233. decoderTaskProcessor,
  234. model,
  235. loadResources,
  236. context
  237. );
  238. }
  239. return Promise.all(decodingPromises);
  240. };
  241. /**
  242. * Decodes a compressed point cloud. Returns undefined if the task cannot be scheduled.
  243. * @private
  244. */
  245. DracoLoader.decodePointCloud = function (parameters) {
  246. const decoderTaskProcessor = DracoLoader._getDecoderTaskProcessor();
  247. if (!DracoLoader._taskProcessorReady) {
  248. // The task processor is not ready to schedule tasks
  249. return;
  250. }
  251. return decoderTaskProcessor.scheduleTask(parameters, [
  252. parameters.buffer.buffer,
  253. ]);
  254. };
  255. /**
  256. * Decodes a buffer view. Returns undefined if the task cannot be scheduled.
  257. *
  258. * @param {Object} options Object with the following properties:
  259. * @param {Uint8Array} options.array The typed array containing the buffer view data.
  260. * @param {Object} options.bufferView The glTF buffer view object.
  261. * @param {Object.<String, Number>} options.compressedAttributes The compressed attributes.
  262. * @param {Boolean} options.dequantizeInShader Whether POSITION and NORMAL attributes should be dequantized on the GPU.
  263. *
  264. * @returns {Promise} A promise that resolves to the decoded indices and attributes.
  265. * @private
  266. */
  267. DracoLoader.decodeBufferView = function (options) {
  268. const decoderTaskProcessor = DracoLoader._getDecoderTaskProcessor();
  269. if (!DracoLoader._taskProcessorReady) {
  270. // The task processor is not ready to schedule tasks
  271. return;
  272. }
  273. return decoderTaskProcessor.scheduleTask(options, [options.array.buffer]);
  274. };
  275. /**
  276. * Caches a models decoded data so it doesn't need to decode more than once.
  277. * @private
  278. */
  279. DracoLoader.cacheDataForModel = function (model) {
  280. const cacheKey = model.cacheKey;
  281. if (defined(cacheKey) && defined(DracoLoader._decodedModelResourceCache)) {
  282. const cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
  283. if (defined(cachedData)) {
  284. cachedData.ready = true;
  285. cachedData.data = model._decodedData;
  286. }
  287. }
  288. };
  289. /**
  290. * Destroys the cached data that this model references if it is no longer in use.
  291. * @private
  292. */
  293. DracoLoader.destroyCachedDataForModel = function (model) {
  294. const cacheKey = model.cacheKey;
  295. if (defined(cacheKey) && defined(DracoLoader._decodedModelResourceCache)) {
  296. const cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
  297. if (defined(cachedData) && --cachedData.count === 0) {
  298. delete DracoLoader._decodedModelResourceCache[cacheKey];
  299. }
  300. }
  301. };
  302. export default DracoLoader;