VertexBuffer.js

  1. 'use strict';
  2. const WebGLContext = require('./WebGLContext');
  3. const MODES = {
  4. POINTS: true,
  5. LINES: true,
  6. LINE_STRIP: true,
  7. LINE_LOOP: true,
  8. TRIANGLES: true,
  9. TRIANGLE_STRIP: true,
  10. TRIANGLE_FAN: true
  11. };
  12. const TYPES = {
  13. BYTE: true,
  14. UNSIGNED_BYTE: true,
  15. SHORT: true,
  16. UNSIGNED_SHORT: true,
  17. FIXED: true,
  18. FLOAT: true
  19. };
  20. const BYTES_PER_TYPE = {
  21. BYTE: 1,
  22. UNSIGNED_BYTE: 1,
  23. SHORT: 2,
  24. UNSIGNED_SHORT: 2,
  25. FIXED: 4,
  26. FLOAT: 4
  27. };
  28. const SIZES = {
  29. 1: true,
  30. 2: true,
  31. 3: true,
  32. 4: true
  33. };
  34. /**
  35. * The default attribute point byte offset.
  36. * @private
  37. * @constant {number}
  38. */
  39. const DEFAULT_BYTE_OFFSET = 0;
  40. /**
  41. * The default render mode (primitive type).
  42. * @private
  43. * @constant {string}
  44. */
  45. const DEFAULT_MODE = 'TRIANGLES';
  46. /**
  47. * The default index offset to render from.
  48. * @private
  49. * @constant {number}
  50. */
  51. const DEFAULT_INDEX_OFFSET = 0;
  52. /**
  53. * The default count of indices to render.
  54. * @private
  55. * @constant {number}
  56. */
  57. const DEFAULT_COUNT = 0;
  58. /**
  59. * Parse the attribute pointers and determine the byte stride of the buffer.
  60. *
  61. * @private
  62. *
  63. * @param {Map} attributePointers - The attribute pointer map.
  64. *
  65. * @returns {number} The byte stride of the buffer.
  66. */
  67. function getStride(attributePointers) {
  68. // if there is only one attribute pointer assigned to this buffer,
  69. // there is no need for stride, set to default of 0
  70. if (attributePointers.size === 1) {
  71. return 0;
  72. }
  73. let maxByteOffset = 0;
  74. let byteSizeSum = 0;
  75. let byteStride = 0;
  76. attributePointers.forEach(pointer => {
  77. const byteOffset = pointer.byteOffset;
  78. const size = pointer.size;
  79. const type = pointer.type;
  80. // track the sum of each attribute size
  81. byteSizeSum += size * BYTES_PER_TYPE[type];
  82. // track the largest offset to determine the byte stride of the buffer
  83. if (byteOffset > maxByteOffset) {
  84. maxByteOffset = byteOffset;
  85. byteStride = byteOffset + (size * BYTES_PER_TYPE[type]);
  86. }
  87. });
  88. // check if the max byte offset is greater than or equal to the the sum of
  89. // the sizes. If so this buffer is not interleaved and does not need a
  90. // stride.
  91. if (maxByteOffset >= byteSizeSum) {
  92. // TODO: test what stride === 0 does for an interleaved buffer of
  93. // length === 1.
  94. return 0;
  95. }
  96. return byteStride;
  97. }
  98. /**
  99. * Parse the attribute pointers to ensure they are valid.
  100. *
  101. * @private
  102. *
  103. * @param {Object} attributePointers - The attribute pointer map.
  104. *
  105. * @returns {Object} The validated attribute pointer map.
  106. */
  107. function getAttributePointers(attributePointers) {
  108. // parse pointers to ensure they are valid
  109. const pointers = new Map();
  110. Object.keys(attributePointers).forEach(key => {
  111. const index = parseInt(key, 10);
  112. // check that key is an valid integer
  113. if (isNaN(index)) {
  114. throw `Attribute index \`${key}\` does not represent an integer`;
  115. }
  116. const pointer = attributePointers[key];
  117. const size = pointer.size;
  118. const type = pointer.type;
  119. const byteOffset = pointer.byteOffset;
  120. // check size
  121. if (!SIZES[size]) {
  122. throw 'Attribute pointer `size` parameter is invalid, must be one of ' +
  123. JSON.stringify(Object.keys(SIZES));
  124. }
  125. // check type
  126. if (!TYPES[type]) {
  127. throw 'Attribute pointer `type` parameter is invalid, must be one of ' +
  128. JSON.stringify(Object.keys(TYPES));
  129. }
  130. pointers.set(index, {
  131. size: size,
  132. type: type,
  133. byteOffset: (byteOffset !== undefined) ? byteOffset : DEFAULT_BYTE_OFFSET
  134. });
  135. });
  136. return pointers;
  137. }
  138. /**
  139. * A vertex buffer object.
  140. */
  141. class VertexBuffer {
  142. /**
  143. * Instantiates an VertexBuffer object.
  144. *
  145. * @param {WebGLBuffer|VertexPackage|ArrayBuffer|Array|Number} arg - The buffer or length of the buffer.
  146. * @param {Object} attributePointers - The array pointer map, or in the case of a vertex package arg, the options.
  147. * @param {Object} options - The rendering options.
  148. * @param {string} options.mode - The draw mode / primitive type.
  149. * @param {string} options.indexOffset - The index offset into the drawn buffer.
  150. * @param {string} options.count - The number of indices to draw.
  151. */
  152. constructor(arg, attributePointers = {}, options = {}) {
  153. this.gl = WebGLContext.get();
  154. this.buffer = null;
  155. this.mode = MODES[options.mode] ? options.mode : DEFAULT_MODE;
  156. this.count = (options.count !== undefined) ? options.count : DEFAULT_COUNT;
  157. this.indexOffset = (options.indexOffset !== undefined) ? options.indexOffset : DEFAULT_INDEX_OFFSET;
  158. // first, set the attribute pointers
  159. this.pointers = getAttributePointers(attributePointers);
  160. // set the byte stride
  161. this.byteStride = getStride(this.pointers);
  162. // then buffer the data
  163. if (arg) {
  164. if (arg instanceof WebGLBuffer) {
  165. // WebGLBuffer argument
  166. this.buffer = arg;
  167. } else {
  168. // Array or ArrayBuffer or number argument
  169. this.bufferData(arg);
  170. }
  171. }
  172. }
  173. /**
  174. * Upload vertex data to the GPU.
  175. *
  176. * @param {Array|ArrayBuffer|ArrayBufferView|number} arg - The array of data to buffer, or size of the buffer in bytes.
  177. *
  178. * @returns {VertexBuffer} The vertex buffer object, for chaining.
  179. */
  180. bufferData(arg) {
  181. const gl = this.gl;
  182. // ensure argument is valid
  183. if (Array.isArray(arg)) {
  184. // cast array into Float32Array
  185. arg = new Float32Array(arg);
  186. } else if (
  187. !(arg instanceof ArrayBuffer) &&
  188. !(ArrayBuffer.isView(arg)) &&
  189. !(Number.isInteger(arg))
  190. ) {
  191. // if not arraybuffer or a numeric size
  192. throw 'Argument must be of type `Array`, `ArrayBuffer`, `ArrayBufferView`, or `Number`';
  193. }
  194. // create buffer if it doesn't exist already
  195. if (!this.buffer) {
  196. this.buffer = gl.createBuffer();
  197. }
  198. // buffer the data
  199. gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
  200. gl.bufferData(gl.ARRAY_BUFFER, arg, gl.STATIC_DRAW);
  201. }
  202. /**
  203. * Upload partial vertex data to the GPU.
  204. *
  205. * @param {Array|ArrayBuffer} array - The array of data to buffer.
  206. * @param {number} byteOffset - The byte offset at which to buffer.
  207. *
  208. * @returns {VertexBuffer} The vertex buffer object, for chaining.
  209. */
  210. bufferSubData(array, byteOffset = DEFAULT_BYTE_OFFSET) {
  211. const gl = this.gl;
  212. // ensure the buffer exists
  213. if (!this.buffer) {
  214. throw 'Buffer has not yet been allocated, allocate with `bufferData`';
  215. }
  216. // ensure argument is valid
  217. if (Array.isArray(array)) {
  218. array = new Float32Array(array);
  219. } else if (
  220. !(array instanceof ArrayBuffer) &&
  221. !ArrayBuffer.isView(array)
  222. ) {
  223. throw 'Argument must be of type `Array`, `ArrayBuffer`, or `ArrayBufferView`';
  224. }
  225. gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
  226. gl.bufferSubData(gl.ARRAY_BUFFER, byteOffset, array);
  227. return this;
  228. }
  229. /**
  230. * Binds the vertex buffer object.
  231. *
  232. * @returns {VertexBuffer} - Returns the vertex buffer object for chaining.
  233. */
  234. bind() {
  235. const gl = this.gl;
  236. // bind buffer
  237. gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
  238. // for each attribute pointer
  239. this.pointers.forEach((pointer, index) => {
  240. // set attribute pointer
  241. gl.vertexAttribPointer(
  242. index,
  243. pointer.size,
  244. gl[pointer.type],
  245. false,
  246. this.byteStride,
  247. pointer.byteOffset);
  248. // enable attribute index
  249. gl.enableVertexAttribArray(index);
  250. });
  251. return this;
  252. }
  253. /**
  254. * Unbinds the vertex buffer object.
  255. *
  256. * @returns {VertexBuffer} The vertex buffer object, for chaining.
  257. */
  258. unbind() {
  259. const gl = this.gl;
  260. // unbind buffer
  261. gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
  262. this.pointers.forEach((pointer, index) => {
  263. // disable attribute index
  264. gl.disableVertexAttribArray(index);
  265. });
  266. return this;
  267. }
  268. /**
  269. * Execute the draw command for the bound buffer.
  270. *
  271. * @param {Object} options - The options to pass to 'drawArrays'. Optional.
  272. * @param {string} options.mode - The draw mode / primitive type.
  273. * @param {string} options.indexOffset - The index offset into the drawn buffer.
  274. * @param {string} options.count - The number of indices to draw.
  275. *
  276. * @returns {VertexBuffer} The vertex buffer object, for chaining.
  277. */
  278. draw(options = {}) {
  279. const gl = this.gl;
  280. const mode = gl[options.mode || this.mode];
  281. const indexOffset = (options.indexOffset !== undefined) ? options.indexOffset : this.indexOffset;
  282. const count = (options.count !== undefined) ? options.count : this.count;
  283. if (count === 0) {
  284. throw 'Attempting to draw with a count of 0';
  285. }
  286. // draw elements
  287. gl.drawArrays(mode, indexOffset, count);
  288. return this;
  289. }
  290. }
  291. module.exports = VertexBuffer;