import Check from "../Core/Check.js";
import clone from "../Core/clone.js";
import Color from "../Core/Color.js";
import ComponentDatatype from "../Core/ComponentDatatype.js";
import createGuid from "../Core/createGuid.js";
import defaultValue from "../Core/defaultValue.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import Geometry from "../Core/Geometry.js";
import GeometryAttribute from "../Core/GeometryAttribute.js";
import loadKTX2 from "../Core/loadKTX2.js";
import Matrix4 from "../Core/Matrix4.js";
import PixelFormat from "../Core/PixelFormat.js";
import PrimitiveType from "../Core/PrimitiveType.js";
import RuntimeError from "../Core/RuntimeError.js";
import WebGLConstants from "../Core/WebGLConstants.js";
import ViewportQuadVS from "../Shaders/ViewportQuadVS.js";
import BufferUsage from "./BufferUsage.js";
import ClearCommand from "./ClearCommand.js";
import ContextLimits from "./ContextLimits.js";
import CubeMap from "./CubeMap.js";
import DrawCommand from "./DrawCommand.js";
import PassState from "./PassState.js";
import PixelDatatype from "./PixelDatatype.js";
import RenderState from "./RenderState.js";
import ShaderCache from "./ShaderCache.js";
import ShaderProgram from "./ShaderProgram.js";
import Texture from "./Texture.js";
import TextureCache from "./TextureCache.js";
import UniformState from "./UniformState.js";
import VertexArray from "./VertexArray.js";

function errorToString(gl, error) {
  let 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) {
  let message = `${errorToString(gl, error)}: ${glFunc.name}(`;

  for (let i = 0; i < glFuncArguments.length; ++i) {
    if (i !== 0) {
      message += ", ";
    }
    message += glFuncArguments[i];
  }
  message += ");";

  return message;
}

function throwOnError(gl, glFunc, glFuncArguments) {
  const error = gl.getError();
  if (error !== gl.NO_ERROR) {
    throw new RuntimeError(
      createErrorMessage(gl, glFunc, glFuncArguments, error)
    );
  }
}

function makeGetterSetter(gl, propertyName, logFunction) {
  return {
    get: function () {
      const value = gl[propertyName];
      logFunction(gl, `get: ${propertyName}`, value);
      return gl[propertyName];
    },
    set: function (value) {
      gl[propertyName] = value;
      logFunction(gl, `set: ${propertyName}`, value);
    },
  };
}

function wrapGL(gl, logFunction) {
  if (!defined(logFunction)) {
    return gl;
  }

  function wrapFunction(property) {
    return function () {
      const result = property.apply(gl, arguments);
      logFunction(gl, property, arguments);
      return result;
    };
  }

  const glWrapper = {};

  // JavaScript linters normally demand 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.
  /*eslint-disable guard-for-in*/
  for (const propertyName in gl) {
    const property = gl[propertyName];

    // wrap any functions we encounter, otherwise just copy the property to the wrapper.
    if (property instanceof Function) {
      glWrapper[propertyName] = wrapFunction(property);
    } else {
      Object.defineProperty(
        glWrapper,
        propertyName,
        makeGetterSetter(gl, propertyName, logFunction)
      );
    }
  }
  /*eslint-enable guard-for-in*/

  return glWrapper;
}

function getExtension(gl, names) {
  const length = names.length;
  for (let i = 0; i < length; ++i) {
    const extension = gl.getExtension(names[i]);
    if (extension) {
      return extension;
    }
  }

  return undefined;
}

