├── .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 |
--------------------------------------------------------------------------------