luma.gl/src/webgl/vertex-array-object.js
2017-05-26 19:55:50 -07:00

393 lines
12 KiB
JavaScript

// WebGL2 VertexArray Objects Helper
import {isWebGL2} from './context';
import Resource from './resource';
import assert from 'assert';
import {log} from '../utils';
/* eslint-disable camelcase */
const OES_vertex_array_object = 'OES_vertex_array_object';
const GL_ELEMENT_ARRAY_BUFFER = 0x8893;
// const GL_CURRENT_VERTEX_ATTRIB = 0x8626;
// const GL_VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622;
// const GL_VERTEX_ATTRIB_ARRAY_SIZE = 0x8623;
// const GL_VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624;
// const GL_VERTEX_ATTRIB_ARRAY_TYPE = 0x8625;
// const GL_VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A;
// const GL_VERTEX_ATTRIB_ARRAY_POINTER = 0x8645;
// const GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F;
// const GL_VERTEX_ATTRIB_ARRAY_INTEGER = 0x88FD;
// const GL_VERTEX_ATTRIB_ARRAY_DIVISOR = 0x88FE;
const ERR_ELEMENTS = 'elements must be GL.ELEMENT_ARRAY_BUFFER';
export default class VertexArrayObject extends Resource {
static isSupported(gl) {
return isWebGL2(gl) || gl.getExtension(OES_vertex_array_object);
}
static getDefaultObject(gl) {
gl.luma = gl.luma || {};
if (!gl.luma.defaultVertexArray) {
console.error('Creating getDefaultObject'); // eslint-disable-line
gl.luma.defaultVertexArray = new VertexArrayObject(gl, {handle: null});
console.error('Created getDefaultObject'); // eslint-disable-line
}
return gl.luma.defaultVertexArray;
}
static getMaxAttributes(gl) {
return gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
}
// Create a VertexArrayObject
constructor(gl, opts = {}) {
super(gl, opts);
this.elements = null;
this.buffers = {}; // new Array(this.MAX_VERTEX_ATTRIBS).fill(null);
this.locations = {};
this.names = {};
this.drawParameters = {};
this._bound = false;
Object.seal(this);
this.initialize(opts);
}
initialize({
buffers = {},
elements = null,
locations = {}
} = {}) {
this.setLocations(locations);
this.setBuffers(buffers, {clear: true});
this.setElements(elements);
}
// Register an optional buffer name to location mapping
setLocations(locations) {
this.locations = locations;
this.names = {};
}
// Set (bind) an elements buffer, for indexed rendering. Must be GL.ELEMENT_ARRAY_BUFFER
setElements(elements) {
assert(!elements || elements.target === GL_ELEMENT_ARRAY_BUFFER, ERR_ELEMENTS);
this.ext.bindVertexArray(this.handle);
this.gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elements && elements.handle);
this.ext.bindVertexArray(null);
this.elements = elements;
return this;
}
// Set (bind) an array or map of vertex array buffers, either in numbered or
// named locations. (named locations requires `locations` to have been provided).
// For names that are not present in `location`, the supplied buffers will be ignored.
// if a single buffer of type GL.ELEMENT_ARRAY_BUFFER is present, it will be set as elements
// @param {Object} buffers - An object map with attribute names being keys
// and values are expected to be instances of Buffer.
setBuffers(buffers, {clear, check}) {
const {locations, elements} = this._getLocations(buffers);
this.ext.bindVertexArray(this.handle);
// Process locations in order
for (let location = 0; location < locations.length; ++location) {
const buffer = locations[location];
// DISABLE MISSING ATTRIBUTE
if (buffer) {
const divisor = buffer.layout.instanced ? 1 : 0;
this.vertexAttributes.enable(location);
this.vertexAttributes.setBuffer({location, buffer});
this.vertexAttributes.setDivisor(location, divisor);
} else {
this.vertexAttributes.disable(location);
}
}
this.buffers = buffers;
this.ext.bindVertexArray(null);
if (elements) {
this.setElements(elements);
}
if (check) {
this._checkBuffers();
}
}
// Enable an attribute
enable(location) {
this.bind(() => {
this.gl.enableVertexAttribArray(location);
});
}
// Disable an attribute
disable(location) {
// Don't disable location 0
if (location > 0) {
this.bind(() => {
this.gl.disableVertexAttribArray(location);
});
}
}
// Set the frequency divisor used for instanced rendering.
setDivisor(location, divisor) {
this.bind(() => {
this.ext.vertexAttribDivisor(location, divisor);
});
}
// Set a location in vertex attributes array to a buffer
setBuffer({
location,
buffer,
target,
layout
} = {}) {
const {gl} = this;
// Copy main data characteristics from buffer
target = target !== undefined ? target : buffer.target;
layout = layout !== undefined ? layout : buffer.layout;
assert(target, 'setBuffer needs target');
assert(layout, 'setBuffer called on uninitialized buffer');
this.bind(() => {
// a non-zero named buffer object must be bound to the GL_ARRAY_BUFFER target
buffer.bind({target: gl.ARRAY_BUFFER});
const {size, type, normalized, stride, offset} = layout;
// Attach _bound ARRAY_BUFFER with specified buffer format to location
if (!layout.integer) {
gl.vertexAttribPointer(location, size, type, normalized, stride, offset);
} else {
// specifies *integer* data formats and locations of vertex attributes
assert(isWebGL2(gl));
gl.vertexAttribIPointer(location, size, type, stride, offset);
}
});
}
// Specify values for generic vertex attributes
setGeneric({location, array}) {
log.warn(0, 'VertexAttributes.setGeneric is not well tested');
// throw new Error('vertex attribute size must be between 1 and 4');
const {gl} = this;
switch (array.constructor) {
case Float32Array:
gl.vertexAttrib4fv(location, array);
break;
case Int32Array:
assert(isWebGL2(gl));
gl.vertexAttribI4iv(location, array);
break;
case Uint32Array:
assert(isWebGL2(gl));
gl.vertexAttribI4uiv(location, array);
break;
default:
}
return this;
}
// Specify values for generic vertex attributes
setGenericValues(location, v0, v1, v2, v3) {
log.warn(0, 'VertexAttributes.setGenericValues is not well tested');
switch (arguments.length - 1) {
case 1: this.gl.vertexAttrib1f(location, v0); break;
case 2: this.gl.vertexAttrib2f(location, v0, v1); break;
case 3: this.gl.vertexAttrib3f(location, v0, v1, v2); break;
case 4: this.gl.vertexAttrib4f(location, v0, v1, v2, v3); break;
default: throw new Error('vertex attribute size must be between 1 and 4');
}
// assert(gl instanceof WebGL2RenderingContext, 'WebGL2 required');
// Looks like these will check how many arguments were supplied?
// gl.vertexAttribI4i(location, v0, v1, v2, v3);
// gl.vertexAttribI4ui(location, v0, v1, v2, v3);
}
bind(funcOrHandle = this.handle) {
if (typeof funcOrHandle !== 'function') {
this.bindVertexArray(funcOrHandle);
return this;
}
let value;
if (!this._bound) {
this.ext.bindVertexArray(this.handle);
this._bound = true;
value = funcOrHandle();
this.ext.bindVertexArray(null);
this._bound = false;
} else {
value = funcOrHandle();
}
return value;
}
// PRIVATE
// Auto detect draw parameters from the complement of buffers provided
_deduceDrawParameters() {
// indexing is autodetected - buffer with target gl.ELEMENT_ARRAY_BUFFER
// index type is saved for drawElement calls
let isInstanced = false;
let isIndexed = false;
let indexType = null;
// Check if we have an elements array buffer
if (this.elements) {
isIndexed = true;
indexType = this.elements.layout.type;
}
// Check if any instanced buffers
this.buffers.forEach(buffer => {
if (buffer.layout.instanced > 0) {
isInstanced = true;
}
});
return {isInstanced, isIndexed, indexType};
}
// this._filledLocations[bufferName] = true;
_getLocations(buffers) {
// Try to extract elements and locations
let elements = null;
const locations = {};
for (const bufferName in buffers) {
const buffer = buffers[bufferName];
// Check if this is an elements array
if (buffer && buffer.target === GL_ELEMENT_ARRAY_BUFFER) {
assert(!elements, 'Duplicate GL.ELEMENT_ARRAY_BUFFER');
// assert(location === undefined, 'GL.ELEMENT_ARRAY_BUFFER assigned to location');
elements = buffer;
}
// else if (!this._warn[bufferName]) {
// log.warn(2, `${this._print(bufferName)} not used`);
// this._warn[bufferName] = true;
// }
let location = Number(bufferName);
// if key is a number, interpret as the location
// if key is not a location number, assume it is a named buffer, look it up in supplied map
if (!Number.isFinite(location)) {
location = this.locations[bufferName];
}
assert(Number.isFinite(location));
assert(!locations[location], `Duplicate attribute for binding point ${location}`);
locations[location] = buffer;
}
return {locations, elements};
}
_sortBuffersByLocation(buffers) {
// Try to extract elements and locations
let elements = null;
const locations = new Array(this._attributeCount).fill(null);
for (const bufferName in buffers) {
const buffer = buffers[bufferName];
// Check if this is an elements arrau
if (buffer.target === GL_ELEMENT_ARRAY_BUFFER) {
assert(!elements, 'Duplicate GL.ELEMENT_ARRAY_BUFFER');
// assert(location === undefined, 'GL.ELEMENT_ARRAY_BUFFER assigned to location');
elements = buffer;
} else if (!this._warn[bufferName]) {
log.warn(2, `${this._print(bufferName)} not used`);
this._warn[bufferName] = true;
}
let location = Number(bufferName);
// if key is a number, interpret as the location
// if key is not a location number, assume it is a named buffer, look it up in supplied map
if (!Number.isFinite(location)) {
location = this.locations[bufferName];
}
locations[location] = bufferName;
assert(locations[location] === null, `Duplicate attribute for binding point ${location}`);
locations[location] = location;
}
return {locations, elements};
}
_checkBuffers() {
for (const attributeName in this._attributeLocations) {
if (!this._filledLocations[attributeName] && !this._warn[attributeName]) {
const location = this._attributeLocations[attributeName];
// throw new Error(`Program ${this.id}: ` +
// `Attribute ${location}:${attributeName} not supplied`);
log.warn(0, `Program ${this.id}: Attribute ${location}:${attributeName} not supplied`);
this._warn[attributeName] = true;
}
}
return this;
}
// RESOURCE IMPLEMENTATION
_createHandle() {
return this.ext.createVertexArray();
}
_deleteHandle(handle) {
this.ext.deleteVertexArray(handle);
return [this.elements];
// return [this.elements, ...this.buffers];
}
// Generic getter for information about a vertex attribute at a given position
// @param {GLuint} location - index of the vertex attribute.
// @param {GLenum} pname - specifies the information to query.
// @returns {*} - requested vertex attribute information (specified by pname)
_getParameter(pname, {location}) {
assert(Number.isFinite(location));
this.ext.bindVertexArray(this.handle);
// Let the polyfill intercept the query
let result = this.ext.getVertexAttrib(location, pname);
if (result === undefined) {
result = this.gl.getVertexAttrib(location, pname);
}
this.ext.bindVertexArray(null);
return result;
}
_bind(handle) {
this.ext.bindVertexArray(handle);
}
}