/**
 * @private
 * @constructor
 */
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);
  Check.defined("canvas", canvas);
  //>>includeEnd('debug');

  this._canvas = canvas;

  options = clone(options, true);
  // Don't use defaultValue.EMPTY_OBJECT here because the options object gets modified in the next line.
  options = defaultValue(options, {});
  options.allowTextureFilterAnisotropic = defaultValue(
    options.allowTextureFilterAnisotropic,
    true
  );
  const webglOptions = defaultValue(options.webgl, {});

  // Override select WebGL defaults
  webglOptions.alpha = defaultValue(webglOptions.alpha, false); // WebGL default is true
  webglOptions.stencil = defaultValue(webglOptions.stencil, true); // WebGL default is false

  const requestWebgl2 =
    defaultValue(options.requestWebgl2, false) &&
    typeof WebGL2RenderingContext !== "undefined";
  let webgl2 = false;

  let glContext;
  const getWebGLStub = options.getWebGLStub;

  if (!defined(getWebGLStub)) {
    if (requestWebgl2) {
      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."
      );
    }
  } else {
    // Use WebGL stub when requested for unit tests
    glContext = getWebGLStub(canvas, webglOptions);
  }

  this._originalGLContext = glContext;
  this._gl = 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);
  this._textureCache = new TextureCache();

  const gl = glContext;

  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

  ContextLimits._maximumSamples = this._webgl2
    ? gl.getParameter(gl.MAX_SAMPLES)
    : 0;

  const aliasedLineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); // must include 1
  ContextLimits._minimumAliasedLineWidth = aliasedLineWidthRange[0];
  ContextLimits._maximumAliasedLineWidth = aliasedLineWidthRange[1];

  const aliasedPointSizeRange = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE); // must include 1
  ContextLimits._minimumAliasedPointSize = aliasedPointSizeRange[0];
  ContextLimits._maximumAliasedPointSize = aliasedPointSizeRange[1];

  const maximumViewportDimensions = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
  ContextLimits._maximumViewportWidth = maximumViewportDimensions[0];
  ContextLimits._maximumViewportHeight = maximumViewportDimensions[1];

  const highpFloat = gl.getShaderPrecisionFormat(
    gl.FRAGMENT_SHADER,
    gl.HIGH_FLOAT
  );
  ContextLimits._highpFloatSupported = highpFloat.precision !== 0;
  const 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._blendMinmax = !!getExtension(gl, ["EXT_blend_minmax"]);
  this._elementIndexUint = !!getExtension(gl, ["OES_element_index_uint"]);
  this._depthTexture = !!getExtension(gl, [
    "WEBGL_depth_texture",
    "WEBKIT_WEBGL_depth_texture",
  ]);
  this._fragDepth = !!getExtension(gl, ["EXT_frag_depth"]);
  this._debugShaders = getExtension(gl, ["WEBGL_debug_shaders"]);

  this._textureFloat = !!getExtension(gl, ["OES_texture_float"]);
  this._textureHalfFloat = !!getExtension(gl, ["OES_texture_half_float"]);

  this._textureFloatLinear = !!getExtension(gl, ["OES_texture_float_linear"]);
  this._textureHalfFloatLinear = !!getExtension(gl, [
    "OES_texture_half_float_linear",
  ]);

  this._colorBufferFloat = !!getExtension(gl, [
    "EXT_color_buffer_float",
    "WEBGL_color_buffer_float",
  ]);
  this._floatBlend = !!getExtension(gl, ["EXT_float_blend"]);
  this._colorBufferHalfFloat = !!getExtension(gl, [
    "EXT_color_buffer_half_float",
  ]);

  this._s3tc = !!getExtension(gl, [
    "WEBGL_compressed_texture_s3tc",
    "MOZ_WEBGL_compressed_texture_s3tc",
    "WEBKIT_WEBGL_compressed_texture_s3tc",
  ]);
  this._pvrtc = !!getExtension(gl, [
    "WEBGL_compressed_texture_pvrtc",
    "WEBKIT_WEBGL_compressed_texture_pvrtc",
  ]);
  this._astc = !!getExtension(gl, ["WEBGL_compressed_texture_astc"]);
  this._etc = !!getExtension(gl, ["WEBG_compressed_texture_etc"]);
  this._etc1 = !!getExtension(gl, ["WEBGL_compressed_texture_etc1"]);
  this._bc7 = !!getExtension(gl, ["EXT_texture_compression_bptc"]);

  // It is necessary to pass supported formats to loadKTX2
  // because imagery layers don't have access to the context.
  loadKTX2.setKTX2SupportedFormats(
    this._s3tc,
    this._pvrtc,
    this._astc,
    this._etc,
    this._etc1,
    this._bc7
  );

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

  let glCreateVertexArray;
  let glBindVertexArray;
  let glDeleteVertexArray;

  let glDrawElementsInstanced;
  let glDrawArraysInstanced;
  let glVertexAttribDivisor;

  let glDrawBuffers;

  let vertexArrayObject;
  let instancedArrays;
  let drawBuffers;

  if (webgl2) {
    const 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;

  this._clearColor = new Color(0.0, 0.0, 0.0, 0.0);
  this._clearDepth = 1.0;
  this._clearStencil = 0;

  const us = new UniformState();
  const ps = new PassState(this);
  const rs = RenderState.fromCache();

  this._defaultPassState = ps;
  this._defaultRenderState = rs;
  // default texture has a value of (1, 1, 1)
  // default emissive texture has a value of (0, 0, 0)
  // default normal texture is +z which is encoded as (0.5, 0.5, 1)
  this._defaultTexture = undefined;
  this._defaultEmissiveTexture = undefined;
  this._defaultNormalTexture = 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 (let 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);
}

