Buffer.js 14 KB

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