text: /*global define*/
  define([
          '../Core/clone',
          '../Core/Color',
          '../Core/ComponentDatatype',
          '../Core/createGuid',
          '../Core/defaultValue',
          '../Core/defined',
          '../Core/defineProperties',
          '../Core/destroyObject',
          '../Core/DeveloperError',
          '../Core/FeatureDetection',
          '../Core/Geometry',
          '../Core/GeometryAttribute',
          '../Core/Math',
          '../Core/Matrix4',
          '../Core/PrimitiveType',
          '../Core/RuntimeError',
          '../Shaders/ViewportQuadVS',
          './BufferUsage',
          './ClearCommand',
          './ContextLimits',
          './CubeMap',
          './DrawCommand',
          './PassState',
          './PickFramebuffer',
          './PixelDatatype',
          './RenderbufferFormat',
          './RenderState',
          './ShaderCache',
          './ShaderProgram',
          './Texture',
          './UniformState',
          './VertexArray',
          './WebGLConstants'
      ], function(
          clone,
          Color,
          ComponentDatatype,
          createGuid,
          defaultValue,
          defined,
          defineProperties,
          destroyObject,
          DeveloperError,
          FeatureDetection,
          Geometry,
          GeometryAttribute,
          CesiumMath,
          Matrix4,
          PrimitiveType,
          RuntimeError,
          ViewportQuadVS,
          BufferUsage,
          ClearCommand,
          ContextLimits,
          CubeMap,
          DrawCommand,
          PassState,
          PickFramebuffer,
          PixelDatatype,
          RenderbufferFormat,
          RenderState,
          ShaderCache,
          ShaderProgram,
          Texture,
          UniformState,
          VertexArray,
          WebGLConstants) {
      "use strict";
      /*global WebGLRenderingContext*/
      /*global WebGL2RenderingContext*/
  
      function errorToString(gl, error) {
          var message = 'WebGL Error:  ';
          switch (error) {
          case gl.INVALID_ENUM:
              message += 'INVALID_ENUM';
              break;
          case gl.INVALID_VALUE:
              message += 'INVALID_VALUE';
              break;
          case gl.INVALID_OPERATION:
              message += 'INVALID_OPERATION';
              break;
          case gl.OUT_OF_MEMORY:
              message += 'OUT_OF_MEMORY';
              break;
          case gl.CONTEXT_LOST_WEBGL:
              message += 'CONTEXT_LOST_WEBGL lost';
              break;
          default:
              message += 'Unknown (' + error + ')';
          }
  
          return message;
      }
  
      function createErrorMessage(gl, glFunc, glFuncArguments, error) {
          var message = errorToString(gl, error) + ': ' + glFunc.name + '(';
  
          for ( var i = 0; i < glFuncArguments.length; ++i) {
              if (i !== 0) {
                  message += ', ';
              }
              message += glFuncArguments[i];
          }
          message += ');';
  
          return message;
      }
  
      function throwOnError(gl, glFunc, glFuncArguments) {
          var error = gl.getError();
          if (error !== gl.NO_ERROR) {
              throw new RuntimeError(createErrorMessage(gl, glFunc, glFuncArguments, error));
          }
      }
  
      function makeGetterSetter(gl, propertyName, logFunc) {
          return {
              get : function() {
                  var value = gl[propertyName];
                  logFunc(gl, 'get: ' + propertyName, value);
                  return gl[propertyName];
              },
              set : function(value) {
                  gl[propertyName] = value;
                  logFunc(gl, 'set: ' + propertyName, value);
              }
          };
      }
  
      function wrapGL(gl, logFunc) {
          if (!logFunc) {
              return gl;
          }
  
          function wrapFunction(property) {
              return function() {
                  var result = property.apply(gl, arguments);
                  logFunc(gl, property, arguments);
                  return result;
              };
          }
  
          var glWrapper = {};
  
          /*jslint forin: true*/
          /*jshint forin: false*/
          // JSLint normally demands that a for..in loop must directly contain an if,
          // but in our loop below, we actually intend to iterate all properties, including
          // those in the prototype.
          for ( var propertyName in gl) {
              var property = gl[propertyName];
  
              // wrap any functions we encounter, otherwise just copy the property to the wrapper.
              if (typeof property === 'function') {
                  glWrapper[propertyName] = wrapFunction(property);
              } else {
                  Object.defineProperty(glWrapper, propertyName, makeGetterSetter(gl, propertyName, logFunc));
              }
          }
  
          return glWrapper;
      }
  
      function getExtension(gl, names) {
          var length = names.length;
          for (var i = 0; i < length; ++i) {
              var extension = gl.getExtension(names[i]);
              if (extension) {
                  return extension;
              }
          }
  
          return undefined;
      }
  
      /**
       * @private
       */
      function Context(canvas, options) {
          // this check must use typeof, not defined, because defined doesn't work with undeclared variables.
          if (typeof WebGLRenderingContext === 'undefined') {
              throw new RuntimeError('The browser does not support WebGL.  Visit http://get.webgl.org.');
          }
  
          //>>includeStart('debug', pragmas.debug);
          if (!defined(canvas)) {
              throw new DeveloperError('canvas is required.');
          }
          //>>includeEnd('debug');
  
          this._canvas = canvas;
  
          options = clone(options, true);
          options = defaultValue(options, {});
          options.allowTextureFilterAnisotropic = defaultValue(options.allowTextureFilterAnisotropic, true);
          var webglOptions = defaultValue(options.webgl, {});
  
          // Override select WebGL defaults
          webglOptions.alpha = defaultValue(webglOptions.alpha, false); // WebGL default is true
  
          var defaultToWebgl2 = false;
          var webgl2Supported = (typeof WebGL2RenderingContext !== 'undefined');
          var webgl2 = false;
          var glContext;
  
          if (defaultToWebgl2 && webgl2Supported) {
              glContext = canvas.getContext('webgl2', webglOptions) || canvas.getContext('experimental-webgl2', webglOptions) || undefined;
              if (defined(glContext)) {
                  webgl2 = true;
              }
          }
          if (!defined(glContext)) {
              glContext = canvas.getContext('webgl', webglOptions) || canvas.getContext('experimental-webgl', webglOptions) || undefined;
          }
          if (!defined(glContext)) {
              throw new RuntimeError('The browser supports WebGL, but initialization failed.');
          }
  
          this._originalGLContext = glContext;
          this._webgl2 = webgl2;
          this._id = createGuid();
  
          // Validation and logging disabled by default for speed.
          this.validateFramebuffer = false;
          this.validateShaderProgram = false;
          this.logShaderCompilation = false;
  
          this._throwOnWebGLError = false;
  
          this._shaderCache = new ShaderCache(this);
  
          var gl = this._gl = this._originalGLContext;
  
          this._redBits = gl.getParameter(gl.RED_BITS);
          this._greenBits = gl.getParameter(gl.GREEN_BITS);
          this._blueBits = gl.getParameter(gl.BLUE_BITS);
          this._alphaBits = gl.getParameter(gl.ALPHA_BITS);
          this._depthBits = gl.getParameter(gl.DEPTH_BITS);
          this._stencilBits = gl.getParameter(gl.STENCIL_BITS);
  
          ContextLimits._maximumCombinedTextureImageUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); // min: 8
          ContextLimits._maximumCubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); // min: 16
          ContextLimits._maximumFragmentUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS); // min: 16
          ContextLimits._maximumTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); // min: 8
          ContextLimits._maximumRenderbufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE); // min: 1
          ContextLimits._maximumTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); // min: 64
          ContextLimits._maximumVaryingVectors = gl.getParameter(gl.MAX_VARYING_VECTORS); // min: 8
          ContextLimits._maximumVertexAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); // min: 8
          ContextLimits._maximumVertexTextureImageUnits = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS); // min: 0
          ContextLimits._maximumVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS); // min: 128
  
          var aliasedLineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); // must include 1
          ContextLimits._minimumAliasedLineWidth = aliasedLineWidthRange[0];
          ContextLimits._maximumAliasedLineWidth = aliasedLineWidthRange[1];
  
          var aliasedPointSizeRange = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE); // must include 1
          ContextLimits._minimumAliasedPointSize = aliasedPointSizeRange[0];
          ContextLimits._maximumAliasedPointSize = aliasedPointSizeRange[1];
  
          var maximumViewportDimensions = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
          ContextLimits._maximumViewportWidth = maximumViewportDimensions[0];
          ContextLimits._maximumViewportHeight = maximumViewportDimensions[1];
  
          var highpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
          ContextLimits._highpFloatSupported = highpFloat.precision !== 0;
          var highpInt = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT);
          ContextLimits._highpIntSupported = highpInt.rangeMax !== 0;
  
          this._antialias = gl.getContextAttributes().antialias;
  
          // Query and initialize extensions
          this._standardDerivatives = !!getExtension(gl, ['OES_standard_derivatives']);
          this._elementIndexUint = !!getExtension(gl, ['OES_element_index_uint']);
          this._depthTexture = !!getExtension(gl, ['WEBGL_depth_texture', 'WEBKIT_WEBGL_depth_texture']);
          this._textureFloat = !!getExtension(gl, ['OES_texture_float']);
          this._fragDepth = !!getExtension(gl, ['EXT_frag_depth']);
          this._debugShaders = getExtension(gl, ['WEBGL_debug_shaders']);
  
          var textureFilterAnisotropic = options.allowTextureFilterAnisotropic ? getExtension(gl, ['EXT_texture_filter_anisotropic', 'WEBKIT_EXT_texture_filter_anisotropic']) : undefined;
          this._textureFilterAnisotropic = textureFilterAnisotropic;
          ContextLimits._maximumTextureFilterAnisotropy = defined(textureFilterAnisotropic) ? gl.getParameter(textureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1.0;
  
          var glCreateVertexArray;
          var glBindVertexArray;
          var glDeleteVertexArray;
  
          var glDrawElementsInstanced;
          var glDrawArraysInstanced;
          var glVertexAttribDivisor;
  
          var glDrawBuffers;
  
          var vertexArrayObject;
          var instancedArrays;
          var drawBuffers;
  
          if (webgl2) {
              var that = this;
  
              glCreateVertexArray = function () { return that._gl.createVertexArray(); };
              glBindVertexArray = function(vao) { that._gl.bindVertexArray(vao); };
              glDeleteVertexArray = function(vao) { that._gl.deleteVertexArray(vao); };
  
              glDrawElementsInstanced = function(mode, count, type, offset, instanceCount) { gl.drawElementsInstanced(mode, count, type, offset, instanceCount); };
              glDrawArraysInstanced = function(mode, first, count, instanceCount) { gl.drawArraysInstanced(mode, first, count, instanceCount); };
              glVertexAttribDivisor = function(index, divisor) { gl.vertexAttribDivisor(index, divisor); };
  
              glDrawBuffers = function(buffers) { gl.drawBuffers(buffers); };
          } else {
              vertexArrayObject = getExtension(gl, ['OES_vertex_array_object']);
              if (defined(vertexArrayObject)) {
                  glCreateVertexArray = function() { return vertexArrayObject.createVertexArrayOES(); };
                  glBindVertexArray = function(vertexArray) { vertexArrayObject.bindVertexArrayOES(vertexArray); };
                  glDeleteVertexArray = function(vertexArray) { vertexArrayObject.deleteVertexArrayOES(vertexArray); };
              }
  
              instancedArrays = getExtension(gl, ['ANGLE_instanced_arrays']);
              if (defined(instancedArrays)) {
                  glDrawElementsInstanced = function(mode, count, type, offset, instanceCount) { instancedArrays.drawElementsInstancedANGLE(mode, count, type, offset, instanceCount); };
                  glDrawArraysInstanced = function(mode, first, count, instanceCount) { instancedArrays.drawArraysInstancedANGLE(mode, first, count, instanceCount); };
                  glVertexAttribDivisor = function(index, divisor) { instancedArrays.vertexAttribDivisorANGLE(index, divisor); };
              }
  
              drawBuffers = getExtension(gl, ['WEBGL_draw_buffers']);
              if (defined(drawBuffers)) {
                  glDrawBuffers = function(buffers) { drawBuffers.drawBuffersWEBGL(buffers); };
              }
          }
  
          this.glCreateVertexArray = glCreateVertexArray;
          this.glBindVertexArray = glBindVertexArray;
          this.glDeleteVertexArray = glDeleteVertexArray;
  
          this.glDrawElementsInstanced = glDrawElementsInstanced;
          this.glDrawArraysInstanced = glDrawArraysInstanced;
          this.glVertexAttribDivisor = glVertexAttribDivisor;
  
          this.glDrawBuffers = glDrawBuffers;
  
          this._vertexArrayObject = !!vertexArrayObject;
          this._instancedArrays = !!instancedArrays;
          this._drawBuffers = !!drawBuffers;
  
          ContextLimits._maximumDrawBuffers = this.drawBuffers ? gl.getParameter(WebGLConstants.MAX_DRAW_BUFFERS) : 1;
          ContextLimits._maximumColorAttachments = this.drawBuffers ? gl.getParameter(WebGLConstants.MAX_COLOR_ATTACHMENTS) : 1;
  
          var cc = gl.getParameter(gl.COLOR_CLEAR_VALUE);
          this._clearColor = new Color(cc[0], cc[1], cc[2], cc[3]);
          this._clearDepth = gl.getParameter(gl.DEPTH_CLEAR_VALUE);
          this._clearStencil = gl.getParameter(gl.STENCIL_CLEAR_VALUE);
  
          var us = new UniformState();
          var ps = new PassState(this);
          var rs = RenderState.fromCache();
  
          this._defaultPassState = ps;
          this._defaultRenderState = rs;
          this._defaultTexture = undefined;
          this._defaultCubeMap = undefined;
  
          this._us = us;
          this._currentRenderState = rs;
          this._currentPassState = ps;
          this._currentFramebuffer = undefined;
          this._maxFrameTextureUnitIndex = 0;
  
          // Vertex attribute divisor state cache. Workaround for ANGLE (also look at VertexArray.setVertexAttribDivisor)
          this._vertexAttribDivisors = [];
          this._previousDrawInstanced = false;
          for (var i = 0; i < ContextLimits._maximumVertexAttributes; i++) {
              this._vertexAttribDivisors.push(0);
          }
  
          this._pickObjects = {};
          this._nextPickColor = new Uint32Array(1);
  
          /**
           * @example
           * {
           *   webgl : {
           *     alpha : false,
           *     depth : true,
           *     stencil : false,
           *     antialias : true,
           *     premultipliedAlpha : true,
           *     preserveDrawingBuffer : false,
           *     failIfMajorPerformanceCaveat : true
           *   },
           *   allowTextureFilterAnisotropic : true
           * }
           */
          this.options = options;
  
          /**
           * A cache of objects tied to this context.  Just before the Context is destroyed,
           * <code>destroy</code> will be invoked on each object in this object literal that has
           * such a method.  This is useful for caching any objects that might otherwise
           * be stored globally, except they're tied to a particular context, and to manage
           * their lifetime.
           *
           * @type {Object}
           */
          this.cache = {};
  
  
          RenderState.apply(gl, rs, ps);
      }
  
      var defaultFramebufferMarker = {};
  
      defineProperties(Context.prototype, {
          id : {
              get : function() {
                  return this._id;
              }
          },
          webgl2 : {
              get : function() {
                  return this._webgl2;
              }
          },
          canvas : {
              get : function() {
                  return this._canvas;
              }
          },
          shaderCache : {
              get : function() {
                  return this._shaderCache;
              }
          },
          uniformState : {
              get : function() {
                  return this._us;
              }
          },
  
          /**
           * The number of red bits per component in the default framebuffer's color buffer.  The minimum is eight.
           * @memberof Context.prototype
           * @type {Number}
           * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>RED_BITS</code>.
           */
          redBits : {
              get : function() {
                  return this._redBits;
              }
          },
  
          /**
           * The number of green bits per component in the default framebuffer's color buffer.  The minimum is eight.
           * @memberof Context.prototype
           * @type {Number}
           * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>GREEN_BITS</code>.
           */
          greenBits : {
              get : function() {
                  return this._greenBits;
              }
          },
  
          /**
           * The number of blue bits per component in the default framebuffer's color buffer.  The minimum is eight.
           * @memberof Context.prototype
           * @type {Number}
           * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>BLUE_BITS</code>.
           */
          blueBits : {
              get : function() {
                  return this._blueBits;
              }
          },
  
          /**
           * The number of alpha bits per component in the default framebuffer's color buffer.  The minimum is eight.
           * <br /><br />
           * The alpha channel is used for GL destination alpha operations and by the HTML compositor to combine the color buffer
           * with the rest of the page.
           * @memberof Context.prototype
           * @type {Number}
           * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>ALPHA_BITS</code>.
           */
          alphaBits : {
              get : function() {
                  return this._alphaBits;
              }
          },
  
          /**
           * The number of depth bits per pixel in the default bound framebuffer.  The minimum is 16 bits; most
           * implementations will have 24 bits.
           * @memberof Context.prototype
           * @type {Number}
           * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>DEPTH_BITS</code>.
           */
          depthBits : {
              get : function() {
                  return this._depthBits;
              }
          },
  
          /**
           * The number of stencil bits per pixel in the default bound framebuffer.  The minimum is eight bits.
           * @memberof Context.prototype
           * @type {Number}
           * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>STENCIL_BITS</code>.
           */
          stencilBits : {
              get : function() {
                  return this._stencilBits;
              }
          },
  
          /**
           * <code>true</code> if the WebGL context supports antialiasing.  By default
           * antialiasing is requested, but it is not supported by all systems.
           * @memberof Context.prototype
           * @type {Boolean}
           */
          antialias : {
              get : function() {
                  return this._antialias;
              }
          },
  
          /**
           * <code>true</code> if the OES_standard_derivatives extension is supported.  This
           * extension provides access to <code>dFdx</code>, <code>dFdy</code>, and <code>fwidth</code>
           * functions from GLSL.  A shader using these functions still needs to explicitly enable the
           * extension with <code>#extension GL_OES_standard_derivatives : enable</code>.
           * @memberof Context.prototype
           * @type {Boolean}
           * @see {@link http://www.khronos.org/registry/gles/extensions/OES/OES_standard_derivatives.txt|OES_standard_derivatives}
           */
          standardDerivatives : {
              get : function() {
                  return this._standardDerivatives;
              }
          },
  
          /**
           * <code>true</code> if the OES_element_index_uint extension is supported.  This
           * extension allows the use of unsigned int indices, which can improve performance by
           * eliminating batch breaking caused by unsigned short indices.
           * @memberof Context.prototype
           * @type {Boolean}
           * @see {@link http://www.khronos.org/registry/webgl/extensions/OES_element_index_uint/|OES_element_index_uint}
           */
          elementIndexUint : {
              get : function() {
                  return this._elementIndexUint || this._webgl2;
              }
          },
  
          /**
           * <code>true</code> if WEBGL_depth_texture is supported.  This extension provides
           * access to depth textures that, for example, can be attached to framebuffers for shadow mapping.
           * @memberof Context.prototype
           * @type {Boolean}
           * @see {@link http://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/|WEBGL_depth_texture}
           */
          depthTexture : {
              get : function() {
                  return this._depthTexture;
              }
          },
  
          /**
           * <code>true</code> if OES_texture_float is supported.  This extension provides
           * access to floating point textures that, for example, can be attached to framebuffers for high dynamic range.
           * @memberof Context.prototype
           * @type {Boolean}
           * @see {@link http://www.khronos.org/registry/gles/extensions/OES/OES_texture_float.txt|OES_texture_float}
           */
          floatingPointTexture : {
              get : function() {
                  return this._textureFloat;
              }
          },
  
          textureFilterAnisotropic : {
              get : function() {
                  return !!this._textureFilterAnisotropic;
              }
          },
  
          /**
           * <code>true</code> if the OES_vertex_array_object extension is supported.  This
           * extension can improve performance by reducing the overhead of switching vertex arrays.
           * When enabled, this extension is automatically used by {@link VertexArray}.
           * @memberof Context.prototype
           * @type {Boolean}
           * @see {@link http://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/|OES_vertex_array_object}
           */
          vertexArrayObject : {
              get : function() {
                  return this._vertexArrayObject || this._webgl2;
              }
          },
  
          /**
           * <code>true</code> if the EXT_frag_depth extension is supported.  This
           * extension provides access to the <code>gl_FragDepthEXT</code> built-in output variable
           * from GLSL fragment shaders.  A shader using these functions still needs to explicitly enable the
           * extension with <code>#extension GL_EXT_frag_depth : enable</code>.
           * @memberof Context.prototype
           * @type {Boolean}
           * @see {@link http://www.khronos.org/registry/webgl/extensions/EXT_frag_depth/|EXT_frag_depth}
           */
          fragmentDepth : {
              get : function() {
                  return this._fragDepth;
              }
          },
  
          /**
           * <code>true</code> if the ANGLE_instanced_arrays extension is supported.  This
           * extension provides access to instanced rendering.
           * @memberof Context.prototype
           * @type {Boolean}
           * @see {@link https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays}
           */
          instancedArrays : {
              get : function() {
                  return this._instancedArrays || this._webgl2;
              }
          },
  
          /**
           * <code>true</code> if the WEBGL_draw_buffers extension is supported. This
           * extensions provides support for multiple render targets. The framebuffer object can have mutiple
           * color attachments and the GLSL fragment shader can write to the built-in output array <code>gl_FragData</code>.
           * A shader using this feature needs to explicitly enable the extension with
           * <code>#extension GL_EXT_draw_buffers : enable</code>.
           * @memberof Context.prototype
           * @type {Boolean}
           * @see {@link http://www.khronos.org/registry/webgl/extensions/WEBGL_draw_buffers/|WEBGL_draw_buffers}
           */
          drawBuffers : {
              get : function() {
                  return this._drawBuffers || this._webgl2;
              }
          },
  
          debugShaders : {
              get : function() {
                  return this._debugShaders;
              }
          },
  
          throwOnWebGLError : {
              get : function() {
                  return this._throwOnWebGLError;
              },
              set : function(value) {
                  this._throwOnWebGLError = value;
                  this._gl = wrapGL(this._originalGLContext, value ? throwOnError : null);
              }
          },
  
          /**
           * A 1x1 RGBA texture initialized to [255, 255, 255, 255].  This can
           * be used as a placeholder texture while other textures are downloaded.
           * @memberof Context.prototype
           * @type {Texture}
           */
          defaultTexture : {
              get : function() {
                  if (this._defaultTexture === undefined) {
                      this._defaultTexture = new Texture({
                          context : this,
                          source : {
                              width : 1,
                              height : 1,
                              arrayBufferView : new Uint8Array([255, 255, 255, 255])
                          }
                      });
                  }
  
                  return this._defaultTexture;
              }
          },
  
          /**
           * A cube map, where each face is a 1x1 RGBA texture initialized to
           * [255, 255, 255, 255].  This can be used as a placeholder cube map while
           * other cube maps are downloaded.
           * @memberof Context.prototype
           * @type {CubeMap}
           */
          defaultCubeMap : {
              get : function() {
                  if (this._defaultCubeMap === undefined) {
                      var face = {
                          width : 1,
                          height : 1,
                          arrayBufferView : new Uint8Array([255, 255, 255, 255])
                      };
  
                      this._defaultCubeMap = new CubeMap({
                          context : this,
                          source : {
                              positiveX : face,
                              negativeX : face,
                              positiveY : face,
                              negativeY : face,
                              positiveZ : face,
                              negativeZ : face
                          }
                      });
                  }
  
                  return this._defaultCubeMap;
  
              }
          },
  
          /**
           * The drawingBufferHeight of the underlying GL context.
           * @memberof Context.prototype
           * @type {Number}
           * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
           */
          drawingBufferHeight : {
              get : function() {
                  return this._gl.drawingBufferHeight;
              }
          },
  
          /**
           * The drawingBufferWidth of the underlying GL context.
           * @memberof Context.prototype
           * @type {Number}
           * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferWidth|drawingBufferWidth}
           */
          drawingBufferWidth : {
              get : function() {
                  return this._gl.drawingBufferWidth;
              }
          },
  
          /**
           * Gets an object representing the currently bound framebuffer.  While this instance is not an actual
           * {@link Framebuffer}, it is used to represent the default framebuffer in calls to
           * {@link Texture.FromFramebuffer}.
           * @memberof Context.prototype
           * @type {Object}
           */
          defaultFramebuffer : {
              get : function() {
                  return defaultFramebufferMarker;
              }
          }
      });
  
      function validateFramebuffer(context, framebuffer) {
          if (context.validateFramebuffer) {
              var gl = context._gl;
              var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  
              if (status !== gl.FRAMEBUFFER_COMPLETE) {
                  var message;
  
                  switch (status) {
                  case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
                      message = 'Framebuffer is not complete.  Incomplete attachment: at least one attachment point with a renderbuffer or texture attached has its attached object no longer in existence or has an attached image with a width or height of zero, or the color attachment point has a non-color-renderable image attached, or the depth attachment point has a non-depth-renderable image attached, or the stencil attachment point has a non-stencil-renderable image attached.  Color-renderable formats include GL_RGBA4, GL_RGB5_A1, and GL_RGB565. GL_DEPTH_COMPONENT16 is the only depth-renderable format. GL_STENCIL_INDEX8 is the only stencil-renderable format.';
                      break;
                  case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
                      message = 'Framebuffer is not complete.  Incomplete dimensions: not all attached images have the same width and height.';
                      break;
                  case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
                      message = 'Framebuffer is not complete.  Missing attachment: no images are attached to the framebuffer.';
                      break;
                  case gl.FRAMEBUFFER_UNSUPPORTED:
                      message = 'Framebuffer is not complete.  Unsupported: the combination of internal formats of the attached images violates an implementation-dependent set of restrictions.';
                      break;
                  }
  
                  throw new DeveloperError(message);
              }
          }
      }
  
      function applyRenderState(context, renderState, passState, clear) {
          var previousRenderState = context._currentRenderState;
          var previousPassState = context._currentPassState;
          context._currentRenderState = renderState;
          context._currentPassState = passState;
          RenderState.partialApply(context._gl, previousRenderState, renderState, previousPassState, passState, clear);
      }
  
      var scratchBackBufferArray;
      // this check must use typeof, not defined, because defined doesn't work with undeclared variables.
      if (typeof WebGLRenderingContext !== 'undefined') {
          scratchBackBufferArray = [WebGLConstants.BACK];
      }
  
      function bindFramebuffer(context, framebuffer) {
          if (framebuffer !== context._currentFramebuffer) {
              context._currentFramebuffer = framebuffer;
              var buffers = scratchBackBufferArray;
  
              if (defined(framebuffer)) {
                  framebuffer._bind();
                  validateFramebuffer(context, framebuffer);
  
                  // TODO: Need a way for a command to give what draw buffers are active.
                  buffers = framebuffer._getActiveColorAttachments();
              } else {
                  var gl = context._gl;
                  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
              }
  
              if (context.drawBuffers) {
                  context.glDrawBuffers(buffers);
              }
          }
      }
  
      var defaultClearCommand = new ClearCommand();
  
      Context.prototype.clear = function(clearCommand, passState) {
          clearCommand = defaultValue(clearCommand, defaultClearCommand);
          passState = defaultValue(passState, this._defaultPassState);
  
          var gl = this._gl;
          var bitmask = 0;
  
          var c = clearCommand.color;
          var d = clearCommand.depth;
          var s = clearCommand.stencil;
  
          if (defined(c)) {
              if (!Color.equals(this._clearColor, c)) {
                  Color.clone(c, this._clearColor);
                  gl.clearColor(c.red, c.green, c.blue, c.alpha);
              }
              bitmask |= gl.COLOR_BUFFER_BIT;
          }
  
          if (defined(d)) {
              if (d !== this._clearDepth) {
                  this._clearDepth = d;
                  gl.clearDepth(d);
              }
              bitmask |= gl.DEPTH_BUFFER_BIT;
          }
  
          if (defined(s)) {
              if (s !== this._clearStencil) {
                  this._clearStencil = s;
                  gl.clearStencil(s);
              }
              bitmask |= gl.STENCIL_BUFFER_BIT;
          }
  
          var rs = defaultValue(clearCommand.renderState, this._defaultRenderState);
          applyRenderState(this, rs, passState, true);
  
          // The command's framebuffer takes presidence over the pass' framebuffer, e.g., for off-screen rendering.
          var framebuffer = defaultValue(clearCommand.framebuffer, passState.framebuffer);
          bindFramebuffer(this, framebuffer);
  
          gl.clear(bitmask);
      };
  
      function beginDraw(context, framebuffer, drawCommand, passState, renderState, shaderProgram) {
          var rs = defaultValue(defaultValue(renderState, drawCommand.renderState), context._defaultRenderState);
  
          //>>includeStart('debug', pragmas.debug);
          if (defined(framebuffer) && rs.depthTest) {
              if (rs.depthTest.enabled && !framebuffer.hasDepthAttachment) {
                  throw new DeveloperError('The depth test can not be enabled (drawCommand.renderState.depthTest.enabled) because the framebuffer (drawCommand.framebuffer) does not have a depth or depth-stencil renderbuffer.');
              }
          }
          //>>includeEnd('debug');
  
          bindFramebuffer(context, framebuffer);
  
          applyRenderState(context, rs, passState, false);
  
          var sp = defaultValue(shaderProgram, drawCommand.shaderProgram);
          sp._bind();
          context._maxFrameTextureUnitIndex = Math.max(context._maxFrameTextureUnitIndex, sp.maximumTextureUnitIndex);
      }
  
      function continueDraw(context, drawCommand, shaderProgram) {
          var primitiveType = drawCommand.primitiveType;
          var va = drawCommand.vertexArray;
          var offset = drawCommand.offset;
          var count = drawCommand.count;
          var instanceCount = drawCommand.instanceCount;
  
          //>>includeStart('debug', pragmas.debug);
          if (!PrimitiveType.validate(primitiveType)) {
              throw new DeveloperError('drawCommand.primitiveType is required and must be valid.');
          }
  
          if (!defined(va)) {
              throw new DeveloperError('drawCommand.vertexArray is required.');
          }
  
          if (offset < 0) {
              throw new DeveloperError('drawCommand.offset must be greater than or equal to zero.');
          }
  
          if (count < 0) {
              throw new DeveloperError('drawCommand.count must be greater than or equal to zero.');
          }
  
          if (instanceCount < 0) {
              throw new DeveloperError('drawCommand.instanceCount must be greater than or equal to zero.');
          }
  
          if (instanceCount > 0 && !context.instancedArrays) {
              throw new DeveloperError('Instanced arrays extension is not supported');
          }
          //>>includeEnd('debug');
  
          context._us.model = defaultValue(drawCommand.modelMatrix, Matrix4.IDENTITY);
          var sp = defaultValue(shaderProgram, drawCommand.shaderProgram);
          sp._setUniforms(drawCommand.uniformMap, context._us, context.validateShaderProgram);
  
          va._bind();
          var indexBuffer = va.indexBuffer;
  
          if (defined(indexBuffer)) {
              offset = offset * indexBuffer.bytesPerIndex; // offset in vertices to offset in bytes
              count = defaultValue(count, indexBuffer.numberOfIndices);
              if (instanceCount === 0) {
                  context._gl.drawElements(primitiveType, count, indexBuffer.indexDatatype, offset);
              } else {
                  context.glDrawElementsInstanced(primitiveType, count, indexBuffer.indexDatatype, offset, instanceCount);
              }
          } else {
              count = defaultValue(count, va.numberOfVertices);
              if (instanceCount === 0) {
                  context._gl.drawArrays(primitiveType, offset, count);
              } else {
                  context.glDrawArraysInstanced(primitiveType, offset, count, instanceCount);
              }
          }
  
          va._unBind();
      }
  
      Context.prototype.draw = function(drawCommand, passState, renderState, shaderProgram) {
          //>>includeStart('debug', pragmas.debug);
          if (!defined(drawCommand)) {
              throw new DeveloperError('drawCommand is required.');
          }
  
          if (!defined(drawCommand.shaderProgram)) {
              throw new DeveloperError('drawCommand.shaderProgram is required.');
          }
          //>>includeEnd('debug');
  
          passState = defaultValue(passState, this._defaultPassState);
          // The command's framebuffer takes presidence over the pass' framebuffer, e.g., for off-screen rendering.
          var framebuffer = defaultValue(drawCommand.framebuffer, passState.framebuffer);
  
          beginDraw(this, framebuffer, drawCommand, passState, renderState, shaderProgram);
          continueDraw(this, drawCommand, shaderProgram);
      };
  
      Context.prototype.endFrame = function() {
          var gl = this._gl;
          gl.useProgram(null);
  
          this._currentFramebuffer = undefined;
          gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  
          var buffers = scratchBackBufferArray;
          if (this.drawBuffers) {
              this.glDrawBuffers(buffers);
          }
  
          var length = this._maxFrameTextureUnitIndex;
          this._maxFrameTextureUnitIndex = 0;
  
          for (var i = 0; i < length; ++i) {
              gl.activeTexture(gl.TEXTURE0 + i);
              gl.bindTexture(gl.TEXTURE_2D, null);
              gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
          }
      };
  
      Context.prototype.readPixels = function(readState) {
          var gl = this._gl;
  
          readState = readState || {};
          var x = Math.max(readState.x || 0, 0);
          var y = Math.max(readState.y || 0, 0);
          var width = readState.width || gl.drawingBufferWidth;
          var height = readState.height || gl.drawingBufferHeight;
          var framebuffer = readState.framebuffer;
  
          //>>includeStart('debug', pragmas.debug);
          if (width <= 0) {
              throw new DeveloperError('readState.width must be greater than zero.');
          }
  
          if (height <= 0) {
              throw new DeveloperError('readState.height must be greater than zero.');
          }
          //>>includeEnd('debug');
  
          var pixels = new Uint8Array(4 * width * height);
  
          bindFramebuffer(this, framebuffer);
  
          gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
  
          return pixels;
      };
  
      var viewportQuadAttributeLocations = {
          position : 0,
          textureCoordinates : 1
      };
  
      Context.prototype.getViewportQuadVertexArray = function() {
          // Per-context cache for viewport quads
          var vertexArray = this.cache.viewportQuad_vertexArray;
  
          if (!defined(vertexArray)) {
              var geometry = new Geometry({
                  attributes : {
                      position : new GeometryAttribute({
                          componentDatatype : ComponentDatatype.FLOAT,
                          componentsPerAttribute : 2,
                          values : [
                             -1.0, -1.0,
                              1.0, -1.0,
                              1.0,  1.0,
                             -1.0,  1.0
                          ]
                      }),
  
                      textureCoordinates : new GeometryAttribute({
                          componentDatatype : ComponentDatatype.FLOAT,
                          componentsPerAttribute : 2,
                          values : [
                              0.0, 0.0,
                              1.0, 0.0,
                              1.0, 1.0,
                              0.0, 1.0
                          ]
                      })
                  },
                  // Workaround Internet Explorer 11.0.8 lack of TRIANGLE_FAN
                  indices : new Uint16Array([0, 1, 2, 0, 2, 3]),
                  primitiveType : PrimitiveType.TRIANGLES
              });
  
              vertexArray = VertexArray.fromGeometry({
                  context : this,
                  geometry : geometry,
                  attributeLocations : viewportQuadAttributeLocations,
                  bufferUsage : BufferUsage.STATIC_DRAW,
                  interleave : true
              });
  
              this.cache.viewportQuad_vertexArray = vertexArray;
          }
  
          return vertexArray;
      };
  
      Context.prototype.createViewportQuadCommand = function(fragmentShaderSource, overrides) {
          overrides = defaultValue(overrides, defaultValue.EMPTY_OBJECT);
  
          return new DrawCommand({
              vertexArray : this.getViewportQuadVertexArray(),
              primitiveType : PrimitiveType.TRIANGLES,
              renderState : overrides.renderState,
              shaderProgram : ShaderProgram.fromCache({
                  context : this,
                  vertexShaderSource : ViewportQuadVS,
                  fragmentShaderSource : fragmentShaderSource,
                  attributeLocations : viewportQuadAttributeLocations
              }),
              uniformMap : overrides.uniformMap,
              owner : overrides.owner,
              framebuffer : overrides.framebuffer
          });
      };
  
      Context.prototype.createPickFramebuffer = function() {
          return new PickFramebuffer(this);
      };
  
      /**
       * Gets the object associated with a pick color.
       *
       * @param {Color} pickColor The pick color.
       * @returns {Object} The object associated with the pick color, or undefined if no object is associated with that color.
       *
       *
       * @example
       * var object = context.getObjectByPickColor(pickColor);
       * 
       * @see Context#createPickId
       */
      Context.prototype.getObjectByPickColor = function(pickColor) {
          //>>includeStart('debug', pragmas.debug);
          if (!defined(pickColor)) {
              throw new DeveloperError('pickColor is required.');
          }
          //>>includeEnd('debug');
  
          return this._pickObjects[pickColor.toRgba()];
      };
  
      function PickId(pickObjects, key, color) {
          this._pickObjects = pickObjects;
          this.key = key;
          this.color = color;
      }
  
      defineProperties(PickId.prototype, {
          object : {
              get : function() {
                  return this._pickObjects[this.key];
              },
              set : function(value) {
                  this._pickObjects[this.key] = value;
              }
          }
      });
  
      PickId.prototype.destroy = function() {
          delete this._pickObjects[this.key];
          return undefined;
      };
  
      /**
       * Creates a unique ID associated with the input object for use with color-buffer picking.
       * The ID has an RGBA color value unique to this context.  You must call destroy()
       * on the pick ID when destroying the input object.
       *
       * @param {Object} object The object to associate with the pick ID.
       * @returns {Object} A PickId object with a <code>color</code> property.
       *
       * @exception {RuntimeError} Out of unique Pick IDs.
       *
       *
       * @example
       * this._pickId = context.createPickId({
       *   primitive : this,
       *   id : this.id
       * });
       * 
       * @see Context#getObjectByPickColor
       */
      Context.prototype.createPickId = function(object) {
          //>>includeStart('debug', pragmas.debug);
          if (!defined(object)) {
              throw new DeveloperError('object is required.');
          }
          //>>includeEnd('debug');
  
          // the increment and assignment have to be separate statements to
          // actually detect overflow in the Uint32 value
          ++this._nextPickColor[0];
          var key = this._nextPickColor[0];
          if (key === 0) {
              // In case of overflow
              throw new RuntimeError('Out of unique Pick IDs.');
          }
  
          this._pickObjects[key] = object;
          return new PickId(this._pickObjects, key, Color.fromRgba(key));
      };
  
      Context.prototype.isDestroyed = function() {
          return false;
      };
  
      Context.prototype.destroy = function() {
          // Destroy all objects in the cache that have a destroy method.
          var cache = this.cache;
          for (var property in cache) {
              if (cache.hasOwnProperty(property)) {
                  var propertyValue = cache[property];
                  if (defined(propertyValue.destroy)) {
                      propertyValue.destroy();
                  }
              }
          }
  
          this._shaderCache = this._shaderCache.destroy();
          this._defaultTexture = this._defaultTexture && this._defaultTexture.destroy();
          this._defaultCubeMap = this._defaultCubeMap && this._defaultCubeMap.destroy();
  
          return destroyObject(this);
      };
  
      return Context;
  });
  