const defaultFramebufferMarker = {};

Object.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;
    },
  },
  textureCache: {
    get: function () {
      return this._textureCache;
    },
  },
  uniformState: {
    get: function () {
      return this._us;
    },
  },

  /**
   * 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 stencil buffers.
   * Stencil buffers are not supported by all systems.
   * @memberof Context.prototype
   * @type {Boolean}
   */
  stencilBuffer: {
    get: function () {
      return this._stencilBits >= 8;
    },
  },

  /**
   * <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 WebGL context supports multisample antialiasing. Requires
   * WebGL2.
   * @memberof Context.prototype
   * @type {Boolean}
   */
  msaa: {
    get: function () {
      return this._webgl2;
    },
  },

  /**
   * <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 || this._webgl2;
    },
  },

  /**
   * <code>true</code> if the EXT_float_blend extension is supported. This
   * extension enables blending with 32-bit float values.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_float_blend/}
   */
  floatBlend: {
    get: function () {
      return this._floatBlend;
    },
  },

  /**
   * <code>true</code> if the EXT_blend_minmax extension is supported.  This
   * extension extends blending capabilities by adding two new blend equations:
   * the minimum or maximum color components of the source and destination colors.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_blend_minmax/}
   */
  blendMinmax: {
    get: function () {
      return this._blendMinmax || this._webgl2;
    },
  },

  /**
   * <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 || this._webgl2;
    },
  },

  /**
   * <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 https://www.khronos.org/registry/webgl/extensions/OES_texture_float/}
   */
  floatingPointTexture: {
    get: function () {
      return this._webgl2 || this._textureFloat;
    },
  },

  /**
   * <code>true</code> if OES_texture_half_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 https://www.khronos.org/registry/webgl/extensions/OES_texture_half_float/}
   */
  halfFloatingPointTexture: {
    get: function () {
      return this._webgl2 || this._textureHalfFloat;
    },
  },

  /**
   * <code>true</code> if OES_texture_float_linear is supported. This extension provides
   * access to linear sampling methods for minification and magnification filters of floating-point textures.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/}
   */
  textureFloatLinear: {
    get: function () {
      return this._textureFloatLinear;
    },
  },

  /**
   * <code>true</code> if OES_texture_half_float_linear is supported. This extension provides
   * access to linear sampling methods for minification and magnification filters of half floating-point textures.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/OES_texture_half_float_linear/}
   */
  textureHalfFloatLinear: {
    get: function () {
      return (
        (this._webgl2 && this._textureFloatLinear) ||
        (!this._webgl2 && this._textureHalfFloatLinear)
      );
    },
  },

  /**
   * <code>true</code> if EXT_texture_filter_anisotropic is supported. This extension provides
   * access to anisotropic filtering for textured surfaces at an oblique angle from the viewer.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/}
   */
  textureFilterAnisotropic: {
    get: function () {
      return !!this._textureFilterAnisotropic;
    },
  },

  /**
   * <code>true</code> if WEBGL_compressed_texture_s3tc is supported.  This extension provides
   * access to DXT compressed textures.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/}
   */
  s3tc: {
    get: function () {
      return this._s3tc;
    },
  },

  /**
   * <code>true</code> if WEBGL_compressed_texture_pvrtc is supported.  This extension provides
   * access to PVR compressed textures.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/}
   */
  pvrtc: {
    get: function () {
      return this._pvrtc;
    },
  },

  /**
   * <code>true</code> if WEBGL_compressed_texture_astc is supported.  This extension provides
   * access to ASTC compressed textures.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/}
   */
  astc: {
    get: function () {
      return this._astc;
    },
  },

  /**
   * <code>true</code> if WEBGL_compressed_texture_etc is supported.  This extension provides
   * access to ETC compressed textures.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/}
   */
  etc: {
    get: function () {
      return this._etc;
    },
  },

  /**
   * <code>true</code> if WEBGL_compressed_texture_etc1 is supported.  This extension provides
   * access to ETC1 compressed textures.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/}
   */
  etc1: {
    get: function () {
      return this._etc1;
    },
  },

  /**
   * <code>true</code> if EXT_texture_compression_bptc is supported.  This extension provides
   * access to BC7 compressed textures.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_texture_compression_bptc/}
   */
  bc7: {
    get: function () {
      return this._bc7;
    },
  },

  /**
   * <code>true</code> if S3TC, PVRTC, ASTC, ETC, ETC1, or BC7 compression is supported.
   * @memberof Context.prototype
   * @type {Boolean}
   */
  supportsBasis: {
    get: function () {
      return (
        this._s3tc ||
        this._pvrtc ||
        this._astc ||
        this._etc ||
        this._etc1 ||
        this._bc7
      );
    },
  },

  /**
   * <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 || this._webgl2;
    },
  },

  /**
   * <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 EXT_color_buffer_float extension is supported.  This
   * extension makes the gl.RGBA32F format color renderable.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_color_buffer_float/}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_color_buffer_float/}
   */
  colorBufferFloat: {
    get: function () {
      return this._colorBufferFloat;
    },
  },

  /**
   * <code>true</code> if the EXT_color_buffer_half_float extension is supported.  This
   * extension makes the format gl.RGBA16F format color renderable.
   * @memberof Context.prototype
   * @type {Boolean}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_color_buffer_half_float/}
   * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_color_buffer_float/}
   */
  colorBufferHalfFloat: {
    get: function () {
      return (
        (this._webgl2 && this._colorBufferFloat) ||
        (!this._webgl2 && this._colorBufferHalfFloat)
      );
    },
  },

  /**
   * <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 : undefined
      );
    },
  },

  /**
   * 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]),
          },
          flipY: false,
        });
      }

      return this._defaultTexture;
    },
  },
  /**
   * A 1x1 RGB texture initialized to [0, 0, 0] representing a material that is
   * not emissive. This can be used as a placeholder texture for emissive
   * textures while other textures are downloaded.
   * @memberof Context.prototype
   * @type {Texture}
   */
  defaultEmissiveTexture: {
    get: function () {
      if (this._defaultEmissiveTexture === undefined) {
        this._defaultEmissiveTexture = new Texture({
          context: this,
          pixelFormat: PixelFormat.RGB,
          source: {
            width: 1,
            height: 1,
            arrayBufferView: new Uint8Array([0, 0, 0]),
          },
          flipY: false,
        });
      }

      return this._defaultEmissiveTexture;
    },
  },
  /**
   * A 1x1 RGBA texture initialized to [128, 128, 255] to encode a tangent
   * space normal pointing in the +z direction, i.e. (0, 0, 1). This can
   * be used as a placeholder normal texture while other textures are
   * downloaded.
   * @memberof Context.prototype
   * @type {Texture}
   */
  defaultNormalTexture: {
    get: function () {
      if (this._defaultNormalTexture === undefined) {
        this._defaultNormalTexture = new Texture({
          context: this,
          pixelFormat: PixelFormat.RGB,
          source: {
            width: 1,
            height: 1,
            arrayBufferView: new Uint8Array([128, 128, 255]),
          },
          flipY: false,
        });
      }

      return this._defaultNormalTexture;
    },
  },

  /**
   * 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) {
        const 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,
          },
          flipY: false,
        });
      }

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

/**
 * Validates a framebuffer.
 * Available in debug builds only.
 * @private
 */
