import Check from "../Core/Check.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 deprecationWarning from "../Core/deprecationWarning.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";
/**
* @private
* @constructor
*
* @param {HTMLCanvasElement} canvas The canvas element to which the context will be associated
* @param {ContextOptions} [options] Options to control WebGL settings for the context
*/
function Context(canvas, options) {
//>>includeStart('debug', pragmas.debug);
Check.defined("canvas", canvas);
//>>includeEnd('debug');
const {
getWebGLStub,
requestWebgl1,
webgl: webglOptions = {},
allowTextureFilterAnisotropic = true,
} = defaultValue(options, {});
// Override select WebGL defaults
webglOptions.alpha = defaultValue(webglOptions.alpha, false); // WebGL default is true
webglOptions.stencil = defaultValue(webglOptions.stencil, true); // WebGL default is false
webglOptions.powerPreference = defaultValue(
webglOptions.powerPreference,
"high-performance"
); // WebGL default is "default"
const glContext = defined(getWebGLStub)
? getWebGLStub(canvas, webglOptions)
: getWebGLContext(canvas, webglOptions, requestWebgl1);
// Get context type. instanceof will throw if WebGL2 is not supported
const webgl2Supported = typeof WebGL2RenderingContext !== "undefined";
const webgl2 = webgl2Supported && glContext instanceof WebGL2RenderingContext;
this._canvas = canvas;
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 = 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);
/**
* The options used to construct this context
*
* @type {ContextOptions}
*/
this.options = {
getWebGLStub: getWebGLStub,
requestWebgl1: requestWebgl1,
webgl: webglOptions,
allowTextureFilterAnisotropic: allowTextureFilterAnisotropic,
};
/**
* A cache of objects tied to this context. Just before the Context is destroyed,
* destroy
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);
}
/**
* @typedef {object} ContextOptions
*
* Options to control the setting up of a WebGL Context.
*
* allowTextureFilterAnisotropic
defaults to true, which enables
* anisotropic texture filtering when the WebGL extension is supported.
* Setting this to false will improve performance, but hurt visual quality,
* especially for horizon views.
*
* alpha
defaults to false, which can improve performance
* compared to the standard WebGL default of true. If an application needs
* to composite Cesium above other HTML elements using alpha-blending, set
* alpha
to true.
*
STENCIL_BITS
.
*/
stencilBits: {
get: function () {
return this._stencilBits;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
if the WebGL context supports multisample antialiasing. Requires
* WebGL2.
* @memberof Context.prototype
* @type {boolean}
*/
msaa: {
get: function () {
return this._webgl2;
},
},
/**
* true
if the OES_standard_derivatives extension is supported. This
* extension provides access to dFdx
, dFdy
, and fwidth
* functions from GLSL. A shader using these functions still needs to explicitly enable the
* extension with #extension GL_OES_standard_derivatives : enable
.
* @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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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)
);
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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
);
},
},
/**
* true
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;
},
},
/**
* true
if the EXT_frag_depth extension is supported. This
* extension provides access to the gl_FragDepthEXT
built-in output variable
* from GLSL fragment shaders. A shader using these functions still needs to explicitly enable the
* extension with #extension GL_EXT_frag_depth : enable
.
* @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;
},
},
/**
* true
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;
},
},
/**
* true
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;
},
},
/**
* true
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)
);
},
},
/**
* true
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 gl_FragData
.
* A shader using this feature needs to explicitly enable the extension with
* #extension GL_EXT_draw_buffers : enable
.
* @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 color
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);
};
// Used for specs.
Context._deprecationWarning = deprecationWarning;
export default Context;