├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── buffer.js ├── example └── example.js ├── package.json ├── screenshot.png └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules/* 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | npm-debug.log 3 | example 4 | screenshot.png 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Mikola Lysenko 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gl-buffer 2 | ========= 3 | A wrapper for WebGL buffer objects. 4 | 5 | # Example 6 | 7 | [View this demo in your browser](http://stack.gl/gl-buffer) 8 | 9 | ```javascript 10 | var shell = require("gl-now")() 11 | var glslify = require("glslify") 12 | var createBuffer = require("gl-buffer") 13 | 14 | var createShader = glslify({ 15 | vertex: "\ 16 | attribute vec2 position;\ 17 | varying vec2 uv;\ 18 | void main() {\ 19 | gl_Position = vec4(position, 0.0, 1.0);\ 20 | uv = position.xy;\ 21 | }", 22 | fragment: "\ 23 | precision highp float;\ 24 | uniform float tick;\ 25 | varying vec2 uv;\ 26 | void main() {\ 27 | gl_FragColor = vec4(0.5*(uv+1.0), 0.5*(cos(tick)+1.0), 1.0);\ 28 | }", 29 | inline: true 30 | }) 31 | 32 | var buffer, shader 33 | 34 | shell.on("gl-init", function() { 35 | var gl = shell.gl 36 | 37 | //Create buffer 38 | buffer = createBuffer(gl, [-1, 0, 0,-1, 1, 1]) 39 | 40 | //Create shader 41 | shader = createShader(gl) 42 | shader.attributes.position.location = 0 43 | }) 44 | 45 | shell.on("gl-render", function(t) { 46 | var gl = shell.gl 47 | shader.bind() 48 | buffer.bind() 49 | shader.attributes.position.pointer() 50 | shader.uniforms.tick = Date.now() / 1000.0 51 | gl.drawArrays(gl.TRIANGLES, 0, 3) 52 | }) 53 | ``` 54 | 55 | Output: 56 | 57 | 58 | 59 | # Install 60 | 61 | npm install gl-buffer 62 | 63 | # API 64 | 65 | ```javascript 66 | var createBuffer = require("gl-buffer") 67 | ``` 68 | 69 | ## Constructor 70 | The constructor for a GL buffer works as follows: 71 | 72 | ### `var buffer = createBuffer(gl[, data, type, usage])` 73 | 74 | * `gl` is a WebGL context 75 | * `data` is either an integer, an array, a typed array, an array buffer or an ndarray representing the data of the buffer. Default is `0` 76 | * `type` is an optional parameter specifying the type of the webgl buffer. Default is `gl.ARRAY_BUFFER`. 77 | * `usage` is an optional parameter representing the intended usage for the buffer (in the WebGL sense). It is not clear this does anything in current WebGL implementations. Default `gl.DYNAMIC_DRAW` 78 | 79 | ## Properties 80 | 81 | ### `buffer.gl` 82 | A reference to the buffer's WebGL context 83 | 84 | ### `buffer.handle` 85 | A handle to the underlying WebGLBuffer object 86 | 87 | ### `buffer.type` 88 | The type of the buffer (either `gl.ARRAY_BUFFER` or `gl.ELEMENT_ARRAY_BUFFER`) 89 | 90 | ### `buffer.length` 91 | The size of the buffer in bytes 92 | 93 | ### `buffer.usage` 94 | The internal WebGL usage for the buffer. 95 | 96 | ## Methods 97 | 98 | ### `buffer.bind()` 99 | Binds the buffer to the appropriate target. Equivalent to `gl.bindBuffer( ... )` 100 | 101 | ### `buffer.dispose()` 102 | Deletes the buffer releasing all associated resources. Equivalent to `gl.deleteBuffer(...)` 103 | 104 | ### `buffer.update(data[, offset])` 105 | Updates the data in the buffer. There are two basic modes to this function. In the first, it calls `gl.bufferSubData` to update a portion of the buffer in place, and in the second it calls `gl.bufferData` to completely resize the buffer. 106 | 107 | * `data` the new data to add to the buffer. This follows the same semantics as in the constructor. 108 | * `offset` the offset **in bytes** to copy data into the buffer from *or* if unspecified then the buffer is resized by calling `gl.bufferData` instead of `gl.bufferSubData`. Default `0`. 109 | 110 | ## Credits 111 | (c) 2013-2014 Mikola Lysenko. MIT License 112 | -------------------------------------------------------------------------------- /buffer.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var pool = require("typedarray-pool") 4 | var ops = require("ndarray-ops") 5 | var ndarray = require("ndarray") 6 | 7 | var SUPPORTED_TYPES = [ 8 | "uint8", 9 | "uint8_clamped", 10 | "uint16", 11 | "uint32", 12 | "int8", 13 | "int16", 14 | "int32", 15 | "float32" ] 16 | 17 | function GLBuffer(gl, type, handle, length, usage) { 18 | this.gl = gl 19 | this.type = type 20 | this.handle = handle 21 | this.length = length 22 | this.usage = usage 23 | } 24 | 25 | var proto = GLBuffer.prototype 26 | 27 | proto.bind = function() { 28 | this.gl.bindBuffer(this.type, this.handle) 29 | } 30 | 31 | proto.unbind = function() { 32 | this.gl.bindBuffer(this.type, null) 33 | } 34 | 35 | proto.dispose = function() { 36 | this.gl.deleteBuffer(this.handle) 37 | } 38 | 39 | function updateTypeArray(gl, type, len, usage, data, offset) { 40 | var dataLen = data.length * data.BYTES_PER_ELEMENT 41 | if(offset < 0) { 42 | gl.bufferData(type, data, usage) 43 | return dataLen 44 | } 45 | if(dataLen + offset > len) { 46 | throw new Error("gl-buffer: If resizing buffer, must not specify offset") 47 | } 48 | gl.bufferSubData(type, offset, data) 49 | return len 50 | } 51 | 52 | function makeScratchTypeArray(array, dtype) { 53 | var res = pool.malloc(array.length, dtype) 54 | var n = array.length 55 | for(var i=0; i=0; --i) { 64 | if(stride[i] !== n) { 65 | return false 66 | } 67 | n *= shape[i] 68 | } 69 | return true 70 | } 71 | 72 | proto.update = function(array, offset) { 73 | if(typeof offset !== "number") { 74 | offset = -1 75 | } 76 | this.bind() 77 | if(typeof array === "object" && typeof array.shape !== "undefined") { //ndarray 78 | var dtype = array.dtype 79 | if(SUPPORTED_TYPES.indexOf(dtype) < 0) { 80 | dtype = "float32" 81 | } 82 | if(this.type === this.gl.ELEMENT_ARRAY_BUFFER) { 83 | var ext = gl.getExtension('OES_element_index_uint') 84 | if(ext && dtype !== "uint16") { 85 | dtype = "uint32" 86 | } else { 87 | dtype = "uint16" 88 | } 89 | } 90 | if(dtype === array.dtype && isPacked(array.shape, array.stride)) { 91 | if(array.offset === 0 && array.data.length === array.shape[0]) { 92 | this.length = updateTypeArray(this.gl, this.type, this.length, this.usage, array.data, offset) 93 | } else { 94 | this.length = updateTypeArray(this.gl, this.type, this.length, this.usage, array.data.subarray(array.offset, array.shape[0]), offset) 95 | } 96 | } else { 97 | var tmp = pool.malloc(array.size, dtype) 98 | var ndt = ndarray(tmp, array.shape) 99 | ops.assign(ndt, array) 100 | if(offset < 0) { 101 | this.length = updateTypeArray(this.gl, this.type, this.length, this.usage, tmp, offset) 102 | } else { 103 | this.length = updateTypeArray(this.gl, this.type, this.length, this.usage, tmp.subarray(0, array.size), offset) 104 | } 105 | pool.free(tmp) 106 | } 107 | } else if(Array.isArray(array)) { //Vanilla array 108 | var t 109 | if(this.type === this.gl.ELEMENT_ARRAY_BUFFER) { 110 | t = makeScratchTypeArray(array, "uint16") 111 | } else { 112 | t = makeScratchTypeArray(array, "float32") 113 | } 114 | if(offset < 0) { 115 | this.length = updateTypeArray(this.gl, this.type, this.length, this.usage, t, offset) 116 | } else { 117 | this.length = updateTypeArray(this.gl, this.type, this.length, this.usage, t.subarray(0, array.length), offset) 118 | } 119 | pool.free(t) 120 | } else if(typeof array === "object" && typeof array.length === "number") { //Typed array 121 | this.length = updateTypeArray(this.gl, this.type, this.length, this.usage, array, offset) 122 | } else if(typeof array === "number" || array === undefined) { //Number/default 123 | if(offset >= 0) { 124 | throw new Error("gl-buffer: Cannot specify offset when resizing buffer") 125 | } 126 | array = array | 0 127 | if(array <= 0) { 128 | array = 1 129 | } 130 | this.gl.bufferData(this.type, array|0, this.usage) 131 | this.length = array 132 | } else { //Error, case should not happen 133 | throw new Error("gl-buffer: Invalid data type") 134 | } 135 | } 136 | 137 | function createBuffer(gl, data, type, usage) { 138 | type = type || gl.ARRAY_BUFFER 139 | usage = usage || gl.DYNAMIC_DRAW 140 | if(type !== gl.ARRAY_BUFFER && type !== gl.ELEMENT_ARRAY_BUFFER) { 141 | throw new Error("gl-buffer: Invalid type for webgl buffer, must be either gl.ARRAY_BUFFER or gl.ELEMENT_ARRAY_BUFFER") 142 | } 143 | if(usage !== gl.DYNAMIC_DRAW && usage !== gl.STATIC_DRAW && usage !== gl.STREAM_DRAW) { 144 | throw new Error("gl-buffer: Invalid usage for buffer, must be either gl.DYNAMIC_DRAW, gl.STATIC_DRAW or gl.STREAM_DRAW") 145 | } 146 | var handle = gl.createBuffer() 147 | var result = new GLBuffer(gl, type, handle, 0, usage) 148 | result.update(data) 149 | return result 150 | } 151 | 152 | module.exports = createBuffer 153 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | var shell = require("gl-now")() 2 | var glslify = require("glslify") 3 | var createBuffer = require("../buffer.js") 4 | 5 | var createShader = glslify({ 6 | vertex: "\ 7 | attribute vec2 position;\ 8 | varying vec2 uv;\ 9 | void main() {\ 10 | gl_Position = vec4(position, 0.0, 1.0);\ 11 | uv = position.xy;\ 12 | }", 13 | fragment: "\ 14 | precision highp float;\ 15 | uniform float tick;\ 16 | varying vec2 uv;\ 17 | void main() {\ 18 | gl_FragColor = vec4(0.5*(uv+1.0), 0.5*(cos(tick)+1.0), 1.0);\ 19 | }", 20 | inline: true 21 | }) 22 | 23 | var buffer, shader 24 | 25 | shell.on("gl-init", function() { 26 | var gl = shell.gl 27 | 28 | //Create buffer 29 | buffer = createBuffer(gl, [-1, 0, 0,-1, 1, 1]) 30 | 31 | //Create shader 32 | shader = createShader(gl) 33 | shader.attributes.position.location = 0 34 | }) 35 | 36 | shell.on("gl-render", function(t) { 37 | var gl = shell.gl 38 | shader.bind() 39 | buffer.bind() 40 | shader.attributes.position.pointer() 41 | shader.uniforms.tick = Date.now() / 1000.0 42 | gl.drawArrays(gl.TRIANGLES, 0, 3) 43 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gl-buffer", 3 | "version": "2.1.2", 4 | "description": "WebGL buffer wrapper", 5 | "main": "buffer.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "dependencies": { 10 | "ndarray": "^1.0.15", 11 | "typedarray-pool": "^1.0.0", 12 | "ndarray-ops": "^1.1.0" 13 | }, 14 | "devDependencies": { 15 | "gl-now": "^1.3.1", 16 | "glslify": "^1.4.0", 17 | "smokestack": "^2.0.0", 18 | "tap-spec": "^1.0.1", 19 | "tape": "^3.0.1" 20 | }, 21 | "scripts": { 22 | "test": "browserify ./test/index.js | smokestack | tap-spec", 23 | "start": "beefy --open example/example.js -- --transform glslify" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git://github.com/stackgl/gl-buffer.git" 28 | }, 29 | "keywords": [ 30 | "webgl", 31 | "buffer", 32 | "vertex", 33 | "array", 34 | "element", 35 | "array" 36 | ], 37 | "author": "Mikola Lysenko", 38 | "license": "MIT", 39 | "readmeFilename": "README.md", 40 | "gitHead": "fa00ce204834850a2a9118824aca293399bc3e4d", 41 | "bugs": { 42 | "url": "https://github.com/stackgl/gl-buffer/issues" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackgl/gl-buffer/c8c86a6ef32f55081e087152d83ab375d3e8bcbf/screenshot.png -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var createBuffer = require('../') 2 | var test = require('tape') 3 | 4 | var data = { 5 | basic: [0, 1, 2, 3, 4, 5] 6 | } 7 | 8 | var gl = getContext() 9 | function getContext() { 10 | var canvas = document.body.appendChild(document.createElement('canvas')) 11 | var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') 12 | return gl 13 | } 14 | 15 | 16 | test('buffer.handle', function(t) { 17 | var buffer = createBuffer(gl, data.basic) 18 | t.ok(buffer.handle instanceof WebGLBuffer, 'buffer.handle is a WebGLBuffer instance') 19 | t.end() 20 | }) 21 | 22 | test('buffer.gl', function(t) { 23 | var buffer = createBuffer(gl, data.basic) 24 | t.equal(gl, buffer.gl, 'buffer.gl === gl') 25 | t.end() 26 | }) 27 | 28 | test('buffer(): accepts Float32Arrays', function(t) { 29 | t.plan(1) 30 | t.doesNotThrow(function() { 31 | createBuffer(gl, new Float32Array(data.basic)) 32 | }, 'does not throw on creation') 33 | }) 34 | 35 | test('buffer(): checks for a valid type', function(t) { 36 | t.plan(4) 37 | 38 | t.throws(function() { 39 | createBuffer(gl, data.basic, -10) 40 | }, 'throws on creation when type is invalid') 41 | 42 | t.doesNotThrow(function() { 43 | createBuffer(gl, data.basic, gl.ARRAY_BUFFER) 44 | }, 'does now throw for gl.ARRAY_BUFFER') 45 | 46 | t.doesNotThrow(function() { 47 | createBuffer(gl, data.basic, gl.ELEMENT_ARRAY_BUFFER) 48 | }, 'does now throw for gl.ELEMENT_ARRAY_BUFFER') 49 | 50 | t.doesNotThrow(function() { 51 | createBuffer(gl, data.basic, null) 52 | }, 'passing null is acceptable') 53 | }) 54 | 55 | test('buffer(): checks for a valid usage', function(t) { 56 | t.plan(5) 57 | 58 | t.throws(function() { 59 | createBuffer(gl, data.basic, null, -10) 60 | }, 'throws on creation when usage is invalid') 61 | 62 | t.doesNotThrow(function() { 63 | createBuffer(gl, data.basic, null, gl.DYNAMIC_DRAW) 64 | }, 'does now throw for gl.DYNAMIC_DRAW') 65 | 66 | t.doesNotThrow(function() { 67 | createBuffer(gl, data.basic, null, gl.STATIC_DRAW) 68 | }, 'does now throw for gl.STATIC_DRAW') 69 | 70 | t.doesNotThrow(function() { 71 | createBuffer(gl, data.basic, null, gl.STREAM_DRAW) 72 | }, 'does now throw for gl.STREAM_DRAW') 73 | 74 | t.doesNotThrow(function() { 75 | createBuffer(gl, data.basic, null, null) 76 | }, 'null is acceptable') 77 | }) 78 | 79 | test('buffer(): handles being created without data', function(t) { 80 | var buffer = createBuffer(gl) 81 | t.ok(buffer.handle) 82 | t.end() 83 | }) 84 | 85 | test('buffer.length', function(t) { 86 | var ui8a = new Uint8Array(data.basic) 87 | var f32a = new Float32Array(data.basic) 88 | var buffer 89 | 90 | buffer = createBuffer(gl, data.basic) 91 | t.equal(buffer.length, data.basic.length * 4, 'vanilla arrays: correct byte length') 92 | 93 | buffer = createBuffer(gl, f32a) 94 | t.equal(buffer.length, f32a.byteLength, 'Float32Arrays: correct byte length') 95 | 96 | buffer = createBuffer(gl, ui8a) 97 | t.equal(buffer.length, ui8a.byteLength, 'Uint8Arrays: correct byte length') 98 | 99 | t.end() 100 | }) 101 | 102 | test('buffer.bind()', function(t) { 103 | var buffer = createBuffer(gl, data.basic) 104 | 105 | t.ok(gl.getParameter(gl.ARRAY_BUFFER_BINDING) === buffer.handle 106 | , 'initially will be bound on creation') 107 | 108 | buffer.unbind() 109 | t.ok(gl.getParameter(gl.ARRAY_BUFFER_BINDING) === null 110 | , '.unbind() will bind to null') 111 | 112 | buffer.bind() 113 | t.ok(gl.getParameter(gl.ARRAY_BUFFER_BINDING) === buffer.handle 114 | , 'bind() will bind to gl.ARRAY_BUFFER by default') 115 | 116 | // TODO: test ELEMENT_ARRAY_BUFFER_BINDING, which I haven't been 117 | // able to get working properly yet. 118 | t.end() 119 | }) 120 | 121 | test('shutdown', function(t) { 122 | t.end() 123 | setTimeout(function() { 124 | window.close() 125 | }) 126 | }) 127 | --------------------------------------------------------------------------------