DracoLoader.js 8.9 KB

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