text: define([
          '../Core/BoundingSphere',
          '../Core/BoxOutlineGeometry',
          '../Core/Cartesian2',
          '../Core/Cartesian3',
          '../Core/Cartesian4',
          '../Core/Cartographic',
          '../Core/Color',
          '../Core/ColorGeometryInstanceAttribute',
          '../Core/combine',
          '../Core/defaultValue',
          '../Core/defined',
          '../Core/defineProperties',
          '../Core/destroyObject',
          '../Core/DeveloperError',
          '../Core/Event',
          '../Core/GeometryInstance',
          '../Core/GeometryPipeline',
          '../Core/IndexDatatype',
          '../Core/Intersect',
          '../Core/Math',
          '../Core/Matrix4',
          '../Core/OrientedBoundingBox',
          '../Core/OrthographicFrustum',
          '../Core/PrimitiveType',
          '../Core/Rectangle',
          '../Core/SphereOutlineGeometry',
          '../Core/TerrainMesh',
          '../Core/TerrainQuantization',
          '../Core/Visibility',
          '../Core/WebMercatorProjection',
          '../Renderer/Buffer',
          '../Renderer/BufferUsage',
          '../Renderer/ContextLimits',
          '../Renderer/DrawCommand',
          '../Renderer/Pass',
          '../Renderer/RenderState',
          '../Renderer/VertexArray',
          './BlendingState',
          './ImageryState',
          './TileBoundingRegion',
          './TileSelectionResult',
          './ClippingPlaneCollection',
          './DepthFunction',
          './GlobeSurfaceTile',
          './ImageryLayer',
          './PerInstanceColorAppearance',
          './Primitive',
          './QuadtreeTileLoadState',
          './SceneMode',
          './ShadowMode',
          './TerrainFillMesh',
          './TerrainState'
      ], function(
          BoundingSphere,
          BoxOutlineGeometry,
          Cartesian2,
          Cartesian3,
          Cartesian4,
          Cartographic,
          Color,
          ColorGeometryInstanceAttribute,
          combine,
          defaultValue,
          defined,
          defineProperties,
          destroyObject,
          DeveloperError,
          Event,
          GeometryInstance,
          GeometryPipeline,
          IndexDatatype,
          Intersect,
          CesiumMath,
          Matrix4,
          OrientedBoundingBox,
          OrthographicFrustum,
          PrimitiveType,
          Rectangle,
          SphereOutlineGeometry,
          TerrainMesh,
          TerrainQuantization,
          Visibility,
          WebMercatorProjection,
          Buffer,
          BufferUsage,
          ContextLimits,
          DrawCommand,
          Pass,
          RenderState,
          VertexArray,
          BlendingState,
          ImageryState,
          TileBoundingRegion,
          TileSelectionResult,
          ClippingPlaneCollection,
          DepthFunction,
          GlobeSurfaceTile,
          ImageryLayer,
          PerInstanceColorAppearance,
          Primitive,
          QuadtreeTileLoadState,
          SceneMode,
          ShadowMode,
          TerrainFillMesh,
          TerrainState) {
      'use strict';
  
      /**
       * Provides quadtree tiles representing the surface of the globe.  This type is intended to be used
       * with {@link QuadtreePrimitive}.
       *
       * @alias GlobeSurfaceTileProvider
       * @constructor
       *
       * @param {TerrainProvider} options.terrainProvider The terrain provider that describes the surface geometry.
       * @param {ImageryLayerCollection} option.imageryLayers The collection of imagery layers describing the shading of the surface.
       * @param {GlobeSurfaceShaderSet} options.surfaceShaderSet The set of shaders used to render the surface.
       *
       * @private
       */
      function GlobeSurfaceTileProvider(options) {
          //>>includeStart('debug', pragmas.debug);
          if (!defined(options)) {
              throw new DeveloperError('options is required.');
          }
          if (!defined(options.terrainProvider)) {
              throw new DeveloperError('options.terrainProvider is required.');
          } else if (!defined(options.imageryLayers)) {
              throw new DeveloperError('options.imageryLayers is required.');
          } else if (!defined(options.surfaceShaderSet)) {
              throw new DeveloperError('options.surfaceShaderSet is required.');
          }
          //>>includeEnd('debug');
  
          this.lightingFadeOutDistance = 6500000.0;
          this.lightingFadeInDistance = 9000000.0;
          this.hasWaterMask = false;
          this.oceanNormalMap = undefined;
          this.zoomedOutOceanSpecularIntensity = 0.5;
          this.enableLighting = false;
          this.showGroundAtmosphere = false;
          this.shadows = ShadowMode.RECEIVE_ONLY;
  
          /**
           * The color to use to highlight terrain fill tiles. If undefined, fill tiles are not
           * highlighted at all. The alpha value is used to alpha blend with the tile's
           * actual color. Because terrain fill tiles do not represent the actual terrain surface,
           * it may be useful in some applications to indicate visually that they are not to be trusted.
           * @type {Color}
           * @default undefined
           */
          this.fillHighlightColor = undefined;
  
          this.hueShift = 0.0;
          this.saturationShift = 0.0;
          this.brightnessShift = 0.0;
  
          this._quadtree = undefined;
          this._terrainProvider = options.terrainProvider;
          this._imageryLayers = options.imageryLayers;
          this._surfaceShaderSet = options.surfaceShaderSet;
  
          this._renderState = undefined;
          this._blendRenderState = undefined;
  
          this._errorEvent = new Event();
  
          this._imageryLayers.layerAdded.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerAdded, this);
          this._imageryLayers.layerRemoved.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerRemoved, this);
          this._imageryLayers.layerMoved.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerMoved, this);
          this._imageryLayers.layerShownOrHidden.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden, this);
          this._imageryLayersUpdatedEvent = new Event();
  
          this._layerOrderChanged = false;
  
          this._tilesToRenderByTextureCount = [];
          this._drawCommands = [];
          this._uniformMaps = [];
          this._usedDrawCommands = 0;
  
          this._vertexArraysToDestroy = [];
  
          this._debug = {
              wireframe : false,
              boundingSphereTile : undefined
          };
  
          this._baseColor = undefined;
          this._firstPassInitialColor = undefined;
          this.baseColor = new Color(0.0, 0.0, 0.5, 1.0);
  
          /**
           * A property specifying a {@link ClippingPlaneCollection} used to selectively disable rendering on the outside of each plane.
           * @type {ClippingPlaneCollection}
           * @private
           */
          this._clippingPlanes = undefined;
  
          /**
           * A property specifying a {@link Rectangle} used to selectively limit terrain and imagery rendering.
           * @type {Rectangle}
           */
          this.cartographicLimitRectangle = Rectangle.clone(Rectangle.MAX_VALUE);
  
          this._hasLoadedTilesThisFrame = false;
          this._hasFillTilesThisFrame = false;
      }
  
      defineProperties(GlobeSurfaceTileProvider.prototype, {
          /**
           * Gets or sets the color of the globe when no imagery is available.
           * @memberof GlobeSurfaceTileProvider.prototype
           * @type {Color}
           */
          baseColor : {
              get : function() {
                  return this._baseColor;
              },
              set : function(value) {
                  //>>includeStart('debug', pragmas.debug);
                  if (!defined(value)) {
                      throw new DeveloperError('value is required.');
                  }
                  //>>includeEnd('debug');
  
                  this._baseColor = value;
                  this._firstPassInitialColor = Cartesian4.fromColor(value, this._firstPassInitialColor);
              }
          },
          /**
           * Gets or sets the {@link QuadtreePrimitive} for which this provider is
           * providing tiles.  This property may be undefined if the provider is not yet associated
           * with a {@link QuadtreePrimitive}.
           * @memberof GlobeSurfaceTileProvider.prototype
           * @type {QuadtreePrimitive}
           */
          quadtree : {
              get : function() {
                  return this._quadtree;
              },
              set : function(value) {
                  //>>includeStart('debug', pragmas.debug);
                  if (!defined(value)) {
                      throw new DeveloperError('value is required.');
                  }
                  //>>includeEnd('debug');
  
                  this._quadtree = value;
              }
          },
  
          /**
           * Gets a value indicating whether or not the provider is ready for use.
           * @memberof GlobeSurfaceTileProvider.prototype
           * @type {Boolean}
           */
          ready : {
              get : function() {
                  return this._terrainProvider.ready && (this._imageryLayers.length === 0 || this._imageryLayers.get(0).imageryProvider.ready);
              }
          },
  
          /**
           * Gets the tiling scheme used by the provider.  This property should
           * not be accessed before {@link GlobeSurfaceTileProvider#ready} returns true.
           * @memberof GlobeSurfaceTileProvider.prototype
           * @type {TilingScheme}
           */
          tilingScheme : {
              get : function() {
                  return this._terrainProvider.tilingScheme;
              }
          },
  
          /**
           * Gets an event that is raised when the geometry provider encounters an asynchronous error.  By subscribing
           * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
           * are passed an instance of {@link TileProviderError}.
           * @memberof GlobeSurfaceTileProvider.prototype
           * @type {Event}
           */
          errorEvent : {
              get : function() {
                  return this._errorEvent;
              }
          },
  
          /**
           * Gets an event that is raised when an imagery layer is added, shown, hidden, moved, or removed.
           * @memberof GlobeSurfaceTileProvider.prototype
           * @type {Event}
           */
          imageryLayersUpdatedEvent : {
              get : function() {
                  return this._imageryLayersUpdatedEvent;
              }
          },
  
          /**
           * Gets or sets the terrain provider that describes the surface geometry.
           * @memberof GlobeSurfaceTileProvider.prototype
           * @type {TerrainProvider}
           */
          terrainProvider : {
              get : function() {
                  return this._terrainProvider;
              },
              set : function(terrainProvider) {
                  if (this._terrainProvider === terrainProvider) {
                      return;
                  }
  
                  //>>includeStart('debug', pragmas.debug);
                  if (!defined(terrainProvider)) {
                      throw new DeveloperError('terrainProvider is required.');
                  }
                  //>>includeEnd('debug');
  
                  this._terrainProvider = terrainProvider;
  
                  if (defined(this._quadtree)) {
                      this._quadtree.invalidateAllTiles();
                  }
              }
          },
          /**
           * The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset.
           *
           * @type {ClippingPlaneCollection}
           *
           * @private
           */
          clippingPlanes : {
              get : function() {
                  return this._clippingPlanes;
              },
              set : function(value) {
                  ClippingPlaneCollection.setOwner(value, this, '_clippingPlanes');
              }
          }
      });
  
      function sortTileImageryByLayerIndex(a, b) {
          var aImagery = a.loadingImagery;
          if (!defined(aImagery)) {
              aImagery = a.readyImagery;
          }
  
          var bImagery = b.loadingImagery;
          if (!defined(bImagery)) {
              bImagery = b.readyImagery;
          }
  
          return aImagery.imageryLayer._layerIndex - bImagery.imageryLayer._layerIndex;
      }
  
       /**
       * Make updates to the tile provider that are not involved in rendering. Called before the render update cycle.
       */
      GlobeSurfaceTileProvider.prototype.update = function(frameState) {
          // update collection: imagery indices, base layers, raise layer show/hide event
          this._imageryLayers._update();
      };
  
      function updateCredits(surface, frameState) {
          var creditDisplay = frameState.creditDisplay;
          if (surface._terrainProvider.ready && defined(surface._terrainProvider.credit)) {
              creditDisplay.addCredit(surface._terrainProvider.credit);
          }
  
          var imageryLayers = surface._imageryLayers;
          for (var i = 0, len = imageryLayers.length; i < len; ++i) {
              var imageryProvider = imageryLayers.get(i).imageryProvider;
              if (imageryProvider.ready && defined(imageryProvider.credit)) {
                  creditDisplay.addCredit(imageryProvider.credit);
              }
          }
      }
  
      /**
       * Called at the beginning of each render frame, before {@link QuadtreeTileProvider#showTileThisFrame}
       * @param {FrameState} frameState The frame state.
       */
      GlobeSurfaceTileProvider.prototype.initialize = function(frameState) {
          // update each layer for texture reprojection.
          this._imageryLayers.queueReprojectionCommands(frameState);
  
          if (this._layerOrderChanged) {
              this._layerOrderChanged = false;
  
              // Sort the TileImagery instances in each tile by the layer index.
              this._quadtree.forEachLoadedTile(function(tile) {
                  tile.data.imagery.sort(sortTileImageryByLayerIndex);
              });
          }
  
          // Add credits for terrain and imagery providers.
          updateCredits(this, frameState);
  
          var vertexArraysToDestroy = this._vertexArraysToDestroy;
          var length = vertexArraysToDestroy.length;
          for (var j = 0; j < length; ++j) {
              GlobeSurfaceTile._freeVertexArray(vertexArraysToDestroy[j]);
          }
          vertexArraysToDestroy.length = 0;
      };
  
      /**
       * Called at the beginning of the update cycle for each render frame, before {@link QuadtreeTileProvider#showTileThisFrame}
       * or any other functions.
       *
       * @param {FrameState} frameState The frame state.
       */
      GlobeSurfaceTileProvider.prototype.beginUpdate = function(frameState) {
          var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
          for (var i = 0, len = tilesToRenderByTextureCount.length; i < len; ++i) {
              var tiles = tilesToRenderByTextureCount[i];
              if (defined(tiles)) {
                  tiles.length = 0;
              }
          }
          // update clipping planes
          var clippingPlanes = this._clippingPlanes;
          if (defined(clippingPlanes) && clippingPlanes.enabled) {
              clippingPlanes.update(frameState);
          }
          this._usedDrawCommands = 0;
  
          this._hasLoadedTilesThisFrame = false;
          this._hasFillTilesThisFrame = false;
      };
  
      /**
       * Called at the end of the update cycle for each render frame, after {@link QuadtreeTileProvider#showTileThisFrame}
       * and any other functions.
       *
       * @param {FrameState} frameState The frame state.
       */
      GlobeSurfaceTileProvider.prototype.endUpdate = function(frameState) {
          if (!defined(this._renderState)) {
              this._renderState = RenderState.fromCache({ // Write color and depth
                  cull : {
                      enabled : true
                  },
                  depthTest : {
                      enabled : true,
                      func : DepthFunction.LESS
                  }
              });
  
              this._blendRenderState = RenderState.fromCache({ // Write color and depth
                  cull : {
                      enabled : true
                  },
                  depthTest : {
                      enabled : true,
                      func : DepthFunction.LESS_OR_EQUAL
                  },
                  blending : BlendingState.ALPHA_BLEND
              });
          }
  
          // If this frame has a mix of loaded and fill tiles, we need to propagate
          // loaded heights to the fill tiles.
          if (this._hasFillTilesThisFrame && this._hasLoadedTilesThisFrame) {
              TerrainFillMesh.updateFillTiles(this, this._quadtree._tilesToRender, frameState, this._vertexArraysToDestroy);
          }
  
          // Add the tile render commands to the command list, sorted by texture count.
          var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
          for (var textureCountIndex = 0, textureCountLength = tilesToRenderByTextureCount.length; textureCountIndex < textureCountLength; ++textureCountIndex) {
              var tilesToRender = tilesToRenderByTextureCount[textureCountIndex];
              if (!defined(tilesToRender)) {
                  continue;
              }
  
              for (var tileIndex = 0, tileLength = tilesToRender.length; tileIndex < tileLength; ++tileIndex) {
                  addDrawCommandsForTile(this, tilesToRender[tileIndex], frameState);
              }
          }
      };
  
      /**
       * Adds draw commands for tiles rendered in the previous frame for a pick pass.
       *
       * @param {FrameState} frameState The frame state.
       */
      GlobeSurfaceTileProvider.prototype.updateForPick = function(frameState) {
          // Add the tile pick commands from the tiles drawn last frame.
          var drawCommands = this._drawCommands;
          for (var i = 0, length = this._usedDrawCommands; i < length; ++i) {
              frameState.commandList.push(drawCommands[i]);
          }
      };
  
      /**
       * Cancels any imagery re-projections in the queue.
       */
      GlobeSurfaceTileProvider.prototype.cancelReprojections = function() {
          this._imageryLayers.cancelReprojections();
      };
  
      /**
       * Gets the maximum geometric error allowed in a tile at a given level, in meters.  This function should not be
       * called before {@link GlobeSurfaceTileProvider#ready} returns true.
       *
       * @param {Number} level The tile level for which to get the maximum geometric error.
       * @returns {Number} The maximum geometric error in meters.
       */
      GlobeSurfaceTileProvider.prototype.getLevelMaximumGeometricError = function(level) {
          return this._terrainProvider.getLevelMaximumGeometricError(level);
      };
  
      /**
       * Loads, or continues loading, a given tile.  This function will continue to be called
       * until {@link QuadtreeTile#state} is no longer {@link QuadtreeTileLoadState#LOADING}.  This function should
       * not be called before {@link GlobeSurfaceTileProvider#ready} returns true.
       *
       * @param {FrameState} frameState The frame state.
       * @param {QuadtreeTile} tile The tile to load.
       *
       * @exception {DeveloperError} <code>loadTile</code> must not be called before the tile provider is ready.
       */
      GlobeSurfaceTileProvider.prototype.loadTile = function(frameState, tile) {
          // We don't want to load imagery until we're certain that the terrain tiles are actually visible.
          // So if our bounding volume isn't accurate because it came from another tile, load terrain only
          // initially. If we load some terrain and suddenly have a more accurate bounding volume and the
          // tile is _still_ visible, give the tile a chance to load imagery immediately rather than
          // waiting for next frame.
  
          var surfaceTile = tile.data;
          var terrainOnly = true;
          var terrainStateBefore;
          if (defined(surfaceTile)) {
              terrainOnly = surfaceTile.boundingVolumeSourceTile !== tile || tile._lastSelectionResult === TileSelectionResult.CULLED_BUT_NEEDED;
              terrainStateBefore = surfaceTile.terrainState;
          }
  
          GlobeSurfaceTile.processStateMachine(tile, frameState, this.terrainProvider, this._imageryLayers, this._vertexArraysToDestroy, terrainOnly);
  
          surfaceTile = tile.data;
          if (terrainOnly && terrainStateBefore !== tile.data.terrainState) {
              // Terrain state changed. If:
              // a) The tile is visible, and
              // b) The bounding volume is accurate (updated as a side effect of computing visibility)
              // Then we'll load imagery, too.
              if (this.computeTileVisibility(tile, frameState, this.quadtree.occluders) && surfaceTile.boundingVolumeSourceTile === tile) {
                  terrainOnly = false;
                  GlobeSurfaceTile.processStateMachine(tile, frameState, this.terrainProvider, this._imageryLayers, this._vertexArraysToDestroy, terrainOnly);
              }
          }
      };
  
      var boundingSphereScratch = new BoundingSphere();
      var rectangleIntersectionScratch = new Rectangle();
      var splitCartographicLimitRectangleScratch = new Rectangle();
      var rectangleCenterScratch = new Cartographic();
  
      // cartographicLimitRectangle may span the IDL, but tiles never will.
      function clipRectangleAntimeridian(tileRectangle, cartographicLimitRectangle) {
          if (cartographicLimitRectangle.west < cartographicLimitRectangle.east) {
              return cartographicLimitRectangle;
          }
          var splitRectangle = Rectangle.clone(cartographicLimitRectangle, splitCartographicLimitRectangleScratch);
          var tileCenter = Rectangle.center(tileRectangle, rectangleCenterScratch);
          if (tileCenter.longitude > 0.0) {
              splitRectangle.east = CesiumMath.PI;
          } else {
              splitRectangle.west = -CesiumMath.PI;
          }
          return splitRectangle;
      }
  
      /**
       * Determines the visibility of a given tile.  The tile may be fully visible, partially visible, or not
       * visible at all.  Tiles that are renderable and are at least partially visible will be shown by a call
       * to {@link GlobeSurfaceTileProvider#showTileThisFrame}.
       *
       * @param {QuadtreeTile} tile The tile instance.
       * @param {FrameState} frameState The state information about the current frame.
       * @param {QuadtreeOccluders} occluders The objects that may occlude this tile.
       *
       * @returns {Visibility} The visibility of the tile.
       */
      GlobeSurfaceTileProvider.prototype.computeTileVisibility = function(tile, frameState, occluders) {
          var distance = this.computeDistanceToTile(tile, frameState);
          tile._distance = distance;
  
          if (frameState.fog.enabled) {
              if (CesiumMath.fog(distance, frameState.fog.density) >= 1.0) {
                  // Tile is completely in fog so return that it is not visible.
                  return Visibility.NONE;
              }
          }
  
          var surfaceTile = tile.data;
          var tileBoundingRegion = surfaceTile.tileBoundingRegion;
  
          if (surfaceTile.boundingVolumeSourceTile === undefined) {
              // We have no idea where this tile is, so let's just call it partially visible.
              return Visibility.PARTIAL;
          }
  
          var cullingVolume = frameState.cullingVolume;
          var boundingVolume = surfaceTile.orientedBoundingBox;
  
          // Check if the tile is outside the limit area in cartographic space
          surfaceTile.clippedByBoundaries = false;
          var clippedCartographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, this.cartographicLimitRectangle);
          var areaLimitIntersection = Rectangle.simpleIntersection(clippedCartographicLimitRectangle, tile.rectangle, rectangleIntersectionScratch);
          if (!defined(areaLimitIntersection)) {
              return Visibility.NONE;
          }
          if (!Rectangle.equals(areaLimitIntersection, tile.rectangle)) {
              surfaceTile.clippedByBoundaries = true;
          }
  
          if (frameState.mode !== SceneMode.SCENE3D) {
              boundingVolume = boundingSphereScratch;
              BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, tileBoundingRegion.minimumHeight, tileBoundingRegion.maximumHeight, boundingVolume);
              Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
  
              if (frameState.mode === SceneMode.MORPHING && defined(surfaceTile.renderedMesh)) {
                  boundingVolume = BoundingSphere.union(surfaceTile.renderedMesh.boundingSphere3D, boundingVolume, boundingVolume);
              }
          }
  
          var clippingPlanes = this._clippingPlanes;
          if (defined(clippingPlanes) && clippingPlanes.enabled) {
              var planeIntersection = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume);
              tile.isClipped = (planeIntersection !== Intersect.INSIDE);
              if (planeIntersection === Intersect.OUTSIDE) {
                  return Visibility.NONE;
              }
          }
  
          var intersection = cullingVolume.computeVisibility(boundingVolume);
          if (intersection === Intersect.OUTSIDE) {
              return Visibility.NONE;
          }
  
          var ortho3D = frameState.mode === SceneMode.SCENE3D && frameState.camera.frustum instanceof OrthographicFrustum;
          if (frameState.mode === SceneMode.SCENE3D && !ortho3D && defined(occluders)) {
              var occludeePointInScaledSpace = surfaceTile.occludeePointInScaledSpace;
              if (!defined(occludeePointInScaledSpace)) {
                  return intersection;
              }
  
              if (occluders.ellipsoid.isScaledSpacePointVisible(occludeePointInScaledSpace)) {
                  return intersection;
              }
  
              return Visibility.NONE;
          }
  
          return intersection;
      };
  
      /**
       * Determines if the given tile can be refined
       * @param {QuadtreeTile} tile The tile to check.
       * @returns {boolean} True if the tile can be refined, false if it cannot.
       */
      GlobeSurfaceTileProvider.prototype.canRefine = function(tile) {
          // Only allow refinement it we know whether or not the children of this tile exist.
          // For a tileset with `availability`, we'll always be able to refine.
          // We can ask for availability of _any_ child tile because we only need to confirm
          // that we get a yes or no answer, it doesn't matter what the answer is.
          if (defined(tile.data.terrainData)) {
              return true;
          }
          var childAvailable = this.terrainProvider.getTileDataAvailable(tile.x * 2, tile.y * 2, tile.level + 1);
          return childAvailable !== undefined;
      };
  
      var readyImageryScratch = [];
      var canRenderTraversalStack = [];
  
      /**
       * Determines if the given not-fully-loaded tile can be rendered without losing detail that
       * was present last frame as a result of rendering descendant tiles. This method will only be
       * called if this tile's descendants were rendered last frame. If the tile is fully loaded,
       * it is assumed that this method will return true and it will not be called.
       * @param {QuadtreeTile} tile The tile to check.
       * @returns {boolean} True if the tile can be rendered without losing detail.
       */
      GlobeSurfaceTileProvider.prototype.canRenderWithoutLosingDetail = function(tile, frameState) {
          var surfaceTile = tile.data;
  
          var readyImagery = readyImageryScratch;
          readyImagery.length = this._imageryLayers.length;
  
          var terrainReady = false;
          var initialImageryState = false;
          var imagery;
  
          if (defined(surfaceTile)) {
              // We can render even with non-ready terrain as long as all our rendered descendants
              // are missing terrain geometry too. i.e. if we rendered fills for more detailed tiles
              // last frame, it's ok to render a fill for this tile this frame.
              terrainReady = surfaceTile.terrainState === TerrainState.READY;
  
              // Initially assume all imagery layers are ready, unless imagery hasn't been initialized at all.
              initialImageryState = true;
  
              imagery = surfaceTile.imagery;
          }
  
          var i;
          var len;
  
          for (i = 0, len = readyImagery.length; i < len; ++i) {
              readyImagery[i] = initialImageryState;
          }
  
          if (defined(imagery)) {
              for (i = 0, len = imagery.length; i < len; ++i) {
                  var tileImagery = imagery[i];
                  var loadingImagery = tileImagery.loadingImagery;
                  var isReady = !defined(loadingImagery) || loadingImagery.state === ImageryState.FAILED || loadingImagery.state === ImageryState.INVALID;
                  var layerIndex = (tileImagery.loadingImagery || tileImagery.readyImagery).imageryLayer._layerIndex;
  
                  // For a layer to be ready, all tiles belonging to that layer must be ready.
                  readyImagery[layerIndex] = isReady && readyImagery[layerIndex];
              }
          }
  
          var lastFrame = this.quadtree._lastSelectionFrameNumber;
  
          // Traverse the descendants looking for one with terrain or imagery that is not loaded on this tile.
          var stack = canRenderTraversalStack;
          stack.length = 0;
          stack.push(tile.southwestChild, tile.southeastChild, tile.northwestChild, tile.northeastChild);
  
          while (stack.length > 0) {
              var descendant = stack.pop();
              var lastFrameSelectionResult = descendant._lastSelectionResultFrame === lastFrame ? descendant._lastSelectionResult : TileSelectionResult.NONE;
  
              if (lastFrameSelectionResult === TileSelectionResult.RENDERED) {
                  var descendantSurface = descendant.data;
  
                  if (!defined(descendantSurface)) {
                      // Descendant has no data, so it can't block rendering.
                      continue;
                  }
  
                  if (!terrainReady && descendant.data.terrainState === TerrainState.READY) {
                      // Rendered descendant has real terrain, but we don't. Rendering is blocked.
                      return false;
                  }
  
                  var descendantImagery = descendant.data.imagery;
                  for (i = 0, len = descendantImagery.length; i < len; ++i) {
                      var descendantTileImagery = descendantImagery[i];
                      var descendantLoadingImagery = descendantTileImagery.loadingImagery;
                      var descendantIsReady = !defined(descendantLoadingImagery) || descendantLoadingImagery.state === ImageryState.FAILED || descendantLoadingImagery.state === ImageryState.INVALID;
                      var descendantLayerIndex = (descendantTileImagery.loadingImagery || descendantTileImagery.readyImagery).imageryLayer._layerIndex;
  
                      // If this imagery tile of a descendant is ready but the layer isn't ready in this tile,
                      // then rendering is blocked.
                      if (descendantIsReady && !readyImagery[descendantLayerIndex]) {
                          return false;
                      }
                  }
              } else if (lastFrameSelectionResult === TileSelectionResult.REFINED) {
                  stack.push(descendant.southwestChild, descendant.southeastChild, descendant.northwestChild, descendant.northeastChild);
              }
          }
  
          return true;
      };
  
      var tileDirectionScratch = new Cartesian3();
  
      /**
       * Determines the priority for loading this tile. Lower priority values load sooner.
       * @param {QuatreeTile} tile The tile.
       * @param {FrameState} frameState The frame state.
       * @returns {Number} The load priority value.
       */
      GlobeSurfaceTileProvider.prototype.computeTileLoadPriority = function(tile, frameState) {
          var surfaceTile = tile.data;
          if (surfaceTile === undefined) {
              return 0.0;
          }
  
          var obb = surfaceTile.orientedBoundingBox;
          if (obb === undefined) {
              return 0.0;
          }
  
          var cameraPosition = frameState.camera.positionWC;
          var cameraDirection = frameState.camera.directionWC;
          var tileDirection = Cartesian3.subtract(obb.center, cameraPosition, tileDirectionScratch);
          var magnitude = Cartesian3.magnitude(tileDirection);
          if (magnitude < CesiumMath.EPSILON5) {
              return 0.0;
          }
          Cartesian3.divideByScalar(tileDirection, magnitude, tileDirection);
          return (1.0 - Cartesian3.dot(tileDirection, cameraDirection)) * tile._distance;
      };
  
      var modifiedModelViewScratch = new Matrix4();
      var modifiedModelViewProjectionScratch = new Matrix4();
      var tileRectangleScratch = new Cartesian4();
      var localizedCartographicLimitRectangleScratch = new Cartesian4();
      var rtcScratch = new Cartesian3();
      var centerEyeScratch = new Cartesian3();
      var southwestScratch = new Cartesian3();
      var northeastScratch = new Cartesian3();
  
      /**
       * Shows a specified tile in this frame.  The provider can cause the tile to be shown by adding
       * render commands to the commandList, or use any other method as appropriate.  The tile is not
       * expected to be visible next frame as well, unless this method is called next frame, too.
       *
       * @param {QuadtreeTile} tile The tile instance.
       * @param {FrameState} frameState The state information of the current rendering frame.
       */
      GlobeSurfaceTileProvider.prototype.showTileThisFrame = function(tile, frameState) {
          var readyTextureCount = 0;
          var tileImageryCollection = tile.data.imagery;
          for (var i = 0, len = tileImageryCollection.length; i < len; ++i) {
              var tileImagery = tileImageryCollection[i];
              if (defined(tileImagery.readyImagery) && tileImagery.readyImagery.imageryLayer.alpha !== 0.0) {
                  ++readyTextureCount;
              }
          }
  
          var tileSet = this._tilesToRenderByTextureCount[readyTextureCount];
          if (!defined(tileSet)) {
              tileSet = [];
              this._tilesToRenderByTextureCount[readyTextureCount] = tileSet;
          }
  
          tileSet.push(tile);
  
          var surfaceTile = tile.data;
          if (!defined(surfaceTile.vertexArray)) {
              this._hasFillTilesThisFrame = true;
          } else {
              this._hasLoadedTilesThisFrame = true;
          }
  
          var debug = this._debug;
          ++debug.tilesRendered;
          debug.texturesRendered += readyTextureCount;
      };
  
      var cornerPositionsScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
  
      function computeOccludeePoint(tileProvider, center, rectangle, height, result) {
          var ellipsoidalOccluder = tileProvider.quadtree._occluders.ellipsoid;
          var ellipsoid = ellipsoidalOccluder.ellipsoid;
  
          var cornerPositions = cornerPositionsScratch;
          Cartesian3.fromRadians(rectangle.west, rectangle.south, height, ellipsoid, cornerPositions[0]);
          Cartesian3.fromRadians(rectangle.east, rectangle.south, height, ellipsoid, cornerPositions[1]);
          Cartesian3.fromRadians(rectangle.west, rectangle.north, height, ellipsoid, cornerPositions[2]);
          Cartesian3.fromRadians(rectangle.east, rectangle.north, height, ellipsoid, cornerPositions[3]);
  
          return ellipsoidalOccluder.computeHorizonCullingPoint(center, cornerPositions, result);
      }
  
      /**
       * Gets the distance from the camera to the closest point on the tile.  This is used for level-of-detail selection.
       *
       * @param {QuadtreeTile} tile The tile instance.
       * @param {FrameState} frameState The state information of the current rendering frame.
       *
       * @returns {Number} The distance from the camera to the closest point on the tile, in meters.
       */
      GlobeSurfaceTileProvider.prototype.computeDistanceToTile = function(tile, frameState) {
          // The distance should be:
          // 1. the actual distance to the tight-fitting bounding volume, or
          // 2. a distance that is equal to or greater than the actual distance to the tight-fitting bounding volume.
          //
          // When we don't know the min/max heights for a tile, but we do know the min/max of an ancestor tile, we can
          // build a tight-fitting bounding volume horizontally, but not vertically. The min/max heights from the
          // ancestor will likely form a volume that is much bigger than it needs to be. This means that the volume may
          // be deemed to be much closer to the camera than it really is, causing us to select tiles that are too detailed.
          // Loading too-detailed tiles is super expensive, so we don't want to do that. We don't know where the child
          // tile really lies within the parent range of heights, but we _do_ know the child tile can't be any closer than
          // the ancestor height surface (min or max) that is _farthest away_ from the camera. So if we compute distance
          // based that conservative metric, we may end up loading tiles that are not detailed enough, but that's much
          // better (faster) than loading tiles that are too detailed.
  
          var heightSource = updateTileBoundingRegion(tile, this.terrainProvider, frameState);
          var surfaceTile = tile.data;
          var tileBoundingRegion = surfaceTile.tileBoundingRegion;
  
          if (heightSource === undefined) {
              // Can't find any min/max heights anywhere? Ok, let's just say the
              // tile is really far away so we'll load and render it rather than
              // refining.
              return 9999999999.0;
          } else if (surfaceTile.boundingVolumeSourceTile !== heightSource) {
              // Heights are from a new source tile, so update the bounding volume.
              surfaceTile.boundingVolumeSourceTile = heightSource;
              surfaceTile.orientedBoundingBox = OrientedBoundingBox.fromRectangle(
                  tile.rectangle,
                  tileBoundingRegion.minimumHeight,
                  tileBoundingRegion.maximumHeight,
                  tile.tilingScheme.ellipsoid,
                  surfaceTile.orientedBoundingBox);
  
              surfaceTile.occludeePointInScaledSpace = computeOccludeePoint(this, surfaceTile.orientedBoundingBox.center, tile.rectangle, tileBoundingRegion.maximumHeight, surfaceTile.occludeePointInScaledSpace);
          }
  
          var min = tileBoundingRegion.minimumHeight;
          var max = tileBoundingRegion.maximumHeight;
  
          if (surfaceTile.boundingVolumeSourceTile !== tile) {
              var cameraHeight = frameState.camera.positionCartographic.height;
              var distanceToMin = Math.abs(cameraHeight - min);
              var distanceToMax = Math.abs(cameraHeight - max);
              if (distanceToMin > distanceToMax) {
                  tileBoundingRegion.minimumHeight = min;
                  tileBoundingRegion.maximumHeight = min;
              } else {
                  tileBoundingRegion.minimumHeight = max;
                  tileBoundingRegion.maximumHeight = max;
              }
          }
  
          var result = tileBoundingRegion.distanceToCamera(frameState);
  
          tileBoundingRegion.minimumHeight = min;
          tileBoundingRegion.maximumHeight = max;
  
          return result;
      };
  
      function updateTileBoundingRegion(tile, terrainProvider, frameState) {
          var surfaceTile = tile.data;
          if (surfaceTile === undefined) {
              surfaceTile = tile.data = new GlobeSurfaceTile();
          }
  
          if (surfaceTile.tileBoundingRegion === undefined) {
              surfaceTile.tileBoundingRegion = new TileBoundingRegion({
                  computeBoundingVolumes : false,
                  rectangle : tile.rectangle,
                  ellipsoid : tile.tilingScheme.ellipsoid,
                  minimumHeight : 0.0,
                  maximumHeight : 0.0
              });
          }
  
          var terrainData = surfaceTile.terrainData;
          var mesh = surfaceTile.mesh;
          var tileBoundingRegion = surfaceTile.tileBoundingRegion;
  
          if (mesh !== undefined && mesh.minimumHeight !== undefined && mesh.maximumHeight !== undefined) {
              // We have tight-fitting min/max heights from the mesh.
              tileBoundingRegion.minimumHeight = mesh.minimumHeight;
              tileBoundingRegion.maximumHeight = mesh.maximumHeight;
              return tile;
          }
  
          if (terrainData !== undefined && terrainData._minimumHeight !== undefined && terrainData._maximumHeight !== undefined) {
              // We have tight-fitting min/max heights from the terrain data.
              tileBoundingRegion.minimumHeight = terrainData._minimumHeight * frameState.terrainExaggeration;
              tileBoundingRegion.maximumHeight = terrainData._maximumHeight * frameState.terrainExaggeration;
              return tile;
          }
  
          // No accurate min/max heights available, so we're stuck with min/max heights from an ancestor tile.
          tileBoundingRegion.minimumHeight = Number.NaN;
          tileBoundingRegion.maximumHeight = Number.NaN;
  
          var ancestor = tile.parent;
          while (ancestor !== undefined) {
              var ancestorSurfaceTile = ancestor.data;
              if (ancestorSurfaceTile !== undefined) {
                  var ancestorMesh = ancestorSurfaceTile.mesh;
                  if (ancestorMesh !== undefined && ancestorMesh.minimumHeight !== undefined && ancestorMesh.maximumHeight !== undefined) {
                      tileBoundingRegion.minimumHeight = ancestorMesh.minimumHeight;
                      tileBoundingRegion.maximumHeight = ancestorMesh.maximumHeight;
                      return ancestor;
                  }
  
                  var ancestorTerrainData = ancestorSurfaceTile.terrainData;
                  if (ancestorTerrainData !== undefined && ancestorTerrainData._minimumHeight !== undefined && ancestorTerrainData._maximumHeight !== undefined) {
                      tileBoundingRegion.minimumHeight = ancestorTerrainData._minimumHeight * frameState.terrainExaggeration;
                      tileBoundingRegion.maximumHeight = ancestorTerrainData._maximumHeight * frameState.terrainExaggeration;
                      return ancestor;
                  }
              }
              ancestor = ancestor.parent;
          }
  
          return undefined;
      }
  
      /**
       * Returns true if this object was destroyed; otherwise, false.
       * <br /><br />
       * If this object was destroyed, it should not be used; calling any function other than
       * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
       *
       * @returns {Boolean} True if this object was destroyed; otherwise, false.
       *
       * @see GlobeSurfaceTileProvider#destroy
       */
      GlobeSurfaceTileProvider.prototype.isDestroyed = function() {
          return false;
      };
  
      /**
       * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
       * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
       * <br /><br />
       * Once an object is destroyed, it should not be used; calling any function other than
       * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
       * assign the return value (<code>undefined</code>) to the object as done in the example.
       *
       * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
       *
       *
       * @example
       * provider = provider && provider();
       *
       * @see GlobeSurfaceTileProvider#isDestroyed
       */
      GlobeSurfaceTileProvider.prototype.destroy = function() {
          this._tileProvider = this._tileProvider && this._tileProvider.destroy();
          this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy();
  
          return destroyObject(this);
      };
  
      function getTileReadyCallback(tileImageriesToFree, layer, terrainProvider) {
          return function(tile) {
              var tileImagery;
              var imagery;
              var startIndex = -1;
              var tileImageryCollection = tile.data.imagery;
              var length = tileImageryCollection.length;
              var i;
              for (i = 0; i < length; ++i) {
                  tileImagery = tileImageryCollection[i];
                  imagery = defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery);
                  if (imagery.imageryLayer === layer) {
                      startIndex = i;
                      break;
                  }
              }
  
              if (startIndex !== -1) {
                  var endIndex = startIndex + tileImageriesToFree;
                  tileImagery = tileImageryCollection[endIndex];
                  imagery = defined(tileImagery) ? defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery) : undefined;
                  if (!defined(imagery) || imagery.imageryLayer !== layer) {
                      // Return false to keep the callback if we have to wait on the skeletons
                      // Return true to remove the callback if something went wrong
                      return !(layer._createTileImagerySkeletons(tile, terrainProvider, endIndex));
                  }
  
                  for (i = startIndex; i < endIndex; ++i) {
                      tileImageryCollection[i].freeResources();
                  }
  
                  tileImageryCollection.splice(startIndex, tileImageriesToFree);
              }
  
              return true; // Everything is done, so remove the callback
          };
      }
  
      GlobeSurfaceTileProvider.prototype._onLayerAdded = function(layer, index) {
          if (layer.show) {
              var terrainProvider = this._terrainProvider;
  
              var that = this;
              var imageryProvider = layer.imageryProvider;
              var tileImageryUpdatedEvent = this._imageryLayersUpdatedEvent;
              imageryProvider._reload = function() {
                  // Clear the layer's cache
                  layer._imageryCache = {};
  
                  that._quadtree.forEachLoadedTile(function(tile) {
                      // If this layer is still waiting to for the loaded callback, just return
                      if (defined(tile._loadedCallbacks[layer._layerIndex])) {
                          return;
                      }
  
                      var i;
  
                      // Figure out how many TileImageries we will need to remove and where to insert new ones
                      var tileImageryCollection = tile.data.imagery;
                      var length = tileImageryCollection.length;
                      var startIndex = -1;
                      var tileImageriesToFree = 0;
                      for (i = 0; i < length; ++i) {
                          var tileImagery = tileImageryCollection[i];
                          var imagery = defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery);
                          if (imagery.imageryLayer === layer) {
                              if (startIndex === -1) {
                                  startIndex = i;
                              }
  
                              ++tileImageriesToFree;
                          } else if (startIndex !== -1) {
                              // iterated past the section of TileImageries belonging to this layer, no need to continue.
                              break;
                          }
                      }
  
                      if (startIndex === -1) {
                          return;
                      }
  
                      // Insert immediately after existing TileImageries
                      var insertionPoint = startIndex + tileImageriesToFree;
  
                      // Create new TileImageries for all loaded tiles
                      if (layer._createTileImagerySkeletons(tile, terrainProvider, insertionPoint)) {
                          // Add callback to remove old TileImageries when the new TileImageries are ready
                          tile._loadedCallbacks[layer._layerIndex] = getTileReadyCallback(tileImageriesToFree, layer, terrainProvider);
  
                          tile.state = QuadtreeTileLoadState.LOADING;
                      }
                  });
              };
  
              // create TileImageries for this layer for all previously loaded tiles
              this._quadtree.forEachLoadedTile(function(tile) {
                  if (layer._createTileImagerySkeletons(tile, terrainProvider)) {
                      tile.state = QuadtreeTileLoadState.LOADING;
  
                      // Tiles that are not currently being rendered need to load the new layer before they're renderable.
                      // We don't mark the rendered tiles non-renderable, though, because that would make the globe disappear.
                      if (tile.level !== 0 && (tile._lastSelectionResultFrame !== that.quadtree._lastSelectionFrameNumber || tile._lastSelectionResult !== TileSelectionResult.RENDERED)) {
                          tile.renderable = false;
                      }
                  }
              });
  
              this._layerOrderChanged = true;
              tileImageryUpdatedEvent.raiseEvent();
          }
      };
  
      GlobeSurfaceTileProvider.prototype._onLayerRemoved = function(layer, index) {
          // destroy TileImagerys for this layer for all previously loaded tiles
          this._quadtree.forEachLoadedTile(function(tile) {
              var tileImageryCollection = tile.data.imagery;
  
              var startIndex = -1;
              var numDestroyed = 0;
              for (var i = 0, len = tileImageryCollection.length; i < len; ++i) {
                  var tileImagery = tileImageryCollection[i];
                  var imagery = tileImagery.loadingImagery;
                  if (!defined(imagery)) {
                      imagery = tileImagery.readyImagery;
                  }
                  if (imagery.imageryLayer === layer) {
                      if (startIndex === -1) {
                          startIndex = i;
                      }
  
                      tileImagery.freeResources();
                      ++numDestroyed;
                  } else if (startIndex !== -1) {
                      // iterated past the section of TileImagerys belonging to this layer, no need to continue.
                      break;
                  }
              }
  
              if (startIndex !== -1) {
                  tileImageryCollection.splice(startIndex, numDestroyed);
              }
          });
  
          if (defined(layer.imageryProvider)) {
              layer.imageryProvider._reload = undefined;
          }
  
          this._imageryLayersUpdatedEvent.raiseEvent();
      };
  
      GlobeSurfaceTileProvider.prototype._onLayerMoved = function(layer, newIndex, oldIndex) {
          this._layerOrderChanged = true;
          this._imageryLayersUpdatedEvent.raiseEvent();
      };
  
      GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden = function(layer, index, show) {
          if (show) {
              this._onLayerAdded(layer, index);
          } else {
              this._onLayerRemoved(layer, index);
          }
      };
  
      var scratchClippingPlaneMatrix = new Matrix4();
      function createTileUniformMap(frameState, globeSurfaceTileProvider) {
          var uniformMap = {
              u_initialColor : function() {
                  return this.properties.initialColor;
              },
              u_fillHighlightColor : function() {
                  return this.properties.fillHighlightColor;
              },
              u_zoomedOutOceanSpecularIntensity : function() {
                  return this.properties.zoomedOutOceanSpecularIntensity;
              },
              u_oceanNormalMap : function() {
                  return this.properties.oceanNormalMap;
              },
              u_lightingFadeDistance : function() {
                  return this.properties.lightingFadeDistance;
              },
              u_nightFadeDistance : function() {
                  return this.properties.nightFadeDistance;
              },
              u_center3D : function() {
                  return this.properties.center3D;
              },
              u_tileRectangle : function() {
                  return this.properties.tileRectangle;
              },
              u_modifiedModelView : function() {
                  var viewMatrix = frameState.context.uniformState.view;
                  var centerEye = Matrix4.multiplyByPoint(viewMatrix, this.properties.rtc, centerEyeScratch);
                  Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewScratch);
                  return modifiedModelViewScratch;
              },
              u_modifiedModelViewProjection : function() {
                  var viewMatrix = frameState.context.uniformState.view;
                  var projectionMatrix = frameState.context.uniformState.projection;
                  var centerEye = Matrix4.multiplyByPoint(viewMatrix, this.properties.rtc, centerEyeScratch);
                  Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewProjectionScratch);
                  Matrix4.multiply(projectionMatrix, modifiedModelViewProjectionScratch, modifiedModelViewProjectionScratch);
                  return modifiedModelViewProjectionScratch;
              },
              u_dayTextures : function() {
                  return this.properties.dayTextures;
              },
              u_dayTextureTranslationAndScale : function() {
                  return this.properties.dayTextureTranslationAndScale;
              },
              u_dayTextureTexCoordsRectangle : function() {
                  return this.properties.dayTextureTexCoordsRectangle;
              },
              u_dayTextureUseWebMercatorT : function() {
                  return this.properties.dayTextureUseWebMercatorT;
              },
              u_dayTextureAlpha : function() {
                  return this.properties.dayTextureAlpha;
              },
              u_dayTextureBrightness : function() {
                  return this.properties.dayTextureBrightness;
              },
              u_dayTextureContrast : function() {
                  return this.properties.dayTextureContrast;
              },
              u_dayTextureHue : function() {
                  return this.properties.dayTextureHue;
              },
              u_dayTextureSaturation : function() {
                  return this.properties.dayTextureSaturation;
              },
              u_dayTextureOneOverGamma : function() {
                  return this.properties.dayTextureOneOverGamma;
              },
              u_dayIntensity : function() {
                  return this.properties.dayIntensity;
              },
              u_southAndNorthLatitude : function() {
                  return this.properties.southAndNorthLatitude;
              },
              u_southMercatorYAndOneOverHeight : function() {
                  return this.properties.southMercatorYAndOneOverHeight;
              },
              u_waterMask : function() {
                  return this.properties.waterMask;
              },
              u_waterMaskTranslationAndScale : function() {
                  return this.properties.waterMaskTranslationAndScale;
              },
              u_minMaxHeight : function() {
                  return this.properties.minMaxHeight;
              },
              u_scaleAndBias : function() {
                  return this.properties.scaleAndBias;
              },
              u_dayTextureSplit : function() {
                  return this.properties.dayTextureSplit;
              },
              u_dayTextureCutoutRectangles : function() {
                  return this.properties.dayTextureCutoutRectangles;
              },
              u_clippingPlanes : function() {
                  var clippingPlanes = globeSurfaceTileProvider._clippingPlanes;
                  if (defined(clippingPlanes) && defined(clippingPlanes.texture)) {
                      // Check in case clippingPlanes hasn't been updated yet.
                      return clippingPlanes.texture;
                  }
                  return frameState.context.defaultTexture;
              },
              u_cartographicLimitRectangle : function() {
                  return this.properties.localizedCartographicLimitRectangle;
              },
              u_clippingPlanesMatrix : function() {
                  var clippingPlanes = globeSurfaceTileProvider._clippingPlanes;
                  return defined(clippingPlanes) ? Matrix4.multiply(frameState.context.uniformState.view, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix) : Matrix4.IDENTITY;
              },
              u_clippingPlanesEdgeStyle : function() {
                  var style = this.properties.clippingPlanesEdgeColor;
                  style.alpha = this.properties.clippingPlanesEdgeWidth;
                  return style;
              },
              u_minimumBrightness : function() {
                  return frameState.fog.minimumBrightness;
              },
              u_hsbShift : function() {
                  return this.properties.hsbShift;
              },
  
              // make a separate object so that changes to the properties are seen on
              // derived commands that combine another uniform map with this one.
              properties : {
                  initialColor : new Cartesian4(0.0, 0.0, 0.5, 1.0),
                  fillHighlightColor : new Color(0.0, 0.0, 0.0, 0.0),
                  zoomedOutOceanSpecularIntensity : 0.5,
                  oceanNormalMap : undefined,
                  lightingFadeDistance : new Cartesian2(6500000.0, 9000000.0),
                  nightFadeDistance : new Cartesian2(10000000.0, 40000000.0),
                  hsbShift : new Cartesian3(),
  
                  center3D : undefined,
                  rtc : new Cartesian3(),
                  modifiedModelView : new Matrix4(),
                  tileRectangle : new Cartesian4(),
  
                  dayTextures : [],
                  dayTextureTranslationAndScale : [],
                  dayTextureTexCoordsRectangle : [],
                  dayTextureUseWebMercatorT : [],
                  dayTextureAlpha : [],
                  dayTextureBrightness : [],
                  dayTextureContrast : [],
                  dayTextureHue : [],
                  dayTextureSaturation : [],
                  dayTextureOneOverGamma : [],
                  dayTextureSplit : [],
                  dayTextureCutoutRectangles : [],
                  dayIntensity : 0.0,
  
                  southAndNorthLatitude : new Cartesian2(),
                  southMercatorYAndOneOverHeight : new Cartesian2(),
  
                  waterMask : undefined,
                  waterMaskTranslationAndScale : new Cartesian4(),
  
                  minMaxHeight : new Cartesian2(),
                  scaleAndBias : new Matrix4(),
                  clippingPlanesEdgeColor : Color.clone(Color.WHITE),
                  clippingPlanesEdgeWidth : 0.0,
  
                  localizedCartographicLimitRectangle : new Cartesian4()
              }
          };
  
          return uniformMap;
      }
  
      function createWireframeVertexArrayIfNecessary(context, provider, tile) {
          var surfaceTile = tile.data;
  
          var mesh;
          var vertexArray;
  
          if (defined(surfaceTile.vertexArray)) {
              mesh = surfaceTile.mesh;
              vertexArray = surfaceTile.vertexArray;
          } else if (defined(surfaceTile.fill) && defined(surfaceTile.fill.vertexArray)) {
              mesh = surfaceTile.fill.mesh;
              vertexArray = surfaceTile.fill.vertexArray;
          }
  
          if (!defined(mesh) || !defined(vertexArray)) {
              return;
          }
  
          if (defined(surfaceTile.wireframeVertexArray)) {
              if (surfaceTile.wireframeVertexArray.mesh === mesh) {
                  return;
              }
  
              surfaceTile.wireframeVertexArray.destroy();
              surfaceTile.wireframeVertexArray = undefined;
          }
  
          surfaceTile.wireframeVertexArray = createWireframeVertexArray(context, vertexArray, mesh);
          surfaceTile.wireframeVertexArray.mesh = mesh;
      }
  
      /**
       * Creates a vertex array for wireframe rendering of a terrain tile.
       *
       * @private
       *
       * @param {Context} context The context in which to create the vertex array.
       * @param {VertexArray} vertexArray The existing, non-wireframe vertex array.  The new vertex array
       *                      will share vertex buffers with this existing one.
       * @param {TerrainMesh} terrainMesh The terrain mesh containing non-wireframe indices.
       * @returns {VertexArray} The vertex array for wireframe rendering.
       */
      function createWireframeVertexArray(context, vertexArray, terrainMesh) {
          var indices = terrainMesh.indices;
  
          var geometry = {
              indices : indices,
              primitiveType : PrimitiveType.TRIANGLES
          };
  
          GeometryPipeline.toWireframe(geometry);
  
          var wireframeIndices = geometry.indices;
          var wireframeIndexBuffer = Buffer.createIndexBuffer({
              context : context,
              typedArray : wireframeIndices,
              usage : BufferUsage.STATIC_DRAW,
              indexDatatype : IndexDatatype.fromSizeInBytes(wireframeIndices.BYTES_PER_ELEMENT)
          });
          return new VertexArray({
              context : context,
              attributes : vertexArray._attributes,
              indexBuffer : wireframeIndexBuffer
          });
      }
  
      var getDebugOrientedBoundingBox;
      var getDebugBoundingSphere;
      var debugDestroyPrimitive;
  
      (function() {
          var instanceOBB = new GeometryInstance({
              geometry : BoxOutlineGeometry.fromDimensions({dimensions : new Cartesian3(2.0, 2.0, 2.0)})
          });
          var instanceSphere = new GeometryInstance({
              geometry : new SphereOutlineGeometry({radius : 1.0})
          });
          var modelMatrix = new Matrix4();
          var previousVolume;
          var primitive;
  
          function createDebugPrimitive(instance) {
              return new Primitive({
                  geometryInstances : instance,
                  appearance : new PerInstanceColorAppearance({
                      translucent : false,
                      flat : true
                  }),
                  asynchronous : false
              });
          }
  
          getDebugOrientedBoundingBox = function(obb, color) {
              if (obb === previousVolume) {
                  return primitive;
              }
              debugDestroyPrimitive();
  
              previousVolume = obb;
              modelMatrix = Matrix4.fromRotationTranslation(obb.halfAxes, obb.center, modelMatrix);
  
              instanceOBB.modelMatrix = modelMatrix;
              instanceOBB.attributes.color = ColorGeometryInstanceAttribute.fromColor(color);
  
              primitive = createDebugPrimitive(instanceOBB);
              return primitive;
          };
  
          getDebugBoundingSphere = function(sphere, color) {
              if (sphere === previousVolume) {
                  return primitive;
              }
              debugDestroyPrimitive();
  
              previousVolume = sphere;
              modelMatrix = Matrix4.fromTranslation(sphere.center, modelMatrix);
              modelMatrix = Matrix4.multiplyByUniformScale(modelMatrix, sphere.radius, modelMatrix);
  
              instanceSphere.modelMatrix = modelMatrix;
              instanceSphere.attributes.color = ColorGeometryInstanceAttribute.fromColor(color);
  
              primitive = createDebugPrimitive(instanceSphere);
              return primitive;
          };
  
          debugDestroyPrimitive = function() {
              if (defined(primitive)) {
                  primitive.destroy();
                  primitive = undefined;
                  previousVolume = undefined;
              }
          };
      })();
  
      var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0);
      var surfaceShaderSetOptionsScratch = {
          frameState : undefined,
          surfaceTile : undefined,
          numberOfDayTextures : undefined,
          applyBrightness : undefined,
          applyContrast : undefined,
          applyHue : undefined,
          applySaturation : undefined,
          applyGamma : undefined,
          applyAlpha : undefined,
          applySplit : undefined,
          showReflectiveOcean : undefined,
          showOceanWaves : undefined,
          enableLighting : undefined,
          showGroundAtmosphere : undefined,
          perFragmentGroundAtmosphere : undefined,
          hasVertexNormals : undefined,
          useWebMercatorProjection : undefined,
          enableFog : undefined,
          enableClippingPlanes : undefined,
          clippingPlanes : undefined,
          clippedByBoundaries : undefined,
          hasImageryLayerCutout : undefined,
          colorCorrect : undefined
      };
  
      function addDrawCommandsForTile(tileProvider, tile, frameState) {
          var surfaceTile = tile.data;
  
          if (!defined(surfaceTile.vertexArray)) {
              if (surfaceTile.fill === undefined) {
                  // No fill was created for this tile, probably because this tile is not connected to
                  // any renderable tiles. So create a simple tile in the middle of the tile's possible
                  // height range.
                  surfaceTile.fill = new TerrainFillMesh(tile);
              }
              surfaceTile.fill.update(tileProvider, frameState);
          }
  
          var creditDisplay = frameState.creditDisplay;
  
          var terrainData = surfaceTile.terrainData;
          if (defined(terrainData) && defined(terrainData.credits)) {
              var tileCredits = terrainData.credits;
              for (var tileCreditIndex = 0,
                       tileCreditLength = tileCredits.length; tileCreditIndex < tileCreditLength; ++tileCreditIndex) {
                  creditDisplay.addCredit(tileCredits[tileCreditIndex]);
              }
          }
  
          var maxTextures = ContextLimits.maximumTextureImageUnits;
  
          var waterMaskTexture = surfaceTile.waterMaskTexture;
          var waterMaskTranslationAndScale = surfaceTile.waterMaskTranslationAndScale;
          if (!defined(waterMaskTexture) && defined(surfaceTile.fill)) {
              waterMaskTexture = surfaceTile.fill.waterMaskTexture;
              waterMaskTranslationAndScale = surfaceTile.fill.waterMaskTranslationAndScale;
          }
  
          var showReflectiveOcean = tileProvider.hasWaterMask && defined(waterMaskTexture);
          var oceanNormalMap = tileProvider.oceanNormalMap;
          var showOceanWaves = showReflectiveOcean && defined(oceanNormalMap);
          var hasVertexNormals = tileProvider.terrainProvider.ready && tileProvider.terrainProvider.hasVertexNormals;
          var enableFog = frameState.fog.enabled;
          var showGroundAtmosphere = tileProvider.showGroundAtmosphere;
          var castShadows = ShadowMode.castShadows(tileProvider.shadows);
          var receiveShadows = ShadowMode.receiveShadows(tileProvider.shadows);
  
          var hueShift = tileProvider.hueShift;
          var saturationShift = tileProvider.saturationShift;
          var brightnessShift = tileProvider.brightnessShift;
  
          var colorCorrect = !(CesiumMath.equalsEpsilon(hueShift, 0.0, CesiumMath.EPSILON7) &&
                               CesiumMath.equalsEpsilon(saturationShift, 0.0, CesiumMath.EPSILON7) &&
                               CesiumMath.equalsEpsilon(brightnessShift, 0.0, CesiumMath.EPSILON7));
  
          var perFragmentGroundAtmosphere = false;
          if (showGroundAtmosphere) {
              var mode = frameState.mode;
              var camera = frameState.camera;
              var cameraDistance;
              if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
                  cameraDistance = camera.positionCartographic.height;
              } else {
                  cameraDistance = Cartesian3.magnitude(camera.positionWC);
              }
              var fadeOutDistance = tileProvider.nightFadeOutDistance;
              if (mode !== SceneMode.SCENE3D) {
                  fadeOutDistance -= frameState.mapProjection.ellipsoid.maximumRadius;
              }
              perFragmentGroundAtmosphere = cameraDistance > fadeOutDistance;
          }
  
          if (showReflectiveOcean) {
              --maxTextures;
          }
          if (showOceanWaves) {
              --maxTextures;
          }
  
          var mesh = surfaceTile.renderedMesh;
          var rtc = mesh.center;
          var encoding = mesh.encoding;
  
          // Not used in 3D.
          var tileRectangle = tileRectangleScratch;
  
          // Only used for Mercator projections.
          var southLatitude = 0.0;
          var northLatitude = 0.0;
          var southMercatorY = 0.0;
          var oneOverMercatorHeight = 0.0;
  
          var useWebMercatorProjection = false;
  
          if (frameState.mode !== SceneMode.SCENE3D) {
              var projection = frameState.mapProjection;
              var southwest = projection.project(Rectangle.southwest(tile.rectangle), southwestScratch);
              var northeast = projection.project(Rectangle.northeast(tile.rectangle), northeastScratch);
  
              tileRectangle.x = southwest.x;
              tileRectangle.y = southwest.y;
              tileRectangle.z = northeast.x;
              tileRectangle.w = northeast.y;
  
              // In 2D and Columbus View, use the center of the tile for RTC rendering.
              if (frameState.mode !== SceneMode.MORPHING) {
                  rtc = rtcScratch;
                  rtc.x = 0.0;
                  rtc.y = (tileRectangle.z + tileRectangle.x) * 0.5;
                  rtc.z = (tileRectangle.w + tileRectangle.y) * 0.5;
                  tileRectangle.x -= rtc.y;
                  tileRectangle.y -= rtc.z;
                  tileRectangle.z -= rtc.y;
                  tileRectangle.w -= rtc.z;
              }
  
              if (frameState.mode === SceneMode.SCENE2D && encoding.quantization === TerrainQuantization.BITS12) {
                  // In 2D, the texture coordinates of the tile are interpolated over the rectangle to get the position in the vertex shader.
                  // When the texture coordinates are quantized, error is introduced. This can be seen through the 1px wide cracking
                  // between the quantized tiles in 2D. To compensate for the error, move the expand the rectangle in each direction by
                  // half the error amount.
                  var epsilon = (1.0 / (Math.pow(2.0, 12.0) - 1.0)) * 0.5;
                  var widthEpsilon = (tileRectangle.z - tileRectangle.x) * epsilon;
                  var heightEpsilon = (tileRectangle.w - tileRectangle.y) * epsilon;
                  tileRectangle.x -= widthEpsilon;
                  tileRectangle.y -= heightEpsilon;
                  tileRectangle.z += widthEpsilon;
                  tileRectangle.w += heightEpsilon;
              }
  
              if (projection instanceof WebMercatorProjection) {
                  southLatitude = tile.rectangle.south;
                  northLatitude = tile.rectangle.north;
  
                  southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(southLatitude);
  
                  oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(northLatitude) - southMercatorY);
  
                  useWebMercatorProjection = true;
              }
          }
  
          var surfaceShaderSetOptions = surfaceShaderSetOptionsScratch;
          surfaceShaderSetOptions.frameState = frameState;
          surfaceShaderSetOptions.surfaceTile = surfaceTile;
          surfaceShaderSetOptions.showReflectiveOcean = showReflectiveOcean;
          surfaceShaderSetOptions.showOceanWaves = showOceanWaves;
          surfaceShaderSetOptions.enableLighting = tileProvider.enableLighting;
          surfaceShaderSetOptions.showGroundAtmosphere = showGroundAtmosphere;
          surfaceShaderSetOptions.perFragmentGroundAtmosphere = perFragmentGroundAtmosphere;
          surfaceShaderSetOptions.hasVertexNormals = hasVertexNormals;
          surfaceShaderSetOptions.useWebMercatorProjection = useWebMercatorProjection;
          surfaceShaderSetOptions.clippedByBoundaries = surfaceTile.clippedByBoundaries;
  
          var tileImageryCollection = surfaceTile.imagery;
          var imageryIndex = 0;
          var imageryLen = tileImageryCollection.length;
  
          var firstPassRenderState = tileProvider._renderState;
          var otherPassesRenderState = tileProvider._blendRenderState;
          var renderState = firstPassRenderState;
  
          var initialColor = tileProvider._firstPassInitialColor;
  
          var context = frameState.context;
  
          if (!defined(tileProvider._debug.boundingSphereTile)) {
              debugDestroyPrimitive();
          }
  
          do {
              var numberOfDayTextures = 0;
  
              var command;
              var uniformMap;
  
              if (tileProvider._drawCommands.length <= tileProvider._usedDrawCommands) {
                  command = new DrawCommand();
                  command.owner = tile;
                  command.cull = false;
                  command.boundingVolume = new BoundingSphere();
                  command.orientedBoundingBox = undefined;
  
                  uniformMap = createTileUniformMap(frameState, tileProvider);
  
                  tileProvider._drawCommands.push(command);
                  tileProvider._uniformMaps.push(uniformMap);
              } else {
                  command = tileProvider._drawCommands[tileProvider._usedDrawCommands];
                  uniformMap = tileProvider._uniformMaps[tileProvider._usedDrawCommands];
              }
  
              command.owner = tile;
  
              ++tileProvider._usedDrawCommands;
  
              if (tile === tileProvider._debug.boundingSphereTile) {
                  var obb = surfaceTile.orientedBoundingBox;
                  // If a debug primitive already exists for this tile, it will not be
                  // re-created, to avoid allocation every frame. If it were possible
                  // to have more than one selected tile, this would have to change.
                  if (defined(obb)) {
                      getDebugOrientedBoundingBox(obb, Color.RED).update(frameState);
                  } else if (defined(mesh) && defined(mesh.boundingSphere3D)) {
                      getDebugBoundingSphere(mesh.boundingSphere3D, Color.RED).update(frameState);
                  }
              }
  
              var uniformMapProperties = uniformMap.properties;
              Cartesian4.clone(initialColor, uniformMapProperties.initialColor);
              uniformMapProperties.oceanNormalMap = oceanNormalMap;
              uniformMapProperties.lightingFadeDistance.x = tileProvider.lightingFadeOutDistance;
              uniformMapProperties.lightingFadeDistance.y = tileProvider.lightingFadeInDistance;
              uniformMapProperties.nightFadeDistance.x = tileProvider.nightFadeOutDistance;
              uniformMapProperties.nightFadeDistance.y = tileProvider.nightFadeInDistance;
              uniformMapProperties.zoomedOutOceanSpecularIntensity = tileProvider.zoomedOutOceanSpecularIntensity;
  
              var highlightFillTile = !defined(surfaceTile.vertexArray) && defined(tileProvider.fillHighlightColor) && tileProvider.fillHighlightColor.alpha > 0.0;
              if (highlightFillTile) {
                  Color.clone(tileProvider.fillHighlightColor, uniformMapProperties.fillHighlightColor);
              }
  
              uniformMapProperties.center3D = mesh.center;
              Cartesian3.clone(rtc, uniformMapProperties.rtc);
  
              Cartesian4.clone(tileRectangle, uniformMapProperties.tileRectangle);
              uniformMapProperties.southAndNorthLatitude.x = southLatitude;
              uniformMapProperties.southAndNorthLatitude.y = northLatitude;
              uniformMapProperties.southMercatorYAndOneOverHeight.x = southMercatorY;
              uniformMapProperties.southMercatorYAndOneOverHeight.y = oneOverMercatorHeight;
  
              // Convert tile limiter rectangle from cartographic to texture space using the tileRectangle.
              var localizedCartographicLimitRectangle = localizedCartographicLimitRectangleScratch;
              var cartographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, tileProvider.cartographicLimitRectangle);
  
              Cartesian3.fromElements(hueShift, saturationShift, brightnessShift, uniformMapProperties.hsbShift);
  
              var cartographicTileRectangle = tile.rectangle;
              var inverseTileWidth = 1.0 / cartographicTileRectangle.width;
              var inverseTileHeight = 1.0 / cartographicTileRectangle.height;
              localizedCartographicLimitRectangle.x = (cartographicLimitRectangle.west - cartographicTileRectangle.west) * inverseTileWidth;
              localizedCartographicLimitRectangle.y = (cartographicLimitRectangle.south - cartographicTileRectangle.south) * inverseTileHeight;
              localizedCartographicLimitRectangle.z = (cartographicLimitRectangle.east - cartographicTileRectangle.west) * inverseTileWidth;
              localizedCartographicLimitRectangle.w = (cartographicLimitRectangle.north - cartographicTileRectangle.south) * inverseTileHeight;
  
              Cartesian4.clone(localizedCartographicLimitRectangle, uniformMapProperties.localizedCartographicLimitRectangle);
  
              // For performance, use fog in the shader only when the tile is in fog.
              var applyFog = enableFog && CesiumMath.fog(tile._distance, frameState.fog.density) > CesiumMath.EPSILON3;
              colorCorrect = colorCorrect && (applyFog || showGroundAtmosphere);
  
              var applyBrightness = false;
              var applyContrast = false;
              var applyHue = false;
              var applySaturation = false;
              var applyGamma = false;
              var applyAlpha = false;
              var applySplit = false;
              var applyCutout = false;
  
              while (numberOfDayTextures < maxTextures && imageryIndex < imageryLen) {
                  var tileImagery = tileImageryCollection[imageryIndex];
                  var imagery = tileImagery.readyImagery;
                  ++imageryIndex;
  
                  if (!defined(imagery) || imagery.imageryLayer.alpha === 0.0) {
                      continue;
                  }
  
                  var texture = tileImagery.useWebMercatorT ? imagery.textureWebMercator : imagery.texture;
  
                  //>>includeStart('debug', pragmas.debug);
                  if (!defined(texture)) {
                      // Our "ready" texture isn't actually ready.  This should never happen.
                      //
                      // Side note: It IS possible for it to not be in the READY ImageryState, though.
                      // This can happen when a single imagery tile is shared by two terrain tiles (common)
                      // and one of them (A) needs a geographic version of the tile because it is near the poles,
                      // and the other (B) does not.  B can and will transition the imagery tile to the READY state
                      // without reprojecting to geographic.  Then, later, A will deem that same tile not-ready-yet
                      // because it only has the Web Mercator texture, and flip it back to the TRANSITIONING state.
                      // The imagery tile won't be in the READY state anymore, but it's still READY enough for B's
                      // purposes.
                      throw new DeveloperError('readyImagery is not actually ready!');
                  }
                  //>>includeEnd('debug');
  
                  var imageryLayer = imagery.imageryLayer;
  
                  if (!defined(tileImagery.textureTranslationAndScale)) {
                      tileImagery.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, tileImagery);
                  }
  
                  uniformMapProperties.dayTextures[numberOfDayTextures] = texture;
                  uniformMapProperties.dayTextureTranslationAndScale[numberOfDayTextures] = tileImagery.textureTranslationAndScale;
                  uniformMapProperties.dayTextureTexCoordsRectangle[numberOfDayTextures] = tileImagery.textureCoordinateRectangle;
                  uniformMapProperties.dayTextureUseWebMercatorT[numberOfDayTextures] = tileImagery.useWebMercatorT;
  
                  uniformMapProperties.dayTextureAlpha[numberOfDayTextures] = imageryLayer.alpha;
                  applyAlpha = applyAlpha || uniformMapProperties.dayTextureAlpha[numberOfDayTextures] !== 1.0;
  
                  uniformMapProperties.dayTextureBrightness[numberOfDayTextures] = imageryLayer.brightness;
                  applyBrightness = applyBrightness || uniformMapProperties.dayTextureBrightness[numberOfDayTextures] !== ImageryLayer.DEFAULT_BRIGHTNESS;
  
                  uniformMapProperties.dayTextureContrast[numberOfDayTextures] = imageryLayer.contrast;
                  applyContrast = applyContrast || uniformMapProperties.dayTextureContrast[numberOfDayTextures] !== ImageryLayer.DEFAULT_CONTRAST;
  
                  uniformMapProperties.dayTextureHue[numberOfDayTextures] = imageryLayer.hue;
                  applyHue = applyHue || uniformMapProperties.dayTextureHue[numberOfDayTextures] !== ImageryLayer.DEFAULT_HUE;
  
                  uniformMapProperties.dayTextureSaturation[numberOfDayTextures] = imageryLayer.saturation;
                  applySaturation = applySaturation || uniformMapProperties.dayTextureSaturation[numberOfDayTextures] !== ImageryLayer.DEFAULT_SATURATION;
  
                  uniformMapProperties.dayTextureOneOverGamma[numberOfDayTextures] = 1.0 / imageryLayer.gamma;
                  applyGamma = applyGamma || uniformMapProperties.dayTextureOneOverGamma[numberOfDayTextures] !== 1.0 / ImageryLayer.DEFAULT_GAMMA;
  
                  uniformMapProperties.dayTextureSplit[numberOfDayTextures] = imageryLayer.splitDirection;
                  applySplit = applySplit || uniformMapProperties.dayTextureSplit[numberOfDayTextures] !== 0.0;
  
                  // Update cutout rectangle
                  var dayTextureCutoutRectangle = uniformMapProperties.dayTextureCutoutRectangles[numberOfDayTextures];
                  if (!defined(dayTextureCutoutRectangle)) {
                      dayTextureCutoutRectangle = uniformMapProperties.dayTextureCutoutRectangles[numberOfDayTextures] = new Cartesian4();
                  }
  
                  Cartesian4.clone(Cartesian4.ZERO, dayTextureCutoutRectangle);
                  if (defined(imageryLayer.cutoutRectangle)) {
                      var cutoutRectangle = clipRectangleAntimeridian(cartographicTileRectangle, imageryLayer.cutoutRectangle);
                      var intersection = Rectangle.simpleIntersection(cutoutRectangle, cartographicTileRectangle, rectangleIntersectionScratch);
                      applyCutout = defined(intersection) || applyCutout;
  
                      dayTextureCutoutRectangle.x = (cutoutRectangle.west - cartographicTileRectangle.west) * inverseTileWidth;
                      dayTextureCutoutRectangle.y = (cutoutRectangle.south - cartographicTileRectangle.south) * inverseTileHeight;
                      dayTextureCutoutRectangle.z = (cutoutRectangle.east - cartographicTileRectangle.west) * inverseTileWidth;
                      dayTextureCutoutRectangle.w = (cutoutRectangle.north - cartographicTileRectangle.south) * inverseTileHeight;
                  }
  
                  if (defined(imagery.credits)) {
                      var credits = imagery.credits;
                      for (var creditIndex = 0, creditLength = credits.length; creditIndex < creditLength; ++creditIndex) {
                          creditDisplay.addCredit(credits[creditIndex]);
                      }
                  }
  
                  ++numberOfDayTextures;
              }
  
              // trim texture array to the used length so we don't end up using old textures
              // which might get destroyed eventually
              uniformMapProperties.dayTextures.length = numberOfDayTextures;
              uniformMapProperties.waterMask = waterMaskTexture;
              Cartesian4.clone(waterMaskTranslationAndScale, uniformMapProperties.waterMaskTranslationAndScale);
  
              uniformMapProperties.minMaxHeight.x = encoding.minimumHeight;
              uniformMapProperties.minMaxHeight.y = encoding.maximumHeight;
              Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias);
  
              // update clipping planes
              var clippingPlanes = tileProvider._clippingPlanes;
              var clippingPlanesEnabled = defined(clippingPlanes) && clippingPlanes.enabled && tile.isClipped;
              if (clippingPlanesEnabled) {
                  uniformMapProperties.clippingPlanesEdgeColor = Color.clone(clippingPlanes.edgeColor, uniformMapProperties.clippingPlanesEdgeColor);
                  uniformMapProperties.clippingPlanesEdgeWidth = clippingPlanes.edgeWidth;
              }
  
              if (defined(tileProvider.uniformMap)) {
                  uniformMap = combine(uniformMap, tileProvider.uniformMap);
              }
  
              surfaceShaderSetOptions.numberOfDayTextures = numberOfDayTextures;
              surfaceShaderSetOptions.applyBrightness = applyBrightness;
              surfaceShaderSetOptions.applyContrast = applyContrast;
              surfaceShaderSetOptions.applyHue = applyHue;
              surfaceShaderSetOptions.applySaturation = applySaturation;
              surfaceShaderSetOptions.applyGamma = applyGamma;
              surfaceShaderSetOptions.applyAlpha = applyAlpha;
              surfaceShaderSetOptions.applySplit = applySplit;
              surfaceShaderSetOptions.enableFog = applyFog;
              surfaceShaderSetOptions.enableClippingPlanes = clippingPlanesEnabled;
              surfaceShaderSetOptions.clippingPlanes = clippingPlanes;
              surfaceShaderSetOptions.hasImageryLayerCutout = applyCutout;
              surfaceShaderSetOptions.colorCorrect = colorCorrect;
              surfaceShaderSetOptions.highlightFillTile = highlightFillTile;
  
              command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(surfaceShaderSetOptions);
              command.castShadows = castShadows;
              command.receiveShadows = receiveShadows;
              command.renderState = renderState;
              command.primitiveType = PrimitiveType.TRIANGLES;
              command.vertexArray = surfaceTile.vertexArray || surfaceTile.fill.vertexArray;
              command.uniformMap = uniformMap;
              command.pass = Pass.GLOBE;
  
              if (tileProvider._debug.wireframe) {
                  createWireframeVertexArrayIfNecessary(context, tileProvider, tile);
                  if (defined(surfaceTile.wireframeVertexArray)) {
                      command.vertexArray = surfaceTile.wireframeVertexArray;
                      command.primitiveType = PrimitiveType.LINES;
                  }
              }
  
              var boundingVolume = command.boundingVolume;
              var orientedBoundingBox = command.orientedBoundingBox;
  
              if (frameState.mode !== SceneMode.SCENE3D) {
                  var tileBoundingRegion = surfaceTile.tileBoundingRegion;
                  BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, tileBoundingRegion.minimumHeight, tileBoundingRegion.maximumHeight, boundingVolume);
                  Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
  
                  if (frameState.mode === SceneMode.MORPHING) {
                      boundingVolume = BoundingSphere.union(mesh.boundingSphere3D, boundingVolume, boundingVolume);
                  }
              } else {
                  command.boundingVolume = BoundingSphere.clone(mesh.boundingSphere3D, boundingVolume);
                  command.orientedBoundingBox = OrientedBoundingBox.clone(surfaceTile.orientedBoundingBox, orientedBoundingBox);
              }
  
              command.dirty = true;
              frameState.commandList.push(command);
  
              renderState = otherPassesRenderState;
              initialColor = otherPassesInitialColor;
          } while (imageryIndex < imageryLen);
      }
  
      return GlobeSurfaceTileProvider;
  });
  