cesium
Version:
Cesium is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,203 lines (1,036 loc) • 46.7 kB
JavaScript
/*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) {
var rs = defaultValue(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 = drawCommand._shaderProgram;
sp._bind();
context._maxFrameTextureUnitIndex = Math.max(context._maxFrameTextureUnitIndex, sp.maximumTextureUnitIndex);
}
function continueDraw(context, drawCommand) {
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);
drawCommand._shaderProgram._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) {
//>>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);
continueDraw(this, drawCommand);
};
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;
});