You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1454 lines
43 KiB
JavaScript
1454 lines
43 KiB
JavaScript
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 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) {
|
|
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, logFunction) {
|
|
return {
|
|
get: function () {
|
|
var 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 () {
|
|
var result = property.apply(gl, arguments);
|
|
logFunction(gl, property, arguments);
|
|
return result;
|
|
};
|
|
}
|
|
|
|
var 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 (var propertyName in gl) {
|
|
var 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) {
|
|
var length = names.length;
|
|
for (var i = 0; i < length; ++i) {
|
|
var 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
|
|
);
|
|
var 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
|
|
|
|
var requestWebgl2 =
|
|
defaultValue(options.requestWebgl2, false) &&
|
|
typeof WebGL2RenderingContext !== "undefined";
|
|
var webgl2 = false;
|
|
|
|
var glContext;
|
|
var 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();
|
|
|
|
var 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
|
|
|
|
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._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._etc1 = !!getExtension(gl, ["WEBGL_compressed_texture_etc1"]);
|
|
|
|
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;
|
|
|
|
this._clearColor = new Color(0.0, 0.0, 0.0, 0.0);
|
|
this._clearDepth = 1.0;
|
|
this._clearStencil = 0;
|
|
|
|
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 = {};
|
|
|
|
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 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_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_texture_compression_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_texture_compression_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_texture_compression_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 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 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,
|
|
},
|
|
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) {
|
|
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);
|
|
}
|
|
}
|
|
//>>includeEnd('debug');
|
|
}
|
|
|
|
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);
|
|
|
|
// 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,
|
|
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) {
|
|
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."
|
|
);
|
|
}
|
|
|
|
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();
|
|
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,
|
|
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.
|
|
var framebuffer = defaultValue(
|
|
drawCommand._framebuffer,
|
|
passState.framebuffer
|
|
);
|
|
var 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 () {
|
|
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 = defaultValue(readState, defaultValue.EMPTY_OBJECT);
|
|
var x = Math.max(defaultValue(readState.x, 0), 0);
|
|
var y = Math.max(defaultValue(readState.y, 0), 0);
|
|
var width = defaultValue(readState.width, gl.drawingBufferWidth);
|
|
var height = defaultValue(readState.height, gl.drawingBufferHeight);
|
|
var 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');
|
|
|
|
var pixelDatatype = PixelDatatype.UNSIGNED_BYTE;
|
|
if (defined(framebuffer) && framebuffer.numberOfColorAttachments > 0) {
|
|
pixelDatatype = framebuffer.getColorTexture(0).pixelDatatype;
|
|
}
|
|
|
|
var 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;
|
|
};
|
|
|
|
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,
|
|
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
|
|
* var 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];
|
|
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._textureCache = this._textureCache.destroy();
|
|
this._defaultTexture = this._defaultTexture && this._defaultTexture.destroy();
|
|
this._defaultCubeMap = this._defaultCubeMap && this._defaultCubeMap.destroy();
|
|
|
|
return destroyObject(this);
|
|
};
|
|
export default Context;
|