mirror of
https://github.com/visgl/luma.gl.git
synced 2025-12-08 17:36:19 +00:00
Refactor VertexArrayObject (#184)
This commit is contained in:
parent
a2af1caa2a
commit
71f260ccff
@ -1,12 +1,32 @@
|
||||
# VertexArrayObject (EXT)
|
||||
# VertexArrayObject
|
||||
|
||||
A vertex array object is a WebGL object that stores all of the state needed to supply vertex data. While `VertexArrayObject`s are not available in basic WebGL1 environments, they are available by default in WebGL2 and via a commonly supported extension under WebGL1.
|
||||
A `VertexArrayObject` stores a set of `Buffer` bindings representing the input data to GLSL shaders, in much the same way that a `TransformFeedback` object stores a set of `Buffer` bindings for output data from shaders.
|
||||
|
||||
For more information, see [OpenGL Wiki](https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_Array_Object).
|
||||
|
||||
Version Notes:
|
||||
* Instancing requires a WebGL extension or WebGL2.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
| **Function** | **WebGL Counterpart** | **Description** |
|
||||
| --- | --- | --- |
|
||||
| [`setBuffer`](#setBuffer) | `vertexAttrib{I}Pointer` | Set to ['WebGLBuffer'](buffer.html) |
|
||||
| [`setGeneric`](#setGeneric) | `vertexAttrib4[u]{f,i}v` | Set value to a constant |
|
||||
| `enable` | `enableVertexAttribArray` | attribute visible to shader |
|
||||
| `disable` | `disableVertexAttribArray` | not visible to shader |
|
||||
| `setDivisor` <sub>**WebGL2/ext**</sub> | `vertexAttribDivisor` | (un)marks as instanced |
|
||||
| `getMaxAttributes` | `MAX_VERTEX_ATTRIBS` | Length of array (>=8) |
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Getting the global `VertexArrayObject` for a WebGL context
|
||||
```js
|
||||
const vertexArray = VertexArrayObject.global(gl);
|
||||
```
|
||||
|
||||
Creating a VertexArrayObject
|
||||
```js
|
||||
import {VertexArrayObject} from 'luma.gl';
|
||||
@ -27,17 +47,63 @@ Deleting a VertexArrayObject
|
||||
vertexArrayObject.delete();
|
||||
```
|
||||
|
||||
Setting a set of attributes and an elements array
|
||||
```js
|
||||
const vertexArray = new VertexArrayObject(gl, {
|
||||
elements: new Buffer({target: GL.ELEMENT_ARRAY_BUFFER, data: new Uint32Array([...])}),
|
||||
attributes: {
|
||||
0:
|
||||
}
|
||||
}
|
||||
|
||||
Setting a buffer name map
|
||||
```js
|
||||
const vertexArray = new VertexArrayObject(gl);
|
||||
// Can only set buffers using location indices
|
||||
vertexArray.setAttributes({
|
||||
0: new Buffer({size: 3, data: new Float32Array([...]), ...})
|
||||
})
|
||||
// Register a location map
|
||||
const program = new Program();
|
||||
const locations = program.getLocations(); // Note: slow call, GPU driver roundtrip
|
||||
vertexArray.update({locations});
|
||||
// Now possible to set buffers using attribute names
|
||||
vertexArray.setAttributes({
|
||||
aColor: new Buffer({size: 3, data: new Float32Array([...]), ...})
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Setting a generic vertex attribute
|
||||
```js
|
||||
import {VertexAttributes} from 'luma.gl';
|
||||
VertexAttributes.setGeneric(gl, 0, ...);
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
`VertexArrayObject` inherits from `Resource`.
|
||||
|
||||
### global (static method)
|
||||
|
||||
Returns the "global" `VertexArrayObject` which is always supported.
|
||||
|
||||
|
||||
### isSupported (static method)
|
||||
|
||||
`VertexArrayObject.isSupported({vertexArrayObjects: true, instanceDivisors: true});`
|
||||
|
||||
Parameters:
|
||||
* gl (WebGLRenderingContext) - gl context
|
||||
* `vertexArrayObjects`=`true` - if set, returns true only if `VertexArrayObject`s are supported.
|
||||
* `instanceDivisors`=`true` - if set, returns true only if instance divisors are supported.
|
||||
|
||||
Returns:
|
||||
* Boolean - true if VertexArrayObjects are supported in the current environment.
|
||||
* Boolean - true if `VertexArrayObject`s and/or instanced rendering are supported in the current environment.
|
||||
|
||||
Note that while `VertexArrayObject`s and instance divisors are technically not available in basic WebGL1 environments, they available by default in WebGL2 and via commonly supported extensions under WebGL1.
|
||||
* [instanced_arrays](https://webglstats.com/webgl/extension/ANGLE_instanced_arrays)
|
||||
* [vertex_array_objects](https://webglstats.com/webgl/extension/OES_vertex_array_object)
|
||||
|
||||
|
||||
### constructor
|
||||
@ -45,8 +111,238 @@ Returns:
|
||||
Creates a new VertexArrayObject
|
||||
|
||||
Parameters:
|
||||
* gl (WebGLRenderingContext) - gl context
|
||||
* `gl` (WebGLRenderingContext) - gl context
|
||||
* `opts` (Object) - passed through to `Resource` constructor and to `initialize`
|
||||
|
||||
|
||||
### initialize
|
||||
|
||||
Parameters:
|
||||
* `elements`=`null` (`Buffer`) - optional buffer representing elements array (i.e. indices)
|
||||
* `buffers`=`null` (`Buffer`) - optional buffer representing elements array (i.e. indices)
|
||||
* `location`={} (Object) - optional map of (attribute) names to location indices.
|
||||
|
||||
|
||||
### setLocations
|
||||
|
||||
|
||||
|
||||
### enable
|
||||
|
||||
// Enable the attribute
|
||||
* Note: By default all attributes are disabled. Only attributes
|
||||
* used by a program's shaders should be enabled.
|
||||
|
||||
|
||||
### disable
|
||||
|
||||
// Disable the attribute
|
||||
* Note: Only attributes used by a program's shaders should be enabled.
|
||||
* @param {GLuint} location - ordinal number of the attribute
|
||||
|
||||
* Attribute 0 can sometimes be treated specially by the driver, so to be safe this method avoids disabling it.
|
||||
|
||||
[gl.enable]
|
||||
|
||||
|
||||
### setBuffer
|
||||
|
||||
Assigns a buffer a vertex attribute. Vertex Shader will be invoked once (not considering indexing and instancing) with each value in the buffer's array.
|
||||
|
||||
// specifies *integer* data formats and locations of vertex attributes
|
||||
// For glVertexAttribIPointer, Values are always left as integer values.
|
||||
// Only accepts the integer types gl.BYTE, gl.UNSIGNED_BYTE,
|
||||
// gl.SHORT, gl.UNSIGNED_SHORT, gl.INT, gl.UNSIGNED_INT
|
||||
|
||||
/**
|
||||
* Set a location in vertex attributes array to a buffer, specifying
|
||||
* its data layout and integer to float conversion and normalization flags
|
||||
*
|
||||
* @param {GLuint} location - ordinal number of the attribute
|
||||
* @param {WebGLBuffer|Buffer} buffer - WebGL buffer to set as value
|
||||
* @param {GLuint} target=gl.ARRAY_BUFFER - which target to bind to
|
||||
* @param {Object} layout= Optional data layout, defaults to buffer's layout
|
||||
* @param {GLuint} layout.size - number of values per element (1-4)
|
||||
* @param {GLuint} layout.type - type of values (e.g. gl.FLOAT)
|
||||
* @param {GLbool} layout.normalized=false - normalize integers to [-1,1], [0,1]
|
||||
* @param {GLuint} layout.integer=false - WebGL2 only, disable int-to-float conv
|
||||
* @param {GLuint} layout.stride=0 - supports strided arrays
|
||||
* @param {GLuint} layout.offset=0 - supports strided arrays
|
||||
*/
|
||||
|
||||
setBuffer({location, buffer, ...});
|
||||
|
||||
1. **gl** (*WebGLRenderingContext) - gl context
|
||||
2. **location** (*GLuint*) - index of the attribute
|
||||
3. **buffer** (*WebGLBuffer*|*Buffer*)
|
||||
4. **target** (*GLuint*, gl.ARRAY_BUFFER) - which target to bind to
|
||||
4. **size** (*GLuint*) - number of values per element (1-4)
|
||||
4. **type** (*GLuint*) - type of values (e.g. gl.FLOAT)
|
||||
4. **normalized** (*boolean*, false) - normalize integers to [-1,1] or [0,1]
|
||||
4. **integer** (*boolean*, false) - **WebGL2 only** disable int-to-float conversion
|
||||
4. **stride** (*GLuint*, 0) - supports strided arrays
|
||||
4. **offset** (*GLuint*, 0) - supports strided arrays
|
||||
|
||||
* The application can enable normalization by setting the `normalized` flag to `true` in the `setBuffer` call.
|
||||
* **WebGL2** The application can disable integer to float conversion when running under WebGL2, by setting the `integer` flag to `true`.
|
||||
|
||||
[vertexAttrib{I}Pointer]()
|
||||
|
||||
|
||||
### setGeneric
|
||||
|
||||
Sets a constant (i.e. generic) value for a vertex attribute. All Vertex
|
||||
Shader invocations will get the same value.
|
||||
|
||||
/*
|
||||
* Specify values for generic vertex attributes
|
||||
* Generic vertex attributes are constant for all vertices
|
||||
* Up to 4 values depending on attribute size
|
||||
*
|
||||
* @param {GLuint} location - ordinal number of the attribute
|
||||
* @param {GLuint} divisor - instances that pass between updates of attribute
|
||||
*/
|
||||
|
||||
`VertexAttributes.setGeneric({gl, location, array});`
|
||||
|
||||
Arguments
|
||||
1. **gl** (*WebGLRenderingContext) - gl context
|
||||
2. **location** (*GLuint*) - index of the attribute
|
||||
|
||||
[vertexAttrib4[u]{f,i}v]()
|
||||
|
||||
|
||||
### setGenericValues
|
||||
|
||||
/*
|
||||
* Specify values for generic vertex attributes
|
||||
* Generic vertex attributes are constant for all vertices
|
||||
* Up to 4 values depending on attribute size
|
||||
*
|
||||
* @param {GLuint} location - ordinal number of the attribute
|
||||
* @param {GLuint} divisor - instances that pass between updates of attribute
|
||||
*/
|
||||
/* eslint-disable max-params */
|
||||
|
||||
|
||||
### setDivisor
|
||||
|
||||
Sets the instance divisor. 0 disables instancing, >=1 enables it.
|
||||
|
||||
See description of instancing in the overview above.
|
||||
|
||||
/**
|
||||
* Set the frequency divisor used for instanced rendering.
|
||||
* Note: Usually simply set to 1 or 0 to enable/disable instanced rendering
|
||||
* for a specific attribute.
|
||||
* @param {GLuint} location - ordinal number of the attribute
|
||||
* @param {GLuint} divisor - instances that pass between updates of attribute
|
||||
*/
|
||||
|
||||
`VertexAttributes.setDivistor({gl, location, array});`
|
||||
|
||||
1. **gl** (*WebGLRenderingContext) - gl context
|
||||
2. **location** (*GLuint*) - index of the attribute
|
||||
|
||||
|
||||
* An attribute is referred to as **instanced** if its divisor value is non-zero.
|
||||
* The divisor modifies the rate at which generic vertex attributes advance when rendering multiple instances of primitives in a single draw call.
|
||||
* If divisor is zero, the attribute at slot index advances once per vertex.
|
||||
* If divisor is non-zero, the attribute advances once per divisor instances of the set(s) of vertices being rendered.
|
||||
|
||||
* This method will look use WebGL2 or the `array_instanced_ANGLE` extension, if available. To avoid exceptions on unsupported platforms. the app can call `VertexAttributeObject.isSupported()` to determine whether instancing is supported before invoking `VertexAttributes.setDivisor`.
|
||||
|
||||
[vertexAttribDivisor]()
|
||||
|
||||
|
||||
|
||||
|
||||
### getParameter
|
||||
|
||||
* **gl** (*WebGLRenderingContext*) - WebGL context
|
||||
* **location** (*Number*) - index of attributes
|
||||
|
||||
| Parameter | Type | Value |
|
||||
| --- | --- | --- |
|
||||
| `GL.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING` | `WebGLBuffer` (not `Buffer`) | Get currently bound buffer |
|
||||
| `GL.VERTEX_ATTRIB_ARRAY_ENABLED` | `GLboolean` | true if the vertex attribute at this index is enabled |
|
||||
| `GL.VERTEX_ATTRIB_ARRAY_SIZE` | `GLint` | indicating the size of an element of the vertex array. |
|
||||
| `GL.VERTEX_ATTRIB_ARRAY_STRIDE` | `GLint` | indicating the number of bytes between successive elements in |the array. 0 means that the elements are sequential.
|
||||
| `GL.VERTEX_ATTRIB_ARRAY_TYPE` | `GLenum` | The array type. One of
|
||||
`GL.BYTE`, `GL.UNSIGNED_BYTE`, `GL.SHORT`, `GL.UNSIGNED_SHORT`, `GL.FIXED`, `GL.FLOAT`. |
|
||||
| `GL.VERTEX_ATTRIB_ARRAY_NORMALIZED` | `GLboolean` | true if fixed-point data types are normalized for the vertex attribute array at the given index. |
|
||||
| `GL.CURRENT_VERTEX_ATTRIB` | `Float32Array(4)` | The current value of the vertex attribute at the given index. |
|
||||
When using a WebGL 2 context, the following values are available additionally:
|
||||
| `GL.VERTEX_ATTRIB_ARRAY_INTEGER` | `GLboolean` | true if an integer data type is in the vertex attribute array at the given index. |
|
||||
| `GL.VERTEX_ATTRIB_ARRAY_DIVISOR` | `GLint` | The frequency divisor used for instanced rendering. |
|
||||
|
||||
| `hasDivisor` | `ANGLE_instanced_arrays` | Instancing supported? |
|
||||
| `isEnabled` | `..._ARRAY_ENABLED` | Is attribute enabled? |
|
||||
| `getBuffer` | `..._ARRAY_BUFFER_BINDING` | Get buffer value |
|
||||
| `getGeneric` | `..._CURRENT_VERTEX_ATTRIB` | Get generic value |
|
||||
| `getSize` | `..._ARRAY_SIZE` | Elements/vertex (1-4) |
|
||||
| `getType` | `..._ARRAY_TYPE` | element type (GLenum)|
|
||||
| `isNormalized` | `..._ARRAY_NORMALIZED` | are integers normalized? |
|
||||
| `isInteger` <sub>**WebGL2**</sub> | `..._ARRAY_INTEGER` | integer-to-float disabled? |
|
||||
| `getStride` | `..._ARRAY_STRIDE` | bytes between elements |
|
||||
| `getOffset` | `getVertexAttribOffset` | index of first element |
|
||||
|
||||
|
||||
## Technical Notes
|
||||
|
||||
This module offers set of functions for manipulating WebGL's global "vertex attributes array". Essentially, this module collects all WebGL `gl.vertexAttrib*` methods and `gl.VERTEX_ATTRIB_ARRAY_*` queries.
|
||||
|
||||
It is usually not necessary to manipulate the vertex attributes array directly in luma.gl applications. It is often simpler to just supply named attribute buffers to the [`Model`](model.html) class, and rely on that class to automatically manage the vertex attributes array before running a program (e.g. when rendering, picking etc).
|
||||
|
||||
In WebGL, **vertex attributes** (often just called **attributes**) are input data to the Vertex Shader, the first shader stage in the GPU rendering pipeline.
|
||||
|
||||
These vertex attributes are stored in a conceptual global array with indices from 0 and up. At the start of shader execution, these indices (or 'locations') are matched to small integer indices assigned to shader attributes during shader compilation and program linking. This makes the data the application has set up in the vertex attributes available during shader execution. Vertex attributes thus represent one of the primary mechanisms for communication between JavaScript code and GPU code (GLSL shaders).
|
||||
|
||||
|
||||
### Vertex Attribute Values and Properties
|
||||
|
||||
Each vertex attribute has these properties:
|
||||
|
||||
- A value (constant or a buffered array with one set of values per vertex) that is accessible in shaders
|
||||
- Enabled status: Can be enabled or disabled
|
||||
- Data layout information: size (1-4 values per vertex), type, offset, stride
|
||||
- An instance `divisor` (which enables/disables instancing) **WebGL2/Extension**
|
||||
- An integer normalization policy (see below)
|
||||
- An integer conversion policy (see below) **WebGL2**
|
||||
|
||||
Normally attributes are set to a [`WebGLBuffer`](buffer.html) that stores unique values for each vertex/instance, combined with information about the layout of data in the memory managed by the buffer.
|
||||
|
||||
Attributes can also be set to "generic" values. This single value will then be passed to every invocation of the vertex shader effectively representing a constant attribute value. A typical example could be to specify a single color for all vertices, instead of providing a buffer with unique colors per vertex.
|
||||
|
||||
|
||||
### Integer to Float Conversion and Normalization
|
||||
|
||||
Integer values in attributes (e.g in an `Int32Array`) are converted to floats before being passed to the shader.
|
||||
|
||||
In addition, normalization, maps values stored in an integer format to a normalized floating point range before they are passed to the shader:
|
||||
|
||||
* `[-1,1]` (SNORM, for signed integers)
|
||||
* `[0,1]` (UNORM, for unsigned integers)
|
||||
|
||||
In WebGL2, it is possible to disable automatic conversion of integers to integers, enabling shaders to work directly with integer values. This works with all the integer types: `gl.BYTE`, `gl.UNSIGNED_BYTE`,
|
||||
`gl.SHORT`, `gl.UNSIGNED_SHORT`, `gl.INT` and `gl.UNSIGNED_INT`.
|
||||
|
||||
|
||||
## Remarks
|
||||
|
||||
Note that while you can create your own `VertexArrayObjects` there is a global "vertex attributes array" that is always available (even in core WebGL1)
|
||||
which is where vertex data is staged for vertex shader execution. This API is somewhat hard to learn for OpenGL newcomers so luma.gl provides this thin wrapper module to simplify its use.
|
||||
|
||||
* All methods in this class take a `location` index to specify which vertex attribute in the array they are operating on. This location needs to be matched with the location (i.e. index) selected by the compiler when compiling a Shader. Therefore it is usually better to work with symbolic names for vertex attributes, which is supported by other luma.gl classes.
|
||||
* It is strongly recommended to only enable attributes that are actually used by a program. Other attributes can be left unchanged but disabled.
|
||||
|
||||
* The raw WebGL APIs for are working with `WebGLVertexArrayObject`s are exposed differently in the WebGL1 extension and WebGL2. As always, the luma.gl `VertexArrayObject` class transparently handles the necessary API detection and selection.
|
||||
|
||||
**`ANGLE_instanced_arrays` Extension** Allows instance divisors to be set, enabling instanced rendering.
|
||||
* **`OES_VertexArrayObject` Extension** Enables the application to create and "VertexArrayObject"s to save and restore the entire global vertex attribute array with a single operation. luma.gl provides a class wrapper for `VertexArrayObjects`.
|
||||
|
||||
* Setting instance divisors no longer requires a WebGL extension.
|
||||
* `VertexArrayObjects` no longer require using a WebGL extension.
|
||||
* Adds support for exposing integer attribute values directly to shaders
|
||||
(without those values first being auto-converted to floats).
|
||||
The improvements cover both generic and buffered attributes.
|
||||
|
||||
@ -42,9 +42,6 @@ export {default as Renderbuffer} from './renderbuffer';
|
||||
export {default as Texture2D} from './texture-2d';
|
||||
export {default as TextureCube} from './texture-cube';
|
||||
|
||||
import * as VertexAttributes from './vertex-attributes';
|
||||
export {VertexAttributes};
|
||||
|
||||
// Functions
|
||||
export {
|
||||
draw
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-inline-comments */
|
||||
import GL from './api';
|
||||
import {assertWebGL2Context, isWebGL2Context} from './context';
|
||||
import * as VertexAttributes from './vertex-attributes';
|
||||
import VertexArrayObject from './vertex-array-object';
|
||||
import Resource from './resource';
|
||||
import Texture from './texture';
|
||||
import {parseUniformName, getUniformSetter} from './uniforms';
|
||||
@ -24,6 +24,7 @@ export default class Program extends Resource {
|
||||
constructor(gl, opts = {}) {
|
||||
super(gl, opts);
|
||||
this.initialize(opts);
|
||||
this.vertexAttributes = VertexArrayObject.getDefaultObject(gl);
|
||||
Object.seal(this);
|
||||
|
||||
// If program is not named, name it after shader names
|
||||
@ -66,8 +67,6 @@ export default class Program extends Resource {
|
||||
this._uniformCount = this.getUniformCount();
|
||||
this._textureIndexCounter = 0;
|
||||
|
||||
Object.seal(this);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -143,20 +142,18 @@ export default class Program extends Resource {
|
||||
|
||||
const {locations, elements} = this._sortBuffersByLocation(buffers);
|
||||
|
||||
const {gl} = this;
|
||||
|
||||
// Process locations in order
|
||||
for (let location = 0; location < locations.length; ++location) {
|
||||
const bufferName = locations[location];
|
||||
const buffer = buffers[bufferName];
|
||||
// DISABLE MISSING ATTRIBUTE
|
||||
if (!buffer) {
|
||||
VertexAttributes.disable(gl, location);
|
||||
this.vertexAttributes.disable(location);
|
||||
} else {
|
||||
const divisor = buffer.layout.instanced ? 1 : 0;
|
||||
VertexAttributes.enable(gl, location);
|
||||
VertexAttributes.setBuffer({gl, location, buffer});
|
||||
VertexAttributes.setDivisor(gl, location, divisor);
|
||||
this.vertexAttributes.enable(location);
|
||||
this.vertexAttributes.setBuffer({location, buffer});
|
||||
this.vertexAttributes.setDivisor(location, divisor);
|
||||
drawParams.isInstanced = buffer.layout.instanced > 0;
|
||||
this._filledLocations[bufferName] = true;
|
||||
}
|
||||
@ -184,8 +181,8 @@ export default class Program extends Resource {
|
||||
unsetBuffers() {
|
||||
const length = this._attributeCount;
|
||||
for (let i = 1; i < length; ++i) {
|
||||
// VertexAttributes.setDivisor(gl, i, 0);
|
||||
VertexAttributes.disable(this.gl, i);
|
||||
// this.vertexAttributes.setDivisor(i, 0);
|
||||
this.vertexAttributes.disable(i);
|
||||
}
|
||||
|
||||
// Clear elements buffer
|
||||
@ -384,10 +381,9 @@ export default class Program extends Resource {
|
||||
|
||||
// Check that all active attributes are enabled
|
||||
_areAllAttributesEnabled() {
|
||||
const {gl} = this;
|
||||
const length = this._attributeCount;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (!VertexAttributes.isEnabled(gl, i)) {
|
||||
if (!this.vertexAttributes.isEnabled(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,82 +1,392 @@
|
||||
// WebGL2 VertexArray Objects Helper
|
||||
import {isWebGL2Context} from './context';
|
||||
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 ERR_NOT_SUPPORTED = 'VertexArrayObject: WebGL2 or OES_vertex_array_object required';
|
||||
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 isWebGL2Context(gl) || gl.getExtension(OES_vertex_array_object);
|
||||
return isWebGL2(gl) || gl.getExtension(OES_vertex_array_object);
|
||||
}
|
||||
|
||||
static isHandle(gl, vertexArray) {
|
||||
if (isWebGL2Context(gl)) {
|
||||
return gl.isVertexArray(vertexArray);
|
||||
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
|
||||
}
|
||||
const ext = gl.getExtension(OES_vertex_array_object);
|
||||
if (ext) {
|
||||
return ext.isVertexArrayOES(vertexArray);
|
||||
}
|
||||
return false;
|
||||
return gl.luma.defaultVertexArray;
|
||||
}
|
||||
|
||||
static getMaxAttributes(gl) {
|
||||
return gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
|
||||
}
|
||||
|
||||
// Create a VertexArrayObject
|
||||
constructor(gl, opts = {}) {
|
||||
assert(VertexArrayObject.isSupported(gl), ERR_NOT_SUPPORTED);
|
||||
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);
|
||||
}
|
||||
|
||||
bind() {
|
||||
this._bindVertexArray(this.gl, this.handle);
|
||||
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;
|
||||
}
|
||||
|
||||
unbind() {
|
||||
this._bindVertexArray(this.gl, null);
|
||||
// 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() {
|
||||
if (isWebGL2Context(this.gl)) {
|
||||
return this.gl.createVertexArray();
|
||||
}
|
||||
const ext = this.gl.getExtension(OES_vertex_array_object);
|
||||
if (ext) {
|
||||
return ext.createVertexArrayOES();
|
||||
}
|
||||
return null;
|
||||
return this.ext.createVertexArray();
|
||||
}
|
||||
|
||||
_deleteHandle(handle) {
|
||||
if (isWebGL2Context(this.gl)) {
|
||||
this.gl.deleteVertexArray(handle);
|
||||
return;
|
||||
}
|
||||
const ext = this.gl.getExtension(OES_vertex_array_object);
|
||||
if (ext) {
|
||||
ext.deleteVertexArrayOES(handle);
|
||||
}
|
||||
this.ext.deleteVertexArray(handle);
|
||||
return [this.elements];
|
||||
// return [this.elements, ...this.buffers];
|
||||
}
|
||||
|
||||
// WebGL2 does not have any methods for querying vertex array objects
|
||||
// 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));
|
||||
|
||||
// PRIVATE METHODS
|
||||
this.ext.bindVertexArray(this.handle);
|
||||
|
||||
_bindVertexArray(gl, vertexArray) {
|
||||
if (isWebGL2Context(gl)) {
|
||||
gl.bindVertexArray(vertexArray);
|
||||
}
|
||||
const ext = gl.getExtension(OES_vertex_array_object);
|
||||
if (ext) {
|
||||
ext.bindVertexArrayOES(vertexArray);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,8 @@ import './context-limits.spec.js';
|
||||
import './context-state.spec.js';
|
||||
|
||||
import './buffer.spec';
|
||||
import './vertex-array-object.spec';
|
||||
|
||||
import './program.spec';
|
||||
import './renderbuffer.spec';
|
||||
import './texture.spec';
|
||||
@ -23,4 +25,3 @@ import './uniform-buffer-layout.spec';
|
||||
|
||||
// Extensions / webgl2
|
||||
import './query.spec';
|
||||
// import './vertex-array-object.spec';
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import test from 'tape-catch';
|
||||
import {createGLContext} from 'luma.gl';
|
||||
import 'luma.gl/headless';
|
||||
import {GL, createGLContext} from 'luma.gl';
|
||||
import {VertexArrayObject} from 'luma.gl';
|
||||
|
||||
const fixture = {
|
||||
@ -18,7 +17,6 @@ test('WebGL#VertexArrayObject construct/delete', t => {
|
||||
|
||||
t.throws(
|
||||
() => new VertexArrayObject(),
|
||||
/.*WebGLRenderingContext.*/,
|
||||
'VertexArrayObject throws on missing gl context');
|
||||
|
||||
const vao = new VertexArrayObject(gl);
|
||||
@ -32,3 +30,60 @@ test('WebGL#VertexArrayObject construct/delete', t => {
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('WebGL#VertexAttributes#enable', t => {
|
||||
const gl = createGLContext();
|
||||
|
||||
const vertexAttributes = VertexArrayObject.getDefaultObject(gl);
|
||||
|
||||
const MAX_ATTRIBUTES = VertexArrayObject.getMaxAttributes(gl);
|
||||
t.ok(MAX_ATTRIBUTES >= 8, 'vertexAttributes.getMaxAttributes() >= 8');
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
t.equal(vertexAttributes.getParameter(GL.VERTEX_ATTRIB_ARRAY_ENABLED, {location: i}), false,
|
||||
`vertex attribute ${i} should initially be disabled`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
vertexAttributes.enable(i);
|
||||
}
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
t.equal(vertexAttributes.getParameter(GL.VERTEX_ATTRIB_ARRAY_ENABLED, {location: i}), true,
|
||||
`vertex attribute ${i} should now be enabled`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
vertexAttributes.disable(i);
|
||||
}
|
||||
|
||||
t.equal(vertexAttributes.getParameter(GL.VERTEX_ATTRIB_ARRAY_ENABLED, {location: 0}), true,
|
||||
'vertex attribute 0 should **NOT** be disabled');
|
||||
for (let i = 1; i < MAX_ATTRIBUTES; i++) {
|
||||
t.equal(vertexAttributes.getParameter(GL.VERTEX_ATTRIB_ARRAY_ENABLED, {location: i}), false,
|
||||
`vertex attribute ${i} should now be disabled`);
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('WebGL#vertexAttributes#WebGL2 support', t => {
|
||||
const gl = createGLContext({webgl2: true});
|
||||
|
||||
if (!VertexArrayObject.isSupported(gl, {instancedArrays: true})) {
|
||||
t.comment('- instanced arrays not enabled: skipping tests');
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const vertexAttributes = VertexArrayObject.getDefaultObject(gl);
|
||||
|
||||
const MAX_ATTRIBUTES = VertexArrayObject.getMaxAttributes(gl);
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
t.equal(vertexAttributes.getParameter(GL.VERTEX_ATTRIB_ARRAY_DIVISOR, {location: i}), 0,
|
||||
`vertex attribute ${i} should have 0 divisor`);
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
/* eslint-disable max-len */
|
||||
import test from 'tape-catch';
|
||||
import 'luma.gl/headless';
|
||||
import {createGLContext, isWebGL2Context} from 'luma.gl';
|
||||
import * as VertexAttributes from 'luma.gl/webgl/vertex-attributes';
|
||||
|
||||
test('WebGL#VertexAttributes#enable', t => {
|
||||
const gl = createGLContext();
|
||||
|
||||
const MAX_ATTRIBUTES = VertexAttributes.getMaxAttributes(gl);
|
||||
t.ok(MAX_ATTRIBUTES >= 8, 'VertexAttributes.getMaxAttributes() >= 8');
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
t.equal(VertexAttributes.isEnabled(gl, i), false, `vertex attribute ${i} should initially be disabled`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
VertexAttributes.enable(gl, i);
|
||||
}
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
t.equal(VertexAttributes.isEnabled(gl, i), true, `vertex attribute ${i} should now be enabled`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
VertexAttributes.disable(gl, i);
|
||||
}
|
||||
|
||||
t.equal(VertexAttributes.isEnabled(gl, 0), true, 'vertex attribute 0 should **NOT** be disabled');
|
||||
for (let i = 1; i < MAX_ATTRIBUTES; i++) {
|
||||
t.equal(VertexAttributes.isEnabled(gl, i), false, `vertex attribute ${i} should now be disabled`);
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('WebGL#VertexAttributes#WebGL2 support', t => {
|
||||
const gl = createGLContext({webgl2: true});
|
||||
|
||||
if (!isWebGL2Context(gl)) {
|
||||
t.comment('- WebGL2 NOT ENABLED: skipping tests');
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
const MAX_ATTRIBUTES = VertexAttributes.getMaxAttributes(gl);
|
||||
|
||||
t.ok(MAX_ATTRIBUTES >= 8, 'VertexAttributes.getMaxAttributes() >= 8');
|
||||
|
||||
for (let i = 0; i < MAX_ATTRIBUTES; i++) {
|
||||
t.equal(VertexAttributes.wegl2getDivisor(gl, i), 0, `vertex attribute ${i} should have 0 divisor`);
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user