function validateFramebuffer(context) {
  //>>includeStart('debug', pragmas.debug);
  if (context.validateFramebuffer) {
    const gl = context._gl;
    const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);

    if (status !== gl.FRAMEBUFFER_COMPLETE) {
      let 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);
    }
  }
  //>>includeEnd('debug');
}

function applyRenderState(context, renderState, passState, clear) {
  const previousRenderState = context._currentRenderState;
  const previousPassState = context._currentPassState;
  context._currentRenderState = renderState;
  context._currentPassState = passState;
  RenderState.partialApply(
    context._gl,
    previousRenderState,
    renderState,
    previousPassState,
    passState,
    clear
  );
}

let 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;
    let buffers = scratchBackBufferArray;

    if (defined(framebuffer)) {
      framebuffer._bind();
      validateFramebuffer(context);

      // TODO: Need a way for a command to give what draw buffers are active.
      buffers = framebuffer._getActiveColorAttachments();
    } else {
      const gl = context._gl;
      gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    }

    if (context.drawBuffers) {
      context.glDrawBuffers(buffers);
    }
  }
}

const defaultClearCommand = new ClearCommand();

Context.prototype.clear = function (clearCommand, passState) {
  clearCommand = defaultValue(clearCommand, defaultClearCommand);
  passState = defaultValue(passState, this._defaultPassState);

  const gl = this._gl;
  let bitmask = 0;

  const c = clearCommand.color;
  const d = clearCommand.depth;
  const 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;
  }

  const 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.
  const framebuffer = defaultValue(
    clearCommand.framebuffer,
    passState.framebuffer
  );
  bindFramebuffer(this, framebuffer);

  gl.clear(bitmask);
};

