import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import getAbsoluteUri from "../Core/getAbsoluteUri.js"; import GltfLoaderUtil from "./GltfLoaderUtil.js"; import hasExtension from "./hasExtension.js"; /** * Compute cache keys for resources in {@link ResourceCache}. * * @namespace ResourceCacheKey * * @private */ const ResourceCacheKey = {}; function getExternalResourceCacheKey(resource) { return getAbsoluteUri(resource.url); } function getBufferViewCacheKey(bufferView) { let byteOffset = bufferView.byteOffset; let byteLength = bufferView.byteLength; if (hasExtension(bufferView, "EXT_meshopt_compression")) { const meshopt = bufferView.extensions.EXT_meshopt_compression; byteOffset = defaultValue(meshopt.byteOffset, 0); byteLength = meshopt.byteLength; } return `${byteOffset}-${byteOffset + byteLength}`; } function getAccessorCacheKey(accessor, bufferView) { const byteOffset = bufferView.byteOffset + accessor.byteOffset; const componentType = accessor.componentType; const type = accessor.type; const count = accessor.count; return `${byteOffset}-${componentType}-${type}-${count}`; } function getExternalBufferCacheKey(resource) { return getExternalResourceCacheKey(resource); } function getEmbeddedBufferCacheKey(parentResource, bufferId) { const parentCacheKey = getExternalResourceCacheKey(parentResource); return `${parentCacheKey}-buffer-id-${bufferId}`; } function getBufferCacheKey(buffer, bufferId, gltfResource, baseResource) { if (defined(buffer.uri)) { const resource = baseResource.getDerivedResource({ url: buffer.uri, }); return getExternalBufferCacheKey(resource); } return getEmbeddedBufferCacheKey(gltfResource, bufferId); } function getDracoCacheKey(gltf, draco, gltfResource, baseResource) { const bufferViewId = draco.bufferView; const bufferView = gltf.bufferViews[bufferViewId]; const bufferId = bufferView.buffer; const buffer = gltf.buffers[bufferId]; const bufferCacheKey = getBufferCacheKey( buffer, bufferId, gltfResource, baseResource ); const bufferViewCacheKey = getBufferViewCacheKey(bufferView); return `${bufferCacheKey}-range-${bufferViewCacheKey}`; } function getImageCacheKey(gltf, imageId, gltfResource, baseResource) { const image = gltf.images[imageId]; const bufferViewId = image.bufferView; const uri = image.uri; if (defined(uri)) { const resource = baseResource.getDerivedResource({ url: uri, }); return getExternalResourceCacheKey(resource); } const bufferView = gltf.bufferViews[bufferViewId]; const bufferId = bufferView.buffer; const buffer = gltf.buffers[bufferId]; const bufferCacheKey = getBufferCacheKey( buffer, bufferId, gltfResource, baseResource ); const bufferViewCacheKey = getBufferViewCacheKey(bufferView); return `${bufferCacheKey}-range-${bufferViewCacheKey}`; } function getSamplerCacheKey(gltf, textureInfo) { const sampler = GltfLoaderUtil.createSampler({ gltf: gltf, textureInfo: textureInfo, }); return `${sampler.wrapS}-${sampler.wrapT}-${sampler.minificationFilter}-${sampler.magnificationFilter}`; } /** * Gets the schema cache key. * * @param {object} options Object with the following properties: * @param {object} [options.schema] An object that explicitly defines a schema JSON. Mutually exclusive with options.resource. * @param {Resource} [options.resource] The {@link Resource} pointing to the schema JSON. Mutually exclusive with options.schema. * * @returns {string} The schema cache key. * * @exception {DeveloperError} One of options.schema and options.resource must be defined. * @private */ ResourceCacheKey.getSchemaCacheKey = function (options) { const schema = options.schema; const resource = options.resource; //>>includeStart('debug', pragmas.debug); if (defined(schema) === defined(resource)) { throw new DeveloperError( "One of options.schema and options.resource must be defined." ); } //>>includeEnd('debug'); if (defined(schema)) { return `embedded-schema:${JSON.stringify(schema)}`; } return `external-schema:${getExternalResourceCacheKey(resource)}`; }; /** * Gets the external buffer cache key. * * @param {object} options Object with the following properties: * @param {Resource} options.resource The {@link Resource} pointing to the external buffer. * * @returns {string} The external buffer cache key. * @private */ ResourceCacheKey.getExternalBufferCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const resource = options.resource; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.resource", resource); //>>includeEnd('debug'); return `external-buffer:${getExternalBufferCacheKey(resource)}`; }; /** * Gets the embedded buffer cache key. * * @param {object} options Object with the following properties: * @param {Resource} options.parentResource The {@link Resource} containing the embedded buffer. * @param {number} options.bufferId A unique identifier of the embedded buffer within the parent resource. * * @returns {string} The embedded buffer cache key. * @private */ ResourceCacheKey.getEmbeddedBufferCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const parentResource = options.parentResource; const bufferId = options.bufferId; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.parentResource", parentResource); Check.typeOf.number("options.bufferId", bufferId); //>>includeEnd('debug'); return `embedded-buffer:${getEmbeddedBufferCacheKey( parentResource, bufferId )}`; }; /** * Gets the glTF cache key. * * @param {object} options Object with the following properties: * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * * @returns {string} The glTF cache key. * @private */ ResourceCacheKey.getGltfCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltfResource = options.gltfResource; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.gltfResource", gltfResource); //>>includeEnd('debug'); return `gltf:${getExternalResourceCacheKey(gltfResource)}`; }; /** * Gets the buffer view cache key. * * @param {object} options Object with the following properties: * @param {object} options.gltf The glTF JSON. * @param {number} options.bufferViewId The bufferView ID. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * * @returns {string} The buffer view cache key. * @private */ ResourceCacheKey.getBufferViewCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const bufferViewId = options.bufferViewId; const gltfResource = options.gltfResource; const baseResource = options.baseResource; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.gltf", gltf); Check.typeOf.number("options.bufferViewId", bufferViewId); Check.typeOf.object("options.gltfResource", gltfResource); Check.typeOf.object("options.baseResource", baseResource); //>>includeEnd('debug'); const bufferView = gltf.bufferViews[bufferViewId]; let bufferId = bufferView.buffer; const buffer = gltf.buffers[bufferId]; if (hasExtension(bufferView, "EXT_meshopt_compression")) { const meshopt = bufferView.extensions.EXT_meshopt_compression; bufferId = meshopt.buffer; } const bufferCacheKey = getBufferCacheKey( buffer, bufferId, gltfResource, baseResource ); const bufferViewCacheKey = getBufferViewCacheKey(bufferView); return `buffer-view:${bufferCacheKey}-range-${bufferViewCacheKey}`; }; /** * Gets the Draco cache key. * * @param {object} options Object with the following properties: * @param {object} options.gltf The glTF JSON. * @param {object} options.draco The Draco extension object. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * * @returns {string} The Draco cache key. * @private */ ResourceCacheKey.getDracoCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const draco = options.draco; const gltfResource = options.gltfResource; const baseResource = options.baseResource; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.gltf", gltf); Check.typeOf.object("options.draco", draco); Check.typeOf.object("options.gltfResource", gltfResource); Check.typeOf.object("options.baseResource", baseResource); //>>includeEnd('debug'); return `draco:${getDracoCacheKey(gltf, draco, gltfResource, baseResource)}`; }; /** * Gets the vertex buffer cache key. * * @param {object} options Object with the following properties: * @param {object} options.gltf The glTF JSON. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * @param {FrameState} options.frameState The frame state. * @param {number} [options.bufferViewId] The bufferView ID corresponding to the vertex buffer. * @param {object} [options.draco] The Draco extension object. * @param {string} [options.attributeSemantic] The attribute semantic, e.g. POSITION or NORMAL. * @param {boolean} [options.dequantize=false] Determines whether or not the vertex buffer will be dequantized on the CPU. * @param {boolean} [options.loadBuffer=false] Load vertex buffer as a GPU vertex buffer. * @param {boolean} [options.loadTypedArray=false] Load vertex buffer as a typed array. * @exception {DeveloperError} One of options.bufferViewId and options.draco must be defined. * @exception {DeveloperError} When options.draco is defined options.attributeSemantic must also be defined. * * @returns {string} The vertex buffer cache key. * @private */ ResourceCacheKey.getVertexBufferCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const gltfResource = options.gltfResource; const baseResource = options.baseResource; const frameState = options.frameState; const bufferViewId = options.bufferViewId; const draco = options.draco; const attributeSemantic = options.attributeSemantic; const dequantize = defaultValue(options.dequantize, false); const loadBuffer = defaultValue(options.loadBuffer, false); const loadTypedArray = defaultValue(options.loadTypedArray, false); //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.gltf", gltf); Check.typeOf.object("options.gltfResource", gltfResource); Check.typeOf.object("options.baseResource", baseResource); Check.typeOf.object("options.frameState", frameState); const hasBufferViewId = defined(bufferViewId); const hasDraco = hasDracoCompression(draco, attributeSemantic); const hasAttributeSemantic = defined(attributeSemantic); if (hasBufferViewId === hasDraco) { throw new DeveloperError( "One of options.bufferViewId and options.draco must be defined." ); } if (hasDraco && !hasAttributeSemantic) { throw new DeveloperError( "When options.draco is defined options.attributeSemantic must also be defined." ); } if (hasDraco) { Check.typeOf.object("options.draco", draco); Check.typeOf.string("options.attributeSemantic", attributeSemantic); } if (!loadBuffer && !loadTypedArray) { throw new DeveloperError( "At least one of loadBuffer and loadTypedArray must be true." ); } //>>includeEnd('debug'); let cacheKeySuffix = ""; if (dequantize) { cacheKeySuffix += "-dequantize"; } if (loadBuffer) { cacheKeySuffix += "-buffer"; cacheKeySuffix += `-context-${frameState.context.id}`; } if (loadTypedArray) { cacheKeySuffix += "-typed-array"; } if (defined(draco)) { const dracoCacheKey = getDracoCacheKey( gltf, draco, gltfResource, baseResource ); return `vertex-buffer:${dracoCacheKey}-draco-${attributeSemantic}${cacheKeySuffix}`; } const bufferView = gltf.bufferViews[bufferViewId]; const bufferId = bufferView.buffer; const buffer = gltf.buffers[bufferId]; const bufferCacheKey = getBufferCacheKey( buffer, bufferId, gltfResource, baseResource ); const bufferViewCacheKey = getBufferViewCacheKey(bufferView); return `vertex-buffer:${bufferCacheKey}-range-${bufferViewCacheKey}${cacheKeySuffix}`; }; function hasDracoCompression(draco, semantic) { return ( defined(draco) && defined(draco.attributes) && defined(draco.attributes[semantic]) ); } /** * Gets the index buffer cache key. * * @param {object} options Object with the following properties: * @param {object} options.gltf The glTF JSON. * @param {number} options.accessorId The accessor ID corresponding to the index buffer. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * @param {FrameState} options.frameState The frame state. * @param {object} [options.draco] The Draco extension object. * @param {boolean} [options.loadBuffer=false] Load index buffer as a GPU index buffer. * @param {boolean} [options.loadTypedArray=false] Load index buffer as a typed array. * * @returns {string} The index buffer cache key. * @private */ ResourceCacheKey.getIndexBufferCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const accessorId = options.accessorId; const gltfResource = options.gltfResource; const baseResource = options.baseResource; const frameState = options.frameState; const draco = options.draco; const loadBuffer = defaultValue(options.loadBuffer, false); const loadTypedArray = defaultValue(options.loadTypedArray, false); //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.gltf", gltf); Check.typeOf.number("options.accessorId", accessorId); Check.typeOf.object("options.gltfResource", gltfResource); Check.typeOf.object("options.baseResource", baseResource); Check.typeOf.object("options.frameState", frameState); if (!loadBuffer && !loadTypedArray) { throw new DeveloperError( "At least one of loadBuffer and loadTypedArray must be true." ); } //>>includeEnd('debug'); let cacheKeySuffix = ""; if (loadBuffer) { cacheKeySuffix += "-buffer"; cacheKeySuffix += `-context-${frameState.context.id}`; } if (loadTypedArray) { cacheKeySuffix += "-typed-array"; } if (defined(draco)) { const dracoCacheKey = getDracoCacheKey( gltf, draco, gltfResource, baseResource ); return `index-buffer:${dracoCacheKey}-draco${cacheKeySuffix}`; } const accessor = gltf.accessors[accessorId]; const bufferViewId = accessor.bufferView; const bufferView = gltf.bufferViews[bufferViewId]; const bufferId = bufferView.buffer; const buffer = gltf.buffers[bufferId]; const bufferCacheKey = getBufferCacheKey( buffer, bufferId, gltfResource, baseResource ); const accessorCacheKey = getAccessorCacheKey(accessor, bufferView); return `index-buffer:${bufferCacheKey}-accessor-${accessorCacheKey}${cacheKeySuffix}`; }; /** * Gets the image cache key. * * @param {object} options Object with the following properties: * @param {object} options.gltf The glTF JSON. * @param {number} options.imageId The image ID. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * * @returns {string} The image cache key. * @private */ ResourceCacheKey.getImageCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const imageId = options.imageId; const gltfResource = options.gltfResource; const baseResource = options.baseResource; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.gltf", gltf); Check.typeOf.number("options.imageId", imageId); Check.typeOf.object("options.gltfResource", gltfResource); Check.typeOf.object("options.baseResource", baseResource); //>>includeEnd('debug'); const imageCacheKey = getImageCacheKey( gltf, imageId, gltfResource, baseResource ); return `image:${imageCacheKey}`; }; /** * Gets the texture cache key. * * @param {object} options Object with the following properties: * @param {object} options.gltf The glTF JSON. * @param {object} options.textureInfo The texture info object. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * @param {SupportedImageFormats} options.supportedImageFormats The supported image formats. * @param {FrameState} options.frameState The frame state. * * @returns {string} The texture cache key. * @private */ ResourceCacheKey.getTextureCacheKey = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltf = options.gltf; const textureInfo = options.textureInfo; const gltfResource = options.gltfResource; const baseResource = options.baseResource; const supportedImageFormats = options.supportedImageFormats; const frameState = options.frameState; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.gltf", gltf); Check.typeOf.object("options.textureInfo", textureInfo); Check.typeOf.object("options.gltfResource", gltfResource); Check.typeOf.object("options.baseResource", baseResource); Check.typeOf.object("options.supportedImageFormats", supportedImageFormats); Check.typeOf.object("options.frameState", frameState); //>>includeEnd('debug'); const textureId = textureInfo.index; const imageId = GltfLoaderUtil.getImageIdFromTexture({ gltf: gltf, textureId: textureId, supportedImageFormats: supportedImageFormats, }); const imageCacheKey = getImageCacheKey( gltf, imageId, gltfResource, baseResource ); // Include the sampler cache key in the texture cache key since textures and // samplers are coupled in WebGL 1. When upgrading to WebGL 2 consider // removing the sampleCacheKey here. const samplerCacheKey = getSamplerCacheKey(gltf, textureInfo); return `texture:${imageCacheKey}-sampler-${samplerCacheKey}-context-${frameState.context.id}`; }; export default ResourceCacheKey;