var check = require('./util/check') var isTypedArray = require('./util/is-typed-array') var isNDArrayLike = require('./util/is-ndarray') var values = require('./util/values') var pool = require('./util/pool') var flattenUtil = require('./util/flatten') var arrayFlatten = flattenUtil.flatten var arrayShape = flattenUtil.shape var arrayTypes = require('./constants/arraytypes.json') var bufferTypes = require('./constants/dtypes.json') var usageTypes = require('./constants/usage.json') var GL_STATIC_DRAW = 0x88E4 var GL_STREAM_DRAW = 0x88E0 var GL_UNSIGNED_BYTE = 5121 var GL_FLOAT = 5126 var DTYPES_SIZES = [] DTYPES_SIZES[5120] = 1 // int8 DTYPES_SIZES[5122] = 2 // int16 DTYPES_SIZES[5124] = 4 // int32 DTYPES_SIZES[5121] = 1 // uint8 DTYPES_SIZES[5123] = 2 // uint16 DTYPES_SIZES[5125] = 4 // uint32 DTYPES_SIZES[5126] = 4 // float32 function typedArrayCode (data) { return arrayTypes[Object.prototype.toString.call(data)] | 0 } function copyArray (out, inp) { for (var i = 0; i < inp.length; ++i) { out[i] = inp[i] } } function transpose ( result, data, shapeX, shapeY, strideX, strideY, offset) { var ptr = 0 for (var i = 0; i < shapeX; ++i) { for (var j = 0; j < shapeY; ++j) { result[ptr++] = data[strideX * i + strideY * j + offset] } } } module.exports = function wrapBufferState (gl, stats, config, destroyBuffer) { var bufferCount = 0 var bufferSet = {} function REGLBuffer (type) { this.id = bufferCount++ this.buffer = gl.createBuffer() this.type = type this.usage = GL_STATIC_DRAW this.byteLength = 0 this.dimension = 1 this.dtype = GL_UNSIGNED_BYTE this.persistentData = null if (config.profile) { this.stats = { size: 0 } } } REGLBuffer.prototype.bind = function () { gl.bindBuffer(this.type, this.buffer) } REGLBuffer.prototype.destroy = function () { destroy(this) } var streamPool = [] function createStream (type, data) { var buffer = streamPool.pop() if (!buffer) { buffer = new REGLBuffer(type) } buffer.bind() initBufferFromData(buffer, data, GL_STREAM_DRAW, 0, 1, false) return buffer } function destroyStream (stream) { streamPool.push(stream) } function initBufferFromTypedArray (buffer, data, usage) { buffer.byteLength = data.byteLength gl.bufferData(buffer.type, data, usage) } function initBufferFromData (buffer, data, usage, dtype, dimension, persist) { var shape buffer.usage = usage if (Array.isArray(data)) { buffer.dtype = dtype || GL_FLOAT if (data.length > 0) { var flatData if (Array.isArray(data[0])) { shape = arrayShape(data) var dim = 1 for (var i = 1; i < shape.length; ++i) { dim *= shape[i] } buffer.dimension = dim flatData = arrayFlatten(data, shape, buffer.dtype) initBufferFromTypedArray(buffer, flatData, usage) if (persist) { buffer.persistentData = flatData } else { pool.freeType(flatData) } } else if (typeof data[0] === 'number') { buffer.dimension = dimension var typedData = pool.allocType(buffer.dtype, data.length) copyArray(typedData, data) initBufferFromTypedArray(buffer, typedData, usage) if (persist) { buffer.persistentData = typedData } else { pool.freeType(typedData) } } else if (isTypedArray(data[0])) { buffer.dimension = data[0].length buffer.dtype = dtype || typedArrayCode(data[0]) || GL_FLOAT flatData = arrayFlatten( data, [data.length, data[0].length], buffer.dtype) initBufferFromTypedArray(buffer, flatData, usage) if (persist) { buffer.persistentData = flatData } else { pool.freeType(flatData) } } else { check.raise('invalid buffer data') } } } else if (isTypedArray(data)) { buffer.dtype = dtype || typedArrayCode(data) buffer.dimension = dimension initBufferFromTypedArray(buffer, data, usage) if (persist) { buffer.persistentData = new Uint8Array(new Uint8Array(data.buffer)) } } else if (isNDArrayLike(data)) { shape = data.shape var stride = data.stride var offset = data.offset var shapeX = 0 var shapeY = 0 var strideX = 0 var strideY = 0 if (shape.length === 1) { shapeX = shape[0] shapeY = 1 strideX = stride[0] strideY = 0 } else if (shape.length === 2) { shapeX = shape[0] shapeY = shape[1] strideX = stride[0] strideY = stride[1] } else { check.raise('invalid shape') } buffer.dtype = dtype || typedArrayCode(data.data) || GL_FLOAT buffer.dimension = shapeY var transposeData = pool.allocType(buffer.dtype, shapeX * shapeY) transpose(transposeData, data.data, shapeX, shapeY, strideX, strideY, offset) initBufferFromTypedArray(buffer, transposeData, usage) if (persist) { buffer.persistentData = transposeData } else { pool.freeType(transposeData) } } else if (data instanceof ArrayBuffer) { buffer.dtype = GL_UNSIGNED_BYTE buffer.dimension = dimension initBufferFromTypedArray(buffer, data, usage) if (persist) { buffer.persistentData = new Uint8Array(new Uint8Array(data)) } } else { check.raise('invalid buffer data') } } function destroy (buffer) { stats.bufferCount-- // remove attribute link destroyBuffer(buffer) var handle = buffer.buffer check(handle, 'buffer must not be deleted already') gl.deleteBuffer(handle) buffer.buffer = null delete bufferSet[buffer.id] } function createBuffer (options, type, deferInit, persistent) { stats.bufferCount++ var buffer = new REGLBuffer(type) bufferSet[buffer.id] = buffer function reglBuffer (options) { var usage = GL_STATIC_DRAW var data = null var byteLength = 0 var dtype = 0 var dimension = 1 if (Array.isArray(options) || isTypedArray(options) || isNDArrayLike(options) || options instanceof ArrayBuffer) { data = options } else if (typeof options === 'number') { byteLength = options | 0 } else if (options) { check.type( options, 'object', 'buffer arguments must be an object, a number or an array') if ('data' in options) { check( data === null || Array.isArray(data) || isTypedArray(data) || isNDArrayLike(data), 'invalid data for buffer') data = options.data } if ('usage' in options) { check.parameter(options.usage, usageTypes, 'invalid buffer usage') usage = usageTypes[options.usage] } if ('type' in options) { check.parameter(options.type, bufferTypes, 'invalid buffer type') dtype = bufferTypes[options.type] } if ('dimension' in options) { check.type(options.dimension, 'number', 'invalid dimension') dimension = options.dimension | 0 } if ('length' in options) { check.nni(byteLength, 'buffer length must be a nonnegative integer') byteLength = options.length | 0 } } buffer.bind() if (!data) { // #475 if (byteLength) gl.bufferData(buffer.type, byteLength, usage) buffer.dtype = dtype || GL_UNSIGNED_BYTE buffer.usage = usage buffer.dimension = dimension buffer.byteLength = byteLength } else { initBufferFromData(buffer, data, usage, dtype, dimension, persistent) } if (config.profile) { buffer.stats.size = buffer.byteLength * DTYPES_SIZES[buffer.dtype] } return reglBuffer } function setSubData (data, offset) { check(offset + data.byteLength <= buffer.byteLength, 'invalid buffer subdata call, buffer is too small. ' + ' Can\'t write data of size ' + data.byteLength + ' starting from offset ' + offset + ' to a buffer of size ' + buffer.byteLength) gl.bufferSubData(buffer.type, offset, data) } function subdata (data, offset_) { var offset = (offset_ || 0) | 0 var shape buffer.bind() if (isTypedArray(data) || data instanceof ArrayBuffer) { setSubData(data, offset) } else if (Array.isArray(data)) { if (data.length > 0) { if (typeof data[0] === 'number') { var converted = pool.allocType(buffer.dtype, data.length) copyArray(converted, data) setSubData(converted, offset) pool.freeType(converted) } else if (Array.isArray(data[0]) || isTypedArray(data[0])) { shape = arrayShape(data) var flatData = arrayFlatten(data, shape, buffer.dtype) setSubData(flatData, offset) pool.freeType(flatData) } else { check.raise('invalid buffer data') } } } else if (isNDArrayLike(data)) { shape = data.shape var stride = data.stride var shapeX = 0 var shapeY = 0 var strideX = 0 var strideY = 0 if (shape.length === 1) { shapeX = shape[0] shapeY = 1 strideX = stride[0] strideY = 0 } else if (shape.length === 2) { shapeX = shape[0] shapeY = shape[1] strideX = stride[0] strideY = stride[1] } else { check.raise('invalid shape') } var dtype = Array.isArray(data.data) ? buffer.dtype : typedArrayCode(data.data) var transposeData = pool.allocType(dtype, shapeX * shapeY) transpose(transposeData, data.data, shapeX, shapeY, strideX, strideY, data.offset) setSubData(transposeData, offset) pool.freeType(transposeData) } else { check.raise('invalid data for buffer subdata') } return reglBuffer } if (!deferInit) { reglBuffer(options) } reglBuffer._reglType = 'buffer' reglBuffer._buffer = buffer reglBuffer.subdata = subdata if (config.profile) { reglBuffer.stats = buffer.stats } reglBuffer.destroy = function () { destroy(buffer) } return reglBuffer } function restoreBuffers () { values(bufferSet).forEach(function (buffer) { buffer.buffer = gl.createBuffer() gl.bindBuffer(buffer.type, buffer.buffer) gl.bufferData( buffer.type, buffer.persistentData || buffer.byteLength, buffer.usage) }) } if (config.profile) { stats.getTotalBufferSize = function () { var total = 0 // TODO: Right now, the streams are not part of the total count. Object.keys(bufferSet).forEach(function (key) { total += bufferSet[key].stats.size }) return total } } return { create: createBuffer, createStream: createStream, destroyStream: destroyStream, clear: function () { values(bufferSet).forEach(destroy) streamPool.forEach(destroy) }, getBuffer: function (wrapper) { if (wrapper && wrapper._buffer instanceof REGLBuffer) { return wrapper._buffer } return null }, restore: restoreBuffers, _initBuffer: initBufferFromData } }