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 GL_FLOAT = 5126 var GL_ARRAY_BUFFER = 34962 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) { 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) 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) } } } state.currentVAO = vao } function destroyVAOEXT () { values(vaoSet).forEach(function (vao) { vao.destroy() }) } function REGLVAO () { this.id = ++vaoCount this.attributes = [] 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) } } REGLVAO.prototype.refresh = function () { var ext = extVAO() if (ext) { ext.bindVertexArrayOES(this.vao) this.bindAttrs() state.currentVAO = this } } 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 (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 (attributes) { check(Array.isArray(attributes), 'arguments to vertex array constructor 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 vao.destroy() } updateVAO._vao = vao updateVAO._reglType = 'vao' return updateVAO(_attr) } return state }