Buffer.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import Check from '../Core/Check.js';
  2. import defaultValue from '../Core/defaultValue.js';
  3. import defined from '../Core/defined.js';
  4. import defineProperties from '../Core/defineProperties.js';
  5. import destroyObject from '../Core/destroyObject.js';
  6. import DeveloperError from '../Core/DeveloperError.js';
  7. import IndexDatatype from '../Core/IndexDatatype.js';
  8. import WebGLConstants from '../Core/WebGLConstants.js';
  9. import BufferUsage from './BufferUsage.js';
  10. /**
  11. * @private
  12. */
  13. function Buffer(options) {
  14. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  15. //>>includeStart('debug', pragmas.debug);
  16. Check.defined('options.context', options.context);
  17. if (!defined(options.typedArray) && !defined(options.sizeInBytes)) {
  18. throw new DeveloperError('Either options.sizeInBytes or options.typedArray is required.');
  19. }
  20. if (defined(options.typedArray) && defined(options.sizeInBytes)) {
  21. throw new DeveloperError('Cannot pass in both options.sizeInBytes and options.typedArray.');
  22. }
  23. if (defined(options.typedArray)) {
  24. Check.typeOf.object('options.typedArray', options.typedArray);
  25. Check.typeOf.number('options.typedArray.byteLength', options.typedArray.byteLength);
  26. }
  27. if (!BufferUsage.validate(options.usage)) {
  28. throw new DeveloperError('usage is invalid.');
  29. }
  30. //>>includeEnd('debug');
  31. var gl = options.context._gl;
  32. var bufferTarget = options.bufferTarget;
  33. var typedArray = options.typedArray;
  34. var sizeInBytes = options.sizeInBytes;
  35. var usage = options.usage;
  36. var hasArray = defined(typedArray);
  37. if (hasArray) {
  38. sizeInBytes = typedArray.byteLength;
  39. }
  40. //>>includeStart('debug', pragmas.debug);
  41. Check.typeOf.number.greaterThan('sizeInBytes', sizeInBytes, 0);
  42. //>>includeEnd('debug');
  43. var buffer = gl.createBuffer();
  44. gl.bindBuffer(bufferTarget, buffer);
  45. gl.bufferData(bufferTarget, hasArray ? typedArray : sizeInBytes, usage);
  46. gl.bindBuffer(bufferTarget, null);
  47. this._gl = gl;
  48. this._webgl2 = options.context._webgl2;
  49. this._bufferTarget = bufferTarget;
  50. this._sizeInBytes = sizeInBytes;
  51. this._usage = usage;
  52. this._buffer = buffer;
  53. this.vertexArrayDestroyable = true;
  54. }
  55. /**
  56. * Creates a vertex buffer, which contains untyped vertex data in GPU-controlled memory.
  57. * <br /><br />
  58. * A vertex array defines the actual makeup of a vertex, e.g., positions, normals, texture coordinates,
  59. * etc., by interpreting the raw data in one or more vertex buffers.
  60. *
  61. * @param {Object} options An object containing the following properties:
  62. * @param {Context} options.context The context in which to create the buffer
  63. * @param {ArrayBufferView} [options.typedArray] A typed array containing the data to copy to the buffer.
  64. * @param {Number} [options.sizeInBytes] A <code>Number</code> defining the size of the buffer in bytes. Required if options.typedArray is not given.
  65. * @param {BufferUsage} options.usage Specifies the expected usage pattern of the buffer. On some GL implementations, this can significantly affect performance. See {@link BufferUsage}.
  66. * @returns {VertexBuffer} The vertex buffer, ready to be attached to a vertex array.
  67. *
  68. * @exception {DeveloperError} Must specify either <options.typedArray> or <options.sizeInBytes>, but not both.
  69. * @exception {DeveloperError} The buffer size must be greater than zero.
  70. * @exception {DeveloperError} Invalid <code>usage</code>.
  71. *
  72. *
  73. * @example
  74. * // Example 1. Create a dynamic vertex buffer 16 bytes in size.
  75. * var buffer = Buffer.createVertexBuffer({
  76. * context : context,
  77. * sizeInBytes : 16,
  78. * usage : BufferUsage.DYNAMIC_DRAW
  79. * });
  80. *
  81. * @example
  82. * // Example 2. Create a dynamic vertex buffer from three floating-point values.
  83. * // The data copied to the vertex buffer is considered raw bytes until it is
  84. * // interpreted as vertices using a vertex array.
  85. * var positionBuffer = buffer.createVertexBuffer({
  86. * context : context,
  87. * typedArray : new Float32Array([0, 0, 0]),
  88. * usage : BufferUsage.STATIC_DRAW
  89. * });
  90. *
  91. * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffer.xml|glGenBuffer}
  92. * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml|glBindBuffer} with <code>ARRAY_BUFFER</code>
  93. * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with <code>ARRAY_BUFFER</code>
  94. */
  95. Buffer.createVertexBuffer = function(options) {
  96. //>>includeStart('debug', pragmas.debug);
  97. Check.defined('options.context', options.context);
  98. //>>includeEnd('debug');
  99. return new Buffer({
  100. context: options.context,
  101. bufferTarget: WebGLConstants.ARRAY_BUFFER,
  102. typedArray: options.typedArray,
  103. sizeInBytes: options.sizeInBytes,
  104. usage: options.usage
  105. });
  106. };
  107. /**
  108. * Creates an index buffer, which contains typed indices in GPU-controlled memory.
  109. * <br /><br />
  110. * An index buffer can be attached to a vertex array to select vertices for rendering.
  111. * <code>Context.draw</code> can render using the entire index buffer or a subset
  112. * of the index buffer defined by an offset and count.
  113. *
  114. * @param {Object} options An object containing the following properties:
  115. * @param {Context} options.context The context in which to create the buffer
  116. * @param {ArrayBufferView} [options.typedArray] A typed array containing the data to copy to the buffer.
  117. * @param {Number} [options.sizeInBytes] A <code>Number</code> defining the size of the buffer in bytes. Required if options.typedArray is not given.
  118. * @param {BufferUsage} options.usage Specifies the expected usage pattern of the buffer. On some GL implementations, this can significantly affect performance. See {@link BufferUsage}.
  119. * @param {IndexDatatype} options.indexDatatype The datatype of indices in the buffer.
  120. * @returns {IndexBuffer} The index buffer, ready to be attached to a vertex array.
  121. *
  122. * @exception {DeveloperError} Must specify either <options.typedArray> or <options.sizeInBytes>, but not both.
  123. * @exception {DeveloperError} IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system. Check context.elementIndexUint.
  124. * @exception {DeveloperError} The size in bytes must be greater than zero.
  125. * @exception {DeveloperError} Invalid <code>usage</code>.
  126. * @exception {DeveloperError} Invalid <code>indexDatatype</code>.
  127. *
  128. *
  129. * @example
  130. * // Example 1. Create a stream index buffer of unsigned shorts that is
  131. * // 16 bytes in size.
  132. * var buffer = Buffer.createIndexBuffer({
  133. * context : context,
  134. * sizeInBytes : 16,
  135. * usage : BufferUsage.STREAM_DRAW,
  136. * indexDatatype : IndexDatatype.UNSIGNED_SHORT
  137. * });
  138. *
  139. * @example
  140. * // Example 2. Create a static index buffer containing three unsigned shorts.
  141. * var buffer = Buffer.createIndexBuffer({
  142. * context : context,
  143. * typedArray : new Uint16Array([0, 1, 2]),
  144. * usage : BufferUsage.STATIC_DRAW,
  145. * indexDatatype : IndexDatatype.UNSIGNED_SHORT
  146. * });
  147. *
  148. * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffer.xml|glGenBuffer}
  149. * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml|glBindBuffer} with <code>ELEMENT_ARRAY_BUFFER</code>
  150. * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with <code>ELEMENT_ARRAY_BUFFER</code>
  151. */
  152. Buffer.createIndexBuffer = function(options) {
  153. //>>includeStart('debug', pragmas.debug);
  154. Check.defined('options.context', options.context);
  155. if (!IndexDatatype.validate(options.indexDatatype)) {
  156. throw new DeveloperError('Invalid indexDatatype.');
  157. }
  158. if (options.indexDatatype === IndexDatatype.UNSIGNED_INT && !options.context.elementIndexUint) {
  159. throw new DeveloperError('IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system. Check context.elementIndexUint.');
  160. }
  161. //>>includeEnd('debug');
  162. var context = options.context;
  163. var indexDatatype = options.indexDatatype;
  164. var bytesPerIndex = IndexDatatype.getSizeInBytes(indexDatatype);
  165. var buffer = new Buffer({
  166. context : context,
  167. bufferTarget : WebGLConstants.ELEMENT_ARRAY_BUFFER,
  168. typedArray : options.typedArray,
  169. sizeInBytes : options.sizeInBytes,
  170. usage : options.usage
  171. });
  172. var numberOfIndices = buffer.sizeInBytes / bytesPerIndex;
  173. defineProperties(buffer, {
  174. indexDatatype: {
  175. get : function() {
  176. return indexDatatype;
  177. }
  178. },
  179. bytesPerIndex : {
  180. get : function() {
  181. return bytesPerIndex;
  182. }
  183. },
  184. numberOfIndices : {
  185. get : function() {
  186. return numberOfIndices;
  187. }
  188. }
  189. });
  190. return buffer;
  191. };
  192. defineProperties(Buffer.prototype, {
  193. sizeInBytes : {
  194. get : function() {
  195. return this._sizeInBytes;
  196. }
  197. },
  198. usage: {
  199. get : function() {
  200. return this._usage;
  201. }
  202. }
  203. });
  204. Buffer.prototype._getBuffer = function() {
  205. return this._buffer;
  206. };
  207. Buffer.prototype.copyFromArrayView = function(arrayView, offsetInBytes) {
  208. offsetInBytes = defaultValue(offsetInBytes, 0);
  209. //>>includeStart('debug', pragmas.debug);
  210. Check.defined('arrayView', arrayView);
  211. Check.typeOf.number.lessThanOrEquals('offsetInBytes + arrayView.byteLength', offsetInBytes + arrayView.byteLength, this._sizeInBytes);
  212. //>>includeEnd('debug');
  213. var gl = this._gl;
  214. var target = this._bufferTarget;
  215. gl.bindBuffer(target, this._buffer);
  216. gl.bufferSubData(target, offsetInBytes, arrayView);
  217. gl.bindBuffer(target, null);
  218. };
  219. Buffer.prototype.copyFromBuffer = function(readBuffer, readOffset, writeOffset, sizeInBytes) {
  220. //>>includeStart('debug', pragmas.debug);
  221. if (!this._webgl2) {
  222. throw new DeveloperError('A WebGL 2 context is required.');
  223. }
  224. if (!defined(readBuffer)) {
  225. throw new DeveloperError('readBuffer must be defined.');
  226. }
  227. if (!defined(sizeInBytes) || sizeInBytes <= 0) {
  228. throw new DeveloperError('sizeInBytes must be defined and be greater than zero.');
  229. }
  230. if (!defined(readOffset) || readOffset < 0 || readOffset + sizeInBytes > readBuffer._sizeInBytes) {
  231. throw new DeveloperError('readOffset must be greater than or equal to zero and readOffset + sizeInBytes must be less than of equal to readBuffer.sizeInBytes.');
  232. }
  233. if (!defined(writeOffset) || writeOffset < 0 || writeOffset + sizeInBytes > this._sizeInBytes) {
  234. throw new DeveloperError('writeOffset must be greater than or equal to zero and writeOffset + sizeInBytes must be less than of equal to this.sizeInBytes.');
  235. }
  236. if (this._buffer === readBuffer._buffer && ((writeOffset >= readOffset && writeOffset < readOffset + sizeInBytes) || (readOffset > writeOffset && readOffset < writeOffset + sizeInBytes))) {
  237. throw new DeveloperError('When readBuffer is equal to this, the ranges [readOffset + sizeInBytes) and [writeOffset, writeOffset + sizeInBytes) must not overlap.');
  238. }
  239. if ((this._bufferTarget === WebGLConstants.ELEMENT_ARRAY_BUFFER && readBuffer._bufferTarget !== WebGLConstants.ELEMENT_ARRAY_BUFFER) ||
  240. (this._bufferTarget !== WebGLConstants.ELEMENT_ARRAY_BUFFER && readBuffer._bufferTarget === WebGLConstants.ELEMENT_ARRAY_BUFFER)) {
  241. throw new DeveloperError('Can not copy an index buffer into another buffer type.');
  242. }
  243. //>>includeEnd('debug');
  244. var readTarget = WebGLConstants.COPY_READ_BUFFER;
  245. var writeTarget = WebGLConstants.COPY_WRITE_BUFFER;
  246. var gl = this._gl;
  247. gl.bindBuffer(writeTarget, this._buffer);
  248. gl.bindBuffer(readTarget, readBuffer._buffer);
  249. gl.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, sizeInBytes);
  250. gl.bindBuffer(writeTarget, null);
  251. gl.bindBuffer(readTarget, null);
  252. };
  253. Buffer.prototype.getBufferData = function(arrayView, sourceOffset, destinationOffset, length) {
  254. sourceOffset = defaultValue(sourceOffset, 0);
  255. destinationOffset = defaultValue(destinationOffset, 0);
  256. //>>includeStart('debug', pragmas.debug);
  257. if (!this._webgl2) {
  258. throw new DeveloperError('A WebGL 2 context is required.');
  259. }
  260. if (!defined(arrayView)) {
  261. throw new DeveloperError('arrayView is required.');
  262. }
  263. var copyLength;
  264. var elementSize;
  265. var arrayLength = arrayView.byteLength;
  266. if (!defined(length)) {
  267. if (defined(arrayLength)) {
  268. copyLength = arrayLength - destinationOffset;
  269. elementSize = 1;
  270. } else {
  271. arrayLength = arrayView.length;
  272. copyLength = arrayLength - destinationOffset;
  273. elementSize = arrayView.BYTES_PER_ELEMENT;
  274. }
  275. } else {
  276. copyLength = length;
  277. if (defined(arrayLength)) {
  278. elementSize = 1;
  279. } else {
  280. arrayLength = arrayView.length;
  281. elementSize = arrayView.BYTES_PER_ELEMENT;
  282. }
  283. }
  284. if (destinationOffset < 0 || destinationOffset > arrayLength) {
  285. throw new DeveloperError('destinationOffset must be greater than zero and less than the arrayView length.');
  286. }
  287. if (destinationOffset + copyLength > arrayLength) {
  288. throw new DeveloperError('destinationOffset + length must be less than or equal to the arrayViewLength.');
  289. }
  290. if (sourceOffset < 0 || sourceOffset > this._sizeInBytes) {
  291. throw new DeveloperError('sourceOffset must be greater than zero and less than the buffers size.');
  292. }
  293. if (sourceOffset + copyLength * elementSize > this._sizeInBytes) {
  294. throw new DeveloperError('sourceOffset + length must be less than the buffers size.');
  295. }
  296. //>>includeEnd('debug');
  297. var gl = this._gl;
  298. var target = WebGLConstants.COPY_READ_BUFFER;
  299. gl.bindBuffer(target, this._buffer);
  300. gl.getBufferSubData(target, sourceOffset, arrayView, destinationOffset, length);
  301. gl.bindBuffer(target, null);
  302. };
  303. Buffer.prototype.isDestroyed = function() {
  304. return false;
  305. };
  306. Buffer.prototype.destroy = function() {
  307. this._gl.deleteBuffer(this._buffer);
  308. return destroyObject(this);
  309. };
  310. export default Buffer;