Add existing to tracked
This commit is contained in:
+1880
File diff suppressed because it is too large
Load Diff
+452
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
+1732
File diff suppressed because it is too large
Load Diff
+332
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
+1137
File diff suppressed because it is too large
Load Diff
+737
@@ -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
@@ -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 0–1 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
@@ -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;
|
||||
+43
@@ -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;
|
||||
+96
@@ -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
@@ -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;
|
||||
+1955
File diff suppressed because it is too large
Load Diff
+949
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
Reference in New Issue
Block a user