417 lines
12 KiB
JavaScript
417 lines
12 KiB
JavaScript
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
|
|
}
|
|
}
|