Add existing to tracked

This commit is contained in:
Jay
2026-08-11 09:53:42 -04:00
parent afe07f3055
commit ffd6e3d73c
8531 changed files with 4396230 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+452
View File
@@ -0,0 +1,452 @@
import Check from "../Core/Check.js";
import createGuid from "../Core/createGuid.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import WebGLConstants from "../Core/WebGLConstants.js";
import BufferUsage from "./BufferUsage.js";
/**
* @private
*/
function Buffer(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
if (!defined(options.typedArray) && !defined(options.sizeInBytes)) {
throw new DeveloperError(
"Either options.sizeInBytes or options.typedArray is required.",
);
}
if (defined(options.typedArray) && defined(options.sizeInBytes)) {
throw new DeveloperError(
"Cannot pass in both options.sizeInBytes and options.typedArray.",
);
}
if (defined(options.typedArray)) {
Check.typeOf.object("options.typedArray", options.typedArray);
Check.typeOf.number(
"options.typedArray.byteLength",
options.typedArray.byteLength,
);
}
if (!BufferUsage.validate(options.usage)) {
throw new DeveloperError("usage is invalid.");
}
//>>includeEnd('debug');
const gl = options.context._gl;
const bufferTarget = options.bufferTarget;
const typedArray = options.typedArray;
let sizeInBytes = options.sizeInBytes;
const usage = options.usage;
const hasArray = defined(typedArray);
if (hasArray) {
sizeInBytes = typedArray.byteLength;
}
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThan("sizeInBytes", sizeInBytes, 0);
//>>includeEnd('debug');
const buffer = gl.createBuffer();
gl.bindBuffer(bufferTarget, buffer);
gl.bufferData(bufferTarget, hasArray ? typedArray : sizeInBytes, usage);
gl.bindBuffer(bufferTarget, null);
this._id = createGuid();
this._gl = gl;
this._webgl2 = options.context._webgl2;
this._bufferTarget = bufferTarget;
this._sizeInBytes = sizeInBytes;
this._usage = usage;
this._buffer = buffer;
this.vertexArrayDestroyable = true;
}
Buffer.createPixelBuffer = function (options) {
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
//>>includeEnd('debug');
if (!options.context._webgl2) {
throw new DeveloperError(
"A WebGL 2 context is required to create PixelBuffers.",
);
}
return new Buffer({
context: options.context,
bufferTarget: WebGLConstants.PIXEL_PACK_BUFFER,
typedArray: options.typedArray,
sizeInBytes: options.sizeInBytes,
usage: options.usage,
});
};
/**
* Creates a vertex buffer, which contains untyped vertex data in GPU-controlled memory.
* <br /><br />
* A vertex array defines the actual makeup of a vertex, e.g., positions, normals, texture coordinates,
* etc., by interpreting the raw data in one or more vertex buffers.
*
* @param {object} options An object containing the following properties:
* @param {Context} options.context The context in which to create the buffer
* @param {ArrayBufferView} [options.typedArray] A typed array containing the data to copy to the buffer.
* @param {number} [options.sizeInBytes] A <code>Number</code> defining the size of the buffer in bytes. Required if options.typedArray is not given.
* @param {BufferUsage} options.usage Specifies the expected usage pattern of the buffer. On some GL implementations, this can significantly affect performance. See {@link BufferUsage}.
* @returns {VertexBuffer} The vertex buffer, ready to be attached to a vertex array.
*
* @exception {DeveloperError} Must specify either <options.typedArray> or <options.sizeInBytes>, but not both.
* @exception {DeveloperError} The buffer size must be greater than zero.
* @exception {DeveloperError} Invalid <code>usage</code>.
*
*
* @example
* // Example 1. Create a dynamic vertex buffer 16 bytes in size.
* const buffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 16,
* usage : BufferUsage.DYNAMIC_DRAW
* });
*
* @example
* // Example 2. Create a dynamic vertex buffer from three floating-point values.
* // The data copied to the vertex buffer is considered raw bytes until it is
* // interpreted as vertices using a vertex array.
* const positionBuffer = buffer.createVertexBuffer({
* context : context,
* typedArray : new Float32Array([0, 0, 0]),
* usage : BufferUsage.STATIC_DRAW
* });
*
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffer.xml|glGenBuffer}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml|glBindBuffer} with <code>ARRAY_BUFFER</code>
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with <code>ARRAY_BUFFER</code>
*/
Buffer.createVertexBuffer = function (options) {
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
//>>includeEnd('debug');
return new Buffer({
context: options.context,
bufferTarget: WebGLConstants.ARRAY_BUFFER,
typedArray: options.typedArray,
sizeInBytes: options.sizeInBytes,
usage: options.usage,
});
};
/**
* Creates an index buffer, which contains typed indices in GPU-controlled memory.
* <br /><br />
* An index buffer can be attached to a vertex array to select vertices for rendering.
* <code>Context.draw</code> can render using the entire index buffer or a subset
* of the index buffer defined by an offset and count.
*
* @param {object} options An object containing the following properties:
* @param {Context} options.context The context in which to create the buffer
* @param {ArrayBufferView} [options.typedArray] A typed array containing the data to copy to the buffer.
* @param {number} [options.sizeInBytes] A <code>Number</code> defining the size of the buffer in bytes. Required if options.typedArray is not given.
* @param {BufferUsage} options.usage Specifies the expected usage pattern of the buffer. On some GL implementations, this can significantly affect performance. See {@link BufferUsage}.
* @param {IndexDatatype} options.indexDatatype The datatype of indices in the buffer.
* @returns {IndexBuffer} The index buffer, ready to be attached to a vertex array.
*
* @exception {DeveloperError} Must specify either <options.typedArray> or <options.sizeInBytes>, but not both.
* @exception {DeveloperError} IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system. Check context.elementIndexUint.
* @exception {DeveloperError} The size in bytes must be greater than zero.
* @exception {DeveloperError} Invalid <code>usage</code>.
* @exception {DeveloperError} Invalid <code>indexDatatype</code>.
*
*
* @example
* // Example 1. Create a stream index buffer of unsigned shorts that is
* // 16 bytes in size.
* const buffer = Buffer.createIndexBuffer({
* context : context,
* sizeInBytes : 16,
* usage : BufferUsage.STREAM_DRAW,
* indexDatatype : IndexDatatype.UNSIGNED_SHORT
* });
*
* @example
* // Example 2. Create a static index buffer containing three unsigned shorts.
* const buffer = Buffer.createIndexBuffer({
* context : context,
* typedArray : new Uint16Array([0, 1, 2]),
* usage : BufferUsage.STATIC_DRAW,
* indexDatatype : IndexDatatype.UNSIGNED_SHORT
* });
*
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffer.xml|glGenBuffer}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml|glBindBuffer} with <code>ELEMENT_ARRAY_BUFFER</code>
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with <code>ELEMENT_ARRAY_BUFFER</code>
*/
Buffer.createIndexBuffer = function (options) {
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
if (!IndexDatatype.validate(options.indexDatatype)) {
throw new DeveloperError("Invalid indexDatatype.");
}
if (
options.indexDatatype === IndexDatatype.UNSIGNED_INT &&
!options.context.elementIndexUint
) {
throw new DeveloperError(
"IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system. Check context.elementIndexUint.",
);
}
//>>includeEnd('debug');
const context = options.context;
const indexDatatype = options.indexDatatype;
const bytesPerIndex = IndexDatatype.getSizeInBytes(indexDatatype);
const buffer = new Buffer({
context: context,
bufferTarget: WebGLConstants.ELEMENT_ARRAY_BUFFER,
typedArray: options.typedArray,
sizeInBytes: options.sizeInBytes,
usage: options.usage,
});
const numberOfIndices = buffer.sizeInBytes / bytesPerIndex;
Object.defineProperties(buffer, {
indexDatatype: {
get: function () {
return indexDatatype;
},
},
bytesPerIndex: {
get: function () {
return bytesPerIndex;
},
},
numberOfIndices: {
get: function () {
return numberOfIndices;
},
},
});
return buffer;
};
Object.defineProperties(Buffer.prototype, {
sizeInBytes: {
get: function () {
return this._sizeInBytes;
},
},
usage: {
get: function () {
return this._usage;
},
},
});
Buffer.prototype._getBuffer = function () {
return this._buffer;
};
Buffer.prototype._bind = function () {
const gl = this._gl;
const target = this._bufferTarget;
gl.bindBuffer(target, this._buffer);
};
Buffer.prototype._unBind = function () {
const gl = this._gl;
const target = this._bufferTarget;
gl.bindBuffer(target, null);
};
Buffer.prototype.copyFromArrayView = function (arrayView, offsetInBytes) {
offsetInBytes = offsetInBytes ?? 0;
//>>includeStart('debug', pragmas.debug);
Check.defined("arrayView", arrayView);
Check.typeOf.number.lessThanOrEquals(
"offsetInBytes + arrayView.byteLength",
offsetInBytes + arrayView.byteLength,
this._sizeInBytes,
);
//>>includeEnd('debug');
const gl = this._gl;
const target = this._bufferTarget;
gl.bindBuffer(target, this._buffer);
gl.bufferSubData(target, offsetInBytes, arrayView);
gl.bindBuffer(target, null);
};
Buffer.prototype.copyFromBuffer = function (
readBuffer,
readOffset,
writeOffset,
sizeInBytes,
) {
//>>includeStart('debug', pragmas.debug);
if (!this._webgl2) {
throw new DeveloperError("A WebGL 2 context is required.");
}
if (!defined(readBuffer)) {
throw new DeveloperError("readBuffer must be defined.");
}
if (!defined(sizeInBytes) || sizeInBytes <= 0) {
throw new DeveloperError(
"sizeInBytes must be defined and be greater than zero.",
);
}
if (
!defined(readOffset) ||
readOffset < 0 ||
readOffset + sizeInBytes > readBuffer._sizeInBytes
) {
throw new DeveloperError(
"readOffset must be greater than or equal to zero and readOffset + sizeInBytes must be less than of equal to readBuffer.sizeInBytes.",
);
}
if (
!defined(writeOffset) ||
writeOffset < 0 ||
writeOffset + sizeInBytes > this._sizeInBytes
) {
throw new DeveloperError(
"writeOffset must be greater than or equal to zero and writeOffset + sizeInBytes must be less than of equal to this.sizeInBytes.",
);
}
if (
this._buffer === readBuffer._buffer &&
((writeOffset >= readOffset && writeOffset < readOffset + sizeInBytes) ||
(readOffset > writeOffset && readOffset < writeOffset + sizeInBytes))
) {
throw new DeveloperError(
"When readBuffer is equal to this, the ranges [readOffset + sizeInBytes) and [writeOffset, writeOffset + sizeInBytes) must not overlap.",
);
}
if (
(this._bufferTarget === WebGLConstants.ELEMENT_ARRAY_BUFFER &&
readBuffer._bufferTarget !== WebGLConstants.ELEMENT_ARRAY_BUFFER) ||
(this._bufferTarget !== WebGLConstants.ELEMENT_ARRAY_BUFFER &&
readBuffer._bufferTarget === WebGLConstants.ELEMENT_ARRAY_BUFFER)
) {
throw new DeveloperError(
"Can not copy an index buffer into another buffer type.",
);
}
//>>includeEnd('debug');
const readTarget = WebGLConstants.COPY_READ_BUFFER;
const writeTarget = WebGLConstants.COPY_WRITE_BUFFER;
const gl = this._gl;
gl.bindBuffer(writeTarget, this._buffer);
gl.bindBuffer(readTarget, readBuffer._buffer);
gl.copyBufferSubData(
readTarget,
writeTarget,
readOffset,
writeOffset,
sizeInBytes,
);
gl.bindBuffer(writeTarget, null);
gl.bindBuffer(readTarget, null);
};
Buffer.prototype.getBufferData = function (
arrayView,
sourceOffset,
destinationOffset,
length,
) {
sourceOffset = sourceOffset ?? 0;
destinationOffset = destinationOffset ?? 0;
//>>includeStart('debug', pragmas.debug);
if (!this._webgl2) {
throw new DeveloperError("A WebGL 2 context is required.");
}
if (!defined(arrayView)) {
throw new DeveloperError("arrayView is required.");
}
let copyLength;
let elementSize;
let arrayLength = arrayView.byteLength;
if (!defined(length)) {
if (defined(arrayLength)) {
copyLength = arrayLength - destinationOffset;
elementSize = 1;
} else {
arrayLength = arrayView.length;
copyLength = arrayLength - destinationOffset;
elementSize = arrayView.BYTES_PER_ELEMENT;
}
} else {
copyLength = length;
if (defined(arrayLength)) {
elementSize = 1;
} else {
arrayLength = arrayView.length;
elementSize = arrayView.BYTES_PER_ELEMENT;
}
}
if (destinationOffset < 0 || destinationOffset > arrayLength) {
throw new DeveloperError(
"destinationOffset must be greater than zero and less than the arrayView length.",
);
}
if (destinationOffset + copyLength > arrayLength) {
throw new DeveloperError(
"destinationOffset + length must be less than or equal to the arrayViewLength.",
);
}
if (sourceOffset < 0 || sourceOffset > this._sizeInBytes) {
throw new DeveloperError(
"sourceOffset must be greater than zero and less than the buffers size.",
);
}
if (sourceOffset + copyLength * elementSize > this._sizeInBytes) {
throw new DeveloperError(
"sourceOffset + length must be less than the buffers size.",
);
}
//>>includeEnd('debug');
const gl = this._gl;
const target = WebGLConstants.COPY_READ_BUFFER;
gl.bindBuffer(target, this._buffer);
gl.getBufferSubData(
target,
sourceOffset,
arrayView,
destinationOffset,
length,
);
gl.bindBuffer(target, null);
};
Buffer.prototype.isDestroyed = function () {
return false;
};
Buffer.prototype.destroy = function () {
this._gl.deleteBuffer(this._buffer);
return destroyObject(this);
};
export default Buffer;
+32
View File
@@ -0,0 +1,32 @@
// @ts-check
import WebGLConstants from "../Core/WebGLConstants.js";
/**
* @private
* @enum {number}
*/
const BufferUsage = {
STREAM_DRAW: WebGLConstants.STREAM_DRAW,
STATIC_DRAW: WebGLConstants.STATIC_DRAW,
DYNAMIC_DRAW: WebGLConstants.DYNAMIC_DRAW,
DYNAMIC_READ: WebGLConstants.DYNAMIC_READ,
};
/**
* @private
* @param {BufferUsage} bufferUsage
*/
// @ts-expect-error https://github.com/CesiumGS/cesium/issues/13420
BufferUsage.validate = function (bufferUsage) {
return (
bufferUsage === BufferUsage.STREAM_DRAW ||
bufferUsage === BufferUsage.STATIC_DRAW ||
bufferUsage === BufferUsage.DYNAMIC_DRAW ||
bufferUsage === BufferUsage.DYNAMIC_READ
);
};
Object.freeze(BufferUsage);
export default BufferUsage;
+102
View File
@@ -0,0 +1,102 @@
import Color from "../Core/Color.js";
import Frozen from "../Core/Frozen.js";
/**
* Represents a command to the renderer for clearing a framebuffer.
*
* @private
* @constructor
*/
function ClearCommand(options) {
options = options ?? Frozen.EMPTY_OBJECT;
/**
* The value to clear the color buffer to. When <code>undefined</code>, the color buffer is not cleared.
*
* @type {Color}
*
* @default undefined
*/
this.color = options.color;
/**
* The value to clear the depth buffer to. When <code>undefined</code>, the depth buffer is not cleared.
*
* @type {number}
*
* @default undefined
*/
this.depth = options.depth;
/**
* The value to clear the stencil buffer to. When <code>undefined</code>, the stencil buffer is not cleared.
*
* @type {number}
*
* @default undefined
*/
this.stencil = options.stencil;
/**
* The render state to apply when executing the clear command. The following states affect clearing:
* scissor test, color mask, depth mask, and stencil mask. When the render state is
* <code>undefined</code>, the default render state is used.
*
* @type {RenderState}
*
* @default undefined
*/
this.renderState = options.renderState;
/**
* The framebuffer to clear.
*
* @type {Framebuffer}
*
* @default undefined
*/
this.framebuffer = options.framebuffer;
/**
* The object who created this command. This is useful for debugging command
* execution; it allows you to see who created a command when you only have a
* reference to the command, and can be used to selectively execute commands
* with {@link Scene#debugCommandFilter}.
*
* @type {object}
*
* @default undefined
*
* @see Scene#debugCommandFilter
*/
this.owner = options.owner;
/**
* The pass in which to run this command.
*
* @type {Pass}
*
* @default undefined
*/
this.pass = options.pass;
}
/**
* Clears color to (0.0, 0.0, 0.0, 0.0); depth to 1.0; and stencil to 0.
*
* @type {ClearCommand}
*
* @constant
*/
ClearCommand.ALL = Object.freeze(
new ClearCommand({
color: new Color(0.0, 0.0, 0.0, 0.0),
depth: 1.0,
stencil: 0.0,
}),
);
ClearCommand.prototype.execute = function (context, passState) {
context.clear(this, passState);
};
export default ClearCommand;
+119
View File
@@ -0,0 +1,119 @@
import Frozen from "../Core/Frozen.js";
import Pass from "./Pass.js";
/**
* Represents a command to the renderer for GPU Compute (using old-school GPGPU).
*
* @private
* @constructor
*/
function ComputeCommand(options) {
options = options ?? Frozen.EMPTY_OBJECT;
/**
* The vertex array. If none is provided, a viewport quad will be used.
*
* @type {VertexArray}
* @default undefined
*/
this.vertexArray = options.vertexArray;
/**
* The fragment shader source. The default vertex shader is ViewportQuadVS.
*
* @type {ShaderSource}
* @default undefined
*/
this.fragmentShaderSource = options.fragmentShaderSource;
/**
* The shader program to apply.
*
* @type {ShaderProgram}
* @default undefined
*/
this.shaderProgram = options.shaderProgram;
/**
* An object with functions whose names match the uniforms in the shader program
* and return values to set those uniforms.
*
* @type {object}
* @default undefined
*/
this.uniformMap = options.uniformMap;
/**
* Texture to use for offscreen rendering.
*
* @type {Texture}
* @default undefined
*/
this.outputTexture = options.outputTexture;
/**
* Function that is called immediately before the ComputeCommand is executed. Used to
* update any renderer resources. Takes the ComputeCommand as its single argument.
*
* @type {Function}
* @default undefined
*/
this.preExecute = options.preExecute;
/**
* Function that is called after the ComputeCommand is executed. Takes the output
* texture as its single argument.
*
* @type {Function}
* @default undefined
*/
this.postExecute = options.postExecute;
/**
* Function that is called when the command is canceled
*
* @type {Function}
* @default undefined
*/
this.canceled = options.canceled;
/**
* Whether the renderer resources will persist beyond this call. If not, they
* will be destroyed after completion.
*
* @type {boolean}
* @default false
*/
this.persists = options.persists ?? false;
/**
* The pass when to render. Always compute pass.
*
* @type {Pass}
* @default Pass.COMPUTE;
*/
this.pass = Pass.COMPUTE;
/**
* The object who created this command. This is useful for debugging command
* execution; it allows us to see who created a command when we only have a
* reference to the command, and can be used to selectively execute commands
* with {@link Scene#debugCommandFilter}.
*
* @type {object}
* @default undefined
*
* @see Scene#debugCommandFilter
*/
this.owner = options.owner;
}
/**
* Executes the compute command.
*
* @param {ComputeEngine} computeEngine The context that processes the compute command.
*/
ComputeCommand.prototype.execute = function (computeEngine) {
computeEngine.execute(this);
};
export default ComputeCommand;
+135
View File
@@ -0,0 +1,135 @@
import BoundingRectangle from "../Core/BoundingRectangle.js";
import Check from "../Core/Check.js";
import Color from "../Core/Color.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import PrimitiveType from "../Core/PrimitiveType.js";
import ViewportQuadVS from "../Shaders/ViewportQuadVS.js";
import ClearCommand from "./ClearCommand.js";
import DrawCommand from "./DrawCommand.js";
import Framebuffer from "./Framebuffer.js";
import RenderState from "./RenderState.js";
import ShaderProgram from "./ShaderProgram.js";
/**
* @private
*/
function ComputeEngine(context) {
this._context = context;
}
let renderStateScratch;
const drawCommandScratch = new DrawCommand({
primitiveType: PrimitiveType.TRIANGLES,
});
const clearCommandScratch = new ClearCommand({
color: new Color(0.0, 0.0, 0.0, 0.0),
});
function createFramebuffer(context, outputTexture) {
return new Framebuffer({
context: context,
colorTextures: [outputTexture],
destroyAttachments: false,
});
}
function createViewportQuadShader(context, fragmentShaderSource) {
return ShaderProgram.fromCache({
context: context,
vertexShaderSource: ViewportQuadVS,
fragmentShaderSource: fragmentShaderSource,
attributeLocations: {
position: 0,
textureCoordinates: 1,
},
});
}
function createRenderState(width, height) {
if (
!defined(renderStateScratch) ||
renderStateScratch.viewport.width !== width ||
renderStateScratch.viewport.height !== height
) {
renderStateScratch = RenderState.fromCache({
viewport: new BoundingRectangle(0, 0, width, height),
});
}
return renderStateScratch;
}
ComputeEngine.prototype.execute = function (computeCommand) {
//>>includeStart('debug', pragmas.debug);
Check.defined("computeCommand", computeCommand);
//>>includeEnd('debug');
// This may modify the command's resources, so do error checking afterwards
if (defined(computeCommand.preExecute)) {
computeCommand.preExecute(computeCommand);
}
//>>includeStart('debug', pragmas.debug);
if (
!defined(computeCommand.fragmentShaderSource) &&
!defined(computeCommand.shaderProgram)
) {
throw new DeveloperError(
"computeCommand.fragmentShaderSource or computeCommand.shaderProgram is required.",
);
}
Check.defined("computeCommand.outputTexture", computeCommand.outputTexture);
//>>includeEnd('debug');
const outputTexture = computeCommand.outputTexture;
const width = outputTexture.width;
const height = outputTexture.height;
const context = this._context;
const vertexArray = defined(computeCommand.vertexArray)
? computeCommand.vertexArray
: context.getViewportQuadVertexArray();
const shaderProgram = defined(computeCommand.shaderProgram)
? computeCommand.shaderProgram
: createViewportQuadShader(context, computeCommand.fragmentShaderSource);
const framebuffer = createFramebuffer(context, outputTexture);
const renderState = createRenderState(width, height);
const uniformMap = computeCommand.uniformMap;
const clearCommand = clearCommandScratch;
clearCommand.framebuffer = framebuffer;
clearCommand.renderState = renderState;
clearCommand.execute(context);
const drawCommand = drawCommandScratch;
drawCommand.vertexArray = vertexArray;
drawCommand.renderState = renderState;
drawCommand.shaderProgram = shaderProgram;
drawCommand.uniformMap = uniformMap;
drawCommand.framebuffer = framebuffer;
drawCommand.execute(context);
framebuffer.destroy();
if (!computeCommand.persists) {
shaderProgram.destroy();
if (defined(computeCommand.vertexArray)) {
vertexArray.destroy();
}
}
if (defined(computeCommand.postExecute)) {
computeCommand.postExecute(outputTexture);
}
};
ComputeEngine.prototype.isDestroyed = function () {
return false;
};
ComputeEngine.prototype.destroy = function () {
return destroyObject(this);
};
export default ComputeEngine;
File diff suppressed because it is too large Load Diff
+332
View File
@@ -0,0 +1,332 @@
/**
* These are set in the constructor for {@link Context}
*
* @private
*/
const ContextLimits = {
_maximumCombinedTextureImageUnits: 0,
_maximumCubeMapSize: 0,
_maximumFragmentUniformVectors: 0,
_maximumTextureImageUnits: 0,
_maximumRenderbufferSize: 0,
_maximumTextureSize: 0,
_maximum3DTextureSize: 0,
_maximumVaryingVectors: 0,
_maximumVertexAttributes: 0,
_maximumVertexTextureImageUnits: 0,
_maximumVertexUniformVectors: 0,
_minimumAliasedLineWidth: 0,
_maximumAliasedLineWidth: 0,
_minimumAliasedPointSize: 0,
_maximumAliasedPointSize: 0,
_maximumViewportWidth: 0,
_maximumViewportHeight: 0,
_maximumTextureFilterAnisotropy: 0,
_maximumDrawBuffers: 0,
_maximumColorAttachments: 0,
_maximumSamples: 0,
_highpFloatSupported: false,
_highpIntSupported: false,
};
Object.defineProperties(ContextLimits, {
/**
* The maximum number of texture units that can be used from the vertex and fragment
* shader with this WebGL implementation.
* If both shaders access the same texture unit, this counts as two texture units.
* The minimum in WebGL2 contexts is 32, or 8 in WebGL1 contexts.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_COMBINED_TEXTURE_IMAGE_UNITS</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumCombinedTextureImageUnits: {
get: function () {
return ContextLimits._maximumCombinedTextureImageUnits;
},
},
/**
* The approximate maximum cube map width and height supported by this WebGL implementation.
* The minimum in WebGL2 contexts is 2048, but most desktop and laptop implementations will support much larger sizes like 8192.
* The minimum in WebGL1 contexts is 16.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_CUBE_MAP_TEXTURE_SIZE</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumCubeMapSize: {
get: function () {
return ContextLimits._maximumCubeMapSize;
},
},
/**
* The maximum number of <code>vec4</code>, <code>ivec4</code>, and <code>bvec4</code>
* uniforms that can be used by a fragment shader with this WebGL implementation.
* The minimum in WebGL2 contexts is 224, or 16 in WebGL1 contexts.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_FRAGMENT_UNIFORM_VECTORS</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumFragmentUniformVectors: {
get: function () {
return ContextLimits._maximumFragmentUniformVectors;
},
},
/**
* The maximum number of texture units that can be used from the fragment shader with this WebGL implementation.
* The minimum in WebGL2 contexts is 16, or 8 in WebGL1 contexts.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_TEXTURE_IMAGE_UNITS</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumTextureImageUnits: {
get: function () {
return ContextLimits._maximumTextureImageUnits;
},
},
/**
* The maximum renderbuffer width and height supported by this WebGL implementation.
* The minimum in WebGL2 contexts is 2048, but most desktop and laptop implementations will support much larger sizes like 8192.
* The minimum in WebGL1 contexts is 1.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_RENDERBUFFER_SIZE</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumRenderbufferSize: {
get: function () {
return ContextLimits._maximumRenderbufferSize;
},
},
/**
* The approximate maximum texture width and height supported by this WebGL implementation.
* The minimum in WebGL2 contexts is 2048, but most desktop and laptop implementations will support much larger sizes like 8192.
* The minimum in WebGL1 contexts is 64.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_TEXTURE_SIZE</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumTextureSize: {
get: function () {
return ContextLimits._maximumTextureSize;
},
},
/**
* The approximate maximum texture width, height, and depth supported by this WebGL2 implementation.
* The minimum is 256, but most desktop and laptop implementations will support much larger sizes like 2048.
* 3D textures are not supported in WebGL1 contexts.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_3D_TEXTURE_SIZE</code>.
*/
maximum3DTextureSize: {
get: function () {
return ContextLimits._maximum3DTextureSize;
},
},
/**
* The maximum number of <code>vec4</code> varying variables supported by this WebGL implementation.
* The minimum is 15 in WebGL2 contexts, or 8 in WebGL1 contexts. Matrices and arrays count as multiple <code>vec4</code>s.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_VARYING_VECTORS</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumVaryingVectors: {
get: function () {
return ContextLimits._maximumVaryingVectors;
},
},
/**
* The maximum number of <code>vec4</code> vertex attributes supported by this WebGL implementation.
* The minimum is 16 in WebGL2 contexts, or 8 in WebGL1 contexts.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_VERTEX_ATTRIBS</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumVertexAttributes: {
get: function () {
return ContextLimits._maximumVertexAttributes;
},
},
/**
* The maximum number of texture units that can be used from the vertex shader with this WebGL implementation.
* The minimum is 16 in WebGL2 contexts, or 0 in WebGL1 contexts.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_VERTEX_TEXTURE_IMAGE_UNITS</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumVertexTextureImageUnits: {
get: function () {
return ContextLimits._maximumVertexTextureImageUnits;
},
},
/**
* The maximum number of <code>vec4</code>, <code>ivec4</code>, and <code>bvec4</code>
* uniforms that can be used by a vertex shader with this WebGL implementation.
* The minimum is 256 in WebGL2 contexts, or 128 in WebGL1 contexts.
* @memberof ContextLimits
* @type {number}
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_VERTEX_UNIFORM_VECTORS</code>.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glGet.xml|glGet in OpenGL ES 2.0} for WebGL1 contexts.
*/
maximumVertexUniformVectors: {
get: function () {
return ContextLimits._maximumVertexUniformVectors;
},
},
/**
* The minimum aliased line width, in pixels, supported by this WebGL implementation. It will be at most one.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>ALIASED_LINE_WIDTH_RANGE</code>.
* @memberof ContextLimits
* @type {number}
*/
minimumAliasedLineWidth: {
get: function () {
return ContextLimits._minimumAliasedLineWidth;
},
},
/**
* The maximum aliased line width, in pixels, supported by this WebGL implementation. It will be at least one.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>ALIASED_LINE_WIDTH_RANGE</code>.
* @memberof ContextLimits
* @type {number}
*/
maximumAliasedLineWidth: {
get: function () {
return ContextLimits._maximumAliasedLineWidth;
},
},
/**
* The minimum aliased point size, in pixels, supported by this WebGL implementation. It will be at most one.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>ALIASED_POINT_SIZE_RANGE</code>.
* @memberof ContextLimits
* @type {number}
*/
minimumAliasedPointSize: {
get: function () {
return ContextLimits._minimumAliasedPointSize;
},
},
/**
* The maximum aliased point size, in pixels, supported by this WebGL implementation. It will be at least one.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>ALIASED_POINT_SIZE_RANGE</code>.
* @memberof ContextLimits
* @type {number}
*/
maximumAliasedPointSize: {
get: function () {
return ContextLimits._maximumAliasedPointSize;
},
},
/**
* The maximum supported width of the viewport. It will be at least as large as the visible width of the associated canvas.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_VIEWPORT_DIMS</code>.
* @memberof ContextLimits
* @type {number}
*/
maximumViewportWidth: {
get: function () {
return ContextLimits._maximumViewportWidth;
},
},
/**
* The maximum supported height of the viewport. It will be at least as large as the visible height of the associated canvas.
* @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet in OpenGL ES 3.0} with <code>MAX_VIEWPORT_DIMS</code>.
* @memberof ContextLimits
* @type {number}
*/
maximumViewportHeight: {
get: function () {
return ContextLimits._maximumViewportHeight;
},
},
/**
* The maximum degree of anisotropy for texture filtering
* @memberof ContextLimits
* @type {number}
*/
maximumTextureFilterAnisotropy: {
get: function () {
return ContextLimits._maximumTextureFilterAnisotropy;
},
},
/**
* The maximum number of simultaneous outputs that may be written in a fragment shader.
* @memberof ContextLimits
* @type {number}
*/
maximumDrawBuffers: {
get: function () {
return ContextLimits._maximumDrawBuffers;
},
},
/**
* The maximum number of color attachments supported.
* @memberof ContextLimits
* @type {number}
*/
maximumColorAttachments: {
get: function () {
return ContextLimits._maximumColorAttachments;
},
},
/**
* The maximum number of samples supported for multisampling.
* @memberof ContextLimits
* @type {number}
*/
maximumSamples: {
get: function () {
return ContextLimits._maximumSamples;
},
},
/**
* High precision float supported (<code>highp</code>) in fragment shaders.
* @memberof ContextLimits
* @type {boolean}
*/
highpFloatSupported: {
get: function () {
return ContextLimits._highpFloatSupported;
},
},
/**
* High precision int supported (<code>highp</code>) in fragment shaders.
* @memberof ContextLimits
* @type {boolean}
*/
highpIntSupported: {
get: function () {
return ContextLimits._highpIntSupported;
},
},
});
export default ContextLimits;
+686
View File
@@ -0,0 +1,686 @@
import BoxGeometry from "../Core/BoxGeometry.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Check from "../Core/Check.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import GeometryPipeline from "../Core/GeometryPipeline.js";
import CesiumMath from "../Core/Math.js";
import PixelFormat from "../Core/PixelFormat.js";
import VertexFormat from "../Core/VertexFormat.js";
import BufferUsage from "./BufferUsage.js";
import ContextLimits from "./ContextLimits.js";
import CubeMapFace from "./CubeMapFace.js";
import Framebuffer from "./Framebuffer.js";
import MipmapHint from "./MipmapHint.js";
import PixelDatatype from "./PixelDatatype.js";
import Sampler from "./Sampler.js";
import TextureMagnificationFilter from "./TextureMagnificationFilter.js";
import TextureMinificationFilter from "./TextureMinificationFilter.js";
import VertexArray from "./VertexArray.js";
/**
* @typedef CubeMap.BufferSource
*
* @property {TypedArray} arrayBufferView A view of a binary data buffer containing pixel values.
* @property {number} width The width of one face of the cube map, in pixels. Must be equal to height.
* @property {number} height The height of one face of the cube map, in pixels. Must be equal to width.
*
* @private
*/
/**
* @typedef CubeMap.Source
*
* @property {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|CubeMap.BufferSource} positiveX
* @property {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|CubeMap.BufferSource} negativeX
* @property {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|CubeMap.BufferSource} positiveY
* @property {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|CubeMap.BufferSource} negativeY
* @property {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|CubeMap.BufferSource} positiveZ
* @property {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|CubeMap.BufferSource} negativeZ
*/
/**
* @typedef CubeMap.ConstructorOptions
*
* @property {Context} context
* @property {CubeMap.Source} [source] The source for texel values to be loaded into the texture.
* @property {PixelFormat} [pixelFormat=PixelFormat.RGBA] The format of each pixel, i.e., the number of components it has and what they represent.
* @property {PixelDatatype} [pixelDatatype=PixelDatatype.UNSIGNED_BYTE] The data type of each pixel.
* @property {boolean} [flipY=true] If true, the source values will be read as if the y-axis is inverted (y=0 at the top).
* @property {boolean} [skipColorSpaceConversion=false] If true, color space conversions will be skipped when reading the texel values.
* @property {Sampler} [sampler] Information about how to sample the cubemap texture.
* @property {number} [width] The pixel width of the texture. If not supplied, must be available from the source. Must be equal to height.
* @property {number} [height] The pixel height of the texture. If not supplied, must be available from the source. Must be equal to width.
* @property {boolean} [preMultiplyAlpha] If true, the alpha channel will be multiplied into the other channels.
*
* @private
*/
/**
* A wrapper for a {@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture|WebGLTexture}
* used as a cube map, to abstract away the verbose GL calls associated with setting up a texture.
*
* @alias CubeMap
* @constructor
*
* @param {CubeMap.ConstructorOptions} options An object describing initialization options.
* @private
*/
function CubeMap(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
//>>includeEnd('debug');
const {
context,
source,
pixelFormat = PixelFormat.RGBA,
pixelDatatype = PixelDatatype.UNSIGNED_BYTE,
flipY = true,
skipColorSpaceConversion = false,
sampler = new Sampler(),
} = options;
// Use premultiplied alpha for opaque textures should perform better on Chrome:
// http://media.tojicode.com/webglCamp4/#20
const preMultiplyAlpha =
options.preMultiplyAlpha ||
pixelFormat === PixelFormat.RGB ||
pixelFormat === PixelFormat.LUMINANCE;
let { width, height } = options;
if (defined(source)) {
//>>includeStart('debug', pragmas.debug);
if (
!Object.values(CubeMap.FaceName).every((faceName) =>
defined(source[faceName]),
)
) {
throw new DeveloperError(
`options.source requires faces ${Object.values(CubeMap.FaceName).join(
", ",
)}.`,
);
}
//>>includeEnd('debug');
({ width, height } = source.positiveX);
//>>includeStart('debug', pragmas.debug);
for (const faceName of CubeMap.faceNames()) {
const face = source[faceName];
if (Number(face.width) !== width || Number(face.height) !== height) {
throw new DeveloperError(
"Each face in options.source must have the same width and height.",
);
}
}
//>>includeEnd('debug');
}
const size = width;
//>>includeStart('debug', pragmas.debug);
if (!defined(width) || !defined(height)) {
throw new DeveloperError(
"options requires a source field to create an initialized cube map or width and height fields to create a blank cube map.",
);
}
if (width !== height) {
throw new DeveloperError("Width must equal height.");
}
if (size <= 0) {
throw new DeveloperError("Width and height must be greater than zero.");
}
if (size > ContextLimits.maximumCubeMapSize) {
throw new DeveloperError(
`Width and height must be less than or equal to the maximum cube map size (${ContextLimits.maximumCubeMapSize}). Check maximumCubeMapSize.`,
);
}
if (!PixelFormat.validate(pixelFormat)) {
throw new DeveloperError("Invalid options.pixelFormat.");
}
if (PixelFormat.isDepthFormat(pixelFormat)) {
throw new DeveloperError(
"options.pixelFormat cannot be DEPTH_COMPONENT or DEPTH_STENCIL.",
);
}
if (!PixelDatatype.validate(pixelDatatype)) {
throw new DeveloperError("Invalid options.pixelDatatype.");
}
if (pixelDatatype === PixelDatatype.FLOAT && !context.floatingPointTexture) {
throw new DeveloperError(
"When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension.",
);
}
if (
pixelDatatype === PixelDatatype.HALF_FLOAT &&
!context.halfFloatingPointTexture
) {
throw new DeveloperError(
"When options.pixelDatatype is HALF_FLOAT, this WebGL implementation must support the OES_texture_half_float extension.",
);
}
//>>includeEnd('debug');
const sizeInBytes =
PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, size, size) * 6;
const internalFormat = PixelFormat.toInternalFormat(
pixelFormat,
pixelDatatype,
context,
);
const gl = context._gl;
const textureTarget = gl.TEXTURE_CUBE_MAP;
const texture = gl.createTexture();
this._context = context;
this._textureFilterAnisotropic = context._textureFilterAnisotropic;
this._textureTarget = textureTarget;
this._texture = texture;
this._pixelFormat = pixelFormat;
this._pixelDatatype = pixelDatatype;
this._size = size;
this._hasMipmap = false;
this._sizeInBytes = sizeInBytes;
this._preMultiplyAlpha = preMultiplyAlpha;
this._flipY = flipY;
const initialized = defined(source);
function constructFace(targetFace) {
return new CubeMapFace(
context,
texture,
textureTarget,
targetFace,
internalFormat,
pixelFormat,
pixelDatatype,
size,
preMultiplyAlpha,
flipY,
initialized,
);
}
this._positiveX = constructFace(gl.TEXTURE_CUBE_MAP_POSITIVE_X);
this._negativeX = constructFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_X);
this._positiveY = constructFace(gl.TEXTURE_CUBE_MAP_POSITIVE_Y);
this._negativeY = constructFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y);
this._positiveZ = constructFace(gl.TEXTURE_CUBE_MAP_POSITIVE_Z);
this._negativeZ = constructFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z);
this._sampler = sampler;
setupSampler(this, sampler);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(textureTarget, texture);
if (skipColorSpaceConversion) {
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
} else {
gl.pixelStorei(
gl.UNPACK_COLORSPACE_CONVERSION_WEBGL,
gl.BROWSER_DEFAULT_WEBGL,
);
}
for (const faceName of CubeMap.faceNames()) {
loadFace(this[faceName], source?.[faceName], 0);
}
gl.bindTexture(textureTarget, null);
}
/**
* Copy an existing texture to a cubemap face.
* @param {FrameState} frameState The current rendering frameState
* @param {Texture} texture Texture being copied
* @param {CubeMap.FaceName} face The face to which to copy
* @param {number} [mipLevel=0] The mip level at which to copy
*/
CubeMap.prototype.copyFace = function (frameState, texture, face, mipLevel) {
const context = frameState.context;
const framebuffer = new Framebuffer({
context: context,
colorTextures: [texture],
destroyAttachments: false,
});
framebuffer._bind();
this[face].copyMipmapFromFramebuffer(
0,
0,
texture.width,
texture.height,
mipLevel ?? 0,
);
framebuffer._unBind();
framebuffer.destroy();
};
/**
* An enum defining the names of the faces of a cube map.
* @alias {CubeMap.FaceName}
* @enum {string}
* @private
*/
CubeMap.FaceName = Object.freeze({
POSITIVEX: "positiveX",
NEGATIVEX: "negativeX",
POSITIVEY: "positiveY",
NEGATIVEY: "negativeY",
POSITIVEZ: "positiveZ",
NEGATIVEZ: "negativeZ",
});
function* makeFaceNamesIterator() {
yield CubeMap.FaceName.POSITIVEX;
yield CubeMap.FaceName.NEGATIVEX;
yield CubeMap.FaceName.POSITIVEY;
yield CubeMap.FaceName.NEGATIVEY;
yield CubeMap.FaceName.POSITIVEZ;
yield CubeMap.FaceName.NEGATIVEZ;
}
/**
* Creates an iterator for looping over the cubemap faces.
* @type {Iterable<CubeMap.FaceName>}
* @private
*/
CubeMap.faceNames = function () {
return makeFaceNamesIterator();
};
/**
* Load texel data into one face of a cube map.
* @param {CubeMapFace} cubeMapFace The face to which texel values will be loaded.
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|CubeMap.BufferSource} [source] The source for texel values to be loaded into the texture.
* @param {number} [mipLevel=0] The mip level to which the texel values will be loaded.
* @private
*/
function loadFace(cubeMapFace, source, mipLevel) {
mipLevel = mipLevel ?? 0;
const targetFace = cubeMapFace._targetFace;
const size = Math.max(Math.floor(cubeMapFace._size / 2 ** mipLevel), 1);
const pixelFormat = cubeMapFace._pixelFormat;
const pixelDatatype = cubeMapFace._pixelDatatype;
const internalFormat = cubeMapFace._internalFormat;
const flipY = cubeMapFace._flipY;
const preMultiplyAlpha = cubeMapFace._preMultiplyAlpha;
const context = cubeMapFace._context;
const gl = context._gl;
if (!defined(source)) {
gl.texImage2D(
targetFace,
mipLevel,
internalFormat,
size,
size,
0,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, context),
null,
);
return;
}
let { arrayBufferView } = source;
let unpackAlignment = 4;
if (defined(arrayBufferView)) {
unpackAlignment = PixelFormat.alignmentInBytes(
pixelFormat,
pixelDatatype,
size,
);
}
gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
if (defined(arrayBufferView)) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
if (flipY) {
arrayBufferView = PixelFormat.flipY(
arrayBufferView,
pixelFormat,
pixelDatatype,
size,
size,
);
}
gl.texImage2D(
targetFace,
mipLevel,
internalFormat,
size,
size,
0,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, context),
arrayBufferView,
);
} else {
// Only valid for DOM-Element uploads
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
gl.texImage2D(
targetFace,
mipLevel,
internalFormat,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, context),
source,
);
}
}
CubeMap.loadFace = loadFace;
Object.defineProperties(CubeMap.prototype, {
positiveX: {
get: function () {
return this._positiveX;
},
},
negativeX: {
get: function () {
return this._negativeX;
},
},
positiveY: {
get: function () {
return this._positiveY;
},
},
negativeY: {
get: function () {
return this._negativeY;
},
},
positiveZ: {
get: function () {
return this._positiveZ;
},
},
negativeZ: {
get: function () {
return this._negativeZ;
},
},
sampler: {
get: function () {
return this._sampler;
},
set: function (sampler) {
setupSampler(this, sampler);
this._sampler = sampler;
},
},
pixelFormat: {
get: function () {
return this._pixelFormat;
},
},
pixelDatatype: {
get: function () {
return this._pixelDatatype;
},
},
width: {
get: function () {
return this._size;
},
},
height: {
get: function () {
return this._size;
},
},
sizeInBytes: {
get: function () {
if (this._hasMipmap) {
return Math.floor((this._sizeInBytes * 4) / 3);
}
return this._sizeInBytes;
},
},
preMultiplyAlpha: {
get: function () {
return this._preMultiplyAlpha;
},
},
flipY: {
get: function () {
return this._flipY;
},
},
_target: {
get: function () {
return this._textureTarget;
},
},
});
/**
* Get a vector representing the cubemap face direction
* @param {CubeMap.FaceName} face The relevant face
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The vector representing the cubemap face direction
*/
CubeMap.getDirection = function (face, result) {
switch (face) {
case CubeMap.FaceName.POSITIVEX:
return Cartesian3.clone(Cartesian3.UNIT_X, result);
case CubeMap.FaceName.NEGATIVEX:
return Cartesian3.negate(Cartesian3.UNIT_X, result);
case CubeMap.FaceName.POSITIVEY:
return Cartesian3.clone(Cartesian3.UNIT_Y, result);
case CubeMap.FaceName.NEGATIVEY:
return Cartesian3.negate(Cartesian3.UNIT_Y, result);
case CubeMap.FaceName.POSITIVEZ:
return Cartesian3.clone(Cartesian3.UNIT_Z, result);
case CubeMap.FaceName.NEGATIVEZ:
return Cartesian3.negate(Cartesian3.UNIT_Z, result);
}
};
/**
* Set up a sampler for use with a cube map.
* @param {CubeMap} cubeMap The cube map containing the texture to be sampled by this sampler.
* @param {Sampler} sampler Information about how to sample the cubemap texture.
* @private
*/
function setupSampler(cubeMap, sampler) {
let { minificationFilter, magnificationFilter } = sampler;
const mipmap = [
TextureMinificationFilter.NEAREST_MIPMAP_NEAREST,
TextureMinificationFilter.NEAREST_MIPMAP_LINEAR,
TextureMinificationFilter.LINEAR_MIPMAP_NEAREST,
TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
].includes(minificationFilter);
const context = cubeMap._context;
const pixelDatatype = cubeMap._pixelDatatype;
// float textures only support nearest filtering unless the linear extensions are supported
if (
(pixelDatatype === PixelDatatype.FLOAT && !context.textureFloatLinear) ||
(pixelDatatype === PixelDatatype.HALF_FLOAT &&
!context.textureHalfFloatLinear)
) {
// override the sampler's settings
minificationFilter = mipmap
? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST
: TextureMinificationFilter.NEAREST;
magnificationFilter = TextureMagnificationFilter.NEAREST;
}
const gl = context._gl;
const target = cubeMap._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, cubeMap._texture);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
if (defined(cubeMap._textureFilterAnisotropic)) {
gl.texParameteri(
target,
cubeMap._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT,
sampler.maximumAnisotropy,
);
}
gl.bindTexture(target, null);
}
/**
* Load a complete mipmap chain for each cubemap face.
*
* @param {CubeMap.Source[]} source The source data for each mip level, beginning at level 1.
* @param {boolean} [skipColorSpaceConversion=false] If true, color space conversions will be skipped when reading the texel values.
*
* @private
*/
CubeMap.prototype.loadMipmaps = function (source, skipColorSpaceConversion) {
//>>includeStart('debug', pragmas.debug);
Check.defined("source", source);
if (!Array.isArray(source)) {
throw new DeveloperError(`source must be an array`);
}
const mipCount = Math.log2(this._size);
if (source.length !== mipCount) {
throw new DeveloperError(`all mip levels must be defined`);
}
//>>includeEnd('debug');
skipColorSpaceConversion = skipColorSpaceConversion ?? false;
const gl = this._context._gl;
const texture = this._texture;
const textureTarget = this._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(textureTarget, texture);
if (skipColorSpaceConversion) {
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
} else {
gl.pixelStorei(
gl.UNPACK_COLORSPACE_CONVERSION_WEBGL,
gl.BROWSER_DEFAULT_WEBGL,
);
}
for (let i = 0; i < source.length; i++) {
const mipSource = source[i];
// mipLevel 0 was the base layer, already loaded when the CubeMap was constructed.
const mipLevel = i + 1;
for (const faceName of CubeMap.faceNames()) {
loadFace(this[faceName], mipSource[faceName], mipLevel);
}
}
gl.bindTexture(textureTarget, null);
this._hasMipmap = true;
};
/**
* Generates a complete mipmap chain for each cubemap face.
*
* @param {MipmapHint} [hint=MipmapHint.DONT_CARE] A performance vs. quality hint.
*
* @exception {DeveloperError} hint is invalid.
* @exception {DeveloperError} This CubeMap's width must be a power of two to call generateMipmap().
* @exception {DeveloperError} This CubeMap's height must be a power of two to call generateMipmap().
* @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
*
* @example
* // Generate mipmaps, and then set the sampler so mipmaps are used for
* // minification when the cube map is sampled.
* cubeMap.generateMipmap();
* cubeMap.sampler = new Sampler({
* minificationFilter : Cesium.TextureMinificationFilter.NEAREST_MIPMAP_LINEAR
* });
*/
CubeMap.prototype.generateMipmap = function (hint) {
hint = hint ?? MipmapHint.DONT_CARE;
//>>includeStart('debug', pragmas.debug);
if (this._size > 1 && !CesiumMath.isPowerOfTwo(this._size)) {
throw new DeveloperError(
"width and height must be a power of two to call generateMipmap().",
);
}
if (!MipmapHint.validate(hint)) {
throw new DeveloperError("hint is invalid.");
}
//>>includeEnd('debug');
this._hasMipmap = true;
const gl = this._context._gl;
const target = this._textureTarget;
gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.generateMipmap(target);
gl.bindTexture(target, null);
};
/**
* Create a vertex array that can be used for cubemap shaders.
* @param {Context} context The rendering context
* @returns {VertexArray} The created vertex array
*/
CubeMap.createVertexArray = function (context) {
const geometry = BoxGeometry.createGeometry(
BoxGeometry.fromDimensions({
dimensions: new Cartesian3(2.0, 2.0, 2.0),
vertexFormat: VertexFormat.POSITION_ONLY,
}),
);
const attributeLocations = (this._attributeLocations =
GeometryPipeline.createAttributeLocations(geometry));
return VertexArray.fromGeometry({
context: context,
geometry: geometry,
attributeLocations: attributeLocations,
bufferUsage: BufferUsage.STATIC_DRAW,
});
};
CubeMap.prototype.isDestroyed = function () {
return false;
};
CubeMap.prototype.destroy = function () {
this._context._gl.deleteTexture(this._texture);
this._positiveX = destroyObject(this._positiveX);
this._negativeX = destroyObject(this._negativeX);
this._positiveY = destroyObject(this._positiveY);
this._negativeY = destroyObject(this._negativeY);
this._positiveZ = destroyObject(this._positiveZ);
this._negativeZ = destroyObject(this._negativeZ);
return destroyObject(this);
};
export default CubeMap;
+415
View File
@@ -0,0 +1,415 @@
import Check from "../Core/Check.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import PixelFormat from "../Core/PixelFormat.js";
import PixelDatatype from "./PixelDatatype.js";
/**
* @private
*/
function CubeMapFace(
context,
texture,
textureTarget,
targetFace,
internalFormat,
pixelFormat,
pixelDatatype,
size,
preMultiplyAlpha,
flipY,
initialized,
) {
this._context = context;
this._texture = texture;
this._textureTarget = textureTarget;
this._targetFace = targetFace;
this._pixelDatatype = pixelDatatype;
this._internalFormat = internalFormat;
this._pixelFormat = pixelFormat;
this._size = size;
this._preMultiplyAlpha = preMultiplyAlpha;
this._flipY = flipY;
this._initialized = initialized;
}
Object.defineProperties(CubeMapFace.prototype, {
pixelFormat: {
get: function () {
return this._pixelFormat;
},
},
pixelDatatype: {
get: function () {
return this._pixelDatatype;
},
},
_target: {
get: function () {
return this._targetFace;
},
},
});
/**
* Copies texels from the source to the cubemap's face.
* @param {object} options Object with the following properties:
* @param {object} options.source The source {@link ImageData}, {@link HTMLImageElement}, {@link HTMLCanvasElement}, {@link HTMLVideoElement},
* or an object with a width, height, and arrayBufferView properties.
* @param {number} [options.xOffset=0] An offset in the x direction in the cubemap where copying begins.
* @param {number} [options.yOffset=0] An offset in the y direction in the cubemap where copying begins.
* @param {boolean} [options.skipColorSpaceConversion=false] If true, any custom gamma or color profiles in the texture will be ignored.
* @exception {DeveloperError} xOffset must be greater than or equal to zero.
* @exception {DeveloperError} yOffset must be greater than or equal to zero.
* @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
* @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
* @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
*
* @example
* // Create a cubemap with 1x1 faces, and make the +x face red.
* const cubeMap = new CubeMap({
* context : context
* width : 1,
* height : 1
* });
* cubeMap.positiveX.copyFrom({
* source: {
* width : 1,
* height : 1,
* arrayBufferView : new Uint8Array([255, 0, 0, 255])
* }
* });
*/
CubeMapFace.prototype.copyFrom = function (options) {
//>>includeStart('debug', pragmas.debug);
Check.defined("options", options);
//>>includeEnd('debug');
const {
xOffset = 0,
yOffset = 0,
source,
skipColorSpaceConversion = false,
} = options;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.source", source);
Check.typeOf.number.greaterThanOrEquals("xOffset", xOffset, 0);
Check.typeOf.number.greaterThanOrEquals("yOffset", yOffset, 0);
if (xOffset + source.width > this._size) {
throw new DeveloperError(
"xOffset + options.source.width must be less than or equal to width.",
);
}
if (yOffset + source.height > this._size) {
throw new DeveloperError(
"yOffset + options.source.height must be less than or equal to height.",
);
}
//>>includeEnd('debug');
const { width, height } = source;
const gl = this._context._gl;
const target = this._textureTarget;
const targetFace = this._targetFace;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
let arrayBufferView = source.arrayBufferView;
const size = this._size;
const pixelFormat = this._pixelFormat;
const internalFormat = this._internalFormat;
const pixelDatatype = this._pixelDatatype;
const preMultiplyAlpha = this._preMultiplyAlpha;
const flipY = this._flipY;
let unpackAlignment = 4;
if (defined(arrayBufferView)) {
unpackAlignment = PixelFormat.alignmentInBytes(
pixelFormat,
pixelDatatype,
width,
);
}
gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
if (skipColorSpaceConversion) {
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
} else {
gl.pixelStorei(
gl.UNPACK_COLORSPACE_CONVERSION_WEBGL,
gl.BROWSER_DEFAULT_WEBGL,
);
}
let uploaded = false;
if (!this._initialized) {
let pixels;
if (xOffset === 0 && yOffset === 0 && width === size && height === size) {
// initialize the entire texture
if (defined(arrayBufferView)) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
if (flipY) {
arrayBufferView = PixelFormat.flipY(
arrayBufferView,
pixelFormat,
pixelDatatype,
size,
size,
);
}
pixels = arrayBufferView;
} else {
// Only valid for DOM-Element uploads
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
pixels = source;
}
uploaded = true;
} else {
// initialize the entire texture to zero
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
pixels = PixelFormat.createTypedArray(
pixelFormat,
pixelDatatype,
size,
size,
);
}
gl.texImage2D(
targetFace,
0,
internalFormat,
size,
size,
0,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, this._context),
pixels,
);
this._initialized = true;
}
if (!uploaded) {
if (defined(arrayBufferView)) {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
if (flipY) {
arrayBufferView = PixelFormat.flipY(
arrayBufferView,
pixelFormat,
pixelDatatype,
width,
height,
);
}
gl.texSubImage2D(
targetFace,
0,
xOffset,
yOffset,
width,
height,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, this._context),
arrayBufferView,
);
} else {
// Only valid for DOM-Element uploads
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
// Source: ImageData, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
gl.texSubImage2D(
targetFace,
0,
xOffset,
yOffset,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, this._context),
source,
);
}
}
gl.bindTexture(target, null);
};
/**
* Copies texels from the framebuffer to the cubemap's face.
* @param {number} [xOffset=0] An offset in the x direction in the cubemap where copying begins.
* @param {number} [yOffset=0] An offset in the y direction in the cubemap where copying begins.
* @param {number} [framebufferXOffset=0] An offset in the x direction in the framebuffer where copying begins from.
* @param {number} [framebufferYOffset=0] An offset in the y direction in the framebuffer where copying begins from.
* @param {number} [width=CubeMap's width] The width of the subimage to copy.
* @param {number} [height=CubeMap's height] The height of the subimage to copy.
* @throws {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
* @throws {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is HALF_FLOAT.
* @throws {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
* @throws {DeveloperError} xOffset must be greater than or equal to zero.
* @throws {DeveloperError} yOffset must be greater than or equal to zero.
* @throws {DeveloperError} framebufferXOffset must be greater than or equal to zero.
* @throws {DeveloperError} framebufferYOffset must be greater than or equal to zero.
* @throws {DeveloperError} xOffset + source.width must be less than or equal to width.
* @throws {DeveloperError} yOffset + source.height must be less than or equal to height.
* @throws {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
* @example
* // Copy the framebuffer contents to the +x cube map face.
* cubeMap.positiveX.copyFromFramebuffer();
*/
CubeMapFace.prototype.copyFromFramebuffer = function (
xOffset,
yOffset,
framebufferXOffset,
framebufferYOffset,
width,
height,
) {
xOffset = xOffset ?? 0;
yOffset = yOffset ?? 0;
framebufferXOffset = framebufferXOffset ?? 0;
framebufferYOffset = framebufferYOffset ?? 0;
width = width ?? this._size;
height = height ?? this._size;
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("xOffset", xOffset, 0);
Check.typeOf.number.greaterThanOrEquals("yOffset", yOffset, 0);
Check.typeOf.number.greaterThanOrEquals(
"framebufferXOffset",
framebufferXOffset,
0,
);
Check.typeOf.number.greaterThanOrEquals(
"framebufferYOffset",
framebufferYOffset,
0,
);
if (xOffset + width > this._size) {
throw new DeveloperError(
"xOffset + source.width must be less than or equal to width.",
);
}
if (yOffset + height > this._size) {
throw new DeveloperError(
"yOffset + source.height must be less than or equal to height.",
);
}
if (this._pixelDatatype === PixelDatatype.FLOAT) {
throw new DeveloperError(
"Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.",
);
}
if (this._pixelDatatype === PixelDatatype.HALF_FLOAT) {
throw new DeveloperError(
"Cannot call copyFromFramebuffer when the texture pixel data type is HALF_FLOAT.",
);
}
//>>includeEnd('debug');
const gl = this._context._gl;
const target = this._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.copyTexSubImage2D(
this._targetFace,
0,
xOffset,
yOffset,
framebufferXOffset,
framebufferYOffset,
width,
height,
);
gl.bindTexture(target, null);
this._initialized = true;
};
/**
* Copies texels from the framebuffer to the cubemap's face mipmap.
* @param {number} [xOffset=0] An offset in the x direction in the framebuffer where copying begins from.
* @param {number} [yOffset=0] An offset in the y direction in the framebuffer where copying begins from.
* @param {number} [width=CubeMap's width] The width of the subimage to copy.
* @param {number} [height=CubeMap's height] The height of the subimage to copy.
* @param {number} [level=0] The level of detail. Level 0 is the base image level and level n is the n-th mipmap reduction level.
* @throws {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
* @throws {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is HALF_FLOAT.
* @throws {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
* @throws {DeveloperError} xOffset must be greater than or equal to zero.
* @throws {DeveloperError} yOffset must be greater than or equal to zero.
* @throws {DeveloperError} framebufferXOffset must be greater than or equal to zero.
* @throws {DeveloperError} framebufferYOffset must be greater than or equal to zero.
* @throws {DeveloperError} xOffset + source.width must be less than or equal to width.
* @throws {DeveloperError} yOffset + source.height must be less than or equal to height.
* @throws {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
*
* @example
* // Copy the framebuffer contents to the +x cube map face.
* cubeMap.positiveX.copyFromFramebuffer();
*/
CubeMapFace.prototype.copyMipmapFromFramebuffer = function (
xOffset,
yOffset,
width,
height,
level,
) {
xOffset = xOffset ?? 0;
yOffset = yOffset ?? 0;
width = width ?? this._size;
height = height ?? this._size;
level = level ?? 0;
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("xOffset", xOffset, 0);
Check.typeOf.number.greaterThanOrEquals("yOffset", yOffset, 0);
if (xOffset + width > this._size) {
throw new DeveloperError(
"xOffset + source.width must be less than or equal to width.",
);
}
if (yOffset + height > this._size) {
throw new DeveloperError(
"yOffset + source.height must be less than or equal to height.",
);
}
if (this._pixelDatatype === PixelDatatype.FLOAT) {
throw new DeveloperError(
"Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.",
);
}
if (this._pixelDatatype === PixelDatatype.HALF_FLOAT) {
throw new DeveloperError(
"Cannot call copyFromFramebuffer when the texture pixel data type is HALF_FLOAT.",
);
}
//>>includeEnd('debug');
const gl = this._context._gl;
const target = this._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.copyTexImage2D(
this._targetFace,
level,
this._internalFormat,
xOffset,
yOffset,
width,
height,
0,
);
gl.bindTexture(target, null);
this._initialized = true;
};
export default CubeMapFace;
+698
View File
@@ -0,0 +1,698 @@
// @ts-check
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import PrimitiveType from "../Core/PrimitiveType.js";
/** @import Context from "./Context.js"; */
/** @import Framebuffer from "./Framebuffer.js"; */
/** @import Matrix4 from "../Core/Matrix4.js"; */
/** @import OrientedBoundingBox from "../Core/OrientedBoundingBox.js"; */
/** @import Pass from "./Pass.js"; */
/** @import PassState from "./PassState.js"; */
/** @import PickedMetadataInfo from "../Scene/PickedMetadataInfo.js"; */
/** @import RenderState from "./RenderState.js"; */
/** @import ShaderProgram from "./ShaderProgram.js"; */
/** @import VertexArray from "./VertexArray.js"; */
/**
* @enum {number}
* @ignore
*/
const Flags = {
CULL: 1,
OCCLUDE: 2,
EXECUTE_IN_CLOSEST_FRUSTUM: 4,
DEBUG_SHOW_BOUNDING_VOLUME: 8,
CAST_SHADOWS: 16,
RECEIVE_SHADOWS: 32,
PICK_ONLY: 64,
DEPTH_FOR_TRANSLUCENT_CLASSIFICATION: 128,
};
/**
* @typedef {object} DrawCommandOptions
* @property {object} [boundingVolume]
* @property {OrientedBoundingBox} [orientedBoundingBox]
* @property {Matrix4} [modelMatrix]
* @property {PrimitiveType} [primitiveType=PrimitiveType.TRIANGLES]
* @property {VertexArray} [vertexArray]
* @property {number} [count]
* @property {number} [offset]
* @property {number} [instanceCount]
* @property {ShaderProgram} [shaderProgram]
* @property {object} [uniformMap]
* @property {RenderState} [renderState]
* @property {Framebuffer} [framebuffer]
* @property {Pass} [pass]
* @property {object} [owner]
* @property {string} [pickId]
* @property {string} [snapId]
* @property {boolean} [pickMetadataAllowed=false]
* @property {boolean} [cull=true]
* @property {boolean} [occlude=true]
* @property {boolean} [executeInClosestFrustum=false]
* @property {boolean} [debugShowBoundingVolume=false]
* @property {boolean} [castShadows=false]
* @property {boolean} [receiveShadows=false]
* @property {boolean} [pickOnly=false]
* @property {boolean} [depthForTranslucentClassification=false]
*
* @ignore
*/
/**
* Represents a command to the renderer for drawing.
*
* @private
*/
class DrawCommand {
/**
* @param {DrawCommandOptions} [options]
*/
constructor(options = Frozen.EMPTY_OBJECT) {
/** @private */
this._boundingVolume = options.boundingVolume;
/** @private */
this._orientedBoundingBox = options.orientedBoundingBox;
/** @private */
this._modelMatrix = options.modelMatrix;
/** @private */
this._primitiveType = options.primitiveType ?? PrimitiveType.TRIANGLES;
/** @private */
this._vertexArray = options.vertexArray;
/** @private */
this._count = options.count;
/** @private */
this._offset = options.offset ?? 0;
/** @private */
this._instanceCount = options.instanceCount ?? 0;
/** @private */
this._shaderProgram = options.shaderProgram;
/** @private */
this._uniformMap = options.uniformMap;
/** @private */
this._renderState = options.renderState;
/** @private */
this._framebuffer = options.framebuffer;
/** @private */
this._pass = options.pass;
/** @private */
this._owner = options.owner;
/** @private */
this._debugOverlappingFrustums = 0;
/** @private */
this._pickId = options.pickId;
this._snapId = options.snapId;
/** @private */
this._pickMetadataAllowed = options.pickMetadataAllowed === true;
/**
* @type {PickedMetadataInfo|undefined}
* @private
*/
this._pickedMetadataInfo = undefined;
// Set initial flags.
this._flags = 0;
this.cull = options.cull ?? true;
this.occlude = options.occlude ?? true;
this.executeInClosestFrustum = options.executeInClosestFrustum ?? false;
this.debugShowBoundingVolume = options.debugShowBoundingVolume ?? false;
this.castShadows = options.castShadows ?? false;
this.receiveShadows = options.receiveShadows ?? false;
this.pickOnly = options.pickOnly ?? false;
this.depthForTranslucentClassification =
options.depthForTranslucentClassification ?? false;
this.dirty = true;
this.lastDirtyTime = 0;
/**
* @private
*/
this.derivedCommands = {};
}
/**
* The bounding volume of the geometry in world space. This is used for culling and frustum selection.
* <p>
* For best rendering performance, use the tightest possible bounding volume. Although
* <code>undefined</code> is allowed, always try to provide a bounding volume to
* allow the tightest possible near and far planes to be computed for the scene, and
* minimize the number of frustums needed.
* </p>
*
* @type {object}
* @default undefined
*
* @see DrawCommand#debugShowBoundingVolume
*/
get boundingVolume() {
return this._boundingVolume;
}
set boundingVolume(value) {
if (this._boundingVolume !== value) {
this._boundingVolume = value;
this.dirty = true;
}
}
/**
* The oriented bounding box of the geometry in world space. If this is defined, it is used instead of
* {@link DrawCommand#boundingVolume} for plane intersection testing.
*
* @type {OrientedBoundingBox}
* @default undefined
*
* @see DrawCommand#debugShowBoundingVolume
*/
get orientedBoundingBox() {
return this._orientedBoundingBox;
}
set orientedBoundingBox(value) {
if (this._orientedBoundingBox !== value) {
this._orientedBoundingBox = value;
this.dirty = true;
}
}
/**
* When <code>true</code>, the renderer frustum and horizon culls the command based on its {@link DrawCommand#boundingVolume}.
* If the command was already culled, set this to <code>false</code> for a performance improvement.
*
* @type {boolean}
* @default true
*/
get cull() {
return hasFlag(this, Flags.CULL);
}
set cull(value) {
if (hasFlag(this, Flags.CULL) !== value) {
setFlag(this, Flags.CULL, value);
this.dirty = true;
}
}
/**
* When <code>true</code>, the horizon culls the command based on its {@link DrawCommand#boundingVolume}.
* {@link DrawCommand#cull} must also be <code>true</code> in order for the command to be culled.
*
* @type {boolean}
* @default true
*/
get occlude() {
return hasFlag(this, Flags.OCCLUDE);
}
set occlude(value) {
if (hasFlag(this, Flags.OCCLUDE) !== value) {
setFlag(this, Flags.OCCLUDE, value);
this.dirty = true;
}
}
/**
* The transformation from the geometry in model space to world space.
* <p>
* When <code>undefined</code>, the geometry is assumed to be defined in world space.
* </p>
*
* @type {Matrix4}
* @default undefined
*/
get modelMatrix() {
return this._modelMatrix;
}
set modelMatrix(value) {
if (this._modelMatrix !== value) {
this._modelMatrix = value;
this.dirty = true;
}
}
/**
* The type of geometry in the vertex array.
*
* @type {PrimitiveType}
* @default PrimitiveType.TRIANGLES
*/
get primitiveType() {
return this._primitiveType;
}
set primitiveType(value) {
if (this._primitiveType !== value) {
this._primitiveType = value;
this.dirty = true;
}
}
/**
* The vertex array.
*
* @type {VertexArray}
* @default undefined
*/
get vertexArray() {
return this._vertexArray;
}
set vertexArray(value) {
if (this._vertexArray !== value) {
this._vertexArray = value;
this.dirty = true;
}
}
/**
* The number of vertices to draw in the vertex array.
*
* @type {number}
* @default undefined
*/
get count() {
return this._count;
}
set count(value) {
if (this._count !== value) {
this._count = value;
this.dirty = true;
}
}
/**
* The offset to start drawing in the vertex array.
*
* @type {number}
* @default 0
*/
get offset() {
return this._offset;
}
set offset(value) {
if (this._offset !== value) {
this._offset = value;
this.dirty = true;
}
}
/**
* The number of instances to draw.
*
* @type {number}
* @default 0
*/
get instanceCount() {
return this._instanceCount;
}
set instanceCount(value) {
if (this._instanceCount !== value) {
this._instanceCount = value;
this.dirty = true;
}
}
/**
* The shader program to apply.
*
* @type {ShaderProgram}
* @default undefined
*/
get shaderProgram() {
return this._shaderProgram;
}
set shaderProgram(value) {
if (this._shaderProgram !== value) {
this._shaderProgram = value;
this.dirty = true;
}
}
/**
* Whether this command should cast shadows when shadowing is enabled.
*
* @type {boolean}
* @default false
*/
get castShadows() {
return hasFlag(this, Flags.CAST_SHADOWS);
}
set castShadows(value) {
if (hasFlag(this, Flags.CAST_SHADOWS) !== value) {
setFlag(this, Flags.CAST_SHADOWS, value);
this.dirty = true;
}
}
/**
* Whether this command should receive shadows when shadowing is enabled.
*
* @type {boolean}
* @default false
*/
get receiveShadows() {
return hasFlag(this, Flags.RECEIVE_SHADOWS);
}
set receiveShadows(value) {
if (hasFlag(this, Flags.RECEIVE_SHADOWS) !== value) {
setFlag(this, Flags.RECEIVE_SHADOWS, value);
this.dirty = true;
}
}
/**
* An object with functions whose names match the uniforms in the shader program
* and return values to set those uniforms.
*
* @type {object}
* @default undefined
*/
get uniformMap() {
return this._uniformMap;
}
set uniformMap(value) {
if (this._uniformMap !== value) {
this._uniformMap = value;
this.dirty = true;
}
}
/**
* The render state.
*
* @type {RenderState}
* @default undefined
*/
get renderState() {
return this._renderState;
}
set renderState(value) {
if (this._renderState !== value) {
this._renderState = value;
this.dirty = true;
}
}
/**
* The framebuffer to draw to.
*
* @type {Framebuffer}
* @default undefined
*/
get framebuffer() {
return this._framebuffer;
}
set framebuffer(value) {
if (this._framebuffer !== value) {
this._framebuffer = value;
this.dirty = true;
}
}
/**
* The pass when to render.
*
* @type {Pass}
* @default undefined
*/
get pass() {
return this._pass;
}
set pass(value) {
if (this._pass !== value) {
this._pass = value;
this.dirty = true;
}
}
/**
* Specifies if this command is only to be executed in the frustum closest
* to the eye containing the bounding volume. Defaults to <code>false</code>.
*
* @type {boolean}
* @default false
*/
get executeInClosestFrustum() {
return hasFlag(this, Flags.EXECUTE_IN_CLOSEST_FRUSTUM);
}
set executeInClosestFrustum(value) {
if (hasFlag(this, Flags.EXECUTE_IN_CLOSEST_FRUSTUM) !== value) {
setFlag(this, Flags.EXECUTE_IN_CLOSEST_FRUSTUM, value);
this.dirty = true;
}
}
/**
* The object who created this command. This is useful for debugging command
* execution; it allows us to see who created a command when we only have a
* reference to the command, and can be used to selectively execute commands
* with {@link Scene#debugCommandFilter}.
*
* @type {object}
* @default undefined
*
* @see Scene#debugCommandFilter
*/
get owner() {
return this._owner;
}
set owner(value) {
if (this._owner !== value) {
this._owner = value;
this.dirty = true;
}
}
/**
* This property is for debugging only; it is not for production use nor is it optimized.
* <p>
* Draws the {@link DrawCommand#boundingVolume} for this command, assuming it is a sphere, when the command executes.
* </p>
*
* @type {boolean}
* @default false
*
* @see DrawCommand#boundingVolume
*/
get debugShowBoundingVolume() {
return hasFlag(this, Flags.DEBUG_SHOW_BOUNDING_VOLUME);
}
set debugShowBoundingVolume(value) {
if (hasFlag(this, Flags.DEBUG_SHOW_BOUNDING_VOLUME) !== value) {
setFlag(this, Flags.DEBUG_SHOW_BOUNDING_VOLUME, value);
this.dirty = true;
}
}
/**
* Used to implement Scene.debugShowFrustums.
* @ignore
*/
get debugOverlappingFrustums() {
return this._debugOverlappingFrustums;
}
set debugOverlappingFrustums(value) {
if (this._debugOverlappingFrustums !== value) {
this._debugOverlappingFrustums = value;
this.dirty = true;
}
}
/**
* A GLSL string that will evaluate to a pick id. When <code>undefined</code>, the command will only draw depth
* during the pick pass.
*
* @type {string|undefined}
* @default undefined
*/
get pickId() {
return this._pickId;
}
set pickId(value) {
if (this._pickId !== value) {
this._pickId = value;
this.dirty = true;
}
}
/**
* A GLSL string that will evaluate to the float snap payload written during
* a snapping pass (see {@link Scene#snap}). When <code>undefined</code>, the
* command does not render during a snapping pass.
*
* @type {string|undefined}
* @default undefined
*/
get snapId() {
return this._snapId;
}
set snapId(value) {
if (this._snapId !== value) {
this._snapId = value;
this.dirty = true;
}
}
/**
* Whether metadata picking is allowed.
*
* This is essentially only set to `true` for draw commands that are
* part of a `ModelDrawCommand`, to check whether a derived command
* for metadata picking has to be created.
*
* @type {boolean}
* @default undefined
* @private
*/
get pickMetadataAllowed() {
return this._pickMetadataAllowed;
}
/**
* Information about picked metadata.
*
* @type {PickedMetadataInfo|undefined}
* @default undefined
*/
get pickedMetadataInfo() {
return this._pickedMetadataInfo;
}
set pickedMetadataInfo(value) {
if (this._pickedMetadataInfo !== value) {
this._pickedMetadataInfo = value;
this.dirty = true;
}
}
/**
* Whether this command should be executed in the pick pass only.
*
* @type {boolean}
* @default false
*/
get pickOnly() {
return hasFlag(this, Flags.PICK_ONLY);
}
set pickOnly(value) {
if (hasFlag(this, Flags.PICK_ONLY) !== value) {
setFlag(this, Flags.PICK_ONLY, value);
this.dirty = true;
}
}
/**
* Whether this command should be derived to draw depth for classification of translucent primitives.
*
* @type {boolean}
* @default false
*/
get depthForTranslucentClassification() {
return hasFlag(this, Flags.DEPTH_FOR_TRANSLUCENT_CLASSIFICATION);
}
set depthForTranslucentClassification(value) {
if (hasFlag(this, Flags.DEPTH_FOR_TRANSLUCENT_CLASSIFICATION) !== value) {
setFlag(this, Flags.DEPTH_FOR_TRANSLUCENT_CLASSIFICATION, value);
this.dirty = true;
}
}
/**
* @param {DrawCommand} command
* @param {DrawCommand} result
* @returns {DrawCommand}
* @private
*/
static shallowClone(command, result) {
if (!defined(command)) {
return undefined;
}
if (!defined(result)) {
result = new DrawCommand();
}
result._boundingVolume = command._boundingVolume;
result._orientedBoundingBox = command._orientedBoundingBox;
result._modelMatrix = command._modelMatrix;
result._primitiveType = command._primitiveType;
result._vertexArray = command._vertexArray;
result._count = command._count;
result._offset = command._offset;
result._instanceCount = command._instanceCount;
result._shaderProgram = command._shaderProgram;
result._uniformMap = command._uniformMap;
result._renderState = command._renderState;
result._framebuffer = command._framebuffer;
result._pass = command._pass;
result._owner = command._owner;
result._debugOverlappingFrustums = command._debugOverlappingFrustums;
result._pickId = command._pickId;
result._snapId = command._snapId;
result._pickMetadataAllowed = command._pickMetadataAllowed;
result._pickedMetadataInfo = command._pickedMetadataInfo;
result._flags = command._flags;
result.dirty = true;
result.lastDirtyTime = 0;
return result;
}
/**
* Executes the draw command.
*
* @param {Context} context The renderer context in which to draw.
* @param {PassState} [passState] The state for the current render pass.
*/
execute(context, passState) {
context.draw(this, passState);
}
}
/**
* @param {DrawCommand} command
* @param {Flags} flag
* @returns {boolean}
* @ignore
*/
function hasFlag(command, flag) {
return (command._flags & flag) === flag;
}
/**
* @param {DrawCommand} command
* @param {Flags} flag
* @param {boolean} value
* @ignore
*/
function setFlag(command, flag, value) {
if (value) {
command._flags |= flag;
} else {
command._flags &= ~flag;
}
}
export default DrawCommand;
+451
View File
@@ -0,0 +1,451 @@
import Check from "../Core/Check.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import PixelFormat from "../Core/PixelFormat.js";
import ContextLimits from "./ContextLimits.js";
import PixelDatatype from "./PixelDatatype.js";
function attachTexture(framebuffer, attachment, texture) {
const gl = framebuffer._gl;
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
attachment,
texture._target,
texture._texture,
0,
);
}
function attachRenderbuffer(framebuffer, attachment, renderbuffer) {
const gl = framebuffer._gl;
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
attachment,
gl.RENDERBUFFER,
renderbuffer._getRenderbuffer(),
);
}
/**
* Creates a framebuffer with optional initial color, depth, and stencil attachments.
* Framebuffers are used for render-to-texture effects; they allow us to render to
* textures in one pass, and read from it in a later pass.
*
* @param {object} options Object with the following properties:
* @param {Context} options.context
* @param {Texture[]} [options.colorTextures]
* @param {Renderbuffer[]} [options.colorRenderbuffers]
* @param {Texture} [options.depthTexture]
* @param {Renderbuffer} [options.depthRenderbuffer]
* @param {Renderbuffer} [options.stencilRenderbuffer]
* @param {Texture} [options.depthStencilTexture]
* @param {Renderbuffer} [options.depthStencilRenderbuffer]
* @param {boolean} [options.destroyAttachments=true] When true, the framebuffer owns its attachments so they will be destroyed when {@link Framebuffer#destroy} is called or when a new attachment is assigned to an attachment point.
*
* @exception {DeveloperError} Cannot have both color texture and color renderbuffer attachments.
* @exception {DeveloperError} Cannot have both a depth texture and depth renderbuffer attachment.
* @exception {DeveloperError} Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment.
* @exception {DeveloperError} Cannot have both a depth and depth-stencil renderbuffer.
* @exception {DeveloperError} Cannot have both a stencil and depth-stencil renderbuffer.
* @exception {DeveloperError} Cannot have both a depth and stencil renderbuffer.
* @exception {DeveloperError} The color-texture pixel-format must be a color format.
* @exception {DeveloperError} The depth-texture pixel-format must be DEPTH_COMPONENT.
* @exception {DeveloperError} The depth-stencil-texture pixel-format must be DEPTH_STENCIL.
* @exception {DeveloperError} The number of color attachments exceeds the number supported.
* @exception {DeveloperError} The color-texture pixel datatype is HALF_FLOAT and the WebGL implementation does not support the EXT_color_buffer_half_float extension.
* @exception {DeveloperError} The color-texture pixel datatype is FLOAT and the WebGL implementation does not support the EXT_color_buffer_float or WEBGL_color_buffer_float extensions.
*
* @example
* // Create a framebuffer with color and depth texture attachments.
* const width = context.canvas.clientWidth;
* const height = context.canvas.clientHeight;
* const framebuffer = new Framebuffer({
* context : context,
* colorTextures : [new Texture({
* context : context,
* width : width,
* height : height,
* pixelFormat : PixelFormat.RGBA
* })],
* depthTexture : new Texture({
* context : context,
* width : width,
* height : height,
* pixelFormat : PixelFormat.DEPTH_COMPONENT,
* pixelDatatype : PixelDatatype.UNSIGNED_SHORT
* })
* });
*
* @private
* @constructor
*/
function Framebuffer(options) {
options = options ?? Frozen.EMPTY_OBJECT;
const context = options.context;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", context);
//>>includeEnd('debug');
const gl = context._gl;
const maximumColorAttachments = ContextLimits.maximumColorAttachments;
this._gl = gl;
this._framebuffer = gl.createFramebuffer();
this._colorTextures = [];
this._colorRenderbuffers = [];
this._activeColorAttachments = [];
this._depthTexture = undefined;
this._depthRenderbuffer = undefined;
this._stencilRenderbuffer = undefined;
this._depthStencilTexture = undefined;
this._depthStencilRenderbuffer = undefined;
/**
* When true, the framebuffer owns its attachments so they will be destroyed when
* {@link Framebuffer#destroy} is called or when a new attachment is assigned
* to an attachment point.
*
* @type {boolean}
* @default true
*
* @see Framebuffer#destroy
*/
this.destroyAttachments = options.destroyAttachments ?? true;
// Throw if a texture and renderbuffer are attached to the same point. This won't
// cause a WebGL error (because only one will be attached), but is likely a developer error.
//>>includeStart('debug', pragmas.debug);
if (defined(options.colorTextures) && defined(options.colorRenderbuffers)) {
throw new DeveloperError(
"Cannot have both color texture and color renderbuffer attachments.",
);
}
if (defined(options.depthTexture) && defined(options.depthRenderbuffer)) {
throw new DeveloperError(
"Cannot have both a depth texture and depth renderbuffer attachment.",
);
}
if (
defined(options.depthStencilTexture) &&
defined(options.depthStencilRenderbuffer)
) {
throw new DeveloperError(
"Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment.",
);
}
// Avoid errors defined in Section 6.5 of the WebGL spec
const depthAttachment =
defined(options.depthTexture) || defined(options.depthRenderbuffer);
const depthStencilAttachment =
defined(options.depthStencilTexture) ||
defined(options.depthStencilRenderbuffer);
if (depthAttachment && depthStencilAttachment) {
throw new DeveloperError(
"Cannot have both a depth and depth-stencil attachment.",
);
}
if (defined(options.stencilRenderbuffer) && depthStencilAttachment) {
throw new DeveloperError(
"Cannot have both a stencil and depth-stencil attachment.",
);
}
if (depthAttachment && defined(options.stencilRenderbuffer)) {
throw new DeveloperError(
"Cannot have both a depth and stencil attachment.",
);
}
//>>includeEnd('debug');
this._bind();
if (defined(options.colorTextures)) {
const textures = options.colorTextures;
const length =
(this._colorTextures.length =
this._activeColorAttachments.length =
textures.length);
//>>includeStart('debug', pragmas.debug);
if (length > maximumColorAttachments) {
throw new DeveloperError(
"The number of color attachments exceeds the number supported.",
);
}
//>>includeEnd('debug');
for (let i = 0; i < length; ++i) {
const texture = textures[i];
//>>includeStart('debug', pragmas.debug);
if (!PixelFormat.isColorFormat(texture.pixelFormat)) {
throw new DeveloperError(
"The color-texture pixel-format must be a color format.",
);
}
if (
texture.pixelDatatype === PixelDatatype.FLOAT &&
!context.colorBufferFloat
) {
throw new DeveloperError(
"The color texture pixel datatype is FLOAT and the WebGL implementation does not support the EXT_color_buffer_float or WEBGL_color_buffer_float extensions. See Context.colorBufferFloat.",
);
}
if (
texture.pixelDatatype === PixelDatatype.HALF_FLOAT &&
!context.colorBufferHalfFloat
) {
throw new DeveloperError(
"The color texture pixel datatype is HALF_FLOAT and the WebGL implementation does not support the EXT_color_buffer_half_float extension. See Context.colorBufferHalfFloat.",
);
}
//>>includeEnd('debug');
const attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i;
attachTexture(this, attachmentEnum, texture);
this._activeColorAttachments[i] = attachmentEnum;
this._colorTextures[i] = texture;
}
}
if (defined(options.colorRenderbuffers)) {
const renderbuffers = options.colorRenderbuffers;
const length =
(this._colorRenderbuffers.length =
this._activeColorAttachments.length =
renderbuffers.length);
//>>includeStart('debug', pragmas.debug);
if (length > maximumColorAttachments) {
throw new DeveloperError(
"The number of color attachments exceeds the number supported.",
);
}
//>>includeEnd('debug');
for (let i = 0; i < length; ++i) {
const renderbuffer = renderbuffers[i];
const attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i;
attachRenderbuffer(this, attachmentEnum, renderbuffer);
this._activeColorAttachments[i] = attachmentEnum;
this._colorRenderbuffers[i] = renderbuffer;
}
}
if (defined(options.depthTexture)) {
const texture = options.depthTexture;
//>>includeStart('debug', pragmas.debug);
if (texture.pixelFormat !== PixelFormat.DEPTH_COMPONENT) {
throw new DeveloperError(
"The depth-texture pixel-format must be DEPTH_COMPONENT.",
);
}
//>>includeEnd('debug');
attachTexture(this, this._gl.DEPTH_ATTACHMENT, texture);
this._depthTexture = texture;
}
if (defined(options.depthRenderbuffer)) {
const renderbuffer = options.depthRenderbuffer;
attachRenderbuffer(this, this._gl.DEPTH_ATTACHMENT, renderbuffer);
this._depthRenderbuffer = renderbuffer;
}
if (defined(options.stencilRenderbuffer)) {
const renderbuffer = options.stencilRenderbuffer;
attachRenderbuffer(this, this._gl.STENCIL_ATTACHMENT, renderbuffer);
this._stencilRenderbuffer = renderbuffer;
}
if (defined(options.depthStencilTexture)) {
const texture = options.depthStencilTexture;
//>>includeStart('debug', pragmas.debug);
if (texture.pixelFormat !== PixelFormat.DEPTH_STENCIL) {
throw new DeveloperError(
"The depth-stencil pixel-format must be DEPTH_STENCIL.",
);
}
//>>includeEnd('debug');
attachTexture(this, this._gl.DEPTH_STENCIL_ATTACHMENT, texture);
this._depthStencilTexture = texture;
}
if (defined(options.depthStencilRenderbuffer)) {
const renderbuffer = options.depthStencilRenderbuffer;
attachRenderbuffer(this, this._gl.DEPTH_STENCIL_ATTACHMENT, renderbuffer);
this._depthStencilRenderbuffer = renderbuffer;
}
this._unBind();
// _bind and _unBind bypass the context's framebuffer binding cache. The GL
// binding is now the default framebuffer; update the cache to match.
context._currentFramebuffer = undefined;
}
Object.defineProperties(Framebuffer.prototype, {
/**
* The status of the framebuffer. If the status is not WebGLConstants.FRAMEBUFFER_COMPLETE,
* a {@link DeveloperError} will be thrown when attempting to render to the framebuffer.
* @memberof Framebuffer.prototype
* @type {number}
*/
status: {
get: function () {
this._bind();
const status = this._gl.checkFramebufferStatus(this._gl.FRAMEBUFFER);
this._unBind();
return status;
},
},
numberOfColorAttachments: {
get: function () {
return this._activeColorAttachments.length;
},
},
depthTexture: {
get: function () {
return this._depthTexture;
},
},
depthRenderbuffer: {
get: function () {
return this._depthRenderbuffer;
},
},
stencilRenderbuffer: {
get: function () {
return this._stencilRenderbuffer;
},
},
depthStencilTexture: {
get: function () {
return this._depthStencilTexture;
},
},
depthStencilRenderbuffer: {
get: function () {
return this._depthStencilRenderbuffer;
},
},
/**
* True if the framebuffer has a depth attachment. Depth attachments include
* depth and depth-stencil textures, and depth and depth-stencil renderbuffers. When
* rendering to a framebuffer, a depth attachment is required for the depth test to have effect.
* @memberof Framebuffer.prototype
* @type {boolean}
*/
hasDepthAttachment: {
get: function () {
return !!(
this.depthTexture ||
this.depthRenderbuffer ||
this.depthStencilTexture ||
this.depthStencilRenderbuffer
);
},
},
});
Framebuffer.prototype._bind = function () {
const gl = this._gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebuffer);
};
Framebuffer.prototype._unBind = function () {
const gl = this._gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
};
Framebuffer.prototype.bindDraw = function () {
const gl = this._gl;
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._framebuffer);
};
Framebuffer.prototype.bindRead = function () {
const gl = this._gl;
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this._framebuffer);
};
Framebuffer.prototype._getActiveColorAttachments = function () {
return this._activeColorAttachments;
};
Framebuffer.prototype.getColorTexture = function (index) {
//>>includeStart('debug', pragmas.debug);
if (!defined(index) || index < 0 || index >= this._colorTextures.length) {
throw new DeveloperError(
"index is required, must be greater than or equal to zero and must be less than the number of color attachments.",
);
}
//>>includeEnd('debug');
return this._colorTextures[index];
};
Framebuffer.prototype.getColorRenderbuffer = function (index) {
//>>includeStart('debug', pragmas.debug);
if (
!defined(index) ||
index < 0 ||
index >= this._colorRenderbuffers.length
) {
throw new DeveloperError(
"index is required, must be greater than or equal to zero and must be less than the number of color attachments.",
);
}
//>>includeEnd('debug');
return this._colorRenderbuffers[index];
};
Framebuffer.prototype.isDestroyed = function () {
return false;
};
Framebuffer.prototype.destroy = function () {
if (this.destroyAttachments) {
// If the color texture is a cube map face, it is owned by the cube map, and will not be destroyed.
const textures = this._colorTextures;
for (let i = 0; i < textures.length; ++i) {
const texture = textures[i];
if (defined(texture)) {
texture.destroy();
}
}
const renderbuffers = this._colorRenderbuffers;
for (let i = 0; i < renderbuffers.length; ++i) {
const renderbuffer = renderbuffers[i];
if (defined(renderbuffer)) {
renderbuffer.destroy();
}
}
this._depthTexture = this._depthTexture && this._depthTexture.destroy();
this._depthRenderbuffer =
this._depthRenderbuffer && this._depthRenderbuffer.destroy();
this._stencilRenderbuffer =
this._stencilRenderbuffer && this._stencilRenderbuffer.destroy();
this._depthStencilTexture =
this._depthStencilTexture && this._depthStencilTexture.destroy();
this._depthStencilRenderbuffer =
this._depthStencilRenderbuffer &&
this._depthStencilRenderbuffer.destroy();
}
this._gl.deleteFramebuffer(this._framebuffer);
return destroyObject(this);
};
export default Framebuffer;
+489
View File
@@ -0,0 +1,489 @@
import Framebuffer from "./Framebuffer.js";
import MultisampleFramebuffer from "./MultisampleFramebuffer.js";
import PixelDatatype from "./PixelDatatype.js";
import Renderbuffer from "./Renderbuffer.js";
import RenderbufferFormat from "./RenderbufferFormat.js";
import Sampler from "./Sampler.js";
import Texture from "./Texture.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import PixelFormat from "../Core/PixelFormat.js";
/**
* Creates a wrapper object around a framebuffer and its resources.
*
* @param {object} options Object with the following properties:
* @param {number} [options.numSamples=1] The multisampling rate of the render targets. Requires a WebGL2 context.
* @param {number} [options.colorAttachmentsLength=1] The number of color attachments this FramebufferManager will create.
* @param {boolean} [options.color=true] Whether the FramebufferManager will use color attachments.
* @param {boolean} [options.depth=false] Whether the FramebufferManager will use depth attachments.
* @param {boolean} [options.depthStencil=false] Whether the FramebufferManager will use depth-stencil attachments.
* @param {boolean} [options.supportsDepthTexture=false] Whether the FramebufferManager will create a depth texture when the extension is supported.
* @param {boolean} [options.createColorAttachments=true] Whether the FramebufferManager will construct its own color attachments.
* @param {boolean} [options.createDepthAttachments=true] Whether the FramebufferManager will construct its own depth attachments.
* @param {PixelDatatype} [options.pixelDatatype=undefined] The default pixel datatype to use when creating color attachments.
* @param {PixelFormat} [options.pixelFormat=undefined] The default pixel format to use when creating color attachments.
*
* @exception {DeveloperError} Must enable at least one type of framebuffer attachment.
* @exception {DeveloperError} Cannot have both a depth and depth-stencil attachment.
*
* @private
* @constructor
*/
function FramebufferManager(options) {
options = options ?? Frozen.EMPTY_OBJECT;
this._numSamples = options.numSamples ?? 1;
this._colorAttachmentsLength = options.colorAttachmentsLength ?? 1;
this._color = options.color ?? true;
this._depth = options.depth ?? false;
this._depthStencil = options.depthStencil ?? false;
this._supportsDepthTexture = options.supportsDepthTexture ?? false;
//>>includeStart('debug', pragmas.debug);
if (!this._color && !this._depth && !this._depthStencil) {
throw new DeveloperError(
"Must enable at least one type of framebuffer attachment.",
);
}
if (this._depth && this._depthStencil) {
throw new DeveloperError(
"Cannot have both a depth and depth-stencil attachment.",
);
}
//>>includeEnd('debug');
this._createColorAttachments = options.createColorAttachments ?? true;
this._createDepthAttachments = options.createDepthAttachments ?? true;
this._pixelDatatype = options.pixelDatatype;
this._pixelFormat = options.pixelFormat;
this._width = undefined;
this._height = undefined;
this._framebuffer = undefined;
this._multisampleFramebuffer = undefined;
this._colorTextures = undefined;
if (this._color) {
this._colorTextures = new Array(this._colorAttachmentsLength);
this._colorRenderbuffers = new Array(this._colorAttachmentsLength);
}
this._colorRenderbuffer = undefined;
this._depthStencilRenderbuffer = undefined;
this._depthStencilTexture = undefined;
this._depthRenderbuffer = undefined;
this._depthTexture = undefined;
this._attachmentsDirty = false;
}
Object.defineProperties(FramebufferManager.prototype, {
framebuffer: {
get: function () {
if (this._numSamples > 1) {
return this._multisampleFramebuffer.getRenderFramebuffer();
}
return this._framebuffer;
},
},
numSamples: {
get: function () {
return this._numSamples;
},
},
status: {
get: function () {
return this.framebuffer.status;
},
},
});
FramebufferManager.prototype.isDirty = function (
width,
height,
numSamples,
pixelDatatype,
pixelFormat,
) {
numSamples = numSamples ?? 1;
const dimensionChanged = this._width !== width || this._height !== height;
const samplesChanged = this._numSamples !== numSamples;
const pixelChanged =
(defined(pixelDatatype) && this._pixelDatatype !== pixelDatatype) ||
(defined(pixelFormat) && this._pixelFormat !== pixelFormat);
const framebufferDefined =
numSamples === 1
? defined(this._framebuffer)
: defined(this._multisampleFramebuffer);
return (
this._attachmentsDirty ||
dimensionChanged ||
samplesChanged ||
pixelChanged ||
!framebufferDefined ||
(this._color && !defined(this._colorTextures[0]))
);
};
FramebufferManager.prototype.update = function (
context,
width,
height,
numSamples,
pixelDatatype,
pixelFormat,
) {
//>>includeStart('debug', pragmas.debug);
if (!defined(width) || !defined(height)) {
throw new DeveloperError("width and height must be defined.");
}
//>>includeEnd('debug');
numSamples = context.msaa ? (numSamples ?? 1) : 1;
pixelDatatype =
pixelDatatype ??
(this._color
? (this._pixelDatatype ?? PixelDatatype.UNSIGNED_BYTE)
: undefined);
pixelFormat =
pixelFormat ??
(this._color ? (this._pixelFormat ?? PixelFormat.RGBA) : undefined);
if (this.isDirty(width, height, numSamples, pixelDatatype, pixelFormat)) {
this.destroy();
this._width = width;
this._height = height;
this._numSamples = numSamples;
this._pixelDatatype = pixelDatatype;
this._pixelFormat = pixelFormat;
this._attachmentsDirty = false;
// Create color texture
if (this._color && this._createColorAttachments) {
for (let i = 0; i < this._colorAttachmentsLength; ++i) {
this._colorTextures[i] = new Texture({
context: context,
width: width,
height: height,
pixelFormat: pixelFormat,
pixelDatatype: pixelDatatype,
sampler: Sampler.NEAREST,
});
if (this._numSamples > 1) {
const format = RenderbufferFormat.getColorFormat(pixelDatatype);
this._colorRenderbuffers[i] = new Renderbuffer({
context: context,
width: width,
height: height,
format: format,
numSamples: this._numSamples,
});
}
}
}
// Create depth stencil texture or renderbuffer
if (this._depthStencil && this._createDepthAttachments) {
if (this._supportsDepthTexture && context.depthTexture) {
this._depthStencilTexture = new Texture({
context: context,
width: width,
height: height,
pixelFormat: PixelFormat.DEPTH_STENCIL,
pixelDatatype: PixelDatatype.UNSIGNED_INT_24_8,
sampler: Sampler.NEAREST,
});
if (this._numSamples > 1) {
this._depthStencilRenderbuffer = new Renderbuffer({
context: context,
width: width,
height: height,
format: RenderbufferFormat.DEPTH24_STENCIL8,
numSamples: this._numSamples,
});
}
} else {
this._depthStencilRenderbuffer = new Renderbuffer({
context: context,
width: width,
height: height,
format: RenderbufferFormat.DEPTH_STENCIL,
});
}
}
// Create depth texture
if (this._depth && this._createDepthAttachments) {
if (this._supportsDepthTexture && context.depthTexture) {
this._depthTexture = new Texture({
context: context,
width: width,
height: height,
pixelFormat: PixelFormat.DEPTH_COMPONENT,
pixelDatatype: PixelDatatype.UNSIGNED_INT,
sampler: Sampler.NEAREST,
});
} else {
this._depthRenderbuffer = new Renderbuffer({
context: context,
width: width,
height: height,
format: RenderbufferFormat.DEPTH_COMPONENT16,
});
}
}
if (this._numSamples > 1) {
this._multisampleFramebuffer = new MultisampleFramebuffer({
context: context,
width: this._width,
height: this._height,
colorTextures: this._colorTextures,
colorRenderbuffers: this._colorRenderbuffers,
depthStencilTexture: this._depthStencilTexture,
depthStencilRenderbuffer: this._depthStencilRenderbuffer,
destroyAttachments: false,
});
} else {
this._framebuffer = new Framebuffer({
context: context,
colorTextures: this._colorTextures,
depthTexture: this._depthTexture,
depthRenderbuffer: this._depthRenderbuffer,
depthStencilTexture: this._depthStencilTexture,
depthStencilRenderbuffer: this._depthStencilRenderbuffer,
destroyAttachments: false,
});
}
}
};
FramebufferManager.prototype.getColorTexture = function (index) {
index = index ?? 0;
//>>includeStart('debug', pragmas.debug);
if (index >= this._colorAttachmentsLength) {
throw new DeveloperError(
"index must be smaller than total number of color attachments.",
);
}
//>>includeEnd('debug');
return this._colorTextures[index];
};
FramebufferManager.prototype.setColorTexture = function (texture, index) {
index = index ?? 0;
//>>includeStart('debug', pragmas.debug);
if (this._createColorAttachments) {
throw new DeveloperError(
"createColorAttachments must be false if setColorTexture is called.",
);
}
if (index >= this._colorAttachmentsLength) {
throw new DeveloperError(
"index must be smaller than total number of color attachments.",
);
}
//>>includeEnd('debug');
this._attachmentsDirty = texture !== this._colorTextures[index];
this._colorTextures[index] = texture;
};
FramebufferManager.prototype.getColorRenderbuffer = function (index) {
index = index ?? 0;
//>>includeStart('debug', pragmas.debug);
if (index >= this._colorAttachmentsLength) {
throw new DeveloperError(
"index must be smaller than total number of color attachments.",
);
}
//>>includeEnd('debug');
return this._colorRenderbuffers[index];
};
FramebufferManager.prototype.setColorRenderbuffer = function (
renderbuffer,
index,
) {
index = index ?? 0;
//>>includeStart('debug', pragmas.debug);
if (this._createColorAttachments) {
throw new DeveloperError(
"createColorAttachments must be false if setColorRenderbuffer is called.",
);
}
if (index >= this._colorAttachmentsLength) {
throw new DeveloperError(
"index must be smaller than total number of color attachments.",
);
}
//>>includeEnd('debug');
this._attachmentsDirty = renderbuffer !== this._colorRenderbuffers[index];
this._colorRenderbuffers[index] = renderbuffer;
};
FramebufferManager.prototype.getDepthRenderbuffer = function () {
return this._depthRenderbuffer;
};
FramebufferManager.prototype.setDepthRenderbuffer = function (renderbuffer) {
//>>includeStart('debug', pragmas.debug);
if (this._createDepthAttachments) {
throw new DeveloperError(
"createDepthAttachments must be false if setDepthRenderbuffer is called.",
);
}
//>>includeEnd('debug');
this._attachmentsDirty = renderbuffer !== this._depthRenderbuffer;
this._depthRenderbuffer = renderbuffer;
};
FramebufferManager.prototype.getDepthTexture = function () {
return this._depthTexture;
};
FramebufferManager.prototype.setDepthTexture = function (texture) {
//>>includeStart('debug', pragmas.debug);
if (this._createDepthAttachments) {
throw new DeveloperError(
"createDepthAttachments must be false if setDepthTexture is called.",
);
}
//>>includeEnd('debug');
this._attachmentsDirty = texture !== this._depthTexture;
this._depthTexture = texture;
};
FramebufferManager.prototype.getDepthStencilRenderbuffer = function () {
return this._depthStencilRenderbuffer;
};
FramebufferManager.prototype.setDepthStencilRenderbuffer = function (
renderbuffer,
) {
//>>includeStart('debug', pragmas.debug);
if (this._createDepthAttachments) {
throw new DeveloperError(
"createDepthAttachments must be false if setDepthStencilRenderbuffer is called.",
);
}
//>>includeEnd('debug');
this._attachmentsDirty = renderbuffer !== this._depthStencilRenderbuffer;
this._depthStencilRenderbuffer = renderbuffer;
};
FramebufferManager.prototype.getDepthStencilTexture = function () {
return this._depthStencilTexture;
};
FramebufferManager.prototype.setDepthStencilTexture = function (texture) {
//>>includeStart('debug', pragmas.debug);
if (this._createDepthAttachments) {
throw new DeveloperError(
"createDepthAttachments must be false if setDepthStencilTexture is called.",
);
}
//>>includeEnd('debug');
this._attachmentsDirty = texture !== this._depthStencilTexture;
this._depthStencilTexture = texture;
};
/**
* If using MSAA, resolve the stencil.
*
* @param {Context} context
* @param {boolean} blitStencil
*
* @private
*/
FramebufferManager.prototype.prepareTextures = function (context, blitStencil) {
if (this._numSamples > 1) {
this._multisampleFramebuffer.blitFramebuffers(context, blitStencil);
}
};
FramebufferManager.prototype.clear = function (
context,
clearCommand,
passState,
) {
const framebuffer = clearCommand.framebuffer;
clearCommand.framebuffer = this.framebuffer;
clearCommand.execute(context, passState);
clearCommand.framebuffer = framebuffer;
};
FramebufferManager.prototype.destroyFramebuffer = function () {
this._framebuffer = this._framebuffer && this._framebuffer.destroy();
this._multisampleFramebuffer =
this._multisampleFramebuffer && this._multisampleFramebuffer.destroy();
};
FramebufferManager.prototype.destroy = function () {
if (this._color) {
const colorTextures = this._colorTextures;
const colorRenderbuffers = this._colorRenderbuffers;
for (let i = 0; i < colorTextures.length; ++i) {
const texture = colorTextures[i];
if (this._createColorAttachments) {
if (defined(texture) && !texture.isDestroyed()) {
texture.destroy();
}
}
if (defined(texture) && texture.isDestroyed()) {
colorTextures[i] = undefined;
}
const renderbuffer = colorRenderbuffers[i];
if (this._createColorAttachments) {
if (defined(renderbuffer) && !renderbuffer.isDestroyed()) {
renderbuffer.destroy();
}
}
if (defined(renderbuffer) && renderbuffer.isDestroyed()) {
colorRenderbuffers[i] = undefined;
}
}
}
if (this._depthStencil) {
if (this._createDepthAttachments) {
this._depthStencilTexture =
this._depthStencilTexture && this._depthStencilTexture.destroy();
this._depthStencilRenderbuffer =
this._depthStencilRenderbuffer &&
this._depthStencilRenderbuffer.destroy();
}
if (
defined(this._depthStencilTexture) &&
this._depthStencilTexture.isDestroyed()
) {
this._depthStencilTexture = undefined;
}
if (
defined(this._depthStencilRenderbuffer) &&
this._depthStencilRenderbuffer.isDestroyed()
) {
this._depthStencilRenderbuffer = undefined;
}
}
if (this._depth) {
if (this._createDepthAttachments) {
this._depthTexture = this._depthTexture && this._depthTexture.destroy();
this._depthRenderbuffer =
this._depthRenderbuffer && this._depthRenderbuffer.destroy();
}
if (defined(this._depthTexture) && this._depthTexture.isDestroyed()) {
this._depthTexture = undefined;
}
if (
defined(this._depthRenderbuffer) &&
this._depthRenderbuffer.isDestroyed()
) {
this._depthRenderbuffer = undefined;
}
}
this.destroyFramebuffer();
};
export default FramebufferManager;
+23
View File
@@ -0,0 +1,23 @@
import WebGLConstants from "../Core/WebGLConstants.js";
/**
* @enum {number}
* @private
*/
const MipmapHint = {
DONT_CARE: WebGLConstants.DONT_CARE,
FASTEST: WebGLConstants.FASTEST,
NICEST: WebGLConstants.NICEST,
validate: function (mipmapHint) {
return (
mipmapHint === MipmapHint.DONT_CARE ||
mipmapHint === MipmapHint.FASTEST ||
mipmapHint === MipmapHint.NICEST
);
},
};
Object.freeze(MipmapHint);
export default MipmapHint;
+136
View File
@@ -0,0 +1,136 @@
import Check from "../Core/Check.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import Framebuffer from "./Framebuffer.js";
/**
* Creates a multisampling wrapper around two framebuffers with optional initial
* color and depth-stencil attachments. The first framebuffer has multisampled
* renderbuffer attachments and is bound to READ_FRAMEBUFFER during the blit. The
* second is bound to DRAW_FRAMEBUFFER during the blit, and has texture attachments
* to store the copied pixels.
*
* @param {object} options Object with the following properties:
* @param {Context} options.context
* @param {number} options.width
* @param {number} options.height
* @param {Texture[]} [options.colorTextures]
* @param {Renderbuffer[]} [options.colorRenderbuffers]
* @param {Texture} [options.depthStencilTexture]
* @param {Renderbuffer} [options.depthStencilRenderbuffer]
* @param {boolean} [options.destroyAttachments]
*
* @exception {DeveloperError} Both color renderbuffer and texture attachments must be provided.
* @exception {DeveloperError} Both depth-stencil renderbuffer and texture attachments must be provided.
*
* @private
* @constructor
*/
function MultisampleFramebuffer(options) {
options = options ?? Frozen.EMPTY_OBJECT;
const {
context,
width,
height,
colorRenderbuffers,
colorTextures,
depthStencilRenderbuffer,
depthStencilTexture,
destroyAttachments,
} = options;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", context);
Check.defined("options.width", width);
Check.defined("options.height", height);
//>>includeEnd('debug');
this._width = width;
this._height = height;
if (defined(colorRenderbuffers) !== defined(colorTextures)) {
throw new DeveloperError(
"Both color renderbuffer and texture attachments must be provided.",
);
}
if (defined(depthStencilRenderbuffer) !== defined(depthStencilTexture)) {
throw new DeveloperError(
"Both depth-stencil renderbuffer and texture attachments must be provided.",
);
}
this._renderFramebuffer = new Framebuffer({
context: context,
colorRenderbuffers: colorRenderbuffers,
depthStencilRenderbuffer: depthStencilRenderbuffer,
destroyAttachments: destroyAttachments,
});
this._colorFramebuffer = new Framebuffer({
context: context,
colorTextures: colorTextures,
depthStencilTexture: depthStencilTexture,
destroyAttachments: destroyAttachments,
});
}
MultisampleFramebuffer.prototype.getRenderFramebuffer = function () {
return this._renderFramebuffer;
};
MultisampleFramebuffer.prototype.getColorFramebuffer = function () {
return this._colorFramebuffer;
};
/**
* Copy from the render framebuffer to the color framebuffer, resolving the stencil.
*
* @param {Context} context
* @param {boolean} blitStencil <code>true</code> if the stencil mask should be applied.
*
* @private
*/
MultisampleFramebuffer.prototype.blitFramebuffers = function (
context,
blitStencil,
) {
this._renderFramebuffer.bindRead();
this._colorFramebuffer.bindDraw();
const gl = context._gl;
let mask = 0;
if (this._colorFramebuffer._colorTextures.length > 0) {
mask |= gl.COLOR_BUFFER_BIT;
}
if (defined(this._colorFramebuffer.depthStencilTexture)) {
mask |= gl.DEPTH_BUFFER_BIT | (blitStencil ? gl.STENCIL_BUFFER_BIT : 0);
}
gl.blitFramebuffer(
0,
0,
this._width,
this._height,
0,
0,
this._width,
this._height,
mask,
gl.NEAREST,
);
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
};
MultisampleFramebuffer.prototype.isDestroyed = function () {
return false;
};
MultisampleFramebuffer.prototype.destroy = function () {
this._renderFramebuffer.destroy();
this._colorFramebuffer.destroy();
return destroyObject(this);
};
export default MultisampleFramebuffer;
+37
View File
@@ -0,0 +1,37 @@
// @ts-check
/**
* The render pass for a command.
*
* @enum {number}
* @private
*/
const Pass = {
// If you add/modify/remove Pass constants, also change the automatic GLSL constants
// that start with 'czm_pass'
//
// Commands are executed in order by pass up to the translucent pass.
// Translucent geometry needs special handling (sorting/OIT). The compute pass
// is executed first and the overlay pass is executed last. Both are not sorted
// by frustum.
ENVIRONMENT: 0,
COMPUTE: 1,
GLOBE: 2,
TERRAIN_CLASSIFICATION: 3,
CESIUM_3D_TILE_EDGES: 4,
CESIUM_3D_TILE_PLANAR_FILL_ID: 5,
CESIUM_3D_TILE: 6,
CESIUM_3D_TILE_CLASSIFICATION: 7,
CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW: 8,
OPAQUE: 9,
TRANSLUCENT: 10,
VOXELS: 11,
GAUSSIAN_SPLATS: 12,
CESIUM_3D_TILE_EDGES_DIRECT: 13,
OVERLAY: 14,
NUMBER_OF_PASSES: 15,
};
Object.freeze(Pass);
export default Pass;
+57
View File
@@ -0,0 +1,57 @@
/**
* The state for a particular rendering pass. This is used to supplement the state
* in a command being executed.
*
* @private
* @constructor
*/
function PassState(context) {
/**
* The context used to execute commands for this pass.
*
* @type {Context}
*/
this.context = context;
/**
* The framebuffer to render to. This framebuffer is used unless a {@link DrawCommand}
* or {@link ClearCommand} explicitly define a framebuffer, which is used for off-screen
* rendering.
*
* @type {Framebuffer}
* @default undefined
*/
this.framebuffer = undefined;
/**
* When defined, this overrides the blending property of a {@link DrawCommand}'s render state.
* This is used to, for example, to allow the renderer to turn off blending during the picking pass.
* <p>
* When this is <code>undefined</code>, the {@link DrawCommand}'s property is used.
* </p>
*
* @type {boolean}
* @default undefined
*/
this.blendingEnabled = undefined;
/**
* When defined, this overrides the scissor test property of a {@link DrawCommand}'s render state.
* This is used to, for example, to allow the renderer to scissor out the pick region during the picking pass.
* <p>
* When this is <code>undefined</code>, the {@link DrawCommand}'s property is used.
* </p>
*
* @type {object}
* @default undefined
*/
this.scissorTest = undefined;
/**
* The viewport used when one is not defined by a {@link DrawCommand}'s render state.
* @type {BoundingRectangle}
* @default undefined
*/
this.viewport = undefined;
}
export default PassState;
+44
View File
@@ -0,0 +1,44 @@
// @ts-check
/** @import Color from "../Core/Color.js"; */
/** @import {Destroyable} from "../Core/globalTypes.js"; */
/**
* Represents a pickable object with a unique integer ID and picking color.
*
* @implements {Destroyable}
* @ignore
*/
class PickId {
/**
* @param {Map<number, object>} pickObjects
* @param {number} key
* @param {Color} color
*/
constructor(pickObjects, key, color) {
this._pickObjects = pickObjects;
/** @type {number} */
this.key = key;
/** @type {Color} */
this.color = color;
}
/** @type {object} */
get object() {
return this._pickObjects.get(this.key);
}
set object(value) {
this._pickObjects.set(this.key, value);
}
/** @returns {void} */
destroy() {
this._pickObjects.delete(this.key);
return undefined;
}
}
export default PickId;
+123
View File
@@ -0,0 +1,123 @@
import WebGLConstants from "../Core/WebGLConstants.js";
/**
* The data type of a pixel.
*
* @enum {number}
* @see PostProcessStage
*/
const PixelDatatype = {
UNSIGNED_BYTE: WebGLConstants.UNSIGNED_BYTE,
UNSIGNED_SHORT: WebGLConstants.UNSIGNED_SHORT,
UNSIGNED_INT: WebGLConstants.UNSIGNED_INT,
FLOAT: WebGLConstants.FLOAT,
HALF_FLOAT: WebGLConstants.HALF_FLOAT_OES,
UNSIGNED_INT_24_8: WebGLConstants.UNSIGNED_INT_24_8,
UNSIGNED_SHORT_4_4_4_4: WebGLConstants.UNSIGNED_SHORT_4_4_4_4,
UNSIGNED_SHORT_5_5_5_1: WebGLConstants.UNSIGNED_SHORT_5_5_5_1,
UNSIGNED_SHORT_5_6_5: WebGLConstants.UNSIGNED_SHORT_5_6_5,
};
/**
@private
*/
PixelDatatype.toWebGLConstant = function (pixelDatatype, context) {
switch (pixelDatatype) {
case PixelDatatype.UNSIGNED_BYTE:
return WebGLConstants.UNSIGNED_BYTE;
case PixelDatatype.UNSIGNED_SHORT:
return WebGLConstants.UNSIGNED_SHORT;
case PixelDatatype.UNSIGNED_INT:
return WebGLConstants.UNSIGNED_INT;
case PixelDatatype.FLOAT:
return WebGLConstants.FLOAT;
case PixelDatatype.HALF_FLOAT:
return context.webgl2
? WebGLConstants.HALF_FLOAT
: WebGLConstants.HALF_FLOAT_OES;
case PixelDatatype.UNSIGNED_INT_24_8:
return WebGLConstants.UNSIGNED_INT_24_8;
case PixelDatatype.UNSIGNED_SHORT_4_4_4_4:
return WebGLConstants.UNSIGNED_SHORT_4_4_4_4;
case PixelDatatype.UNSIGNED_SHORT_5_5_5_1:
return WebGLConstants.UNSIGNED_SHORT_5_5_5_1;
case PixelDatatype.UNSIGNED_SHORT_5_6_5:
return PixelDatatype.UNSIGNED_SHORT_5_6_5;
}
};
/**
@private
*/
PixelDatatype.isPacked = function (pixelDatatype) {
return (
pixelDatatype === PixelDatatype.UNSIGNED_INT_24_8 ||
pixelDatatype === PixelDatatype.UNSIGNED_SHORT_4_4_4_4 ||
pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_5_5_1 ||
pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_6_5
);
};
/**
@private
*/
PixelDatatype.sizeInBytes = function (pixelDatatype) {
switch (pixelDatatype) {
case PixelDatatype.UNSIGNED_BYTE:
return 1;
case PixelDatatype.UNSIGNED_SHORT:
case PixelDatatype.UNSIGNED_SHORT_4_4_4_4:
case PixelDatatype.UNSIGNED_SHORT_5_5_5_1:
case PixelDatatype.UNSIGNED_SHORT_5_6_5:
case PixelDatatype.HALF_FLOAT:
return 2;
case PixelDatatype.UNSIGNED_INT:
case PixelDatatype.FLOAT:
case PixelDatatype.UNSIGNED_INT_24_8:
return 4;
}
};
/**
@private
*/
PixelDatatype.validate = function (pixelDatatype) {
return (
pixelDatatype === PixelDatatype.UNSIGNED_BYTE ||
pixelDatatype === PixelDatatype.UNSIGNED_SHORT ||
pixelDatatype === PixelDatatype.UNSIGNED_INT ||
pixelDatatype === PixelDatatype.FLOAT ||
pixelDatatype === PixelDatatype.HALF_FLOAT ||
pixelDatatype === PixelDatatype.UNSIGNED_INT_24_8 ||
pixelDatatype === PixelDatatype.UNSIGNED_SHORT_4_4_4_4 ||
pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_5_5_1 ||
pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_6_5
);
};
/**
* Determine which TypedArray class should be used for a given PixelDatatype.
*
* @param {PixelDatatype} pixelDatatype The pixel datatype.
* @returns {function} The constructor for the appropriate TypedArray class.
*
* @private
*/
PixelDatatype.getTypedArrayConstructor = function (pixelDatatype) {
const sizeInBytes = PixelDatatype.sizeInBytes(pixelDatatype);
if (sizeInBytes === Uint8Array.BYTES_PER_ELEMENT) {
return Uint8Array;
} else if (sizeInBytes === Uint16Array.BYTES_PER_ELEMENT) {
return Uint16Array;
} else if (
sizeInBytes === Float32Array.BYTES_PER_ELEMENT &&
pixelDatatype === PixelDatatype.FLOAT
) {
return Float32Array;
}
return Uint32Array;
};
Object.freeze(PixelDatatype);
export default PixelDatatype;
+967
View File
@@ -0,0 +1,967 @@
import BoundingRectangle from "../Core/BoundingRectangle.js";
import Color from "../Core/Color.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import WebGLConstants from "../Core/WebGLConstants.js";
import WindingOrder from "../Core/WindingOrder.js";
import ContextLimits from "./ContextLimits.js";
import freezeRenderState from "./freezeRenderState.js";
function validateBlendEquation(blendEquation) {
return (
blendEquation === WebGLConstants.FUNC_ADD ||
blendEquation === WebGLConstants.FUNC_SUBTRACT ||
blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT ||
blendEquation === WebGLConstants.MIN ||
blendEquation === WebGLConstants.MAX
);
}
function validateBlendFunction(blendFunction) {
return (
blendFunction === WebGLConstants.ZERO ||
blendFunction === WebGLConstants.ONE ||
blendFunction === WebGLConstants.SRC_COLOR ||
blendFunction === WebGLConstants.ONE_MINUS_SRC_COLOR ||
blendFunction === WebGLConstants.DST_COLOR ||
blendFunction === WebGLConstants.ONE_MINUS_DST_COLOR ||
blendFunction === WebGLConstants.SRC_ALPHA ||
blendFunction === WebGLConstants.ONE_MINUS_SRC_ALPHA ||
blendFunction === WebGLConstants.DST_ALPHA ||
blendFunction === WebGLConstants.ONE_MINUS_DST_ALPHA ||
blendFunction === WebGLConstants.CONSTANT_COLOR ||
blendFunction === WebGLConstants.ONE_MINUS_CONSTANT_COLOR ||
blendFunction === WebGLConstants.CONSTANT_ALPHA ||
blendFunction === WebGLConstants.ONE_MINUS_CONSTANT_ALPHA ||
blendFunction === WebGLConstants.SRC_ALPHA_SATURATE
);
}
function validateCullFace(cullFace) {
return (
cullFace === WebGLConstants.FRONT ||
cullFace === WebGLConstants.BACK ||
cullFace === WebGLConstants.FRONT_AND_BACK
);
}
function validateDepthFunction(depthFunction) {
return (
depthFunction === WebGLConstants.NEVER ||
depthFunction === WebGLConstants.LESS ||
depthFunction === WebGLConstants.EQUAL ||
depthFunction === WebGLConstants.LEQUAL ||
depthFunction === WebGLConstants.GREATER ||
depthFunction === WebGLConstants.NOTEQUAL ||
depthFunction === WebGLConstants.GEQUAL ||
depthFunction === WebGLConstants.ALWAYS
);
}
function validateStencilFunction(stencilFunction) {
return (
stencilFunction === WebGLConstants.NEVER ||
stencilFunction === WebGLConstants.LESS ||
stencilFunction === WebGLConstants.EQUAL ||
stencilFunction === WebGLConstants.LEQUAL ||
stencilFunction === WebGLConstants.GREATER ||
stencilFunction === WebGLConstants.NOTEQUAL ||
stencilFunction === WebGLConstants.GEQUAL ||
stencilFunction === WebGLConstants.ALWAYS
);
}
function validateStencilOperation(stencilOperation) {
return (
stencilOperation === WebGLConstants.ZERO ||
stencilOperation === WebGLConstants.KEEP ||
stencilOperation === WebGLConstants.REPLACE ||
stencilOperation === WebGLConstants.INCR ||
stencilOperation === WebGLConstants.DECR ||
stencilOperation === WebGLConstants.INVERT ||
stencilOperation === WebGLConstants.INCR_WRAP ||
stencilOperation === WebGLConstants.DECR_WRAP
);
}
/**
* @private
*/
function RenderState(renderState) {
const rs = renderState ?? Frozen.EMPTY_OBJECT;
const cull = rs.cull ?? Frozen.EMPTY_OBJECT;
const polygonOffset = rs.polygonOffset ?? Frozen.EMPTY_OBJECT;
const scissorTest = rs.scissorTest ?? Frozen.EMPTY_OBJECT;
const scissorTestRectangle = scissorTest.rectangle ?? Frozen.EMPTY_OBJECT;
const depthRange = rs.depthRange ?? Frozen.EMPTY_OBJECT;
const depthTest = rs.depthTest ?? Frozen.EMPTY_OBJECT;
const colorMask = rs.colorMask ?? Frozen.EMPTY_OBJECT;
const blending = rs.blending ?? Frozen.EMPTY_OBJECT;
const blendingColor = blending.color ?? Frozen.EMPTY_OBJECT;
const stencilTest = rs.stencilTest ?? Frozen.EMPTY_OBJECT;
const stencilTestFrontOperation =
stencilTest.frontOperation ?? Frozen.EMPTY_OBJECT;
const stencilTestBackOperation =
stencilTest.backOperation ?? Frozen.EMPTY_OBJECT;
const sampleCoverage = rs.sampleCoverage ?? Frozen.EMPTY_OBJECT;
const viewport = rs.viewport;
this.frontFace = rs.frontFace ?? WindingOrder.COUNTER_CLOCKWISE;
this.cull = {
enabled: cull.enabled ?? false,
face: cull.face ?? WebGLConstants.BACK,
};
this.lineWidth = rs.lineWidth ?? 1.0;
this.polygonOffset = {
enabled: polygonOffset.enabled ?? false,
factor: polygonOffset.factor ?? 0,
units: polygonOffset.units ?? 0,
};
this.scissorTest = {
enabled: scissorTest.enabled ?? false,
rectangle: BoundingRectangle.clone(scissorTestRectangle),
};
this.depthRange = {
near: depthRange.near ?? 0,
far: depthRange.far ?? 1,
};
this.depthTest = {
enabled: depthTest.enabled ?? false,
func: depthTest.func ?? WebGLConstants.LESS, // func, because function is a JavaScript keyword
};
this.colorMask = {
red: colorMask.red ?? true,
green: colorMask.green ?? true,
blue: colorMask.blue ?? true,
alpha: colorMask.alpha ?? true,
};
this.depthMask = rs.depthMask ?? true;
this.stencilMask = rs.stencilMask ?? ~0;
this.blending = {
enabled: blending.enabled ?? false,
color: new Color(
blendingColor.red ?? 0.0,
blendingColor.green ?? 0.0,
blendingColor.blue ?? 0.0,
blendingColor.alpha ?? 0.0,
),
equationRgb: blending.equationRgb ?? WebGLConstants.FUNC_ADD,
equationAlpha: blending.equationAlpha ?? WebGLConstants.FUNC_ADD,
functionSourceRgb: blending.functionSourceRgb ?? WebGLConstants.ONE,
functionSourceAlpha: blending.functionSourceAlpha ?? WebGLConstants.ONE,
functionDestinationRgb:
blending.functionDestinationRgb ?? WebGLConstants.ZERO,
functionDestinationAlpha:
blending.functionDestinationAlpha ?? WebGLConstants.ZERO,
};
this.stencilTest = {
enabled: stencilTest.enabled ?? false,
frontFunction: stencilTest.frontFunction ?? WebGLConstants.ALWAYS,
backFunction: stencilTest.backFunction ?? WebGLConstants.ALWAYS,
reference: stencilTest.reference ?? 0,
mask: stencilTest.mask ?? ~0,
frontOperation: {
fail: stencilTestFrontOperation.fail ?? WebGLConstants.KEEP,
zFail: stencilTestFrontOperation.zFail ?? WebGLConstants.KEEP,
zPass: stencilTestFrontOperation.zPass ?? WebGLConstants.KEEP,
},
backOperation: {
fail: stencilTestBackOperation.fail ?? WebGLConstants.KEEP,
zFail: stencilTestBackOperation.zFail ?? WebGLConstants.KEEP,
zPass: stencilTestBackOperation.zPass ?? WebGLConstants.KEEP,
},
};
this.sampleCoverage = {
enabled: sampleCoverage.enabled ?? false,
value: sampleCoverage.value ?? 1.0,
invert: sampleCoverage.invert ?? false,
};
this.viewport = defined(viewport)
? new BoundingRectangle(
viewport.x,
viewport.y,
viewport.width,
viewport.height,
)
: undefined;
//>>includeStart('debug', pragmas.debug);
if (
this.lineWidth < ContextLimits.minimumAliasedLineWidth ||
this.lineWidth > ContextLimits.maximumAliasedLineWidth
) {
throw new DeveloperError(
"renderState.lineWidth is out of range. Check minimumAliasedLineWidth and maximumAliasedLineWidth.",
);
}
if (!WindingOrder.validate(this.frontFace)) {
throw new DeveloperError("Invalid renderState.frontFace.");
}
if (!validateCullFace(this.cull.face)) {
throw new DeveloperError("Invalid renderState.cull.face.");
}
if (
this.scissorTest.rectangle.width < 0 ||
this.scissorTest.rectangle.height < 0
) {
throw new DeveloperError(
"renderState.scissorTest.rectangle.width and renderState.scissorTest.rectangle.height must be greater than or equal to zero.",
);
}
if (this.depthRange.near > this.depthRange.far) {
// WebGL specific - not an error in GL ES
throw new DeveloperError(
"renderState.depthRange.near can not be greater than renderState.depthRange.far.",
);
}
if (this.depthRange.near < 0) {
// Would be clamped by GL
throw new DeveloperError(
"renderState.depthRange.near must be greater than or equal to zero.",
);
}
if (this.depthRange.far > 1) {
// Would be clamped by GL
throw new DeveloperError(
"renderState.depthRange.far must be less than or equal to one.",
);
}
if (!validateDepthFunction(this.depthTest.func)) {
throw new DeveloperError("Invalid renderState.depthTest.func.");
}
if (
this.blending.color.red < 0.0 ||
this.blending.color.red > 1.0 ||
this.blending.color.green < 0.0 ||
this.blending.color.green > 1.0 ||
this.blending.color.blue < 0.0 ||
this.blending.color.blue > 1.0 ||
this.blending.color.alpha < 0.0 ||
this.blending.color.alpha > 1.0
) {
// Would be clamped by GL
throw new DeveloperError(
"renderState.blending.color components must be greater than or equal to zero and less than or equal to one.",
);
}
if (!validateBlendEquation(this.blending.equationRgb)) {
throw new DeveloperError("Invalid renderState.blending.equationRgb.");
}
if (!validateBlendEquation(this.blending.equationAlpha)) {
throw new DeveloperError("Invalid renderState.blending.equationAlpha.");
}
if (!validateBlendFunction(this.blending.functionSourceRgb)) {
throw new DeveloperError("Invalid renderState.blending.functionSourceRgb.");
}
if (!validateBlendFunction(this.blending.functionSourceAlpha)) {
throw new DeveloperError(
"Invalid renderState.blending.functionSourceAlpha.",
);
}
if (!validateBlendFunction(this.blending.functionDestinationRgb)) {
throw new DeveloperError(
"Invalid renderState.blending.functionDestinationRgb.",
);
}
if (!validateBlendFunction(this.blending.functionDestinationAlpha)) {
throw new DeveloperError(
"Invalid renderState.blending.functionDestinationAlpha.",
);
}
if (!validateStencilFunction(this.stencilTest.frontFunction)) {
throw new DeveloperError("Invalid renderState.stencilTest.frontFunction.");
}
if (!validateStencilFunction(this.stencilTest.backFunction)) {
throw new DeveloperError("Invalid renderState.stencilTest.backFunction.");
}
if (!validateStencilOperation(this.stencilTest.frontOperation.fail)) {
throw new DeveloperError(
"Invalid renderState.stencilTest.frontOperation.fail.",
);
}
if (!validateStencilOperation(this.stencilTest.frontOperation.zFail)) {
throw new DeveloperError(
"Invalid renderState.stencilTest.frontOperation.zFail.",
);
}
if (!validateStencilOperation(this.stencilTest.frontOperation.zPass)) {
throw new DeveloperError(
"Invalid renderState.stencilTest.frontOperation.zPass.",
);
}
if (!validateStencilOperation(this.stencilTest.backOperation.fail)) {
throw new DeveloperError(
"Invalid renderState.stencilTest.backOperation.fail.",
);
}
if (!validateStencilOperation(this.stencilTest.backOperation.zFail)) {
throw new DeveloperError(
"Invalid renderState.stencilTest.backOperation.zFail.",
);
}
if (!validateStencilOperation(this.stencilTest.backOperation.zPass)) {
throw new DeveloperError(
"Invalid renderState.stencilTest.backOperation.zPass.",
);
}
if (defined(this.viewport)) {
if (this.viewport.width < 0) {
throw new DeveloperError(
"renderState.viewport.width must be greater than or equal to zero.",
);
}
if (this.viewport.height < 0) {
throw new DeveloperError(
"renderState.viewport.height must be greater than or equal to zero.",
);
}
if (this.viewport.width > ContextLimits.maximumViewportWidth) {
throw new DeveloperError(
`renderState.viewport.width must be less than or equal to the maximum viewport width (${ContextLimits.maximumViewportWidth.toString()}). Check maximumViewportWidth.`,
);
}
if (this.viewport.height > ContextLimits.maximumViewportHeight) {
throw new DeveloperError(
`renderState.viewport.height must be less than or equal to the maximum viewport height (${ContextLimits.maximumViewportHeight.toString()}). Check maximumViewportHeight.`,
);
}
}
//>>includeEnd('debug');
this.id = 0;
this._applyFunctions = [];
}
let nextRenderStateId = 0;
let renderStateCache = {};
/**
* Validates and then finds or creates an immutable render state, which defines the pipeline
* state for a {@link DrawCommand} or {@link ClearCommand}. All inputs states are optional. Omitted states
* use the defaults shown in the example below.
*
* @param {object} [renderState] The states defining the render state as shown in the example below.
*
* @exception {RuntimeError} renderState.lineWidth is out of range.
* @exception {DeveloperError} Invalid renderState.frontFace.
* @exception {DeveloperError} Invalid renderState.cull.face.
* @exception {DeveloperError} scissorTest.rectangle.width and scissorTest.rectangle.height must be greater than or equal to zero.
* @exception {DeveloperError} renderState.depthRange.near can't be greater than renderState.depthRange.far.
* @exception {DeveloperError} renderState.depthRange.near must be greater than or equal to zero.
* @exception {DeveloperError} renderState.depthRange.far must be less than or equal to zero.
* @exception {DeveloperError} Invalid renderState.depthTest.func.
* @exception {DeveloperError} renderState.blending.color components must be greater than or equal to zero and less than or equal to one
* @exception {DeveloperError} Invalid renderState.blending.equationRgb.
* @exception {DeveloperError} Invalid renderState.blending.equationAlpha.
* @exception {DeveloperError} Invalid renderState.blending.functionSourceRgb.
* @exception {DeveloperError} Invalid renderState.blending.functionSourceAlpha.
* @exception {DeveloperError} Invalid renderState.blending.functionDestinationRgb.
* @exception {DeveloperError} Invalid renderState.blending.functionDestinationAlpha.
* @exception {DeveloperError} Invalid renderState.stencilTest.frontFunction.
* @exception {DeveloperError} Invalid renderState.stencilTest.backFunction.
* @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.fail.
* @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.zFail.
* @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.zPass.
* @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.fail.
* @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.zFail.
* @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.zPass.
* @exception {DeveloperError} renderState.viewport.width must be greater than or equal to zero.
* @exception {DeveloperError} renderState.viewport.width must be less than or equal to the maximum viewport width.
* @exception {DeveloperError} renderState.viewport.height must be greater than or equal to zero.
* @exception {DeveloperError} renderState.viewport.height must be less than or equal to the maximum viewport height.
*
*
* @example
* const defaults = {
* frontFace : WindingOrder.COUNTER_CLOCKWISE,
* cull : {
* enabled : false,
* face : CullFace.BACK
* },
* lineWidth : 1,
* polygonOffset : {
* enabled : false,
* factor : 0,
* units : 0
* },
* scissorTest : {
* enabled : false,
* rectangle : {
* x : 0,
* y : 0,
* width : 0,
* height : 0
* }
* },
* depthRange : {
* near : 0,
* far : 1
* },
* depthTest : {
* enabled : false,
* func : DepthFunction.LESS
* },
* colorMask : {
* red : true,
* green : true,
* blue : true,
* alpha : true
* },
* depthMask : true,
* stencilMask : ~0,
* blending : {
* enabled : false,
* color : {
* red : 0.0,
* green : 0.0,
* blue : 0.0,
* alpha : 0.0
* },
* equationRgb : BlendEquation.ADD,
* equationAlpha : BlendEquation.ADD,
* functionSourceRgb : BlendFunction.ONE,
* functionSourceAlpha : BlendFunction.ONE,
* functionDestinationRgb : BlendFunction.ZERO,
* functionDestinationAlpha : BlendFunction.ZERO
* },
* stencilTest : {
* enabled : false,
* frontFunction : StencilFunction.ALWAYS,
* backFunction : StencilFunction.ALWAYS,
* reference : 0,
* mask : ~0,
* frontOperation : {
* fail : StencilOperation.KEEP,
* zFail : StencilOperation.KEEP,
* zPass : StencilOperation.KEEP
* },
* backOperation : {
* fail : StencilOperation.KEEP,
* zFail : StencilOperation.KEEP,
* zPass : StencilOperation.KEEP
* }
* },
* sampleCoverage : {
* enabled : false,
* value : 1.0,
* invert : false
* }
* };
*
* const rs = RenderState.fromCache(defaults);
*
* @see DrawCommand
* @see ClearCommand
*
* @ignore
*/
RenderState.fromCache = function (renderState) {
const partialKey = JSON.stringify(renderState);
let cachedState = renderStateCache[partialKey];
if (defined(cachedState)) {
++cachedState.referenceCount;
return cachedState.state;
}
// Cache miss. Fully define render state and try again.
let states = new RenderState(renderState);
const fullKey = JSON.stringify(states);
cachedState = renderStateCache[fullKey];
if (!defined(cachedState)) {
states.id = nextRenderStateId++;
//>>includeStart('debug', pragmas.debug);
states = freezeRenderState(states);
//>>includeEnd('debug');
cachedState = {
referenceCount: 0,
state: states,
};
// Cache full render state. Multiple partially defined render states may map to this.
renderStateCache[fullKey] = cachedState;
}
++cachedState.referenceCount;
// Cache partial render state so we can skip validation on a cache hit for a partially defined render state
renderStateCache[partialKey] = {
referenceCount: 1,
state: cachedState.state,
};
return cachedState.state;
};
/**
* @ignore
*/
RenderState.removeFromCache = function (renderState) {
const states = new RenderState(renderState);
const fullKey = JSON.stringify(states);
const fullCachedState = renderStateCache[fullKey];
// decrement partial key reference count
const partialKey = JSON.stringify(renderState);
const cachedState = renderStateCache[partialKey];
if (defined(cachedState)) {
--cachedState.referenceCount;
if (cachedState.referenceCount === 0) {
// remove partial key
delete renderStateCache[partialKey];
// decrement full key reference count
if (defined(fullCachedState)) {
--fullCachedState.referenceCount;
}
}
}
// remove full key if reference count is zero
if (defined(fullCachedState) && fullCachedState.referenceCount === 0) {
delete renderStateCache[fullKey];
}
};
/**
* This function is for testing purposes only.
* @private
*/
RenderState.getCache = function () {
return renderStateCache;
};
/**
* This function is for testing purposes only.
* @private
*/
RenderState.clearCache = function () {
renderStateCache = {};
};
function enableOrDisable(gl, glEnum, enable) {
if (enable) {
gl.enable(glEnum);
} else {
gl.disable(glEnum);
}
}
function applyFrontFace(gl, renderState) {
gl.frontFace(renderState.frontFace);
}
function applyCull(gl, renderState) {
const cull = renderState.cull;
const enabled = cull.enabled;
enableOrDisable(gl, gl.CULL_FACE, enabled);
if (enabled) {
gl.cullFace(cull.face);
}
}
function applyLineWidth(gl, renderState) {
gl.lineWidth(renderState.lineWidth);
}
function applyPolygonOffset(gl, renderState) {
const polygonOffset = renderState.polygonOffset;
const enabled = polygonOffset.enabled;
enableOrDisable(gl, gl.POLYGON_OFFSET_FILL, enabled);
if (enabled) {
gl.polygonOffset(polygonOffset.factor, polygonOffset.units);
}
}
function applyScissorTest(gl, renderState, passState) {
const scissorTest = renderState.scissorTest;
const enabled = defined(passState.scissorTest)
? passState.scissorTest.enabled
: scissorTest.enabled;
enableOrDisable(gl, gl.SCISSOR_TEST, enabled);
if (enabled) {
const rectangle = defined(passState.scissorTest)
? passState.scissorTest.rectangle
: scissorTest.rectangle;
gl.scissor(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
}
function applyDepthRange(gl, renderState) {
const depthRange = renderState.depthRange;
gl.depthRange(depthRange.near, depthRange.far);
}
function applyDepthTest(gl, renderState) {
const depthTest = renderState.depthTest;
const enabled = depthTest.enabled;
enableOrDisable(gl, gl.DEPTH_TEST, enabled);
if (enabled) {
gl.depthFunc(depthTest.func);
}
}
function applyColorMask(gl, renderState) {
const colorMask = renderState.colorMask;
gl.colorMask(colorMask.red, colorMask.green, colorMask.blue, colorMask.alpha);
}
function applyDepthMask(gl, renderState) {
gl.depthMask(renderState.depthMask);
}
function applyStencilMask(gl, renderState) {
gl.stencilMask(renderState.stencilMask);
}
function applyBlendingColor(gl, color) {
gl.blendColor(color.red, color.green, color.blue, color.alpha);
}
function applyBlending(gl, renderState, passState) {
const blending = renderState.blending;
const enabled = defined(passState.blendingEnabled)
? passState.blendingEnabled
: blending.enabled;
enableOrDisable(gl, gl.BLEND, enabled);
if (enabled) {
applyBlendingColor(gl, blending.color);
gl.blendEquationSeparate(blending.equationRgb, blending.equationAlpha);
gl.blendFuncSeparate(
blending.functionSourceRgb,
blending.functionDestinationRgb,
blending.functionSourceAlpha,
blending.functionDestinationAlpha,
);
}
}
function applyStencilTest(gl, renderState) {
const stencilTest = renderState.stencilTest;
const enabled = stencilTest.enabled;
enableOrDisable(gl, gl.STENCIL_TEST, enabled);
if (enabled) {
const frontFunction = stencilTest.frontFunction;
const backFunction = stencilTest.backFunction;
const reference = stencilTest.reference;
const mask = stencilTest.mask;
// Section 6.8 of the WebGL spec requires the reference and masks to be the same for
// front- and back-face tests. This call prevents invalid operation errors when calling
// stencilFuncSeparate on Firefox. Perhaps they should delay validation to avoid requiring this.
gl.stencilFunc(frontFunction, reference, mask);
gl.stencilFuncSeparate(gl.BACK, backFunction, reference, mask);
gl.stencilFuncSeparate(gl.FRONT, frontFunction, reference, mask);
const frontOperation = stencilTest.frontOperation;
const frontOperationFail = frontOperation.fail;
const frontOperationZFail = frontOperation.zFail;
const frontOperationZPass = frontOperation.zPass;
gl.stencilOpSeparate(
gl.FRONT,
frontOperationFail,
frontOperationZFail,
frontOperationZPass,
);
const backOperation = stencilTest.backOperation;
const backOperationFail = backOperation.fail;
const backOperationZFail = backOperation.zFail;
const backOperationZPass = backOperation.zPass;
gl.stencilOpSeparate(
gl.BACK,
backOperationFail,
backOperationZFail,
backOperationZPass,
);
}
}
function applySampleCoverage(gl, renderState) {
const sampleCoverage = renderState.sampleCoverage;
const enabled = sampleCoverage.enabled;
enableOrDisable(gl, gl.SAMPLE_COVERAGE, enabled);
if (enabled) {
gl.sampleCoverage(sampleCoverage.value, sampleCoverage.invert);
}
}
const scratchViewport = new BoundingRectangle();
function applyViewport(gl, renderState, passState) {
let viewport = renderState.viewport ?? passState.viewport;
if (!defined(viewport)) {
viewport = scratchViewport;
viewport.width = passState.context.drawingBufferWidth;
viewport.height = passState.context.drawingBufferHeight;
}
passState.context.uniformState.viewport = viewport;
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
}
RenderState.apply = function (gl, renderState, passState) {
applyFrontFace(gl, renderState);
applyCull(gl, renderState);
applyLineWidth(gl, renderState);
applyPolygonOffset(gl, renderState);
applyDepthRange(gl, renderState);
applyDepthTest(gl, renderState);
applyColorMask(gl, renderState);
applyDepthMask(gl, renderState);
applyStencilMask(gl, renderState);
applyStencilTest(gl, renderState);
applySampleCoverage(gl, renderState);
applyScissorTest(gl, renderState, passState);
applyBlending(gl, renderState, passState);
applyViewport(gl, renderState, passState);
};
function createFuncs(previousState, nextState) {
const funcs = [];
if (previousState.frontFace !== nextState.frontFace) {
funcs.push(applyFrontFace);
}
if (
previousState.cull.enabled !== nextState.cull.enabled ||
previousState.cull.face !== nextState.cull.face
) {
funcs.push(applyCull);
}
if (previousState.lineWidth !== nextState.lineWidth) {
funcs.push(applyLineWidth);
}
if (
previousState.polygonOffset.enabled !== nextState.polygonOffset.enabled ||
previousState.polygonOffset.factor !== nextState.polygonOffset.factor ||
previousState.polygonOffset.units !== nextState.polygonOffset.units
) {
funcs.push(applyPolygonOffset);
}
if (
previousState.depthRange.near !== nextState.depthRange.near ||
previousState.depthRange.far !== nextState.depthRange.far
) {
funcs.push(applyDepthRange);
}
if (
previousState.depthTest.enabled !== nextState.depthTest.enabled ||
previousState.depthTest.func !== nextState.depthTest.func
) {
funcs.push(applyDepthTest);
}
if (
previousState.colorMask.red !== nextState.colorMask.red ||
previousState.colorMask.green !== nextState.colorMask.green ||
previousState.colorMask.blue !== nextState.colorMask.blue ||
previousState.colorMask.alpha !== nextState.colorMask.alpha
) {
funcs.push(applyColorMask);
}
if (previousState.depthMask !== nextState.depthMask) {
funcs.push(applyDepthMask);
}
if (previousState.stencilMask !== nextState.stencilMask) {
funcs.push(applyStencilMask);
}
if (
previousState.stencilTest.enabled !== nextState.stencilTest.enabled ||
previousState.stencilTest.frontFunction !==
nextState.stencilTest.frontFunction ||
previousState.stencilTest.backFunction !==
nextState.stencilTest.backFunction ||
previousState.stencilTest.reference !== nextState.stencilTest.reference ||
previousState.stencilTest.mask !== nextState.stencilTest.mask ||
previousState.stencilTest.frontOperation.fail !==
nextState.stencilTest.frontOperation.fail ||
previousState.stencilTest.frontOperation.zFail !==
nextState.stencilTest.frontOperation.zFail ||
previousState.stencilTest.backOperation.fail !==
nextState.stencilTest.backOperation.fail ||
previousState.stencilTest.backOperation.zFail !==
nextState.stencilTest.backOperation.zFail ||
previousState.stencilTest.backOperation.zPass !==
nextState.stencilTest.backOperation.zPass
) {
funcs.push(applyStencilTest);
}
if (
previousState.sampleCoverage.enabled !== nextState.sampleCoverage.enabled ||
previousState.sampleCoverage.value !== nextState.sampleCoverage.value ||
previousState.sampleCoverage.invert !== nextState.sampleCoverage.invert
) {
funcs.push(applySampleCoverage);
}
return funcs;
}
RenderState.partialApply = function (
gl,
previousRenderState,
renderState,
previousPassState,
passState,
clear,
) {
if (previousRenderState !== renderState) {
// When a new render state is applied, instead of making WebGL calls for all the states or first
// comparing the states one-by-one with the previous state (basically a linear search), we take
// advantage of RenderState's immutability, and store a dynamically populated sparse data structure
// containing functions that make the minimum number of WebGL calls when transitioning from one state
// to the other. In practice, this works well since state-to-state transitions generally only require a
// few WebGL calls, especially if commands are stored by state.
let funcs = renderState._applyFunctions[previousRenderState.id];
if (!defined(funcs)) {
funcs = createFuncs(previousRenderState, renderState);
renderState._applyFunctions[previousRenderState.id] = funcs;
}
const len = funcs.length;
for (let i = 0; i < len; ++i) {
funcs[i](gl, renderState);
}
}
const previousScissorTest = defined(previousPassState.scissorTest)
? previousPassState.scissorTest
: previousRenderState.scissorTest;
const scissorTest = defined(passState.scissorTest)
? passState.scissorTest
: renderState.scissorTest;
// Our scissor rectangle can get out of sync with the GL scissor rectangle on clears.
// Seems to be a problem only on ANGLE. See https://github.com/CesiumGS/cesium/issues/2994
if (previousScissorTest !== scissorTest || clear) {
applyScissorTest(gl, renderState, passState);
}
const previousBlendingEnabled = defined(previousPassState.blendingEnabled)
? previousPassState.blendingEnabled
: previousRenderState.blending.enabled;
const blendingEnabled = defined(passState.blendingEnabled)
? passState.blendingEnabled
: renderState.blending.enabled;
if (
previousBlendingEnabled !== blendingEnabled ||
(blendingEnabled && previousRenderState.blending !== renderState.blending)
) {
applyBlending(gl, renderState, passState);
}
if (
previousRenderState !== renderState ||
previousPassState !== passState ||
previousPassState.context !== passState.context
) {
applyViewport(gl, renderState, passState);
}
};
RenderState.getState = function (renderState) {
//>>includeStart('debug', pragmas.debug);
if (!defined(renderState)) {
throw new DeveloperError("renderState is required.");
}
//>>includeEnd('debug');
return {
frontFace: renderState.frontFace,
cull: {
enabled: renderState.cull.enabled,
face: renderState.cull.face,
},
lineWidth: renderState.lineWidth,
polygonOffset: {
enabled: renderState.polygonOffset.enabled,
factor: renderState.polygonOffset.factor,
units: renderState.polygonOffset.units,
},
scissorTest: {
enabled: renderState.scissorTest.enabled,
rectangle: BoundingRectangle.clone(renderState.scissorTest.rectangle),
},
depthRange: {
near: renderState.depthRange.near,
far: renderState.depthRange.far,
},
depthTest: {
enabled: renderState.depthTest.enabled,
func: renderState.depthTest.func,
},
colorMask: {
red: renderState.colorMask.red,
green: renderState.colorMask.green,
blue: renderState.colorMask.blue,
alpha: renderState.colorMask.alpha,
},
depthMask: renderState.depthMask,
stencilMask: renderState.stencilMask,
blending: {
enabled: renderState.blending.enabled,
color: Color.clone(renderState.blending.color),
equationRgb: renderState.blending.equationRgb,
equationAlpha: renderState.blending.equationAlpha,
functionSourceRgb: renderState.blending.functionSourceRgb,
functionSourceAlpha: renderState.blending.functionSourceAlpha,
functionDestinationRgb: renderState.blending.functionDestinationRgb,
functionDestinationAlpha: renderState.blending.functionDestinationAlpha,
},
stencilTest: {
enabled: renderState.stencilTest.enabled,
frontFunction: renderState.stencilTest.frontFunction,
backFunction: renderState.stencilTest.backFunction,
reference: renderState.stencilTest.reference,
mask: renderState.stencilTest.mask,
frontOperation: {
fail: renderState.stencilTest.frontOperation.fail,
zFail: renderState.stencilTest.frontOperation.zFail,
zPass: renderState.stencilTest.frontOperation.zPass,
},
backOperation: {
fail: renderState.stencilTest.backOperation.fail,
zFail: renderState.stencilTest.backOperation.zFail,
zPass: renderState.stencilTest.backOperation.zPass,
},
},
sampleCoverage: {
enabled: renderState.sampleCoverage.enabled,
value: renderState.sampleCoverage.value,
invert: renderState.sampleCoverage.invert,
},
viewport: defined(renderState.viewport)
? BoundingRectangle.clone(renderState.viewport)
: undefined,
};
};
export default RenderState;
+105
View File
@@ -0,0 +1,105 @@
import Check from "../Core/Check.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import ContextLimits from "./ContextLimits.js";
import RenderbufferFormat from "./RenderbufferFormat.js";
/**
* @private
*/
function Renderbuffer(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
//>>includeEnd('debug');
const context = options.context;
const gl = context._gl;
const maximumRenderbufferSize = ContextLimits.maximumRenderbufferSize;
const format = options.format ?? RenderbufferFormat.RGBA4;
const width = defined(options.width)
? options.width
: context.drawingBufferWidth;
const height = defined(options.height)
? options.height
: context.drawingBufferHeight;
const numSamples = options.numSamples ?? 1;
//>>includeStart('debug', pragmas.debug);
if (!RenderbufferFormat.validate(format)) {
throw new DeveloperError("Invalid format.");
}
Check.typeOf.number.greaterThan("width", width, 0);
if (width > maximumRenderbufferSize) {
throw new DeveloperError(
`Width must be less than or equal to the maximum renderbuffer size (${maximumRenderbufferSize}). Check maximumRenderbufferSize.`,
);
}
Check.typeOf.number.greaterThan("height", height, 0);
if (height > maximumRenderbufferSize) {
throw new DeveloperError(
`Height must be less than or equal to the maximum renderbuffer size (${maximumRenderbufferSize}). Check maximumRenderbufferSize.`,
);
}
//>>includeEnd('debug');
this._gl = gl;
this._format = format;
this._width = width;
this._height = height;
this._renderbuffer = this._gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this._renderbuffer);
if (numSamples > 1) {
gl.renderbufferStorageMultisample(
gl.RENDERBUFFER,
numSamples,
format,
width,
height,
);
} else {
gl.renderbufferStorage(gl.RENDERBUFFER, format, width, height);
}
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}
Object.defineProperties(Renderbuffer.prototype, {
format: {
get: function () {
return this._format;
},
},
width: {
get: function () {
return this._width;
},
},
height: {
get: function () {
return this._height;
},
},
});
Renderbuffer.prototype._getRenderbuffer = function () {
return this._renderbuffer;
};
Renderbuffer.prototype.isDestroyed = function () {
return false;
};
Renderbuffer.prototype.destroy = function () {
this._gl.deleteRenderbuffer(this._renderbuffer);
return destroyObject(this);
};
export default Renderbuffer;
+46
View File
@@ -0,0 +1,46 @@
import WebGLConstants from "../Core/WebGLConstants.js";
/**
* @enum {number}
* @private
*/
const RenderbufferFormat = {
RGBA4: WebGLConstants.RGBA4,
RGBA8: WebGLConstants.RGBA8,
RGBA16F: WebGLConstants.RGBA16F,
RGBA32F: WebGLConstants.RGBA32F,
RGB5_A1: WebGLConstants.RGB5_A1,
RGB565: WebGLConstants.RGB565,
DEPTH_COMPONENT16: WebGLConstants.DEPTH_COMPONENT16,
STENCIL_INDEX8: WebGLConstants.STENCIL_INDEX8,
DEPTH_STENCIL: WebGLConstants.DEPTH_STENCIL,
DEPTH24_STENCIL8: WebGLConstants.DEPTH24_STENCIL8,
validate: function (renderbufferFormat) {
return (
renderbufferFormat === RenderbufferFormat.RGBA4 ||
renderbufferFormat === RenderbufferFormat.RGBA8 ||
renderbufferFormat === RenderbufferFormat.RGBA16F ||
renderbufferFormat === RenderbufferFormat.RGBA32F ||
renderbufferFormat === RenderbufferFormat.RGB5_A1 ||
renderbufferFormat === RenderbufferFormat.RGB565 ||
renderbufferFormat === RenderbufferFormat.DEPTH_COMPONENT16 ||
renderbufferFormat === RenderbufferFormat.STENCIL_INDEX8 ||
renderbufferFormat === RenderbufferFormat.DEPTH_STENCIL ||
renderbufferFormat === RenderbufferFormat.DEPTH24_STENCIL8
);
},
getColorFormat: function (datatype) {
if (datatype === WebGLConstants.FLOAT) {
return RenderbufferFormat.RGBA32F;
} else if (datatype === WebGLConstants.HALF_FLOAT_OES) {
return RenderbufferFormat.RGBA16F;
}
return RenderbufferFormat.RGBA8;
},
};
Object.freeze(RenderbufferFormat);
export default RenderbufferFormat;
+116
View File
@@ -0,0 +1,116 @@
import Check from "../Core/Check.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import TextureMagnificationFilter from "./TextureMagnificationFilter.js";
import TextureMinificationFilter from "./TextureMinificationFilter.js";
import TextureWrap from "./TextureWrap.js";
/**
* @private
*/
function Sampler(options) {
options = options ?? Frozen.EMPTY_OBJECT;
const {
wrapR = TextureWrap.CLAMP_TO_EDGE,
wrapS = TextureWrap.CLAMP_TO_EDGE,
wrapT = TextureWrap.CLAMP_TO_EDGE,
minificationFilter = TextureMinificationFilter.LINEAR,
magnificationFilter = TextureMagnificationFilter.LINEAR,
maximumAnisotropy = 1.0,
} = options;
//>>includeStart('debug', pragmas.debug);
if (!TextureWrap.validate(wrapR)) {
throw new DeveloperError("Invalid sampler.wrapR.");
}
if (!TextureWrap.validate(wrapS)) {
throw new DeveloperError("Invalid sampler.wrapS.");
}
if (!TextureWrap.validate(wrapT)) {
throw new DeveloperError("Invalid sampler.wrapT.");
}
if (!TextureMinificationFilter.validate(minificationFilter)) {
throw new DeveloperError("Invalid sampler.minificationFilter.");
}
if (!TextureMagnificationFilter.validate(magnificationFilter)) {
throw new DeveloperError("Invalid sampler.magnificationFilter.");
}
Check.typeOf.number.greaterThanOrEquals(
"maximumAnisotropy",
maximumAnisotropy,
1.0,
);
//>>includeEnd('debug');
this._wrapR = wrapR;
this._wrapS = wrapS;
this._wrapT = wrapT;
this._minificationFilter = minificationFilter;
this._magnificationFilter = magnificationFilter;
this._maximumAnisotropy = maximumAnisotropy;
}
Object.defineProperties(Sampler.prototype, {
wrapR: {
get: function () {
return this._wrapR;
},
},
wrapS: {
get: function () {
return this._wrapS;
},
},
wrapT: {
get: function () {
return this._wrapT;
},
},
minificationFilter: {
get: function () {
return this._minificationFilter;
},
},
magnificationFilter: {
get: function () {
return this._magnificationFilter;
},
},
maximumAnisotropy: {
get: function () {
return this._maximumAnisotropy;
},
},
});
Sampler.equals = function (left, right) {
return (
left === right ||
(defined(left) &&
defined(right) &&
left._wrapR === right._wrapR &&
left._wrapS === right._wrapS &&
left._wrapT === right._wrapT &&
left._minificationFilter === right._minificationFilter &&
left._magnificationFilter === right._magnificationFilter &&
left._maximumAnisotropy === right._maximumAnisotropy)
);
};
Sampler.NEAREST = Object.freeze(
new Sampler({
wrapR: TextureWrap.CLAMP_TO_EDGE,
wrapS: TextureWrap.CLAMP_TO_EDGE,
wrapT: TextureWrap.CLAMP_TO_EDGE,
minificationFilter: TextureMinificationFilter.NEAREST,
magnificationFilter: TextureMagnificationFilter.NEAREST,
}),
);
export default Sampler;
+597
View File
@@ -0,0 +1,597 @@
import Check from "../Core/Check.js";
import clone from "../Core/clone.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import ShaderDestination from "./ShaderDestination.js";
import ShaderProgram from "./ShaderProgram.js";
import ShaderSource from "./ShaderSource.js";
import ShaderStruct from "./ShaderStruct.js";
import ShaderFunction from "./ShaderFunction.js";
import addAllToArray from "../Core/addAllToArray.js";
/**
* An object that makes it easier to build the text of a {@link ShaderProgram}. This tracks GLSL code for both the vertex shader and the fragment shader.
* <p>
* For vertex shaders, the shader builder tracks a list of <code>#defines</code>,
* a list of attributes, a list of uniforms, and a list of shader lines. It also
* tracks the location of each attribute so the caller can easily build the {@link VertexArray}
* </p>
* <p>
* For fragment shaders, the shader builder tracks a list of <code>#defines</code>,
* a list of attributes, a list of uniforms, and a list of shader lines.
* </p>
*
* @alias ShaderBuilder
* @constructor
*
* @example
* const shaderBuilder = new ShaderBuilder();
* shaderBuilder.addDefine("SOLID_COLOR", undefined, ShaderDestination.FRAGMENT);
* shaderBuilder.addUniform("vec3", "u_color", ShaderDestination.FRAGMENT);
* shaderBuilder.addVarying("vec3", v_color");
* // These locations can be used when creating the VertexArray
* const positionLocation = shaderBuilder.addPositionAttribute("vec3", "a_position");
* const colorLocation = shaderBuilder.addAttribute("vec3", "a_color");
* shaderBuilder.addVertexLines([
* "void main()",
* "{",
* " v_color = a_color;",
* " gl_Position = vec4(a_position, 1.0);",
* "}"
* ]);
* shaderBuilder.addFragmentLines([
* "void main()",
* "{",
* " #ifdef SOLID_COLOR",
* " out_FragColor = vec4(u_color, 1.0);",
* " #else",
* " out_FragColor = vec4(v_color, 1.0);",
* " #endif",
* "}"
* ]);
* const shaderProgram = shaderBuilder.build(context);
*
* @private
*/
function ShaderBuilder() {
// Some WebGL implementations require attribute 0 to always
// be active, so the position attribute is tracked separately
this._positionAttributeLine = undefined;
this._nextAttributeLocation = 1;
this._attributeLocations = {};
this._attributeLines = [];
// Dynamically-generated structs and functions
// these are dictionaries of id -> ShaderStruct or ShaderFunction respectively
this._structs = {};
this._functions = {};
this._vertexShaderParts = {
defineLines: [],
uniformLines: [],
shaderLines: [],
varyingLines: [],
// identifiers of structs/functions to include, listed in insertion order
structIds: [],
functionIds: [],
};
this._fragmentShaderParts = {
defineLines: [],
uniformLines: [],
shaderLines: [],
varyingLines: [],
// identifiers of structs/functions to include, listed in insertion order
structIds: [],
functionIds: [],
};
}
Object.defineProperties(ShaderBuilder.prototype, {
/**
* Get a dictionary of attribute names to the integer location in
* the vertex shader.
*
* @memberof ShaderBuilder.prototype
* @type {Object<string, number>}
* @readonly
* @private
*/
attributeLocations: {
get: function () {
return this._attributeLocations;
},
},
});
/**
* Add a <code>#define</code> macro to one or both of the shaders. These lines
* will appear at the top of the final shader source.
*
* @param {string} identifier An identifier for the macro. Identifiers must use uppercase letters with underscores to be consistent with Cesium's style guide.
* @param {string} [value] The value of the macro. If undefined, the define will not include a value. The value will be converted to GLSL code via <code>toString()</code>
* @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the define appears in the vertex shader, the fragment shader, or both.
*
* @example
* // creates the line "#define ENABLE_LIGHTING" in both shaders
* shaderBuilder.addDefine("ENABLE_LIGHTING");
* // creates the line "#define PI 3.141592" in the fragment shader
* shaderBuilder.addDefine("PI", 3.141593, ShaderDestination.FRAGMENT);
*/
ShaderBuilder.prototype.addDefine = function (identifier, value, destination) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("identifier", identifier);
//>>includeEnd('debug');
destination = destination ?? ShaderDestination.BOTH;
// The ShaderSource created in build() will add the #define part
let line = identifier;
if (defined(value)) {
line += ` ${value.toString()}`;
}
if (ShaderDestination.includesVertexShader(destination)) {
this._vertexShaderParts.defineLines.push(line);
}
if (ShaderDestination.includesFragmentShader(destination)) {
this._fragmentShaderParts.defineLines.push(line);
}
};
/**
* Add a new dynamically-generated struct to the shader
* @param {string} structId A unique ID to identify this struct in {@link ShaderBuilder#addStructField}
* @param {string} structName The name of the struct as it will appear in the shader.
* @param {ShaderDestination} destination Whether the struct will appear in the vertex shader, the fragment shader, or both.
*
* @example
* // generates the following struct in the fragment shader
* // struct TestStruct
* // {
* // };
* shaderBuilder.addStruct("testStructId", "TestStruct", ShaderDestination.FRAGMENT);
*/
ShaderBuilder.prototype.addStruct = function (
structId,
structName,
destination,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("structId", structId);
Check.typeOf.string("structName", structName);
Check.typeOf.number("destination", destination);
//>>includeEnd('debug');
this._structs[structId] = new ShaderStruct(structName);
if (ShaderDestination.includesVertexShader(destination)) {
this._vertexShaderParts.structIds.push(structId);
}
if (ShaderDestination.includesFragmentShader(destination)) {
this._fragmentShaderParts.structIds.push(structId);
}
};
/**
* Add a field to a dynamically-generated struct.
* @param {string} structId The ID of the struct. This must be created first with {@link ShaderBuilder#addStruct}
* @param {string} type The GLSL type of the field
* @param {string} identifier The identifier of the field.
*
* @example
* // generates the following struct in the fragment shader
* // struct TestStruct
* // {
* // float minimum;
* // float maximum;
* // };
* shaderBuilder.addStruct("testStructId", "TestStruct", ShaderDestination.FRAGMENT);
* shaderBuilder.addStructField("testStructId", "float", "maximum");
* shaderBuilder.addStructField("testStructId", "float", "minimum");
*/
ShaderBuilder.prototype.addStructField = function (structId, type, identifier) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("structId", structId);
Check.typeOf.string("type", type);
Check.typeOf.string("identifier", identifier);
//>>includeEnd('debug');
this._structs[structId].addField(type, identifier);
};
/**
* Add a new dynamically-generated function to the shader.
* @param {string} functionName The name of the function. This will be used to identify the function in {@link ShaderBuilder#addFunctionLines}.
* @param {string} signature The full signature of the function as it will appear in the shader. Do not include the curly braces.
* @param {ShaderDestination} destination Whether the struct will appear in the vertex shader, the fragment shader, or both.
* @example
* // generates the following function in the vertex shader
* // vec3 testFunction(float parameter)
* // {
* // }
* shaderBuilder.addStruct("testFunction", "vec3 testFunction(float parameter)", ShaderDestination.VERTEX);
*/
ShaderBuilder.prototype.addFunction = function (
functionName,
signature,
destination,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("functionName", functionName);
Check.typeOf.string("signature", signature);
Check.typeOf.number("destination", destination);
//>>includeEnd('debug');
this._functions[functionName] = new ShaderFunction(signature);
if (ShaderDestination.includesVertexShader(destination)) {
this._vertexShaderParts.functionIds.push(functionName);
}
if (ShaderDestination.includesFragmentShader(destination)) {
this._fragmentShaderParts.functionIds.push(functionName);
}
};
/**
* Add lines to a dynamically-generated function
* @param {string} functionName The name of the function. This must be created beforehand using {@link ShaderBuilder#addFunction}
* @param {string|string[]} lines One or more lines of GLSL code to add to the function body. Do not include any preceding or ending whitespace, but do include the semicolon for each line.
*
* @example
* // generates the following function in the vertex shader
* // vec3 testFunction(float parameter)
* // {
* // float signed = 2.0 * parameter - 1.0;
* // return vec3(signed, 0.0, 0.0);
* // }
* shaderBuilder.addStruct("testFunction", "vec3 testFunction(float parameter)", ShaderDestination.VERTEX);
* shaderBuilder.addFunctionLines("testFunction", [
* "float signed = 2.0 * parameter - 1.0;",
* "return vec3(parameter);"
* ]);
*/
ShaderBuilder.prototype.addFunctionLines = function (functionName, lines) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("functionName", functionName);
if (typeof lines !== "string" && !Array.isArray(lines)) {
throw new DeveloperError(
`Expected lines to be a string or an array of strings, actual value was ${lines}`,
);
}
//>>includeEnd('debug');
this._functions[functionName].addLines(lines);
};
/**
* Add a uniform declaration to one or both of the shaders. These lines
* will appear grouped near the top of the final shader source.
*
* @param {string} type The GLSL type of the uniform.
* @param {string} identifier An identifier for the uniform. Identifiers must begin with <code>u_</code> to be consistent with Cesium's style guide.
* @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the uniform appears in the vertex shader, the fragment shader, or both.
*
* @example
* // creates the line "uniform vec3 u_resolution;"
* shaderBuilder.addUniform("vec3", "u_resolution", ShaderDestination.FRAGMENT);
* // creates the line "uniform float u_time;" in both shaders
* shaderBuilder.addUniform("float", "u_time", ShaderDestination.BOTH);
*/
ShaderBuilder.prototype.addUniform = function (type, identifier, destination) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("type", type);
Check.typeOf.string("identifier", identifier);
//>>includeEnd('debug');
destination = destination ?? ShaderDestination.BOTH;
const line = `uniform ${type} ${identifier};`;
if (ShaderDestination.includesVertexShader(destination)) {
this._vertexShaderParts.uniformLines.push(line);
}
if (ShaderDestination.includesFragmentShader(destination)) {
this._fragmentShaderParts.uniformLines.push(line);
}
};
/**
* Add a position attribute declaration to the vertex shader. These lines
* will appear grouped near the top of the final shader source.
* <p>
* Some WebGL implementations require attribute 0 to be enabled, so this is
* reserved for the position attribute. For all other attributes, see
* {@link ShaderBuilder#addAttribute}
* </p>
*
* @param {string} type The GLSL type of the attribute
* @param {string} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide.
* @return {number} The integer location of the attribute. This location can be used when creating attributes for a {@link VertexArray}. This will always be 0.
*
* @example
* // creates the line "in vec3 a_position;"
* shaderBuilder.setPositionAttribute("vec3", "a_position");
*/
ShaderBuilder.prototype.setPositionAttribute = function (type, identifier) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("type", type);
Check.typeOf.string("identifier", identifier);
if (defined(this._positionAttributeLine)) {
throw new DeveloperError(
"setPositionAttribute() must be called exactly once for the attribute used for gl_Position. For other attributes, use addAttribute()",
);
}
//>>includeEnd('debug');
this._positionAttributeLine = `in ${type} ${identifier};`;
// Some WebGL implementations require attribute 0 to always be active, so
// this builder assumes the position will always go in location 0
this._attributeLocations[identifier] = 0;
return 0;
};
/**
* Add an attribute declaration to the vertex shader. These lines
* will appear grouped near the top of the final shader source.
* <p>
* Some WebGL implementations require attribute 0 to be enabled, so this is
* reserved for the position attribute. See {@link ShaderBuilder#setPositionAttribute}
* </p>
*
* @param {string} type The GLSL type of the attribute
* @param {string} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide.
* @return {number} The integer location of the attribute. This location can be used when creating attributes for a {@link VertexArray}
*
* @example
* // creates the line "in vec2 a_texCoord0;" in the vertex shader
* shaderBuilder.addAttribute("vec2", "a_texCoord0");
*/
ShaderBuilder.prototype.addAttribute = function (type, identifier) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("type", type);
Check.typeOf.string("identifier", identifier);
//>>includeEnd('debug');
const line = `in ${type} ${identifier};`;
this._attributeLines.push(line);
const location = this._nextAttributeLocation;
this._attributeLocations[identifier] = location;
// Most attributes only require a single attribute location, but matrices
// require more.
this._nextAttributeLocation += getAttributeLocationCount(type);
return location;
};
/**
* Add a varying declaration to both the vertex and fragment shaders.
*
* @param {string} type The GLSL type of the varying
* @param {string} identifier An identifier for the varying. Identifiers must begin with <code>v_</code> to be consistent with Cesium's style guide.
* @param {string} [qualifier] A qualifier for the varying, such as <code>flat</code>.
*
* @example
* // creates the line "in vec3 v_color;" in the vertex shader
* // creates the line "out vec3 v_color;" in the fragment shader
* shaderBuilder.addVarying("vec3", "v_color");
*/
ShaderBuilder.prototype.addVarying = function (type, identifier, qualifier) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("type", type);
Check.typeOf.string("identifier", identifier);
//>>includeEnd('debug');
qualifier = defined(qualifier) ? `${qualifier} ` : "";
const line = `${type} ${identifier};`;
this._vertexShaderParts.varyingLines.push(`${qualifier}out ${line}`);
this._fragmentShaderParts.varyingLines.push(`${qualifier}in ${line}`);
};
/**
* Appends lines of GLSL code to the vertex shader
*
* @param {string|string[]} lines One or more lines to add to the end of the vertex shader source
*
* @example
* shaderBuilder.addVertexLines([
* "void main()",
* "{",
* " v_color = a_color;",
* " gl_Position = vec4(a_position, 1.0);",
* "}"
* ]);
*/
ShaderBuilder.prototype.addVertexLines = function (lines) {
//>>includeStart('debug', pragmas.debug);
if (typeof lines !== "string" && !Array.isArray(lines)) {
throw new DeveloperError(
`Expected lines to be a string or an array of strings, actual value was ${lines}`,
);
}
//>>includeEnd('debug');
const vertexLines = this._vertexShaderParts.shaderLines;
if (Array.isArray(lines)) {
addAllToArray(vertexLines, lines);
} else {
// Single string case
vertexLines.push(lines);
}
};
/**
* Appends lines of GLSL code to the fragment shader
*
* @param {string[]} lines The lines to add to the end of the fragment shader source
*
* @example
* shaderBuilder.addFragmentLines([
* "void main()",
* "{",
* " #ifdef SOLID_COLOR",
* " out_FragColor = vec4(u_color, 1.0);",
* " #else",
* " out_FragColor = vec4(v_color, 1.0);",
* " #endif",
* "}"
* ]);
*/
ShaderBuilder.prototype.addFragmentLines = function (lines) {
//>>includeStart('debug', pragmas.debug);
if (typeof lines !== "string" && !Array.isArray(lines)) {
throw new DeveloperError(
`Expected lines to be a string or an array of strings, actual value was ${lines}`,
);
}
//>>includeEnd('debug');
const fragmentLines = this._fragmentShaderParts.shaderLines;
if (Array.isArray(lines)) {
addAllToArray(fragmentLines, lines);
} else {
// Single string case
fragmentLines.push(lines);
}
};
/**
* Builds the {@link ShaderProgram} from the pieces added by the other methods.
* Call this one time at the end of modifying the shader through the other
* methods in this class.
*
* @param {Context} context The context to use for creating the shader.
* @return {ShaderProgram} A shader program to use for rendering.
*
* @example
* const shaderProgram = shaderBuilder.buildShaderProgram(context);
*/
ShaderBuilder.prototype.buildShaderProgram = function (context) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("context", context);
//>>includeEnd('debug');
const positionAttribute = defined(this._positionAttributeLine)
? [this._positionAttributeLine]
: [];
const structLines = generateStructLines(this);
const functionLines = generateFunctionLines(this);
// Lines are joined here so the ShaderSource
// generates a single #line 0 directive
const vertexLines = positionAttribute
.concat(
this._attributeLines,
this._vertexShaderParts.uniformLines,
this._vertexShaderParts.varyingLines,
structLines.vertexLines,
functionLines.vertexLines,
this._vertexShaderParts.shaderLines,
)
.join("\n");
const vertexShaderSource = new ShaderSource({
defines: this._vertexShaderParts.defineLines,
sources: [vertexLines],
});
const fragmentLines = this._fragmentShaderParts.uniformLines
.concat(
this._fragmentShaderParts.varyingLines,
structLines.fragmentLines,
functionLines.fragmentLines,
this._fragmentShaderParts.shaderLines,
)
.join("\n");
const fragmentShaderSource = new ShaderSource({
defines: this._fragmentShaderParts.defineLines,
sources: [fragmentLines],
});
return ShaderProgram.fromCache({
context: context,
vertexShaderSource: vertexShaderSource,
fragmentShaderSource: fragmentShaderSource,
attributeLocations: this._attributeLocations,
});
};
ShaderBuilder.prototype.clone = function () {
return clone(this, true);
};
function generateStructLines(shaderBuilder) {
const vertexLines = [];
const fragmentLines = [];
let i;
let structIds = shaderBuilder._vertexShaderParts.structIds;
let structId;
let struct;
let structLines;
for (i = 0; i < structIds.length; i++) {
structId = structIds[i];
struct = shaderBuilder._structs[structId];
structLines = struct.generateGlslLines();
addAllToArray(vertexLines, structLines);
}
structIds = shaderBuilder._fragmentShaderParts.structIds;
for (i = 0; i < structIds.length; i++) {
structId = structIds[i];
struct = shaderBuilder._structs[structId];
structLines = struct.generateGlslLines();
addAllToArray(fragmentLines, structLines);
}
return {
vertexLines: vertexLines,
fragmentLines: fragmentLines,
};
}
function getAttributeLocationCount(glslType) {
switch (glslType) {
case "mat2":
return 2;
case "mat3":
return 3;
case "mat4":
return 4;
default:
return 1;
}
}
function generateFunctionLines(shaderBuilder) {
const vertexLines = [];
const fragmentLines = [];
let i;
let functionIds = shaderBuilder._vertexShaderParts.functionIds;
let functionId;
let func;
let functionLines;
for (i = 0; i < functionIds.length; i++) {
functionId = functionIds[i];
func = shaderBuilder._functions[functionId];
functionLines = func.generateGlslLines();
addAllToArray(vertexLines, functionLines);
}
functionIds = shaderBuilder._fragmentShaderParts.functionIds;
for (i = 0; i < functionIds.length; i++) {
functionId = functionIds[i];
func = shaderBuilder._functions[functionId];
functionLines = func.generateGlslLines();
addAllToArray(fragmentLines, functionLines);
}
return {
vertexLines: vertexLines,
fragmentLines: fragmentLines,
};
}
export default ShaderBuilder;
+287
View File
@@ -0,0 +1,287 @@
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import ShaderProgram from "./ShaderProgram.js";
import ShaderSource from "./ShaderSource.js";
/**
* @private
*/
function ShaderCache(context) {
this._context = context;
this._shaders = {};
this._numberOfShaders = 0;
this._shadersToRelease = {};
}
Object.defineProperties(ShaderCache.prototype, {
numberOfShaders: {
get: function () {
return this._numberOfShaders;
},
},
});
/**
* Returns a shader program from the cache, or creates and caches a new shader program,
* given the GLSL vertex and fragment shader source and attribute locations.
* <p>
* The difference between this and {@link ShaderCache#getShaderProgram}, is this is used to
* replace an existing reference to a shader program, which is passed as the first argument.
* </p>
*
* @param {object} options Object with the following properties:
* @param {ShaderProgram} [options.shaderProgram] The shader program that is being reassigned.
* @param {string|ShaderSource} options.vertexShaderSource The GLSL source for the vertex shader.
* @param {string|ShaderSource} options.fragmentShaderSource The GLSL source for the fragment shader.
* @param {object} options.attributeLocations Indices for the attribute inputs to the vertex shader.
* @returns {ShaderProgram} The cached or newly created shader program.
*
*
* @example
* this._shaderProgram = context.shaderCache.replaceShaderProgram({
* shaderProgram : this._shaderProgram,
* vertexShaderSource : vs,
* fragmentShaderSource : fs,
* attributeLocations : attributeLocations
* });
*
* @see ShaderCache#getShaderProgram
*/
ShaderCache.prototype.replaceShaderProgram = function (options) {
if (defined(options.shaderProgram)) {
options.shaderProgram.destroy();
}
return this.getShaderProgram(options);
};
function toSortedJson(dictionary) {
const sortedKeys = Object.keys(dictionary).sort();
return JSON.stringify(dictionary, sortedKeys);
}
/**
* Returns a shader program from the cache, or creates and caches a new shader program,
* given the GLSL vertex and fragment shader source and attribute locations.
*
* @param {object} options Object with the following properties:
* @param {string|ShaderSource} options.vertexShaderSource The GLSL source for the vertex shader.
* @param {string|ShaderSource} options.fragmentShaderSource The GLSL source for the fragment shader.
* @param {object} options.attributeLocations Indices for the attribute inputs to the vertex shader.
*
* @returns {ShaderProgram} The cached or newly created shader program.
*/
ShaderCache.prototype.getShaderProgram = function (options) {
// convert shaders which are provided as strings into ShaderSource objects
// because ShaderSource handles all the automatic including of built-in functions, etc.
let vertexShaderSource = options.vertexShaderSource;
let fragmentShaderSource = options.fragmentShaderSource;
const attributeLocations = options.attributeLocations;
if (typeof vertexShaderSource === "string") {
vertexShaderSource = new ShaderSource({
sources: [vertexShaderSource],
});
}
if (typeof fragmentShaderSource === "string") {
fragmentShaderSource = new ShaderSource({
sources: [fragmentShaderSource],
});
}
// Since ShaderSource.createCombinedXxxShader() can be expensive, use a
// simpler key for caching. This way, the function does not have to be called
// for each cache lookup.
const vertexShaderKey = vertexShaderSource.getCacheKey();
const fragmentShaderKey = fragmentShaderSource.getCacheKey();
// Sort the keys in the JSON to ensure a consistent order
const attributeLocationKey = defined(attributeLocations)
? toSortedJson(attributeLocations)
: "";
const keyword = `${vertexShaderKey}:${fragmentShaderKey}:${attributeLocationKey}`;
let cachedShader;
if (defined(this._shaders[keyword])) {
cachedShader = this._shaders[keyword];
// No longer want to release this if it was previously released.
delete this._shadersToRelease[keyword];
} else {
const context = this._context;
const vertexShaderText =
vertexShaderSource.createCombinedVertexShader(context);
const fragmentShaderText =
fragmentShaderSource.createCombinedFragmentShader(context);
const shaderProgram = new ShaderProgram({
gl: context._gl,
logShaderCompilation: context.logShaderCompilation,
debugShaders: context.debugShaders,
vertexShaderSource: vertexShaderSource,
vertexShaderText: vertexShaderText,
fragmentShaderSource: fragmentShaderSource,
fragmentShaderText: fragmentShaderText,
attributeLocations: attributeLocations,
});
cachedShader = {
cache: this,
shaderProgram: shaderProgram,
keyword: keyword,
derivedKeywords: [],
count: 0,
};
// A shader can't be in more than one cache.
shaderProgram._cachedShader = cachedShader;
this._shaders[keyword] = cachedShader;
++this._numberOfShaders;
}
++cachedShader.count;
return cachedShader.shaderProgram;
};
ShaderCache.prototype.replaceDerivedShaderProgram = function (
shaderProgram,
keyword,
options,
) {
const cachedShader = shaderProgram._cachedShader;
const derivedKeyword = keyword + cachedShader.keyword;
const cachedDerivedShader = this._shaders[derivedKeyword];
if (defined(cachedDerivedShader)) {
destroyShader(this, cachedDerivedShader);
const index = cachedShader.derivedKeywords.indexOf(keyword);
if (index > -1) {
cachedShader.derivedKeywords.splice(index, 1);
}
}
return this.createDerivedShaderProgram(shaderProgram, keyword, options);
};
ShaderCache.prototype.getDerivedShaderProgram = function (
shaderProgram,
keyword,
) {
const cachedShader = shaderProgram._cachedShader;
const derivedKeyword = keyword + cachedShader.keyword;
const cachedDerivedShader = this._shaders[derivedKeyword];
if (!defined(cachedDerivedShader)) {
return undefined;
}
return cachedDerivedShader.shaderProgram;
};
ShaderCache.prototype.createDerivedShaderProgram = function (
shaderProgram,
keyword,
options,
) {
const cachedShader = shaderProgram._cachedShader;
const derivedKeyword = keyword + cachedShader.keyword;
let vertexShaderSource = options.vertexShaderSource;
let fragmentShaderSource = options.fragmentShaderSource;
const attributeLocations = options.attributeLocations;
if (typeof vertexShaderSource === "string") {
vertexShaderSource = new ShaderSource({
sources: [vertexShaderSource],
});
}
if (typeof fragmentShaderSource === "string") {
fragmentShaderSource = new ShaderSource({
sources: [fragmentShaderSource],
});
}
const context = this._context;
const vertexShaderText =
vertexShaderSource.createCombinedVertexShader(context);
const fragmentShaderText =
fragmentShaderSource.createCombinedFragmentShader(context);
const derivedShaderProgram = new ShaderProgram({
gl: context._gl,
logShaderCompilation: context.logShaderCompilation,
debugShaders: context.debugShaders,
vertexShaderSource: vertexShaderSource,
vertexShaderText: vertexShaderText,
fragmentShaderSource: fragmentShaderSource,
fragmentShaderText: fragmentShaderText,
attributeLocations: attributeLocations,
});
const derivedCachedShader = {
cache: this,
shaderProgram: derivedShaderProgram,
keyword: derivedKeyword,
derivedKeywords: [],
count: 0,
};
cachedShader.derivedKeywords.push(keyword);
derivedShaderProgram._cachedShader = derivedCachedShader;
this._shaders[derivedKeyword] = derivedCachedShader;
return derivedShaderProgram;
};
function destroyShader(cache, cachedShader) {
const derivedKeywords = cachedShader.derivedKeywords;
const length = derivedKeywords.length;
for (let i = 0; i < length; ++i) {
const keyword = derivedKeywords[i] + cachedShader.keyword;
const derivedCachedShader = cache._shaders[keyword];
destroyShader(cache, derivedCachedShader);
}
delete cache._shaders[cachedShader.keyword];
cachedShader.shaderProgram.finalDestroy();
}
ShaderCache.prototype.destroyReleasedShaderPrograms = function () {
const shadersToRelease = this._shadersToRelease;
for (const keyword in shadersToRelease) {
if (shadersToRelease.hasOwnProperty(keyword)) {
const cachedShader = shadersToRelease[keyword];
destroyShader(this, cachedShader);
--this._numberOfShaders;
}
}
this._shadersToRelease = {};
};
ShaderCache.prototype.releaseShaderProgram = function (shaderProgram) {
if (defined(shaderProgram)) {
const cachedShader = shaderProgram._cachedShader;
if (cachedShader && --cachedShader.count === 0) {
this._shadersToRelease[cachedShader.keyword] = cachedShader;
}
}
};
ShaderCache.prototype.isDestroyed = function () {
return false;
};
ShaderCache.prototype.destroy = function () {
const shaders = this._shaders;
for (const keyword in shaders) {
if (shaders.hasOwnProperty(keyword)) {
shaders[keyword].shaderProgram.finalDestroy();
}
}
return destroyObject(this);
};
export default ShaderCache;
+95
View File
@@ -0,0 +1,95 @@
import Check from "../Core/Check.js";
import DeveloperError from "../Core/DeveloperError.js";
/**
* A bit flag describing whether a variable should be added to the
* vertex shader, the fragment shader, or both (or none).
*
* @enum {number}
* @private
*/
const ShaderDestination = {
NONE: 0,
VERTEX: 1,
FRAGMENT: 2,
BOTH: 3,
};
/**
* Check if a variable should be included in the vertex shader.
*
* @param {ShaderDestination} destination The ShaderDestination to check
* @return {boolean} <code>true</code> if the variable appears in the vertex shader, or <code>false</code> otherwise
* @private
*/
ShaderDestination.includesVertexShader = function (destination) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("destination", destination);
//>>includeEnd('debug');
return (destination & ShaderDestination.VERTEX) !== 0;
};
/**
* Check if a variable should be included in the vertex shader.
*
* @param {ShaderDestination} destination The ShaderDestination to check
* @return {boolean} <code>true</code> if the variable appears in the vertex shader, or <code>false</code> otherwise
* @private
*/
ShaderDestination.includesFragmentShader = function (destination) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("destination", destination);
//>>includeEnd('debug');
//
return (destination & ShaderDestination.FRAGMENT) !== 0;
};
/**
* Compute the union of multiple ShaderDestinations (e.g., VERTEX | FRAGMENT yields BOTH)
* @param {...ShaderDestination} destinations
* @returns {ShaderDestination} The union of the provided destinations
* @private
*/
ShaderDestination.union = function (...destinations) {
//>>includeStart('debug', pragmas.debug);
if (destinations.length === 0) {
throw new DeveloperError(
"ShaderDestination.union requires at least one destination.",
);
}
//>>includeEnd('debug');
let result = 0;
for (let i = 0; i < destinations.length; i++) {
result |= destinations[i];
}
return result;
};
/**
* Compute the intersection of multiple ShaderDestinations (e.g., VERTEX & FRAGMENT yields NONE)
* @param {...ShaderDestination} destinations
* @returns {ShaderDestination} The intersection of the provided destinations
* @private
*/
ShaderDestination.intersection = function (...destinations) {
//>>includeStart('debug', pragmas.debug);
if (destinations.length === 0) {
throw new DeveloperError(
"ShaderDestination.intersection requires at least one destination.",
);
}
//>>includeEnd('debug');
let result = destinations[0];
for (let i = 1; i < destinations.length; i++) {
result &= destinations[i];
}
return result;
};
Object.freeze(ShaderDestination);
export default ShaderDestination;
+66
View File
@@ -0,0 +1,66 @@
import DeveloperError from "../Core/DeveloperError.js";
/**
* A utility for dynamically-generating a GLSL function
*
* @alias ShaderFunction
* @constructor
*
* @see {@link ShaderBuilder}
* @param {string} signature The full signature of the function as it will appear in the shader. Do not include the curly braces.
* @example
* // generate the following function
* //
* // void assignVaryings(vec3 position)
* // {
* // v_positionEC = (czm_modelView * vec4(a_position, 1.0)).xyz;
* // v_texCoord = a_texCoord;
* // }
* const signature = "void assignVaryings(vec3 position)";
* const func = new ShaderFunction(signature);
* func.addLine("v_positionEC = (czm_modelView * vec4(a_position, 1.0)).xyz;");
* func.addLine("v_texCoord = a_texCoord;");
* const generatedLines = func.generateGlslLines();
*
* @private
*/
function ShaderFunction(signature) {
this.signature = signature;
this.body = [];
}
/**
* Adds one or more lines to the body of the function
* @param {string|string[]} lines One or more lines of GLSL code to add to the function body. Do not include any preceding or ending whitespace, but do include the semicolon for each line.
*/
ShaderFunction.prototype.addLines = function (lines) {
//>>includeStart('debug', pragmas.debug);
if (typeof lines !== "string" && !Array.isArray(lines)) {
throw new DeveloperError(
`Expected lines to be a string or an array of strings, actual value was ${lines}`,
);
}
//>>includeEnd('debug');
const body = this.body;
// Indent the body of the function by 4 spaces
if (Array.isArray(lines)) {
const length = lines.length;
for (let i = 0; i < length; i++) {
body.push(` ${lines[i]}`);
}
} else {
// Single string case
body.push(` ${lines}`);
}
};
/**
* Generate lines of GLSL code for use with {@link ShaderBuilder}
* @return {string[]}
*/
ShaderFunction.prototype.generateGlslLines = function () {
return [].concat(this.signature, "{", this.body, "}");
};
export default ShaderFunction;
+619
View File
@@ -0,0 +1,619 @@
import Check from "../Core/Check.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import RuntimeError from "../Core/RuntimeError.js";
import AutomaticUniforms from "./AutomaticUniforms.js";
import ContextLimits from "./ContextLimits.js";
import createUniform from "./createUniform.js";
import createUniformArray from "./createUniformArray.js";
let nextShaderProgramId = 0;
/**
* @private
*/
function ShaderProgram(options) {
let vertexShaderText = options.vertexShaderText;
let fragmentShaderText = options.fragmentShaderText;
if (typeof spector !== "undefined") {
// The #line statements common in Cesium shaders interfere with the ability of the
// SpectorJS to show errors on the correct line. So remove them when SpectorJS
// is active.
vertexShaderText = vertexShaderText.replace(/^#line/gm, "//#line");
fragmentShaderText = fragmentShaderText.replace(/^#line/gm, "//#line");
}
const modifiedFS = handleUniformPrecisionMismatches(
vertexShaderText,
fragmentShaderText,
);
this._gl = options.gl;
this._logShaderCompilation = options.logShaderCompilation;
this._debugShaders = options.debugShaders;
this._attributeLocations = options.attributeLocations;
this._program = undefined;
this._numberOfVertexAttributes = undefined;
this._vertexAttributes = undefined;
this._uniformsByName = undefined;
this._uniforms = undefined;
this._automaticUniforms = undefined;
this._manualUniforms = undefined;
this._duplicateUniformNames = modifiedFS.duplicateUniformNames;
this._cachedShader = undefined; // Used by ShaderCache
/**
* @private
*/
this.maximumTextureUnitIndex = undefined;
this._vertexShaderSource = options.vertexShaderSource;
this._vertexShaderText = options.vertexShaderText;
this._fragmentShaderSource = options.fragmentShaderSource;
this._fragmentShaderText = modifiedFS.fragmentShaderText;
/**
* @private
*/
this.id = nextShaderProgramId++;
}
ShaderProgram.fromCache = function (options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
//>>includeEnd('debug');
return options.context.shaderCache.getShaderProgram(options);
};
ShaderProgram.replaceCache = function (options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
//>>includeEnd('debug');
return options.context.shaderCache.replaceShaderProgram(options);
};
Object.defineProperties(ShaderProgram.prototype, {
/**
* GLSL source for the shader program's vertex shader.
* @memberof ShaderProgram.prototype
*
* @type {ShaderSource}
* @readonly
*/
vertexShaderSource: {
get: function () {
return this._vertexShaderSource;
},
},
/**
* GLSL source for the shader program's fragment shader.
* @memberof ShaderProgram.prototype
*
* @type {ShaderSource}
* @readonly
*/
fragmentShaderSource: {
get: function () {
return this._fragmentShaderSource;
},
},
vertexAttributes: {
get: function () {
initialize(this);
return this._vertexAttributes;
},
},
numberOfVertexAttributes: {
get: function () {
initialize(this);
return this._numberOfVertexAttributes;
},
},
allUniforms: {
get: function () {
initialize(this);
return this._uniformsByName;
},
},
});
function extractUniforms(shaderText) {
const uniformNames = [];
const uniformLines = shaderText.match(/uniform.*?(?![^{]*})(?=[=\[;])/g);
if (defined(uniformLines)) {
const len = uniformLines.length;
for (let i = 0; i < len; i++) {
const line = uniformLines[i].trim();
const name = line.slice(line.lastIndexOf(" ") + 1);
uniformNames.push(name);
}
}
return uniformNames;
}
function handleUniformPrecisionMismatches(
vertexShaderText,
fragmentShaderText,
) {
// If a uniform exists in both the vertex and fragment shader but with different precision qualifiers,
// give the fragment shader uniform a different name. This fixes shader compilation errors on devices
// that only support mediump in the fragment shader.
const duplicateUniformNames = {};
if (!ContextLimits.highpFloatSupported || !ContextLimits.highpIntSupported) {
let i, j;
let uniformName;
let duplicateName;
const vertexShaderUniforms = extractUniforms(vertexShaderText);
const fragmentShaderUniforms = extractUniforms(fragmentShaderText);
const vertexUniformsCount = vertexShaderUniforms.length;
const fragmentUniformsCount = fragmentShaderUniforms.length;
for (i = 0; i < vertexUniformsCount; i++) {
for (j = 0; j < fragmentUniformsCount; j++) {
if (vertexShaderUniforms[i] === fragmentShaderUniforms[j]) {
uniformName = vertexShaderUniforms[i];
duplicateName = `czm_mediump_${uniformName}`;
// Update fragmentShaderText with renamed uniforms
const re = new RegExp(`${uniformName}\\b`, "g");
fragmentShaderText = fragmentShaderText.replace(re, duplicateName);
duplicateUniformNames[duplicateName] = uniformName;
}
}
}
}
return {
fragmentShaderText: fragmentShaderText,
duplicateUniformNames: duplicateUniformNames,
};
}
const consolePrefix = "[Cesium WebGL] ";
function createAndLinkProgram(gl, shader) {
const vsSource = shader._vertexShaderText;
const fsSource = shader._fragmentShaderText;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vsSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fsSource);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
const attributeLocations = shader._attributeLocations;
if (defined(attributeLocations)) {
for (const attribute in attributeLocations) {
if (attributeLocations.hasOwnProperty(attribute)) {
gl.bindAttribLocation(
program,
attributeLocations[attribute],
attribute,
);
}
}
}
gl.linkProgram(program);
let log;
// For performance: if linker succeeds, return without checking compile status
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
if (shader._logShaderCompilation) {
log = gl.getShaderInfoLog(vertexShader);
if (defined(log) && log.length > 0) {
console.log(`${consolePrefix}Vertex shader compile log: ${log}`);
}
log = gl.getShaderInfoLog(fragmentShader);
if (defined(log) && log.length > 0) {
console.log(`${consolePrefix}Fragment shader compile log: ${log}`);
}
log = gl.getProgramInfoLog(program);
if (defined(log) && log.length > 0) {
console.log(`${consolePrefix}Shader program link log: ${log}`);
}
}
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
return program;
}
// Program failed to link. Try to find and report the reason
let errorMessage;
const debugShaders = shader._debugShaders;
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
log = gl.getShaderInfoLog(fragmentShader);
console.error(`${consolePrefix}Fragment shader compile log: ${log}`);
console.error(`${consolePrefix} Fragment shader source:\n${fsSource}`);
errorMessage = `Fragment shader failed to compile. Compile log: ${log}`;
} else if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
log = gl.getShaderInfoLog(vertexShader);
console.error(`${consolePrefix}Vertex shader compile log: ${log}`);
console.error(`${consolePrefix} Vertex shader source:\n${vsSource}`);
errorMessage = `Vertex shader failed to compile. Compile log: ${log}`;
} else {
log = gl.getProgramInfoLog(program);
console.error(`${consolePrefix}Shader program link log: ${log}`);
logTranslatedSource(vertexShader, "vertex");
logTranslatedSource(fragmentShader, "fragment");
errorMessage = `Program failed to link. Link log: ${log}`;
}
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
gl.deleteProgram(program);
throw new RuntimeError(errorMessage);
function logTranslatedSource(compiledShader, name) {
if (!defined(debugShaders)) {
return;
}
const translation = debugShaders.getTranslatedShaderSource(compiledShader);
if (translation === "") {
console.error(`${consolePrefix}${name} shader translation failed.`);
return;
}
console.error(
`${consolePrefix}Translated ${name} shaderSource:\n${translation}`,
);
}
}
function findVertexAttributes(gl, program, numberOfAttributes) {
const attributes = {};
for (let i = 0; i < numberOfAttributes; ++i) {
const attr = gl.getActiveAttrib(program, i);
const location = gl.getAttribLocation(program, attr.name);
attributes[attr.name] = {
name: attr.name,
type: attr.type,
index: location,
};
}
return attributes;
}
function findUniforms(gl, program) {
const uniformsByName = {};
const uniforms = [];
const samplerUniforms = [];
const numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < numberOfUniforms; ++i) {
const activeUniform = gl.getActiveUniform(program, i);
const suffix = "[0]";
const uniformName =
activeUniform.name.indexOf(
suffix,
activeUniform.name.length - suffix.length,
) !== -1
? activeUniform.name.slice(0, activeUniform.name.length - 3)
: activeUniform.name;
// Ignore GLSL built-in uniforms returned in Firefox.
if (uniformName.indexOf("gl_") !== 0) {
if (activeUniform.name.indexOf("[") < 0) {
// Single uniform
const location = gl.getUniformLocation(program, uniformName);
// IE 11.0.9 needs this check since getUniformLocation can return null
// if the uniform is not active (e.g., it is optimized out). Looks like
// getActiveUniform() above returns uniforms that are not actually active.
if (location !== null) {
const uniform = createUniform(
gl,
activeUniform,
uniformName,
location,
);
uniformsByName[uniformName] = uniform;
uniforms.push(uniform);
if (uniform._setSampler) {
samplerUniforms.push(uniform);
}
}
} else {
// Uniform array
let uniformArray;
let locations;
let value;
let loc;
// On some platforms - Nexus 4 in Firefox for one - an array of sampler2D ends up being represented
// as separate uniforms, one for each array element. Check for and handle that case.
const indexOfBracket = uniformName.indexOf("[");
if (indexOfBracket >= 0) {
// We're assuming the array elements show up in numerical order - it seems to be true.
uniformArray = uniformsByName[uniformName.slice(0, indexOfBracket)];
// Nexus 4 with Android 4.3 needs this check, because it reports a uniform
// with the strange name webgl_3467e0265d05c3c1[1] in our globe surface shader.
if (!defined(uniformArray)) {
continue;
}
locations = uniformArray._locations;
// On the Nexus 4 in Chrome, we get one uniform per sampler, just like in Firefox,
// but the size is not 1 like it is in Firefox. So if we push locations here,
// we'll end up adding too many locations.
if (locations.length <= 1) {
value = uniformArray.value;
loc = gl.getUniformLocation(program, uniformName);
// Workaround for IE 11.0.9. See above.
if (loc !== null) {
locations.push(loc);
value.push(gl.getUniform(program, loc));
}
}
} else {
locations = [];
for (let j = 0; j < activeUniform.size; ++j) {
loc = gl.getUniformLocation(program, `${uniformName}[${j}]`);
// Workaround for IE 11.0.9. See above.
if (loc !== null) {
locations.push(loc);
}
}
uniformArray = createUniformArray(
gl,
activeUniform,
uniformName,
locations,
);
uniformsByName[uniformName] = uniformArray;
uniforms.push(uniformArray);
if (uniformArray._setSampler) {
samplerUniforms.push(uniformArray);
}
}
}
}
}
return {
uniformsByName: uniformsByName,
uniforms: uniforms,
samplerUniforms: samplerUniforms,
};
}
function partitionUniforms(shader, uniforms) {
const automaticUniforms = [];
const manualUniforms = [];
for (const uniform in uniforms) {
if (uniforms.hasOwnProperty(uniform)) {
const uniformObject = uniforms[uniform];
let uniformName = uniform;
// if it's a duplicate uniform, use its original name so it is updated correctly
const duplicateUniform = shader._duplicateUniformNames[uniformName];
if (defined(duplicateUniform)) {
uniformObject.name = duplicateUniform;
uniformName = duplicateUniform;
}
const automaticUniform = AutomaticUniforms[uniformName];
if (defined(automaticUniform)) {
automaticUniforms.push({
uniform: uniformObject,
automaticUniform: automaticUniform,
});
} else {
manualUniforms.push(uniformObject);
}
}
}
return {
automaticUniforms: automaticUniforms,
manualUniforms: manualUniforms,
};
}
function setSamplerUniforms(gl, program, samplerUniforms) {
gl.useProgram(program);
let textureUnitIndex = 0;
const length = samplerUniforms.length;
for (let i = 0; i < length; ++i) {
textureUnitIndex = samplerUniforms[i]._setSampler(textureUnitIndex);
}
gl.useProgram(null);
return textureUnitIndex;
}
function initialize(shader) {
if (defined(shader._program)) {
return;
}
reinitialize(shader);
}
function reinitialize(shader) {
const oldProgram = shader._program;
const gl = shader._gl;
const program = createAndLinkProgram(gl, shader, shader._debugShaders);
const numberOfVertexAttributes = gl.getProgramParameter(
program,
gl.ACTIVE_ATTRIBUTES,
);
const uniforms = findUniforms(gl, program);
const partitionedUniforms = partitionUniforms(
shader,
uniforms.uniformsByName,
);
shader._program = program;
shader._numberOfVertexAttributes = numberOfVertexAttributes;
shader._vertexAttributes = findVertexAttributes(
gl,
program,
numberOfVertexAttributes,
);
shader._uniformsByName = uniforms.uniformsByName;
shader._uniforms = uniforms.uniforms;
shader._automaticUniforms = partitionedUniforms.automaticUniforms;
shader._manualUniforms = partitionedUniforms.manualUniforms;
shader.maximumTextureUnitIndex = setSamplerUniforms(
gl,
program,
uniforms.samplerUniforms,
);
if (oldProgram) {
shader._gl.deleteProgram(oldProgram);
}
// If SpectorJS is active, add the hook to make the shader editor work.
// https://github.com/BabylonJS/Spector.js/blob/master/documentation/extension.md#shader-editor
if (typeof spector !== "undefined") {
shader._program.__SPECTOR_rebuildProgram = function (
vertexSourceCode, // The new vertex shader source
fragmentSourceCode, // The new fragment shader source
onCompiled, // Callback triggered by your engine when the compilation is successful. It needs to send back the new linked program.
onError, // Callback triggered by your engine in case of error. It needs to send the WebGL error to allow the editor to display the error in the gutter.
) {
const originalVS = shader._vertexShaderText;
const originalFS = shader._fragmentShaderText;
// SpectorJS likes to replace `!=` with `! =` for unknown reasons,
// and that causes glsl compile failures. So fix that up.
const regex = / ! = /g;
shader._vertexShaderText = vertexSourceCode.replace(regex, " != ");
shader._fragmentShaderText = fragmentSourceCode.replace(regex, " != ");
try {
reinitialize(shader);
onCompiled(shader._program);
} catch (e) {
shader._vertexShaderText = originalVS;
shader._fragmentShaderText = originalFS;
// Only pass on the WebGL error:
const errorMatcher = /(?:Compile|Link) error: ([^]*)/;
const match = errorMatcher.exec(e.message);
if (match) {
onError(match[1]);
} else {
onError(e.message);
}
}
};
}
}
ShaderProgram.prototype._bind = function () {
initialize(this);
this._gl.useProgram(this._program);
};
ShaderProgram.prototype._setUniforms = function (
uniformMap,
uniformState,
validate,
) {
let len;
let i;
if (defined(uniformMap)) {
const manualUniforms = this._manualUniforms;
len = manualUniforms.length;
for (i = 0; i < len; ++i) {
const mu = manualUniforms[i];
//>>includeStart('debug', pragmas.debug);
if (!defined(uniformMap[mu.name])) {
throw new DeveloperError(`Unknown uniform: ${mu.name}`);
}
//>>includeEnd('debug');
mu.value = uniformMap[mu.name]();
}
}
const automaticUniforms = this._automaticUniforms;
len = automaticUniforms.length;
for (i = 0; i < len; ++i) {
const au = automaticUniforms[i];
au.uniform.value = au.automaticUniform.getValue(uniformState);
}
///////////////////////////////////////////////////////////////////
// It appears that assigning the uniform values above and then setting them here
// (which makes the GL calls) is faster than removing this loop and making
// the GL calls above. I suspect this is because each GL call pollutes the
// L2 cache making our JavaScript and the browser/driver ping-pong cache lines.
const uniforms = this._uniforms;
len = uniforms.length;
for (i = 0; i < len; ++i) {
uniforms[i].set();
}
if (validate) {
const gl = this._gl;
const program = this._program;
gl.validateProgram(program);
//>>includeStart('debug', pragmas.debug);
if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
throw new DeveloperError(
`Program validation failed. Program info log: ${gl.getProgramInfoLog(
program,
)}`,
);
}
//>>includeEnd('debug');
}
};
ShaderProgram.prototype.isDestroyed = function () {
return false;
};
ShaderProgram.prototype.destroy = function () {
this._cachedShader.cache.releaseShaderProgram(this);
return undefined;
};
ShaderProgram.prototype.finalDestroy = function () {
this._gl.deleteProgram(this._program);
return destroyObject(this);
};
export default ShaderProgram;
+525
View File
@@ -0,0 +1,525 @@
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import CzmBuiltins from "../Shaders/Builtin/CzmBuiltins.js";
import AutomaticUniforms from "./AutomaticUniforms.js";
import demodernizeShader from "./demodernizeShader.js";
function removeComments(source) {
// remove inline comments
source = source.replace(/\/\/.*/g, "");
// remove multiline comment block
return source.replace(/\/\*\*[\s\S]*?\*\//gm, function (match) {
// preserve the number of lines in the comment block so the line numbers will be correct when debugging shaders
const numberOfLines = match.match(/\n/gm).length;
let replacement = "";
for (let lineNumber = 0; lineNumber < numberOfLines; ++lineNumber) {
replacement += "\n";
}
return replacement;
});
}
function getDependencyNode(name, glslSource, nodes) {
let dependencyNode;
// check if already loaded
for (let i = 0; i < nodes.length; ++i) {
if (nodes[i].name === name) {
dependencyNode = nodes[i];
}
}
if (!defined(dependencyNode)) {
// strip doc comments so we don't accidentally try to determine a dependency for something found
// in a comment
glslSource = removeComments(glslSource);
// create new node
dependencyNode = {
name: name,
glslSource: glslSource,
dependsOn: [],
requiredBy: [],
evaluated: false,
};
nodes.push(dependencyNode);
}
return dependencyNode;
}
function generateDependencies(currentNode, dependencyNodes) {
if (currentNode.evaluated) {
return;
}
currentNode.evaluated = true;
// identify all dependencies that are referenced from this glsl source code
let czmMatches = currentNode.glslSource.match(/\bczm_[a-zA-Z0-9_]*/g);
if (defined(czmMatches) && czmMatches !== null) {
// remove duplicates
czmMatches = czmMatches.filter(function (elem, pos) {
return czmMatches.indexOf(elem) === pos;
});
czmMatches.forEach(function (element) {
if (
element !== currentNode.name &&
ShaderSource._czmBuiltinsAndUniforms.hasOwnProperty(element)
) {
const referencedNode = getDependencyNode(
element,
ShaderSource._czmBuiltinsAndUniforms[element],
dependencyNodes,
);
currentNode.dependsOn.push(referencedNode);
referencedNode.requiredBy.push(currentNode);
// recursive call to find any dependencies of the new node
generateDependencies(referencedNode, dependencyNodes);
}
});
}
}
function sortDependencies(dependencyNodes) {
const nodesWithoutIncomingEdges = [];
const allNodes = [];
while (dependencyNodes.length > 0) {
const node = dependencyNodes.pop();
allNodes.push(node);
if (node.requiredBy.length === 0) {
nodesWithoutIncomingEdges.push(node);
}
}
while (nodesWithoutIncomingEdges.length > 0) {
const currentNode = nodesWithoutIncomingEdges.shift();
dependencyNodes.push(currentNode);
for (let i = 0; i < currentNode.dependsOn.length; ++i) {
// remove the edge from the graph
const referencedNode = currentNode.dependsOn[i];
const index = referencedNode.requiredBy.indexOf(currentNode);
referencedNode.requiredBy.splice(index, 1);
// if referenced node has no more incoming edges, add to list
if (referencedNode.requiredBy.length === 0) {
nodesWithoutIncomingEdges.push(referencedNode);
}
}
}
// if there are any nodes left with incoming edges, then there was a circular dependency somewhere in the graph
const badNodes = [];
for (let j = 0; j < allNodes.length; ++j) {
if (allNodes[j].requiredBy.length !== 0) {
badNodes.push(allNodes[j]);
}
}
//>>includeStart('debug', pragmas.debug);
if (badNodes.length !== 0) {
let message =
"A circular dependency was found in the following built-in functions/structs/constants: \n";
for (let k = 0; k < badNodes.length; ++k) {
message = `${message + badNodes[k].name}\n`;
}
throw new DeveloperError(message);
}
//>>includeEnd('debug');
}
function getBuiltinsAndAutomaticUniforms(shaderSource) {
// generate a dependency graph for builtin functions
const dependencyNodes = [];
const root = getDependencyNode("main", shaderSource, dependencyNodes);
generateDependencies(root, dependencyNodes);
sortDependencies(dependencyNodes);
// Concatenate the source code for the function dependencies.
// Iterate in reverse so that dependent items are declared before they are used.
let builtinsSource = "";
for (let i = dependencyNodes.length - 1; i >= 0; --i) {
builtinsSource = `${builtinsSource + dependencyNodes[i].glslSource}\n`;
}
return builtinsSource.replace(root.glslSource, "");
}
function combineShader(shaderSource, isFragmentShader, context) {
// Combine shader sources, generally for pseudo-polymorphism, e.g., czm_getMaterial.
let combinedSources = "";
const sources = shaderSource.sources;
if (defined(sources)) {
for (let i = 0; i < sources.length; ++i) {
// #line needs to be on its own line.
combinedSources += `\n#line 0\n${sources[i]}`;
}
}
combinedSources = removeComments(combinedSources);
// Extract existing shader version from sources
let version;
combinedSources = combinedSources.replace(
/#version\s+(.*?)\n/gm,
function (match, group1) {
//>>includeStart('debug', pragmas.debug);
if (defined(version) && version !== group1) {
throw new DeveloperError(
`inconsistent versions found: ${version} and ${group1}`,
);
}
//>>includeEnd('debug');
// Extract #version to put at the top
version = group1;
// Replace original #version directive with a new line so the line numbers
// are not off by one. There can be only one #version directive
// and it must appear at the top of the source, only preceded by
// whitespace and comments.
return "\n";
},
);
// Extract shader extensions from sources
const extensions = [];
combinedSources = combinedSources.replace(
/#extension.*\n/gm,
function (match) {
// Extract extension to put at the top
extensions.push(match);
// Replace original #extension directive with a new line so the line numbers
// are not off by one.
return "\n";
},
);
// Remove precision qualifier
combinedSources = combinedSources.replace(
/precision\s(lowp|mediump|highp)\s(float|int);/,
"",
);
// Replace main() for picked if desired.
const pickColorQualifier = shaderSource.pickColorQualifier;
if (defined(pickColorQualifier)) {
combinedSources = ShaderSource.createPickFragmentShaderSource(
combinedSources,
pickColorQualifier,
);
}
// combine into single string
let result = "";
const extensionsLength = extensions.length;
for (let i = 0; i < extensionsLength; i++) {
result += extensions[i];
}
if (isFragmentShader) {
// If high precision isn't supported, replace occurrences of highp with mediump.
// The highp keyword is not always available on older mobile devices.
// See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#In_WebGL_1_highp_float_support_is_optional_in_fragment_shaders
result += `
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
precision highp int;
#else
precision mediump float;
precision mediump int;
#define highp mediump
#endif
`;
}
if (context.webgl2) {
result += `precision highp sampler3D;\n\n`;
}
// Prepend #defines for uber-shaders
const defines = shaderSource.defines;
if (defined(defines)) {
for (let i = 0, length = defines.length; i < length; ++i) {
const define = defines[i];
if (define.length !== 0) {
result += `#define ${define}\n`;
}
}
}
// Define a constant for the OES_texture_float_linear extension since WebGL does not.
if (context.textureFloatLinear) {
result += "#define OES_texture_float_linear\n\n";
}
// Define a constant for the OES_texture_float extension since WebGL does not.
if (context.floatingPointTexture) {
result += "#define OES_texture_float\n\n";
}
// append built-ins
let builtinSources = "";
if (shaderSource.includeBuiltIns) {
builtinSources = getBuiltinsAndAutomaticUniforms(combinedSources);
}
// reset line number
result += "\n#line 0\n";
// append actual source
const combinedShader = builtinSources + combinedSources;
if (
context.webgl2 &&
isFragmentShader &&
!/layout\s*\(location\s*=\s*0\)\s*out\s+vec4\s+out_FragColor;/g.test(
combinedShader,
) &&
!/czm_out_FragColor/g.test(combinedShader) &&
/out_FragColor/g.test(combinedShader)
) {
result += "layout(location = 0) out vec4 out_FragColor;\n\n";
}
result += builtinSources;
result += combinedSources;
// modernize the source
if (!context.webgl2) {
result = demodernizeShader(result, isFragmentShader);
} else {
result = `#version 300 es\n${result}`;
}
return result;
}
/**
* An object containing various inputs that will be combined to form a final GLSL shader string.
*
* @param {object} [options] Object with the following properties:
* @param {string[]} [options.sources] An array of strings to combine containing GLSL code for the shader.
* @param {string[]} [options.defines] An array of strings containing GLSL identifiers to <code>#define</code>.
* @param {string} [options.pickColorQualifier] The GLSL qualifier, <code>uniform</code> or <code>in</code>, for the input <code>czm_pickColor</code>. When defined, a pick fragment shader is generated.
* @param {boolean} [options.includeBuiltIns=true] If true, referenced built-in functions will be included with the combined shader. Set to false if this shader will become a source in another shader, to avoid duplicating functions.
*
* @exception {DeveloperError} options.pickColorQualifier must be 'uniform' or 'in'.
*
* @example
* // 1. Prepend #defines to a shader
* const source = new Cesium.ShaderSource({
* defines : ['WHITE'],
* sources : ['void main() { \n#ifdef WHITE\n out_FragColor = vec4(1.0); \n#else\n out_FragColor = vec4(0.0); \n#endif\n }']
* });
*
* // 2. Modify a fragment shader for picking
* const source2 = new Cesium.ShaderSource({
* sources : ['void main() { out_FragColor = vec4(1.0); }'],
* pickColorQualifier : 'uniform'
* });
*
* @private
*/
function ShaderSource(options) {
options = options ?? Frozen.EMPTY_OBJECT;
const pickColorQualifier = options.pickColorQualifier;
//>>includeStart('debug', pragmas.debug);
if (
defined(pickColorQualifier) &&
pickColorQualifier !== "uniform" &&
pickColorQualifier !== "in"
) {
throw new DeveloperError(
"options.pickColorQualifier must be 'uniform' or 'in'.",
);
}
//>>includeEnd('debug');
this.defines = defined(options.defines) ? options.defines.slice(0) : [];
this.sources = defined(options.sources) ? options.sources.slice(0) : [];
this.pickColorQualifier = pickColorQualifier;
this.includeBuiltIns = options.includeBuiltIns ?? true;
}
ShaderSource.prototype.clone = function () {
return new ShaderSource({
sources: this.sources,
defines: this.defines,
pickColorQualifier: this.pickColorQualifier,
includeBuiltIns: this.includeBuiltIns,
});
};
ShaderSource.replaceMain = function (source, renamedMain) {
renamedMain = `void ${renamedMain}()`;
return source.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, renamedMain);
};
/**
* Since {@link ShaderSource#createCombinedVertexShader} and
* {@link ShaderSource#createCombinedFragmentShader} are both expensive to
* compute, create a simpler string key for lookups in the {@link ShaderCache}.
*
* @returns {string} A key for identifying this shader
*
* @private
*/
ShaderSource.prototype.getCacheKey = function () {
// Sort defines to make the key comparison deterministic
const sortedDefines = this.defines.slice().sort();
const definesKey = sortedDefines.join(",");
const pickKey = this.pickColorQualifier;
const builtinsKey = this.includeBuiltIns;
const sourcesKey = this.sources.join("\n");
return `${definesKey}:${pickKey}:${builtinsKey}:${sourcesKey}`;
};
/**
* Create a single string containing the full, combined vertex shader with all dependencies and defines.
*
* @param {Context} context The current rendering context
*
* @returns {string} The combined shader string.
*/
ShaderSource.prototype.createCombinedVertexShader = function (context) {
return combineShader(this, false, context);
};
/**
* Create a single string containing the full, combined fragment shader with all dependencies and defines.
*
* @param {Context} context The current rendering context
*
* @returns {string} The combined shader string.
*/
ShaderSource.prototype.createCombinedFragmentShader = function (context) {
return combineShader(this, true, context);
};
/**
* For ShaderProgram testing
* @private
*/
ShaderSource._czmBuiltinsAndUniforms = {};
// combine automatic uniforms and Cesium built-ins
for (const builtinName in CzmBuiltins) {
if (CzmBuiltins.hasOwnProperty(builtinName)) {
ShaderSource._czmBuiltinsAndUniforms[builtinName] =
CzmBuiltins[builtinName];
}
}
for (const uniformName in AutomaticUniforms) {
if (AutomaticUniforms.hasOwnProperty(uniformName)) {
const uniform = AutomaticUniforms[uniformName];
if (typeof uniform.getDeclaration === "function") {
ShaderSource._czmBuiltinsAndUniforms[uniformName] =
uniform.getDeclaration(uniformName);
}
}
}
ShaderSource.createPickVertexShaderSource = function (vertexShaderSource) {
const renamedVS = ShaderSource.replaceMain(
vertexShaderSource,
"czm_old_main",
);
const pickMain =
"in vec4 pickColor; \n" +
"out vec4 czm_pickColor; \n" +
"void main() \n" +
"{ \n" +
" czm_old_main(); \n" +
" czm_pickColor = pickColor; \n" +
"}";
return `${renamedVS}\n${pickMain}`;
};
ShaderSource.createPickFragmentShaderSource = function (
fragmentShaderSource,
pickColorQualifier,
) {
const renamedFS = ShaderSource.replaceMain(
fragmentShaderSource,
"czm_old_main",
);
const pickMain =
`${pickColorQualifier} vec4 czm_pickColor; \n` +
`void main() \n` +
`{ \n` +
` czm_old_main(); \n` +
` if (out_FragColor.a == 0.0) { \n` +
` discard; \n` +
` } \n` +
` out_FragColor = czm_pickColor; \n` +
`}`;
return `${renamedFS}\n${pickMain}`;
};
function containsDefine(shaderSource, define) {
const defines = shaderSource.defines;
const definesLength = defines.length;
for (let i = 0; i < definesLength; ++i) {
if (defines[i] === define) {
return true;
}
}
return false;
}
function containsString(shaderSource, string) {
const sources = shaderSource.sources;
const sourcesLength = sources.length;
for (let i = 0; i < sourcesLength; ++i) {
if (sources[i].indexOf(string) !== -1) {
return true;
}
}
return false;
}
function findFirstString(shaderSource, strings) {
const stringsLength = strings.length;
for (let i = 0; i < stringsLength; ++i) {
const string = strings[i];
if (containsString(shaderSource, string)) {
return string;
}
}
return undefined;
}
const normalVaryingNames = ["v_normalEC", "v_normal"];
ShaderSource.findNormalVarying = function (shaderSource) {
// Fix for Model: the shader text always has the word v_normalEC
// wrapped in an #ifdef so instead of looking for v_normalEC look for the define
if (containsString(shaderSource, "#ifdef HAS_NORMALS")) {
if (containsDefine(shaderSource, "HAS_NORMALS")) {
return "v_normalEC";
}
return undefined;
}
return findFirstString(shaderSource, normalVaryingNames);
};
const positionVaryingNames = ["v_positionEC"];
ShaderSource.findPositionVarying = function (shaderSource) {
return findFirstString(shaderSource, positionVaryingNames);
};
export default ShaderSource;
+55
View File
@@ -0,0 +1,55 @@
/**
* A utility for dynamically-generating a GLSL struct.
*
* @alias ShaderStruct
* @constructor
*
* @see {@link ShaderBuilder}
* @param {string} name The name of the struct as it will appear in the shader.
* @example
* // Generate the struct:
* //
* // struct Attributes
* // {
* // vec3 position;
* // vec3 normal;
* // vec2 texCoord;
* // };
* const struct = new ShaderStruct("Attributes");
* struct.addField("vec3", "position");
* struct.addField("vec3", "normal");
* struct.addField("vec2", "texCoord");
* const generatedLines = struct.generateGlslLines();
*
* @private
*/
function ShaderStruct(name) {
this.name = name;
this.fields = [];
}
/**
* Add a field to the struct
* @param {string} type The type of the struct field
* @param {string} identifier The identifier of the struct field
*/
ShaderStruct.prototype.addField = function (type, identifier) {
const field = ` ${type} ${identifier};`;
this.fields.push(field);
};
/**
* Generate a list of lines of GLSL code for use with {@link ShaderBuilder}
* @return {string[]} The generated GLSL code.
*/
ShaderStruct.prototype.generateGlslLines = function () {
let fields = this.fields;
if (fields.length === 0) {
// GLSL requires structs to have at least one field
fields = [" float _empty;"];
}
return [].concat(`struct ${this.name}`, "{", fields, "};");
};
export default ShaderStruct;
+186
View File
@@ -0,0 +1,186 @@
import clone from "../Core/clone.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import Context from "./Context.js";
/**
* Enables a single WebGL context to be used by any number of {@link Scene}s.
* You can pass a SharedContext in place of a {@link ContextOptions} to the constructors of {@link Scene}, {@link CesiumWidget}, and {@link Viewer}.
* {@link Primitive}s associated with the shared WebGL context can be displayed in any Scene that uses the same context.
* The context renders each Scene to an off-screen canvas, then blits the result to that Scene's on-screen canvas.
*
* @private
* @alias SharedContext
* @constructor
*
* @param {object} [options] Object with the following properties:
* @param {ContextOptions} [options.contextOptions] Context and WebGL creation properties.
* @param {boolean} [options.autoDestroy=true] Destroys this context and all of its WebGL resources after all Scenes using the context are destroyed.
* @see {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
*
* @example
* // Create two Scenes sharing a single WebGL context
* const context = new Cesium.SharedContext();
* const scene1 = new Cesium.Scene({
* canvas: canvas1,
* contextOptions: context,
* });
* const scene2 = new Cesium.Scene({
* canvas: canvas2,
* contextOptions: context,
* });
*/
function SharedContext(options) {
this._autoDestroy = options?.autoDestroy ?? true;
this._canvas = document.createElement("canvas");
this._context = new Context(this._canvas, clone(options?.contextOptions));
this._canvases = [];
}
/**
* Creates an instance of {@link Context} that manages the shared WebGL context for a specific canvas.
* @param {HTMLCanvasElement} canvas The canvas element to which the context will be associated
* @returns {Context} The created context instance
* @private
*/
SharedContext.prototype.createSceneContext = function (canvas) {
const context2d = canvas.getContext("2d", { alpha: true });
//>>includeStart('debug', pragmas.debug);
if (!context2d) {
throw new DeveloperError(
"canvas used with SharedContext must provide a 2d context",
);
}
if (this._canvases.includes(canvas)) {
throw new DeveloperError("canvas is already associated with a scene");
}
//>>includeEnd('debug');
const sharedContext = this;
sharedContext._canvases.push(canvas);
let isDestroyed = false;
const destroy = function () {
isDestroyed = true;
const index = sharedContext._canvases.indexOf(canvas);
if (-1 !== index) {
sharedContext._canvases.splice(index, 1);
if (sharedContext._autoDestroy && sharedContext._canvases.length === 0) {
sharedContext.destroy();
}
}
};
const beginFrame = function () {
// Ensure the off-screen canvas is at least as large as the on-screen canvas.
const sharedCanvas = sharedContext._context.canvas;
const width = this.drawingBufferWidth;
if (sharedCanvas.width < width) {
sharedCanvas.width = width;
}
const height = this.drawingBufferHeight;
if (sharedCanvas.height < height) {
sharedCanvas.height = height;
}
};
const endFrame = function () {
// Blit the image from the off-screen canvas to the on-screen canvas.
const w = this.drawingBufferWidth;
const h = this.drawingBufferHeight;
const yOffset = sharedContext._context.canvas.height - h; // drawImage has top as Y=0, GL has bottom as Y=0
context2d.drawImage(
sharedContext._context.canvas,
0,
yOffset,
w,
h,
0,
0,
w,
h,
);
// Do normal post-frame cleanup.
sharedContext._context.endFrame();
};
const proxy = new Proxy(this._context, {
get(target, prop, receiver) {
if (prop === "isDestroyed") {
return function () {
return isDestroyed;
};
} else if (isDestroyed) {
//>>includeStart('debug', pragmas.debug);
throw new DeveloperError(
"This object was destroyed, i.e., destroy() was called.",
);
//>>includeEnd('debug');
}
switch (prop) {
case "_canvas":
return canvas;
case "destroy":
return destroy;
case "drawingBufferWidth":
return canvas.width;
case "drawingBufferHeight":
return canvas.height;
case "beginFrame":
return beginFrame;
case "endFrame":
return endFrame;
default:
return Reflect.get(target, prop, receiver);
}
},
});
return proxy;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
* <br /><br />
* Once an object is destroyed, it should not be used; calling any function other than
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (<code>undefined</code>) to the object as done in the example.
* <br /><br />
* By default, a SharedContext is destroyed automatically once the last Scene using it is destroyed, in which case it
* is not necessary to call this method directly.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* context = context && context.destroy();
*
* @see SharedContext#isDestroyed
*/
SharedContext.prototype.destroy = function () {
this._context.destroy();
destroyObject(this);
};
/**
* Returns true if this object was destroyed; otherwise, false.
* <br /><br />
* If this object was destroyed, it should not be used; calling any function other than
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
*
* @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
*
* @see SharedContext#destroy
*/
SharedContext.prototype.isDestroyed = function () {
return false;
};
export default SharedContext;
+104
View File
@@ -0,0 +1,104 @@
import Check from "../Core/Check.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import Frozen from "../Core/Frozen.js";
import RuntimeError from "../Core/RuntimeError.js";
import WebGLConstants from "../Core/WebGLConstants.js";
/**
* The WebGLSync interface is part of the WebGL 2 API and is used to synchronize activities between the GPU and the application.
*
* @param {object} options Object with the following properties:
* @param {Context} context
*
* @exception {DeveloperError} A WebGL 2 context is required to use Sync operations.
*
* @private
* @constructor
*/
function Sync(options) {
options = options ?? Frozen.EMPTY_OBJECT;
const context = options.context;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", context);
//>>includeEnd('debug');
if (!context._webgl2) {
throw new DeveloperError(
"A WebGL 2 context is required to use Sync operations.",
);
}
const gl = context._gl;
const sync = gl.fenceSync(WebGLConstants.SYNC_GPU_COMMANDS_COMPLETE, 0);
this._gl = gl;
this._sync = sync;
}
Sync.create = function (options) {
return new Sync(options);
};
/**
* Query the sync status of this Sync object.
*
* @returns {number} Returns a WebGLConstants indicating the status of the sync object (WebGLConstants.SIGNALED or WebGLConstants.UNSIGNALED).
*
* @private
*/
Sync.prototype.getStatus = function () {
const status = this._gl.getSyncParameter(
this._sync,
WebGLConstants.SYNC_STATUS,
);
return status;
};
Sync.prototype.isDestroyed = function () {
return false;
};
Sync.prototype.destroy = function () {
this._gl.deleteSync(this._sync);
return destroyObject(this);
};
/**
* Incremantally polls the status of the Sync object until signaled then resolves.
* Usually polling should be done once per frame.
*
* @example
* try {
* await sync.waitForSignal(function (next) {
* setTimeout(next, 100);
* });
*} catch (e) {
* throw "Signal timeout";
*} finally {
* sync.destroy();
*}
*
* @param {function} scheduleFunction Function for scheduling the next poll. Receives a callback as its only parameter.
* @param {number} [ttl=10] Max number of iterations to poll until timeout.
*
* @exception {RuntimeError} Wait for signal timeout.
*/
Sync.prototype.waitForSignal = async function (scheduleFunction, ttl) {
const self = this;
ttl = ttl ?? 10;
function waitForSignal0(resolve, reject, ttl) {
return () => {
const syncStatus = self.getStatus();
const signaled = syncStatus === WebGLConstants.SIGNALED;
if (signaled) {
resolve();
} else if (ttl <= 0) {
reject(new RuntimeError("Wait for signal timeout"));
} else {
scheduleFunction(waitForSignal0(resolve, reject, ttl - 1));
}
};
}
return new Promise((resolve, reject) => {
scheduleFunction(waitForSignal0(resolve, reject, ttl));
});
};
export default Sync;
File diff suppressed because it is too large Load Diff
+737
View File
@@ -0,0 +1,737 @@
import Cartesian3 from "../Core/Cartesian3.js";
import Check from "../Core/Check.js";
import createGuid from "../Core/createGuid.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import PixelFormat from "../Core/PixelFormat.js";
import ContextLimits from "./ContextLimits.js";
import MipmapHint from "./MipmapHint.js";
import PixelDatatype from "./PixelDatatype.js";
import Sampler from "./Sampler.js";
import TextureMagnificationFilter from "./TextureMagnificationFilter.js";
import TextureMinificationFilter from "./TextureMinificationFilter.js";
/**
* @typedef {object} Texture3D.Source
* @property {number} width The width (in pixels) of the 3D texture source data.
* @property {number} height The height (in pixels) of the 3D texture source data.
* @property {number} depth The depth (in pixels) of the 3D texture source data.
* @property {TypedArray|DataView} arrayBufferView The source data for a 3D texture. The type of each element needs to match the pixelDatatype.
* @property {TypedArray|DataView} [mipLevels] An array of mip level data. Each element in the array should be a TypedArray or DataView that matches the pixelDatatype.
*/
/**
* @typedef {object} Texture3D.ConstructorOptions
*
* @property {Context} context
* @property {Texture3D.Source} [source] The source for texel values to be loaded into the 3D texture.
* @property {PixelFormat} [pixelFormat=PixelFormat.RGBA] The format of each pixel, i.e., the number of components it has and what they represent.
* @property {PixelDatatype} [pixelDatatype=PixelDatatype.UNSIGNED_BYTE] The data type of each pixel.
* @property {boolean} [flipY=true] If true, the source values will be read as if the y-axis is inverted (y=0 at the top).
* @property {boolean} [skipColorSpaceConversion=false] If true, color space conversions will be skipped when reading the texel values.
* @property {Sampler} [sampler] Information about how to sample the 3D texture.
* @property {number} [width] The width (in pixels) of the 3D texture. If not supplied, must be available from the source.
* @property {number} [height] The height (in pixels) of the 3D texture. If not supplied, must be available from the source.
* @property {number} [depth] The depth (in pixels) of the 3D texture. If not supplied, must be available from the source.
* @property {boolean} [preMultiplyAlpha] If true, the alpha channel will be multiplied into the other channels.
* @property {string} [id] A unique identifier for the 3D texture. If this is not given, then a GUID will be created.
*
* @private
*/
/**
* A wrapper for a {@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture|WebGLTexture}
* to abstract away the verbose GL calls associated with setting up a texture3D.
*
* @alias Texture3D
* @constructor
*
* @param {Texture3D.ConstructorOptions} options
* @private
*/
function Texture3D(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
//>>includeEnd('debug');
const {
context,
source,
pixelFormat = PixelFormat.RGBA,
pixelDatatype = PixelDatatype.UNSIGNED_BYTE,
flipY = true,
skipColorSpaceConversion = false,
sampler = new Sampler(),
} = options;
// 3D textures are not supported in a WebGL1 context. But we allow a stub context for testing.
if (!context.webgl2 && !defined(context.options.getWebGLStub)) {
throw new DeveloperError(
"WebGL1 does not support texture3D. Please use a WebGL2 context.",
);
}
let { width, height, depth } = options;
if (defined(source)) {
// Make sure we are using the element's intrinsic width and height where available
if (!defined(width)) {
width = source.width;
}
if (!defined(height)) {
height = source.height;
}
// depth is not used for 2D textures, but is required for 3D textures
if (!defined(depth)) {
depth = source.depth;
}
}
// Use premultiplied alpha for opaque textures should perform better on Chrome:
// http://media.tojicode.com/webglCamp4/#20
const preMultiplyAlpha =
options.preMultiplyAlpha ||
pixelFormat === PixelFormat.RGB ||
pixelFormat === PixelFormat.LUMINANCE;
const internalFormat = PixelFormat.toInternalFormat(
pixelFormat,
pixelDatatype,
context,
);
const isCompressed = PixelFormat.isCompressedFormat(internalFormat);
//>>includeStart('debug', pragmas.debug);
if (!defined(width) || !defined(height) || !defined(depth)) {
throw new DeveloperError(
"options requires a source field to create an initialized texture3D or width, height and depth fields to create a blank texture3D.",
);
}
Check.typeOf.number.greaterThan("width", width, 0);
if (width > ContextLimits.maximum3DTextureSize) {
throw new DeveloperError(
`Width must be less than or equal to the maximum texture3D size (${ContextLimits.maximum3DTextureSize}). Check maximum3DTextureSize.`,
);
}
Check.typeOf.number.greaterThan("height", height, 0);
if (height > ContextLimits.maximum3DTextureSize) {
throw new DeveloperError(
`Height must be less than or equal to the maximum texture3D size (${ContextLimits.maximum3DTextureSize}). Check maximum3DTextureSize.`,
);
}
Check.typeOf.number.greaterThan("depth", depth, 0);
if (depth > ContextLimits.maximum3DTextureSize) {
throw new DeveloperError(
`Depth must be less than or equal to the maximum texture3D size (${ContextLimits.maximum3DTextureSize}). Check maximum3DTextureSize.`,
);
}
if (!PixelFormat.validate(pixelFormat)) {
throw new DeveloperError("Invalid options.pixelFormat.");
}
if (!isCompressed && !PixelDatatype.validate(pixelDatatype)) {
throw new DeveloperError("Invalid options.pixelDatatype.");
}
if (
pixelFormat === PixelFormat.DEPTH_COMPONENT &&
pixelDatatype !== PixelDatatype.UNSIGNED_SHORT &&
pixelDatatype !== PixelDatatype.UNSIGNED_INT
) {
throw new DeveloperError(
"When options.pixelFormat is DEPTH_COMPONENT, options.pixelDatatype must be UNSIGNED_SHORT or UNSIGNED_INT.",
);
}
if (
pixelFormat === PixelFormat.DEPTH_STENCIL &&
pixelDatatype !== PixelDatatype.UNSIGNED_INT_24_8
) {
throw new DeveloperError(
"When options.pixelFormat is DEPTH_STENCIL, options.pixelDatatype must be UNSIGNED_INT_24_8.",
);
}
if (pixelDatatype === PixelDatatype.FLOAT && !context.floatingPointTexture) {
throw new DeveloperError(
"When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension. Check context.floatingPointTexture.",
);
}
if (
pixelDatatype === PixelDatatype.HALF_FLOAT &&
!context.halfFloatingPointTexture
) {
throw new DeveloperError(
"When options.pixelDatatype is HALF_FLOAT, this WebGL implementation must support the OES_texture_half_float extension. Check context.halfFloatingPointTexture.",
);
}
if (PixelFormat.isDepthFormat(pixelFormat)) {
if (defined(source)) {
throw new DeveloperError(
"When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, source cannot be provided.",
);
}
if (!context.depthTexture) {
throw new DeveloperError(
"When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, this WebGL implementation must support WEBGL_depth_texture. Check context.depthTexture.",
);
}
}
if (isCompressed) {
throw new DeveloperError(
"Texture3D does not currently support compressed formats.",
);
}
//>>includeEnd('debug');
const gl = context._gl;
const sizeInBytes = PixelFormat.texture3DSizeInBytes(
pixelFormat,
pixelDatatype,
width,
height,
depth,
);
this._id = options.id ?? createGuid();
this._context = context;
this._textureFilterAnisotropic = context._textureFilterAnisotropic;
this._textureTarget = gl.TEXTURE_3D;
this._texture = gl.createTexture();
this._internalFormat = internalFormat;
this._pixelFormat = pixelFormat;
this._pixelDatatype = pixelDatatype;
this._width = width;
this._height = height;
this._depth = depth;
this._dimensions = new Cartesian3(width, height, depth);
this._hasMipmap = false;
this._sizeInBytes = sizeInBytes;
this._preMultiplyAlpha = preMultiplyAlpha;
this._flipY = flipY;
this._initialized = false;
this._sampler = undefined;
this._sampler = sampler;
setupSampler(this, sampler);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(this._textureTarget, this._texture);
if (defined(source)) {
if (skipColorSpaceConversion) {
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
} else {
gl.pixelStorei(
gl.UNPACK_COLORSPACE_CONVERSION_WEBGL,
gl.BROWSER_DEFAULT_WEBGL,
);
}
if (!defined(source.arrayBufferView)) {
throw new DeveloperError(
"For Texture3D, options.source.arrayBufferView must be defined",
);
}
loadBufferSource(this, source);
this._initialized = true;
} else {
loadNull(this);
}
gl.bindTexture(this._textureTarget, null);
}
/**
* Load texel data from a buffer into a texture3D.
*
* @param {Texture3D} texture3D The texture3D to which texel values will be loaded.
* @param {Texture3D.Source} source The source for texel values to be loaded into the texture3D.
*
* @private
*/
function loadBufferSource(texture3D, source) {
const context = texture3D._context;
const gl = context._gl;
const textureTarget = texture3D._textureTarget;
const internalFormat = texture3D._internalFormat;
const { width, height, depth, pixelFormat, pixelDatatype, flipY } = texture3D;
const unpackAlignment = PixelFormat.alignmentInBytes(
pixelFormat,
pixelDatatype,
width,
);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
const { arrayBufferView } = source;
if (flipY) {
console.warn("texture3D.flipY is not supported.");
}
let levels = 1;
if (source.mipLevels && source.mipLevels.length) {
levels = source.mipLevels.length + 1;
}
gl.texStorage3D(textureTarget, levels, internalFormat, width, height, depth);
gl.texSubImage3D(
textureTarget,
0,
0,
0,
0,
width,
height,
depth,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, context),
arrayBufferView,
);
if (levels > 1) {
let mipWidth = width;
let mipHeight = height;
let mipDepth = depth;
for (let i = 0; i < source.mipLevels.length; ++i) {
mipWidth = nextMipSize(mipWidth);
mipHeight = nextMipSize(mipHeight);
mipDepth = nextMipSize(mipDepth);
gl.texSubImage3D(
textureTarget,
i + 1,
0,
0,
0,
mipWidth,
mipHeight,
mipDepth,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, context),
source.mipLevels[i],
);
}
}
}
/**
* Copy new image data into this texture, from a source object with width, height, depth, and arrayBufferView properties.
* @param {object} options Object with the following properties:
* @param {object} options.source The source object with width, height, depth, and arrayBufferView properties.
* @param {number} [options.xOffset=0] The offset in the x direction within the texture to copy into.
* @param {number} [options.yOffset=0] The offset in the y direction within the texture to copy into.
* @param {number} [options.zOffset=0] The offset in the z direction within the texture to copy into.
* @param {boolean} [options.skipColorSpaceConversion=false] If true, any custom gamma or color profiles in the texture will be ignored.
*
* @exception {DeveloperError} Unsupported copyFrom with a compressed texture pixel format.
* @exception {DeveloperError} xOffset must be greater than or equal to zero.
* @exception {DeveloperError} yOffset must be greater than or equal to zero.
* @exception {DeveloperError} zOffset must be greater than or equal to zero.
* @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
* @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
* @exception {DeveloperError} zOffset + source.depth must be less than or equal to depth.
* @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
* @private
* @example
* texture.copyFrom({
* source: {
* width : 1,
* height : 1,
* depth : 1,
* arrayBufferView : new Uint8Array([255, 0, 0, 255])
* }
* });
*/
Texture3D.prototype.copyFrom = function (options) {
options = options ?? Frozen.EMPTY_OBJECT;
const { source, xOffset = 0, yOffset = 0, zOffset = 0 } = options;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.source", source);
Check.defined("options.source.arrayBufferView", source.arrayBufferView);
if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
throw new DeveloperError(
"Unsupported copyFrom with a compressed texture pixel format.",
);
}
Check.typeOf.number.greaterThanOrEquals("xOffset", xOffset, 0);
Check.typeOf.number.greaterThanOrEquals("yOffset", yOffset, 0);
Check.typeOf.number.greaterThanOrEquals("zOffset", zOffset, 0);
Check.typeOf.number.lessThanOrEquals(
"xOffset + options.source.width",
xOffset + source.width,
this._width,
);
Check.typeOf.number.lessThanOrEquals(
"yOffset + options.source.height",
yOffset + source.height,
this._height,
);
Check.typeOf.number.lessThanOrEquals(
"zOffset + options.source.depth",
zOffset + source.depth,
this._depth,
);
//>>includeEnd('debug');
const context = this._context;
const gl = context._gl;
const target = this._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
const { width, height, depth } = source;
let uploaded = false;
if (!this._initialized) {
if (
xOffset === 0 &&
yOffset === 0 &&
zOffset === 0 &&
width === this._width &&
height === this._height &&
depth === this._depth
) {
loadBufferSource(this, source);
uploaded = true;
} else {
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
loadNull(this);
}
this._initialized = true;
}
if (!uploaded) {
loadPartialBufferSource(
this,
source.arrayBufferView,
xOffset,
yOffset,
zOffset,
width,
height,
depth,
);
}
gl.bindTexture(target, null);
};
/**
* Load texel data from a buffer into part of a 3D texture
*
* @param {Texture3D} texture3D The texture3D to which texel values will be loaded.
* @param {TypedArray} arrayBufferView The texel values to be loaded into the texture3D.
* @param {number} xOffset The texel x coordinate of the lower left corner of the subregion of the texture to be updated.
* @param {number} yOffset The texel y coordinate of the lower left corner of the subregion of the texture to be updated.
* @param {number} zOffset The texel z coordinate of the lower left corner of the subregion of the texture to be updated.
* @param {number} width The width of the source data, in pixels.
* @param {number} height The height of the source data, in pixels.
* @param {number} depth The depth of the source data, in pixels.
*
* @private
*/
function loadPartialBufferSource(
texture3D,
arrayBufferView,
xOffset,
yOffset,
zOffset,
width,
height,
depth,
) {
const context = texture3D._context;
const gl = context._gl;
const { pixelFormat, pixelDatatype } = texture3D;
const unpackAlignment = PixelFormat.alignmentInBytes(
pixelFormat,
pixelDatatype,
width,
);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texSubImage3D(
texture3D._textureTarget,
0,
xOffset,
yOffset,
zOffset,
width,
height,
depth,
pixelFormat,
PixelDatatype.toWebGLConstant(pixelDatatype, context),
arrayBufferView,
);
}
/**
* Compute a dimension of the image for the next mip level.
*
* @param {number} currentSize The size of the current mip level.
* @returns {number} The size of the next mip level.
*
* @private
*/
function nextMipSize(currentSize) {
const nextSize = Math.floor(currentSize / 2) | 0;
return Math.max(nextSize, 1);
}
/**
* Allocate a texture3D in GPU memory, without providing any image data.
*
* @param {Texture3D} texture3D The texture3D to be initialized with null values.
*
* @private
*/
function loadNull(texture3D) {
const context = texture3D._context;
context._gl.texImage3D(
texture3D._textureTarget,
0,
texture3D._internalFormat,
texture3D._width,
texture3D._height,
texture3D._depth,
0,
texture3D._pixelFormat,
PixelDatatype.toWebGLConstant(texture3D._pixelDatatype, context),
null,
);
}
/**
* This function is identical to using the Texture3D constructor except that it can be
* replaced with a mock/spy in tests.
* @private
*/
Texture3D.create = function (options) {
return new Texture3D(options);
};
Object.defineProperties(Texture3D.prototype, {
/**
* A unique id for the texture3D
* @memberof Texture3D.prototype
* @type {string}
* @readonly
* @private
*/
id: {
get: function () {
return this._id;
},
},
/**
* The sampler to use when sampling this texture3D.
* Create a sampler by calling {@link Sampler}. If this
* parameter is not specified, a default sampler is used. The default sampler clamps texture3D
* coordinates in both directions, uses linear filtering for both magnification and minification,
* and uses a maximum anisotropy of 1.0.
* @memberof Texture3D.prototype
* @type {Sampler}
* @private
*/
sampler: {
get: function () {
return this._sampler;
},
set: function (sampler) {
setupSampler(this, sampler);
this._sampler = sampler;
},
},
pixelFormat: {
get: function () {
return this._pixelFormat;
},
},
pixelDatatype: {
get: function () {
return this._pixelDatatype;
},
},
dimensions: {
get: function () {
return this._dimensions;
},
},
preMultiplyAlpha: {
get: function () {
return this._preMultiplyAlpha;
},
},
flipY: {
get: function () {
return this._flipY;
},
},
width: {
get: function () {
return this._width;
},
},
height: {
get: function () {
return this._height;
},
},
depth: {
get: function () {
return this._depth;
},
},
sizeInBytes: {
get: function () {
if (this._hasMipmap) {
return Math.floor((this._sizeInBytes * 8) / 7);
}
return this._sizeInBytes;
},
},
_target: {
get: function () {
return this._textureTarget;
},
},
});
/**
* Set up a sampler for use with a texture3D
* @param {Texture3D} texture3D The texture3D to be sampled by this sampler
* @param {Sampler} sampler Information about how to sample the texture3D
* @private
*/
function setupSampler(texture3D, sampler) {
let { minificationFilter, magnificationFilter } = sampler;
const mipmap = [
TextureMinificationFilter.NEAREST_MIPMAP_NEAREST,
TextureMinificationFilter.NEAREST_MIPMAP_LINEAR,
TextureMinificationFilter.LINEAR_MIPMAP_NEAREST,
TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
].includes(minificationFilter);
const context = texture3D._context;
const pixelFormat = texture3D._pixelFormat;
const pixelDatatype = texture3D._pixelDatatype;
// float textures only support nearest filtering unless the linear extensions are supported
if (
(pixelDatatype === PixelDatatype.FLOAT && !context.textureFloatLinear) ||
(pixelDatatype === PixelDatatype.HALF_FLOAT &&
!context.textureHalfFloatLinear)
) {
// override the sampler's settings
minificationFilter = mipmap
? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST
: TextureMinificationFilter.NEAREST;
magnificationFilter = TextureMagnificationFilter.NEAREST;
}
// WebGL 2 depth texture3D only support nearest filtering. See section 3.8.13 OpenGL ES 3 spec
if (PixelFormat.isDepthFormat(pixelFormat)) {
minificationFilter = TextureMinificationFilter.NEAREST;
magnificationFilter = TextureMagnificationFilter.NEAREST;
}
const gl = context._gl;
const target = texture3D._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, texture3D._texture);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
gl.texParameteri(target, gl.TEXTURE_WRAP_R, sampler.wrapR);
gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
if (defined(texture3D._textureFilterAnisotropic)) {
gl.texParameteri(
target,
texture3D._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT,
sampler.maximumAnisotropy,
);
}
gl.bindTexture(target, null);
}
/**
* @param {MipmapHint} [hint=MipmapHint.DONT_CARE] optional.
* @private
* @exception {DeveloperError} Cannot call generateMipmap when the texture3D pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
* @exception {DeveloperError} Cannot call generateMipmap when the texture3D pixel format is a compressed format.
* @exception {DeveloperError} hint is invalid.
* @exception {DeveloperError} This texture3D's width must be a power of two to call generateMipmap() in a WebGL1 context.
* @exception {DeveloperError} This texture3D's height must be a power of two to call generateMipmap() in a WebGL1 context.
* @exception {DeveloperError} This texture3D was destroyed, i.e., destroy() was called.
*/
Texture3D.prototype.generateMipmap = function (hint) {
hint = hint ?? MipmapHint.DONT_CARE;
//>>includeStart('debug', pragmas.debug);
if (PixelFormat.isDepthFormat(this._pixelFormat)) {
throw new DeveloperError(
"Cannot call generateMipmap when the texture3D pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.",
);
}
if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
throw new DeveloperError(
"Cannot call generateMipmap with a compressed pixel format.",
);
}
if (!MipmapHint.validate(hint)) {
throw new DeveloperError("hint is invalid.");
}
//>>includeEnd('debug');
this._hasMipmap = true;
const gl = this._context._gl;
const target = this._textureTarget;
gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.generateMipmap(target);
gl.bindTexture(target, null);
};
Texture3D.prototype.isDestroyed = function () {
return false;
};
Texture3D.prototype.destroy = function () {
this._context._gl.deleteTexture(this._texture);
return destroyObject(this);
};
export default Texture3D;
+833
View File
@@ -0,0 +1,833 @@
import BoundingRectangle from "../Core/BoundingRectangle.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Check from "../Core/Check.js";
import createGuid from "../Core/createGuid.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import CesiumMath from "../Core/Math.js";
import PixelFormat from "../Core/PixelFormat.js";
import Resource from "../Core/Resource.js";
import RuntimeError from "../Core/RuntimeError.js";
import TexturePacker from "../Core/TexturePacker.js";
import Framebuffer from "./Framebuffer.js";
import Texture from "./Texture.js";
const defaultInitialDimensions = 16;
/**
* A TextureAtlas stores multiple images in one∂ texture and keeps
* track of the texture coordinates for each image. A TextureAtlas is dynamic,
* meaning new images can be added at any point in time.
* Texture coordinates are subject to change if the texture atlas resizes, so it's
* important to check {@link TextureAtlas#guid} before using old values.
*
* @alias TextureAtlas
* @constructor
*
* @param {object} options Object with the following properties:
* @param {PixelFormat} [options.pixelFormat=PixelFormat.RGBA] The pixel format of the texture.
* @param {Sampler} [options.sampler=new Sampler()] Information about how to sample the texture.
* @param {number} [options.borderWidthInPixels=1] The amount of spacing between adjacent images in pixels.
* @param {Cartesian2} [options.initialSize=new Cartesian2(16.0, 16.0)] The initial side lengths of the texture.
*
* @exception {DeveloperError} borderWidthInPixels must be greater than or equal to zero.
* @exception {DeveloperError} initialSize must be greater than zero.
*
* @private
*/
function TextureAtlas(options) {
options = options ?? Frozen.EMPTY_OBJECT;
const borderWidthInPixels = options.borderWidthInPixels ?? 1.0;
const initialSize =
options.initialSize ??
new Cartesian2(defaultInitialDimensions, defaultInitialDimensions);
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals(
"options.borderWidthInPixels",
borderWidthInPixels,
0,
);
Check.typeOf.number.greaterThan("options.initialSize.x", initialSize.x, 0);
Check.typeOf.number.greaterThan("options.initialSize.y", initialSize.y, 0);
//>>includeEnd('debug');
this._pixelFormat = options.pixelFormat ?? PixelFormat.RGBA;
this._sampler = options.sampler;
this._borderWidthInPixels = borderWidthInPixels;
this._initialSize = initialSize;
this._texturePacker = undefined;
/** @type {BoundingRectangle[]} */
this._rectangles = [];
/** @type {Map<number, number>} */
this._subRegions = new Map();
this._guid = createGuid();
this._imagesToAddQueue = [];
/** @type {Map<string, number>} */
this._indexById = new Map();
/** @type {Map<string, Promise<number>>} */
this._indexPromiseById = new Map();
this._nextIndex = 0;
}
Object.defineProperties(TextureAtlas.prototype, {
/**
* The amount of spacing between adjacent images in pixels.
* @memberof TextureAtlas.prototype
* @type {number}
* @readonly
* @private
*/
borderWidthInPixels: {
get: function () {
return this._borderWidthInPixels;
},
},
/**
* An array of {@link BoundingRectangle} pixel offset and dimensions for all the images in the texture atlas.
* The x and y values of the rectangle correspond to the bottom-left corner of the texture coordinate.
* If the index is a subregion of an existing image, thea and y values are specified as offsets relative to the parent.
* The coordinates are in the order that the corresponding images were added to the atlas.
* @memberof TextureAtlas.prototype
* @type {BoundingRectangle[]}
* @readonly
* @private
*/
rectangles: {
get: function () {
return this._rectangles;
},
},
/**
* The texture that all of the images are being written to. The value will be <code>undefined</code> until the first update.
* @memberof TextureAtlas.prototype
* @type {Texture|undefined}
* @readonly
* @private
*/
texture: {
get: function () {
return this._texture;
},
},
/**
* The pixel format of the texture.
* @memberof TextureAtlas.prototype
* @type {PixelFormat}
* @readonly
* @private
*/
pixelFormat: {
get: function () {
return this._pixelFormat;
},
},
/**
* The sampler to use when sampling this texture. If <code>undefined</code>, the default sampler is used.
* @memberof TextureAtlas.prototype
* @type {Sampler|undefined}
* @readonly
* @private
*/
sampler: {
get: function () {
return this._sampler;
},
},
/**
* The number of images in the texture atlas. This value increases
* every time addImage or addImageSubRegion is called.
* Texture coordinates are subject to change if the texture atlas resizes, so it is
* important to check {@link TextureAtlas#guid} before using old values.
* @memberof TextureAtlas.prototype
* @type {number}
* @readonly
* @private
*/
numberOfImages: {
get: function () {
return this._nextIndex;
},
},
/**
* The atlas' globally unique identifier (GUID).
* The GUID changes whenever the texture atlas is modified.
* Classes that use a texture atlas should check if the GUID
* has changed before processing the atlas data.
* @memberof TextureAtlas.prototype
* @type {string}
* @readonly
* @private
*/
guid: {
get: function () {
return this._guid;
},
},
/**
* Returns the size in bytes of the texture.
* @memberof TextureAtlas.prototype
* @type {number}
* @readonly
* @private
*/
sizeInBytes: {
get: function () {
if (!defined(this._texture)) {
return 0;
}
return this._texture.sizeInBytes;
},
},
});
/**
* Get the texture coordinates for reading the associated image in shaders.
* @param {number} index The index of the image region.
* @param {BoundingRectangle} [result] The object into which to store the result.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @private
* @example
* const index = await atlas.addImage("myImage", image);
* const rectangle = atlas.computeTextureCoordinates(index);
* BoundingRectangle.pack(rectangle, bufferView);
*/
TextureAtlas.prototype.computeTextureCoordinates = function (index, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("index", index, 0);
//>>includeEnd('debug');
const texture = this._texture;
const rectangle = this._rectangles[index];
if (!defined(result)) {
result = new BoundingRectangle();
}
if (!defined(rectangle)) {
result.x = 0;
result.y = 0;
result.width = 0;
result.height = 0;
return result;
}
const atlasWidth = texture.width;
const atlasHeight = texture.height;
const width = rectangle.width;
const height = rectangle.height;
let x = rectangle.x;
let y = rectangle.y;
const parentIndex = this._subRegions.get(index);
if (defined(parentIndex)) {
const parentRectangle = this._rectangles[parentIndex];
x += parentRectangle.x;
y += parentRectangle.y;
}
result.x = x / atlasWidth;
result.y = y / atlasHeight;
result.width = width / atlasWidth;
result.height = height / atlasHeight;
return result;
};
/**
* Perform a WebGL texture copy for each existing image from its previous packed position to its new packed position in the new texture.
* @param {Context} context The rendering context
* @param {number} width The pixel width of the texture
* @param {number} height The pixel height of the texture
* @param {BoundingRectangle[]} rectangles The packed bounding rectangles for the reszied texture
* @param {number} queueOffset Index of the last queued item that was successfully packed
* @private
*/
TextureAtlas.prototype._copyFromTexture = function (
context,
width,
height,
rectangles,
) {
const pixelFormat = this._pixelFormat;
const sampler = this._sampler;
const newTexture = new Texture({
context,
height,
width,
pixelFormat,
sampler,
});
const gl = context._gl;
const target = newTexture._textureTarget;
const oldTexture = this._texture;
const framebuffer = new Framebuffer({
context,
colorTextures: [oldTexture],
destroyAttachments: false,
});
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, newTexture._texture);
framebuffer._bind();
// Copy any textures from the old atlas to its new position in the new atlas
const oldRectangles = this.rectangles;
const subRegions = this._subRegions;
for (let index = 0; index < oldRectangles.length; ++index) {
const rectangle = rectangles[index];
const frameBufferOffset = oldRectangles[index];
if (
!defined(rectangle) ||
!defined(frameBufferOffset) ||
defined(subRegions.get(index)) // The rectangle corresponds to a subregion of a parent image
) {
continue;
}
const { x, y, width, height } = rectangle;
gl.copyTexSubImage2D(
target,
0,
x,
y,
frameBufferOffset.x,
frameBufferOffset.y,
width,
height,
);
}
gl.bindTexture(target, null);
newTexture._initialized = true;
framebuffer._unBind();
framebuffer.destroy();
return newTexture;
};
/**
* Recreates the texture atlas texture with new dimensions and repacks images as needed.
* @param {Context} context The rendering context
* @param {number} [queueOffset = 0] Index of the last queued item that was successfully packed
* @private
*/
TextureAtlas.prototype._resize = function (context, queueOffset = 0) {
const borderPadding = this._borderWidthInPixels;
const oldRectangles = this._rectangles;
const queue = this._imagesToAddQueue;
const oldTexture = this._texture;
let width = oldTexture.width;
let height = oldTexture.height;
// Get the rectangles (width and height) of the current set of images,
// ignoring the subregions, which don't get packed
const subRegions = this._subRegions;
const toPack = oldRectangles
.map((image, index) => {
return new AddImageRequest({ index, image });
})
.filter(
(request, index) =>
defined(request.image) && !defined(subRegions.get(index)),
);
// Add the new set of images
let maxWidth = 0;
let maxHeight = 0;
let areaQueued = 0;
for (let i = queueOffset; i < queue.length; ++i) {
const { width, height } = queue[i].image;
maxWidth = Math.max(maxWidth, width);
maxHeight = Math.max(maxHeight, height);
areaQueued += width * height;
toPack.push(queue[i]);
}
// At minimum, atlas must fit its largest input images. Texture coordinates are
// compressed to 01 with 12-bit precision, so use power-of-two size to align pixels.
width = CesiumMath.nextPowerOfTwo(Math.max(maxWidth, width));
height = CesiumMath.nextPowerOfTwo(Math.max(maxHeight, height));
// Iteratively double the smallest dimension until atlas area is (approximately) sufficient.
while (areaQueued >= width * height) {
if (width > height) {
height *= 2;
} else {
width *= 2;
}
}
toPack.sort(
({ image: imageA }, { image: imageB }) =>
imageB.height * imageB.width - imageA.height * imageA.width,
);
const newRectangles = new Array(this._nextIndex);
for (const index of this._subRegions.keys()) {
// Subregions are specified relative to their parents,
// so we can copy them directly
if (defined(subRegions.get(index))) {
newRectangles[index] = oldRectangles[index];
}
}
let texturePacker,
packed = false;
while (!packed) {
texturePacker = new TexturePacker({ height, width, borderPadding });
let i;
for (i = 0; i < toPack.length; ++i) {
const { index, image } = toPack[i];
if (!defined(image)) {
continue;
}
const repackedNode = texturePacker.pack(index, image);
if (!defined(repackedNode)) {
// Could not fit everything into the new texture.
// Scale texture size and try again
if (width > height) {
// Resize height
height *= 2.0;
} else {
// Resize width
width *= 2.0;
}
break;
}
newRectangles[index] = repackedNode.rectangle;
}
packed = i === toPack.length;
}
this._texturePacker = texturePacker;
this._texture = this._copyFromTexture(context, width, height, newRectangles);
oldTexture.destroy();
this._rectangles = newRectangles;
this._guid = createGuid();
};
/**
* Return the index of the image region for the specified ID. If the image is already in the atlas, the existing index is returned. Otherwise, the result is undefined.
* @param {string} id An identifier to detect whether the image already exists in the atlas.
* @returns {number|undefined} The image index, or undefined if the image does not exist in the atlas.
* @private
*/
TextureAtlas.prototype.getImageIndex = function (id) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("id", id);
//>>includeEnd('debug');
return this._indexById.get(id);
};
/**
* Copy image data into the underlying texture atlas.
* @param {AddImageRequest} imageRequest The data needed to resolve the call to addImage in the queue
* @private
*/
TextureAtlas.prototype._copyImageToTexture = function ({
index,
image,
resolve,
reject,
}) {
const texture = this._texture;
const rectangle = this._rectangles[index];
try {
texture.copyFrom({
source: image,
xOffset: rectangle.x,
yOffset: rectangle.y,
});
if (defined(resolve)) {
resolve(index);
}
} catch (e) {
if (defined(reject)) {
reject(e);
return;
}
}
};
/**
* Info needed to add a queued image to the texture atlas when update operatons are executed, typically at the end of a frame.
* @constructor
* @private
* @param {object} options Object with the following properties:
* @param {number} options.index An identifier
* @param {TexturePacker.PackableObject} options.image An object, such as an <code>Image</code> with <code>width</code> and <code>height</code> properties in pixels
* @param {function} [options.resolve] The promise resolver
* @param {function} [options.reject] The promise rejecter
*/
function AddImageRequest({ index, image, resolve, reject }) {
this.index = index;
this.image = image;
this.resolve = resolve;
this.reject = reject;
this.rectangle = undefined;
}
/**
* Adds an image to the queue for this frame.
* The image will be copied to the texture at the end of the frame, resizing the texture if needed.
*
* @private
* @param {number} index An identifier
* @param {TexturePacker.PackableObject} image An object, such as an <code>Image</code> with <code>width</code> and <code>height</code> properties in pixels
* @returns {Promise<number>} Promise which resolves to the image index once the image has been added, or rejects if there was an error. The promise resolves to <code>-1</code> if the texture atlas is destoyed in the interim.
*/
TextureAtlas.prototype._addImage = function (index, image) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("index", index, 0);
Check.defined("image", image);
//>>includeEnd('debug');
return new Promise((resolve, reject) => {
this._imagesToAddQueue.push(
new AddImageRequest({
index,
image,
resolve,
reject,
}),
);
this._imagesToAddQueue.sort(
({ image: imageA }, { image: imageB }) =>
imageB.height * imageB.width - imageA.height * imageA.width,
);
});
};
/**
* Process the image queue for this frame, copying to the texture atlas and resizing the texture as needed.
* @private
* @param {Context} context The rendering context
* @return {boolean} true if the texture was updated this frame
*/
TextureAtlas.prototype._processImageQueue = function (context) {
const queue = this._imagesToAddQueue;
if (queue.length === 0) {
return false;
}
this._rectangles.length = this._nextIndex;
let i, error;
for (i = 0; i < queue.length; ++i) {
const imageRequest = queue[i];
const { image, index } = imageRequest;
const node = this._texturePacker.pack(index, image);
if (!defined(node)) {
// Atlas cannot fit all images in the queue
// Bail early and resize
try {
this._resize(context, i);
} catch (e) {
error = e;
if (defined(imageRequest.reject)) {
imageRequest.reject(error);
}
}
break;
}
this._rectangles[index] = node.rectangle;
}
if (defined(error)) {
for (i = i + 1; i < queue.length; ++i) {
const { resolve } = queue[i];
if (defined(resolve)) {
resolve(-1);
}
}
queue.length = 0;
return false;
}
for (let i = 0; i < queue.length; ++i) {
this._copyImageToTexture(queue[i]);
}
queue.length = 0;
return true;
};
/**
* Processes any updates queued this frame, and updates rendering resources accordingly. Call before or after a frame has been rendered to avoid any race conditions for any dependant render commands.
* @private
* @param {Context} context The rendering context
* @return {boolean} true if rendering resources were updated.
*/
TextureAtlas.prototype.update = function (context) {
if (!defined(this._texture)) {
const width = this._initialSize.x;
const height = this._initialSize.y;
const pixelFormat = this._pixelFormat;
const sampler = this._sampler;
const borderPadding = this._borderWidthInPixels;
this._texture = new Texture({
context,
width,
height,
pixelFormat,
sampler,
});
this._texturePacker = new TexturePacker({
height,
width,
borderPadding,
});
}
return this._processImageQueue(context);
};
async function resolveImage(image, id) {
if (typeof image === "function") {
image = image(id);
}
if (typeof image === "string" || image instanceof Resource) {
// Fetch the resource
const resource = Resource.createIfNeeded(image);
image = resource.fetchImage();
}
return image;
}
/**
* Adds an image to the atlas. If the image is already in the atlas, the atlas is unchanged and
* the existing index is used.
* @private
* @param {string} id An identifier to detect whether the image already exists in the atlas.
* @param {HTMLImageElement|HTMLCanvasElement|string|Resource|Promise|TextureAtlas.CreateImageCallback} image An image or canvas to add to the texture atlas,
* or a URL to an Image, or a Promise for an image, or a function that creates an image.
* @param {number} width A number specifying the width of the texture. If undefined, the image width will be used.
* @param {number} height A number specifying the height of the texture. If undefined, the image height will be used.
* @returns {Promise<number> | number} The image region index or a promise that resolves to it. -1 is returned if resources are in the process of being destroyed.
*/
TextureAtlas.prototype.addImage = function (id, image, width, height) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("id", id);
Check.defined("image", image);
//>>includeEnd('debug');
let promise = this._indexPromiseById.get(id);
let index = this._indexById.get(id);
if (defined(promise)) {
// This image is already being added
return promise;
}
if (defined(index)) {
// This image has already been added and resolved
return index;
}
index = this._nextIndex++;
this._indexById.set(id, index);
const resolveAndAddImage = async () => {
const resolvedImage = await resolveImage(image, id);
//>>includeStart('debug', pragmas.debug);
Check.defined("image", resolvedImage);
//>>includeEnd('debug');
if (this.isDestroyed() || !defined(resolvedImage)) {
this._indexPromiseById.delete(id);
return -1;
}
if (defined(width)) {
resolvedImage.width = width;
}
if (defined(height)) {
resolvedImage.height = height;
}
const imageIndex = await this._addImage(index, resolvedImage);
this._indexPromiseById.delete(id);
return imageIndex;
};
promise = resolveAndAddImage();
this._indexPromiseById.set(id, promise);
return promise;
};
/**
* Get an existing sub-region of an existing atlas image as additional image indices.
* @private
* @param {string} id The identifier of the existing image.
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
* @param {number} imageIndex The index of the image.
* @returns {Promise<number> | number | undefined} The existing subRegion index, or undefined if not yet added.
*/
TextureAtlas.prototype.getCachedImageSubRegion = function (
id,
subRegion,
imageIndex,
) {
const imagePromise = this._indexPromiseById.get(id);
for (const [index, parentIndex] of this._subRegions.entries()) {
if (imageIndex === parentIndex) {
const boundingRegion = this._rectangles[index];
if (boundingRegion.equals(subRegion)) {
// The subregion is already being tracked
if (imagePromise) {
return imagePromise.then((resolvedImageIndex) =>
resolvedImageIndex === -1 ? -1 : index,
);
}
return index;
}
}
}
};
/**
* Add a sub-region of an existing atlas image as additional image indices.
* @private
* @param {string} id The identifier of the existing image.
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
* @returns {number | Promise<number>} The resolved image region index, or a Promise that resolves to it. -1 is returned if resources are in the process of being destroyed.
*/
TextureAtlas.prototype.addImageSubRegion = function (id, subRegion) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("id", id);
Check.defined("subRegion", subRegion);
//>>includeEnd('debug');
const imageIndex = this._indexById.get(id);
if (!defined(imageIndex)) {
throw new RuntimeError(`image with id "${id}" not found in the atlas.`);
}
let index = this.getCachedImageSubRegion(id, subRegion, imageIndex);
if (defined(index)) {
return index;
}
index = this._nextIndex++;
this._subRegions.set(index, imageIndex);
this._rectangles[index] = subRegion.clone();
const indexPromise =
this._indexPromiseById.get(id) ?? Promise.resolve(imageIndex);
return indexPromise.then((imageIndex) => {
if (imageIndex === -1) {
// The atlas has been destroyed
return -1;
}
const rectangle = this._rectangles[imageIndex];
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.lessThanOrEquals(
"subRegion.x",
subRegion.x,
rectangle.width,
);
Check.typeOf.number.lessThanOrEquals(
"subRegion.x + subRegion.width",
subRegion.x + subRegion.width,
rectangle.width,
);
Check.typeOf.number.lessThanOrEquals(
"subRegion.y",
subRegion.y,
rectangle.height,
);
Check.typeOf.number.lessThanOrEquals(
"subRegion.y + subRegion.height",
subRegion.y + subRegion.height,
rectangle.height,
);
//>>includeEnd('debug');
return index;
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
* <br /><br />
* If this object was destroyed, it should not be used; calling any function other than
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
* @private
* @returns {boolean} True if this object was destroyed; otherwise, false.
* @see TextureAtlas#destroy
*/
TextureAtlas.prototype.isDestroyed = function () {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
* <br /><br />
* Once an object is destroyed, it should not be used; calling any function other than
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (<code>undefined</code>) to the object as done in the example.
* @private
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
* @example
* atlas = atlas && atlas.destroy();
* @see TextureAtlas#isDestroyed
*/
TextureAtlas.prototype.destroy = function () {
this._texture = this._texture && this._texture.destroy();
this._imagesToAddQueue.forEach(({ resolve }) => {
if (defined(resolve)) {
resolve(-1);
}
});
return destroyObject(this);
};
/**
* A function that creates an image.
* @private
* @callback TextureAtlas.CreateImageCallback
* @param {string} id The identifier of the image to load.
* @returns {HTMLImageElement|Promise<HTMLImageElement>} The image, or a promise that will resolve to an image.
*/
export default TextureAtlas;
+81
View File
@@ -0,0 +1,81 @@
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
/**
* @private
*/
function TextureCache() {
this._textures = {};
this._numberOfTextures = 0;
this._texturesToRelease = {};
}
Object.defineProperties(TextureCache.prototype, {
numberOfTextures: {
get: function () {
return this._numberOfTextures;
},
},
});
TextureCache.prototype.getTexture = function (keyword) {
const cachedTexture = this._textures[keyword];
if (!defined(cachedTexture)) {
return undefined;
}
// No longer want to release this if it was previously released.
delete this._texturesToRelease[keyword];
++cachedTexture.count;
return cachedTexture.texture;
};
TextureCache.prototype.addTexture = function (keyword, texture) {
const cachedTexture = {
texture: texture,
count: 1,
};
texture.finalDestroy = texture.destroy;
const that = this;
texture.destroy = function () {
if (--cachedTexture.count === 0) {
that._texturesToRelease[keyword] = cachedTexture;
}
};
this._textures[keyword] = cachedTexture;
++this._numberOfTextures;
};
TextureCache.prototype.destroyReleasedTextures = function () {
const texturesToRelease = this._texturesToRelease;
for (const keyword in texturesToRelease) {
if (texturesToRelease.hasOwnProperty(keyword)) {
const cachedTexture = texturesToRelease[keyword];
delete this._textures[keyword];
cachedTexture.texture.finalDestroy();
--this._numberOfTextures;
}
}
this._texturesToRelease = {};
};
TextureCache.prototype.isDestroyed = function () {
return false;
};
TextureCache.prototype.destroy = function () {
const textures = this._textures;
for (const keyword in textures) {
if (textures.hasOwnProperty(keyword)) {
textures[keyword].texture.finalDestroy();
}
}
return destroyObject(this);
};
export default TextureCache;
@@ -0,0 +1,43 @@
import WebGLConstants from "../Core/WebGLConstants.js";
/**
* Enumerates all possible filters used when magnifying WebGL textures.
*
* @enum {number}
*
* @see TextureMinificationFilter
*/
const TextureMagnificationFilter = {
/**
* Samples the texture by returning the closest pixel.
*
* @type {number}
* @constant
*/
NEAREST: WebGLConstants.NEAREST,
/**
* Samples the texture through bi-linear interpolation of the four nearest pixels. This produces smoother results than <code>NEAREST</code> filtering.
*
* @type {number}
* @constant
*/
LINEAR: WebGLConstants.LINEAR,
};
/**
* Validates the given <code>textureMinificationFilter</code> with respect to the possible enum values.
* @param textureMagnificationFilter
* @returns {boolean} <code>true</code> if <code>textureMagnificationFilter</code> is valid.
*
* @private
*/
TextureMagnificationFilter.validate = function (textureMagnificationFilter) {
return (
textureMagnificationFilter === TextureMagnificationFilter.NEAREST ||
textureMagnificationFilter === TextureMagnificationFilter.LINEAR
);
};
Object.freeze(TextureMagnificationFilter);
export default TextureMagnificationFilter;
@@ -0,0 +1,96 @@
import WebGLConstants from "../Core/WebGLConstants.js";
/**
* Enumerates all possible filters used when minifying WebGL textures.
*
* @enum {number}
*
* @see TextureMagnificationFilter
*/
const TextureMinificationFilter = {
/**
* Samples the texture by returning the closest pixel.
*
* @type {number}
* @constant
*/
NEAREST: WebGLConstants.NEAREST,
/**
* Samples the texture through bi-linear interpolation of the four nearest pixels. This produces smoother results than <code>NEAREST</code> filtering.
*
* @type {number}
* @constant
*/
LINEAR: WebGLConstants.LINEAR,
/**
* Selects the nearest mip level and applies nearest sampling within that level.
* <p>
* Requires that the texture has a mipmap. The mip level is chosen by the view angle and screen-space size of the texture.
* </p>
*
* @type {number}
* @constant
*/
NEAREST_MIPMAP_NEAREST: WebGLConstants.NEAREST_MIPMAP_NEAREST,
/**
* Selects the nearest mip level and applies linear sampling within that level.
* <p>
* Requires that the texture has a mipmap. The mip level is chosen by the view angle and screen-space size of the texture.
* </p>
*
* @type {number}
* @constant
*/
LINEAR_MIPMAP_NEAREST: WebGLConstants.LINEAR_MIPMAP_NEAREST,
/**
* Read texture values with nearest sampling from two adjacent mip levels and linearly interpolate the results.
* <p>
* This option provides a good balance of visual quality and speed when sampling from a mipmapped texture.
* </p>
* <p>
* Requires that the texture has a mipmap. The mip level is chosen by the view angle and screen-space size of the texture.
* </p>
*
* @type {number}
* @constant
*/
NEAREST_MIPMAP_LINEAR: WebGLConstants.NEAREST_MIPMAP_LINEAR,
/**
* Read texture values with linear sampling from two adjacent mip levels and linearly interpolate the results.
* <p>
* This option provides a good balance of visual quality and speed when sampling from a mipmapped texture.
* </p>
* <p>
* Requires that the texture has a mipmap. The mip level is chosen by the view angle and screen-space size of the texture.
* </p>
* @type {number}
* @constant
*/
LINEAR_MIPMAP_LINEAR: WebGLConstants.LINEAR_MIPMAP_LINEAR,
};
/**
* Validates the given <code>textureMinificationFilter</code> with respect to the possible enum values.
*
* @private
*
* @param textureMinificationFilter
* @returns {boolean} <code>true</code> if <code>textureMinificationFilter</code> is valid.
*/
TextureMinificationFilter.validate = function (textureMinificationFilter) {
return (
textureMinificationFilter === TextureMinificationFilter.NEAREST ||
textureMinificationFilter === TextureMinificationFilter.LINEAR ||
textureMinificationFilter ===
TextureMinificationFilter.NEAREST_MIPMAP_NEAREST ||
textureMinificationFilter ===
TextureMinificationFilter.LINEAR_MIPMAP_NEAREST ||
textureMinificationFilter ===
TextureMinificationFilter.NEAREST_MIPMAP_LINEAR ||
textureMinificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR
);
};
Object.freeze(TextureMinificationFilter);
export default TextureMinificationFilter;
+23
View File
@@ -0,0 +1,23 @@
import WebGLConstants from "../Core/WebGLConstants.js";
/**
* @enum {number}
* @private
*/
const TextureWrap = {
CLAMP_TO_EDGE: WebGLConstants.CLAMP_TO_EDGE,
REPEAT: WebGLConstants.REPEAT,
MIRRORED_REPEAT: WebGLConstants.MIRRORED_REPEAT,
validate: function (textureWrap) {
return (
textureWrap === TextureWrap.CLAMP_TO_EDGE ||
textureWrap === TextureWrap.REPEAT ||
textureWrap === TextureWrap.MIRRORED_REPEAT
);
},
};
Object.freeze(TextureWrap);
export default TextureWrap;
File diff suppressed because it is too large Load Diff
+949
View File
@@ -0,0 +1,949 @@
import Check from "../Core/Check.js";
import ComponentDatatype from "../Core/ComponentDatatype.js";
import Frozen from "../Core/Frozen.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 IndexDatatype from "../Core/IndexDatatype.js";
import CesiumMath from "../Core/Math.js";
import RuntimeError from "../Core/RuntimeError.js";
import Buffer from "./Buffer.js";
import BufferUsage from "./BufferUsage.js";
import ContextLimits from "./ContextLimits.js";
import AttributeType from "../Scene/AttributeType.js";
import assert from "../Core/assert.js";
/** @import {TypedArray, TypedArrayConstructor} from "../Core/globalTypes.js"; */
/** @ignore */
function addAttribute(attributes, attribute, index, context) {
const hasVertexBuffer = defined(attribute.vertexBuffer);
const hasValue = defined(attribute.value);
const componentsPerAttribute = attribute.value
? attribute.value.length
: attribute.componentsPerAttribute;
//>>includeStart('debug', pragmas.debug);
if (!hasVertexBuffer && !hasValue) {
throw new DeveloperError("attribute must have a vertexBuffer or a value.");
}
if (hasVertexBuffer && hasValue) {
throw new DeveloperError(
"attribute cannot have both a vertexBuffer and a value. It must have either a vertexBuffer property defining per-vertex data or a value property defining data for all vertices.",
);
}
if (
componentsPerAttribute !== 1 &&
componentsPerAttribute !== 2 &&
componentsPerAttribute !== 3 &&
componentsPerAttribute !== 4
) {
if (hasValue) {
throw new DeveloperError(
"attribute.value.length must be in the range [1, 4].",
);
}
throw new DeveloperError(
"attribute.componentsPerAttribute must be in the range [1, 4].",
);
}
if (
defined(attribute.componentDatatype) &&
!ComponentDatatype.validate(attribute.componentDatatype)
) {
throw new DeveloperError(
"attribute must have a valid componentDatatype or not specify it.",
);
}
if (defined(attribute.strideInBytes) && attribute.strideInBytes > 255) {
// WebGL limit. Not in GL ES.
throw new DeveloperError(
"attribute must have a strideInBytes less than or equal to 255 or not specify it.",
);
}
if (
defined(attribute.instanceDivisor) &&
attribute.instanceDivisor > 0 &&
!context.instancedArrays
) {
throw new DeveloperError("instanced arrays is not supported");
}
if (defined(attribute.instanceDivisor) && attribute.instanceDivisor < 0) {
throw new DeveloperError(
"attribute must have an instanceDivisor greater than or equal to zero",
);
}
if (defined(attribute.instanceDivisor) && hasValue) {
throw new DeveloperError(
"attribute cannot have have an instanceDivisor if it is not backed by a buffer",
);
}
if (
defined(attribute.instanceDivisor) &&
attribute.instanceDivisor > 0 &&
attribute.index === 0
) {
throw new DeveloperError(
"attribute zero cannot have an instanceDivisor greater than 0",
);
}
//>>includeEnd('debug');
// Shallow copy the attribute; we do not want to copy the vertex buffer.
const attr = {
index: attribute.index ?? index,
enabled: attribute.enabled ?? true,
vertexBuffer: attribute.vertexBuffer,
value: hasValue ? attribute.value.slice(0) : undefined,
componentsPerAttribute: componentsPerAttribute,
componentDatatype: attribute.componentDatatype ?? ComponentDatatype.FLOAT,
normalize: attribute.normalize ?? false,
offsetInBytes: attribute.offsetInBytes ?? 0,
strideInBytes: attribute.strideInBytes ?? 0,
instanceDivisor: attribute.instanceDivisor ?? 0,
};
if (hasVertexBuffer) {
// Common case: vertex buffer for per-vertex data
attr.vertexAttrib = function (gl) {
const index = this.index;
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer._getBuffer());
gl.vertexAttribPointer(
index,
this.componentsPerAttribute,
this.componentDatatype,
this.normalize,
this.strideInBytes,
this.offsetInBytes,
);
gl.enableVertexAttribArray(index);
if (this.instanceDivisor > 0) {
context.glVertexAttribDivisor(index, this.instanceDivisor);
context._vertexAttribDivisors[index] = this.instanceDivisor;
context._previousDrawInstanced = true;
}
};
attr.disableVertexAttribArray = function (gl) {
gl.disableVertexAttribArray(this.index);
if (this.instanceDivisor > 0) {
context.glVertexAttribDivisor(index, 0);
}
};
} else {
// Less common case: value array for the same data for each vertex
switch (attr.componentsPerAttribute) {
case 1:
attr.vertexAttrib = function (gl) {
gl.vertexAttrib1fv(this.index, this.value);
};
break;
case 2:
attr.vertexAttrib = function (gl) {
gl.vertexAttrib2fv(this.index, this.value);
};
break;
case 3:
attr.vertexAttrib = function (gl) {
gl.vertexAttrib3fv(this.index, this.value);
};
break;
case 4:
attr.vertexAttrib = function (gl) {
gl.vertexAttrib4fv(this.index, this.value);
};
break;
}
attr.disableVertexAttribArray = function (gl) {};
}
attributes.push(attr);
}
function bind(gl, attributes, indexBuffer) {
for (let i = 0; i < attributes.length; ++i) {
const attribute = attributes[i];
if (attribute.enabled) {
attribute.vertexAttrib(gl);
}
}
if (defined(indexBuffer)) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer._getBuffer());
}
}
/**
* Creates a vertex array, which defines the attributes making up a vertex, and contains an optional index buffer
* to select vertices for rendering. Attributes are defined using object literals as shown in Example 1 below.
*
* @param {object} options Object with the following properties:
* @param {Context} options.context The context in which the VertexArray gets created.
* @param {object[]} options.attributes An array of attributes.
* @param {IndexBuffer} [options.indexBuffer] An optional index buffer.
*
* @returns {VertexArray} The vertex array, ready for use with drawing.
*
* @exception {DeveloperError} Attribute must have a <code>vertexBuffer</code>.
* @exception {DeveloperError} Attribute must have a <code>componentsPerAttribute</code>.
* @exception {DeveloperError} Attribute must have a valid <code>componentDatatype</code> or not specify it.
* @exception {DeveloperError} Attribute must have a <code>strideInBytes</code> less than or equal to 255 or not specify it.
* @exception {DeveloperError} Index n is used by more than one attribute.
*
*
* @example
* // Example 1. Create a vertex array with vertices made up of three floating point
* // values, e.g., a position, from a single vertex buffer. No index buffer is used.
* const positionBuffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 12,
* usage : BufferUsage.STATIC_DRAW
* });
* const attributes = [
* {
* index : 0,
* enabled : true,
* vertexBuffer : positionBuffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT,
* normalize : false,
* offsetInBytes : 0,
* strideInBytes : 0 // tightly packed
* instanceDivisor : 0 // not instanced
* }
* ];
* const va = new VertexArray({
* context : context,
* attributes : attributes
* });
*
* @example
* // Example 2. Create a vertex array with vertices from two different vertex buffers.
* // Each vertex has a three-component position and three-component normal.
* const positionBuffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 12,
* usage : BufferUsage.STATIC_DRAW
* });
* const normalBuffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 12,
* usage : BufferUsage.STATIC_DRAW
* });
* const attributes = [
* {
* index : 0,
* vertexBuffer : positionBuffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT
* },
* {
* index : 1,
* vertexBuffer : normalBuffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT
* }
* ];
* const va = new VertexArray({
* context : context,
* attributes : attributes
* });
*
* @example
* // Example 3. Creates the same vertex layout as Example 2 using a single
* // vertex buffer, instead of two.
* const buffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 24,
* usage : BufferUsage.STATIC_DRAW
* });
* const attributes = [
* {
* vertexBuffer : buffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT,
* offsetInBytes : 0,
* strideInBytes : 24
* },
* {
* vertexBuffer : buffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT,
* normalize : true,
* offsetInBytes : 12,
* strideInBytes : 24
* }
* ];
* const va = new VertexArray({
* context : context,
* attributes : attributes
* });
*
* @see Buffer#createVertexBuffer
* @see Buffer#createIndexBuffer
* @see Context#draw
*
* @private
*/
function VertexArray(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
Check.defined("options.attributes", options.attributes);
//>>includeEnd('debug');
const context = options.context;
const gl = context._gl;
const attributes = options.attributes;
const indexBuffer = options.indexBuffer;
let i;
const vaAttributes = [];
let numberOfVertices = 1; // if every attribute is backed by a single value
let hasInstancedAttributes = false;
let hasConstantAttributes = false;
let length = attributes.length;
for (i = 0; i < length; ++i) {
addAttribute(vaAttributes, attributes[i], i, context);
}
length = vaAttributes.length;
for (i = 0; i < length; ++i) {
const attribute = vaAttributes[i];
if (defined(attribute.vertexBuffer) && attribute.instanceDivisor === 0) {
// This assumes that each vertex buffer in the vertex array has the same number of vertices.
const bytes =
attribute.strideInBytes ||
attribute.componentsPerAttribute *
ComponentDatatype.getSizeInBytes(attribute.componentDatatype);
numberOfVertices = attribute.vertexBuffer.sizeInBytes / bytes;
break;
}
}
for (i = 0; i < length; ++i) {
if (vaAttributes[i].instanceDivisor > 0) {
hasInstancedAttributes = true;
}
if (defined(vaAttributes[i].value)) {
hasConstantAttributes = true;
}
}
//>>includeStart('debug', pragmas.debug);
// Verify all attribute names are unique
const uniqueIndices = {};
for (i = 0; i < length; ++i) {
const index = vaAttributes[i].index;
if (uniqueIndices[index]) {
throw new DeveloperError(
`Index ${index} is used by more than one attribute.`,
);
}
uniqueIndices[index] = true;
}
//>>includeEnd('debug');
let vao;
// Setup VAO if supported
if (context.vertexArrayObject) {
vao = context.glCreateVertexArray();
context.glBindVertexArray(vao);
bind(gl, vaAttributes, indexBuffer);
context.glBindVertexArray(null);
}
this._numberOfVertices = numberOfVertices;
this._hasInstancedAttributes = hasInstancedAttributes;
this._hasConstantAttributes = hasConstantAttributes;
this._context = context;
this._gl = gl;
this._vao = vao;
this._attributes = vaAttributes;
this._indexBuffer = indexBuffer;
}
function computeNumberOfVertices(attribute) {
return attribute.values.length / attribute.componentsPerAttribute;
}
function computeAttributeSizeInBytes(attribute) {
return (
ComponentDatatype.getSizeInBytes(attribute.componentDatatype) *
attribute.componentsPerAttribute
);
}
function interleaveAttributes(attributes) {
let j;
let name;
let attribute;
// Extract attribute names.
const names = [];
for (name in attributes) {
// Attribute needs to have per-vertex values; not a constant value for all vertices.
if (
attributes.hasOwnProperty(name) &&
defined(attributes[name]) &&
defined(attributes[name].values)
) {
names.push(name);
if (attributes[name].componentDatatype === ComponentDatatype.DOUBLE) {
attributes[name].componentDatatype = ComponentDatatype.FLOAT;
attributes[name].values = ComponentDatatype.createTypedArray(
ComponentDatatype.FLOAT,
attributes[name].values,
);
}
}
}
// Validation. Compute number of vertices.
let numberOfVertices;
const namesLength = names.length;
if (namesLength > 0) {
numberOfVertices = computeNumberOfVertices(attributes[names[0]]);
for (j = 1; j < namesLength; ++j) {
const currentNumberOfVertices = computeNumberOfVertices(
attributes[names[j]],
);
if (currentNumberOfVertices !== numberOfVertices) {
throw new RuntimeError(
`${
"Each attribute list must have the same number of vertices. " +
"Attribute "
}${names[j]} has a different number of vertices ` +
`(${currentNumberOfVertices.toString()})` +
` than attribute ${names[0]} (${numberOfVertices.toString()}).`,
);
}
}
}
// Sort attributes by the size of their components. From left to right, a vertex stores floats, shorts, and then bytes.
names.sort(function (left, right) {
return (
ComponentDatatype.getSizeInBytes(attributes[right].componentDatatype) -
ComponentDatatype.getSizeInBytes(attributes[left].componentDatatype)
);
});
// Compute sizes and strides.
let vertexSizeInBytes = 0;
const offsetsInBytes = {};
for (j = 0; j < namesLength; ++j) {
name = names[j];
attribute = attributes[name];
offsetsInBytes[name] = vertexSizeInBytes;
vertexSizeInBytes += computeAttributeSizeInBytes(attribute);
}
if (vertexSizeInBytes > 0) {
// Pad each vertex to be a multiple of the largest component datatype so each
// attribute can be addressed using typed arrays.
const maxComponentSizeInBytes = ComponentDatatype.getSizeInBytes(
attributes[names[0]].componentDatatype,
); // Sorted large to small
const remainder = vertexSizeInBytes % maxComponentSizeInBytes;
if (remainder !== 0) {
vertexSizeInBytes += maxComponentSizeInBytes - remainder;
}
// Total vertex buffer size in bytes, including per-vertex padding.
const vertexBufferSizeInBytes = numberOfVertices * vertexSizeInBytes;
// Create array for interleaved vertices. Each attribute has a different view (pointer) into the array.
const buffer = new ArrayBuffer(vertexBufferSizeInBytes);
const views = {};
for (j = 0; j < namesLength; ++j) {
name = names[j];
const sizeInBytes = ComponentDatatype.getSizeInBytes(
attributes[name].componentDatatype,
);
views[name] = {
pointer: ComponentDatatype.createTypedArray(
attributes[name].componentDatatype,
buffer,
),
index: offsetsInBytes[name] / sizeInBytes, // Offset in ComponentType
strideInComponentType: vertexSizeInBytes / sizeInBytes,
};
}
// Copy attributes into one interleaved array.
// PERFORMANCE_IDEA: Can we optimize these loops?
for (j = 0; j < numberOfVertices; ++j) {
for (let n = 0; n < namesLength; ++n) {
name = names[n];
attribute = attributes[name];
const values = attribute.values;
const view = views[name];
const pointer = view.pointer;
const numberOfComponents = attribute.componentsPerAttribute;
for (let k = 0; k < numberOfComponents; ++k) {
pointer[view.index + k] = values[j * numberOfComponents + k];
}
view.index += view.strideInComponentType;
}
}
return {
buffer: buffer,
offsetsInBytes: offsetsInBytes,
vertexSizeInBytes: vertexSizeInBytes,
};
}
// No attributes to interleave.
return undefined;
}
/**
* Creates a vertex array from a geometry. A geometry contains vertex attributes and optional index data
* in system memory, whereas a vertex array contains vertex buffers and an optional index buffer in WebGL
* memory for use with rendering.
* <br /><br />
* The <code>geometry</code> argument should use the standard layout like the geometry returned by {@link BoxGeometry}.
* <br /><br />
* <code>options</code> can have four properties:
* <ul>
* <li><code>geometry</code>: The source geometry containing data used to create the vertex array.</li>
* <li><code>attributeLocations</code>: An object that maps geometry attribute names to vertex shader attribute locations.</li>
* <li><code>bufferUsage</code>: The expected usage pattern of the vertex array's buffers. On some WebGL implementations, this can significantly affect performance. See {@link BufferUsage}. Default: <code>BufferUsage.DYNAMIC_DRAW</code>.</li>
* <li><code>interleave</code>: Determines if all attributes are interleaved in a single vertex buffer or if each attribute is stored in a separate vertex buffer. Default: <code>false</code>.</li>
* </ul>
* <br />
* If <code>options</code> is not specified or the <code>geometry</code> contains no data, the returned vertex array is empty.
*
* @param {object} options An object defining the geometry, attribute indices, buffer usage, and vertex layout used to create the vertex array.
*
* @exception {RuntimeError} Each attribute list must have the same number of vertices.
* @exception {DeveloperError} The geometry must have zero or one index lists.
* @exception {DeveloperError} Index n is used by more than one attribute.
*
*
* @example
* // Example 1. Creates a vertex array for rendering a box. The default dynamic draw
* // usage is used for the created vertex and index buffer. The attributes are not
* // interleaved by default.
* const geometry = new BoxGeometry();
* const va = VertexArray.fromGeometry({
* context : context,
* geometry : geometry,
* attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
* });
*
* @example
* // Example 2. Creates a vertex array with interleaved attributes in a
* // single vertex buffer. The vertex and index buffer have static draw usage.
* const va = VertexArray.fromGeometry({
* context : context,
* geometry : geometry,
* attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
* bufferUsage : BufferUsage.STATIC_DRAW,
* interleave : true
* });
*
* @example
* // Example 3. When the caller destroys the vertex array, it also destroys the
* // attached vertex buffer(s) and index buffer.
* va = va.destroy();
*
* @see Buffer#createVertexBuffer
* @see Buffer#createIndexBuffer
* @see GeometryPipeline.createAttributeLocations
* @see ShaderProgram
*/
VertexArray.fromGeometry = function (options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.defined("options.context", options.context);
//>>includeEnd('debug');
const context = options.context;
const geometry = options.geometry ?? Frozen.EMPTY_OBJECT;
const bufferUsage = options.bufferUsage ?? BufferUsage.DYNAMIC_DRAW;
const attributeLocations = options.attributeLocations ?? Frozen.EMPTY_OBJECT;
const interleave = options.interleave ?? false;
const createdVAAttributes = options.vertexArrayAttributes;
let name;
let attribute;
let vertexBuffer;
const vaAttributes = defined(createdVAAttributes) ? createdVAAttributes : [];
const attributes = geometry.attributes;
if (interleave) {
// Use a single vertex buffer with interleaved vertices.
const interleavedAttributes = interleaveAttributes(attributes);
if (defined(interleavedAttributes)) {
vertexBuffer = Buffer.createVertexBuffer({
context: context,
typedArray: interleavedAttributes.buffer,
usage: bufferUsage,
});
const offsetsInBytes = interleavedAttributes.offsetsInBytes;
const strideInBytes = interleavedAttributes.vertexSizeInBytes;
for (name in attributes) {
if (attributes.hasOwnProperty(name) && defined(attributes[name])) {
attribute = attributes[name];
if (defined(attribute.values)) {
// Common case: per-vertex attributes
vaAttributes.push({
index: attributeLocations[name],
vertexBuffer: vertexBuffer,
componentDatatype: attribute.componentDatatype,
componentsPerAttribute: attribute.componentsPerAttribute,
normalize: attribute.normalize,
offsetInBytes: offsetsInBytes[name],
strideInBytes: strideInBytes,
});
} else {
// Constant attribute for all vertices
vaAttributes.push({
index: attributeLocations[name],
value: attribute.value,
componentDatatype: attribute.componentDatatype,
normalize: attribute.normalize,
});
}
}
}
}
} else {
// One vertex buffer per attribute.
for (name in attributes) {
if (attributes.hasOwnProperty(name) && defined(attributes[name])) {
attribute = attributes[name];
let componentDatatype = attribute.componentDatatype;
if (componentDatatype === ComponentDatatype.DOUBLE) {
componentDatatype = ComponentDatatype.FLOAT;
}
let attrProps = {};
if (defined(attribute.values)) {
vertexBuffer = Buffer.createVertexBuffer({
context: context,
typedArray: ComponentDatatype.createTypedArray(
componentDatatype,
attribute.values,
),
usage: bufferUsage,
});
attrProps = {
index: attributeLocations[name],
vertexBuffer: vertexBuffer,
value: attribute.value,
componentDatatype: componentDatatype,
componentsPerAttribute: attribute.componentsPerAttribute,
normalize: attribute.normalize,
};
}
//if we already have a typedArray lets use it
if (defined(attribute.typedArray)) {
vertexBuffer = Buffer.createVertexBuffer({
context: context,
typedArray: attribute.typedArray,
usage: bufferUsage,
});
attrProps = {
index: attributeLocations[name],
vertexBuffer: vertexBuffer,
value: undefined,
componentDatatype: componentDatatype,
componentsPerAttribute: AttributeType.getNumberOfComponents(
attribute.type,
),
normalize: attribute.normalized,
instanceDivisor: attribute.instanceDivisor,
};
}
vaAttributes.push(attrProps);
}
}
}
let indexBuffer;
const indices = geometry.indices;
if (defined(indices)) {
if (
Geometry.computeNumberOfVertices(geometry) >=
CesiumMath.SIXTY_FOUR_KILOBYTES &&
context.elementIndexUint
) {
indexBuffer = Buffer.createIndexBuffer({
context: context,
typedArray: new Uint32Array(indices),
usage: bufferUsage,
indexDatatype: IndexDatatype.UNSIGNED_INT,
});
} else {
indexBuffer = Buffer.createIndexBuffer({
context: context,
typedArray: new Uint16Array(indices),
usage: bufferUsage,
indexDatatype: IndexDatatype.UNSIGNED_SHORT,
});
}
}
return new VertexArray({
context: context,
attributes: vaAttributes,
indexBuffer: indexBuffer,
});
};
Object.defineProperties(VertexArray.prototype, {
numberOfAttributes: {
get: function () {
return this._attributes.length;
},
},
numberOfVertices: {
get: function () {
return this._numberOfVertices;
},
},
indexBuffer: {
get: function () {
return this._indexBuffer;
},
},
});
/**
* index is the location in the array of attributes, not the index property of an attribute.
*/
VertexArray.prototype.getAttribute = function (index) {
//>>includeStart('debug', pragmas.debug);
Check.defined("index", index);
//>>includeEnd('debug');
return this._attributes[index];
};
// Workaround for ANGLE, where the attribute divisor seems to be part of the global state instead
// of the VAO state. This function is called when the vao is bound, and should be removed
// once the ANGLE issue is resolved. Setting the divisor should normally happen in vertexAttrib and
// disableVertexAttribArray.
function setVertexAttribDivisor(vertexArray) {
const context = vertexArray._context;
const hasInstancedAttributes = vertexArray._hasInstancedAttributes;
if (!hasInstancedAttributes && !context._previousDrawInstanced) {
return;
}
context._previousDrawInstanced = hasInstancedAttributes;
const divisors = context._vertexAttribDivisors;
const attributes = vertexArray._attributes;
const maxAttributes = ContextLimits.maximumVertexAttributes;
let i;
if (hasInstancedAttributes) {
const length = attributes.length;
for (i = 0; i < length; ++i) {
const attribute = attributes[i];
if (attribute.enabled) {
const divisor = attribute.instanceDivisor;
const index = attribute.index;
if (divisor !== divisors[index]) {
context.glVertexAttribDivisor(index, divisor);
divisors[index] = divisor;
}
}
}
} else {
for (i = 0; i < maxAttributes; ++i) {
if (divisors[i] > 0) {
context.glVertexAttribDivisor(i, 0);
divisors[i] = 0;
}
}
}
}
// Vertex attributes backed by a constant value go through vertexAttrib[1234]f[v]
// which is part of context state rather than VAO state.
function setConstantAttributes(vertexArray, gl) {
const attributes = vertexArray._attributes;
const length = attributes.length;
for (let i = 0; i < length; ++i) {
const attribute = attributes[i];
if (attribute.enabled && defined(attribute.value)) {
attribute.vertexAttrib(gl);
}
}
}
/**
* Copies into a vertex attribute buffer from the given array, at a given
* range specified as offset and count, in number of (VECN) vertices. Array
* and vertex attribute must have the same length, which can be larger
* than the specified range to update.
* @param {number} attributeIndex
* @param {TypedArray} array
* @param {number} vertexOffset
* @param {number} vertexCount
*/
VertexArray.prototype.copyAttributeFromRange = function (
attributeIndex,
array,
vertexOffset,
vertexCount,
) {
const attribute = this.getAttribute(attributeIndex);
const buffer = /** @type {Buffer} */ (attribute.vertexBuffer);
const elementsPerVertex = attribute.componentsPerAttribute;
//>>includeStart('debug', pragmas.debug);
assert(buffer.sizeInBytes === array.byteLength, "Invalid buffer length");
//>>includeEnd('debug');
const ArrayConstructor = /** @type {TypedArrayConstructor} */ (
array.constructor
);
const byteOffset =
vertexOffset * elementsPerVertex * ArrayConstructor.BYTES_PER_ELEMENT;
// Create a zero-copy ArrayView onto the specified range of the source array.
const rangeArrayView = new ArrayConstructor(
/** @type {ArrayBuffer} */ (array.buffer),
array.byteOffset + byteOffset,
vertexCount * elementsPerVertex,
);
buffer.copyFromArrayView(rangeArrayView, byteOffset);
};
/**
* Copies into the index buffer from the given array, at a given range
* specified as offset and count, in number of (uint) indices. Array
* and index buffer must have the same length, which can be larger
* than the specified range to update.
* @param {TypedArray} array
* @param {number} indexOffset
* @param {number} indexCount
*/
VertexArray.prototype.copyIndexFromRange = function (
array,
indexOffset,
indexCount,
) {
const buffer = /** @type {Buffer} */ (this._indexBuffer);
//>>includeStart('debug', pragmas.debug);
assert(buffer.sizeInBytes === array.byteLength, "Invalid buffer length");
//>>includeEnd('debug');
const ArrayConstructor = /** @type {TypedArrayConstructor} */ (
array.constructor
);
const byteOffset = indexOffset * ArrayConstructor.BYTES_PER_ELEMENT;
// Create a zero-copy ArrayView onto the specified range of the source array.
const rangeArrayView = new ArrayConstructor(
/** @type {ArrayBuffer} */ (array.buffer),
array.byteOffset + byteOffset,
indexCount,
);
buffer.copyFromArrayView(rangeArrayView, byteOffset);
};
VertexArray.prototype._bind = function () {
if (defined(this._vao)) {
this._context.glBindVertexArray(this._vao);
if (this._context.instancedArrays) {
setVertexAttribDivisor(this);
}
if (this._hasConstantAttributes) {
setConstantAttributes(this, this._gl);
}
} else {
bind(this._gl, this._attributes, this._indexBuffer);
}
};
VertexArray.prototype._unBind = function () {
if (defined(this._vao)) {
this._context.glBindVertexArray(null);
} else {
const attributes = this._attributes;
const gl = this._gl;
for (let i = 0; i < attributes.length; ++i) {
const attribute = attributes[i];
if (attribute.enabled) {
attribute.disableVertexAttribArray(gl);
}
}
if (this._indexBuffer) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
};
VertexArray.prototype.isDestroyed = function () {
return false;
};
VertexArray.prototype.destroy = function () {
const attributes = this._attributes;
for (let i = 0; i < attributes.length; ++i) {
const vertexBuffer = attributes[i].vertexBuffer;
if (
defined(vertexBuffer) &&
!vertexBuffer.isDestroyed() &&
vertexBuffer.vertexArrayDestroyable
) {
vertexBuffer.destroy();
}
}
const indexBuffer = this._indexBuffer;
if (
defined(indexBuffer) &&
!indexBuffer.isDestroyed() &&
indexBuffer.vertexArrayDestroyable
) {
indexBuffer.destroy();
}
if (defined(this._vao)) {
this._context.glDeleteVertexArray(this._vao);
}
return destroyObject(this);
};
export default VertexArray;
+509
View File
@@ -0,0 +1,509 @@
import Check from "../Core/Check.js";
import ComponentDatatype from "../Core/ComponentDatatype.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import CesiumMath from "../Core/Math.js";
import Buffer from "./Buffer.js";
import BufferUsage from "./BufferUsage.js";
import VertexArray from "./VertexArray.js";
/**
* @private
*/
function VertexArrayFacade(context, attributes, sizeInVertices, instanced) {
//>>includeStart('debug', pragmas.debug);
Check.defined("context", context);
if (!attributes || attributes.length === 0) {
throw new DeveloperError("At least one attribute is required.");
}
//>>includeEnd('debug');
const attrs = VertexArrayFacade._verifyAttributes(attributes);
sizeInVertices = sizeInVertices ?? 0;
const precreatedAttributes = [];
const attributesByUsage = {};
let attributesForUsage;
let usage;
// Bucket the attributes by usage.
const length = attrs.length;
for (let i = 0; i < length; ++i) {
const attribute = attrs[i];
// If the attribute already has a vertex buffer, we do not need
// to manage a vertex buffer or typed array for it.
if (attribute.vertexBuffer) {
precreatedAttributes.push(attribute);
continue;
}
usage = attribute.usage;
attributesForUsage = attributesByUsage[usage];
if (!defined(attributesForUsage)) {
attributesForUsage = attributesByUsage[usage] = [];
}
attributesForUsage.push(attribute);
}
// A function to sort attributes by the size of their components. From left to right, a vertex
// stores floats, shorts, and then bytes.
function compare(left, right) {
return (
ComponentDatatype.getSizeInBytes(right.componentDatatype) -
ComponentDatatype.getSizeInBytes(left.componentDatatype)
);
}
this._allBuffers = [];
for (usage in attributesByUsage) {
if (attributesByUsage.hasOwnProperty(usage)) {
attributesForUsage = attributesByUsage[usage];
attributesForUsage.sort(compare);
const vertexSizeInBytes =
VertexArrayFacade._vertexSizeInBytes(attributesForUsage);
const bufferUsage = attributesForUsage[0].usage;
const buffer = {
vertexSizeInBytes: vertexSizeInBytes,
vertexBuffer: undefined,
usage: bufferUsage,
needsCommit: false,
arrayBuffer: undefined,
arrayViews: VertexArrayFacade._createArrayViews(
attributesForUsage,
vertexSizeInBytes,
),
};
this._allBuffers.push(buffer);
}
}
this._size = 0;
this._instanced = instanced ?? false;
this._precreated = precreatedAttributes;
this._context = context;
this.writers = undefined;
this.va = undefined;
this.resize(sizeInVertices);
}
VertexArrayFacade._verifyAttributes = function (attributes) {
const attrs = [];
for (let i = 0; i < attributes.length; ++i) {
const attribute = attributes[i];
const attr = {
index: attribute.index ?? i,
enabled: attribute.enabled ?? true,
componentsPerAttribute: attribute.componentsPerAttribute,
componentDatatype: attribute.componentDatatype ?? ComponentDatatype.FLOAT,
normalize: attribute.normalize ?? false,
// There will be either a vertexBuffer or an [optional] usage.
vertexBuffer: attribute.vertexBuffer,
usage: attribute.usage ?? BufferUsage.STATIC_DRAW,
};
attrs.push(attr);
//>>includeStart('debug', pragmas.debug);
if (
attr.componentsPerAttribute !== 1 &&
attr.componentsPerAttribute !== 2 &&
attr.componentsPerAttribute !== 3 &&
attr.componentsPerAttribute !== 4
) {
throw new DeveloperError(
"attribute.componentsPerAttribute must be in the range [1, 4].",
);
}
const datatype = attr.componentDatatype;
if (!ComponentDatatype.validate(datatype)) {
throw new DeveloperError(
"Attribute must have a valid componentDatatype or not specify it.",
);
}
if (!BufferUsage.validate(attr.usage)) {
throw new DeveloperError(
"Attribute must have a valid usage or not specify it.",
);
}
//>>includeEnd('debug');
}
// Verify all attribute names are unique.
const uniqueIndices = new Array(attrs.length);
for (let j = 0; j < attrs.length; ++j) {
const currentAttr = attrs[j];
const index = currentAttr.index;
//>>includeStart('debug', pragmas.debug);
if (uniqueIndices[index]) {
throw new DeveloperError(
`Index ${index} is used by more than one attribute.`,
);
}
//>>includeEnd('debug');
uniqueIndices[index] = true;
}
return attrs;
};
VertexArrayFacade._vertexSizeInBytes = function (attributes) {
let sizeInBytes = 0;
const length = attributes.length;
for (let i = 0; i < length; ++i) {
const attribute = attributes[i];
sizeInBytes +=
attribute.componentsPerAttribute *
ComponentDatatype.getSizeInBytes(attribute.componentDatatype);
}
const maxComponentSizeInBytes =
length > 0
? ComponentDatatype.getSizeInBytes(attributes[0].componentDatatype)
: 0; // Sorted by size
const remainder =
maxComponentSizeInBytes > 0 ? sizeInBytes % maxComponentSizeInBytes : 0;
const padding = remainder === 0 ? 0 : maxComponentSizeInBytes - remainder;
sizeInBytes += padding;
return sizeInBytes;
};
VertexArrayFacade._createArrayViews = function (attributes, vertexSizeInBytes) {
const views = [];
let offsetInBytes = 0;
const length = attributes.length;
for (let i = 0; i < length; ++i) {
const attribute = attributes[i];
const componentDatatype = attribute.componentDatatype;
views.push({
index: attribute.index,
enabled: attribute.enabled,
componentsPerAttribute: attribute.componentsPerAttribute,
componentDatatype: componentDatatype,
normalize: attribute.normalize,
offsetInBytes: offsetInBytes,
vertexSizeInComponentType:
vertexSizeInBytes / ComponentDatatype.getSizeInBytes(componentDatatype),
view: undefined,
});
offsetInBytes +=
attribute.componentsPerAttribute *
ComponentDatatype.getSizeInBytes(componentDatatype);
}
return views;
};
/**
* Invalidates writers. Can't render again until commit is called.
*/
VertexArrayFacade.prototype.resize = function (sizeInVertices) {
this._size = sizeInVertices;
const allBuffers = this._allBuffers;
this.writers = [];
for (let i = 0, len = allBuffers.length; i < len; ++i) {
const buffer = allBuffers[i];
VertexArrayFacade._resize(buffer, this._size);
// Reserving invalidates the writers, so if client's cache them, they need to invalidate their cache.
VertexArrayFacade._appendWriters(this.writers, buffer);
}
// VAs are recreated next time commit is called.
destroyVA(this);
};
VertexArrayFacade._resize = function (buffer, size) {
if (buffer.vertexSizeInBytes > 0) {
// Create larger array buffer
const arrayBuffer = new ArrayBuffer(size * buffer.vertexSizeInBytes);
// Copy contents from previous array buffer
if (defined(buffer.arrayBuffer)) {
const destView = new Uint8Array(arrayBuffer);
const sourceView = new Uint8Array(buffer.arrayBuffer);
const sourceLength = sourceView.length;
for (let j = 0; j < sourceLength; ++j) {
destView[j] = sourceView[j];
}
}
// Create typed views into the new array buffer
const views = buffer.arrayViews;
const length = views.length;
for (let i = 0; i < length; ++i) {
const view = views[i];
view.view = ComponentDatatype.createArrayBufferView(
view.componentDatatype,
arrayBuffer,
view.offsetInBytes,
);
}
buffer.arrayBuffer = arrayBuffer;
}
};
const createWriters = [
// 1 component per attribute
function (buffer, view, vertexSizeInComponentType) {
return function (index, attribute) {
view[index * vertexSizeInComponentType] = attribute;
buffer.needsCommit = true;
};
},
// 2 component per attribute
function (buffer, view, vertexSizeInComponentType) {
return function (index, component0, component1) {
const i = index * vertexSizeInComponentType;
view[i] = component0;
view[i + 1] = component1;
buffer.needsCommit = true;
};
},
// 3 component per attribute
function (buffer, view, vertexSizeInComponentType) {
return function (index, component0, component1, component2) {
const i = index * vertexSizeInComponentType;
view[i] = component0;
view[i + 1] = component1;
view[i + 2] = component2;
buffer.needsCommit = true;
};
},
// 4 component per attribute
function (buffer, view, vertexSizeInComponentType) {
return function (index, component0, component1, component2, component3) {
const i = index * vertexSizeInComponentType;
view[i] = component0;
view[i + 1] = component1;
view[i + 2] = component2;
view[i + 3] = component3;
buffer.needsCommit = true;
};
},
];
VertexArrayFacade._appendWriters = function (writers, buffer) {
const arrayViews = buffer.arrayViews;
const length = arrayViews.length;
for (let i = 0; i < length; ++i) {
const arrayView = arrayViews[i];
writers[arrayView.index] = createWriters[
arrayView.componentsPerAttribute - 1
](buffer, arrayView.view, arrayView.vertexSizeInComponentType);
}
};
VertexArrayFacade.prototype.commit = function (indexBuffer) {
let recreateVA = false;
const allBuffers = this._allBuffers;
let buffer;
let i;
let length;
for (i = 0, length = allBuffers.length; i < length; ++i) {
buffer = allBuffers[i];
recreateVA = commit(this, buffer) || recreateVA;
}
///////////////////////////////////////////////////////////////////////
if (recreateVA || !defined(this.va)) {
destroyVA(this);
const va = (this.va = []);
const chunkSize = CesiumMath.SIXTY_FOUR_KILOBYTES - 4; // The 65535 index is reserved for primitive restart. Reserve the last 4 indices so that billboard quads are not broken up.
const numberOfVertexArrays =
defined(indexBuffer) && !this._instanced
? Math.ceil(this._size / chunkSize)
: 1;
for (let k = 0; k < numberOfVertexArrays; ++k) {
let attributes = [];
for (i = 0, length = allBuffers.length; i < length; ++i) {
buffer = allBuffers[i];
const offset = k * (buffer.vertexSizeInBytes * chunkSize);
VertexArrayFacade._appendAttributes(
attributes,
buffer,
offset,
this._instanced,
);
}
attributes = attributes.concat(this._precreated);
va.push({
va: new VertexArray({
context: this._context,
attributes: attributes,
indexBuffer: indexBuffer,
}),
indicesCount:
1.5 *
(k !== numberOfVertexArrays - 1 ? chunkSize : this._size % chunkSize),
// TODO: not hardcode 1.5, this assumes 6 indices per 4 vertices (as for Billboard quads).
});
}
}
};
function commit(vertexArrayFacade, buffer) {
if (buffer.needsCommit && buffer.vertexSizeInBytes > 0) {
buffer.needsCommit = false;
const vertexBuffer = buffer.vertexBuffer;
const vertexBufferSizeInBytes =
vertexArrayFacade._size * buffer.vertexSizeInBytes;
const vertexBufferDefined = defined(vertexBuffer);
if (
!vertexBufferDefined ||
vertexBuffer.sizeInBytes < vertexBufferSizeInBytes
) {
if (vertexBufferDefined) {
vertexBuffer.destroy();
}
buffer.vertexBuffer = Buffer.createVertexBuffer({
context: vertexArrayFacade._context,
typedArray: buffer.arrayBuffer,
usage: buffer.usage,
});
buffer.vertexBuffer.vertexArrayDestroyable = false;
return true; // Created new vertex buffer
}
buffer.vertexBuffer.copyFromArrayView(buffer.arrayBuffer);
}
return false; // Did not create new vertex buffer
}
VertexArrayFacade._appendAttributes = function (
attributes,
buffer,
vertexBufferOffset,
instanced,
) {
const arrayViews = buffer.arrayViews;
const length = arrayViews.length;
for (let i = 0; i < length; ++i) {
const view = arrayViews[i];
attributes.push({
index: view.index,
enabled: view.enabled,
componentsPerAttribute: view.componentsPerAttribute,
componentDatatype: view.componentDatatype,
normalize: view.normalize,
vertexBuffer: buffer.vertexBuffer,
offsetInBytes: vertexBufferOffset + view.offsetInBytes,
strideInBytes: buffer.vertexSizeInBytes,
instanceDivisor: instanced ? 1 : 0,
});
}
};
VertexArrayFacade.prototype.subCommit = function (
offsetInVertices,
lengthInVertices,
) {
//>>includeStart('debug', pragmas.debug);
if (offsetInVertices < 0 || offsetInVertices >= this._size) {
throw new DeveloperError(
"offsetInVertices must be greater than or equal to zero and less than the vertex array size.",
);
}
if (offsetInVertices + lengthInVertices > this._size) {
throw new DeveloperError(
"offsetInVertices + lengthInVertices cannot exceed the vertex array size.",
);
}
//>>includeEnd('debug');
const allBuffers = this._allBuffers;
for (let i = 0, len = allBuffers.length; i < len; ++i) {
subCommit(allBuffers[i], offsetInVertices, lengthInVertices);
}
};
function subCommit(buffer, offsetInVertices, lengthInVertices) {
if (buffer.needsCommit && buffer.vertexSizeInBytes > 0) {
const byteOffset = buffer.vertexSizeInBytes * offsetInVertices;
const byteLength = buffer.vertexSizeInBytes * lengthInVertices;
// PERFORMANCE_IDEA: If we want to get really crazy, we could consider updating
// individual attributes instead of the entire (sub-)vertex.
//
// PERFORMANCE_IDEA: Does creating the typed view add too much GC overhead?
buffer.vertexBuffer.copyFromArrayView(
new Uint8Array(buffer.arrayBuffer, byteOffset, byteLength),
byteOffset,
);
}
}
VertexArrayFacade.prototype.endSubCommits = function () {
const allBuffers = this._allBuffers;
for (let i = 0, len = allBuffers.length; i < len; ++i) {
allBuffers[i].needsCommit = false;
}
};
function destroyVA(vertexArrayFacade) {
const va = vertexArrayFacade.va;
if (!defined(va)) {
return;
}
const length = va.length;
for (let i = 0; i < length; ++i) {
va[i].va.destroy();
}
vertexArrayFacade.va = undefined;
}
VertexArrayFacade.prototype.isDestroyed = function () {
return false;
};
VertexArrayFacade.prototype.destroy = function () {
const allBuffers = this._allBuffers;
for (let i = 0, len = allBuffers.length; i < len; ++i) {
const buffer = allBuffers[i];
buffer.vertexBuffer = buffer.vertexBuffer && buffer.vertexBuffer.destroy();
}
destroyVA(this);
return destroyObject(this);
};
export default VertexArrayFacade;
+529
View File
@@ -0,0 +1,529 @@
// @ts-check
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import Color from "../Core/Color.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import Matrix2 from "../Core/Matrix2.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import RuntimeError from "../Core/RuntimeError.js";
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
* @private
*/
function createUniform(gl, activeUniform, uniformName, location) {
switch (activeUniform.type) {
case gl.FLOAT:
return new UniformFloat(gl, activeUniform, uniformName, location);
case gl.FLOAT_VEC2:
return new UniformFloatVec2(gl, activeUniform, uniformName, location);
case gl.FLOAT_VEC3:
return new UniformFloatVec3(gl, activeUniform, uniformName, location);
case gl.FLOAT_VEC4:
return new UniformFloatVec4(gl, activeUniform, uniformName, location);
case gl.SAMPLER_2D:
case gl.SAMPLER_3D:
case gl.SAMPLER_CUBE:
return new UniformSampler(gl, activeUniform, uniformName, location);
case gl.UNSIGNED_INT_SAMPLER_2D:
return new UniformSampler(gl, activeUniform, uniformName, location);
case gl.INT:
case gl.BOOL:
return new UniformInt(gl, activeUniform, uniformName, location);
case gl.INT_VEC2:
case gl.BOOL_VEC2:
return new UniformIntVec2(gl, activeUniform, uniformName, location);
case gl.INT_VEC3:
case gl.BOOL_VEC3:
return new UniformIntVec3(gl, activeUniform, uniformName, location);
case gl.INT_VEC4:
case gl.BOOL_VEC4:
return new UniformIntVec4(gl, activeUniform, uniformName, location);
case gl.FLOAT_MAT2:
return new UniformMat2(gl, activeUniform, uniformName, location);
case gl.FLOAT_MAT3:
return new UniformMat3(gl, activeUniform, uniformName, location);
case gl.FLOAT_MAT4:
return new UniformMat4(gl, activeUniform, uniformName, location);
default:
throw new RuntimeError(
`Unrecognized uniform type: ${activeUniform.type} for uniform "${uniformName}".`,
);
}
}
/**
* @private
*/
class UniformFloat {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = 0.0;
this._gl = gl;
this._location = location;
}
set() {
if (this.value !== this._value) {
this._value = this.value;
this._gl.uniform1f(this._location, this.value);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformFloatVec2 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Cartesian2();
this._gl = gl;
this._location = location;
}
set() {
const v = this.value;
if (!Cartesian2.equals(v, this._value)) {
Cartesian2.clone(v, this._value);
this._gl.uniform2f(this._location, v.x, v.y);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformFloatVec3 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = undefined;
this._gl = gl;
this._location = location;
}
set() {
const v = this.value;
if (defined(v.red)) {
if (!Color.equals(v, this._value)) {
this._value = Color.clone(v, this._value);
this._gl.uniform3f(this._location, v.red, v.green, v.blue);
}
} else if (defined(v.x)) {
if (!Cartesian3.equals(v, this._value)) {
this._value = Cartesian3.clone(v, this._value);
this._gl.uniform3f(this._location, v.x, v.y, v.z);
}
} else {
//>>includeStart('debug', pragmas.debug);
throw new DeveloperError(
`Invalid vec3 value for uniform "${this.name}".`,
);
//>>includeEnd('debug');
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformFloatVec4 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = undefined;
this._gl = gl;
this._location = location;
}
set() {
const v = this.value;
if (defined(v.red)) {
if (!Color.equals(v, this._value)) {
this._value = Color.clone(v, this._value);
this._gl.uniform4f(this._location, v.red, v.green, v.blue, v.alpha);
}
} else if (defined(v.x)) {
if (!Cartesian4.equals(v, this._value)) {
this._value = Cartesian4.clone(v, this._value);
this._gl.uniform4f(this._location, v.x, v.y, v.z, v.w);
}
} else {
//>>includeStart('debug', pragmas.debug);
throw new DeveloperError(
`Invalid vec4 value for uniform "${this.name}".`,
);
//>>includeEnd('debug');
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformSampler {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._gl = gl;
this._location = location;
this.textureUnitIndex = undefined;
}
set() {
const gl = this._gl;
gl.activeTexture(gl.TEXTURE0 + this.textureUnitIndex);
const v = this.value;
gl.bindTexture(v._target, v._texture);
}
/**
* @param {number} textureUnitIndex
* @returns {number}
*/
_setSampler(textureUnitIndex) {
this.textureUnitIndex = textureUnitIndex;
this._gl.uniform1i(this._location, textureUnitIndex);
return textureUnitIndex + 1;
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformInt {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = 0.0;
this._gl = gl;
this._location = location;
}
set() {
if (this.value !== this._value) {
this._value = this.value;
this._gl.uniform1i(this._location, this.value);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformIntVec2 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Cartesian2();
this._gl = gl;
this._location = location;
}
set() {
const v = this.value;
if (!Cartesian2.equals(v, this._value)) {
Cartesian2.clone(v, this._value);
this._gl.uniform2i(this._location, v.x, v.y);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformIntVec3 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Cartesian3();
this._gl = gl;
this._location = location;
}
set() {
const v = this.value;
if (!Cartesian3.equals(v, this._value)) {
Cartesian3.clone(v, this._value);
this._gl.uniform3i(this._location, v.x, v.y, v.z);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformIntVec4 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Cartesian4();
this._gl = gl;
this._location = location;
}
set() {
const v = this.value;
if (!Cartesian4.equals(v, this._value)) {
Cartesian4.clone(v, this._value);
this._gl.uniform4i(this._location, v.x, v.y, v.z, v.w);
}
}
}
///////////////////////////////////////////////////////////////////////////
const scratchUniformArray = new Float32Array(4);
/**
* @private
*/
class UniformMat2 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Matrix2();
this._gl = gl;
this._location = location;
}
set() {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13290
if (!Matrix2.equalsArray(this.value, this._value, 0)) {
Matrix2.clone(this.value, this._value);
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
const array = Matrix2.toArray(this.value, scratchUniformArray);
this._gl.uniformMatrix2fv(this._location, false, array);
}
}
}
///////////////////////////////////////////////////////////////////////////
const scratchMat3Array = new Float32Array(9);
/**
* @private
*/
class UniformMat3 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Matrix3();
this._gl = gl;
this._location = location;
}
set() {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13290
if (!Matrix3.equalsArray(this.value, this._value, 0)) {
Matrix3.clone(this.value, this._value);
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
const array = Matrix3.toArray(this.value, scratchMat3Array);
this._gl.uniformMatrix3fv(this._location, false, array);
}
}
}
///////////////////////////////////////////////////////////////////////////
const scratchMat4Array = new Float32Array(16);
/**
* @private
*/
class UniformMat4 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation} location
*/
constructor(gl, activeUniform, uniformName, location) {
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Matrix4();
this._gl = gl;
this._location = location;
}
set() {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13290
if (!Matrix4.equalsArray(this.value, this._value, 0)) {
Matrix4.clone(this.value, this._value);
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
const array = Matrix4.toArray(this.value, scratchMat4Array);
this._gl.uniformMatrix4fv(this._location, false, array);
}
}
}
export default createUniform;
+751
View File
@@ -0,0 +1,751 @@
// @ts-check
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import Color from "../Core/Color.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import Matrix2 from "../Core/Matrix2.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import RuntimeError from "../Core/RuntimeError.js";
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
* @private
*/
function createUniformArray(gl, activeUniform, uniformName, locations) {
switch (activeUniform.type) {
case gl.FLOAT:
return new UniformArrayFloat(gl, activeUniform, uniformName, locations);
case gl.FLOAT_VEC2:
return new UniformArrayFloatVec2(
gl,
activeUniform,
uniformName,
locations,
);
case gl.FLOAT_VEC3:
return new UniformArrayFloatVec3(
gl,
activeUniform,
uniformName,
locations,
);
case gl.FLOAT_VEC4:
return new UniformArrayFloatVec4(
gl,
activeUniform,
uniformName,
locations,
);
case gl.SAMPLER_2D:
case gl.SAMPLER_3D:
case gl.SAMPLER_CUBE:
return new UniformArraySampler(gl, activeUniform, uniformName, locations);
case gl.INT:
case gl.BOOL:
return new UniformArrayInt(gl, activeUniform, uniformName, locations);
case gl.INT_VEC2:
case gl.BOOL_VEC2:
return new UniformArrayIntVec2(gl, activeUniform, uniformName, locations);
case gl.INT_VEC3:
case gl.BOOL_VEC3:
return new UniformArrayIntVec3(gl, activeUniform, uniformName, locations);
case gl.INT_VEC4:
case gl.BOOL_VEC4:
return new UniformArrayIntVec4(gl, activeUniform, uniformName, locations);
case gl.FLOAT_MAT2:
return new UniformArrayMat2(gl, activeUniform, uniformName, locations);
case gl.FLOAT_MAT3:
return new UniformArrayMat3(gl, activeUniform, uniformName, locations);
case gl.FLOAT_MAT4:
return new UniformArrayMat4(gl, activeUniform, uniformName, locations);
default:
throw new RuntimeError(
`Unrecognized uniform type: ${activeUniform.type} for uniform "${uniformName}".`,
);
}
}
/**
* @private
*/
class UniformArrayFloat {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
for (let i = 0; i < length; ++i) {
const v = value[i];
if (v !== arraybuffer[i]) {
arraybuffer[i] = v;
changed = true;
}
}
if (changed) {
this._gl.uniform1fv(this._location, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayFloatVec2 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 2);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Cartesian2.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Cartesian2.pack(v, arraybuffer, j);
changed = true;
}
j += 2;
}
if (changed) {
this._gl.uniform2fv(this._location, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayFloatVec3 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 3);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
if (defined(v.red)) {
if (
v.red !== arraybuffer[j] ||
v.green !== arraybuffer[j + 1] ||
v.blue !== arraybuffer[j + 2]
) {
arraybuffer[j] = v.red;
arraybuffer[j + 1] = v.green;
arraybuffer[j + 2] = v.blue;
changed = true;
}
} else if (defined(v.x)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Cartesian3.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Cartesian3.pack(v, arraybuffer, j);
changed = true;
}
} else {
//>>includeStart('debug', pragmas.debug);
throw new DeveloperError("Invalid vec3 value.");
//>>includeEnd('debug');
}
j += 3;
}
if (changed) {
this._gl.uniform3fv(this._location, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayFloatVec4 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 4);
this._gl = gl;
this._location = locations[0];
}
set() {
// PERFORMANCE_IDEA: if it is a common case that only a few elements
// in a uniform array change, we could use heuristics to determine
// when it is better to call uniform4f for each element that changed
// vs. call uniform4fv once to set the entire array. This applies
// to all uniform array types, not just vec4. We might not care
// once we have uniform buffers since that will be the fast path.
// PERFORMANCE_IDEA: Micro-optimization (I bet it works though):
// As soon as changed is true, break into a separate loop that
// does the copy without the equals check.
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
if (defined(v.red)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Color.equalsArray(v, arraybuffer, j)) {
Color.pack(v, arraybuffer, j);
changed = true;
}
} else if (defined(v.x)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Cartesian4.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Cartesian4.pack(v, arraybuffer, j);
changed = true;
}
} else {
//>>includeStart('debug', pragmas.debug);
throw new DeveloperError("Invalid vec4 value.");
//>>includeEnd('debug');
}
j += 4;
}
if (changed) {
this._gl.uniform4fv(this._location, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArraySampler {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length);
this._gl = gl;
this._locations = locations;
this.textureUnitIndex = undefined;
}
set() {
const gl = this._gl;
const textureUnitIndex = gl.TEXTURE0 + this.textureUnitIndex;
const value = this.value;
const length = value.length;
for (let i = 0; i < length; ++i) {
const v = value[i];
gl.activeTexture(textureUnitIndex + i);
gl.bindTexture(v._target, v._texture);
}
}
/**
* @param {number} textureUnitIndex
* @returns {number}
*/
_setSampler(textureUnitIndex) {
this.textureUnitIndex = textureUnitIndex;
const locations = this._locations;
const length = locations.length;
for (let i = 0; i < length; ++i) {
const index = textureUnitIndex + i;
this._gl.uniform1i(locations[i], index);
}
return textureUnitIndex + length;
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayInt {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Int32Array(length);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
for (let i = 0; i < length; ++i) {
const v = value[i];
if (v !== arraybuffer[i]) {
arraybuffer[i] = v;
changed = true;
}
}
if (changed) {
this._gl.uniform1iv(this._location, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayIntVec2 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Int32Array(length * 2);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Cartesian2.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Cartesian2.pack(v, arraybuffer, j);
changed = true;
}
j += 2;
}
if (changed) {
this._gl.uniform2iv(this._location, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayIntVec3 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Int32Array(length * 3);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Cartesian3.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Cartesian3.pack(v, arraybuffer, j);
changed = true;
}
j += 3;
}
if (changed) {
this._gl.uniform3iv(this._location, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayIntVec4 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Int32Array(length * 4);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Cartesian4.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Cartesian4.pack(v, arraybuffer, j);
changed = true;
}
j += 4;
}
if (changed) {
this._gl.uniform4iv(this._location, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayMat2 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 4);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Matrix2.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Matrix2.pack(v, arraybuffer, j);
changed = true;
}
j += 4;
}
if (changed) {
this._gl.uniformMatrix2fv(this._location, false, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayMat3 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 9);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Matrix3.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Matrix3.pack(v, arraybuffer, j);
changed = true;
}
j += 9;
}
if (changed) {
this._gl.uniformMatrix3fv(this._location, false, arraybuffer);
}
}
}
///////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class UniformArrayMat4 {
/**
* @param {WebGL2RenderingContext} gl
* @param {WebGLActiveInfo} activeUniform
* @param {string} uniformName
* @param {WebGLUniformLocation[]} locations
*/
constructor(gl, activeUniform, uniformName, locations) {
const length = locations.length;
/**
* @type {string}
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 16);
this._gl = gl;
this._location = locations[0];
}
set() {
const value = this.value;
const length = value.length;
const arraybuffer = this._value;
let changed = false;
let j = 0;
for (let i = 0; i < length; ++i) {
const v = value[i];
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
if (!Matrix4.equalsArray(v, arraybuffer, j)) {
// @ts-expect-error https://github.com/CesiumGS/cesium/pull/13302
Matrix4.pack(v, arraybuffer, j);
changed = true;
}
j += 16;
}
if (changed) {
this._gl.uniformMatrix4fv(this._location, false, arraybuffer);
}
}
}
export default createUniformArray;
+84
View File
@@ -0,0 +1,84 @@
/**
* Transpiles a [GLSL 3.00]{@link https://registry.khronos.org/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf}
* shader to a [GLSL 1.00]{@link https://registry.khronos.org/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf} shader.
*
* This function does not aim to provide a comprehensive transpilation from GLSL 3.00 to GLSL 1.00; only the functionality
* used within the CesiumJS shaders is supported.
*
* @private
*
* @param {string} input The GLSL 3.00 shader.
* @param {boolean} isFragmentShader True if the shader is a fragment shader.
*
* @return {string}
*/
function demodernizeShader(input, isFragmentShader) {
let output = input;
// Remove version string got GLSL 3.00.
output = output.replaceAll(`version 300 es`, ``);
// Replace all texture calls with texture2D
output = output.replaceAll(
/(texture\()/g,
`texture2D(`, // Trailing ')' is included in the match group.
);
if (isFragmentShader) {
// Replace the in with varying.
output = output.replaceAll(
/\n\s*(in)\s+(vec\d|mat\d|float)/g,
`\nvarying $2`,
);
if (/out_FragData_(\d+)/.test(output)) {
output = `#extension GL_EXT_draw_buffers : enable\n${output}`;
// Remove all layout declarations for out_FragData.
output = output.replaceAll(
/layout\s+\(location\s*=\s*\d+\)\s*out\s+vec4\s+out_FragData_\d+;/g,
``,
);
// Replace out_FragData with gl_FragData.
output = output.replaceAll(/out_FragData_(\d+)/g, `gl_FragData[$1]`);
}
// Remove all layout declarations for out_FragColor.
output = output.replaceAll(
/layout\s+\(location\s*=\s*0\)\s*out\s+vec4\s+out_FragColor;/g,
``,
);
// Replace out_FragColor with gl_FragColor.
output = output.replaceAll(/out_FragColor/g, `gl_FragColor`);
output = output.replaceAll(/out_FragColor\[(\d+)\]/g, `gl_FragColor[$1]`);
if (/gl_FragDepth/.test(output)) {
output = `#extension GL_EXT_frag_depth : enable\n${output}`;
// Replace gl_FragDepth with gl_FragDepthEXT.
output = output.replaceAll(/gl_FragDepth/g, `gl_FragDepthEXT`);
}
// Enable the EXT_shader_texture_lod extension
output = `#ifdef GL_EXT_shader_texture_lod\n#extension GL_EXT_shader_texture_lod : enable\n#endif\n${output}`;
// Enable the OES_standard_derivatives extension
output = `#ifdef GL_OES_standard_derivatives\n#extension GL_OES_standard_derivatives : enable\n#endif\n${output}`;
} else {
// Replace the in with attribute.
output = output.replaceAll(/(in)\s+(vec\d|mat\d|float)/g, `attribute $2`);
// Replace the out with varying.
output = output.replaceAll(
/(out)\s+(vec\d|mat\d|float)\s+([\w]+);/g,
`varying $2 $3;`,
);
}
// Add version string for GLSL 1.00.
output = `#version 100\n${output}`;
return output;
}
export default demodernizeShader;
+30
View File
@@ -0,0 +1,30 @@
/**
* Returns frozen renderState as well as all of the object literal properties. This function is deep object freeze
* function ignoring properties named "_applyFunctions".
*
* @private
*
* @param {object} renderState
* @returns {object} Returns frozen renderState.
*
*/
function freezeRenderState(renderState) {
if (typeof renderState !== "object" || renderState === null) {
return renderState;
}
let propName;
const propNames = Object.keys(renderState);
for (let i = 0; i < propNames.length; i++) {
propName = propNames[i];
if (
renderState.hasOwnProperty(propName) &&
propName !== "_applyFunctions"
) {
renderState[propName] = freezeRenderState(renderState[propName]);
}
}
return Object.freeze(renderState);
}
export default freezeRenderState;
+88
View File
@@ -0,0 +1,88 @@
import Check from "../Core/Check.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import Resource from "../Core/Resource.js";
import CubeMap from "./CubeMap.js";
/**
* Asynchronously loads six images and creates a cube map. Returns a promise that
* will resolve to a {@link CubeMap} once loaded, or reject if any image fails to load.
*
* @function loadCubeMap
*
* @param {Context} context The context to use to create the cube map.
* @param {object} urls The source URL of each image. See the example below.
* @param {boolean} [skipColorSpaceConversion=false] If true, any custom gamma or color profiles in the images will be ignored.
* @returns {Promise<CubeMap>} a promise that will resolve to the requested {@link CubeMap} when loaded.
*
* @exception {DeveloperError} context is required.
* @exception {DeveloperError} urls is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.
*
*
* @example
* Cesium.loadCubeMap(context, {
* positiveX : 'skybox_px.png',
* negativeX : 'skybox_nx.png',
* positiveY : 'skybox_py.png',
* negativeY : 'skybox_ny.png',
* positiveZ : 'skybox_pz.png',
* negativeZ : 'skybox_nz.png'
* }).then(function(cubeMap) {
* // use the cubemap
* }).catch(function(error) {
* // an error occurred
* });
*
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*
* @private
*/
function loadCubeMap(context, urls, skipColorSpaceConversion) {
//>>includeStart('debug', pragmas.debug);
Check.defined("context", context);
Check.defined("urls", urls);
if (
Object.values(CubeMap.FaceName).some((faceName) => !defined(urls[faceName]))
) {
throw new DeveloperError(
"urls must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.",
);
}
//>>includeEnd('debug');
// PERFORMANCE_IDEA: Given the size of some cube maps, we should consider tiling them, which
// would prevent hiccups when uploading, for example, six 4096x4096 textures to the GPU.
//
// Also, it is perhaps acceptable to use the context here in the callbacks, but
// ideally, we would do it in the primitive's update function.
const flipOptions = {
flipY: true,
skipColorSpaceConversion: skipColorSpaceConversion,
preferImageBitmap: true,
};
const facePromises = [
Resource.createIfNeeded(urls.positiveX).fetchImage(flipOptions),
Resource.createIfNeeded(urls.negativeX).fetchImage(flipOptions),
Resource.createIfNeeded(urls.positiveY).fetchImage(flipOptions),
Resource.createIfNeeded(urls.negativeY).fetchImage(flipOptions),
Resource.createIfNeeded(urls.positiveZ).fetchImage(flipOptions),
Resource.createIfNeeded(urls.negativeZ).fetchImage(flipOptions),
];
return Promise.all(facePromises).then(function (images) {
return new CubeMap({
context: context,
source: {
positiveX: images[0],
negativeX: images[1],
positiveY: images[2],
negativeY: images[3],
positiveZ: images[4],
negativeZ: images[5],
},
});
});
}
export default loadCubeMap;