Buffer.js 14 KB

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