function beginDraw(
  context,
  framebuffer,
  passState,
  shaderProgram,
  renderState
) {
  //>>includeStart('debug', pragmas.debug);
  if (defined(framebuffer) && renderState.depthTest) {
    if (renderState.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, renderState, passState, false);
  shaderProgram._bind();
  context._maxFrameTextureUnitIndex = Math.max(
    context._maxFrameTextureUnitIndex,
    shaderProgram.maximumTextureUnitIndex
  );
}

function continueDraw(context, drawCommand, shaderProgram, uniformMap) {
  const primitiveType = drawCommand._primitiveType;
  const va = drawCommand._vertexArray;
  let offset = drawCommand._offset;
  let count = drawCommand._count;
  const instanceCount = drawCommand.instanceCount;

  //>>includeStart('debug', pragmas.debug);
  if (!PrimitiveType.validate(primitiveType)) {
    throw new DeveloperError(
      "drawCommand.primitiveType is required and must be valid."
    );
  }

  Check.defined("drawCommand.vertexArray", va);
  Check.typeOf.number.greaterThanOrEquals("drawCommand.offset", offset, 0);
  if (defined(count)) {
    Check.typeOf.number.greaterThanOrEquals("drawCommand.count", count, 0);
  }
  Check.typeOf.number.greaterThanOrEquals(
    "drawCommand.instanceCount",
    instanceCount,
    0
  );
  if (instanceCount > 0 && !context.instancedArrays) {
    throw new DeveloperError("Instanced arrays extension is not supported");
  }
  //>>includeEnd('debug');

  context._us.model = defaultValue(drawCommand._modelMatrix, Matrix4.IDENTITY);
  shaderProgram._setUniforms(
    uniformMap,
    context._us,
    context.validateShaderProgram
  );

  va._bind();
  const 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,
  shaderProgram,
  uniformMap
) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("drawCommand", drawCommand);
  Check.defined("drawCommand.shaderProgram", drawCommand._shaderProgram);
  //>>includeEnd('debug');

  passState = defaultValue(passState, this._defaultPassState);
  // The command's framebuffer takes presidence over the pass' framebuffer, e.g., for off-screen rendering.
  const framebuffer = defaultValue(
    drawCommand._framebuffer,
    passState.framebuffer
  );
  const renderState = defaultValue(
    drawCommand._renderState,
    this._defaultRenderState
  );
  shaderProgram = defaultValue(shaderProgram, drawCommand._shaderProgram);
  uniformMap = defaultValue(uniformMap, drawCommand._uniformMap);

  beginDraw(this, framebuffer, passState, shaderProgram, renderState);
  continueDraw(this, drawCommand, shaderProgram, uniformMap);
};

