123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- import Check from "../Core/Check.js";
- import CesiumMath from "../Core/Math.js";
- import defaultValue from "../Core/defaultValue.js";
- import defer from "../Core/defer.js";
- import defined from "../Core/defined.js";
- import PixelFormat from "../Core/PixelFormat.js";
- import Texture from "../Renderer/Texture.js";
- import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
- import TextureWrap from "../Renderer/TextureWrap.js";
- import GltfLoaderUtil from "./GltfLoaderUtil.js";
- import JobType from "./JobType.js";
- import ResourceLoader from "./ResourceLoader.js";
- import ResourceLoaderState from "./ResourceLoaderState.js";
- /**
- * Loads a glTF texture.
- * <p>
- * Implements the {@link ResourceLoader} interface.
- * </p>
- *
- * @alias GltfTextureLoader
- * @constructor
- * @augments ResourceLoader
- *
- * @param {Object} options Object with the following properties:
- * @param {ResourceCache} options.resourceCache The {@link ResourceCache} (to avoid circular dependencies).
- * @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 {String} [options.cacheKey] The cache key of the resource.
- * @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.
- *
- * @private
- */
- export default function GltfTextureLoader(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- const resourceCache = options.resourceCache;
- const gltf = options.gltf;
- const textureInfo = options.textureInfo;
- const gltfResource = options.gltfResource;
- const baseResource = options.baseResource;
- const supportedImageFormats = options.supportedImageFormats;
- const cacheKey = options.cacheKey;
- const asynchronous = defaultValue(options.asynchronous, true);
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.func("options.resourceCache", resourceCache);
- 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);
- //>>includeEnd('debug');
- const textureId = textureInfo.index;
- // imageId is guaranteed to be defined otherwise the GltfTextureLoader
- // wouldn't have been created
- const imageId = GltfLoaderUtil.getImageIdFromTexture({
- gltf: gltf,
- textureId: textureId,
- supportedImageFormats: supportedImageFormats,
- });
- this._resourceCache = resourceCache;
- this._gltf = gltf;
- this._textureInfo = textureInfo;
- this._imageId = imageId;
- this._gltfResource = gltfResource;
- this._baseResource = baseResource;
- this._cacheKey = cacheKey;
- this._asynchronous = asynchronous;
- this._imageLoader = undefined;
- this._image = undefined;
- this._mipLevels = undefined;
- this._texture = undefined;
- this._state = ResourceLoaderState.UNLOADED;
- this._promise = defer();
- }
- if (defined(Object.create)) {
- GltfTextureLoader.prototype = Object.create(ResourceLoader.prototype);
- GltfTextureLoader.prototype.constructor = GltfTextureLoader;
- }
- Object.defineProperties(GltfTextureLoader.prototype, {
- /**
- * A promise that resolves to the resource when the resource is ready.
- *
- * @memberof GltfTextureLoader.prototype
- *
- * @type {Promise.<GltfTextureLoader>}
- * @readonly
- * @private
- */
- promise: {
- get: function () {
- return this._promise.promise;
- },
- },
- /**
- * The cache key of the resource.
- *
- * @memberof GltfTextureLoader.prototype
- *
- * @type {String}
- * @readonly
- * @private
- */
- cacheKey: {
- get: function () {
- return this._cacheKey;
- },
- },
- /**
- * The texture.
- *
- * @memberof GltfTextureLoader.prototype
- *
- * @type {Texture}
- * @readonly
- * @private
- */
- texture: {
- get: function () {
- return this._texture;
- },
- },
- });
- /**
- * Loads the resource.
- * @private
- */
- GltfTextureLoader.prototype.load = function () {
- const resourceCache = this._resourceCache;
- const imageLoader = resourceCache.loadImage({
- gltf: this._gltf,
- imageId: this._imageId,
- gltfResource: this._gltfResource,
- baseResource: this._baseResource,
- });
- this._imageLoader = imageLoader;
- this._state = ResourceLoaderState.LOADING;
- const that = this;
- imageLoader.promise
- .then(function () {
- if (that.isDestroyed()) {
- return;
- }
- // Now wait for process() to run to finish loading
- that._image = imageLoader.image;
- that._mipLevels = imageLoader.mipLevels;
- that._state = ResourceLoaderState.PROCESSING;
- })
- .catch(function (error) {
- if (that.isDestroyed()) {
- return;
- }
- that.unload();
- that._state = ResourceLoaderState.FAILED;
- const errorMessage = "Failed to load texture";
- that._promise.reject(that.getError(errorMessage, error));
- });
- };
- function CreateTextureJob() {
- this.gltf = undefined;
- this.textureInfo = undefined;
- this.image = undefined;
- this.context = undefined;
- this.texture = undefined;
- }
- CreateTextureJob.prototype.set = function (
- gltf,
- textureInfo,
- image,
- mipLevels,
- context
- ) {
- this.gltf = gltf;
- this.textureInfo = textureInfo;
- this.image = image;
- this.mipLevels = mipLevels;
- this.context = context;
- };
- CreateTextureJob.prototype.execute = function () {
- this.texture = createTexture(
- this.gltf,
- this.textureInfo,
- this.image,
- this.mipLevels,
- this.context
- );
- };
- function resizeImageToNextPowerOfTwo(image) {
- const canvas = document.createElement("canvas");
- canvas.width = CesiumMath.nextPowerOfTwo(image.width);
- canvas.height = CesiumMath.nextPowerOfTwo(image.height);
- const canvasContext = canvas.getContext("2d");
- canvasContext.drawImage(
- image,
- 0,
- 0,
- image.width,
- image.height,
- 0,
- 0,
- canvas.width,
- canvas.height
- );
- return canvas;
- }
- function createTexture(gltf, textureInfo, image, mipLevels, context) {
- // internalFormat is only defined for CompressedTextureBuffer
- const internalFormat = image.internalFormat;
- let compressedTextureNoMipmap = false;
- if (PixelFormat.isCompressedFormat(internalFormat) && !defined(mipLevels)) {
- compressedTextureNoMipmap = true;
- }
- const sampler = GltfLoaderUtil.createSampler({
- gltf: gltf,
- textureInfo: textureInfo,
- compressedTextureNoMipmap: compressedTextureNoMipmap,
- });
- const minFilter = sampler.minificationFilter;
- const wrapS = sampler.wrapS;
- const wrapT = sampler.wrapT;
- const samplerRequiresMipmap =
- minFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST ||
- minFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR ||
- minFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST ||
- minFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR;
- // generateMipmap is disallowed for compressed textures. Compressed textures
- // can have mipmaps but they must come with the KTX2 instead of generated by
- // WebGL. Also note from the KHR_texture_basisu spec:
- //
- // When a texture refers to a sampler with mipmap minification or when the
- // sampler is undefined, the KTX2 image SHOULD contain a full mip pyramid.
- //
- const generateMipmap = !defined(internalFormat) && samplerRequiresMipmap;
- // WebGL 1 requires power-of-two texture dimensions for mipmapping and REPEAT/MIRRORED_REPEAT wrap modes.
- const requiresPowerOfTwo =
- generateMipmap ||
- wrapS === TextureWrap.REPEAT ||
- wrapS === TextureWrap.MIRRORED_REPEAT ||
- wrapT === TextureWrap.REPEAT ||
- wrapT === TextureWrap.MIRRORED_REPEAT;
- const nonPowerOfTwo =
- !CesiumMath.isPowerOfTwo(image.width) ||
- !CesiumMath.isPowerOfTwo(image.height);
- const requiresResize = requiresPowerOfTwo && nonPowerOfTwo;
- let texture;
- if (defined(internalFormat)) {
- if (
- !context.webgl2 &&
- PixelFormat.isCompressedFormat(internalFormat) &&
- nonPowerOfTwo &&
- requiresPowerOfTwo
- ) {
- console.warn(
- "Compressed texture uses REPEAT or MIRRORED_REPEAT texture wrap mode and dimensions are not powers of two. The texture may be rendered incorrectly."
- );
- }
- texture = Texture.create({
- context: context,
- source: {
- arrayBufferView: image.bufferView, // Only defined for CompressedTextureBuffer
- mipLevels: mipLevels,
- },
- width: image.width,
- height: image.height,
- pixelFormat: image.internalFormat, // Only defined for CompressedTextureBuffer
- sampler: sampler,
- });
- } else {
- if (requiresResize) {
- image = resizeImageToNextPowerOfTwo(image);
- }
- texture = Texture.create({
- context: context,
- source: image,
- sampler: sampler,
- flipY: false,
- skipColorSpaceConversion: true,
- });
- }
- if (generateMipmap) {
- texture.generateMipmap();
- }
- return texture;
- }
- const scratchTextureJob = new CreateTextureJob();
- /**
- * Processes the resource until it becomes ready.
- *
- * @param {FrameState} frameState The frame state.
- * @private
- */
- GltfTextureLoader.prototype.process = function (frameState) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.object("frameState", frameState);
- //>>includeEnd('debug');
- if (defined(this._texture)) {
- // Already created texture
- return;
- }
- if (!defined(this._image)) {
- // Not ready to create texture
- return;
- }
- let texture;
- if (this._asynchronous) {
- const textureJob = scratchTextureJob;
- textureJob.set(
- this._gltf,
- this._textureInfo,
- this._image,
- this._mipLevels,
- frameState.context
- );
- const jobScheduler = frameState.jobScheduler;
- if (!jobScheduler.execute(textureJob, JobType.TEXTURE)) {
- // Job scheduler is full. Try again next frame.
- return;
- }
- texture = textureJob.texture;
- } else {
- texture = createTexture(
- this._gltf,
- this._textureInfo,
- this._image,
- this._mipLevels,
- frameState.context
- );
- }
- // Unload everything except the texture
- this.unload();
- this._texture = texture;
- this._state = ResourceLoaderState.READY;
- this._promise.resolve(this);
- };
- /**
- * Unloads the resource.
- * @private
- */
- GltfTextureLoader.prototype.unload = function () {
- if (defined(this._texture)) {
- this._texture.destroy();
- }
- if (defined(this._imageLoader)) {
- this._resourceCache.unload(this._imageLoader);
- }
- this._imageLoader = undefined;
- this._image = undefined;
- this._mipLevels = undefined;
- this._texture = undefined;
- this._gltf = undefined;
- };
|