import Check from "../Core/Check.js"; import createGuid from "../Core/createGuid.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import IndexDatatype from "../Core/IndexDatatype.js"; import WebGLConstants from "../Core/WebGLConstants.js"; import BufferUsage from "./BufferUsage.js"; /** * @private */ function Buffer(options) { options = defaultValue(options, defaultValue.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; } /** * Creates a vertex buffer, which contains untyped vertex data in GPU-controlled memory. *

* 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 Number 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 or , but not both. * @exception {DeveloperError} The buffer size must be greater than zero. * @exception {DeveloperError} Invalid usage. * * * @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 ARRAY_BUFFER * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with ARRAY_BUFFER */ 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. *

* An index buffer can be attached to a vertex array to select vertices for rendering. * Context.draw 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 Number 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 or , 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 usage. * @exception {DeveloperError} Invalid indexDatatype. * * * @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 ELEMENT_ARRAY_BUFFER * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with ELEMENT_ARRAY_BUFFER */ 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.copyFromArrayView = function (arrayView, offsetInBytes) { offsetInBytes = defaultValue(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 = defaultValue(sourceOffset, 0); destinationOffset = defaultValue(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;