419 lines
12 KiB
JavaScript
419 lines
12 KiB
JavaScript
var check = require('./util/check')
|
|
var values = require('./util/values')
|
|
var bufferTypes = require('./constants/dtypes.json')
|
|
var isTypedArray = require('./util/is-typed-array')
|
|
var isNDArrayLike = require('./util/is-ndarray')
|
|
var primitives = require('./constants/primitives.json')
|
|
|
|
var GL_FLOAT = 5126
|
|
var GL_ARRAY_BUFFER = 34962
|
|
var GL_ELEMENT_ARRAY_BUFFER = 34963
|
|
|
|
var VAO_OPTIONS = [
|
|
'attributes',
|
|
'elements',
|
|
'offset',
|
|
'count',
|
|
'primitive',
|
|
'instances'
|
|
]
|
|
|
|
function AttributeRecord () {
|
|
this.state = 0
|
|
|
|
this.x = 0.0
|
|
this.y = 0.0
|
|
this.z = 0.0
|
|
this.w = 0.0
|
|
|
|
this.buffer = null
|
|
this.size = 0
|
|
this.normalized = false
|
|
this.type = GL_FLOAT
|
|
this.offset = 0
|
|
this.stride = 0
|
|
this.divisor = 0
|
|
}
|
|
|
|
module.exports = function wrapAttributeState (
|
|
gl,
|
|
extensions,
|
|
limits,
|
|
stats,
|
|
bufferState,
|
|
elementState,
|
|
drawState) {
|
|
var NUM_ATTRIBUTES = limits.maxAttributes
|
|
var attributeBindings = new Array(NUM_ATTRIBUTES)
|
|
for (var i = 0; i < NUM_ATTRIBUTES; ++i) {
|
|
attributeBindings[i] = new AttributeRecord()
|
|
}
|
|
var vaoCount = 0
|
|
var vaoSet = {}
|
|
|
|
var state = {
|
|
Record: AttributeRecord,
|
|
scope: {},
|
|
state: attributeBindings,
|
|
currentVAO: null,
|
|
targetVAO: null,
|
|
restore: extVAO() ? restoreVAO : function () {},
|
|
createVAO: createVAO,
|
|
getVAO: getVAO,
|
|
destroyBuffer: destroyBuffer,
|
|
setVAO: extVAO() ? setVAOEXT : setVAOEmulated,
|
|
clear: extVAO() ? destroyVAOEXT : function () {}
|
|
}
|
|
|
|
function destroyBuffer (buffer) {
|
|
for (var i = 0; i < attributeBindings.length; ++i) {
|
|
var record = attributeBindings[i]
|
|
if (record.buffer === buffer) {
|
|
gl.disableVertexAttribArray(i)
|
|
record.buffer = null
|
|
}
|
|
}
|
|
}
|
|
|
|
function extVAO () {
|
|
return extensions.oes_vertex_array_object
|
|
}
|
|
|
|
function extInstanced () {
|
|
return extensions.angle_instanced_arrays
|
|
}
|
|
|
|
function getVAO (vao) {
|
|
if (typeof vao === 'function' && vao._vao) {
|
|
return vao._vao
|
|
}
|
|
return null
|
|
}
|
|
|
|
function setVAOEXT (vao) {
|
|
if (vao === state.currentVAO) {
|
|
return
|
|
}
|
|
var ext = extVAO()
|
|
if (vao) {
|
|
ext.bindVertexArrayOES(vao.vao)
|
|
} else {
|
|
ext.bindVertexArrayOES(null)
|
|
}
|
|
state.currentVAO = vao
|
|
}
|
|
|
|
function setVAOEmulated (vao) {
|
|
if (vao === state.currentVAO) {
|
|
return
|
|
}
|
|
if (vao) {
|
|
vao.bindAttrs()
|
|
} else {
|
|
var exti = extInstanced()
|
|
for (var i = 0; i < attributeBindings.length; ++i) {
|
|
var binding = attributeBindings[i]
|
|
if (binding.buffer) {
|
|
gl.enableVertexAttribArray(i)
|
|
binding.buffer.bind()
|
|
gl.vertexAttribPointer(i, binding.size, binding.type, binding.normalized, binding.stride, binding.offfset)
|
|
if (exti && binding.divisor) {
|
|
exti.vertexAttribDivisorANGLE(i, binding.divisor)
|
|
}
|
|
} else {
|
|
gl.disableVertexAttribArray(i)
|
|
gl.vertexAttrib4f(i, binding.x, binding.y, binding.z, binding.w)
|
|
}
|
|
}
|
|
if (drawState.elements) {
|
|
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, drawState.elements.buffer.buffer)
|
|
} else {
|
|
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, null)
|
|
}
|
|
}
|
|
state.currentVAO = vao
|
|
}
|
|
|
|
function destroyVAOEXT () {
|
|
values(vaoSet).forEach(function (vao) {
|
|
vao.destroy()
|
|
})
|
|
}
|
|
|
|
function REGLVAO () {
|
|
this.id = ++vaoCount
|
|
this.attributes = []
|
|
this.elements = null
|
|
this.ownsElements = false
|
|
this.count = 0
|
|
this.offset = 0
|
|
this.instances = -1
|
|
this.primitive = 4
|
|
var extension = extVAO()
|
|
if (extension) {
|
|
this.vao = extension.createVertexArrayOES()
|
|
} else {
|
|
this.vao = null
|
|
}
|
|
vaoSet[this.id] = this
|
|
this.buffers = []
|
|
}
|
|
|
|
REGLVAO.prototype.bindAttrs = function () {
|
|
var exti = extInstanced()
|
|
var attributes = this.attributes
|
|
for (var i = 0; i < attributes.length; ++i) {
|
|
var attr = attributes[i]
|
|
if (attr.buffer) {
|
|
gl.enableVertexAttribArray(i)
|
|
gl.bindBuffer(GL_ARRAY_BUFFER, attr.buffer.buffer)
|
|
gl.vertexAttribPointer(i, attr.size, attr.type, attr.normalized, attr.stride, attr.offset)
|
|
if (exti && attr.divisor) {
|
|
exti.vertexAttribDivisorANGLE(i, attr.divisor)
|
|
}
|
|
} else {
|
|
gl.disableVertexAttribArray(i)
|
|
gl.vertexAttrib4f(i, attr.x, attr.y, attr.z, attr.w)
|
|
}
|
|
}
|
|
for (var j = attributes.length; j < NUM_ATTRIBUTES; ++j) {
|
|
gl.disableVertexAttribArray(j)
|
|
}
|
|
var elements = elementState.getElements(this.elements)
|
|
if (elements) {
|
|
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elements.buffer.buffer)
|
|
} else {
|
|
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, null)
|
|
}
|
|
}
|
|
|
|
REGLVAO.prototype.refresh = function () {
|
|
var ext = extVAO()
|
|
if (ext) {
|
|
ext.bindVertexArrayOES(this.vao)
|
|
this.bindAttrs()
|
|
state.currentVAO = null
|
|
ext.bindVertexArrayOES(null)
|
|
}
|
|
}
|
|
|
|
REGLVAO.prototype.destroy = function () {
|
|
if (this.vao) {
|
|
var extension = extVAO()
|
|
if (this === state.currentVAO) {
|
|
state.currentVAO = null
|
|
extension.bindVertexArrayOES(null)
|
|
}
|
|
extension.deleteVertexArrayOES(this.vao)
|
|
this.vao = null
|
|
}
|
|
if (this.ownsElements) {
|
|
this.elements.destroy()
|
|
this.elements = null
|
|
this.ownsElements = false
|
|
}
|
|
if (vaoSet[this.id]) {
|
|
delete vaoSet[this.id]
|
|
stats.vaoCount -= 1
|
|
}
|
|
}
|
|
|
|
function restoreVAO () {
|
|
var ext = extVAO()
|
|
if (ext) {
|
|
values(vaoSet).forEach(function (vao) {
|
|
vao.refresh()
|
|
})
|
|
}
|
|
}
|
|
|
|
function createVAO (_attr) {
|
|
var vao = new REGLVAO()
|
|
stats.vaoCount += 1
|
|
|
|
function updateVAO (options) {
|
|
var attributes
|
|
if (Array.isArray(options)) {
|
|
attributes = options
|
|
if (vao.elements && vao.ownsElements) {
|
|
vao.elements.destroy()
|
|
}
|
|
vao.elements = null
|
|
vao.ownsElements = false
|
|
vao.offset = 0
|
|
vao.count = 0
|
|
vao.instances = -1
|
|
vao.primitive = 4
|
|
} else {
|
|
check(typeof options === 'object', 'invalid arguments for create vao')
|
|
check('attributes' in options, 'must specify attributes for vao')
|
|
if (options.elements) {
|
|
var elements = options.elements
|
|
if (vao.ownsElements) {
|
|
if (typeof elements === 'function' && elements._reglType === 'elements') {
|
|
vao.elements.destroy()
|
|
vao.ownsElements = false
|
|
} else {
|
|
vao.elements(elements)
|
|
vao.ownsElements = false
|
|
}
|
|
} else if (elementState.getElements(options.elements)) {
|
|
vao.elements = options.elements
|
|
vao.ownsElements = false
|
|
} else {
|
|
vao.elements = elementState.create(options.elements)
|
|
vao.ownsElements = true
|
|
}
|
|
} else {
|
|
vao.elements = null
|
|
vao.ownsElements = false
|
|
}
|
|
attributes = options.attributes
|
|
|
|
// set default vao
|
|
vao.offset = 0
|
|
vao.count = -1
|
|
vao.instances = -1
|
|
vao.primitive = 4
|
|
|
|
// copy element properties
|
|
if (vao.elements) {
|
|
vao.count = vao.elements._elements.vertCount
|
|
vao.primitive = vao.elements._elements.primType
|
|
}
|
|
|
|
if ('offset' in options) {
|
|
vao.offset = options.offset | 0
|
|
}
|
|
if ('count' in options) {
|
|
vao.count = options.count | 0
|
|
}
|
|
if ('instances' in options) {
|
|
vao.instances = options.instances | 0
|
|
}
|
|
if ('primitive' in options) {
|
|
check(options.primitive in primitives, 'bad primitive type: ' + options.primitive)
|
|
vao.primitive = primitives[options.primitive]
|
|
}
|
|
|
|
check.optional(() => {
|
|
var keys = Object.keys(options)
|
|
for (var i = 0; i < keys.length; ++i) {
|
|
check(VAO_OPTIONS.indexOf(keys[i]) >= 0, 'invalid option for vao: "' + keys[i] + '" valid options are ' + VAO_OPTIONS)
|
|
}
|
|
})
|
|
check(Array.isArray(attributes), 'attributes must be an array')
|
|
}
|
|
|
|
check(attributes.length < NUM_ATTRIBUTES, 'too many attributes')
|
|
check(attributes.length > 0, 'must specify at least one attribute')
|
|
|
|
var bufUpdated = {}
|
|
var nattributes = vao.attributes
|
|
nattributes.length = attributes.length
|
|
for (var i = 0; i < attributes.length; ++i) {
|
|
var spec = attributes[i]
|
|
var rec = nattributes[i] = new AttributeRecord()
|
|
var data = spec.data || spec
|
|
if (Array.isArray(data) || isTypedArray(data) || isNDArrayLike(data)) {
|
|
var buf
|
|
if (vao.buffers[i]) {
|
|
buf = vao.buffers[i]
|
|
if (isTypedArray(data) && buf._buffer.byteLength >= data.byteLength) {
|
|
buf.subdata(data)
|
|
} else {
|
|
buf.destroy()
|
|
vao.buffers[i] = null
|
|
}
|
|
}
|
|
if (!vao.buffers[i]) {
|
|
buf = vao.buffers[i] = bufferState.create(spec, GL_ARRAY_BUFFER, false, true)
|
|
}
|
|
rec.buffer = bufferState.getBuffer(buf)
|
|
rec.size = rec.buffer.dimension | 0
|
|
rec.normalized = false
|
|
rec.type = rec.buffer.dtype
|
|
rec.offset = 0
|
|
rec.stride = 0
|
|
rec.divisor = 0
|
|
rec.state = 1
|
|
bufUpdated[i] = 1
|
|
} else if (bufferState.getBuffer(spec)) {
|
|
rec.buffer = bufferState.getBuffer(spec)
|
|
rec.size = rec.buffer.dimension | 0
|
|
rec.normalized = false
|
|
rec.type = rec.buffer.dtype
|
|
rec.offset = 0
|
|
rec.stride = 0
|
|
rec.divisor = 0
|
|
rec.state = 1
|
|
} else if (bufferState.getBuffer(spec.buffer)) {
|
|
rec.buffer = bufferState.getBuffer(spec.buffer)
|
|
rec.size = ((+spec.size) || rec.buffer.dimension) | 0
|
|
rec.normalized = !!spec.normalized || false
|
|
if ('type' in spec) {
|
|
check.parameter(spec.type, bufferTypes, 'invalid buffer type')
|
|
rec.type = bufferTypes[spec.type]
|
|
} else {
|
|
rec.type = rec.buffer.dtype
|
|
}
|
|
rec.offset = (spec.offset || 0) | 0
|
|
rec.stride = (spec.stride || 0) | 0
|
|
rec.divisor = (spec.divisor || 0) | 0
|
|
rec.state = 1
|
|
|
|
check(rec.size >= 1 && rec.size <= 4, 'size must be between 1 and 4')
|
|
check(rec.offset >= 0, 'invalid offset')
|
|
check(rec.stride >= 0 && rec.stride <= 255, 'stride must be between 0 and 255')
|
|
check(rec.divisor >= 0, 'divisor must be positive')
|
|
check(!rec.divisor || !!extensions.angle_instanced_arrays, 'ANGLE_instanced_arrays must be enabled to use divisor')
|
|
} else if ('x' in spec) {
|
|
check(i > 0, 'first attribute must not be a constant')
|
|
rec.x = +spec.x || 0
|
|
rec.y = +spec.y || 0
|
|
rec.z = +spec.z || 0
|
|
rec.w = +spec.w || 0
|
|
rec.state = 2
|
|
} else {
|
|
check(false, 'invalid attribute spec for location ' + i)
|
|
}
|
|
}
|
|
|
|
// retire unused buffers
|
|
for (var j = 0; j < vao.buffers.length; ++j) {
|
|
if (!bufUpdated[j] && vao.buffers[j]) {
|
|
vao.buffers[j].destroy()
|
|
vao.buffers[j] = null
|
|
}
|
|
}
|
|
|
|
vao.refresh()
|
|
return updateVAO
|
|
}
|
|
|
|
updateVAO.destroy = function () {
|
|
for (var j = 0; j < vao.buffers.length; ++j) {
|
|
if (vao.buffers[j]) {
|
|
vao.buffers[j].destroy()
|
|
}
|
|
}
|
|
vao.buffers.length = 0
|
|
|
|
if (vao.ownsElements) {
|
|
vao.elements.destroy()
|
|
vao.elements = null
|
|
vao.ownsElements = false
|
|
}
|
|
|
|
vao.destroy()
|
|
}
|
|
|
|
updateVAO._vao = vao
|
|
updateVAO._reglType = 'vao'
|
|
|
|
return updateVAO(_attr)
|
|
}
|
|
|
|
return state
|
|
}
|