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 }