Context.prototype.endFrame = function () {
  const gl = this._gl;
  gl.useProgram(null);

  this._currentFramebuffer = undefined;
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);

  const buffers = scratchBackBufferArray;
  if (this.drawBuffers) {
    this.glDrawBuffers(buffers);
  }

  const length = this._maxFrameTextureUnitIndex;
  this._maxFrameTextureUnitIndex = 0;

  for (let 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) {
  const gl = this._gl;

  readState = defaultValue(readState, defaultValue.EMPTY_OBJECT);
  const x = Math.max(defaultValue(readState.x, 0), 0);
  const y = Math.max(defaultValue(readState.y, 0), 0);
  const width = defaultValue(readState.width, gl.drawingBufferWidth);
  const height = defaultValue(readState.height, gl.drawingBufferHeight);
  const framebuffer = readState.framebuffer;

  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.number.greaterThan("readState.width", width, 0);
  Check.typeOf.number.greaterThan("readState.height", height, 0);
  //>>includeEnd('debug');

  let pixelDatatype = PixelDatatype.UNSIGNED_BYTE;
  if (defined(framebuffer) && framebuffer.numberOfColorAttachments > 0) {
    pixelDatatype = framebuffer.getColorTexture(0).pixelDatatype;
  }

  const pixels = PixelFormat.createTypedArray(
    PixelFormat.RGBA,
    pixelDatatype,
    width,
    height
  );

  bindFramebuffer(this, framebuffer);

  gl.readPixels(
    x,
    y,
    width,
    height,
    PixelFormat.RGBA,
    PixelDatatype.toWebGLConstant(pixelDatatype, this),
    pixels
  );

  return pixels;
};

const viewportQuadAttributeLocations = {
  position: 0,
  textureCoordinates: 1,
};

Context.prototype.getViewportQuadVertexArray = function () {
  // Per-context cache for viewport quads
  let vertexArray = this.cache.viewportQuad_vertexArray;

  if (!defined(vertexArray)) {
    const 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,
    pass: overrides.pass,
  });
};

/**
 * 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
 * const object = context.getObjectByPickColor(pickColor);
 *
 * @see Context#createPickId
 */
Context.prototype.getObjectByPickColor = function (pickColor) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("pickColor", pickColor);
  //>>includeEnd('debug');

  return this._pickObjects[pickColor.toRgba()];
};

function PickId(pickObjects, key, color) {
  this._pickObjects = pickObjects;
  this.key = key;
  this.color = color;
}

Object.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);
  Check.defined("object", object);
  //>>includeEnd('debug');

  // the increment and assignment have to be separate statements to
  // actually detect overflow in the Uint32 value
  ++this._nextPickColor[0];
  const 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.
  const cache = this.cache;
  for (const property in cache) {
    if (cache.hasOwnProperty(property)) {
      const propertyValue = cache[property];
      if (defined(propertyValue.destroy)) {
        propertyValue.destroy();
      }
    }
  }

  this._shaderCache = this._shaderCache.destroy();
  this._textureCache = this._textureCache.destroy();
  this._defaultTexture = this._defaultTexture && this._defaultTexture.destroy();
  this._defaultEmissiveTexture =
    this._defaultEmissiveTexture && this._defaultEmissiveTexture.destroy();
  this._defaultNormalTexture =
    this._defaultNormalTexture && this._defaultNormalTexture.destroy();
  this._defaultCubeMap = this._defaultCubeMap && this._defaultCubeMap.destroy();

  return destroyObject(this);
};
export default Context;
