| 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;};
 |