ResourceCacheKey.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  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 getAbsoluteUri from "../Core/getAbsoluteUri.js";
  6. import GltfLoaderUtil from "./GltfLoaderUtil.js";
  7. import hasExtension from "./hasExtension.js";
  8. /**
  9. * Compute cache keys for resources in {@link ResourceCache}.
  10. *
  11. * @namespace ResourceCacheKey
  12. *
  13. * @private
  14. */
  15. const ResourceCacheKey = {};
  16. function getExternalResourceCacheKey(resource) {
  17. return getAbsoluteUri(resource.url);
  18. }
  19. function getBufferViewCacheKey(bufferView) {
  20. let byteOffset = bufferView.byteOffset;
  21. let byteLength = bufferView.byteLength;
  22. if (hasExtension(bufferView, "EXT_meshopt_compression")) {
  23. const meshopt = bufferView.extensions.EXT_meshopt_compression;
  24. byteOffset = defaultValue(meshopt.byteOffset, 0);
  25. byteLength = meshopt.byteLength;
  26. }
  27. return `${byteOffset}-${byteOffset + byteLength}`;
  28. }
  29. function getAccessorCacheKey(accessor, bufferView) {
  30. const byteOffset = bufferView.byteOffset + accessor.byteOffset;
  31. const componentType = accessor.componentType;
  32. const type = accessor.type;
  33. const count = accessor.count;
  34. return `${byteOffset}-${componentType}-${type}-${count}`;
  35. }
  36. function getExternalBufferCacheKey(resource) {
  37. return getExternalResourceCacheKey(resource);
  38. }
  39. function getEmbeddedBufferCacheKey(parentResource, bufferId) {
  40. const parentCacheKey = getExternalResourceCacheKey(parentResource);
  41. return `${parentCacheKey}-buffer-id-${bufferId}`;
  42. }
  43. function getBufferCacheKey(buffer, bufferId, gltfResource, baseResource) {
  44. if (defined(buffer.uri)) {
  45. const resource = baseResource.getDerivedResource({
  46. url: buffer.uri,
  47. });
  48. return getExternalBufferCacheKey(resource);
  49. }
  50. return getEmbeddedBufferCacheKey(gltfResource, bufferId);
  51. }
  52. function getDracoCacheKey(gltf, draco, gltfResource, baseResource) {
  53. const bufferViewId = draco.bufferView;
  54. const bufferView = gltf.bufferViews[bufferViewId];
  55. const bufferId = bufferView.buffer;
  56. const buffer = gltf.buffers[bufferId];
  57. const bufferCacheKey = getBufferCacheKey(
  58. buffer,
  59. bufferId,
  60. gltfResource,
  61. baseResource
  62. );
  63. const bufferViewCacheKey = getBufferViewCacheKey(bufferView);
  64. return `${bufferCacheKey}-range-${bufferViewCacheKey}`;
  65. }
  66. function getImageCacheKey(gltf, imageId, gltfResource, baseResource) {
  67. const image = gltf.images[imageId];
  68. const bufferViewId = image.bufferView;
  69. const uri = image.uri;
  70. if (defined(uri)) {
  71. const resource = baseResource.getDerivedResource({
  72. url: uri,
  73. });
  74. return getExternalResourceCacheKey(resource);
  75. }
  76. const bufferView = gltf.bufferViews[bufferViewId];
  77. const bufferId = bufferView.buffer;
  78. const buffer = gltf.buffers[bufferId];
  79. const bufferCacheKey = getBufferCacheKey(
  80. buffer,
  81. bufferId,
  82. gltfResource,
  83. baseResource
  84. );
  85. const bufferViewCacheKey = getBufferViewCacheKey(bufferView);
  86. return `${bufferCacheKey}-range-${bufferViewCacheKey}`;
  87. }
  88. function getSamplerCacheKey(gltf, textureInfo) {
  89. const sampler = GltfLoaderUtil.createSampler({
  90. gltf: gltf,
  91. textureInfo: textureInfo,
  92. });
  93. return `${sampler.wrapS}-${sampler.wrapT}-${sampler.minificationFilter}-${sampler.magnificationFilter}`;
  94. }
  95. /**
  96. * Gets the schema cache key.
  97. *
  98. * @param {object} options Object with the following properties:
  99. * @param {object} [options.schema] An object that explicitly defines a schema JSON. Mutually exclusive with options.resource.
  100. * @param {Resource} [options.resource] The {@link Resource} pointing to the schema JSON. Mutually exclusive with options.schema.
  101. *
  102. * @returns {string} The schema cache key.
  103. *
  104. * @exception {DeveloperError} One of options.schema and options.resource must be defined.
  105. * @private
  106. */
  107. ResourceCacheKey.getSchemaCacheKey = function (options) {
  108. const schema = options.schema;
  109. const resource = options.resource;
  110. //>>includeStart('debug', pragmas.debug);
  111. if (defined(schema) === defined(resource)) {
  112. throw new DeveloperError(
  113. "One of options.schema and options.resource must be defined."
  114. );
  115. }
  116. //>>includeEnd('debug');
  117. if (defined(schema)) {
  118. return `embedded-schema:${JSON.stringify(schema)}`;
  119. }
  120. return `external-schema:${getExternalResourceCacheKey(resource)}`;
  121. };
  122. /**
  123. * Gets the external buffer cache key.
  124. *
  125. * @param {object} options Object with the following properties:
  126. * @param {Resource} options.resource The {@link Resource} pointing to the external buffer.
  127. *
  128. * @returns {string} The external buffer cache key.
  129. * @private
  130. */
  131. ResourceCacheKey.getExternalBufferCacheKey = function (options) {
  132. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  133. const resource = options.resource;
  134. //>>includeStart('debug', pragmas.debug);
  135. Check.typeOf.object("options.resource", resource);
  136. //>>includeEnd('debug');
  137. return `external-buffer:${getExternalBufferCacheKey(resource)}`;
  138. };
  139. /**
  140. * Gets the embedded buffer cache key.
  141. *
  142. * @param {object} options Object with the following properties:
  143. * @param {Resource} options.parentResource The {@link Resource} containing the embedded buffer.
  144. * @param {number} options.bufferId A unique identifier of the embedded buffer within the parent resource.
  145. *
  146. * @returns {string} The embedded buffer cache key.
  147. * @private
  148. */
  149. ResourceCacheKey.getEmbeddedBufferCacheKey = function (options) {
  150. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  151. const parentResource = options.parentResource;
  152. const bufferId = options.bufferId;
  153. //>>includeStart('debug', pragmas.debug);
  154. Check.typeOf.object("options.parentResource", parentResource);
  155. Check.typeOf.number("options.bufferId", bufferId);
  156. //>>includeEnd('debug');
  157. return `embedded-buffer:${getEmbeddedBufferCacheKey(
  158. parentResource,
  159. bufferId
  160. )}`;
  161. };
  162. /**
  163. * Gets the glTF cache key.
  164. *
  165. * @param {object} options Object with the following properties:
  166. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  167. *
  168. * @returns {string} The glTF cache key.
  169. * @private
  170. */
  171. ResourceCacheKey.getGltfCacheKey = function (options) {
  172. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  173. const gltfResource = options.gltfResource;
  174. //>>includeStart('debug', pragmas.debug);
  175. Check.typeOf.object("options.gltfResource", gltfResource);
  176. //>>includeEnd('debug');
  177. return `gltf:${getExternalResourceCacheKey(gltfResource)}`;
  178. };
  179. /**
  180. * Gets the buffer view cache key.
  181. *
  182. * @param {object} options Object with the following properties:
  183. * @param {object} options.gltf The glTF JSON.
  184. * @param {number} options.bufferViewId The bufferView ID.
  185. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  186. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  187. *
  188. * @returns {string} The buffer view cache key.
  189. * @private
  190. */
  191. ResourceCacheKey.getBufferViewCacheKey = function (options) {
  192. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  193. const gltf = options.gltf;
  194. const bufferViewId = options.bufferViewId;
  195. const gltfResource = options.gltfResource;
  196. const baseResource = options.baseResource;
  197. //>>includeStart('debug', pragmas.debug);
  198. Check.typeOf.object("options.gltf", gltf);
  199. Check.typeOf.number("options.bufferViewId", bufferViewId);
  200. Check.typeOf.object("options.gltfResource", gltfResource);
  201. Check.typeOf.object("options.baseResource", baseResource);
  202. //>>includeEnd('debug');
  203. const bufferView = gltf.bufferViews[bufferViewId];
  204. let bufferId = bufferView.buffer;
  205. const buffer = gltf.buffers[bufferId];
  206. if (hasExtension(bufferView, "EXT_meshopt_compression")) {
  207. const meshopt = bufferView.extensions.EXT_meshopt_compression;
  208. bufferId = meshopt.buffer;
  209. }
  210. const bufferCacheKey = getBufferCacheKey(
  211. buffer,
  212. bufferId,
  213. gltfResource,
  214. baseResource
  215. );
  216. const bufferViewCacheKey = getBufferViewCacheKey(bufferView);
  217. return `buffer-view:${bufferCacheKey}-range-${bufferViewCacheKey}`;
  218. };
  219. /**
  220. * Gets the Draco cache key.
  221. *
  222. * @param {object} options Object with the following properties:
  223. * @param {object} options.gltf The glTF JSON.
  224. * @param {object} options.draco The Draco extension object.
  225. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  226. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  227. *
  228. * @returns {string} The Draco cache key.
  229. * @private
  230. */
  231. ResourceCacheKey.getDracoCacheKey = function (options) {
  232. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  233. const gltf = options.gltf;
  234. const draco = options.draco;
  235. const gltfResource = options.gltfResource;
  236. const baseResource = options.baseResource;
  237. //>>includeStart('debug', pragmas.debug);
  238. Check.typeOf.object("options.gltf", gltf);
  239. Check.typeOf.object("options.draco", draco);
  240. Check.typeOf.object("options.gltfResource", gltfResource);
  241. Check.typeOf.object("options.baseResource", baseResource);
  242. //>>includeEnd('debug');
  243. return `draco:${getDracoCacheKey(gltf, draco, gltfResource, baseResource)}`;
  244. };
  245. /**
  246. * Gets the vertex buffer cache key.
  247. *
  248. * @param {object} options Object with the following properties:
  249. * @param {object} options.gltf The glTF JSON.
  250. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  251. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  252. * @param {FrameState} options.frameState The frame state.
  253. * @param {number} [options.bufferViewId] The bufferView ID corresponding to the vertex buffer.
  254. * @param {object} [options.draco] The Draco extension object.
  255. * @param {string} [options.attributeSemantic] The attribute semantic, e.g. POSITION or NORMAL.
  256. * @param {boolean} [options.dequantize=false] Determines whether or not the vertex buffer will be dequantized on the CPU.
  257. * @param {boolean} [options.loadBuffer=false] Load vertex buffer as a GPU vertex buffer.
  258. * @param {boolean} [options.loadTypedArray=false] Load vertex buffer as a typed array.
  259. * @exception {DeveloperError} One of options.bufferViewId and options.draco must be defined.
  260. * @exception {DeveloperError} When options.draco is defined options.attributeSemantic must also be defined.
  261. *
  262. * @returns {string} The vertex buffer cache key.
  263. * @private
  264. */
  265. ResourceCacheKey.getVertexBufferCacheKey = function (options) {
  266. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  267. const gltf = options.gltf;
  268. const gltfResource = options.gltfResource;
  269. const baseResource = options.baseResource;
  270. const frameState = options.frameState;
  271. const bufferViewId = options.bufferViewId;
  272. const draco = options.draco;
  273. const attributeSemantic = options.attributeSemantic;
  274. const dequantize = defaultValue(options.dequantize, false);
  275. const loadBuffer = defaultValue(options.loadBuffer, false);
  276. const loadTypedArray = defaultValue(options.loadTypedArray, false);
  277. //>>includeStart('debug', pragmas.debug);
  278. Check.typeOf.object("options.gltf", gltf);
  279. Check.typeOf.object("options.gltfResource", gltfResource);
  280. Check.typeOf.object("options.baseResource", baseResource);
  281. Check.typeOf.object("options.frameState", frameState);
  282. const hasBufferViewId = defined(bufferViewId);
  283. const hasDraco = hasDracoCompression(draco, attributeSemantic);
  284. const hasAttributeSemantic = defined(attributeSemantic);
  285. if (hasBufferViewId === hasDraco) {
  286. throw new DeveloperError(
  287. "One of options.bufferViewId and options.draco must be defined."
  288. );
  289. }
  290. if (hasDraco && !hasAttributeSemantic) {
  291. throw new DeveloperError(
  292. "When options.draco is defined options.attributeSemantic must also be defined."
  293. );
  294. }
  295. if (hasDraco) {
  296. Check.typeOf.object("options.draco", draco);
  297. Check.typeOf.string("options.attributeSemantic", attributeSemantic);
  298. }
  299. if (!loadBuffer && !loadTypedArray) {
  300. throw new DeveloperError(
  301. "At least one of loadBuffer and loadTypedArray must be true."
  302. );
  303. }
  304. //>>includeEnd('debug');
  305. let cacheKeySuffix = "";
  306. if (dequantize) {
  307. cacheKeySuffix += "-dequantize";
  308. }
  309. if (loadBuffer) {
  310. cacheKeySuffix += "-buffer";
  311. cacheKeySuffix += `-context-${frameState.context.id}`;
  312. }
  313. if (loadTypedArray) {
  314. cacheKeySuffix += "-typed-array";
  315. }
  316. if (defined(draco)) {
  317. const dracoCacheKey = getDracoCacheKey(
  318. gltf,
  319. draco,
  320. gltfResource,
  321. baseResource
  322. );
  323. return `vertex-buffer:${dracoCacheKey}-draco-${attributeSemantic}${cacheKeySuffix}`;
  324. }
  325. const bufferView = gltf.bufferViews[bufferViewId];
  326. const bufferId = bufferView.buffer;
  327. const buffer = gltf.buffers[bufferId];
  328. const bufferCacheKey = getBufferCacheKey(
  329. buffer,
  330. bufferId,
  331. gltfResource,
  332. baseResource
  333. );
  334. const bufferViewCacheKey = getBufferViewCacheKey(bufferView);
  335. return `vertex-buffer:${bufferCacheKey}-range-${bufferViewCacheKey}${cacheKeySuffix}`;
  336. };
  337. function hasDracoCompression(draco, semantic) {
  338. return (
  339. defined(draco) &&
  340. defined(draco.attributes) &&
  341. defined(draco.attributes[semantic])
  342. );
  343. }
  344. /**
  345. * Gets the index buffer cache key.
  346. *
  347. * @param {object} options Object with the following properties:
  348. * @param {object} options.gltf The glTF JSON.
  349. * @param {number} options.accessorId The accessor ID corresponding to the index buffer.
  350. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  351. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  352. * @param {FrameState} options.frameState The frame state.
  353. * @param {object} [options.draco] The Draco extension object.
  354. * @param {boolean} [options.loadBuffer=false] Load index buffer as a GPU index buffer.
  355. * @param {boolean} [options.loadTypedArray=false] Load index buffer as a typed array.
  356. *
  357. * @returns {string} The index buffer cache key.
  358. * @private
  359. */
  360. ResourceCacheKey.getIndexBufferCacheKey = function (options) {
  361. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  362. const gltf = options.gltf;
  363. const accessorId = options.accessorId;
  364. const gltfResource = options.gltfResource;
  365. const baseResource = options.baseResource;
  366. const frameState = options.frameState;
  367. const draco = options.draco;
  368. const loadBuffer = defaultValue(options.loadBuffer, false);
  369. const loadTypedArray = defaultValue(options.loadTypedArray, false);
  370. //>>includeStart('debug', pragmas.debug);
  371. Check.typeOf.object("options.gltf", gltf);
  372. Check.typeOf.number("options.accessorId", accessorId);
  373. Check.typeOf.object("options.gltfResource", gltfResource);
  374. Check.typeOf.object("options.baseResource", baseResource);
  375. Check.typeOf.object("options.frameState", frameState);
  376. if (!loadBuffer && !loadTypedArray) {
  377. throw new DeveloperError(
  378. "At least one of loadBuffer and loadTypedArray must be true."
  379. );
  380. }
  381. //>>includeEnd('debug');
  382. let cacheKeySuffix = "";
  383. if (loadBuffer) {
  384. cacheKeySuffix += "-buffer";
  385. cacheKeySuffix += `-context-${frameState.context.id}`;
  386. }
  387. if (loadTypedArray) {
  388. cacheKeySuffix += "-typed-array";
  389. }
  390. if (defined(draco)) {
  391. const dracoCacheKey = getDracoCacheKey(
  392. gltf,
  393. draco,
  394. gltfResource,
  395. baseResource
  396. );
  397. return `index-buffer:${dracoCacheKey}-draco${cacheKeySuffix}`;
  398. }
  399. const accessor = gltf.accessors[accessorId];
  400. const bufferViewId = accessor.bufferView;
  401. const bufferView = gltf.bufferViews[bufferViewId];
  402. const bufferId = bufferView.buffer;
  403. const buffer = gltf.buffers[bufferId];
  404. const bufferCacheKey = getBufferCacheKey(
  405. buffer,
  406. bufferId,
  407. gltfResource,
  408. baseResource
  409. );
  410. const accessorCacheKey = getAccessorCacheKey(accessor, bufferView);
  411. return `index-buffer:${bufferCacheKey}-accessor-${accessorCacheKey}${cacheKeySuffix}`;
  412. };
  413. /**
  414. * Gets the image cache key.
  415. *
  416. * @param {object} options Object with the following properties:
  417. * @param {object} options.gltf The glTF JSON.
  418. * @param {number} options.imageId The image ID.
  419. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  420. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  421. *
  422. * @returns {string} The image cache key.
  423. * @private
  424. */
  425. ResourceCacheKey.getImageCacheKey = function (options) {
  426. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  427. const gltf = options.gltf;
  428. const imageId = options.imageId;
  429. const gltfResource = options.gltfResource;
  430. const baseResource = options.baseResource;
  431. //>>includeStart('debug', pragmas.debug);
  432. Check.typeOf.object("options.gltf", gltf);
  433. Check.typeOf.number("options.imageId", imageId);
  434. Check.typeOf.object("options.gltfResource", gltfResource);
  435. Check.typeOf.object("options.baseResource", baseResource);
  436. //>>includeEnd('debug');
  437. const imageCacheKey = getImageCacheKey(
  438. gltf,
  439. imageId,
  440. gltfResource,
  441. baseResource
  442. );
  443. return `image:${imageCacheKey}`;
  444. };
  445. /**
  446. * Gets the texture cache key.
  447. *
  448. * @param {object} options Object with the following properties:
  449. * @param {object} options.gltf The glTF JSON.
  450. * @param {object} options.textureInfo The texture info object.
  451. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  452. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  453. * @param {SupportedImageFormats} options.supportedImageFormats The supported image formats.
  454. * @param {FrameState} options.frameState The frame state.
  455. *
  456. * @returns {string} The texture cache key.
  457. * @private
  458. */
  459. ResourceCacheKey.getTextureCacheKey = function (options) {
  460. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  461. const gltf = options.gltf;
  462. const textureInfo = options.textureInfo;
  463. const gltfResource = options.gltfResource;
  464. const baseResource = options.baseResource;
  465. const supportedImageFormats = options.supportedImageFormats;
  466. const frameState = options.frameState;
  467. //>>includeStart('debug', pragmas.debug);
  468. Check.typeOf.object("options.gltf", gltf);
  469. Check.typeOf.object("options.textureInfo", textureInfo);
  470. Check.typeOf.object("options.gltfResource", gltfResource);
  471. Check.typeOf.object("options.baseResource", baseResource);
  472. Check.typeOf.object("options.supportedImageFormats", supportedImageFormats);
  473. Check.typeOf.object("options.frameState", frameState);
  474. //>>includeEnd('debug');
  475. const textureId = textureInfo.index;
  476. const imageId = GltfLoaderUtil.getImageIdFromTexture({
  477. gltf: gltf,
  478. textureId: textureId,
  479. supportedImageFormats: supportedImageFormats,
  480. });
  481. const imageCacheKey = getImageCacheKey(
  482. gltf,
  483. imageId,
  484. gltfResource,
  485. baseResource
  486. );
  487. // Include the sampler cache key in the texture cache key since textures and
  488. // samplers are coupled in WebGL 1. When upgrading to WebGL 2 consider
  489. // removing the sampleCacheKey here.
  490. const samplerCacheKey = getSamplerCacheKey(gltf, textureInfo);
  491. return `texture:${imageCacheKey}-sampler-${samplerCacheKey}-context-${frameState.context.id}`;
  492. };
  493. export default ResourceCacheKey;