├── .gitignore ├── .npmignore ├── test ├── assets │ ├── mireRGB.png │ └── mireRGBA.png ├── glsl │ ├── simple.frag │ ├── simple.vert │ ├── test_umat4.frag │ ├── test_uvec3.frag │ ├── filltex.frag │ ├── test_arraybuffer.frag │ ├── bad.vert │ ├── test_uvec3_array.frag │ ├── test_ufloat.frag │ ├── test_uvec3.vert │ ├── test_ufloat.vert │ ├── test_uvec3_array.vert │ ├── complete.frag │ ├── bad_link_complete.frag │ ├── test_umat4.vert │ ├── filltex.vert │ ├── ublock_compat.vert │ ├── test_arraybuffer.vert │ ├── complete.vert │ └── ublock_compat.frag ├── glsl300 │ ├── simple.frag │ ├── simple.vert │ ├── ublock.frag │ └── ublock.vert ├── renderbuffer.js ├── utils │ ├── FSGeom.js │ └── TestContext.js ├── indexbuffer.js ├── sampler.js ├── arraybuffer.js ├── texture.js ├── program.js └── fbo.js ├── types.js ├── types.d.ts ├── .travis.yml ├── utils.d.ts ├── texture-cube.d.ts ├── src ├── types.ts ├── utils.ts ├── texture-cube.ts ├── texture-2d.ts ├── sampler.ts ├── indexbuffer.ts ├── renderbuffer.ts ├── basebuffer.ts ├── texture-base.ts ├── arraybuffer.ts ├── fbo.ts └── program.ts ├── sampler.d.ts ├── texture.d.ts ├── texture-2d.d.ts ├── tsconfig.json ├── renderbuffer.d.ts ├── utils.js ├── program.d.ts ├── basebuffer.js ├── indexbuffer.d.ts ├── basebuffer.d.ts ├── texture.js ├── texture-2d.js ├── arraybuffer.d.ts ├── texture-base.d.ts ├── sampler.js ├── .github └── workflows │ └── main.yml ├── texture-cube.js ├── fbo.d.ts ├── indexbuffer.js ├── README.md ├── renderbuffer.js ├── package.json ├── texture-base.js ├── arraybuffer.js ├── karma.conf.js ├── fbo.js ├── program.js └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | docs 3 | src 4 | tsconfig.json 5 | .travis.yml -------------------------------------------------------------------------------- /test/assets/mireRGB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plepers/nanogl/HEAD/test/assets/mireRGB.png -------------------------------------------------------------------------------- /test/assets/mireRGBA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plepers/nanogl/HEAD/test/assets/mireRGBA.png -------------------------------------------------------------------------------- /types.js: -------------------------------------------------------------------------------- 1 | export function isWebgl2(context) { 2 | return context.fenceSync !== undefined; 3 | } 4 | -------------------------------------------------------------------------------- /test/glsl/simple.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | void main(void){ 5 | gl_FragColor = vec4(1.0); 6 | } -------------------------------------------------------------------------------- /test/glsl300/simple.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | out vec4 color; 5 | 6 | void main(void){ 7 | color = vec4(1.0); 8 | } -------------------------------------------------------------------------------- /test/glsl/simple.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec3 aPosition; 4 | 5 | void main( void ){ 6 | gl_Position = vec4( aPosition, 1.0 ); 7 | } -------------------------------------------------------------------------------- /test/glsl300/simple.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | in vec3 aPosition; 5 | 6 | void main( void ){ 7 | gl_Position = vec4( aPosition, 1.0 ); 8 | } -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | export type GLContext = WebGLRenderingContext | WebGL2RenderingContext; 2 | export declare function isWebgl2(context: GLContext): context is WebGL2RenderingContext; 3 | -------------------------------------------------------------------------------- /test/glsl/test_umat4.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | 5 | varying vec2 vTexCoord; 6 | 7 | 8 | void main(void){; 9 | gl_FragColor = vec4( .5, .25, 1.0, 1.0); 10 | } -------------------------------------------------------------------------------- /test/glsl/test_uvec3.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | 5 | varying vec2 vTexCoord; 6 | 7 | uniform vec3 uVec3; 8 | 9 | void main(void){; 10 | gl_FragColor = vec4( uVec3, 1.0); 11 | } -------------------------------------------------------------------------------- /test/glsl/filltex.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | uniform sampler2D tTex; 5 | 6 | varying vec2 vTexCoord; 7 | 8 | void main(void){ 9 | gl_FragColor = texture2D( tTex, vTexCoord ); 10 | } -------------------------------------------------------------------------------- /test/glsl/test_arraybuffer.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | varying vec4 vColor; 5 | varying vec2 vTexCoord; 6 | 7 | void main(void){ 8 | gl_FragColor = vec4(vColor.rg, vTexCoord.r, 1.0 ); 9 | } -------------------------------------------------------------------------------- /test/glsl/bad.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec3 aPosition; 4 | 5 | varying vec2 vTexcoords; 6 | 7 | void main( void ){ 8 | gl_Position = aPosition; 9 | 10 | vTexcoords = unknown_var; 11 | } -------------------------------------------------------------------------------- /test/glsl/test_uvec3_array.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | 5 | varying vec2 vTexCoord; 6 | 7 | uniform vec3 uVec3[2]; 8 | 9 | void main(void){; 10 | gl_FragColor = vec4( uVec3[0] * uVec3[1], 1.0); 11 | } -------------------------------------------------------------------------------- /test/glsl/test_ufloat.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | 5 | varying vec2 vTexCoord; 6 | 7 | uniform float uFloat; 8 | 9 | void main(void){; 10 | gl_FragColor = vec4(uFloat, uFloat*2.0, uFloat*.5, 1.0); 11 | } -------------------------------------------------------------------------------- /test/glsl/test_uvec3.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec2 aPosition; 4 | 5 | varying vec2 vTexCoord; 6 | 7 | void main( void ){ 8 | gl_Position = vec4( aPosition, 0.0, 1.0 ); 9 | vTexCoord = aPosition*.5+.5; 10 | } -------------------------------------------------------------------------------- /test/glsl/test_ufloat.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec2 aPosition; 4 | 5 | varying vec2 vTexCoord; 6 | 7 | void main( void ){ 8 | gl_Position = vec4( aPosition, 0.0, 1.0 ); 9 | vTexCoord = aPosition*.5+.5; 10 | } -------------------------------------------------------------------------------- /test/glsl/test_uvec3_array.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec2 aPosition; 4 | 5 | varying vec2 vTexCoord; 6 | 7 | void main( void ){ 8 | gl_Position = vec4( aPosition, 0.0, 1.0 ); 9 | vTexCoord = aPosition*.5+.5; 10 | } -------------------------------------------------------------------------------- /test/glsl/complete.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | varying float vFloat; 5 | varying vec4 vVec4; 6 | 7 | uniform sampler2D uTex; 8 | 9 | void main(void){ 10 | gl_FragColor = vec4( vFloat, vVec4.xyz ) + texture2D( uTex, vVec4.xy ); 11 | } -------------------------------------------------------------------------------- /test/glsl/bad_link_complete.frag: -------------------------------------------------------------------------------- 1 | 2 | precision highp float; 3 | 4 | varying float vFloat; 5 | varying vec3 vVec4; 6 | 7 | uniform sampler2D uTex; 8 | 9 | void main(void){ 10 | gl_FragColor = vec4( vFloat, vVec4.xyz ) + texture2D( uTex, vVec4.xy ); 11 | } -------------------------------------------------------------------------------- /test/glsl/test_umat4.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec2 aPosition; 4 | 5 | uniform mat4 uMat4; 6 | 7 | varying vec2 vTexCoord; 8 | 9 | void main( void ){ 10 | gl_Position = uMat4 * vec4( aPosition, 0.0, 1.0 ); 11 | vTexCoord = aPosition*.5+.5; 12 | } -------------------------------------------------------------------------------- /test/glsl/filltex.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | #ifndef UV_MULT 4 | #define UV_MULT 1.0 5 | #endif 6 | 7 | attribute vec2 aPosition; 8 | 9 | varying vec2 vTexCoord; 10 | 11 | void main( void ){ 12 | gl_Position = vec4( aPosition, 0.0, 1.0 ); 13 | vTexCoord = UV_MULT * (aPosition*.5 + .5); 14 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 9.5.0 4 | branches: 5 | only: 6 | - develop 7 | - master 8 | cache: 9 | directories: 10 | - node_modules 11 | deploy: 12 | provider: npm 13 | email: $NPM_EMAIL 14 | api_key: $NPM_TOKEN 15 | on: 16 | branch: master 17 | repo: plepers/nanogl 18 | -------------------------------------------------------------------------------- /test/glsl/ublock_compat.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | 4 | 5 | #if __VERSION__ == 300 6 | #define IN in 7 | #define OUT out 8 | #else 9 | #define IN attribute 10 | #define OUT varying 11 | #endif 12 | 13 | 14 | IN vec3 aPosition; 15 | 16 | void main( void ){ 17 | gl_Position = vec4( aPosition, 1.0 ); 18 | } -------------------------------------------------------------------------------- /test/glsl/test_arraybuffer.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec3 aPosition; 4 | attribute vec2 aTexCoord; 5 | attribute vec4 aColor; 6 | 7 | varying vec2 vTexCoord; 8 | varying vec4 vColor; 9 | 10 | void main( void ){ 11 | gl_Position = vec4( aPosition, 1.0 ); 12 | vColor = aColor; 13 | vTexCoord = aTexCoord; 14 | } -------------------------------------------------------------------------------- /utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare function isBufferSource(val: GLsizeiptr | BufferSource): val is BufferSource; 2 | export declare function getTextureFiltering(smooth: boolean, mipmap: boolean, miplinear: boolean): GLenum; 3 | type ComponentSize = 1 | 2 | 4; 4 | export declare function getComponentSize(type: GLenum): ComponentSize; 5 | export {}; 6 | -------------------------------------------------------------------------------- /test/glsl300/ublock.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | out vec4 color; 5 | 6 | layout(std140,column_major) uniform BlockA { 7 | mat4 uMat4; 8 | float uFloat; 9 | }; 10 | 11 | uniform BlockB { 12 | mat4 uMat4; 13 | float uFloat; 14 | } BlockBNS; 15 | 16 | 17 | void main(void){ 18 | color = vec4(uFloat, BlockBNS.uFloat, 1.0, 1.0 ); 19 | } -------------------------------------------------------------------------------- /texture-cube.d.ts: -------------------------------------------------------------------------------- 1 | import AbstractTexture, { TextureType } from './texture-base'; 2 | import { GLContext } from './types'; 3 | export default class TextureCube extends AbstractTexture { 4 | readonly textureType: TextureType.TEXTURE_CUBE; 5 | _target: GLenum; 6 | constructor(gl: GLContext, format?: GLenum, type?: GLenum, internal?: GLenum); 7 | fromImages(imgs: TexImageSource[]): void; 8 | } 9 | -------------------------------------------------------------------------------- /test/glsl300/ublock.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | in vec3 aPosition; 5 | 6 | layout(std140,column_major) uniform BlockA { 7 | mat4 uMat4; 8 | float uFloat; 9 | }; 10 | 11 | uniform BlockB { 12 | mat4 uMat4; 13 | float uFloat; 14 | } BlockBNS; 15 | 16 | 17 | void main( void ){ 18 | vec4 p = vec4( aPosition, 1.0 ); 19 | 20 | gl_Position = uMat4 * (BlockBNS.uMat4 * p); 21 | } -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /** A webgl rendering context. */ 2 | export type GLContext = WebGLRenderingContext | WebGL2RenderingContext; 3 | 4 | /** 5 | * Know whether the webgl context is webgl2 or not. 6 | * @param {GLContext} context The webgl context to test 7 | */ 8 | export function isWebgl2(context: GLContext): context is WebGL2RenderingContext { 9 | return (context).fenceSync !== undefined; 10 | } 11 | -------------------------------------------------------------------------------- /sampler.d.ts: -------------------------------------------------------------------------------- 1 | declare class Sampler { 2 | readonly _uid: number; 3 | gl: WebGL2RenderingContext; 4 | id: WebGLSampler; 5 | constructor(gl: WebGL2RenderingContext); 6 | bind(unit: number): void; 7 | dispose(): void; 8 | setFilter(smooth?: boolean, mipmap?: boolean, miplinear?: boolean): void; 9 | repeat(): void; 10 | clamp(): void; 11 | mirror(): void; 12 | wrap(wrap: GLenum): void; 13 | } 14 | export default Sampler; 15 | -------------------------------------------------------------------------------- /texture.d.ts: -------------------------------------------------------------------------------- 1 | import AbstractTexture, { TextureType } from './texture-base'; 2 | import { GLContext } from './types'; 3 | export default class Texture extends AbstractTexture { 4 | readonly textureType: TextureType.TEXTURE_2D; 5 | _target: GLenum; 6 | constructor(gl: GLContext, format?: GLenum, type?: GLenum, internal?: GLenum); 7 | fromImage(img: TexImageSource): void; 8 | fromData(width: number, height: number, data?: ArrayBufferView | null): void; 9 | } 10 | -------------------------------------------------------------------------------- /texture-2d.d.ts: -------------------------------------------------------------------------------- 1 | import AbstractTexture, { TextureType } from './texture-base'; 2 | import { GLContext } from './types'; 3 | export default class Texture2D extends AbstractTexture { 4 | readonly textureType: TextureType.TEXTURE_2D; 5 | _target: GLenum; 6 | constructor(gl: GLContext, format?: GLenum, type?: GLenum, internal?: GLenum); 7 | fromImage(img: TexImageSource): void; 8 | fromData(width: number, height: number, data?: ArrayBufferView | null): void; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "target": "es6", 5 | "outDir": "./", 6 | "rootDir": "./src", 7 | "removeComments": true, 8 | "declaration": true, 9 | "strict": true, 10 | "moduleResolution": "node" 11 | }, 12 | "include": ["src/**/*"], 13 | "exclude": ["node_modules"], 14 | "typedocOptions": { 15 | "entryPoints": "src", 16 | "entryPointStrategy": "expand", 17 | "out": "tempdocs", 18 | "json": "tempdocs/data.json", 19 | "githubPages": false 20 | } 21 | } -------------------------------------------------------------------------------- /test/glsl/complete.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec3 aPosition; 4 | attribute vec2 aTexCoord; 5 | 6 | uniform float uUniform1; 7 | uniform float uUniform2; 8 | uniform float uUniform3; 9 | uniform float uFloat; 10 | uniform vec4 uVec4; 11 | uniform vec2 uVec2Array[4]; 12 | uniform mat4 uMat4; 13 | 14 | varying float vFloat; 15 | varying vec4 vVec4; 16 | 17 | 18 | void main( void ){ 19 | gl_Position = uMat4 * vec4( aPosition, 1.0 ); 20 | vFloat = uFloat + uUniform1 + uUniform2 + uUniform3 + uVec2Array[0].x; 21 | vVec4 = uVec4; 22 | } -------------------------------------------------------------------------------- /renderbuffer.d.ts: -------------------------------------------------------------------------------- 1 | import { GLContext } from './types'; 2 | declare class RenderBuffer { 3 | readonly gl: GLContext; 4 | readonly id: WebGLRenderbuffer; 5 | readonly samples: number; 6 | readonly format: GLenum; 7 | width: number; 8 | height: number; 9 | readonly _uid: number; 10 | private _valid; 11 | constructor(gl: GLContext, format: GLenum, samples?: number); 12 | resize(w: number, h: number): void; 13 | allocate(): void; 14 | bind(): void; 15 | dispose(): void; 16 | _storage(): void; 17 | } 18 | export default RenderBuffer; 19 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | export function isBufferSource(val) { 2 | return val.byteLength !== undefined; 3 | } 4 | export function getTextureFiltering(smooth, mipmap, miplinear) { 5 | return 0x2600 | +smooth | (+mipmap << 8) | (+(mipmap && miplinear) << 1); 6 | } 7 | export function getComponentSize(type) { 8 | switch (type) { 9 | case 0x1400: 10 | case 0x1401: 11 | return 1; 12 | case 0x1402: 13 | case 0x1403: 14 | return 2; 15 | case 0x1404: 16 | case 0x1405: 17 | case 0x1406: 18 | return 4; 19 | default: 20 | throw new Error(`unknown type ${type}`); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /program.d.ts: -------------------------------------------------------------------------------- 1 | import { GLContext } from './types'; 2 | declare class Program { 3 | static debug: boolean; 4 | readonly gl: GLContext; 5 | readonly program: WebGLProgram; 6 | readonly vShader: WebGLShader; 7 | readonly fShader: WebGLShader; 8 | protected dyns: string[]; 9 | ready: boolean; 10 | _uid: number; 11 | _cuid: number; 12 | [k: string]: any; 13 | constructor(gl: GLContext, vert?: string, frag?: string, defs?: string); 14 | use(): void; 15 | bind(): void; 16 | compile(vert: string, frag: string, prefix?: string): boolean; 17 | dispose(): void; 18 | _grabParameters(): void; 19 | } 20 | export default Program; 21 | -------------------------------------------------------------------------------- /test/glsl/ublock_compat.frag: -------------------------------------------------------------------------------- 1 | 2 | 3 | precision highp float; 4 | 5 | 6 | #if __VERSION__ == 300 7 | 8 | #define IN in 9 | #define texture2D(a,b) texture( a, b ) 10 | out vec4 FragColor; 11 | #else 12 | #define IN varying 13 | #define FragColor gl_FragColor 14 | #endif 15 | 16 | 17 | #if __VERSION__ == 300 18 | 19 | // 300 20 | layout(std140,column_major) uniform TonemapBlock { 21 | float exposure; 22 | float gamma; 23 | } Tonemap; 24 | 25 | 26 | #else 27 | 28 | // 100 29 | uniform struct TonemapBlock { 30 | float exposure; 31 | float gamma; 32 | } Tonemap; 33 | 34 | #endif 35 | 36 | void main(void){; 37 | FragColor = vec4( Tonemap.exposure + Tonemap.gamma ); 38 | } -------------------------------------------------------------------------------- /basebuffer.js: -------------------------------------------------------------------------------- 1 | class BaseBuffer { 2 | drawPoints(count, offset) { 3 | this.draw(0, count, offset); 4 | } 5 | drawLines(count, offset) { 6 | this.draw(1, count, offset); 7 | } 8 | drawLineLoop(count, offset) { 9 | this.draw(2, count, offset); 10 | } 11 | drawLineStrip(count, offset) { 12 | this.draw(3, count, offset); 13 | } 14 | drawTriangles(count, offset) { 15 | this.draw(4, count, offset); 16 | } 17 | drawTriangleStrip(count, offset) { 18 | this.draw(5, count, offset); 19 | } 20 | drawTriangleFan(count, offset) { 21 | this.draw(6, count, offset); 22 | } 23 | } 24 | export default BaseBuffer; 25 | -------------------------------------------------------------------------------- /indexbuffer.d.ts: -------------------------------------------------------------------------------- 1 | import { GLContext } from './types'; 2 | import BaseBuffer from './basebuffer'; 3 | declare class IndexBuffer extends BaseBuffer { 4 | readonly gl: GLContext; 5 | readonly buffer: WebGLBuffer; 6 | usage: GLenum; 7 | type: GLenum; 8 | typeSize: number; 9 | byteLength: number; 10 | constructor(gl: GLContext, type?: GLenum, data?: GLsizeiptr | BufferSource, usage?: GLenum, glbuffer?: WebGLBuffer); 11 | bind(): void; 12 | setType(type: GLenum): void; 13 | data(array: GLsizeiptr | BufferSource): void; 14 | subData(array: BufferSource, offset: number): void; 15 | dispose(): void; 16 | draw(mode: GLenum, count?: number, offset?: number): void; 17 | } 18 | export default IndexBuffer; 19 | -------------------------------------------------------------------------------- /basebuffer.d.ts: -------------------------------------------------------------------------------- 1 | declare abstract class BaseBuffer { 2 | abstract bind(): void; 3 | abstract dispose(): void; 4 | abstract data(array: BufferSource | GLsizeiptr): void; 5 | abstract subData(array: BufferSource, offset: number): void; 6 | abstract draw(mode: GLenum, count?: number, offset?: number): void; 7 | drawPoints(count?: number, offset?: number): void; 8 | drawLines(count?: number, offset?: number): void; 9 | drawLineLoop(count?: number, offset?: number): void; 10 | drawLineStrip(count?: number, offset?: number): void; 11 | drawTriangles(count?: number, offset?: number): void; 12 | drawTriangleStrip(count?: number, offset?: number): void; 13 | drawTriangleFan(count?: number, offset?: number): void; 14 | } 15 | export default BaseBuffer; 16 | -------------------------------------------------------------------------------- /texture.js: -------------------------------------------------------------------------------- 1 | import AbstractTexture from './texture-base'; 2 | const GL_TEXTURE_2D = 0x0de1; 3 | export default class Texture extends AbstractTexture { 4 | constructor(gl, format, type, internal) { 5 | super(gl, format, type, internal); 6 | this.textureType = 3553; 7 | this._target = GL_TEXTURE_2D; 8 | gl.bindTexture(GL_TEXTURE_2D, this.id); 9 | this.setFilter(true); 10 | } 11 | fromImage(img) { 12 | const gl = this.gl; 13 | this.width = img.width; 14 | this.height = img.height; 15 | gl.bindTexture(GL_TEXTURE_2D, this.id); 16 | gl.texImage2D(GL_TEXTURE_2D, 0, this.internal, this.format, this.type, img); 17 | } 18 | fromData(width, height, data = null) { 19 | const gl = this.gl; 20 | this.width = width; 21 | this.height = height; 22 | data = data || null; 23 | gl.bindTexture(GL_TEXTURE_2D, this.id); 24 | gl.texImage2D(GL_TEXTURE_2D, 0, this.internal, width, height, 0, this.format, this.type, data); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /texture-2d.js: -------------------------------------------------------------------------------- 1 | import AbstractTexture, { TextureType } from './texture-base'; 2 | const GL_TEXTURE_2D = 0x0de1; 3 | export default class Texture2D extends AbstractTexture { 4 | constructor(gl, format, type, internal) { 5 | super(gl, format, type, internal); 6 | this.textureType = TextureType.TEXTURE_2D; 7 | this._target = GL_TEXTURE_2D; 8 | gl.bindTexture(GL_TEXTURE_2D, this.id); 9 | this.setFilter(true); 10 | } 11 | fromImage(img) { 12 | const gl = this.gl; 13 | this.width = img.width; 14 | this.height = img.height; 15 | gl.bindTexture(GL_TEXTURE_2D, this.id); 16 | gl.texImage2D(GL_TEXTURE_2D, 0, this.internal, this.format, this.type, img); 17 | } 18 | fromData(width, height, data = null) { 19 | const gl = this.gl; 20 | this.width = width; 21 | this.height = height; 22 | data = data || null; 23 | gl.bindTexture(GL_TEXTURE_2D, this.id); 24 | gl.texImage2D(GL_TEXTURE_2D, 0, this.internal, width, height, 0, this.format, this.type, data); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /arraybuffer.d.ts: -------------------------------------------------------------------------------- 1 | import Program from './program'; 2 | import BaseBuffer from './basebuffer'; 3 | import { GLContext } from './types'; 4 | interface AttributeDef { 5 | name: string; 6 | type: GLenum; 7 | size: number; 8 | offset: number; 9 | normalize: boolean; 10 | stride: number; 11 | } 12 | declare class ArrayBuffer extends BaseBuffer { 13 | readonly gl: GLContext; 14 | readonly buffer: WebGLBuffer; 15 | usage: GLenum; 16 | stride: number; 17 | byteLength: number; 18 | length: number; 19 | attribs: AttributeDef[]; 20 | constructor(gl: GLContext, data?: GLsizeiptr | BufferSource, usage?: GLenum, glbuffer?: WebGLBuffer); 21 | bind(): void; 22 | attrib(name: string, size: number, type: GLenum, normalize?: boolean): this; 23 | data(array: BufferSource | GLsizeiptr): void; 24 | subData(array: BufferSource, offset: number): void; 25 | attribPointer(program: Program): void; 26 | draw(mode: GLenum, count?: number, offset?: number): void; 27 | dispose(): void; 28 | _computeLength(): void; 29 | } 30 | export default ArrayBuffer; 31 | -------------------------------------------------------------------------------- /texture-base.d.ts: -------------------------------------------------------------------------------- 1 | import { GLContext } from './types'; 2 | import TextureCube from './texture-cube'; 3 | import Texture2D from './texture-2d'; 4 | export declare enum TextureType { 5 | NONE = 0, 6 | TEXTURE_2D = 3553, 7 | TEXTURE_2D_ARRAY = 35866, 8 | TEXTURE_CUBE = 34067, 9 | TEXTURE_3D = 32879 10 | } 11 | export type Texture = TextureCube | Texture2D; 12 | export default abstract class AbstractTexture { 13 | readonly textureType: TextureType; 14 | readonly gl: GLContext; 15 | readonly id: WebGLTexture; 16 | width: number; 17 | height: number; 18 | format: GLenum; 19 | internal: GLenum; 20 | type: GLenum; 21 | readonly _uid: number; 22 | abstract _target: GLenum; 23 | constructor(gl: GLContext, format?: GLenum, type?: GLenum, internal?: GLenum); 24 | setFormat(format?: GLenum, type?: GLenum, internal?: GLenum): void; 25 | bind(unit?: number): void; 26 | dispose(): void; 27 | setFilter(smooth?: boolean, mipmap?: boolean, miplinear?: boolean): this; 28 | repeat(): this; 29 | clamp(): this; 30 | mirror(): this; 31 | wrap(wrap: GLenum): this; 32 | } 33 | -------------------------------------------------------------------------------- /sampler.js: -------------------------------------------------------------------------------- 1 | import { getTextureFiltering } from './utils'; 2 | let _UID = 0; 3 | class Sampler { 4 | constructor(gl) { 5 | this._uid = _UID++; 6 | this.gl = gl; 7 | this.id = gl.createSampler(); 8 | this.setFilter(true); 9 | } 10 | bind(unit) { 11 | this.gl.bindSampler(unit, this.id); 12 | } 13 | dispose() { 14 | this.gl.deleteSampler(this.id); 15 | } 16 | setFilter(smooth = false, mipmap = false, miplinear = false) { 17 | const gl = this.gl; 18 | gl.samplerParameteri(this.id, gl.TEXTURE_MAG_FILTER, getTextureFiltering(!!smooth, false, false)); 19 | gl.samplerParameteri(this.id, gl.TEXTURE_MIN_FILTER, getTextureFiltering(!!smooth, !!mipmap, !!miplinear)); 20 | } 21 | repeat() { 22 | this.wrap(this.gl.REPEAT); 23 | } 24 | clamp() { 25 | this.wrap(this.gl.CLAMP_TO_EDGE); 26 | } 27 | mirror() { 28 | this.wrap(this.gl.MIRRORED_REPEAT); 29 | } 30 | wrap(wrap) { 31 | const gl = this.gl; 32 | gl.samplerParameteri(this.id, gl.TEXTURE_WRAP_S, wrap); 33 | gl.samplerParameteri(this.id, gl.TEXTURE_WRAP_T, wrap); 34 | } 35 | } 36 | export default Sampler; 37 | -------------------------------------------------------------------------------- /test/renderbuffer.js: -------------------------------------------------------------------------------- 1 | import Renderbuffer from '../renderbuffer' 2 | var expect = require( 'expect.js' ); 3 | 4 | var testContext = require( './utils/TestContext' ); 5 | var gl = testContext.getContext(); 6 | 7 | 8 | 9 | describe( "Renderbuffer", function(){ 10 | 11 | var w = 16, h = 16; 12 | 13 | it( "ctor", function(){ 14 | 15 | var r = new Renderbuffer( gl ); 16 | testContext.assertNoError(); 17 | 18 | }) 19 | 20 | it( "resize", function(){ 21 | 22 | var r = new Renderbuffer( gl ); 23 | r.resize( w, h ); 24 | testContext.assertNoError(); 25 | 26 | }) 27 | 28 | it( "resize and alloc", function(){ 29 | 30 | var r = new Renderbuffer( gl ); 31 | r.resize( w, h ); 32 | r.allocate(); 33 | testContext.assertNoError(); 34 | 35 | }) 36 | 37 | it( "bind", function(){ 38 | 39 | var r = new Renderbuffer( gl ); 40 | r.resize( w, h ); 41 | r.allocate(); 42 | r.bind(); 43 | testContext.assertNoError(); 44 | 45 | }) 46 | 47 | it( "dispose", function(){ 48 | 49 | var r = new Renderbuffer( gl ); 50 | r.resize( w, h ); 51 | r.allocate(); 52 | r.bind(); 53 | r.dispose(); 54 | testContext.assertNoError(); 55 | 56 | }) 57 | 58 | 59 | }); -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Trigger docs re-build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'develop' 7 | 8 | jobs: 9 | trigger_docs: 10 | name: Trigger docs re-build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v2.4.0 15 | 16 | - name: Setup Node 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 19 20 | 21 | - name: Build docs 22 | run: | 23 | npm install 24 | npm run build-docs 25 | 26 | - name: Commit and push 27 | uses: actions-js/push@master 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | message: 'update: docs (runned by GHAction)' 31 | author_email: 'pierre.lepers@gmail.com' 32 | author_name: 'plepers' 33 | branch: 'develop' 34 | 35 | - uses: actions/github-script@v6 36 | with: 37 | github-token: ${{ secrets.ACTIONS_KEY }} 38 | script: | 39 | await github.rest.actions.createWorkflowDispatch({ 40 | owner: 'evanmartiin', 41 | repo: 'nanogl-docs', 42 | workflow_id: 'main.yml', 43 | ref: 'main' 44 | }) 45 | -------------------------------------------------------------------------------- /texture-cube.js: -------------------------------------------------------------------------------- 1 | import AbstractTexture, { TextureType } from './texture-base'; 2 | const GL_TEXTURE_CUBE = 0x8513; 3 | export default class TextureCube extends AbstractTexture { 4 | constructor(gl, format, type, internal) { 5 | super(gl, format, type, internal); 6 | this.textureType = TextureType.TEXTURE_CUBE; 7 | this._target = GL_TEXTURE_CUBE; 8 | gl.bindTexture(GL_TEXTURE_CUBE, this.id); 9 | this.setFilter(true); 10 | } 11 | fromImages(imgs) { 12 | var gl = this.gl, fmt = this.format, internal = this.internal, type = this.type; 13 | this.width = imgs[0].width; 14 | this.height = imgs[0].height; 15 | gl.bindTexture(GL_TEXTURE_CUBE, this.id); 16 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, internal, fmt, type, imgs[0]); 17 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, internal, fmt, type, imgs[1]); 18 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, internal, fmt, type, imgs[2]); 19 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, internal, fmt, type, imgs[3]); 20 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, internal, fmt, type, imgs[4]); 21 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, internal, fmt, type, imgs[5]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/utils/FSGeom.js: -------------------------------------------------------------------------------- 1 | function FSGeom( gl ){ 2 | 3 | this.gl = gl; 4 | 5 | this.vertices = new Float32Array( [ 6 | -1, -1, 7 | 1, -1, 8 | 1, 1, 9 | -1, 1 10 | ] ); 11 | 12 | this.indices = new Uint16Array( [ 13 | 0, 1, 2, 14 | 0, 2, 3 15 | ] ); 16 | 17 | this.stride = 8; 18 | 19 | this.allocate(); 20 | 21 | } 22 | 23 | FSGeom.prototype = { 24 | 25 | allocate : function(){ 26 | var gl = this.gl; 27 | this.buffer = gl.createBuffer(); 28 | gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); 29 | gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.STATIC_DRAW); 30 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 31 | 32 | 33 | this.ibuffer = gl.createBuffer(); 34 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibuffer ); 35 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); 36 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null ); 37 | }, 38 | 39 | 40 | bind : function( prg ){ 41 | var gl = this.gl; 42 | 43 | gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer ); 44 | gl.vertexAttribPointer( prg.aPosition, 2, gl.FLOAT, false, this.stride, 0 ); 45 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibuffer ); 46 | 47 | }, 48 | 49 | render : function() { 50 | var gl = this.gl; 51 | gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); 52 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null ); 53 | } 54 | 55 | } 56 | 57 | module.exports = FSGeom; -------------------------------------------------------------------------------- /fbo.d.ts: -------------------------------------------------------------------------------- 1 | import Texture2D from './texture-2d'; 2 | import RenderBuffer from './renderbuffer'; 3 | import { GLContext } from './types'; 4 | export type AttachmentTarget = Texture2D | RenderBuffer; 5 | export declare class Attachment { 6 | level: number; 7 | readonly target: AttachmentTarget; 8 | private _isTexture; 9 | constructor(target: AttachmentTarget); 10 | isTexture(): boolean; 11 | _resize(w: number, h: number): void; 12 | _attach(bindingPoint: GLenum): void; 13 | _detach(bindingPoint: GLenum): void; 14 | dispose(): void; 15 | } 16 | declare class Fbo { 17 | readonly gl: GLContext; 18 | readonly fbo: WebGLFramebuffer; 19 | readonly attachmentsList: Attachment[]; 20 | attachments: Record; 21 | width: number; 22 | height: number; 23 | constructor(gl: GLContext); 24 | attach(bindingPoint: GLenum, res: AttachmentTarget): Attachment; 25 | detach(bindingPoint: GLenum): void; 26 | getAttachment(bindingPoint: GLenum): Attachment | null; 27 | getColor(index?: number): AttachmentTarget | null; 28 | getColorTexture(index?: number): Texture2D; 29 | getDepth(): AttachmentTarget | null; 30 | attachColor(format?: GLenum, type?: GLenum, internal?: GLenum): Attachment; 31 | attachDepth(depth?: boolean, stencil?: boolean, useTexture?: boolean): Attachment; 32 | resize(w: number, h: number): void; 33 | bind(): void; 34 | clear(): void; 35 | defaultViewport(): void; 36 | isValid(): boolean; 37 | dispose(): void; 38 | _allocate(): void; 39 | } 40 | export default Fbo; 41 | -------------------------------------------------------------------------------- /indexbuffer.js: -------------------------------------------------------------------------------- 1 | import BaseBuffer from './basebuffer'; 2 | import { getComponentSize, isBufferSource } from './utils'; 3 | const TGT = 0x8893; 4 | class IndexBuffer extends BaseBuffer { 5 | constructor(gl, type = gl.UNSIGNED_SHORT, data, usage = gl.STATIC_DRAW, glbuffer) { 6 | super(); 7 | this.gl = gl; 8 | this.usage = usage; 9 | this.buffer = (glbuffer !== undefined) ? glbuffer : gl.createBuffer(); 10 | this.type = 0; 11 | this.typeSize = 0; 12 | this.byteLength = 0; 13 | this.setType(type); 14 | if (data) { 15 | this.data(data); 16 | } 17 | } 18 | bind() { 19 | this.gl.bindBuffer(TGT, this.buffer); 20 | } 21 | setType(type) { 22 | this.type = type; 23 | this.typeSize = getComponentSize(type); 24 | } 25 | data(array) { 26 | const gl = this.gl; 27 | gl.bindBuffer(TGT, this.buffer); 28 | gl.bufferData(TGT, array, this.usage); 29 | gl.bindBuffer(TGT, null); 30 | this.byteLength = isBufferSource(array) ? array.byteLength : array; 31 | } 32 | subData(array, offset) { 33 | const gl = this.gl; 34 | gl.bindBuffer(TGT, this.buffer); 35 | gl.bufferSubData(TGT, offset, array); 36 | gl.bindBuffer(TGT, null); 37 | } 38 | dispose() { 39 | this.gl.deleteBuffer(this.buffer); 40 | } 41 | draw(mode, count, offset = 0) { 42 | count = (count === undefined) ? this.byteLength / this.typeSize : count; 43 | this.gl.drawElements(mode, count, this.type, 0 | offset); 44 | } 45 | } 46 | export default IndexBuffer; 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nanogl 2 | [![NPM Package](https://img.shields.io/npm/v/nanogl.svg)](https://www.npmjs.com/package/nanogl) [![NPM Downloads](https://img.shields.io/npm/dw/nanogl)](https://npmtrends.com/nanogl) [![GitHub](https://img.shields.io/github/license/plepers/nanogl)](https://github.com/plepers/nanogl/blob/develop/LICENSE) 3 | 4 | nanogl is a WebGL micro framework (3Ko) 5 | 6 | [Guide](https://makemepulse.github.io/nanogl-docs/guide) | [API](https://makemepulse.github.io/nanogl-docs/api) | [Examples](https://makemepulse.github.io/nanogl-docs/examples) 7 | 8 | #### Libraries 9 | 10 | There are libraries to expand nanogl for useful features : 11 | - [nanogl-camera](https://github.com/plepers/nanogl-camera) : Cameras for nanogl 12 | - [nanogl-node](https://github.com/plepers/nanogl-node) : Handle nested objects transform in 3D space 13 | - [nanogl-primitives-2d](https://github.com/plepers/nanogl-primitives-2d) : Basic 2D primitives for nanogl 14 | - [nanogl-glft](https://github.com/plepers/nanogl-gltf) : Handle .gltf files 15 | - [nanogl-pbr](https://github.com/plepers/nanogl-pbr) : Physically based rendering materials for nanogl 16 | - [nanogl-pf](https://github.com/plepers/nanogl-pf) : Provide pixel format related capabilities 17 | - [nanogl-post](https://github.com/plepers/nanogl-post) : Post-processing for nanogl 18 | - [nanogl-state](https://github.com/plepers/nanogl-state) : Efficient webgl state management 19 | - [nanogl-sync](https://github.com/plepers/nanogl-sync) : WebGLSync for nanogl 20 | - [nanogl-vao](https://github.com/plepers/nanogl-vao) : OES_vertex_array_object extension support for nanogl 21 | 22 | #### Build and test 23 | 24 | Tests are run with karma. 25 | 26 | ``` 27 | $ npm install 28 | $ npm test 29 | ``` 30 | -------------------------------------------------------------------------------- /renderbuffer.js: -------------------------------------------------------------------------------- 1 | import { isWebgl2 } from './types'; 2 | let _UID = 0; 3 | const RENDERBUFFER = 0x8d41; 4 | class RenderBuffer { 5 | constructor(gl, format, samples = 0) { 6 | this.samples = 0; 7 | this._uid = _UID++; 8 | this.gl = gl; 9 | this.id = gl.createRenderbuffer(); 10 | if (samples > 0 && isWebgl2(gl)) { 11 | const maxSamples = gl.getParameter(gl.MAX_SAMPLES); 12 | this.samples = (samples > maxSamples) ? maxSamples : samples; 13 | } 14 | this.width = 0; 15 | this.height = 0; 16 | this.format = format || gl.DEPTH_COMPONENT16; 17 | this._valid = false; 18 | this._storage(); 19 | } 20 | resize(w, h) { 21 | if (this.width !== w || this.height !== h) { 22 | this.width = w; 23 | this.height = h; 24 | this._valid = false; 25 | } 26 | } 27 | allocate() { 28 | if (!this._valid && this.width > 0 && this.height > 0) { 29 | this._storage(); 30 | this._valid = true; 31 | } 32 | } 33 | bind() { 34 | this.gl.bindRenderbuffer(RENDERBUFFER, this.id); 35 | } 36 | dispose() { 37 | this.gl.deleteRenderbuffer(this.id); 38 | } 39 | _storage() { 40 | const gl = this.gl; 41 | gl.bindRenderbuffer(RENDERBUFFER, this.id); 42 | if (this.samples > 0 && isWebgl2(gl)) { 43 | gl.renderbufferStorageMultisample(RENDERBUFFER, this.samples, this.format, this.width, this.height); 44 | } 45 | else { 46 | gl.renderbufferStorage(RENDERBUFFER, this.format, this.width, this.height); 47 | } 48 | gl.bindRenderbuffer(RENDERBUFFER, null); 49 | } 50 | } 51 | export default RenderBuffer; 52 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Know whether the value is a buffer source or not. 3 | * You can use it for values that can be either a size or a buffer source. 4 | * @param {GLsizeiptr|BufferSource} val The value to test 5 | */ 6 | export function isBufferSource(val: GLsizeiptr | BufferSource): val is BufferSource { 7 | return (val).byteLength !== undefined; 8 | } 9 | 10 | /** 11 | * Compute texture filtering enum from parameters. 12 | * Will return one of the following : 13 | * - `NEAREST` 14 | * - `LINEAR` 15 | * - `NEAREST_MIPMAP_NEAREST` 16 | * - `LINEAR_MIPMAP_NEAREST` 17 | * - `NEAREST_MIPMAP_LINEAR` 18 | * - `LINEAR_MIPMAP_LINEAR` 19 | * @param {boolean} [smooth=false] Use linear filtering or not 20 | * @param {boolean} [mipmap=false] Enable mipmapping or not 21 | * @param {boolean} [miplinear=false] Use linear mipmapping or not 22 | */ 23 | export function getTextureFiltering(smooth: boolean, mipmap: boolean, miplinear: boolean) : GLenum { 24 | return 0x2600 | +smooth | (+mipmap << 8) | (+(mipmap && miplinear) << 1); 25 | } 26 | 27 | /** The size in bytes if a component. */ 28 | export type ComponentSize = 1|2|4 29 | 30 | /** 31 | * Get the component size in bytes from a data type. 32 | * @param {GLenum} type The type of data (`GL_FLOAT`, `GL_SHORT`, etc.) 33 | */ 34 | export function getComponentSize(type: GLenum): ComponentSize { 35 | switch (type) { 36 | case 0x1400: //gl.BYTE: 37 | case 0x1401: //gl.UNSIGNED_BYTE: 38 | return 1; 39 | case 0x1402: //gl.SHORT: 40 | case 0x1403: //gl.UNSIGNED_SHORT: 41 | return 2; 42 | case 0x1404: //gl.INT: 43 | case 0x1405: //gl.UNSIGNED_INT: 44 | case 0x1406: //gl.FLOAT: 45 | return 4; 46 | default: 47 | throw new Error(`unknown type ${type}`) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/texture-cube.ts: -------------------------------------------------------------------------------- 1 | import AbstractTexture, { TextureType } from './texture-base'; 2 | import { GLContext } from './types'; 3 | 4 | const GL_TEXTURE_CUBE = 0x8513; 5 | 6 | /** 7 | * This class manages TEXTURE_CUBE type textures. 8 | */ 9 | export default class TextureCube extends AbstractTexture { 10 | 11 | readonly textureType : TextureType.TEXTURE_CUBE = TextureType.TEXTURE_CUBE; 12 | 13 | _target: GLenum = GL_TEXTURE_CUBE; 14 | 15 | 16 | /** 17 | * @param {GLContext} gl The webgl context this Texture belongs to 18 | * @param {GLenum} [format=GL_RGB] The pixel format of the texture (`GL_RGB`, `GL_RGBA`, etc.), defaults to `GL_RGB` 19 | * @param {GLenum} [type=GL_UNSIGNED_BYTE] The pixel data type of the texture (`GL_UNSIGNED_BYTE`, `GL_FLOAT`, etc.), defaults to `GL_UNSIGNED_BYTE` 20 | * @param {GLenum} [internal=format] The pixel internal format of the texture, defaults to the `format` parameter value 21 | */ 22 | constructor(gl: GLContext, format?: GLenum, type?: GLenum, internal?: GLenum) { 23 | super( gl, format, type, internal ); 24 | 25 | gl.bindTexture(GL_TEXTURE_CUBE, this.id); 26 | this.setFilter(true); 27 | } 28 | 29 | 30 | /** 31 | * Set the texture data from a list of HTML sources. 32 | * @param imgs The list of HTML image, canvas or video elements to use as source 33 | */ 34 | fromImages( imgs: TexImageSource[] ){ 35 | var gl = this.gl, 36 | fmt = this.format, 37 | internal = this.internal, 38 | type = this.type; 39 | 40 | this.width = imgs[0].width; 41 | this.height = imgs[0].height; 42 | 43 | gl.bindTexture( GL_TEXTURE_CUBE, this.id ); 44 | 45 | gl.texImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, internal, fmt, type, imgs[0] ); 46 | gl.texImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, internal, fmt, type, imgs[1] ); 47 | gl.texImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, internal, fmt, type, imgs[2] ); 48 | gl.texImage2D( gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, internal, fmt, type, imgs[3] ); 49 | gl.texImage2D( gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, internal, fmt, type, imgs[4] ); 50 | gl.texImage2D( gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, internal, fmt, type, imgs[5] ); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanogl", 3 | "version": "2.3.2", 4 | "description": "webgl micro framework ", 5 | "main": "nanogl.js", 6 | "scripts": { 7 | "test": "tsc && karma start && karma start --webgl-version=2", 8 | "test_gl1": "tsc && karma start", 9 | "test_gl2": "tsc && karma start --webgl-version=2", 10 | "docs": "jsdoc program.js texture.js fbo.js arraybuffer.js indexbuffer.js bufferutils.js -R README.md -d ../nanogl_gh_pages -t node_modules/minami", 11 | "gztest": "browserify nanogl.js -o gztest.js && uglifyjs --screw-ie8 -m -c warnings=false -o gztest.min.js -- gztest.js && gzip -k -9 gztest.min.js && stat -f%z gztest.min.js.gz && rm ./*gztest*", 12 | "perf": "browserify -t [ stringify --extensions [.vert .frag] ] -o test/perf/main.js test/perf/index.js", 13 | "build-docs": "typedoc && del tempdocs/** '!tempdocs/data.json' && mv tempdocs/* docs/ && rm -rf tempdocs" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/plepers/nanogl.git" 18 | }, 19 | "keywords": [ 20 | "webgl" 21 | ], 22 | "author": "Pierre Lepers", 23 | "license": "GPL-2.0", 24 | "bugs": { 25 | "url": "https://github.com/plepers/nanogl/issues" 26 | }, 27 | "homepage": "https://github.com/plepers/nanogl#readme", 28 | "browserify": { 29 | "transform": [ 30 | [ 31 | "babelify", 32 | { 33 | "presets": [ 34 | "@babel/preset-env" 35 | ] 36 | } 37 | ] 38 | ] 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "^7.7.4", 42 | "@babel/preset-env": "^7.7.4", 43 | "babelify": "^10.0.0", 44 | "browserify": "^16.5.0", 45 | "del-cli": "^5.0.0", 46 | "expect.js": "^0.3.1", 47 | "karma": "^4.4.1", 48 | "karma-browserify": "^6.1.0", 49 | "karma-chrome-launcher": "^3.1.0", 50 | "karma-firefox-launcher": "^0.1.7", 51 | "karma-mocha": "^1.3.0", 52 | "karma-safari-launcher": "^1.0.0", 53 | "karma-sauce-launcher": "^2.0.2", 54 | "mocha": "^6.2.2", 55 | "sinon": "^7.5.0", 56 | "stringify": "^5.2.0", 57 | "typedoc": "^0.23.24", 58 | "typedoc-plugin-rename-defaults": "^0.6.4", 59 | "typescript": "^4.9.5", 60 | "watchify": "^3.11.1", 61 | "when": "^3.7.8" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/texture-2d.ts: -------------------------------------------------------------------------------- 1 | import AbstractTexture, { TextureType } from './texture-base'; 2 | import { GLContext } from './types'; 3 | 4 | const GL_TEXTURE_2D = 0x0de1; 5 | 6 | /** 7 | * This class manages TEXTURE_2D type textures. 8 | */ 9 | export default class Texture2D extends AbstractTexture { 10 | 11 | readonly textureType : TextureType.TEXTURE_2D = TextureType.TEXTURE_2D; 12 | 13 | _target: GLenum = GL_TEXTURE_2D; 14 | 15 | 16 | /** 17 | * @param {GLContext} gl The webgl context this Texture belongs to 18 | * @param {GLenum} [format=GL_RGB] The pixel format of the texture (`GL_RGB`, `GL_RGBA`, etc.), defaults to `GL_RGB` 19 | * @param {GLenum} [type=GL_UNSIGNED_BYTE] The pixel data type of the texture (`GL_UNSIGNED_BYTE`, `GL_FLOAT`, etc.), defaults to `GL_UNSIGNED_BYTE` 20 | * @param {GLenum} [internal=format] The pixel internal format of the texture, defaults to the `format` parameter value 21 | */ 22 | constructor(gl: GLContext, format?: GLenum, type?: GLenum, internal?: GLenum) { 23 | super( gl, format, type, internal ); 24 | 25 | gl.bindTexture(GL_TEXTURE_2D, this.id); 26 | this.setFilter(true); 27 | } 28 | 29 | 30 | /** 31 | * Set the texture data from an HTML source. 32 | * @param img The HTML image, canvas or video element to use as source 33 | */ 34 | fromImage(img: TexImageSource) { 35 | const gl = this.gl; 36 | 37 | this.width = img.width; 38 | this.height = img.height; 39 | 40 | gl.bindTexture(GL_TEXTURE_2D, this.id); 41 | gl.texImage2D(GL_TEXTURE_2D, 0, this.internal, this.format, this.type, img); 42 | } 43 | 44 | /** 45 | * Set the texture data from a TypedArray. 46 | * You can also allocate an empty texture by providing only the size. 47 | * @param {number} width The width of the texture 48 | * @param {number} height The height of the texture 49 | * @param {TypedArray|null} [data=null] The data to fill the texture with, or null to allocate an empty texture 50 | */ 51 | fromData(width: number, height: number, data: ArrayBufferView|null = null) { 52 | const gl = this.gl; 53 | 54 | this.width = width; 55 | this.height = height; 56 | 57 | data = data || null; 58 | 59 | gl.bindTexture(GL_TEXTURE_2D, this.id); 60 | gl.texImage2D(GL_TEXTURE_2D, 0, this.internal, width, height, 0, this.format, this.type, data); 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /texture-base.js: -------------------------------------------------------------------------------- 1 | import { getTextureFiltering } from './utils'; 2 | let _UID = 0; 3 | export var TextureType; 4 | (function (TextureType) { 5 | TextureType[TextureType["NONE"] = 0] = "NONE"; 6 | TextureType[TextureType["TEXTURE_2D"] = 3553] = "TEXTURE_2D"; 7 | TextureType[TextureType["TEXTURE_2D_ARRAY"] = 35866] = "TEXTURE_2D_ARRAY"; 8 | TextureType[TextureType["TEXTURE_CUBE"] = 34067] = "TEXTURE_CUBE"; 9 | TextureType[TextureType["TEXTURE_3D"] = 32879] = "TEXTURE_3D"; 10 | })(TextureType || (TextureType = {})); 11 | export default class AbstractTexture { 12 | constructor(gl, format, type, internal) { 13 | this.textureType = TextureType.NONE; 14 | this.format = 0; 15 | this.internal = 0; 16 | this.type = 0; 17 | this._uid = _UID++; 18 | this.gl = gl; 19 | this.id = gl.createTexture(); 20 | this.width = 0; 21 | this.height = 0; 22 | this.setFormat(format, type, internal); 23 | } 24 | setFormat(format, type, internal) { 25 | this.format = format || this.gl.RGB; 26 | this.internal = internal || this.format; 27 | this.type = type || this.gl.UNSIGNED_BYTE; 28 | } 29 | bind(unit) { 30 | const gl = this.gl; 31 | if (unit !== undefined) { 32 | gl.activeTexture(gl.TEXTURE0 + (0 | unit)); 33 | } 34 | gl.bindTexture(this._target, this.id); 35 | } 36 | dispose() { 37 | this.gl.deleteTexture(this.id); 38 | } 39 | setFilter(smooth = false, mipmap = false, miplinear = false) { 40 | const gl = this.gl; 41 | gl.texParameteri(this._target, gl.TEXTURE_MAG_FILTER, getTextureFiltering(!!smooth, false, false)); 42 | gl.texParameteri(this._target, gl.TEXTURE_MIN_FILTER, getTextureFiltering(!!smooth, !!mipmap, !!miplinear)); 43 | return this; 44 | } 45 | repeat() { 46 | this.wrap(this.gl.REPEAT); 47 | return this; 48 | } 49 | clamp() { 50 | this.wrap(this.gl.CLAMP_TO_EDGE); 51 | return this; 52 | } 53 | mirror() { 54 | this.wrap(this.gl.MIRRORED_REPEAT); 55 | return this; 56 | } 57 | wrap(wrap) { 58 | const gl = this.gl; 59 | gl.texParameteri(this._target, gl.TEXTURE_WRAP_S, wrap); 60 | gl.texParameteri(this._target, gl.TEXTURE_WRAP_T, wrap); 61 | return this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /arraybuffer.js: -------------------------------------------------------------------------------- 1 | import BaseBuffer from './basebuffer'; 2 | import { getComponentSize, isBufferSource } from './utils'; 3 | const GL_ARRAY_BUFFER = 0x8892; 4 | class ArrayBuffer extends BaseBuffer { 5 | constructor(gl, data, usage = gl.STATIC_DRAW, glbuffer) { 6 | super(); 7 | this.gl = gl; 8 | this.usage = usage; 9 | this.buffer = (glbuffer !== undefined) ? glbuffer : gl.createBuffer(); 10 | this.attribs = []; 11 | this.stride = 0; 12 | this.byteLength = 0; 13 | this.length = 0; 14 | if (data) { 15 | this.data(data); 16 | } 17 | } 18 | bind() { 19 | this.gl.bindBuffer(GL_ARRAY_BUFFER, this.buffer); 20 | } 21 | attrib(name, size, type, normalize = false) { 22 | this.attribs.push({ 23 | name, 24 | type: 0 | type, 25 | size: 0 | size, 26 | normalize, 27 | offset: this.stride, 28 | stride: 0 29 | }); 30 | this.stride += getComponentSize(type) * size; 31 | this._computeLength(); 32 | return this; 33 | } 34 | data(array) { 35 | const gl = this.gl; 36 | gl.bindBuffer(GL_ARRAY_BUFFER, this.buffer); 37 | gl.bufferData(GL_ARRAY_BUFFER, array, this.usage); 38 | gl.bindBuffer(GL_ARRAY_BUFFER, null); 39 | this.byteLength = isBufferSource(array) ? array.byteLength : array; 40 | this._computeLength(); 41 | } 42 | subData(array, offset) { 43 | const gl = this.gl; 44 | gl.bindBuffer(GL_ARRAY_BUFFER, this.buffer); 45 | gl.bufferSubData(GL_ARRAY_BUFFER, offset, array); 46 | gl.bindBuffer(GL_ARRAY_BUFFER, null); 47 | } 48 | attribPointer(program) { 49 | const gl = this.gl; 50 | gl.bindBuffer(GL_ARRAY_BUFFER, this.buffer); 51 | for (var i = 0; i < this.attribs.length; i++) { 52 | var attrib = this.attribs[i]; 53 | if (program[attrib.name] !== undefined) { 54 | var aLocation = program[attrib.name](); 55 | gl.enableVertexAttribArray(aLocation); 56 | gl.vertexAttribPointer(aLocation, attrib.size, attrib.type, attrib.normalize, attrib.stride || this.stride, attrib.offset); 57 | } 58 | } 59 | } 60 | draw(mode, count = this.length, offset = 0) { 61 | this.gl.drawArrays(mode, offset, 0 | count); 62 | } 63 | dispose() { 64 | this.gl.deleteBuffer(this.buffer); 65 | } 66 | _computeLength() { 67 | if (this.stride > 0) { 68 | this.length = this.byteLength / this.stride; 69 | } 70 | } 71 | } 72 | export default ArrayBuffer; 73 | -------------------------------------------------------------------------------- /src/sampler.ts: -------------------------------------------------------------------------------- 1 | import { getTextureFiltering } from './utils'; 2 | 3 | let _UID = 0; 4 | 5 | /** 6 | * This class manages GLES3 Samplers. 7 | */ 8 | class Sampler { 9 | /** Unique id for the Sampler */ 10 | readonly _uid: number; 11 | 12 | /** The webgl context this Sampler belongs to */ 13 | gl: WebGL2RenderingContext; 14 | /** The underlying webgl sampler */ 15 | id: WebGLSampler; 16 | 17 | /** 18 | * @param {WebGL2RenderingContext} gl The webgl2 context this Sampler belongs to 19 | */ 20 | constructor(gl: WebGL2RenderingContext) { 21 | this._uid = _UID++; 22 | this.gl = gl; 23 | this.id = gl.createSampler(); 24 | this.setFilter(true); 25 | } 26 | 27 | /** 28 | * Bind the underlying webgl sampler. 29 | * @param {number} [unit] The texture unit to make active before binding 30 | */ 31 | bind(unit: number) { 32 | this.gl.bindSampler(unit, this.id); 33 | } 34 | 35 | /** 36 | * Delete all webgl objects related to this Sampler. 37 | */ 38 | dispose() { 39 | this.gl.deleteSampler(this.id); 40 | } 41 | 42 | /** 43 | * Change the filtering parameters. 44 | * @param {boolean} [smooth=false] Use linear filtering or not 45 | * @param {boolean} [mipmap=false] Enable mipmapping or not 46 | * @param {boolean} [miplinear=false] Use linear mipmapping or not 47 | */ 48 | setFilter(smooth: boolean = false, mipmap: boolean = false, miplinear: boolean = false) { 49 | const gl = this.gl; 50 | gl.samplerParameteri(this.id, gl.TEXTURE_MAG_FILTER, getTextureFiltering(!!smooth, false, false)); 51 | gl.samplerParameteri(this.id, gl.TEXTURE_MIN_FILTER, getTextureFiltering(!!smooth, !!mipmap, !!miplinear)); 52 | } 53 | 54 | /** 55 | * Make sampler repeat : 56 | * Set the `WRAP_S` and `WRAP_T` properties to `GL_REPEAT`. 57 | */ 58 | repeat() { 59 | this.wrap(this.gl.REPEAT); 60 | } 61 | 62 | /** 63 | * Make sampler clamp : 64 | * Set the `WRAP_S` and `WRAP_T` properties to `GL_CLAMP_TO_EDGE`. 65 | */ 66 | clamp() { 67 | this.wrap(this.gl.CLAMP_TO_EDGE); 68 | } 69 | 70 | /** 71 | * Make sampler mirror : 72 | * Set the `WRAP_S` and `WRAP_T` properties to `GL_MIRRORED_REPEAT`. 73 | */ 74 | mirror() { 75 | this.wrap(this.gl.MIRRORED_REPEAT); 76 | } 77 | 78 | /** 79 | * Set the `WRAP_S` and `WRAP_T` properties to the given value. 80 | * @param {GLenum} wrap The wrap value to use (`GL_REPEAT`, `GL_CLAMP_TO_EDGE`, etc.) 81 | */ 82 | wrap(wrap: GLenum) { 83 | const gl = this.gl; 84 | gl.samplerParameteri(this.id, gl.TEXTURE_WRAP_S, wrap); 85 | gl.samplerParameteri(this.id, gl.TEXTURE_WRAP_T, wrap); 86 | } 87 | } 88 | 89 | 90 | export default Sampler -------------------------------------------------------------------------------- /test/utils/TestContext.js: -------------------------------------------------------------------------------- 1 | var FSGeom = require( './FSGeom' ) 2 | 3 | var expect = require( 'expect.js' ); 4 | 5 | var cvs = null, 6 | gl = null; 7 | 8 | var glSize = 64; 9 | var fsgeom = null; 10 | 11 | var ctest32 = new Uint32Array( 1 ) 12 | var ctest8888 = new Uint8Array( ctest32.buffer ) 13 | 14 | var glversion = 1; 15 | 16 | 17 | function initGL(){ 18 | cvs = document.createElement( 'canvas' ); 19 | cvs.width = glSize; 20 | cvs.height = glSize; 21 | 22 | cvs.style.width = glSize*4+'px' 23 | cvs.style.height = glSize*4+'px' 24 | 25 | document.body.appendChild( cvs ) 26 | 27 | var opts = { 28 | alpha : true, 29 | depth : true, 30 | stencil : true, 31 | antialias : true, 32 | premultipliedAlpha : true, 33 | preserveDrawingBuffer : false, 34 | preferLowPowerToHighPerformance : true, 35 | failIfMajorPerformanceCaveat : false 36 | } 37 | 38 | 39 | 40 | if( __karma__.config.webgl_version===2 ){ 41 | glversion = 2 42 | gl = cvs.getContext( 'webgl2', opts ) 43 | } 44 | else 45 | gl = cvs.getContext( 'webgl', opts ) || cvs.getContext( 'experimental-webgl', opts ) || cvs.getContext( 'webgl'); 46 | 47 | gl.viewport( 0,0,glSize, glSize ) 48 | gl.clearColor( 0, 0, 0, 1) 49 | gl.clear( gl.COLOR_BUFFER_BIT ) 50 | 51 | fsgeom = new FSGeom( gl ); 52 | 53 | } 54 | 55 | function getContext(){ 56 | if( ! gl ) 57 | initGL(); 58 | return gl; 59 | } 60 | 61 | 62 | function getGlVersion(){ 63 | return glversion; 64 | } 65 | 66 | 67 | function getPixel(x, y){ 68 | var res = new Uint8Array( 4 ) 69 | gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, res ); 70 | return res; 71 | } 72 | 73 | function testPixel(x, y, rgba){ 74 | var TOLERANCE = 2; 75 | var r = (rgba >>> 16 ) & 0xFF 76 | var g = (rgba >>> 8 ) & 0xFF 77 | var b = rgba & 0xFF 78 | var a = (rgba >>> 24 ) & 0xFF 79 | var res = getPixel( x, y ); 80 | 81 | if( (Math.abs( r-res[0] ) > TOLERANCE) || (Math.abs( g-res[1] ) > TOLERANCE) || (Math.abs( b-res[2] ) > TOLERANCE) || (Math.abs( a-res[3] ) > TOLERANCE)){ 82 | 83 | ctest8888[3] = res[3]; 84 | ctest8888[2] = res[0]; 85 | ctest8888[1] = res[1]; 86 | ctest8888[0] = res[2]; 87 | var hex = '0x' + ctest32[0].toString(16) 88 | var tex = '0x' + rgba.toString(16) 89 | expect( hex ).to.equal( tex ); 90 | } 91 | 92 | } 93 | 94 | function drawProgram( p ){ 95 | 96 | gl.clear( gl.COLOR_BUFFER_BIT ) 97 | p.bind() 98 | fsgeom.bind( p ); 99 | fsgeom.render() 100 | } 101 | 102 | function assertNoError(){ 103 | expect( gl.getError() ).to.equal( 0 ); 104 | } 105 | function bindScreen(){ 106 | gl.bindFramebuffer( gl.FRAMEBUFFER, null ); 107 | gl.viewport( 0,0,glSize, glSize ) 108 | } 109 | 110 | module.exports = { 111 | getGlVersion :getGlVersion, 112 | getContext : getContext, 113 | glSize : glSize, 114 | getPixel : getPixel, 115 | testPixel : testPixel, 116 | fsgeom : fsgeom, 117 | drawProgram : drawProgram, 118 | assertNoError : assertNoError, 119 | bindScreen: bindScreen 120 | } 121 | -------------------------------------------------------------------------------- /test/indexbuffer.js: -------------------------------------------------------------------------------- 1 | import IndexBuffer from '../indexbuffer' 2 | import ArrayBuffer from '../arraybuffer' 3 | import Program from '../program' 4 | 5 | var expect = require( 'expect.js' ); 6 | 7 | var testContext = require( './utils/TestContext' ); 8 | var gl = testContext.getContext(); 9 | 10 | var indices = new Uint16Array([ 11 | 0, 1, 2, 12 | 0, 2, 3 13 | ]); 14 | 15 | describe( "ArrayBuffer", function(){ 16 | 17 | it( "should be exported", function(){ 18 | 19 | expect( IndexBuffer ).to.be.ok( ); 20 | 21 | }); 22 | 23 | it( "constructor should return instance", function(){ 24 | 25 | var p = new IndexBuffer( gl ); 26 | expect( p ).to.be.ok( ); 27 | expect( p.gl ).to.be.ok( ); 28 | 29 | }); 30 | 31 | 32 | it( "ctor should leave clean state", function(){ 33 | var p = new IndexBuffer( gl ); 34 | testContext.assertNoError(); 35 | }); 36 | 37 | 38 | it( "ctor should set default type", function(){ 39 | var p = new IndexBuffer( gl ); 40 | expect( p.type ).to.be.equal( gl.UNSIGNED_SHORT ); 41 | expect( p.usage ).to.be.equal( gl.STATIC_DRAW ); 42 | expect( p.typeSize ).to.be.equal( 2 ); 43 | }); 44 | 45 | 46 | it( "dispose should leave clean state", function(){ 47 | var p = new IndexBuffer( gl ); 48 | p.dispose(); 49 | testContext.assertNoError(); 50 | }); 51 | 52 | 53 | it( "data( array ) should leave clean state", function(){ 54 | var p = new IndexBuffer( gl ); 55 | p.data( indices ); 56 | testContext.assertNoError(); 57 | }); 58 | 59 | it( "data( size ) should leave clean state", function(){ 60 | var p = new IndexBuffer( gl ); 61 | p.data( 64 ); 62 | testContext.assertNoError(); 63 | }); 64 | 65 | 66 | it( "subData() should leave clean state", function(){ 67 | var p = new IndexBuffer( gl ); 68 | p.data( indices ); 69 | p.subData( new Uint16Array( [2, 3, 4]), 6) 70 | testContext.assertNoError(); 71 | }); 72 | 73 | 74 | 75 | it( "should render", function(){ 76 | var b = new IndexBuffer( gl ); 77 | b.data( indices ); 78 | 79 | var p = new Program( gl ); 80 | p.compile( 81 | require( './glsl/test_uvec3.vert'), 82 | require( './glsl/test_uvec3.frag') 83 | ); 84 | p.use(); 85 | p.uVec3( 1.0, 1.0, 1.0 ); 86 | 87 | var vbuffer = new ArrayBuffer( gl, new Float32Array([ 88 | -1, -1, 89 | 1, -1, 90 | -1, 1, 91 | 1, 1 92 | ]) ); 93 | vbuffer.attrib( 'aPosition', 2, gl.FLOAT ); 94 | vbuffer.attribPointer( p ); 95 | 96 | gl.clear( gl.COLOR_BUFFER_BIT ) 97 | b.bind() 98 | b.drawTriangles(); 99 | 100 | 101 | testContext.testPixel( 32, 10, 0xffffffff ); 102 | testContext.testPixel( 32, 54, 0xffffffff ); 103 | testContext.testPixel( 10, 32, 0xffffffff ); 104 | testContext.testPixel( 54, 32, 0xff000000 ); 105 | 106 | b.subData( new Uint16Array([ 107 | 2, 1, 3]), 6 ); 108 | b.bind() 109 | b.drawTriangles(); 110 | 111 | testContext.testPixel( 32, 10, 0xffffffff ); 112 | testContext.testPixel( 32, 54, 0xffffffff ); 113 | testContext.testPixel( 10, 32, 0xffffffff ); 114 | testContext.testPixel( 54, 32, 0xffffffff ); 115 | testContext.assertNoError(); 116 | 117 | 118 | 119 | }); 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | }); -------------------------------------------------------------------------------- /src/indexbuffer.ts: -------------------------------------------------------------------------------- 1 | import { GLContext } from './types'; 2 | import BaseBuffer from './basebuffer' 3 | import { getComponentSize, isBufferSource } from './utils'; 4 | 5 | 6 | /* GL_ELEMENT_ARRAY_BUFFER */ 7 | const TGT = 0x8893; 8 | 9 | /** 10 | * This class manages ELEMENT_ARRAY_BUFFER type buffers. 11 | * @extends {BaseBuffer} 12 | */ 13 | class IndexBuffer extends BaseBuffer { 14 | /** The webgl context this IndexBuffer belongs to */ 15 | readonly gl: GLContext; 16 | /** The webgl buffer this IndexBuffer writes to */ 17 | readonly buffer: WebGLBuffer; 18 | 19 | /** The usage hint for this buffer */ 20 | usage: GLenum; 21 | /** The number type of the index data for this buffer */ 22 | type : GLenum; 23 | /** The number of bytes for each index in this buffer */ 24 | typeSize : number; 25 | /** The length in bytes of the buffer */ 26 | byteLength: number; 27 | 28 | /** 29 | * @param {GLContext} gl The webgl context this IndexBuffer belongs to 30 | * @param {GLenum} [type=GL_UNSIGNED_SHORT] The number type of the index data (`GL_UNSIGNED_BYTE`, `GL_UNSIGNED_INT`, etc.) 31 | * @param {BufferSource|GLsizeiptr} [data] The data to fill the buffer with, or the size (in bytes) 32 | * @param {GLenum} [usage=GL_STATIC_DRAW] The usage hint for this buffer (`GL_STATIC_DRAW`, `GL_DYNAMIC_DRAW`, etc.) 33 | * @param {WebGLBuffer} [glbuffer] A WebGLBuffer to use instead of creating a new one 34 | */ 35 | constructor(gl: GLContext, type: GLenum = gl.UNSIGNED_SHORT, data?: GLsizeiptr | BufferSource, usage: GLenum = gl.STATIC_DRAW, glbuffer? : WebGLBuffer ) { 36 | super(); 37 | 38 | this.gl = gl; 39 | this.usage = usage; 40 | this.buffer = (glbuffer !== undefined ) ? glbuffer : gl.createBuffer(); 41 | 42 | this.type = 0; 43 | this.typeSize = 0; 44 | this.byteLength = 0; 45 | 46 | this.setType(type); 47 | 48 | if (data) { 49 | this.data(data); 50 | } 51 | } 52 | 53 | bind() { 54 | this.gl.bindBuffer(TGT, this.buffer); 55 | } 56 | 57 | /** 58 | * Change the internal type of the index data of the IndexBuffer. 59 | * @param {GLenum} type the number type of the index data (`GL_UNSIGNED_BYTE`, `GL_UNSIGNED_INT`, etc) 60 | */ 61 | setType(type: GLenum) { 62 | this.type = type; 63 | this.typeSize = getComponentSize(type); 64 | } 65 | 66 | data(array: GLsizeiptr | BufferSource) { 67 | const gl = this.gl; 68 | gl.bindBuffer(TGT, this.buffer); 69 | gl.bufferData(TGT, array as any, this.usage); 70 | gl.bindBuffer(TGT, null); 71 | this.byteLength = isBufferSource(array) ? array.byteLength : array; 72 | } 73 | 74 | subData(array: BufferSource, offset: number) { 75 | const gl = this.gl; 76 | gl.bindBuffer(TGT, this.buffer); 77 | gl.bufferSubData(TGT, offset, array); 78 | gl.bindBuffer(TGT, null); 79 | } 80 | 81 | dispose() { 82 | this.gl.deleteBuffer(this.buffer); 83 | } 84 | 85 | /** 86 | * Shortcut to `gl.drawArrays`. 87 | * @param {GLenum} mode The type of primitive to draw (`GL_TRIANGLE`, `GL_POINTS` etc) 88 | * @param {uint} [count=this.length] The number of indices to draw (the full buffer is used if omited) 89 | * @param {uint} [offset=0] The position of the first index to draw 90 | */ 91 | draw(mode: GLenum, count?: number, offset: number = 0) { 92 | count = (count === undefined) ? this.byteLength / this.typeSize : count; 93 | this.gl.drawElements(mode, count, this.type, 0 | offset); 94 | } 95 | } 96 | 97 | 98 | export default IndexBuffer -------------------------------------------------------------------------------- /src/renderbuffer.ts: -------------------------------------------------------------------------------- 1 | import { GLContext, isWebgl2 } from './types'; 2 | 3 | let _UID = 0; 4 | 5 | const RENDERBUFFER = 0x8d41; 6 | 7 | /** 8 | * This class manages renderbuffers. 9 | */ 10 | class RenderBuffer { 11 | /** The webgl context this RenderBuffer belongs to */ 12 | readonly gl: GLContext; 13 | /** The underlying webgl renderbuffer */ 14 | readonly id: WebGLRenderbuffer; 15 | /** The number of samples used for the renderbuffer storage (webgl2 only) */ 16 | readonly samples: number = 0; 17 | /** The pixel internal format of the renderbuffer */ 18 | readonly format: GLenum; 19 | 20 | /** The width of the renderbuffer */ 21 | width: number; 22 | /** The height of the renderbuffer */ 23 | height: number; 24 | 25 | /** Unique id for the RenderBuffer */ 26 | readonly _uid: number; 27 | /** Whether the RenderBuffer is valid or not */ 28 | private _valid: boolean; 29 | 30 | /** 31 | * @param {GLContext} gl The webgl context this RenderBuffer belongs to 32 | * @param {GLenum} [format=GL_DEPTH_COMPONENT16] The pixel internal format of the renderbuffer (`GL_RGB`, `GL_RGBA`, etc.), defaults to `GL_DEPTH_COMPONENT16` 33 | * @param {number} [samples=0] The number of samples to be used for the renderbuffer storage (webgl2 only) 34 | */ 35 | constructor(gl: GLContext, format: GLenum, samples : number = 0) { 36 | this._uid = _UID++; 37 | this.gl = gl; 38 | this.id = gl.createRenderbuffer(); 39 | 40 | if( samples > 0 && isWebgl2(gl) ){ 41 | const maxSamples = gl.getParameter( gl.MAX_SAMPLES ) 42 | this.samples = (samples > maxSamples) ? maxSamples : samples; 43 | } 44 | 45 | this.width = 0; 46 | this.height = 0; 47 | this.format = format || gl.DEPTH_COMPONENT16; 48 | 49 | this._valid = false; 50 | 51 | // Dummy allocation needed 52 | // on some platform (chrome 57, osx, nvidia), gl.framebufferRenderbuffer raise 53 | // an INVALID_OPERATION if a RB is attached before it storage is allocated. 54 | this._storage(); 55 | } 56 | 57 | /** 58 | * Resize the RenderBuffer. 59 | * @param {number} w The new width 60 | * @param {number} h The new height 61 | */ 62 | resize(w: number, h: number) { 63 | if (this.width !== w || this.height !== h) { 64 | this.width = w; 65 | this.height = h; 66 | this._valid = false; 67 | } 68 | } 69 | 70 | /** 71 | * Check renderbuffer for validity and allocate it if invalid. 72 | * This method may leave RENDERBUFFER binding point empty. 73 | */ 74 | allocate() { 75 | if (!this._valid && this.width > 0 && this.height > 0) { 76 | this._storage(); 77 | this._valid = true; 78 | } 79 | } 80 | 81 | /** 82 | * Bind the underlying webgl renderbuffer. 83 | */ 84 | bind() { 85 | this.gl.bindRenderbuffer(RENDERBUFFER, this.id); 86 | } 87 | 88 | /** 89 | * Delete all webgl objects related to this RenderBuffer. 90 | */ 91 | dispose() { 92 | this.gl.deleteRenderbuffer(this.id); 93 | } 94 | 95 | /** 96 | * Allocate webgl renderbuffer with current parameters. 97 | */ 98 | _storage() { 99 | const gl = this.gl; 100 | gl.bindRenderbuffer(RENDERBUFFER, this.id); 101 | if( this.samples > 0 && isWebgl2(gl) ){ 102 | gl.renderbufferStorageMultisample(RENDERBUFFER, this.samples, this.format, this.width, this.height); 103 | } else { 104 | gl.renderbufferStorage(RENDERBUFFER, this.format, this.width, this.height); 105 | } 106 | gl.bindRenderbuffer(RENDERBUFFER, null); 107 | } 108 | } 109 | 110 | 111 | export default RenderBuffer -------------------------------------------------------------------------------- /test/sampler.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import Sampler from '../sampler' 4 | import Texture2D from '../texture-2d' 5 | import Program from '../program' 6 | var expect = require( 'expect.js' ); 7 | 8 | var when = require( 'when' ); 9 | 10 | var testContext = require( './utils/TestContext' ); 11 | var gl = testContext.getContext(); 12 | 13 | function loadImage( img, src ){ 14 | var def = when.defer() 15 | img.onload = function(){ 16 | def.resolve( img ); 17 | } 18 | img.src = src; 19 | return def.promise; 20 | } 21 | 22 | var mireRGB, mireRGBA; 23 | var filltex, filltex16; 24 | 25 | describe( "Sampler @WEBGL2", function(){ 26 | 27 | before(function() { 28 | var vert = require( './glsl/filltex.vert') 29 | var frag = require( './glsl/filltex.frag') 30 | filltex = new Program( gl ); 31 | filltex.compile( vert, frag, "#define UV_MULT 2.0" ); 32 | 33 | filltex16 = new Program( gl ); 34 | filltex16.compile( vert, frag, "#define UV_MULT 17.0" ); 35 | 36 | mireRGB = document.createElement( 'img' ); 37 | mireRGBA = document.createElement( 'img' ); 38 | return when.all( [ 39 | loadImage( mireRGB, 'base/test/assets/mireRGB.png' ), 40 | loadImage( mireRGBA, 'base/test/assets/mireRGBA.png' ), 41 | ]); 42 | }); 43 | 44 | after( function(){ 45 | filltex.dispose() 46 | filltex16.dispose() 47 | }) 48 | 49 | 50 | 51 | 52 | it( "should be exported in nanogl namespace", function(){ 53 | expect( Sampler ).to.be.ok( ); 54 | }); 55 | 56 | it( "creation should leave clean gl state", function(){ 57 | var tex = new Sampler( gl ); 58 | testContext.assertNoError(); 59 | tex.dispose() 60 | }); 61 | 62 | 63 | it( "dispose should leave clean gl state", function(){ 64 | var tex = new Sampler( gl ); 65 | tex.dispose() 66 | testContext.assertNoError(); 67 | }); 68 | 69 | 70 | 71 | it( "should render various filtering", function( ){ 72 | var tex = new Texture2D( gl ); 73 | tex.fromImage( mireRGB, false ); 74 | 75 | var sampler = new Sampler( gl ) 76 | 77 | filltex.bind() 78 | 79 | gl.activeTexture( gl.TEXTURE0 ); 80 | gl.bindTexture( gl.TEXTURE_2D, tex.id ); 81 | gl.uniform1i( filltex.tTex(), 0 ); 82 | 83 | sampler.bind( 0 ) 84 | 85 | // NEAREST 86 | sampler.setFilter( false, false, false ) 87 | sampler.clamp() 88 | 89 | testContext.drawProgram( filltex ); 90 | testContext.testPixel( 16, 3, 0xFFee0000 ) 91 | testContext.testPixel( 48, 3, 0xFF101010 ) 92 | 93 | // LINEAR 94 | sampler.setFilter( true, false, false ) 95 | 96 | testContext.drawProgram( filltex ); 97 | testContext.testPixel( 16, 3, 0xFF955900 ) 98 | testContext.testPixel( 48, 3, 0xFF630A0A ) 99 | 100 | // 16 test 101 | // filltex16.bind() 102 | // gl.uniform1i( filltex16.tTex(), 0 ); 103 | 104 | // tex.setFilter( false, false, false ) 105 | // tex.setRepeat( false ) 106 | // testContext.drawProgram( filltex16 ); 107 | // testContext.testPixel( 0, 0, 0xFF00EE00 ) 108 | 109 | // MIPMAP 110 | gl.generateMipmap( gl.TEXTURE_2D, tex.id ); 111 | sampler.setFilter( false, true, false ) 112 | testContext.drawProgram( filltex16 ); 113 | testContext.testPixel( 0, 0, 0xFF7b4004 ) 114 | testContext.testPixel( 2, 0, 0xFF777700 ) 115 | 116 | 117 | // MIPMAP LINEAR 118 | sampler.setFilter( false, true, true ) 119 | testContext.drawProgram( filltex16 ); 120 | testContext.testPixel( 0, 0, 0xFF794209 ) 121 | testContext.testPixel( 2, 0, 0xFF767405 ) 122 | 123 | // LINEAR MIPMAP LINEAR 124 | sampler.setFilter( true, true, true ) 125 | testContext.drawProgram( filltex16 ); 126 | testContext.testPixel( 0, 0, 0xff794509 ) 127 | testContext.testPixel( 2, 0, 0xff756c0c ) 128 | 129 | testContext.assertNoError(); 130 | 131 | gl.bindSampler( 0, null ); 132 | }); 133 | 134 | 135 | 136 | 137 | 138 | }); 139 | -------------------------------------------------------------------------------- /src/basebuffer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the base for all buffer classes. 3 | */ 4 | abstract class BaseBuffer { 5 | /** 6 | * Bind the underlying webgl buffer. 7 | */ 8 | abstract bind(): void; 9 | 10 | /** 11 | * Delete all webgl objects related to this BaseBuffer. 12 | */ 13 | abstract dispose(): void; 14 | 15 | /** 16 | * Fill the webgl buffer with the given data. You can also pass a number instead to allocate the buffer to a given size. 17 | * @param {BufferSource|GLsizeiptr} array The data to use, or a size 18 | */ 19 | abstract data(array: BufferSource | GLsizeiptr): void; 20 | 21 | /** 22 | * Set part of the buffer with the given data, starting at an offset. 23 | * @param {BufferSource} array The data to use 24 | * @param {number} offset The offset (in bytes) where the data should start to be written 25 | */ 26 | abstract subData(array: BufferSource, offset: number): void; 27 | 28 | /** 29 | * Shortcut to `gl.drawArrays`. 30 | * @param {GLenum} mode The type of primitive to draw (`GL_TRIANGLE`, `GL_POINTS` etc) 31 | * @param {number} [count] The number of elements to draw (the full buffer is used if omited) 32 | * @param {number} [offset=0] The position in the buffer to start from 33 | */ 34 | abstract draw(mode: GLenum, count?: number, offset?: number): void; 35 | 36 | /** 37 | * Shortcut to `gl.drawArrays(gl.POINTS, offset, count)`. 38 | * @param {number} [count] The number of elements to draw (the full buffer is used if omited) 39 | * @param {number} [offset=0] The position in the buffer to start from 40 | */ 41 | drawPoints(count?: number, offset?: number) { 42 | this.draw(0 /* POINTS */, count, offset); 43 | } 44 | 45 | /** 46 | * Shortcut to `gl.drawArrays(gl.LINES, offset, count)`. 47 | * @param {number} [count] The number of elements to draw (the full buffer is used if omited) 48 | * @param {number} [offset=0] The position in the buffer to start from 49 | */ 50 | drawLines(count?: number, offset?: number) { 51 | this.draw(1 /* LINES */, count, offset); 52 | } 53 | 54 | /** 55 | * Shortcut to `gl.drawArrays(gl.LINE_LOOP, offset, count)`. 56 | * @param {number} [count] The number of elements to draw (the full buffer is used if omited) 57 | * @param {number} [offset=0] The position in the buffer to start from 58 | */ 59 | drawLineLoop(count?: number, offset?: number) { 60 | this.draw(2 /* LINE_LOOP */, count, offset); 61 | } 62 | 63 | /** 64 | * Shortcut to `gl.drawArrays(gl.LINE_STRIP, offset, count)`. 65 | * @param {number} [count] The number of elements to draw (the full buffer is used if omited) 66 | * @param {number} [offset=0] The position in the buffer to start from 67 | */ 68 | drawLineStrip(count?: number, offset?: number) { 69 | this.draw(3 /* LINE_STRIP */, count, offset); 70 | } 71 | 72 | /** 73 | * Shortcut to `gl.drawArrays(gl.TRIANGLES, offset, count)`. 74 | * @param {number} [count] The number of elements to draw (the full buffer is used if omited) 75 | * @param {number} [offset=0] The position in the buffer to start from 76 | */ 77 | drawTriangles(count?: number, offset?: number) { 78 | this.draw(4 /* TRIANGLES */, count, offset); 79 | } 80 | 81 | /** 82 | * Shortcut to `gl.drawArrays(gl.TRIANGLE_STRIP, offset, count)`. 83 | * @param {number} [count] The number of elements to draw (the full buffer is used if omited) 84 | * @param {number} [offset=0] The position in the buffer to start from 85 | */ 86 | drawTriangleStrip(count?: number, offset?: number) { 87 | this.draw(5 /* TRIANGLE_STRIP */, count, offset); 88 | } 89 | 90 | /** 91 | * Shortcut to `gl.drawArrays(gl.TRIANGLE_FAN, offset, count)`. 92 | * @param {number} [count] The number of elements to draw (the full buffer is used if omited) 93 | * @param {number} [offset=0] The position in the buffer to start from 94 | */ 95 | drawTriangleFan(count?: number, offset?: number) { 96 | this.draw(6 /* TRIANGLE_FAN */, count, offset); 97 | } 98 | } 99 | 100 | 101 | export default BaseBuffer -------------------------------------------------------------------------------- /test/arraybuffer.js: -------------------------------------------------------------------------------- 1 | import ArrayBuffer from '../arraybuffer' 2 | import Program from '../program' 3 | 4 | var expect = require( 'expect.js' ); 5 | 6 | var testContext = require( './utils/TestContext' ); 7 | var gl = testContext.getContext(); 8 | 9 | var vertexData = new Uint8Array( 24 * 4 ); 10 | var fVertexData = new Float32Array( vertexData.buffer ); 11 | 12 | fVertexData.set( [ -1, -1, 0, 0, 0 ], 0 ); 13 | fVertexData.set( [ 1, -1, 0, 1, 0 ], 6 ); 14 | fVertexData.set( [ -1, 1, 0, 0, 1 ], 12 ); 15 | fVertexData.set( [ 1, 1, 0, 1, 1 ], 18 ); 16 | 17 | vertexData.set( [0xFF, 0, 0, 0xFF ], 20 + 0 ); 18 | vertexData.set( [0xFF, 0xFF, 0, 0xFF ], 20 + 24 ); 19 | vertexData.set( [0xFF, 0xFF, 0, 0xFF ], 20 + 48 ); 20 | vertexData.set( [0, 0, 0xFF, 0xFF ], 20 + 72 ); 21 | 22 | describe( "ArrayBuffer", function(){ 23 | 24 | it( "should be exported", function(){ 25 | 26 | expect( ArrayBuffer ).to.be.ok( ); 27 | 28 | }); 29 | 30 | it( "constructor should return instance", function(){ 31 | 32 | var p = new ArrayBuffer( gl ); 33 | expect( p ).to.be.ok( ); 34 | expect( p.gl ).to.be.ok( ); 35 | 36 | }); 37 | 38 | 39 | it( "ctor should leave clean state", function(){ 40 | var p = new ArrayBuffer( gl ); 41 | testContext.assertNoError(); 42 | }); 43 | 44 | 45 | it( "dispose should leave clean state", function(){ 46 | var p = new ArrayBuffer( gl ); 47 | p.dispose(); 48 | testContext.assertNoError(); 49 | }); 50 | 51 | 52 | it( "data( array ) should leave clean state", function(){ 53 | var p = new ArrayBuffer( gl ); 54 | p.data( vertexData ); 55 | testContext.assertNoError(); 56 | }); 57 | 58 | 59 | it( "data( size ) should leave clean state", function(){ 60 | var p = new ArrayBuffer( gl ); 61 | p.data( 64 ); 62 | testContext.assertNoError(); 63 | }); 64 | 65 | 66 | it( "subData() should leave clean state", function(){ 67 | var p = new ArrayBuffer( gl ); 68 | p.data( vertexData ); 69 | p.subData( new Float32Array([2, 3, 4]), 24) 70 | testContext.assertNoError(); 71 | }); 72 | 73 | 74 | it( "should have correct stride and lenght", function(){ 75 | var b = new ArrayBuffer( gl ); 76 | b.data( vertexData ); 77 | 78 | b.attrib( 'aPosition', 3, gl.FLOAT ); 79 | b.attrib( 'aTexCoord', 2, gl.FLOAT ); 80 | b.attrib( 'aColor', 4, gl.UNSIGNED_BYTE, true ); 81 | 82 | expect( b.stride ).to.be.equal( 24 ) 83 | expect( b.length ).to.be.equal( 4 ) 84 | 85 | }); 86 | 87 | 88 | 89 | it( "should set attribPointer", function(){ 90 | var b = new ArrayBuffer( gl ); 91 | b.data( vertexData ); 92 | 93 | testContext.assertNoError(); 94 | 95 | var p = new Program( gl ); 96 | p.compile( 97 | require( './glsl/test_uvec3_array.vert'), 98 | require( './glsl/test_uvec3_array.frag') 99 | ); 100 | p.use() 101 | 102 | b.attrib( 'aPosition', 3, gl.FLOAT ); 103 | b.attrib( 'aTexCoord', 2, gl.FLOAT ); 104 | b.attrib( 'aColor', 4, gl.UNSIGNED_BYTE, true ); 105 | 106 | b.attribPointer( p ); 107 | testContext.assertNoError(); 108 | 109 | }); 110 | 111 | 112 | 113 | 114 | describe( "should render", function(){ 115 | 116 | var p = new Program( gl ); 117 | p.compile( 118 | require( './glsl/test_arraybuffer.vert'), 119 | require( './glsl/test_arraybuffer.frag') 120 | ); 121 | 122 | testContext.assertNoError(); 123 | 124 | 125 | it( "A", function(){ 126 | 127 | 128 | p.use(); 129 | var b = new ArrayBuffer( gl ); 130 | b.data( vertexData ); 131 | b.attrib( 'aPosition', 3, gl.FLOAT ); 132 | b.attrib( 'aTexCoord', 2, gl.FLOAT ); 133 | b.attrib( 'aColor', 4, gl.UNSIGNED_BYTE, true ); 134 | b.attribPointer( p ); 135 | b.drawTriangleStrip(); 136 | testContext.testPixel( 32, 32, 0xfffbfb81 ) 137 | testContext.testPixel( 0, 0, 0xffff0402 ) 138 | testContext.assertNoError(); 139 | }); 140 | 141 | it( "after subdata", function(){ 142 | p.use(); 143 | var b = new ArrayBuffer( gl ); 144 | b.data( vertexData ); 145 | b.subData( new Uint8Array([128, 128, 128, 128]), 24 ); 146 | b.attrib( 'aPosition', 3, gl.FLOAT ); 147 | b.attrib( 'aTexCoord', 2, gl.FLOAT ); 148 | b.attrib( 'aColor', 4, gl.UNSIGNED_BYTE, true ); 149 | b.attribPointer( p ); 150 | b.drawTriangleStrip(); 151 | testContext.testPixel( 32, 32, 0xffbcbcc0 ) 152 | testContext.testPixel( 0, 0, 0xffff0402 ) 153 | testContext.assertNoError(); 154 | }); 155 | 156 | }); 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | }); -------------------------------------------------------------------------------- /src/texture-base.ts: -------------------------------------------------------------------------------- 1 | import { GLContext } from './types'; 2 | import { getTextureFiltering } from './utils'; 3 | import TextureCube from './texture-cube'; 4 | import Texture2D from './texture-2d'; 5 | 6 | let _UID = 0; 7 | 8 | /** The types of webgl textures. */ 9 | export enum TextureType { 10 | NONE = 0 , 11 | TEXTURE_2D = 0x0de1, 12 | TEXTURE_2D_ARRAY = 0x8C1A, 13 | TEXTURE_CUBE = 0x8513, 14 | TEXTURE_3D = 0x806F, 15 | } 16 | 17 | /** A nanogl texture. */ 18 | export type Texture = TextureCube | Texture2D; 19 | 20 | /** 21 | * This class is the base for all texture classes. 22 | */ 23 | export default abstract class AbstractTexture { 24 | /** The type of webgl texture (`TEXTURE_2D`, `TEXTURE_CUBE`, etc) */ 25 | readonly textureType : TextureType = TextureType.NONE; 26 | 27 | /** The webgl context this Texture belongs to */ 28 | readonly gl: GLContext; 29 | /** The underlying webgl texture */ 30 | readonly id: WebGLTexture; 31 | 32 | /** The width of the texture */ 33 | width : number; 34 | /** The height of the texture */ 35 | height: number; 36 | 37 | /** The pixel format of the texture */ 38 | format : GLenum = 0; 39 | /** The pixel internal format of the texture */ 40 | internal: GLenum = 0; 41 | /** The pixel data type of the texture */ 42 | type : GLenum = 0; 43 | 44 | /** Unique id for the Texture */ 45 | readonly _uid: number; 46 | 47 | /** The binding point for this Texture */ 48 | abstract _target : GLenum 49 | 50 | /** 51 | * @param {GLContext} gl The webgl context this Texture belongs to 52 | * @param {GLenum} [format=GL_RGB] The pixel format of the texture (`GL_RGB`, `GL_RGBA`, etc.), defaults to `GL_RGB` 53 | * @param {GLenum} [type=GL_UNSIGNED_BYTE] The pixel data type of the texture (`GL_UNSIGNED_BYTE`, `GL_FLOAT`, etc.), defaults to `GL_UNSIGNED_BYTE` 54 | * @param {GLenum} [internal=format] The pixel internal format of the texture, defaults to the `format` parameter value 55 | */ 56 | constructor(gl: GLContext, format?: GLenum, type?: GLenum, internal?: GLenum) { 57 | this._uid = _UID++; 58 | this.gl = gl; 59 | this.id = gl.createTexture(); 60 | this.width = 0; 61 | this.height = 0; 62 | this.setFormat(format, type, internal); 63 | } 64 | 65 | /** 66 | * Define underlying format, internal format and data type of the texture. 67 | * @param {GLenum} [format=GL_RGB] The pixel format of the texture (`GL_RGB`, `GL_RGBA`, etc.), defaults to `GL_RGB` 68 | * @param {GLenum} [type=GL_UNSIGNED_BYTE] The pixel data type of the texture (`GL_UNSIGNED_BYTE`, `GL_FLOAT`, etc.), defaults to `GL_UNSIGNED_BYTE` 69 | * @param {GLenum} [internal=this.format] The pixel internal format of the texture, defaults to the `format` parameter value 70 | */ 71 | setFormat(format?: GLenum, type?: GLenum, internal?: GLenum) { 72 | this.format = format || this.gl.RGB; 73 | this.internal = internal || this.format; 74 | this.type = type || this.gl.UNSIGNED_BYTE; 75 | } 76 | 77 | /** 78 | * Bind the underlying webgl texture. 79 | * @param {number} [unit] The texture unit to make active before binding 80 | */ 81 | bind(unit?: number) { 82 | const gl = this.gl; 83 | if (unit !== undefined) { 84 | gl.activeTexture(gl.TEXTURE0 + (0 | unit)); 85 | } 86 | gl.bindTexture(this._target, this.id); 87 | } 88 | 89 | /** 90 | * Delete all webgl objects related to this Texture. 91 | */ 92 | dispose() { 93 | this.gl.deleteTexture(this.id); 94 | } 95 | 96 | /** 97 | * Change the filtering parameters. 98 | * @param {boolean} [smooth=false] Use linear filtering or not 99 | * @param {boolean} [mipmap=false] Enable mipmapping or not 100 | * @param {boolean} [miplinear=false] Use linear mipmapping or not 101 | */ 102 | setFilter(smooth: boolean = false, mipmap: boolean = false, miplinear: boolean = false) : this { 103 | const gl = this.gl; 104 | gl.texParameteri(this._target, gl.TEXTURE_MAG_FILTER, getTextureFiltering(!!smooth, false, false)); 105 | gl.texParameteri(this._target, gl.TEXTURE_MIN_FILTER, getTextureFiltering(!!smooth, !!mipmap, !!miplinear)); 106 | return this; 107 | } 108 | 109 | /** 110 | * Make texture repeat : 111 | * Set the `WRAP_S` and `WRAP_T` properties to `GL_REPEAT`. 112 | */ 113 | repeat() :this { 114 | this.wrap(this.gl.REPEAT); 115 | return this; 116 | } 117 | 118 | /** 119 | * Make texture clamp : 120 | * Set the `WRAP_S` and `WRAP_T` properties to `GL_CLAMP_TO_EDGE`. 121 | */ 122 | clamp() : this { 123 | this.wrap(this.gl.CLAMP_TO_EDGE); 124 | return this; 125 | } 126 | 127 | /** 128 | * Make texture mirror : 129 | * Set the `WRAP_S` and `WRAP_T` properties to `GL_MIRRORED_REPEAT`. 130 | */ 131 | mirror() : this { 132 | this.wrap(this.gl.MIRRORED_REPEAT); 133 | return this; 134 | } 135 | 136 | /** 137 | * Set the `WRAP_S` and `WRAP_T` properties to the given value. 138 | * @param {GLenum} wrap The wrap value to use (`GL_REPEAT`, `GL_CLAMP_TO_EDGE`, etc.) 139 | */ 140 | wrap(wrap: GLenum) : this { 141 | const gl = this.gl; 142 | gl.texParameteri(this._target, gl.TEXTURE_WRAP_S, wrap); 143 | gl.texParameteri(this._target, gl.TEXTURE_WRAP_T, wrap); 144 | return this; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/arraybuffer.ts: -------------------------------------------------------------------------------- 1 | import Program from './program' 2 | import BaseBuffer from './basebuffer' 3 | import { GLContext } from './types'; 4 | import { getComponentSize, isBufferSource } from './utils'; 5 | 6 | 7 | /* 8 | * GL_ARRAY_BUFFER */ 9 | const GL_ARRAY_BUFFER = 0x8892; 10 | 11 | /** The definition for an attribute in a buffer. */ 12 | export interface AttributeDef { 13 | /** The attribute name */ 14 | name: string; 15 | /** The type of data (`GL_FLOAT`, `GL_SHORT`, etc.) */ 16 | type: GLenum; 17 | /** The size of the attribute (`1` for a number, `2` for a vec2, etc.) */ 18 | size: number; 19 | /** The offset in bytes of the attribute data in the buffer */ 20 | offset: number; 21 | /** Whethere the data must be normalized or not */ 22 | normalize: boolean; 23 | /** 24 | * The number of bytes for each vertex in the buffer. 25 | * If not set, the stride of the buffer is used. 26 | */ 27 | stride: number; 28 | } 29 | 30 | /** 31 | * This class manages ARRAY_BUFFER type buffers. 32 | * @extends {BaseBuffer} 33 | */ 34 | class ArrayBuffer extends BaseBuffer { 35 | /** The webgl context this ArrayBuffer belongs to */ 36 | readonly gl: GLContext; 37 | /** The webgl buffer this ArrayBuffer writes to */ 38 | readonly buffer: WebGLBuffer; 39 | 40 | /** The usage hint for this buffer */ 41 | usage: GLenum; 42 | /** The number of bytes for each vertex in this buffer */ 43 | stride : number; 44 | /** The length in bytes of the buffer */ 45 | byteLength: number; 46 | /** The number of vertices in the buffer data */ 47 | length : number; 48 | /** The attributes declared for this buffer */ 49 | attribs: AttributeDef[]; 50 | 51 | /** 52 | * @param {GLContext} gl The webgl context this ArrayBuffer belongs to 53 | * @param {BufferSource|GLsizeiptr} [data] The data to fill the buffer with, or the size (in bytes) 54 | * @param {GLenum} [usage=GL_STATIC_DRAW] The usage hint for this buffer (`GL_STATIC_DRAW`, `GL_DYNAMIC_DRAW`, etc.) 55 | * @param {WebGLBuffer} [glbuffer] A WebGLBuffer to use instead of creating a new one 56 | */ 57 | constructor(gl: GLContext, data?: GLsizeiptr | BufferSource, usage: GLenum = gl.STATIC_DRAW, glbuffer? : WebGLBuffer ) { 58 | super(); 59 | 60 | this.gl = gl; 61 | this.usage = usage; 62 | 63 | this.buffer = (glbuffer !== undefined ) ? glbuffer : gl.createBuffer(); 64 | 65 | this.attribs = []; 66 | this.stride = 0; 67 | this.byteLength = 0; 68 | this.length = 0; 69 | 70 | if (data) { 71 | this.data(data); 72 | } 73 | } 74 | 75 | bind() { 76 | this.gl.bindBuffer(GL_ARRAY_BUFFER, this.buffer); 77 | } 78 | 79 | /** 80 | * Add attribute declaration for this buffer. Once the attributes declared, the buffer can be linked to 81 | * the program's attributes using {@link ArrayBuffer#attribPointer}. 82 | * @param {string} name The name of the program's attribute 83 | * @param {number} size The size of the attribute (`1` for a number, `2` for a vec2, etc.) 84 | * @param {GLenum} type The type of data (`GL_FLOAT`, `GL_SHORT`, etc.) 85 | * @param {boolean} [normalize=false] Indicate if the data must be normalized 86 | */ 87 | attrib(name: string, size: number, type: GLenum, normalize: boolean = false): this { 88 | this.attribs.push({ 89 | name, 90 | type: 0 | type, 91 | size: 0 | size, 92 | normalize, 93 | offset: this.stride, 94 | stride:0 95 | }); 96 | this.stride += getComponentSize(type) * size; 97 | this._computeLength(); 98 | return this; 99 | } 100 | 101 | data(array: BufferSource | GLsizeiptr) { 102 | const gl = this.gl; 103 | gl.bindBuffer(GL_ARRAY_BUFFER, this.buffer); 104 | gl.bufferData(GL_ARRAY_BUFFER, array as any, this.usage); 105 | gl.bindBuffer(GL_ARRAY_BUFFER, null); 106 | 107 | this.byteLength = isBufferSource(array) ? array.byteLength : array; 108 | this._computeLength(); 109 | } 110 | 111 | subData(array: BufferSource, offset: number) { 112 | const gl = this.gl; 113 | gl.bindBuffer(GL_ARRAY_BUFFER, this.buffer); 114 | gl.bufferSubData(GL_ARRAY_BUFFER, offset, array); 115 | gl.bindBuffer(GL_ARRAY_BUFFER, null); 116 | } 117 | 118 | /** 119 | * Link the given program attributes to this buffer. You should first declare attributes using {@link ArrayBuffer#attrib} 120 | * before calling this method. 121 | * @param {Program} program The program to link 122 | */ 123 | attribPointer(program: Program) { 124 | const gl = this.gl; 125 | gl.bindBuffer(GL_ARRAY_BUFFER, this.buffer); 126 | 127 | for (var i = 0; i < this.attribs.length; i++) { 128 | var attrib = this.attribs[i]; 129 | 130 | if (program[attrib.name] !== undefined) { 131 | var aLocation = program[attrib.name](); 132 | gl.enableVertexAttribArray(aLocation); 133 | gl.vertexAttribPointer(aLocation, attrib.size, attrib.type, attrib.normalize, attrib.stride || this.stride, attrib.offset); 134 | } 135 | } 136 | } 137 | 138 | /** 139 | * Shortcut to `gl.drawArrays`. 140 | * @param {GLenum} mode The type of primitive to draw (`GL_TRIANGLE`, `GL_POINTS` etc) 141 | * @param {uint} [count=this.length] The number of vertices to draw (the full buffer is used if omited) 142 | * @param {uint} [offset=0] The position of the first vertex to draw 143 | */ 144 | draw(mode: GLenum, count: number = this.length, offset: number = 0) { 145 | this.gl.drawArrays(mode, offset, 0|count); 146 | } 147 | 148 | dispose() { 149 | this.gl.deleteBuffer(this.buffer); 150 | } 151 | 152 | /** 153 | * Compute the number of vertices in the buffer data. 154 | */ 155 | _computeLength() { 156 | if (this.stride > 0) { 157 | this.length = this.byteLength / this.stride; 158 | } 159 | } 160 | } 161 | 162 | 163 | export default ArrayBuffer -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Dec 19 2015 12:50:43 GMT+0100 (CET) 3 | 4 | 5 | module.exports = function(config) { 6 | var glversion = 1; 7 | 8 | 9 | if( config.webglVersion !== undefined ){ 10 | glversion = config.webglVersion; 11 | } 12 | 13 | 14 | var invgrep; 15 | if( glversion === 1 ){ 16 | invgrep = '@WEBGL2'; 17 | } 18 | else { 19 | invgrep = '@WEBGL1'; 20 | } 21 | 22 | 23 | config.set({ 24 | 25 | // base path that will be used to resolve all patterns (eg. files, exclude) 26 | basePath: '', 27 | 28 | 29 | // frameworks to use 30 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 31 | frameworks: ['browserify', 'mocha'], 32 | 33 | // mocha custom option 34 | client: { 35 | webgl_version: glversion, 36 | mocha:{ 37 | grep : invgrep, 38 | invert:true 39 | } 40 | }, 41 | 42 | // list of files / patterns to load in the browser 43 | files: [ 44 | 'test/*.js', 45 | {pattern: 'test/assets/*.*', watched: false, included: false, served: true, nocache: false} 46 | ], 47 | 48 | proxies: { 49 | '/assets/': '/base/test/assets/' 50 | }, 51 | 52 | 53 | // list of files to exclude 54 | exclude: [ 55 | ], 56 | 57 | 58 | // preprocess matching files before serving them to the browser 59 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 60 | preprocessors: { 61 | 'test/*.js': [ 'browserify' ] 62 | }, 63 | 64 | browserify: { 65 | debug: true, 66 | transform: [ 67 | ["babelify", { "presets": ["@babel/preset-env"]}], 68 | ['stringify', {'extensions': ['.vert', '.frag']}] 69 | ] 70 | }, 71 | 72 | // test results reporter to use 73 | // possible values: 'dots', 'progress' 74 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 75 | reporters: ['dots'], 76 | 77 | 78 | // web server port 79 | port: 9876, 80 | 81 | 82 | // enable / disable colors in the output (reporters and logs) 83 | colors: true, 84 | 85 | 86 | // level of logging 87 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 88 | logLevel: config.LOG_ERROR, 89 | 90 | 91 | // enable / disable watching file and executing tests whenever any file changes 92 | autoWatch: true, 93 | 94 | 95 | // start these browsers 96 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 97 | browsers: ['Chrome'],//, 'Firefox', 'Safari'], 98 | 99 | 100 | // Continuous Integration mode 101 | // if true, Karma captures browsers, runs the tests and exits 102 | singleRun: false, 103 | 104 | // Concurrency level 105 | // how many browser should be started simultanous 106 | concurrency: Infinity, 107 | 108 | 109 | // SL and travis config 110 | sauceLabs: { 111 | startConnect: true, 112 | testName: 'nanogl unit tests' 113 | }, 114 | 115 | // define SL browsers 116 | customLaunchers: { 117 | 118 | // OSX Maverick 119 | 120 | 'SL_Chrome78_OSX10_13': { 121 | base: 'SauceLabs', 122 | browserName: 'chrome', 123 | version: '78.0', 124 | platform: 'macOS 10.13', 125 | webgl2:true 126 | }, 127 | // 'SL_Firefox38_OSX10_10': { 128 | // base: 'SauceLabs', 129 | // browserName: 'firefox', 130 | // version: '38', 131 | // platform: 'OS X 10.10' 132 | // }, 133 | // 'SL_Firefox70_OSX10_13': { 134 | // base: 'SauceLabs', 135 | // browserName: 'firefox', 136 | // version: '70.0', 137 | // platform: 'macOS 10.13' 138 | // }, 139 | // 'SL_Safari': { 140 | // base: 'SauceLabs', 141 | // browserName: 'safari', 142 | // platform: 'OS X 10.10', 143 | // version: '8' 144 | // }, 145 | // 'SL_Safari_12': { 146 | // base: 'SauceLabs', 147 | // browserName: 'safari', 148 | // platform: 'macOS 10.14', 149 | // version: '12.0' 150 | // }, 151 | 152 | //Win 8.1 153 | 'SL_Chrome78_WIN10': { 154 | base: 'SauceLabs', 155 | browserName: 'chrome', 156 | version: '78.0', 157 | platform: 'Windows 10', 158 | webgl2:true 159 | }, 160 | 161 | 162 | 'SL_Chrome65_WIN10': { 163 | base: 'SauceLabs', 164 | browserName: 'chrome', 165 | version: '65.0', 166 | platform: 'Windows 10', 167 | webgl2:true 168 | }, 169 | 170 | // 'SL_Firefox_WIN81': { 171 | // base: 'SauceLabs', 172 | // browserName: 'firefox', 173 | // version: '27', 174 | // platform: 'Windows 8.1' 175 | // }, 176 | 177 | //Linux 178 | // 'SL_Chrome_NIX': { 179 | // base: 'SauceLabs', 180 | // browserName: 'chrome', 181 | // version: '32', 182 | // platform: 'Linux' 183 | // }, 184 | // 'SL_Firefox_NIX': { 185 | // base: 'SauceLabs', 186 | // browserName: 'firefox', 187 | // version: '27', 188 | // platform: 'Linux' 189 | // }, 190 | // 'SL_Chrome_OSX9': { 191 | // base: 'SauceLabs', 192 | // browserName: 'chrome', 193 | // version: '31', 194 | // platform: 'OS X 10.9' 195 | // }, 196 | 'SL_win10_Edge': { 197 | base: 'SauceLabs', 198 | browserName: 'MicrosoftEdge', 199 | platform: 'Windows 10', 200 | version: '18.17763' 201 | }, 202 | 'SL_win10_FF': { 203 | base: 'SauceLabs', 204 | browserName: 'firefox', 205 | platform: 'Windows 10', 206 | version: '70.0' 207 | }, 208 | }, 209 | }); 210 | 211 | if( process.env.TRAVIS ) { 212 | 213 | var browsers = []; 214 | for( var browser in config.customLaunchers ){ 215 | 216 | // skip browser not supporting webgl2 217 | var bdata = config.customLaunchers[browser]; 218 | if( glversion !== 1 && bdata.webgl2 !== true ) 219 | continue; 220 | 221 | browsers.push( browser ); 222 | } 223 | console.log( browsers ); 224 | config.browsers = browsers; 225 | 226 | config.autoWatch = false; 227 | config.singleRun = true; 228 | 229 | } 230 | }; 231 | -------------------------------------------------------------------------------- /fbo.js: -------------------------------------------------------------------------------- 1 | import Texture2D from './texture-2d'; 2 | import RenderBuffer from './renderbuffer'; 3 | import { isWebgl2 } from './types'; 4 | function isTexture(target) { 5 | return target.id instanceof WebGLTexture; 6 | } 7 | function assertIsTexture(target, msg) { 8 | if (target === null || !isTexture(target)) { 9 | throw new Error(msg); 10 | } 11 | } 12 | export class Attachment { 13 | constructor(target) { 14 | this.target = target; 15 | this.level = 0; 16 | this._isTexture = isTexture(target); 17 | } 18 | isTexture() { 19 | return this._isTexture; 20 | } 21 | _resize(w, h) { 22 | if (w > 0 && h > 0) { 23 | if (isTexture(this.target)) { 24 | this.target.fromData(w, h, null); 25 | } 26 | else { 27 | this.target.resize(w, h); 28 | this.target.allocate(); 29 | } 30 | } 31 | } 32 | _attach(bindingPoint) { 33 | var gl = this.target.gl; 34 | if (this._isTexture) { 35 | gl.framebufferTexture2D(gl.FRAMEBUFFER, bindingPoint, gl.TEXTURE_2D, this.target.id, this.level); 36 | } 37 | else { 38 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, bindingPoint, gl.RENDERBUFFER, this.target.id); 39 | } 40 | } 41 | _detach(bindingPoint) { 42 | var gl = this.target.gl; 43 | if (this._isTexture) { 44 | gl.framebufferTexture2D(gl.FRAMEBUFFER, bindingPoint, gl.TEXTURE_2D, null, this.level); 45 | } 46 | else { 47 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, bindingPoint, gl.RENDERBUFFER, null); 48 | } 49 | } 50 | dispose() { 51 | this.target.dispose(); 52 | } 53 | } 54 | class Fbo { 55 | constructor(gl) { 56 | this.gl = gl; 57 | this.width = 0; 58 | this.height = 0; 59 | this.fbo = gl.createFramebuffer(); 60 | this.bind(); 61 | this.attachments = {}; 62 | this.attachmentsList = []; 63 | } 64 | attach(bindingPoint, res) { 65 | const attachment = new Attachment(res); 66 | bindingPoint = 0 | bindingPoint; 67 | this.detach(bindingPoint); 68 | this.attachments[bindingPoint.toString()] = attachment; 69 | this.attachmentsList.push(attachment); 70 | attachment._resize(this.width, this.height); 71 | attachment._attach(bindingPoint); 72 | return attachment; 73 | } 74 | detach(bindingPoint) { 75 | const att = this.attachments[bindingPoint.toString()]; 76 | if (att !== undefined) { 77 | const index = this.attachmentsList.indexOf(att); 78 | this.attachmentsList.splice(index, 1); 79 | att._detach(bindingPoint); 80 | } 81 | delete this.attachments[bindingPoint.toString()]; 82 | } 83 | getAttachment(bindingPoint) { 84 | const att = this.attachments[bindingPoint.toString()]; 85 | if (att !== undefined) { 86 | return att; 87 | } 88 | return null; 89 | } 90 | getColor(index = 0) { 91 | const att = this.getAttachment(0x8ce0 + index); 92 | return att ? att.target : null; 93 | } 94 | getColorTexture(index = 0) { 95 | const res = this.getColor(index); 96 | assertIsTexture(res, `Color attachment ${index} is not a texture.`); 97 | return res; 98 | } 99 | getDepth() { 100 | const att = this.getAttachment(0x8d00) || 101 | this.getAttachment(0x8d20) || 102 | this.getAttachment(0x821a); 103 | return att ? att.target : null; 104 | } 105 | attachColor(format, type, internal) { 106 | const t = new Texture2D(this.gl, format, type, internal); 107 | return this.attach(0x8ce0, t); 108 | } 109 | attachDepth(depth = true, stencil = false, useTexture = false) { 110 | let attachment; 111 | if (useTexture) { 112 | const cfg = dsTextureConfig(this.gl, stencil); 113 | attachment = new Texture2D(this.gl, cfg.format, cfg.type, cfg.internal); 114 | } 115 | else { 116 | attachment = new RenderBuffer(this.gl, dsRenderbufferStorage(depth, stencil)); 117 | } 118 | return this.attach(dsAttachmentPoint(depth, stencil), attachment); 119 | } 120 | resize(w, h) { 121 | if (this.width !== w || this.height !== h) { 122 | this.width = w | 0; 123 | this.height = h | 0; 124 | this._allocate(); 125 | } 126 | } 127 | bind() { 128 | const gl = this.gl; 129 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo); 130 | } 131 | clear() { 132 | this.gl.clear(0x4500); 133 | } 134 | defaultViewport() { 135 | this.gl.viewport(0, 0, this.width, this.height); 136 | } 137 | isValid() { 138 | const gl = this.gl; 139 | return gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE; 140 | } 141 | dispose() { 142 | const gl = this.gl; 143 | gl.deleteFramebuffer(this.fbo); 144 | for (var i = 0; i < this.attachmentsList.length; i++) { 145 | this.attachmentsList[i].dispose(); 146 | } 147 | this.attachmentsList.length = 0; 148 | this.attachments = {}; 149 | } 150 | _allocate() { 151 | for (var attachment of this.attachmentsList) { 152 | attachment._resize(this.width, this.height); 153 | } 154 | } 155 | } 156 | function dsFlag(depth, stencil) { 157 | return depth | (stencil << 1); 158 | } 159 | function dsAttachmentPoint(depth, stencil) { 160 | switch (dsFlag(depth, stencil)) { 161 | case 1: 162 | return 0x8d00; 163 | case 2: 164 | return 0x8d20; 165 | case 3: 166 | return 0x821a; 167 | default: 168 | return 0; 169 | } 170 | } 171 | function dsRenderbufferStorage(depth, stencil) { 172 | switch (dsFlag(depth, stencil)) { 173 | case 1: 174 | return 0x81a5; 175 | case 2: 176 | return 0x8d48; 177 | case 3: 178 | return 0x84f9; 179 | default: 180 | return 0; 181 | } 182 | } 183 | function dsTextureConfig(gl, stencil) { 184 | if (stencil) { 185 | return { format: 0x84f9, type: 0x84fa, internal: isWebgl2(gl) ? gl.DEPTH24_STENCIL8 : gl.DEPTH_STENCIL }; 186 | } 187 | return { format: 0x1902, type: 0x1405, internal: isWebgl2(gl) ? gl.DEPTH_COMPONENT24 : gl.DEPTH_COMPONENT }; 188 | } 189 | export default Fbo; 190 | -------------------------------------------------------------------------------- /test/texture.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import Texture2D from '../texture-2d' 4 | import Program from '../program' 5 | 6 | var expect = require( 'expect.js' ); 7 | 8 | var when = require( 'when' ); 9 | 10 | var testContext = require( './utils/TestContext' ); 11 | var gl = testContext.getContext(); 12 | 13 | function loadImage( img, src ){ 14 | var def = when.defer() 15 | img.onload = function(){ 16 | def.resolve( img ); 17 | } 18 | img.src = src; 19 | return def.promise; 20 | } 21 | 22 | var mireRGB, mireRGBA; 23 | var filltex, filltex16; 24 | 25 | describe( "Texture2d", function(){ 26 | 27 | before(function() { 28 | var vert = require( './glsl/filltex.vert') 29 | var frag = require( './glsl/filltex.frag') 30 | filltex = new Program( gl ); 31 | filltex.compile( vert, frag, "#define UV_MULT 2.0" ); 32 | 33 | filltex16 = new Program( gl ); 34 | filltex16.compile( vert, frag, "#define UV_MULT 17.0" ); 35 | 36 | mireRGB = document.createElement( 'img' ); 37 | mireRGBA = document.createElement( 'img' ); 38 | return when.all( [ 39 | loadImage( mireRGB, 'base/test/assets/mireRGB.png' ), 40 | loadImage( mireRGBA, 'base/test/assets/mireRGBA.png' ), 41 | ]); 42 | }); 43 | 44 | after( function(){ 45 | filltex.dispose() 46 | filltex16.dispose() 47 | }) 48 | 49 | 50 | 51 | it( "should be exported in nanogl namespace", function(){ 52 | expect( Texture2D ).to.be.ok( ); 53 | }); 54 | 55 | it( "creation should leave clean gl state", function(){ 56 | var tex = new Texture2D( gl ); 57 | testContext.assertNoError(); 58 | tex.dispose() 59 | }); 60 | 61 | 62 | it( "dispose should leave clean gl state", function(){ 63 | var tex = new Texture2D( gl ); 64 | tex.dispose() 65 | testContext.assertNoError(); 66 | }); 67 | 68 | 69 | it( "should load rgb tex", function( ){ 70 | var tex = new Texture2D( gl ); 71 | tex.fromImage( mireRGB, false ); 72 | tex.dispose(); 73 | testContext.assertNoError(); 74 | }); 75 | 76 | 77 | it( "should load rgba tex", function( ){ 78 | var tex = new Texture2D( gl ); 79 | tex.fromImage( mireRGBA, true ); 80 | tex.dispose(); 81 | testContext.assertNoError(); 82 | }); 83 | 84 | 85 | 86 | it( "should render rgb tex", function( ){ 87 | var tex = new Texture2D( gl ); 88 | tex.fromImage( mireRGB, false ); 89 | 90 | filltex.bind() 91 | 92 | gl.activeTexture( gl.TEXTURE0 ); 93 | gl.bindTexture( gl.TEXTURE_2D, tex.id ); 94 | gl.uniform1i( filltex.tTex(), 0 ); 95 | 96 | testContext.drawProgram( filltex ); 97 | testContext.testPixel( 0, 0, 0xFF101010 ) 98 | testContext.testPixel( 0, 16, 0xFFee0000 ) 99 | 100 | testContext.assertNoError(); 101 | }); 102 | 103 | 104 | it( "should render nearest filtering", function( ){ 105 | var tex = new Texture2D( gl ); 106 | tex.fromImage( mireRGB, false ); 107 | 108 | filltex.bind() 109 | 110 | gl.activeTexture( gl.TEXTURE0 ); 111 | gl.bindTexture( gl.TEXTURE_2D, tex.id ); 112 | gl.uniform1i( filltex.tTex(), 0 ); 113 | 114 | // NEAREST 115 | tex.setFilter( false, false, false ) 116 | tex.clamp() 117 | testContext.drawProgram( filltex ); 118 | testContext.testPixel( 16, 3, 0xFFee0000 ) 119 | testContext.testPixel( 48, 3, 0xFF101010 ) 120 | }); 121 | 122 | 123 | it( "should render linear filtering", function( ){ 124 | var tex = new Texture2D( gl ); 125 | tex.fromImage( mireRGB, false ); 126 | 127 | filltex.bind() 128 | 129 | gl.activeTexture( gl.TEXTURE0 ); 130 | gl.bindTexture( gl.TEXTURE_2D, tex.id ); 131 | gl.uniform1i( filltex.tTex(), 0 ); 132 | 133 | tex.setFilter( true, false, false ) 134 | tex.clamp() 135 | testContext.drawProgram( filltex ); 136 | testContext.testPixel( 16, 3, 0xFF955900 ) 137 | testContext.testPixel( 48, 3, 0xFF630A0A ) 138 | }); 139 | 140 | 141 | 142 | it( "should render nearest mip filtering", function( ){ 143 | var tex = new Texture2D( gl ); 144 | tex.fromImage( mireRGB, false ); 145 | 146 | filltex.bind() 147 | 148 | gl.activeTexture( gl.TEXTURE0 ); 149 | gl.bindTexture( gl.TEXTURE_2D, tex.id ); 150 | gl.uniform1i( filltex.tTex(), 0 ); 151 | 152 | 153 | gl.generateMipmap( gl.TEXTURE_2D, tex.id ); 154 | tex.setFilter( false, true, false ) 155 | tex.clamp() 156 | testContext.drawProgram( filltex16 ); 157 | testContext.testPixel( 0, 0, 0xFF7b4004 ) 158 | testContext.testPixel( 2, 0, 0xFF777700 ) 159 | }); 160 | 161 | 162 | it( "should render nearest mip linear filtering", function( ){ 163 | var tex = new Texture2D( gl ); 164 | tex.fromImage( mireRGB, false ); 165 | 166 | filltex.bind() 167 | 168 | gl.activeTexture( gl.TEXTURE0 ); 169 | gl.bindTexture( gl.TEXTURE_2D, tex.id ); 170 | gl.uniform1i( filltex.tTex(), 0 ); 171 | 172 | 173 | gl.generateMipmap( gl.TEXTURE_2D, tex.id ); 174 | tex.setFilter( false, true, true ) 175 | tex.clamp() 176 | testContext.drawProgram( filltex16 ); 177 | testContext.testPixel( 0, 0, 0xFF794209 ) 178 | testContext.testPixel( 2, 0, 0xFF767405 ) 179 | }); 180 | 181 | 182 | it( "should render linear mip linear filtering", function( ){ 183 | var tex = new Texture2D( gl ); 184 | tex.fromImage( mireRGB, false ); 185 | 186 | filltex.bind() 187 | 188 | gl.activeTexture( gl.TEXTURE0 ); 189 | gl.bindTexture( gl.TEXTURE_2D, tex.id ); 190 | gl.uniform1i( filltex.tTex(), 0 ); 191 | 192 | 193 | gl.generateMipmap( gl.TEXTURE_2D, tex.id ); 194 | tex.setFilter( true, true, true ) 195 | tex.clamp() 196 | testContext.drawProgram( filltex16 ); 197 | testContext.testPixel( 0, 0, 0xff794509 ) 198 | testContext.testPixel( 2, 0, 0xff756c0c ) 199 | }); 200 | 201 | 202 | it( "should render with program sampler helper", function( ){ 203 | var tex = new Texture2D( gl ); 204 | tex.fromImage( mireRGB, false ); 205 | 206 | filltex.bind() 207 | filltex.tTex( tex ); 208 | 209 | testContext.drawProgram( filltex ); 210 | testContext.testPixel( 0, 0, 0xFF101010 ) 211 | testContext.testPixel( 0, 16, 0xFFee0000 ) 212 | 213 | testContext.assertNoError(); 214 | }); 215 | 216 | 217 | 218 | it( "@should accept Uint8Array RGB data", function( ){ 219 | var tex = new Texture2D( gl, gl.RGB ); 220 | tex.bind(); 221 | gl.pixelStorei( gl.UNPACK_ALIGNMENT, 1 ); 222 | 223 | var data = new Uint8Array( [ 224 | 0x10, 0x10, 0x10, 225 | 0x20, 0x20, 0x20, 226 | 0x30, 0x30, 0x30, 227 | 0x40, 0x40, 0x40 228 | ]); 229 | tex.fromData( 2, 2, data ); 230 | 231 | testContext.assertNoError(); 232 | 233 | filltex.bind() 234 | filltex.tTex( tex ); 235 | tex.setFilter( false, false, false ) 236 | 237 | testContext.drawProgram( filltex ); 238 | testContext.testPixel( 15, 15, 0xFF101010 ) 239 | testContext.testPixel( 16, 15, 0xFF202020 ) 240 | testContext.testPixel( 16, 16, 0xFF404040 ) 241 | 242 | }); 243 | 244 | it( "should accept Uint8Array RGBA data", function( ){ 245 | var tex = new Texture2D( gl, gl.RGBA ); 246 | 247 | var data = new Uint8Array( [ 248 | 0x10, 0x10, 0x10, 0x60, 249 | 0x20, 0x20, 0x20, 0x70, 250 | 0x30, 0x30, 0x30, 0x80, 251 | 0x40, 0x40, 0x40, 0x90 252 | ]); 253 | tex.fromData( 2, 2, data ); 254 | 255 | testContext.assertNoError(); 256 | 257 | filltex.bind() 258 | filltex.tTex( tex ); 259 | tex.setFilter( false, false, false ) 260 | 261 | testContext.drawProgram( filltex ); 262 | testContext.testPixel( 15, 15, 0x60101010 ) 263 | testContext.testPixel( 16, 15, 0x70202020 ) 264 | testContext.testPixel( 16, 16, 0x90404040 ) 265 | 266 | }); 267 | 268 | 269 | }); 270 | -------------------------------------------------------------------------------- /program.js: -------------------------------------------------------------------------------- 1 | import { isWebgl2 } from './types'; 2 | let _UID = 0; 3 | class Program { 4 | constructor(gl, vert, frag, defs) { 5 | this.gl = gl; 6 | this.program = gl.createProgram(); 7 | this.vShader = gl.createShader(gl.VERTEX_SHADER); 8 | this.fShader = gl.createShader(gl.FRAGMENT_SHADER); 9 | this.dyns = []; 10 | this.ready = false; 11 | gl.attachShader(this.program, this.vShader); 12 | gl.attachShader(this.program, this.fShader); 13 | this._uid = _UID++ | 0; 14 | this._cuid = _UID++ | 0; 15 | if (vert !== undefined && frag !== undefined) { 16 | this.compile(vert, frag, defs); 17 | } 18 | } 19 | use() { 20 | if (!this.ready) { 21 | this._grabParameters(); 22 | } 23 | this.gl.useProgram(this.program); 24 | } 25 | bind() { 26 | this.use(); 27 | } 28 | compile(vert, frag, prefix) { 29 | this.ready = false; 30 | prefix = prefix === undefined ? '' : prefix + '\n'; 31 | const gl = this.gl; 32 | if (!(compileShader(gl, this.fShader, prefix + frag) && compileShader(gl, this.vShader, prefix + vert))) { 33 | return false; 34 | } 35 | gl.linkProgram(this.program); 36 | if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { 37 | Program.debug && warn(gl.getProgramInfoLog(this.program)); 38 | return false; 39 | } 40 | while (this.dyns.length > 0) { 41 | delete this[this.dyns.pop()]; 42 | } 43 | this._cuid = _UID++ | 0; 44 | return true; 45 | } 46 | dispose() { 47 | if (this.gl !== null) { 48 | this.gl.deleteProgram(this.program); 49 | this.gl.deleteShader(this.fShader); 50 | this.gl.deleteShader(this.vShader); 51 | } 52 | } 53 | _grabParameters() { 54 | const gl = this.gl, prg = this.program; 55 | const context = { 56 | texIndex: 0, 57 | ublockIndex: 0, 58 | }; 59 | const numUniforms = gl.getProgramParameter(prg, gl.ACTIVE_UNIFORMS); 60 | for (var uniformIndex = 0; uniformIndex < numUniforms; ++uniformIndex) { 61 | var uniform = gl.getActiveUniform(prg, uniformIndex); 62 | if (uniform === null) { 63 | gl.getError(); 64 | continue; 65 | } 66 | var uName = uniform.name, n = uName.indexOf('['); 67 | if (n >= 0) { 68 | uName = uName.substring(0, n); 69 | } 70 | var uLocation = gl.getUniformLocation(prg, uniform.name); 71 | if (uLocation !== null) { 72 | this[uName] = getUniformSetter(uniform.type, uLocation, gl, context); 73 | this.dyns.push(uName); 74 | } 75 | } 76 | const numAttribs = gl.getProgramParameter(prg, gl.ACTIVE_ATTRIBUTES); 77 | for (var aIndex = 0; aIndex < numAttribs; ++aIndex) { 78 | var attribName = gl.getActiveAttrib(prg, aIndex).name; 79 | var aLocation = gl.getAttribLocation(prg, attribName); 80 | this[attribName] = getAttribAccess(aLocation); 81 | this.dyns.push(attribName); 82 | } 83 | if (isWebgl2(gl)) { 84 | const numBlocks = gl.getProgramParameter(prg, gl.ACTIVE_UNIFORM_BLOCKS); 85 | for (var blockIndex = 0; blockIndex < numBlocks; ++blockIndex) { 86 | var blockName = gl.getActiveUniformBlockName(prg, blockIndex); 87 | this[blockName] = getUniformBufferSetFunction(blockIndex, gl, context); 88 | this.dyns.push(blockName); 89 | } 90 | } 91 | this.ready = true; 92 | } 93 | } 94 | Program.debug = false; 95 | function warn(str) { 96 | console.warn(str); 97 | } 98 | const __pads = ['', ' ', ' ', ' ', '']; 99 | function appendLine(l, i) { 100 | return __pads[String(i + 1).length] + (i + 1) + ': ' + l; 101 | } 102 | function formatCode(shader) { 103 | return shader 104 | .split('\n') 105 | .map(appendLine) 106 | .join('\n'); 107 | } 108 | const ErrLineRegex = /^ERROR:\s?(\d+):(\d+)/; 109 | function reportCompileError(infos, source) { 110 | const sourceLines = source.split('\n'); 111 | infos = infos.split('\n').map((line) => { 112 | const rr = ErrLineRegex.exec(line); 113 | if (rr) { 114 | line += '\n > ' + sourceLines[parseInt(rr[2]) - 1]; 115 | } 116 | return line; 117 | }).join('\n'); 118 | source = formatCode(source); 119 | warn(infos); 120 | warn(source); 121 | } 122 | function compileShader(gl, shader, code) { 123 | gl.shaderSource(shader, code); 124 | gl.compileShader(shader); 125 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 126 | Program.debug && reportCompileError(gl.getShaderInfoLog(shader), code); 127 | return false; 128 | } 129 | return true; 130 | } 131 | const USetFMap = {}; 132 | USetFMap[String(5126)] = '1f'; 133 | USetFMap[String(35664)] = '2f'; 134 | USetFMap[String(35665)] = '3f'; 135 | USetFMap[String(35666)] = '4f'; 136 | USetFMap[String(35670)] = 137 | USetFMap[String(5124)] = 138 | USetFMap[String(35678)] = USetFMap[String(35680)] = USetFMap[String(35866)] = '1i'; 139 | USetFMap[String(35671)] = USetFMap[String(35667)] = '2i'; 140 | USetFMap[String(35672)] = USetFMap[String(35668)] = '3i'; 141 | USetFMap[String(35673)] = USetFMap[String(35669)] = '4i'; 142 | USetFMap[String(35674)] = 'Matrix2f'; 143 | USetFMap[String(35675)] = 'Matrix3f'; 144 | USetFMap[String(35676)] = 'Matrix4f'; 145 | function getUniformSetFunctionName(type) { 146 | return 'uniform' + USetFMap[String(type)]; 147 | } 148 | function getUniformSetter(type, location, gl, context) { 149 | switch (type) { 150 | case gl.FLOAT_MAT2: 151 | case gl.FLOAT_MAT3: 152 | case gl.FLOAT_MAT4: 153 | return getMatrixSetFunction(type, location, gl, context); 154 | case gl.SAMPLER_2D: 155 | case gl.SAMPLER_CUBE: 156 | case 0x8b62: 157 | case 0x8b5f: 158 | case 0x8DC1: 159 | return getSamplerSetFunction(type, location, gl, context); 160 | default: 161 | return getUniformSetFunction(type, location, gl, context); 162 | } 163 | } 164 | function getUniformSetFunction(type, location, gl, context) { 165 | context; 166 | const fname = getUniformSetFunctionName(type); 167 | return function (...args) { 168 | if (args.length === 1 && args[0].length != undefined) { 169 | gl[fname + 'v'](location, args[0]); 170 | } 171 | else if (args.length > 0) { 172 | gl[fname](location, ...args); 173 | } 174 | return location; 175 | }; 176 | } 177 | function getMatrixSetFunction(type, location, gl, context) { 178 | context; 179 | const fname = getUniformSetFunctionName(type); 180 | return function () { 181 | if (arguments.length > 0 && arguments[0].length !== undefined) { 182 | var transpose = arguments.length > 1 ? !!arguments[1] : false; 183 | gl[fname + 'v'](location, transpose, arguments[0]); 184 | } 185 | return location; 186 | }; 187 | } 188 | function getSamplerSetFunction(type, location, gl, context) { 189 | const unit = context.texIndex++; 190 | return function () { 191 | if (arguments.length === 1) { 192 | if (arguments[0].bind !== undefined) { 193 | arguments[0].bind(unit); 194 | gl.uniform1i(location, unit); 195 | } 196 | else { 197 | gl.uniform1i(location, arguments[0]); 198 | } 199 | } 200 | return location; 201 | }; 202 | } 203 | function getUniformBufferSetFunction(index, gl, context) { 204 | const unit = context.ublockIndex++; 205 | return function () { 206 | if (arguments.length === 1) { 207 | if (arguments[0] instanceof WebGLBuffer) { 208 | gl.uniformBlockBinding(this.program, index, unit); 209 | gl.bindBufferBase(gl.UNIFORM_BUFFER, unit, arguments[0]); 210 | } 211 | else { 212 | gl.uniformBlockBinding(this.program, index, arguments[0]); 213 | } 214 | } 215 | return index; 216 | }; 217 | } 218 | function getAttribAccess(attrib) { 219 | return function () { 220 | return attrib; 221 | }; 222 | } 223 | export default Program; 224 | -------------------------------------------------------------------------------- /test/program.js: -------------------------------------------------------------------------------- 1 | import Program from '../program' 2 | var expect = require( 'expect.js' ); 3 | var sinon = require('sinon'); 4 | 5 | var testContext = require( './utils/TestContext' ); 6 | var gl = testContext.getContext(); 7 | 8 | Program.debug = true 9 | 10 | describe( "Program GLSL1", function(){ 11 | 12 | it( "should be exported", function(){ 13 | 14 | expect( Program ).to.be.ok( ); 15 | 16 | }); 17 | 18 | it( "constructor should return instance", function(){ 19 | 20 | var p = new Program( gl ); 21 | expect( p ).to.be.ok( ); 22 | expect( p.gl ).to.be.ok( ); 23 | 24 | }); 25 | 26 | 27 | 28 | it( "should compile simple", function(){ 29 | 30 | var vert = require( './glsl/simple.vert') 31 | var frag = require( './glsl/simple.frag') 32 | 33 | var p = new Program( gl ); 34 | p.compile( vert, frag ); 35 | 36 | testContext.assertNoError(); 37 | 38 | }); 39 | 40 | it( "should compile complex", function(){ 41 | 42 | var vert = require( './glsl/complete.vert') 43 | var frag = require( './glsl/complete.frag') 44 | 45 | var p = new Program( gl ); 46 | p.compile( vert, frag ); 47 | 48 | testContext.assertNoError(); 49 | }); 50 | 51 | 52 | 53 | it( "should recompile", function(){ 54 | 55 | var p = new Program( gl ); 56 | p.compile( 57 | require( './glsl/complete.vert'), 58 | require( './glsl/complete.frag') 59 | ); 60 | 61 | var vert = require( './glsl/test_ufloat.vert'), 62 | frag = require( './glsl/test_ufloat.frag'); 63 | p.compile( 64 | vert, 65 | frag 66 | ); 67 | 68 | testContext.assertNoError(); 69 | 70 | p.bind() 71 | 72 | p.uFloat( .5 ); 73 | 74 | testContext.drawProgram( p ); 75 | testContext.testPixel( 0, 0, 0xFF80FF40 ) 76 | 77 | }); 78 | 79 | 80 | describe( "should set float uniform", function(){ 81 | 82 | var vert = require( './glsl/test_ufloat.vert') 83 | var frag = require( './glsl/test_ufloat.frag') 84 | 85 | var p = new Program( gl ); 86 | p.compile( vert, frag ); 87 | 88 | 89 | it( "with helper arguments", function(){ 90 | p.bind() 91 | 92 | p.uFloat( .5 ); 93 | 94 | testContext.drawProgram( p ); 95 | testContext.assertNoError(); 96 | testContext.testPixel( 0, 0, 0xFF80FF40 ) 97 | }); 98 | 99 | it( "with helper vector", function(){ 100 | p.bind() 101 | var val = new Float32Array([.5]) 102 | p.uFloat( val ); 103 | 104 | testContext.drawProgram( p ); 105 | testContext.assertNoError(); 106 | testContext.testPixel( 0, 0, 0xFF80FF40 ) 107 | }); 108 | 109 | it( "with location access", function(){ 110 | p.bind() 111 | 112 | gl.uniform1f( p.uFloat(), .5 ) 113 | 114 | testContext.drawProgram( p ); 115 | testContext.assertNoError(); 116 | testContext.testPixel( 0, 0, 0xFF80FF40 ) 117 | }); 118 | 119 | }); 120 | 121 | 122 | 123 | 124 | describe( "should set vec3 uniform", function(){ 125 | 126 | var vert = require( './glsl/test_uvec3.vert') 127 | var frag = require( './glsl/test_uvec3.frag') 128 | 129 | var p = new Program( gl ); 130 | p.compile( vert, frag ); 131 | 132 | 133 | it( "with helper arguments", function(){ 134 | 135 | p.bind() 136 | p.uVec3( .5, 1, .25 ); 137 | 138 | testContext.drawProgram( p ); 139 | testContext.testPixel( 0, 0, 0xFF80FF40 ) 140 | testContext.assertNoError(); 141 | }); 142 | 143 | it( "with helper vector", function(){ 144 | p.bind() 145 | var val = new Float32Array([.5, 1, .25 ]) 146 | p.uVec3( val ); 147 | 148 | testContext.drawProgram( p ); 149 | testContext.testPixel( 0, 0, 0xFF80FF40 ) 150 | testContext.assertNoError(); 151 | }); 152 | 153 | it( "with location access", function(){ 154 | p.bind() 155 | 156 | gl.uniform3f( p.uVec3(), 1, .5, .25 ) 157 | 158 | testContext.drawProgram( p ); 159 | testContext.testPixel( 0, 0, 0xFFFF8040 ) 160 | }); 161 | 162 | }); 163 | 164 | 165 | 166 | describe( "should set vec3 Array uniform", function(){ 167 | 168 | var vert = require( './glsl/test_uvec3_array.vert') 169 | var frag = require( './glsl/test_uvec3_array.frag') 170 | 171 | var p = new Program( gl ); 172 | p.compile( vert, frag ); 173 | 174 | 175 | 176 | it( "with helper vector", function(){ 177 | p.bind() 178 | var val = new Float32Array([.5, .25, 2 , .5, 1, .25 ]) 179 | p.uVec3( val ); 180 | 181 | testContext.drawProgram( p ); 182 | testContext.testPixel( 0, 0, 0xFF404080 ) 183 | testContext.assertNoError(); 184 | }); 185 | 186 | it( "with location access", function(){ 187 | p.bind() 188 | 189 | gl.uniform3fv( p.uVec3(), [1, .25, 2 , .5, 1, .125] ) 190 | 191 | testContext.drawProgram( p ); 192 | testContext.testPixel( 0, 0, 0xFF804040 ) 193 | testContext.assertNoError(); 194 | }); 195 | 196 | }); 197 | 198 | 199 | describe( "should set mat4 uniform", function(){ 200 | 201 | var vert = require( './glsl/test_umat4.vert') 202 | var frag = require( './glsl/test_umat4.frag') 203 | 204 | var p = new Program( gl ); 205 | p.compile( vert, frag ); 206 | 207 | var matrix = new Float32Array([ 208 | .5, 0, 0, 0, 209 | 0, .5, 0, 0, 210 | 0, 0, .5, 0, 211 | .25, .25, 0, 1 212 | ]) 213 | var nullMat = new Float32Array([ 214 | 0, 0, 0, 0, 215 | 0, 0, 0, 0, 216 | 0, 0, 0, 0, 217 | 0, 0, 0, 0 218 | ]) 219 | 220 | it( "with helper vector", function(){ 221 | p.bind() 222 | p.uMat4( matrix ); 223 | 224 | gl.clear( gl.COLOR_BUFFER_BIT ); 225 | testContext.drawProgram( p ); 226 | testContext.testPixel( 23, 23, 0xFF000000 ); 227 | testContext.testPixel( 24, 24, 0xFF8040FF ); 228 | testContext.testPixel( 55, 55, 0xFF8040FF ); 229 | testContext.testPixel( 56, 56, 0xFF000000 ); 230 | testContext.assertNoError(); 231 | 232 | p.uMat4( nullMat ); 233 | }); 234 | 235 | it( "with location access", function(){ 236 | p.bind() 237 | 238 | gl.uniformMatrix4fv( p.uMat4(), false, matrix ) 239 | 240 | gl.clear( gl.COLOR_BUFFER_BIT ); 241 | testContext.drawProgram( p ); 242 | testContext.testPixel( 23, 23, 0xFF000000 ); 243 | testContext.testPixel( 24, 24, 0xFF8040FF ); 244 | testContext.testPixel( 55, 55, 0xFF8040FF ); 245 | testContext.testPixel( 56, 56, 0xFF000000 ); 246 | testContext.assertNoError(); 247 | p.uMat4( nullMat ); 248 | }); 249 | 250 | }); 251 | 252 | 253 | 254 | 255 | 256 | 257 | describe( "invalid program in debug", function(){ 258 | 259 | var regex = /^ERROR:\s?(\d+):(\d+)/gm 260 | 261 | it( "should warn on invalid program", function(){ 262 | 263 | var vert = require( './glsl/bad.vert') 264 | var frag = require( './glsl/simple.frag') 265 | var warn = sinon.stub( console, 'warn' ); 266 | 267 | Program.debug = true; 268 | 269 | var p = new Program( gl ); 270 | var res = p.compile( vert, frag ); 271 | 272 | expect(warn.called).to.be.ok() 273 | expect(res).to.be(false) 274 | 275 | expect( warn.firstCall.args[0] ).to.match(regex) 276 | 277 | testContext.assertNoError(); 278 | warn.restore() 279 | 280 | }); 281 | 282 | it( "should warn on link error", function(){ 283 | 284 | var vert = require( './glsl/complete.vert') 285 | var frag = require( './glsl/bad_link_complete.frag') 286 | var warn = sinon.stub( console, 'warn' ); 287 | 288 | Program.debug = true; 289 | 290 | var p = new Program( gl ); 291 | var res = p.compile( vert, frag ); 292 | 293 | expect(warn.called).to.be.ok() 294 | expect(res).to.be(false) 295 | 296 | testContext.assertNoError(); 297 | warn.restore() 298 | 299 | }); 300 | }); 301 | 302 | 303 | 304 | 305 | 306 | describe( "invalid program not in debug", function(){ 307 | 308 | var regex = /^ERROR:\s?(\d+):(\d+)/gm 309 | 310 | it( "should not warn on invalid program", function(){ 311 | 312 | var vert = require( './glsl/bad.vert') 313 | var frag = require( './glsl/simple.frag') 314 | var warn = sinon.stub( console, 'warn' ); 315 | 316 | var p = new Program( gl ); 317 | Program.debug = false; 318 | var res = p.compile( vert, frag ); 319 | warn.restore() 320 | 321 | expect(warn.called).not.to.be.ok() 322 | expect(res).to.be(false) 323 | 324 | testContext.assertNoError(); 325 | 326 | }); 327 | 328 | it( "should not warn on link error", function(){ 329 | 330 | var vert = require( './glsl/complete.vert') 331 | var frag = require( './glsl/bad_link_complete.frag') 332 | var warn = sinon.stub( console, 'warn' ); 333 | 334 | Program.debug = false; 335 | 336 | var p = new Program( gl ); 337 | var res = p.compile( vert, frag ); 338 | warn.restore() 339 | 340 | expect(warn.called).not.to.be.ok() 341 | expect(res).to.be(false) 342 | 343 | testContext.assertNoError(); 344 | 345 | }); 346 | }); 347 | 348 | 349 | describe( "uniform struct/block", function(){ 350 | 351 | it( "should compile", function(){ 352 | var prefix = (testContext.getGlVersion()===2) ? '#version 300 es' : ''; 353 | 354 | var vert = require( './glsl/ublock_compat.vert') 355 | var frag = require( './glsl/ublock_compat.frag') 356 | 357 | var p = new Program( gl ); 358 | p.compile( vert, frag, prefix ); 359 | 360 | testContext.assertNoError(); 361 | 362 | }); 363 | 364 | 365 | it( "should grab params", function(){ 366 | var prefix = (testContext.getGlVersion()===2) ? '#version 300 es' : ''; 367 | 368 | var vert = require( './glsl/ublock_compat.vert') 369 | var frag = require( './glsl/ublock_compat.frag') 370 | 371 | var p = new Program( gl ); 372 | p.compile( vert, frag, prefix ); 373 | p.use(); 374 | 375 | testContext.assertNoError(); 376 | 377 | }); 378 | 379 | }); 380 | 381 | }); 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | describe( "@WEBGL2 Program GLSL300", function(){ 390 | 391 | it( "should compile simple", function(){ 392 | 393 | var vert = require( './glsl300/simple.vert') 394 | var frag = require( './glsl300/simple.frag') 395 | 396 | var p = new Program( gl ); 397 | var res = p.compile( vert, frag ); 398 | 399 | expect( res ).to.be.ok() 400 | 401 | testContext.assertNoError(); 402 | 403 | }); 404 | 405 | it( "should compile u blocks", function(){ 406 | 407 | var vert = require( './glsl300/ublock.vert') 408 | var frag = require( './glsl300/ublock.frag') 409 | 410 | var p = new Program( gl ); 411 | var res = p.compile( vert, frag ); 412 | 413 | expect( res ).to.be.ok() 414 | 415 | testContext.assertNoError(); 416 | 417 | }); 418 | 419 | 420 | it( "should retreive u blocks", function(){ 421 | 422 | var vert = require( './glsl300/ublock.vert') 423 | var frag = require( './glsl300/ublock.frag') 424 | 425 | var p = new Program( gl ); 426 | var res = p.compile( vert, frag ); 427 | p.use() 428 | 429 | expect( res ).to.be.ok() 430 | expect( p.BlockA ).to.be.ok() 431 | expect( p.BlockB ).to.be.ok() 432 | expect( p.uMat4 ).to.be( undefined ) 433 | 434 | testContext.assertNoError(); 435 | 436 | }); 437 | }); 438 | -------------------------------------------------------------------------------- /test/fbo.js: -------------------------------------------------------------------------------- 1 | import Fbo from '../fbo' 2 | import Texture2D from '../texture-2d' 3 | import Program from '../program' 4 | import Renderbuffer from '../renderbuffer' 5 | var expect = require( 'expect.js' ); 6 | 7 | var testContext = require( './utils/TestContext' ); 8 | var gl = testContext.getContext(); 9 | 10 | 11 | 12 | describe( "Fbo", function(){ 13 | 14 | 15 | 16 | it( "ctor should leave clean state", function(){ 17 | 18 | var fbo = new Fbo( gl ); 19 | testContext.assertNoError(); 20 | 21 | }) 22 | 23 | 24 | it( "dispose should leave clean state", function(){ 25 | 26 | var fbo = new Fbo( gl ); 27 | fbo.dispose() 28 | 29 | testContext.assertNoError(); 30 | 31 | }) 32 | 33 | it( "should dispose correctly", function(){ 34 | 35 | var fbo = new Fbo( gl ); 36 | fbo.resize( 32, 32 ); 37 | var dispose = function(){ 38 | fbo.dispose() 39 | } 40 | expect(dispose).to.not.throwException(); 41 | testContext.assertNoError(); 42 | }); 43 | 44 | 45 | it( "should dispose when not init", function(){ 46 | 47 | var fbo = new Fbo( gl ); 48 | var dispose = function(){ 49 | fbo.dispose() 50 | } 51 | expect(dispose).to.not.throwException(); 52 | testContext.assertNoError(); 53 | }); 54 | 55 | 56 | it( "should dispose when color attach", function(){ 57 | 58 | var fbo = new Fbo( gl ); 59 | fbo.resize( 32, 32 ); 60 | fbo.bind() 61 | fbo.attachColor() 62 | 63 | var dispose = function(){ 64 | fbo.dispose() 65 | } 66 | expect(dispose).to.not.throwException(); 67 | testContext.assertNoError(); 68 | }); 69 | 70 | it( "should dispose when color attach not alloc", function(){ 71 | 72 | var fbo = new Fbo( gl ); 73 | fbo.bind() 74 | fbo.attachColor() 75 | 76 | var dispose = function(){ 77 | fbo.dispose() 78 | } 79 | expect(dispose).to.not.throwException(); 80 | testContext.assertNoError(); 81 | }); 82 | 83 | 84 | 85 | it( "resize empty fbo", function(){ 86 | 87 | var fbo = new Fbo( gl ); 88 | fbo.resize( 32, 32 ); 89 | 90 | testContext.assertNoError(); 91 | 92 | }) 93 | 94 | 95 | 96 | it( "should pass render test A", function(){ 97 | var vert, frag, p; 98 | 99 | var fbo = new Fbo( gl ); 100 | fbo.resize( 32, 32 ); 101 | fbo.bind() 102 | fbo.attachColor(); 103 | 104 | // draw 0xFF7F0000 to Fbo color 105 | vert = require( './glsl/test_uvec3.vert') 106 | frag = require( './glsl/test_uvec3.frag') 107 | p = new Program( gl ); 108 | p.compile( vert, frag ); 109 | p.bind() 110 | p.uVec3( .5, 0, 0 ); 111 | 112 | 113 | fbo.bind(); 114 | fbo.defaultViewport(); 115 | testContext.drawProgram( p ); 116 | 117 | // draw Fbo to screen 118 | testContext.bindScreen(); 119 | 120 | vert = require( './glsl/filltex.vert') 121 | frag = require( './glsl/filltex.frag') 122 | p = new Program( gl ); 123 | p.compile( vert, frag ); 124 | p.bind() 125 | fbo.getColor().bind( 0 ); 126 | 127 | testContext.drawProgram( p ); 128 | 129 | // test color 130 | testContext.testPixel( 0, 0, 0xFF800000 ) 131 | testContext.assertNoError(); 132 | fbo.dispose(); 133 | }); 134 | 135 | 136 | 137 | describe( "attach basic color", function(){ 138 | 139 | var fbo, tex, color; 140 | 141 | beforeEach( function() { 142 | 143 | tex = new Texture2D(gl) 144 | fbo = new Fbo( gl ); 145 | }); 146 | 147 | 148 | afterEach( function(){ 149 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 150 | fbo.dispose() 151 | }) 152 | 153 | 154 | it( " after resize leave clean state", function(){ 155 | 156 | fbo.bind() 157 | fbo.resize( 32, 32 ); 158 | fbo.attach( 0x8CE0, tex ); 159 | testContext.assertNoError(); 160 | 161 | }) 162 | 163 | 164 | it( "after resize make complete fbo ", function(){ 165 | 166 | fbo.bind() 167 | fbo.resize( 64, 64 ); 168 | fbo.attach( 0x8CE0, tex ); 169 | 170 | expect( fbo.isValid() ).to.be.ok() 171 | testContext.assertNoError(); 172 | 173 | }) 174 | 175 | it( " before resize leave clean state", function(){ 176 | 177 | fbo.bind() 178 | fbo.attach( 0x8CE0, tex ); 179 | fbo.resize( 16, 16 ); 180 | testContext.assertNoError(); 181 | 182 | }) 183 | 184 | 185 | it( "before resize make complete fbo ", function(){ 186 | 187 | fbo.bind() 188 | fbo.attach( 0x8CE0, tex ); 189 | fbo.resize( 4, 8 ); 190 | 191 | expect( fbo.isValid() ).to.be.ok() 192 | testContext.assertNoError(); 193 | 194 | }) 195 | 196 | }) 197 | 198 | 199 | 200 | describe( "detach basic color", function(){ 201 | 202 | var fbo, tex, color; 203 | 204 | beforeEach( function() { 205 | tex = new Texture2D(gl) 206 | fbo = new Fbo( gl ); 207 | }); 208 | 209 | 210 | afterEach( function(){ 211 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 212 | fbo.dispose() 213 | }) 214 | 215 | 216 | it( " leave clean state", function(){ 217 | 218 | fbo.bind() 219 | fbo.attach( 0x8CE0, tex ); 220 | fbo.resize( 32, 32 ); 221 | fbo.detach( 0x8CE0 ); 222 | testContext.assertNoError(); 223 | 224 | }) 225 | 226 | 227 | 228 | }) 229 | 230 | 231 | 232 | 233 | describe( "getAttachment", function(){ 234 | 235 | var fbo, tex, color; 236 | 237 | beforeEach( function() { 238 | tex = new Texture2D(gl) 239 | fbo = new Fbo( gl ); 240 | fbo.bind(); 241 | color = fbo.attach( gl.COLOR_ATTACHMENT0, tex ); 242 | }); 243 | 244 | 245 | afterEach( function(){ 246 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 247 | fbo.dispose() 248 | }) 249 | 250 | it( "return color0", function(){ 251 | 252 | var att = fbo.getAttachment( gl.COLOR_ATTACHMENT0 ); 253 | expect( att.isTexture() ).to.be.ok() 254 | 255 | }) 256 | 257 | it( "return null for color 1", function(){ 258 | 259 | var att = fbo.getAttachment( gl.COLOR_ATTACHMENT0 + 1); 260 | expect( att ).to.be(null) 261 | 262 | }) 263 | 264 | }); 265 | 266 | 267 | 268 | describe( "attach depth/stencil", function(){ 269 | 270 | var fbo, tex, color; 271 | 272 | beforeEach( function() { 273 | tex = new Texture2D(gl) 274 | fbo = new Fbo( gl ); 275 | fbo.bind(); 276 | color = fbo.attach( gl.COLOR_ATTACHMENT0, tex ); 277 | }); 278 | 279 | 280 | afterEach( function(){ 281 | gl.bindFramebuffer( gl.FRAMEBUFFER, null ); 282 | fbo.dispose() 283 | }) 284 | 285 | 286 | it( "after resize leave clean state", function(){ 287 | 288 | var depth = new Renderbuffer( gl, gl.DEPTH_COMPONENT16 ); 289 | fbo.bind() 290 | fbo.resize( 32, 32 ); 291 | fbo.attach( gl.DEPTH_ATTACHMENT, depth ); 292 | testContext.assertNoError(); 293 | 294 | }) 295 | 296 | 297 | it( "after resize make complete fbo ", function(){ 298 | 299 | var depth = new Renderbuffer( gl, gl.DEPTH_COMPONENT16 ); 300 | 301 | fbo.bind() 302 | fbo.resize( 64, 64 ); 303 | fbo.attach( gl.DEPTH_ATTACHMENT, depth ); 304 | 305 | expect( fbo.isValid() ).to.be.ok() 306 | testContext.assertNoError(); 307 | 308 | }) 309 | 310 | 311 | it( " before resize leave clean state", function(){ 312 | var depth = new Renderbuffer( gl, gl.DEPTH_COMPONENT16 ); 313 | 314 | fbo.bind() 315 | fbo.attach( gl.DEPTH_ATTACHMENT, depth ); 316 | fbo.resize( 16, 16 ); 317 | testContext.assertNoError(); 318 | 319 | }) 320 | 321 | 322 | it( "before resize make complete fbo ", function(){ 323 | var depth = new Renderbuffer( gl, gl.DEPTH_COMPONENT16 ); 324 | 325 | fbo.bind() 326 | fbo.attach( gl.DEPTH_ATTACHMENT, depth ); 327 | fbo.resize( 4, 8 ); 328 | 329 | expect( fbo.isValid() ).to.be.ok() 330 | testContext.assertNoError(); 331 | 332 | }) 333 | 334 | }) 335 | 336 | 337 | 338 | 339 | describe( "attach depth/stencil helper", function(){ 340 | 341 | var fbo, tex, color; 342 | 343 | beforeEach( function() { 344 | tex = new Texture2D(gl) 345 | fbo = new Fbo( gl ); 346 | fbo.bind(); 347 | color = fbo.attach( gl.COLOR_ATTACHMENT0, tex ); 348 | }); 349 | 350 | 351 | afterEach( function(){ 352 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 353 | fbo.dispose() 354 | }) 355 | 356 | 357 | it( " depth only", function(){ 358 | 359 | fbo.attachDepth(); 360 | var att = fbo.getDepth() 361 | 362 | expect( att.format ).to.be( gl.DEPTH_COMPONENT16 ) 363 | testContext.assertNoError(); 364 | 365 | }) 366 | 367 | 368 | it( " stencil only", function(){ 369 | 370 | fbo.attachDepth( false, true ); 371 | var att = fbo.getDepth( ) 372 | 373 | expect( att.format ).to.be( gl.STENCIL_INDEX8 ) 374 | testContext.assertNoError(); 375 | 376 | }) 377 | 378 | 379 | it( " both", function(){ 380 | 381 | fbo.attachDepth( true, true ); 382 | var att = fbo.getDepth( ) 383 | 384 | expect( att.format ).to.be( gl.DEPTH_STENCIL ) 385 | testContext.assertNoError(); 386 | 387 | }) 388 | 389 | 390 | 391 | 392 | }) 393 | 394 | 395 | describe( " clear", function(){ 396 | 397 | var fbo, tex, color; 398 | 399 | beforeEach( function() { 400 | tex = new Texture2D(gl) 401 | fbo = new Fbo( gl ); 402 | fbo.bind(); 403 | color = fbo.attach( gl.COLOR_ATTACHMENT0, tex ); 404 | fbo.resize( 16,16 ) 405 | }); 406 | 407 | 408 | afterEach( function(){ 409 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 410 | fbo.dispose() 411 | }) 412 | 413 | 414 | it( " with depth only", function(){ 415 | 416 | fbo.attachDepth(); 417 | fbo.clear(); 418 | testContext.assertNoError(); 419 | 420 | }) 421 | 422 | 423 | it( " with stencil only", function(){ 424 | 425 | fbo.attachDepth( false, true ); 426 | fbo.clear(); 427 | testContext.assertNoError(); 428 | 429 | }) 430 | 431 | 432 | it( " with both", function(){ 433 | 434 | fbo.attachDepth( true, true ); 435 | fbo.clear(); 436 | testContext.assertNoError(); 437 | 438 | }) 439 | 440 | it( " with none", function(){ 441 | 442 | fbo.clear(); 443 | testContext.assertNoError(); 444 | 445 | }) 446 | 447 | 448 | 449 | 450 | }) 451 | 452 | 453 | describe( "@WEBGL2 attach depth/stencil texture helper", function(){ 454 | 455 | var fbo, tex, color; 456 | 457 | beforeEach( function() { 458 | tex = new Texture2D(gl) 459 | fbo = new Fbo( gl ); 460 | fbo.bind(); 461 | color = fbo.attach( gl.COLOR_ATTACHMENT0, tex ); 462 | }); 463 | 464 | 465 | afterEach( function(){ 466 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 467 | fbo.dispose() 468 | }) 469 | 470 | 471 | it( " depth only", function(){ 472 | 473 | fbo.attachDepth( true, false, true ); 474 | var dt = fbo.getDepth() 475 | expect( dt ).to.be.ok() 476 | 477 | testContext.assertNoError(); 478 | 479 | }) 480 | 481 | 482 | 483 | it( " both", function(){ 484 | 485 | fbo.attachDepth( true, true ); 486 | var att = fbo.getDepth( ) 487 | 488 | expect( att ).to.be.ok() 489 | testContext.assertNoError(); 490 | 491 | }) 492 | 493 | 494 | 495 | 496 | }) 497 | 498 | 499 | 500 | 501 | describe( "@WEBGL2 attach second color", function(){ 502 | 503 | 504 | 505 | 506 | 507 | it( " test", function(){ 508 | 509 | var fbo; 510 | 511 | var tex0 = new Texture2D(gl) 512 | var tex1 = new Texture2D(gl) 513 | fbo = new Fbo( gl ); 514 | 515 | 516 | fbo.bind() 517 | fbo.resize( 32, 32 ); 518 | fbo.attach( 0x8CE0 , tex0 ); 519 | fbo.attach( 0x8CE0 + 1 , tex1 ); 520 | 521 | testContext.assertNoError(); 522 | 523 | 524 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 525 | fbo.dispose() 526 | 527 | testContext.assertNoError(); 528 | 529 | }) 530 | 531 | 532 | }) 533 | 534 | 535 | 536 | 537 | }); -------------------------------------------------------------------------------- /src/fbo.ts: -------------------------------------------------------------------------------- 1 | import Texture2D from './texture-2d' 2 | import RenderBuffer from './renderbuffer' 3 | import { GLContext, isWebgl2 } from './types'; 4 | 5 | function isTexture(target: AttachmentTarget): target is Texture2D { 6 | return target.id instanceof WebGLTexture; 7 | } 8 | 9 | 10 | 11 | function assertIsTexture(target: AttachmentTarget|null, msg:string): asserts target is Texture2D { 12 | if( target === null || !isTexture(target) ){ 13 | throw new Error( msg ); 14 | } 15 | } 16 | 17 | /** A target for an Attachment. */ 18 | export type AttachmentTarget = Texture2D | RenderBuffer; 19 | 20 | /** 21 | * This class manages framebuffer attachments. 22 | */ 23 | export class Attachment { 24 | /** The mipmap level of the target texture (must be `0`) */ 25 | level: number; 26 | /** The target (texture or renderbuffer) of this attachment */ 27 | readonly target: AttachmentTarget; 28 | /** Whether the attachment target is a texture or not */ 29 | private _isTexture: boolean; 30 | 31 | /** 32 | * @param {AttachmentTarget} target The texture or renderbuffer to attach 33 | */ 34 | constructor(target: AttachmentTarget) { 35 | this.target = target; 36 | this.level = 0; 37 | 38 | this._isTexture = isTexture(target); 39 | } 40 | 41 | /** 42 | * Getter for the `_isTexture` property. 43 | * Know whether the attachment target is a texture or not. 44 | */ 45 | isTexture() { 46 | return this._isTexture; 47 | } 48 | 49 | /** 50 | * Resize the attachment target. 51 | * @param {number} w The new width 52 | * @param {number} h The new height 53 | */ 54 | _resize(w: number, h: number) { 55 | if (w > 0 && h > 0) { 56 | // const target : 57 | if (isTexture(this.target)) { 58 | this.target.fromData(w, h, null); 59 | } else { 60 | this.target.resize(w, h); 61 | this.target.allocate(); 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Attach the target to the FBO. 68 | * The FBO must be explicitely bound before calling this method. 69 | * @param {GLenum} bindingPoint The binding point of the attachment (`GL_COLOR_ATTACHMENT0`, `GL_DEPTH_ATTACHMENT`, etc.) 70 | */ 71 | _attach(bindingPoint: GLenum) { 72 | var gl = this.target.gl; 73 | if (this._isTexture) { 74 | gl.framebufferTexture2D(gl.FRAMEBUFFER, bindingPoint, gl.TEXTURE_2D, this.target.id, this.level); 75 | } else { 76 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, bindingPoint, gl.RENDERBUFFER, this.target.id); 77 | } 78 | } 79 | 80 | /** 81 | * Detach the target from the FBO. 82 | * The FBO must be explicitely bound before calling this method. 83 | * @param {GLenum} bindingPoint The binding point of the attachment (`GL_COLOR_ATTACHMENT0`, `GL_DEPTH_ATTACHMENT`, etc.) 84 | */ 85 | _detach(bindingPoint: GLenum) { 86 | var gl = this.target.gl; 87 | if (this._isTexture) { 88 | gl.framebufferTexture2D(gl.FRAMEBUFFER, bindingPoint, gl.TEXTURE_2D, null, this.level); 89 | } else { 90 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, bindingPoint, gl.RENDERBUFFER, null); 91 | } 92 | } 93 | 94 | /** 95 | * Delete all webgl objects related to this Attachment. 96 | */ 97 | dispose() { 98 | this.target.dispose(); 99 | } 100 | } 101 | 102 | /** 103 | * This class manages framebuffers and their attachments. 104 | */ 105 | class Fbo { 106 | /** The webgl context this FBO belongs to */ 107 | readonly gl: GLContext; 108 | /** The underlying webgl framebuffer */ 109 | readonly fbo: WebGLFramebuffer; 110 | 111 | /** The list of the attachments setup for this FBO */ 112 | readonly attachmentsList: Attachment[]; 113 | /** The list of the bindingPoint/attachment pairs setup for this FBO */ 114 | attachments: Record; 115 | 116 | /** The width of this FBO */ 117 | width: number; 118 | /** The height of this FBO */ 119 | height: number; 120 | 121 | /** 122 | * @param {GLContext} gl The webgl context this FBO belongs to 123 | */ 124 | constructor( gl: GLContext ) { 125 | this.gl = gl; 126 | this.width = 0; 127 | this.height = 0; 128 | 129 | this.fbo = gl.createFramebuffer(); 130 | this.bind(); 131 | 132 | this.attachments = {}; 133 | this.attachmentsList = []; 134 | } 135 | 136 | /** 137 | * Add an attachment to this FBO. 138 | * The FBO must be explicitely bound before calling this method. 139 | * @param {GLenum} bindingPoint The binding point of the attachment (`GL_COLOR_ATTACHMENT0`, `GL_DEPTH_ATTACHMENT`, etc.) 140 | * @param {AttachmentTarget} res The texture or buffer to attach 141 | */ 142 | attach(bindingPoint: GLenum, res: AttachmentTarget): Attachment { 143 | const attachment = new Attachment(res); 144 | bindingPoint = 0 | bindingPoint; 145 | 146 | this.detach(bindingPoint); 147 | 148 | this.attachments[bindingPoint.toString()] = attachment; 149 | this.attachmentsList.push(attachment); 150 | 151 | attachment._resize(this.width, this.height); 152 | attachment._attach(bindingPoint); 153 | return attachment; 154 | } 155 | 156 | /** 157 | * Remove an attachment from this FBO. 158 | * The FBO must be explicitely bound before calling this method. 159 | * @param {GLenum} bindingPoint The binding point of the attachment (`GL_COLOR_ATTACHMENT0`, `GL_DEPTH_ATTACHMENT`, etc.) 160 | */ 161 | detach(bindingPoint: GLenum) { 162 | const att = this.attachments[bindingPoint.toString()]; 163 | if (att !== undefined) { 164 | const index = this.attachmentsList.indexOf(att); 165 | this.attachmentsList.splice(index, 1); 166 | att._detach(bindingPoint); 167 | } 168 | delete this.attachments[bindingPoint.toString()]; 169 | } 170 | 171 | /** 172 | * Get a specific attachment of this FBO (if it exists). 173 | * @param {GLenum} bindingPoint The binding point of the attachment (`GL_COLOR_ATTACHMENT0`, `GL_DEPTH_ATTACHMENT`, etc.) 174 | */ 175 | getAttachment(bindingPoint: GLenum): Attachment | null { 176 | const att = this.attachments[bindingPoint.toString()]; 177 | if (att !== undefined) { 178 | return att; 179 | } 180 | return null; 181 | } 182 | 183 | /** 184 | * Get the color attachment of this FBO (if it exists). 185 | * @param {number} [index=0] The color attachment index 186 | */ 187 | getColor(index: number = 0): AttachmentTarget | null { 188 | const att = this.getAttachment(0x8ce0 + index); // COLOR_ATTACHMENT 189 | return att ? att.target : null; 190 | } 191 | 192 | /** 193 | * Get the color texture of this FBO (if it exists). 194 | * @param {number} [index=0] The color attachment index 195 | */ 196 | getColorTexture(index: number = 0): Texture2D { 197 | const res = this.getColor( index ); 198 | assertIsTexture( res, `Color attachment ${index} is not a texture.` ); 199 | return res; 200 | } 201 | 202 | /** 203 | * Get the depth attachment of this FBO (if it exists). 204 | */ 205 | getDepth(): AttachmentTarget | null { 206 | const att = 207 | this.getAttachment(0x8d00) || // DEPTH_ATTACHMENT 208 | this.getAttachment(0x8d20) || // STENCIL_ATTACHMENT; 209 | this.getAttachment(0x821a); // DEPTH_STENCIL_ATTACHMENT; 210 | return att ? att.target : null; 211 | } 212 | 213 | /** 214 | * Attach a texture to the color attachment 0 of this FBO. 215 | * The FBO must be explicitely bound before calling this method. 216 | * @param {GLenum} [format=GL_RGB] The pixel format of the texture (`GL_RGB`, `GL_RGBA`, etc.), defaults to `GL_RGB` 217 | * @param {GLenum} [type=GL_UNSIGNED_BYTE] The pixel data type of the texture (`GL_UNSIGNED_BYTE`, `GL_FLOAT`, etc.), defaults to `GL_UNSIGNED_BYTE` 218 | * @param {GLenum} [internal=format] The pixel internal format of the texture, defaults to the `format` parameter value 219 | */ 220 | attachColor(format?: GLenum, type?: GLenum, internal?: GLenum) { 221 | const t = new Texture2D(this.gl, format, type, internal); 222 | return this.attach(0x8ce0, t); 223 | } 224 | 225 | /** 226 | * Attach a renderbuffer/texture to the depth/stencil attachment of this FBO. 227 | * The FBO must be explicitely bound before calling this method. 228 | * @param {boolean} [depth=true] Add depth component or not 229 | * @param {boolean} [stencil=false] Add stencil component or not 230 | * @param {boolean} [useTexture=false] Use a Texture2D instead of a RenderBuffer. If true, the depth param must also be true. You also must ensure Depth Texture capability is available on your context. 231 | */ 232 | attachDepth(depth: boolean = true, stencil: boolean = false, useTexture: boolean = false) { 233 | let attachment: AttachmentTarget; 234 | 235 | if (useTexture) { 236 | const cfg = dsTextureConfig(this.gl, stencil); 237 | attachment = new Texture2D(this.gl, cfg.format, cfg.type, cfg.internal); 238 | } else { 239 | attachment = new RenderBuffer(this.gl, dsRenderbufferStorage(depth, stencil)); 240 | } 241 | 242 | return this.attach(dsAttachmentPoint(depth, stencil), attachment); 243 | } 244 | 245 | /** 246 | * Resize the FBO and its attachments. 247 | * @param {number} w The new width 248 | * @param {number} h The new height 249 | */ 250 | resize(w: number, h: number) { 251 | if (this.width !== w || this.height !== h) { 252 | this.width = w | 0; 253 | this.height = h | 0; 254 | this._allocate(); 255 | } 256 | } 257 | 258 | /** 259 | * Bind the underlying webgl framebuffer. 260 | */ 261 | bind() { 262 | const gl = this.gl; 263 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo); 264 | } 265 | 266 | /** 267 | * Clear all buffers (color, depth and stencil). 268 | */ 269 | clear() { 270 | // COLOR | DEPTH | STENCIL 271 | this.gl.clear(0x4500); 272 | } 273 | 274 | /** 275 | * Set the webgl viewport to the size of this FBO. 276 | */ 277 | defaultViewport() { 278 | this.gl.viewport(0, 0, this.width, this.height); 279 | } 280 | 281 | /** 282 | * Check if the FBO is valid. 283 | * The FBO must be explicitely bound before calling this method. 284 | */ 285 | isValid() { 286 | const gl = this.gl; 287 | return gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE; 288 | } 289 | 290 | /** 291 | * Delete all webgl objects related to this FBO. 292 | */ 293 | dispose() { 294 | const gl = this.gl; 295 | gl.deleteFramebuffer(this.fbo); 296 | 297 | for (var i = 0; i < this.attachmentsList.length; i++) { 298 | this.attachmentsList[i].dispose(); 299 | } 300 | 301 | this.attachmentsList.length = 0; 302 | this.attachments = {}; 303 | 304 | } 305 | 306 | /** 307 | * Resize all attachments to the FBO size. 308 | */ 309 | _allocate() { 310 | for (var attachment of this.attachmentsList) { 311 | attachment._resize(this.width, this.height); 312 | } 313 | } 314 | } 315 | 316 | // ============================================ 317 | // DEPTH AND STENCIL ENUMS HELPERS 318 | // ============================================ 319 | 320 | function dsFlag(depth: number, stencil: number) { 321 | return depth | (stencil << 1); 322 | } 323 | 324 | function dsAttachmentPoint(depth: any, stencil: any) { 325 | switch (dsFlag(depth, stencil)) { 326 | case 1: 327 | return 0x8d00; // DEPTH_ATTACHMENT 328 | case 2: 329 | return 0x8d20; // STENCIL_ATTACHMENT; 330 | case 3: 331 | return 0x821a; // DEPTH_STENCIL_ATTACHMENT; 332 | default: 333 | return 0; 334 | } 335 | } 336 | 337 | // 338 | function dsRenderbufferStorage(depth: any, stencil: any) { 339 | switch (dsFlag(depth, stencil)) { 340 | case 1: 341 | return 0x81a5; // DEPTH_COMPONENT16; 342 | case 2: 343 | return 0x8d48; // STENCIL_INDEX8; 344 | case 3: 345 | return 0x84f9; // DEPTH_STENCIL; 346 | default: 347 | return 0; 348 | } 349 | } 350 | 351 | // depth texture internal format 352 | // must return internal:undefined in webgl 1, so "internal" will match "format" 353 | function dsTextureConfig(gl: GLContext, stencil: boolean) { 354 | if (stencil) { 355 | // DEPTH_STENCIL | DEPTH24_STENCIL8 | UNSIGNED_INT_24_8_WEBGL 356 | return { format: 0x84f9, type: 0x84fa, internal: isWebgl2(gl) ? gl.DEPTH24_STENCIL8 : gl.DEPTH_STENCIL }; 357 | } 358 | // DEPTH_COMPONENT | DEPTH_COMPONENT24 | UNSIGNED_INT 359 | return { format: 0x1902, type: 0x1405, internal: isWebgl2(gl) ? gl.DEPTH_COMPONENT24 : gl.DEPTH_COMPONENT }; 360 | } 361 | 362 | 363 | export default Fbo -------------------------------------------------------------------------------- /src/program.ts: -------------------------------------------------------------------------------- 1 | import { GLContext, isWebgl2 } from './types'; 2 | 3 | interface CompilationContext { 4 | texIndex: number; 5 | ublockIndex: number; 6 | } 7 | 8 | 9 | let _UID: number = 0; 10 | 11 | /** 12 | * This class provides provides shader compilation and webgl program linking functionality. 13 | * It also give you convenient access to active uniforms and attributes. 14 | */ 15 | class Program { 16 | /** Can be set to true to check and log compilation and linking errors */ 17 | static debug: boolean = false; 18 | /** The webgl context this Program belongs to */ 19 | readonly gl: GLContext; 20 | 21 | /** The underlying webgl program */ 22 | readonly program: WebGLProgram; 23 | /** The underlying webgl vertex shader */ 24 | readonly vShader: WebGLShader; 25 | /** The underlying webgl fragment shader */ 26 | readonly fShader: WebGLShader; 27 | 28 | /** The list of uniform & attribute names */ 29 | protected dyns: string[]; 30 | 31 | /** Whether the program is ready to use or not */ 32 | ready: boolean; 33 | /** Unique id for the program */ 34 | _uid: number; 35 | /** Unique id for the program that updates every time it is compiled */ 36 | _cuid: number; 37 | 38 | [k: string]: any; 39 | 40 | /** 41 | * You can pass optional shader code to immediatly compile shaders or compile them later with {@link Program#compile}. 42 | * @param {GLContext} gl The webgl context this Program belongs to 43 | * @param {String} [vert] The vertex shader code 44 | * @param {String} [frag] The fragment shader code 45 | * @param {String} [defs] A string to prepend to both fragment and vertex shader code 46 | */ 47 | constructor(gl: GLContext, vert?: string, frag?: string, defs?: string) { 48 | this.gl = gl; 49 | this.program = gl.createProgram(); 50 | this.vShader = gl.createShader(gl.VERTEX_SHADER); 51 | this.fShader = gl.createShader(gl.FRAGMENT_SHADER); 52 | this.dyns = []; 53 | this.ready = false; 54 | gl.attachShader(this.program, this.vShader); 55 | gl.attachShader(this.program, this.fShader); 56 | 57 | this._uid = _UID++ | 0; 58 | this._cuid = _UID++ | 0; 59 | 60 | if (vert !== undefined && frag !== undefined) { 61 | this.compile(vert, frag, defs); 62 | } 63 | } 64 | 65 | /** 66 | * Bind the underlying webgl program. 67 | * Shortcut for `gl.useProgram()`. 68 | */ 69 | use() { 70 | if (!this.ready) { 71 | this._grabParameters(); 72 | } 73 | this.gl.useProgram(this.program); 74 | } 75 | 76 | /** 77 | * Alias for {@link Program#use}. 78 | */ 79 | bind() { 80 | this.use(); 81 | } 82 | 83 | /** 84 | * Compile vertex and fragment shader, then link webgl program. 85 | * This method can be safely called several times. 86 | * @param {String} vert The vertex shader code 87 | * @param {String} frag The fragment shader code 88 | * @param {String} [prefix] A string to prepend to both fragment and vertex shader code 89 | */ 90 | compile(vert: string, frag: string, prefix?: string ): boolean { 91 | this.ready = false; 92 | 93 | prefix = prefix === undefined ? '' : prefix + '\n'; 94 | 95 | const gl = this.gl; 96 | 97 | if (!(compileShader(gl, this.fShader, prefix + frag) && compileShader(gl, this.vShader, prefix + vert))) { 98 | return false; 99 | } 100 | 101 | gl.linkProgram(this.program); 102 | 103 | if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { 104 | Program.debug && warn(gl.getProgramInfoLog(this.program)); 105 | return false; 106 | } 107 | 108 | // delete old accessors 109 | while (this.dyns.length > 0) { 110 | delete this[this.dyns.pop()]; 111 | } 112 | 113 | this._cuid = _UID++ | 0; 114 | 115 | return true; 116 | } 117 | 118 | /** 119 | * Delete all webgl objects related to this Program. 120 | */ 121 | dispose() { 122 | if (this.gl !== null) { 123 | this.gl.deleteProgram(this.program); 124 | this.gl.deleteShader(this.fShader); 125 | this.gl.deleteShader(this.vShader); 126 | } 127 | } 128 | 129 | /* 130 | * List all uniforms and attributes and create helper function on Program instance 131 | * for a uniform vec3 uDirection; 132 | * create a method 133 | * program.uDirection( 1, 0, 0 ); 134 | */ 135 | /** 136 | * List all uniforms and attributes and create a helper function for each on Program instance. 137 | * 138 | * @example 139 | * For a uniform 140 | * ```glsl 141 | * uniform vec3 uDirection; 142 | * ``` 143 | * it creates a method 144 | * ```js 145 | * program.uDirection(1, 0, 0); 146 | * ``` 147 | */ 148 | _grabParameters() { 149 | const gl = this.gl, 150 | prg = this.program; 151 | 152 | const context: CompilationContext = { 153 | texIndex: 0, 154 | ublockIndex: 0, 155 | }; 156 | 157 | // Uniforms 158 | // ======== 159 | 160 | const numUniforms: number = gl.getProgramParameter(prg, gl.ACTIVE_UNIFORMS); 161 | 162 | for (var uniformIndex = 0; uniformIndex < numUniforms; ++uniformIndex) { 163 | var uniform = gl.getActiveUniform(prg, uniformIndex); 164 | 165 | // safari 8.0 issue, 166 | // when recompiling shader and link the progam again, old uniforms are kept in ACTIVE_UNIFORMS count but return null here 167 | if (uniform === null) { 168 | gl.getError(); // also flush error 169 | continue; 170 | } 171 | 172 | var uName = uniform.name, 173 | n = uName.indexOf('['); 174 | 175 | if (n >= 0) { 176 | uName = uName.substring(0, n); 177 | } 178 | 179 | var uLocation = gl.getUniformLocation(prg, uniform.name); 180 | 181 | // in Webgl2 location can be null here if uniform is member of a uniform block 182 | if (uLocation !== null) { 183 | this[uName] = getUniformSetter(uniform.type, uLocation, gl, context); 184 | this.dyns.push(uName); 185 | } 186 | } 187 | 188 | // Attributes 189 | // ========== 190 | 191 | const numAttribs = gl.getProgramParameter(prg, gl.ACTIVE_ATTRIBUTES); 192 | 193 | for (var aIndex = 0; aIndex < numAttribs; ++aIndex) { 194 | var attribName = (gl.getActiveAttrib(prg, aIndex)).name; 195 | var aLocation = gl.getAttribLocation(prg, attribName); 196 | this[attribName] = getAttribAccess(aLocation); 197 | this.dyns.push(attribName); 198 | } 199 | 200 | // UniformBlock 201 | // ============ 202 | 203 | if (isWebgl2(gl)) { 204 | const numBlocks = gl.getProgramParameter(prg, gl.ACTIVE_UNIFORM_BLOCKS); 205 | 206 | for (var blockIndex = 0; blockIndex < numBlocks; ++blockIndex) { 207 | var blockName = gl.getActiveUniformBlockName(prg, blockIndex); 208 | this[blockName] = getUniformBufferSetFunction(blockIndex, gl, context); 209 | this.dyns.push(blockName); 210 | } 211 | } 212 | 213 | this.ready = true; 214 | } 215 | } 216 | 217 | /* 218 | * internal logs 219 | */ 220 | function warn(str: string|null) { 221 | console.warn(str); 222 | } 223 | 224 | // ------------------------------------------------- 225 | // UTILITIES 226 | // ------------------------------------------------- 227 | 228 | /* 229 | * Shader logging utilities 230 | */ 231 | 232 | const __pads: Array = ['', ' ', ' ', ' ', '']; 233 | 234 | function appendLine(l: string, i: number): string { 235 | return __pads[String(i + 1).length] + (i + 1) + ': ' + l; 236 | } 237 | 238 | /* 239 | * Format shader code 240 | * add padded lines number 241 | */ 242 | function formatCode(shader: string): string { 243 | return shader 244 | .split('\n') 245 | .map(appendLine) 246 | .join('\n'); 247 | } 248 | 249 | const ErrLineRegex = /^ERROR:\s?(\d+):(\d+)/ 250 | 251 | function reportCompileError( infos : string, source : string ){ 252 | const sourceLines = source.split('\n'); 253 | infos = infos.split('\n').map( (line)=>{ 254 | const rr = ErrLineRegex.exec(line); 255 | if( rr ) { line += '\n > ' + sourceLines[parseInt(rr[2])-1] } 256 | return line; 257 | }).join('\n'); 258 | source = formatCode(source); 259 | 260 | warn( infos ); 261 | warn( source ); 262 | } 263 | 264 | 265 | /* 266 | * Shader compilation utility 267 | */ 268 | function compileShader(gl: GLContext, shader: WebGLShader, code: string): boolean { 269 | gl.shaderSource(shader, code); 270 | gl.compileShader(shader); 271 | 272 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 273 | Program.debug && reportCompileError( gl.getShaderInfoLog(shader)!, code ); 274 | return false; 275 | } 276 | 277 | return true; 278 | } 279 | 280 | 281 | 282 | 283 | const USetFMap: Record = {}; 284 | USetFMap[String(5126) /*FLOAT */] = '1f'; 285 | USetFMap[String(35664) /*FLOAT_VEC2 */] = '2f'; 286 | USetFMap[String(35665) /*FLOAT_VEC3 */] = '3f'; 287 | USetFMap[String(35666) /*FLOAT_VEC4 */] = '4f'; 288 | USetFMap[String(35670) /*BOOL */] = 289 | USetFMap[String(5124) /*INT */] = 290 | USetFMap[String(35678) /*SAMPLER_2D */] = USetFMap[String(35680) /*SAMPLER_CUBE*/] = USetFMap[String(35866) /*SAMPLER_2D_ARRAY*/] = '1i'; 291 | USetFMap[String(35671) /*BOOL_VEC2 */] = USetFMap[String(35667) /*INT_VEC2 */] = '2i'; 292 | USetFMap[String(35672) /*BOOL_VEC3 */] = USetFMap[String(35668) /*INT_VEC3 */] = '3i'; 293 | USetFMap[String(35673) /*BOOL_VEC4 */] = USetFMap[String(35669) /*INT_VEC4 */] = '4i'; 294 | USetFMap[String(35674) /*FLOAT_MAT2 */] = 'Matrix2f'; 295 | USetFMap[String(35675) /*FLOAT_MAT3 */] = 'Matrix3f'; 296 | USetFMap[String(35676) /*FLOAT_MAT4 */] = 'Matrix4f'; 297 | 298 | /* 299 | * Uniform upload utilities 300 | */ 301 | 302 | function getUniformSetFunctionName(type: number): string { 303 | return 'uniform' + USetFMap[String(type)]; 304 | } 305 | 306 | /* 307 | * For a given uniform's type, return the proper setter function 308 | */ 309 | function getUniformSetter(type: number, location: WebGLUniformLocation, gl: GLContext, context: CompilationContext) { 310 | switch (type) { 311 | case gl.FLOAT_MAT2: 312 | case gl.FLOAT_MAT3: 313 | case gl.FLOAT_MAT4: 314 | return getMatrixSetFunction(type, location, gl, context); 315 | 316 | case gl.SAMPLER_2D: 317 | case gl.SAMPLER_CUBE: 318 | case 0x8b62: //gl.SAMPLER_2D_SHADOW: 319 | case 0x8b5f: //gl.SAMPLER_3D: 320 | case 0x8DC1: // gl.SAMPLER_2D_ARRAY: 321 | return getSamplerSetFunction(type, location, gl, context); 322 | 323 | default: 324 | return getUniformSetFunction(type, location, gl, context); 325 | } 326 | } 327 | 328 | /* 329 | * setter factory for vector uniforms 330 | * return a function wich take both array or arguments 331 | */ 332 | function getUniformSetFunction( 333 | type: number, 334 | location: WebGLUniformLocation, 335 | gl: GLContext, 336 | context: CompilationContext, 337 | ) { 338 | context; 339 | const fname = getUniformSetFunctionName(type); 340 | return function(...args: number[] | [Float32List]) { 341 | if (args.length === 1 && (args[0] as []).length != undefined ) { 342 | (gl as any)[fname + 'v'](location, args[0]); 343 | } else if (args.length > 0) { 344 | (gl as any)[fname](location, ...args); 345 | } 346 | return location; 347 | }; 348 | } 349 | 350 | /* 351 | * setter factory for matrix uniforms 352 | */ 353 | function getMatrixSetFunction( 354 | type: number, 355 | location: WebGLUniformLocation, 356 | gl: GLContext, 357 | context: CompilationContext, 358 | ) { 359 | context; 360 | const fname = getUniformSetFunctionName(type); 361 | return function() { 362 | if (arguments.length > 0 && arguments[0].length !== undefined) { 363 | var transpose = arguments.length > 1 ? !!arguments[1] : false; 364 | (gl as any)[fname + 'v'](location, transpose, arguments[0]); 365 | } 366 | return location; 367 | }; 368 | } 369 | 370 | /* 371 | * setter factory for sampler uniforms 372 | */ 373 | function getSamplerSetFunction( 374 | type: number, 375 | location: WebGLUniformLocation, 376 | gl: GLContext, 377 | context: CompilationContext, 378 | ) { 379 | const unit = context.texIndex++; 380 | return function() { 381 | if (arguments.length === 1) { 382 | if (arguments[0].bind !== undefined) { 383 | // is texture 384 | arguments[0].bind(unit); 385 | gl.uniform1i(location, unit); 386 | } else { 387 | gl.uniform1i(location, arguments[0]); 388 | } 389 | } 390 | return location; 391 | }; 392 | } 393 | 394 | /* 395 | * setter factory for uniform buffers 396 | * can be 397 | * f( buffer ) 398 | * buffer is bound to auto increment binding point unit and block binding reset to this point 399 | * f( unit ) 400 | * only set the block binding point to given unit 401 | */ 402 | function getUniformBufferSetFunction(index: number, gl: WebGL2RenderingContext, context: CompilationContext) { 403 | const unit = context.ublockIndex++; 404 | return function( this: Program ) { 405 | if (arguments.length === 1) { 406 | if (arguments[0] instanceof WebGLBuffer) { 407 | // is buffer 408 | gl.uniformBlockBinding( this.program, index, unit); 409 | gl.bindBufferBase(gl.UNIFORM_BUFFER, unit, arguments[0]); 410 | } else { 411 | gl.uniformBlockBinding( this.program, index, arguments[0]); 412 | } 413 | } 414 | return index; 415 | }; 416 | } 417 | 418 | /* 419 | * getter factory for attributes 420 | */ 421 | function getAttribAccess(attrib:number) { 422 | return function() { 423 | return attrib; 424 | }; 425 | } 426 | 427 | 428 | export default Program -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | --------------------------------------------------------------------------------