├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── examples ├── cube.html ├── cube.ts ├── math.ts ├── offscreen.html ├── offscreen.ts ├── triangle.html └── triangle.ts ├── src ├── altai.ts ├── enums.ts ├── options.ts └── types.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | *.js 4 | *.map 5 | 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Chrome against localhost, with sourcemaps", 6 | "type": "chrome", 7 | "request": "launch", 8 | "url": "http://localhost:8080", 9 | "sourceMaps": true, 10 | "webRoot": "${workspaceRoot}" 11 | }, 12 | { 13 | "name": "Attach to Chrome, with sourcemaps", 14 | "type": "chrome", 15 | "request": "attach", 16 | "port": 9222, 17 | "sourceMaps": true, 18 | "webRoot": "${workspaceRoot}" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/*.js": true, 5 | "**/*.map": true 6 | } 7 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "command": "tsc", 6 | "args": [ 7 | "-p", 8 | "." 9 | ], 10 | "problemMatcher": "$tsc", 11 | "tasks": [ 12 | { 13 | "label": "tsc", 14 | "type": "shell", 15 | "command": "tsc", 16 | "args": [ 17 | "-p", 18 | "." 19 | ], 20 | "problemMatcher": "$tsc", 21 | "group": { 22 | "_id": "build", 23 | "isDefault": false 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Andre Weissflog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Altai 2 | 3 | Work In Progress! 4 | 5 | A 'Modern 3D-API' wrapper for WebGL in TypeScript, or: 6 | 7 | Like the Oryol Gfx module, but in TS. 8 | -------------------------------------------------------------------------------- /examples/cube.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/cube.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | function cube() { 5 | const WIDTH = 600; 6 | const HEIGHT = 400; 7 | 8 | let gfx = new altai.Gfx({ 9 | UseWebGL2: true, 10 | Width: WIDTH, 11 | Height: HEIGHT, 12 | Depth: true, 13 | Canvas: "canvas", 14 | AntiAlias: true 15 | }); 16 | 17 | let pass = gfx.makePass({ 18 | ColorAttachments: [ { ClearColor: [ 0.3, 0.4, 0.5, 1.0 ] } ] 19 | }) 20 | 21 | let vertexBuffer = gfx.makeBuffer({ 22 | Type: altai.BufferType.VertexBuffer, 23 | Data: new Float32Array([ 24 | -1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 25 | 1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 26 | 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 27 | -1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 28 | 29 | -1.0, -1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 30 | 1.0, -1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 31 | 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 32 | -1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 33 | 34 | -1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 1.0, 35 | -1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 1.0, 36 | -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 37 | -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 38 | 39 | 1.0, -1.0, -1.0, 1.0, 1.0, 0.0, 1.0, 40 | 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, 1.0, 41 | 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 42 | 1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 43 | 44 | -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 45 | -1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 46 | 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 47 | 1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 48 | 49 | -1.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 50 | -1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 51 | 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 52 | 1.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0 53 | ]) 54 | }) 55 | let indexBuffer = gfx.makeBuffer({ 56 | Type: altai.BufferType.IndexBuffer, 57 | Data: new Uint16Array([ 58 | 0, 1, 2, 0, 2, 3, 59 | 4, 5, 6, 4, 6, 7, 60 | 8, 9, 10, 8, 10, 11, 61 | 12, 13, 14, 12, 14, 15, 62 | 16, 17, 18, 16, 18, 19, 63 | 20, 21, 22, 20, 22, 23 64 | ]) 65 | }) 66 | 67 | let shader = gfx.makeShader({ 68 | VertexShader: ` 69 | uniform mat4 mvp; 70 | attribute vec4 position; 71 | attribute vec4 color; 72 | varying lowp vec4 vColor; 73 | void main(void) { 74 | gl_Position = mvp * position; 75 | vColor = color; 76 | }`, 77 | FragmentShader: ` 78 | varying lowp vec4 vColor; 79 | void main(void) { 80 | gl_FragColor = vColor; 81 | }` 82 | }); 83 | 84 | let pipeline = gfx.makePipeline({ 85 | Shader: shader, 86 | VertexLayouts: [{ 87 | Components: [ 88 | [ "position", altai.VertexFormat.Float3 ], 89 | [ "color", altai.VertexFormat.Float4 ], 90 | ] 91 | }], 92 | IndexFormat: altai.IndexFormat.UInt16, 93 | DepthCmpFunc: altai.CompareFunc.LessEqual, 94 | DepthWriteEnabled: true, 95 | CullFaceEnabled: false 96 | }); 97 | 98 | let drawState = gfx.makeDrawState({ 99 | Pipeline: pipeline, 100 | IndexBuffer: indexBuffer, 101 | VertexBuffers: [ vertexBuffer ], 102 | }); 103 | 104 | let angleX = 0.0; 105 | let angleY = 0.0; 106 | const proj = mat4.perspective_fov(deg2rad(45.0), WIDTH, HEIGHT, 0.01, 100.0); 107 | const view = mat4.identity(); 108 | 109 | function draw() { 110 | angleX += 0.01; 111 | angleY += 0.02; 112 | let model = mat4.translate(mat4.identity(), 0.0, 0.0, -6.0); 113 | model = mat4.rotate(model, angleX, 1.0, 0.0, 0.0); 114 | model = mat4.rotate(model, angleY, 0.0, 1.0, 0.0); 115 | let mvp = mat4.mul(proj, mat4.mul(view, model)); 116 | gfx.beginPass(pass); 117 | gfx.applyDrawState(drawState); 118 | gfx.applyUniforms({ 119 | "mvp": mvp.values(), 120 | }) 121 | gfx.draw(0, 36); 122 | gfx.endPass(); 123 | gfx.commitFrame(draw); 124 | } 125 | draw(); 126 | } -------------------------------------------------------------------------------- /examples/math.ts: -------------------------------------------------------------------------------- 1 | function deg2rad(deg: number) { 2 | return (deg * Math.PI)/180.0; 3 | } 4 | 5 | function rad2deg(rad: number) { 6 | return (rad * 180.0) / Math.PI; 7 | } 8 | 9 | class vec4 { 10 | public x: number; 11 | public y: number; 12 | public z: number; 13 | public w: number; 14 | 15 | public constructor(x: number, y: number, z: number, w: number) { 16 | this.x=x; this.y=y; this.z=z; this.w=w; 17 | } 18 | 19 | public copy(): vec4 { 20 | return new vec4(this.x, this.y, this.z, this.w); 21 | } 22 | 23 | public values(): number[] { 24 | return [this.x, this.y, this.z, this.w]; 25 | } 26 | 27 | public len(): number { 28 | return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w); 29 | } 30 | 31 | public static normalize(v: vec4): vec4 { 32 | const l = v.len(); 33 | if (l > 0.0) { 34 | return new vec4(v.x/l, v.y/l, v.z/l, v.w/l); 35 | } 36 | else { 37 | return new vec4(0.0, 0.0, 0.0, 0.0); 38 | } 39 | } 40 | 41 | public static add(v0: vec4, v1: vec4): vec4 { 42 | return new vec4(v0.x+v1.x, v0.y+v1.y, v0.z+v1.z, v0.w+v1.w); 43 | } 44 | 45 | public static add3(v0: vec4, v1: vec4, v2: vec4): vec4 { 46 | return new vec4(v0.x+v1.x+v2.x, v0.y+v1.y+v2.y, v0.z+v1.z+v2.z, v0.w+v1.w+v2.w); 47 | } 48 | 49 | public static add4(v0: vec4, v1: vec4, v2: vec4, v3: vec4): vec4 { 50 | return new vec4(v0.x+v1.x+v2.x+v3.x, v0.y+v1.y+v2.y+v3.y, v0.z+v1.z+v2.z+v3.z, v0.w+v1.w+v2.w+v3.w); 51 | } 52 | public static muls(v0: vec4, s: number): vec4 { 53 | return new vec4(v0.x*s, v0.y*s, v0.z*s, v0.w*s); 54 | } 55 | } 56 | 57 | class mat4 { 58 | public v: vec4[]; 59 | 60 | /** construct identity matrix */ 61 | public constructor() { 62 | this.v = [ 63 | new vec4(1.0, 0.0, 0.0, 0.0), 64 | new vec4(0.0, 1.0, 0.0, 0.0), 65 | new vec4(0.0, 0.0, 1.0, 0.0), 66 | new vec4(0.0, 0.0, 0.0, 1.0) 67 | ]; 68 | } 69 | 70 | /** return content as array of numbers */ 71 | public values(): number[] { 72 | return [ 73 | this.v[0].x, this.v[0].y, this.v[0].z, this.v[0].w, 74 | this.v[1].x, this.v[1].y, this.v[1].z, this.v[1].w, 75 | this.v[2].x, this.v[2].y, this.v[2].z, this.v[2].w, 76 | this.v[3].x, this.v[3].y, this.v[3].z, this.v[3].w, 77 | ]; 78 | } 79 | 80 | /** return a new identity matrix */ 81 | public static identity(): mat4 { 82 | return new mat4(); 83 | } 84 | 85 | /** create a new matrix by translating an existing one */ 86 | public static translate(m: mat4, x: number, y: number, z: number): mat4 { 87 | let r = new mat4(); 88 | let t0 = vec4.muls(m.v[0], x); 89 | let t1 = vec4.muls(m.v[1], y); 90 | let t2 = vec4.muls(m.v[2], z); 91 | let t3 = m.v[3]; 92 | r.v[3] = vec4.add(t0, vec4.add(t1, vec4.add(t2, t3))); 93 | return r; 94 | } 95 | 96 | /** create a new matrix by rotating an existing one around an axis */ 97 | public static rotate(m: mat4, a: number, x: number, y: number, z: number): mat4 { 98 | const c = Math.cos(a); 99 | const s = Math.sin(a); 100 | const axis = vec4.normalize(new vec4(x, y, z, 0.0)); 101 | const t = vec4.muls(axis, (1.0 - c)); 102 | 103 | let rot = new mat4(); 104 | rot.v[0].x = c + t.x*axis.x; 105 | rot.v[0].y = t.x*axis.y + s*axis.z; 106 | rot.v[0].z = t.x*axis.z - s*axis.y; 107 | 108 | rot.v[1].x = t.y*axis.x - s*axis.z; 109 | rot.v[1].y = c + t.y*axis.y; 110 | rot.v[1].z = t.y*axis.z + s*axis.x; 111 | 112 | rot.v[2].x = t.z*axis.x + s*axis.y; 113 | rot.v[2].y = t.z*axis.y - s*axis.x; 114 | rot.v[2].z = c + t.z*axis.z; 115 | 116 | let res = new mat4(); 117 | res.v[0] = vec4.add3(vec4.muls(m.v[0],rot.v[0].x), vec4.muls(m.v[1], rot.v[0].y), vec4.muls(m.v[2], rot.v[0].z)); 118 | res.v[1] = vec4.add3(vec4.muls(m.v[0],rot.v[1].x), vec4.muls(m.v[1], rot.v[1].y), vec4.muls(m.v[2], rot.v[1].z)); 119 | res.v[2] = vec4.add3(vec4.muls(m.v[0],rot.v[2].x), vec4.muls(m.v[1], rot.v[2].y), vec4.muls(m.v[2], rot.v[2].z)); 120 | res.v[3] = m.v[3]; 121 | return res; 122 | } 123 | 124 | 125 | /** matrix multiplication */ 126 | public static mul(m1: mat4, m2: mat4): mat4 { 127 | const [a0,a1,a2,a3] = m1.v; 128 | const [b0,b1,b2,b3] = m2.v; 129 | let res = new mat4(); 130 | res.v[0] = vec4.add4(vec4.muls(a0,b0.x), vec4.muls(a1,b0.y), vec4.muls(a2,b0.z), vec4.muls(a3,b0.w)); 131 | res.v[1] = vec4.add4(vec4.muls(a0,b1.x), vec4.muls(a1,b1.y), vec4.muls(a2,b1.z), vec4.muls(a3,b1.w)); 132 | res.v[2] = vec4.add4(vec4.muls(a0,b2.x), vec4.muls(a1,b2.y), vec4.muls(a2,b2.z), vec4.muls(a3,b2.w)); 133 | res.v[3] = vec4.add4(vec4.muls(a0,b3.x), vec4.muls(a1,b3.y), vec4.muls(a2,b3.z), vec4.muls(a3,b3.w)); 134 | return res; 135 | } 136 | 137 | /** 138 | * Return new perspective matrix 139 | * 140 | * @param {number} fov - field-of-view angle in radians 141 | * @param {number} width - width (to compute aspect ratio) 142 | * @param {number} height - height (to compute aspect ratio) 143 | * @param {number} znear - near-plane distance 144 | * @param {number} zfar - far-plane distance 145 | */ 146 | public static perspective_fov(fov: number, width: number, height: number, znear: number, zfar: number): mat4 { 147 | let m = new mat4(); 148 | const rad = fov; 149 | const h = Math.cos(rad * 0.5) / Math.sin(rad * 0.5); 150 | const w = h * height / width; 151 | m.v[0].x = w; 152 | m.v[1].y = h; 153 | m.v[2].z = -(zfar + znear) / (zfar - znear); 154 | m.v[2].w = -1.0; 155 | m.v[3].z = -(2.0 * zfar * znear) / (zfar - znear); 156 | m.v[3].w = 0.0; 157 | return m; 158 | } 159 | } -------------------------------------------------------------------------------- /examples/offscreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/offscreen.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | function offscreen() { 5 | // setup the GL canvas 6 | const WIDTH = 600; 7 | const HEIGHT = 400; 8 | let gfx = new altai.Gfx({ 9 | UseWebGL2: true, 10 | Width: WIDTH, 11 | Height: HEIGHT, 12 | Depth: true, 13 | Canvas: "canvas", 14 | }); 15 | 16 | // create texture and render-pass for offscreen-rendering 17 | let offTex = gfx.makeTexture({ 18 | Type: altai.TextureType.Texture2D, 19 | Width: 256, 20 | Height: 256, 21 | ColorFormat: altai.PixelFormat.RGBA8, 22 | DepthFormat: altai.DepthStencilFormat.DEPTH, 23 | WrapU: altai.Wrap.Repeat, 24 | WrapV: altai.Wrap.Repeat, 25 | MinFilter: altai.Filter.Linear, 26 | MagFilter: altai.Filter.Linear, 27 | }); 28 | let offPass = gfx.makePass({ 29 | ColorAttachments: [{ Texture: offTex, ClearColor: [0.25, 0.25, 0.25, 1.0] }], 30 | DepthAttachment: { Texture: offTex } 31 | }); 32 | 33 | // a vertex and index buffer for a cube 34 | let cubeVB = gfx.makeBuffer({ 35 | Type: altai.BufferType.VertexBuffer, 36 | Data: new Float32Array([ 37 | -1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 38 | 1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 39 | 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 40 | -1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 41 | 42 | -1.0, -1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 43 | 1.0, -1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 44 | 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 45 | -1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 46 | 47 | -1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 1.0, 48 | -1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 1.0, 49 | -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 50 | -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 51 | 52 | 1.0, -1.0, -1.0, 1.0, 1.0, 0.0, 1.0, 53 | 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, 1.0, 54 | 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 55 | 1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 56 | 57 | -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 58 | -1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 59 | 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 60 | 1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 61 | 62 | -1.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 63 | -1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 64 | 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 65 | 1.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0 66 | ]) 67 | }) 68 | let cubeIB = gfx.makeBuffer({ 69 | Type: altai.BufferType.IndexBuffer, 70 | Data: new Uint16Array([ 71 | 0, 1, 2, 0, 2, 3, 72 | 4, 5, 6, 4, 6, 7, 73 | 8, 9, 10, 8, 10, 11, 74 | 12, 13, 14, 12, 14, 15, 75 | 16, 17, 18, 16, 18, 19, 76 | 20, 21, 22, 20, 22, 23 77 | ]) 78 | }) 79 | 80 | // a shader to render a vertex-colored cube 81 | let cubeShader = gfx.makeShader({ 82 | VertexShader: ` 83 | uniform mat4 mvp; 84 | attribute vec4 position; 85 | attribute vec4 color; 86 | varying lowp vec4 vColor; 87 | void main(void) { 88 | gl_Position = mvp * position; 89 | vColor = color; 90 | }`, 91 | FragmentShader: ` 92 | varying lowp vec4 vColor; 93 | void main(void) { 94 | gl_FragColor = vColor; 95 | }` 96 | }); 97 | 98 | // a pipeline object to render the cube 99 | let cubePipeline = gfx.makePipeline({ 100 | Shader: cubeShader, 101 | VertexLayouts: [{ 102 | Components: [ 103 | [ "position", altai.VertexFormat.Float3 ], 104 | [ "color", altai.VertexFormat.Float4 ], 105 | ] 106 | }], 107 | IndexFormat: altai.IndexFormat.UInt16, 108 | DepthCmpFunc: altai.CompareFunc.LessEqual, 109 | DepthWriteEnabled: true, 110 | CullFaceEnabled: false 111 | }); 112 | 113 | // draw state with resource bindings for the cube 114 | let cubeDrawState = gfx.makeDrawState({ 115 | Pipeline: cubePipeline, 116 | IndexBuffer: cubeIB, 117 | VertexBuffers: [ cubeVB ], 118 | }); 119 | 120 | // default render pass 121 | let pass = gfx.makePass({ 122 | ColorAttachments: [{ ClearColor: [0.75, 0.75, 0.75, 1.0] }] 123 | }); 124 | 125 | // a vertex buffer for a textured quad 126 | let quadVB = gfx.makeBuffer({ 127 | Type: altai.BufferType.VertexBuffer, 128 | Data: new Float32Array([ 129 | // positions texcoords 130 | -0.5, -0.5, 0.5, 0.0, 0.0, // first triangle 131 | +0.5, -0.5, 0.5, 2.0, 0.0, 132 | +0.5, +0.5, 0.5, 2.0, 2.0, 133 | 134 | -0.5, -0.5, 0.5, 0.0, 0.0, // second triangle 135 | +0.5, +0.5, 0.5, 2.0, 2.0, 136 | -0.5, +0.5, 0.5, 0.0, 2.0, 137 | ]), 138 | }); 139 | 140 | // shader to render a textured quad into the default framebuffer 141 | let shader = gfx.makeShader({ 142 | VertexShader: ` 143 | attribute vec4 position; 144 | attribute vec2 texcoord0; 145 | varying vec2 uv; 146 | void main(void) { 147 | gl_Position = position; 148 | uv = texcoord0; 149 | }`, 150 | FragmentShader: ` 151 | precision mediump float; 152 | uniform sampler2D texture; 153 | varying vec2 uv; 154 | void main(void) { 155 | gl_FragColor = texture2D(texture, uv); 156 | }` 157 | }); 158 | 159 | // the pipeline-state object for the textured quad 160 | let pipeline = gfx.makePipeline({ 161 | VertexLayouts: [{ 162 | Components: [ 163 | [ "position", altai.VertexFormat.Float3 ], 164 | [ "texcoord0", altai.VertexFormat.Float2 ], 165 | ] 166 | }], 167 | Shader: shader, 168 | DepthCmpFunc: altai.CompareFunc.Always, 169 | DepthWriteEnabled: false, 170 | CullFaceEnabled: false, 171 | }); 172 | 173 | // DrawState object for the texture quad 174 | let drawState = gfx.makeDrawState({ 175 | Pipeline: pipeline, 176 | VertexBuffers: [ quadVB ], 177 | Textures: { 178 | "texture": offTex, 179 | } 180 | }); 181 | 182 | // rotation angles and projection matrix 183 | let angleX = 0.0; 184 | let angleY = 0.0; 185 | const proj = mat4.perspective_fov(deg2rad(45.0), WIDTH, HEIGHT, 0.01, 100.0); 186 | function draw() { 187 | 188 | // model-view-projection matrix for rotating cube 189 | angleX += 0.01; 190 | angleY += 0.02; 191 | let model = mat4.translate(mat4.identity(), 0.0, 0.0, -6.0); 192 | model = mat4.rotate(model, angleX, 1.0, 0.0, 0.0); 193 | model = mat4.rotate(model, angleY, 0.0, 1.0, 0.0); 194 | let mvp = mat4.mul(proj, model); 195 | 196 | // render rotating cube to offscreen render target 197 | gfx.beginPass(offPass); 198 | gfx.applyDrawState(cubeDrawState); 199 | gfx.applyUniforms({ 200 | "mvp": mvp.values() 201 | }); 202 | gfx.draw(0, 36); 203 | gfx.endPass(); 204 | 205 | // render to default framebuffer 206 | gfx.beginPass(pass); 207 | gfx.applyDrawState(drawState); 208 | gfx.draw(0, 6); 209 | gfx.endPass(); 210 | gfx.commitFrame(draw); 211 | } 212 | draw(); 213 | } 214 | -------------------------------------------------------------------------------- /examples/triangle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/triangle.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | function triangle() { 4 | 5 | // create the Altai Gfx object which initializes 6 | // a WebGL context and the default render state 7 | let gfx = new altai.Gfx({ UseWebGL2: true, Width: 400, Height: 300, Canvas: "triangle-canvas" }); 8 | 9 | // create a render-pass 10 | let pass = gfx.makePass({ 11 | ColorAttachments: [ { ClearColor: [0.5, 0.5, 0.5, 1.0] } ] 12 | }); 13 | 14 | // a vertex buffer with positions and colors for a triangle 15 | let vertexBuffer = gfx.makeBuffer({ 16 | Type: altai.BufferType.VertexBuffer, 17 | Data: new Float32Array([ 18 | // positions colors 19 | 0.0, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 20 | 0.5, -0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 21 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 22 | ]), 23 | }); 24 | 25 | // create a vertex/fragment shader pair 26 | let shader = gfx.makeShader({ 27 | VertexShader: ` 28 | attribute vec4 position; 29 | attribute vec4 color; 30 | varying lowp vec4 vColor; 31 | void main(void) { 32 | gl_Position = position; 33 | vColor = color; 34 | }`, 35 | FragmentShader: ` 36 | varying lowp vec4 vColor; 37 | void main(void) { 38 | gl_FragColor = vColor; 39 | }` 40 | }); 41 | 42 | // the pipeline-state object 43 | let pipeline = gfx.makePipeline({ 44 | VertexLayouts: [{ 45 | Components: [ 46 | [ "position", altai.VertexFormat.Float3 ], 47 | [ "color", altai.VertexFormat.Float4 ], 48 | ] 49 | }], 50 | Shader: shader, 51 | DepthCmpFunc: altai.CompareFunc.Always, 52 | DepthWriteEnabled: false, 53 | CullFaceEnabled: false, 54 | }); 55 | 56 | // putting it all together in a draw-state object 57 | let drawState = gfx.makeDrawState({ 58 | Pipeline: pipeline, 59 | VertexBuffers: [ vertexBuffer ], 60 | }); 61 | 62 | function draw() { 63 | gfx.beginPass(pass); 64 | gfx.applyDrawState(drawState); 65 | gfx.draw(0, 3); 66 | gfx.endPass(); 67 | gfx.commitFrame(draw); 68 | } 69 | draw(); 70 | } 71 | -------------------------------------------------------------------------------- /src/altai.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | 'use strict' 4 | 5 | namespace altai { 6 | 7 | const vertexFormatMap = [ 8 | [ 1, WebGLRenderingContext.FLOAT, false ], 9 | [ 2, WebGLRenderingContext.FLOAT, false ], 10 | [ 3, WebGLRenderingContext.FLOAT, false ], 11 | [ 4, WebGLRenderingContext.FLOAT, false ], 12 | [ 4, WebGLRenderingContext.BYTE, false ], 13 | [ 4, WebGLRenderingContext.BYTE, true ], 14 | [ 4, WebGLRenderingContext.UNSIGNED_BYTE, false ], 15 | [ 4, WebGLRenderingContext.UNSIGNED_BYTE, true ], 16 | [ 2, WebGLRenderingContext.SHORT, false ], 17 | [ 2, WebGLRenderingContext.SHORT, true ], 18 | [ 4, WebGLRenderingContext.SHORT, false ], 19 | [ 4, WebGLRenderingContext.SHORT, true ] 20 | ]; 21 | 22 | const cubeFaceMap = [ 23 | WebGLRenderingContext.TEXTURE_CUBE_MAP_POSITIVE_X, 24 | WebGLRenderingContext.TEXTURE_CUBE_MAP_NEGATIVE_X, 25 | WebGLRenderingContext.TEXTURE_CUBE_MAP_POSITIVE_Y, 26 | WebGLRenderingContext.TEXTURE_CUBE_MAP_NEGATIVE_Y, 27 | WebGLRenderingContext.TEXTURE_CUBE_MAP_POSITIVE_Z, 28 | WebGLRenderingContext.TEXTURE_CUBE_MAP_NEGATIVE_Z, 29 | ]; 30 | 31 | /** 32 | * Altai's main interface for resource creation and rendering. 33 | */ 34 | export class Gfx { 35 | private gl: WebGL2RenderingContext | WebGLRenderingContext; 36 | private webgl2: boolean = false; 37 | private cache: PipelineState; 38 | private curProgram: WebGLProgram; 39 | private curIndexFormat: GLenum; 40 | private curIndexSize: number = 0; 41 | private curPrimType: PrimitiveType; 42 | 43 | /** 44 | * Create the Altai interface object. 45 | * 46 | * @param {GfxOptions} options - WebGL context and HTML canvas intialization options 47 | */ 48 | constructor(options: GfxOptions) { 49 | const glContextAttrs = { 50 | alpha: some(options.Alpha, true), 51 | depth: some(options.Depth, true), 52 | stencil: some(options.Stencil, false), 53 | antialias: some(options.AntiAlias, true), 54 | premultipliedAlpha: some(options.PreMultipliedAlpha, true), 55 | preserveDrawingBuffer: some(options.PreserveDrawingBuffer, false), 56 | preferLowPowerToHighPerformance: some(options.PreferLowPowerToHighPerformance, false), 57 | failIfMajorPerformanceCaveat: some(options.FailIfMajorPerformanceCaveat, false) 58 | }; 59 | const canvas = document.getElementById(some(options.Canvas, "canvas")) as HTMLCanvasElement; 60 | if (options.Width != null) { 61 | canvas.width = options.Width; 62 | } 63 | if (options.Height != null) { 64 | canvas.height = options.Height; 65 | } 66 | if (some(options.UseWebGL2, false)) { 67 | this.gl = (canvas.getContext("webgl2", glContextAttrs) || 68 | canvas.getContext("webgl2-experimental", glContextAttrs)) as WebGL2RenderingContext; 69 | if (this.gl != null) { 70 | this.webgl2 = true; 71 | console.log("altai: using webgl2"); 72 | } 73 | } 74 | if (this.gl == null) { 75 | this.gl = (canvas.getContext("webgl", glContextAttrs) || 76 | canvas.getContext("experimental-webgl", glContextAttrs)) as WebGLRenderingContext; 77 | console.log("altai: using webgl1"); 78 | } 79 | this.gl.viewport(0, 0, canvas.width, canvas.height); 80 | this.gl.enable(this.gl.DEPTH_TEST); 81 | 82 | // FIXME: HighDPI handling 83 | 84 | // apply default state 85 | this.cache = new PipelineState({ VertexLayouts: [], Shader: null }); 86 | this.applyState(this.cache, true); 87 | } 88 | 89 | /** 90 | * Create a new Pass object. 91 | * 92 | * @param {PassOptions} options - Pass creation options 93 | */ 94 | public makePass(options: PassOptions): Pass { 95 | // special handling for default pass 96 | if (null == options.ColorAttachments[0].Texture) { 97 | return new Pass(options, null, null); 98 | } 99 | 100 | // an offscreen pass, need to create a framebuffer with color- and depth attachments 101 | const gl = this.gl; 102 | const gl2 = this.gl as WebGL2RenderingContext; 103 | const isMSAA = options.ColorAttachments[0].Texture.sampleCount > 1; 104 | const glFb = gl.createFramebuffer(); 105 | gl.bindFramebuffer(gl.FRAMEBUFFER, glFb); 106 | if (isMSAA) { 107 | // MSAA offscreen rendering, attach the MSAA renderbuffers from texture objects 108 | for (let i = 0; i < options.ColorAttachments.length; i++) { 109 | const glMsaaFb = options.ColorAttachments[i].Texture.glMSAARenderBuffer; 110 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, glMsaaFb); 111 | } 112 | } 113 | else { 114 | // non-MSAA rendering, attach texture objects 115 | for (let i = 0; i < options.ColorAttachments.length; i++) { 116 | const att = options.ColorAttachments[i]; 117 | const tex = att.Texture; 118 | switch (tex.type) { 119 | case TextureType.Texture2D: 120 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, 121 | gl.TEXTURE_2D, tex.glTexture, att.MipLevel); 122 | break; 123 | case TextureType.TextureCube: 124 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, 125 | cubeFaceMap[att.Slice], tex.glTexture, att.MipLevel); 126 | break; 127 | default: 128 | // 3D and 2D-array textures 129 | gl2.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, 130 | tex.glTexture, att.MipLevel, att.Slice); 131 | break; 132 | } 133 | } 134 | } 135 | // attach optional depth-stencil buffer to framebuffer 136 | if (options.DepthAttachment.Texture) { 137 | const glDSRenderBuffer = options.DepthAttachment.Texture.glDepthRenderBuffer; 138 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, glDSRenderBuffer); 139 | if (options.DepthAttachment.Texture.depthFormat === DepthStencilFormat.DEPTHSTENCIL) { 140 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, glDSRenderBuffer); 141 | } 142 | } 143 | if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { 144 | console.warn('altai.makePass(): framebuffer completeness check failed!'); 145 | } 146 | 147 | // for MSAA, create resolve-framebuffers 148 | const glMsaaFbs = []; 149 | if (isMSAA) { 150 | for (let i = 0; i < options.ColorAttachments.length; i++) { 151 | glMsaaFbs[i] = gl.createFramebuffer(); 152 | gl.bindFramebuffer(gl.FRAMEBUFFER, glMsaaFbs[i]); 153 | const att = options.ColorAttachments[i]; 154 | const glTex = att.Texture.glTexture; 155 | switch (att.Texture.type) { 156 | case TextureType.Texture2D: 157 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, glTex, att.MipLevel); 158 | break; 159 | case TextureType.TextureCube: 160 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, cubeFaceMap[att.Slice], glTex, att.MipLevel); 161 | break; 162 | default: 163 | gl2.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, glTex, att.MipLevel, att.Slice); 164 | break; 165 | } 166 | if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { 167 | console.warn('altai.makePass(): framebuffer completeness check failed (for MSAA resolve buffers)'); 168 | } 169 | } 170 | } 171 | return new Pass(options, glFb, glMsaaFbs); 172 | } 173 | 174 | /** 175 | * Create a new Buffer object. 176 | * 177 | * @param {BufferOptions} options - Buffer creation options 178 | */ 179 | public makeBuffer(options: BufferOptions): Buffer { 180 | const gl: WebGLRenderingContext = this.gl; 181 | const buf = new Buffer(options, gl.createBuffer()); 182 | gl.bindBuffer(buf.type, buf.glBuffer); 183 | if (options.Data) { 184 | gl.bufferData(buf.type, options.Data as ArrayBuffer, buf.usage); 185 | } 186 | else if (options.LengthInBytes) { 187 | gl.bufferData(buf.type, options.LengthInBytes, buf.usage); 188 | } 189 | return buf; 190 | } 191 | 192 | private asGLTexImgFormat(p: PixelFormat): number { 193 | const gl = this.gl; 194 | switch (p) { 195 | case PixelFormat.RGBA8: 196 | case PixelFormat.RGBA4: 197 | case PixelFormat.RGB5_A1: 198 | case PixelFormat.RGB10_A2: 199 | case PixelFormat.RGBA32F: 200 | case PixelFormat.RGBA16F: 201 | return gl.RGBA; 202 | case PixelFormat.RGB8: 203 | case PixelFormat.RGB565: 204 | return gl.RGB; 205 | case PixelFormat.R32F: 206 | case PixelFormat.R16F: 207 | return gl.LUMINANCE; 208 | default: 209 | return 0; 210 | } 211 | } 212 | 213 | private asGLDepthTexImgFormat(d: DepthStencilFormat): number { 214 | switch (d) { 215 | case DepthStencilFormat.DEPTH: return this.gl.DEPTH_COMPONENT16; 216 | case DepthStencilFormat.DEPTHSTENCIL: return this.gl.DEPTH_STENCIL; 217 | default: return 0; 218 | } 219 | } 220 | 221 | private asGLTexImgType(p: PixelFormat): number { 222 | const gl = this.gl; 223 | switch (p) { 224 | case PixelFormat.RGBA32F: 225 | case PixelFormat.R32F: 226 | return gl.FLOAT; 227 | case PixelFormat.RGBA16F: 228 | case PixelFormat.R16F: 229 | return WebGL2RenderingContext.HALF_FLOAT; 230 | case PixelFormat.RGBA8: 231 | case PixelFormat.RGB8: 232 | return gl.UNSIGNED_BYTE; 233 | case PixelFormat.RGB5_A1: 234 | return gl.UNSIGNED_SHORT_5_5_5_1; 235 | case PixelFormat.RGB565: 236 | return gl.UNSIGNED_SHORT_5_6_5; 237 | case PixelFormat.RGBA4: 238 | return gl.UNSIGNED_SHORT_4_4_4_4; 239 | } 240 | } 241 | 242 | /** 243 | * Create a new Texture object 244 | * 245 | * @param {TextureOptions} options - Texture creation options 246 | */ 247 | public makeTexture(options: TextureOptions): Texture { 248 | const gl = this.gl; 249 | const tex = new Texture(options, gl); 250 | gl.activeTexture(gl.TEXTURE0); 251 | gl.bindTexture(tex.type, tex.glTexture); 252 | gl.texParameteri(tex.type, gl.TEXTURE_MIN_FILTER, tex.minFilter); 253 | gl.texParameteri(tex.type, gl.TEXTURE_MAG_FILTER, tex.magFilter); 254 | gl.texParameteri(tex.type, gl.TEXTURE_WRAP_S, tex.wrapU); 255 | gl.texParameteri(tex.type, gl.TEXTURE_WRAP_T, tex.wrapV); 256 | if (tex.type === WebGL2RenderingContext.TEXTURE_3D) { 257 | gl.texParameteri(tex.type, WebGL2RenderingContext.TEXTURE_WRAP_R, tex.wrapW); 258 | } 259 | const numFaces = tex.type === TextureType.TextureCube ? 6 : 1; 260 | const imgFmt = this.asGLTexImgFormat(tex.colorFormat); 261 | const imgType = this.asGLTexImgType(tex.colorFormat); 262 | for (let faceIndex = 0; faceIndex < numFaces; faceIndex++) { 263 | const imgTgt = tex.type === TextureType.TextureCube ? cubeFaceMap[faceIndex] : tex.type; 264 | for (let mipIndex = 0; mipIndex < tex.numMipMaps; mipIndex++) { 265 | // FIXME: data! 266 | let mipWidth = tex.width >> mipIndex; 267 | if (mipWidth === 0) { 268 | mipWidth = 1; 269 | } 270 | let mipHeight = tex.height >> mipIndex; 271 | if (mipHeight === 0) { 272 | mipHeight = 1; 273 | } 274 | if ((TextureType.Texture2D === tex.type) || (TextureType.TextureCube === tex.type)) { 275 | // FIXME: compressed formats + data 276 | gl.texImage2D(imgTgt, mipIndex, imgFmt, mipWidth, mipHeight, 0, imgFmt, imgType, null); 277 | } 278 | } 279 | } 280 | 281 | // MSAA render buffer? 282 | const isMSAA = tex.sampleCount > 1; 283 | if (isMSAA) { 284 | const gl2 = gl as WebGL2RenderingContext; 285 | gl2.bindRenderbuffer(gl.RENDERBUFFER, tex.glMSAARenderBuffer); 286 | gl2.renderbufferStorageMultisample(gl.RENDERBUFFER, tex.sampleCount, imgFmt, tex.width, tex.height); 287 | } 288 | // depth render buffer? 289 | if (tex.depthFormat != DepthStencilFormat.NONE) { 290 | const depthFmt = this.asGLDepthTexImgFormat(tex.depthFormat); 291 | gl.bindRenderbuffer(gl.RENDERBUFFER, tex.glDepthRenderBuffer); 292 | if (isMSAA) { 293 | const gl2 = gl as WebGL2RenderingContext; 294 | gl2.renderbufferStorageMultisample(gl.RENDERBUFFER, tex.sampleCount, depthFmt, tex.width, tex.height); 295 | } else { 296 | gl.renderbufferStorage(gl.RENDERBUFFER, depthFmt, tex.width, tex.height); 297 | } 298 | } 299 | return tex; 300 | } 301 | 302 | /** 303 | * Create a new Shader object. 304 | * 305 | * @param {ShaderOptions} options - Shader creation options 306 | */ 307 | public makeShader(options: ShaderOptions): Shader { 308 | const gl = this.gl; 309 | const vs = gl.createShader(gl.VERTEX_SHADER); 310 | gl.shaderSource(vs, options.VertexShader); 311 | gl.compileShader(vs); 312 | if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) { 313 | console.error("Failed to compile vertex shader:\n" + gl.getShaderInfoLog(vs)); 314 | } 315 | 316 | const fs = gl.createShader(gl.FRAGMENT_SHADER); 317 | gl.shaderSource(fs, options.FragmentShader); 318 | gl.compileShader(fs); 319 | if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) { 320 | console.error("Failed to compile fragment shader:\n" + gl.getShaderInfoLog(fs)); 321 | } 322 | 323 | const prog = gl.createProgram(); 324 | gl.attachShader(prog, vs); 325 | gl.attachShader(prog, fs); 326 | gl.linkProgram(prog); 327 | if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { 328 | console.error("Failed to link shader program!"); 329 | } 330 | const shd = new Shader(prog); 331 | gl.deleteShader(vs); 332 | gl.deleteShader(fs); 333 | 334 | return shd; 335 | } 336 | 337 | /** 338 | * Create a new Pipeline object. 339 | * 340 | * @param {PipelineOptions} options - Pipeline creation options 341 | */ 342 | public makePipeline(options: PipelineOptions): Pipeline { 343 | const gl = this.gl; 344 | const pip = new Pipeline(options); 345 | 346 | // resolve vertex attributes 347 | for (let layoutIndex = 0; layoutIndex < pip.vertexLayouts.length; layoutIndex++) { 348 | const layout = pip.vertexLayouts[layoutIndex]; 349 | const layoutByteSize = layout.byteSize(); 350 | for (let compIndex = 0; compIndex < layout.components.length; compIndex++) { 351 | const comp = layout.components[compIndex]; 352 | const attrName = comp[0]; 353 | const attrFormat = comp[1]; 354 | const attrIndex = gl.getAttribLocation(pip.shader.glProgram, attrName); 355 | if (attrIndex != -1) { 356 | const attrib = pip.glAttribs[attrIndex]; 357 | attrib.enabled = true; 358 | attrib.vbIndex = layoutIndex; 359 | attrib.divisor = layout.stepFunc == StepFunc.PerVertex ? 0 : layout.stepRate; 360 | attrib.stride = layoutByteSize; 361 | attrib.offset = layout.componentByteOffset(compIndex); 362 | attrib.size = vertexFormatMap[attrFormat][0] as number; 363 | attrib.type = vertexFormatMap[attrFormat][1] as number; 364 | attrib.normalized = vertexFormatMap[attrFormat][2] as boolean; 365 | } 366 | else { 367 | console.warn("Attribute '", attrName, "' not found in shader!"); 368 | } 369 | } 370 | } 371 | return pip; 372 | } 373 | 374 | /** 375 | * Create a new DrawState object. 376 | * 377 | * @param {DrawStateOptions} options - DrawState creation options 378 | */ 379 | public makeDrawState(options: DrawStateOptions): DrawState { 380 | return new DrawState(options); 381 | } 382 | 383 | /** 384 | * Begin a render-pass. 385 | * 386 | * @param {Pass} pass - a Pass object which describes what happens at the start and end of the render pass 387 | */ 388 | public beginPass(pass: Pass) { 389 | const gl = this.gl; 390 | const gl2 = this.gl as WebGL2RenderingContext; 391 | const isDefaultPass: boolean = !pass.ColorAttachments[0].texture; 392 | const width = isDefaultPass ? gl.canvas.width : pass.ColorAttachments[0].texture.width; 393 | const height = isDefaultPass ? gl.canvas.height : pass.ColorAttachments[0].texture.height; 394 | if (isDefaultPass) { 395 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 396 | } else { 397 | gl.bindFramebuffer(gl.FRAMEBUFFER, pass.glFramebuffer); 398 | if (this.webgl2) { 399 | const drawBuffers: number[] = []; 400 | for (let i = 0; i < pass.ColorAttachments.length; i++) { 401 | if (pass.ColorAttachments[i].texture) { 402 | drawBuffers[i] = gl.COLOR_ATTACHMENT0 + i; 403 | } 404 | } 405 | gl2.drawBuffers(drawBuffers); 406 | } 407 | } 408 | 409 | // prepare clear operations 410 | gl.viewport(0, 0, width, height); 411 | gl.disable(WebGLRenderingContext.SCISSOR_TEST); 412 | gl.colorMask(true, true, true, true); 413 | gl.depthMask(true); 414 | gl.stencilMask(0xFF); 415 | 416 | // update cache 417 | this.cache.scissorTestEnabled = false; 418 | this.cache.colorWriteMask[0] = true; 419 | this.cache.colorWriteMask[1] = true; 420 | this.cache.colorWriteMask[2] = true; 421 | this.cache.colorWriteMask[3] = true; 422 | this.cache.depthWriteEnabled = true; 423 | this.cache.frontStencilWriteMask = 0xFF; 424 | this.cache.backStencilWriteMask = 0xFF; 425 | 426 | if (isDefaultPass || !this.webgl2) { 427 | let clearMask = 0; 428 | const col = pass.ColorAttachments[0]; 429 | const dep = pass.DepthAttachment; 430 | if (col.loadAction === LoadAction.Clear) { 431 | clearMask |= WebGLRenderingContext.COLOR_BUFFER_BIT; 432 | gl.clearColor(col.clearColor[0], col.clearColor[1], col.clearColor[2], col.clearColor[3]); 433 | } 434 | if (dep.loadAction === LoadAction.Clear) { 435 | clearMask |= WebGLRenderingContext.DEPTH_BUFFER_BIT | WebGLRenderingContext.STENCIL_BUFFER_BIT; 436 | gl.clearDepth(dep.clearDepth); 437 | gl.clearStencil(dep.clearStencil); 438 | } 439 | if (0 !== clearMask) { 440 | gl.clear(clearMask); 441 | } 442 | } else { 443 | // offscreen WebGL2 (could be MRT) 444 | for (let i = 0; i < pass.ColorAttachments.length; i++) { 445 | const col = pass.ColorAttachments[i]; 446 | if (col.texture && (LoadAction.Clear == col.loadAction)) { 447 | gl2.clearBufferfv(gl2.COLOR, i, col.clearColor); 448 | } 449 | } 450 | const dep = pass.DepthAttachment; 451 | if (LoadAction.Clear === dep.loadAction) { 452 | gl2.clearBufferfi(gl2.DEPTH_STENCIL, 0, dep.clearDepth, dep.clearStencil); 453 | } 454 | } 455 | } 456 | /** 457 | * Finish current render-pass. 458 | */ 459 | public endPass() { 460 | // FIXME: perform MSAA resolve 461 | } 462 | /** 463 | * Apply a new viewport area. 464 | * 465 | * @param {number} x - horizontal pixel position of viewport area 466 | * @param {number} y - vertical pixel position of viewport area 467 | * @param {number} width - width in pixels of viewport area 468 | * @param {number} height - height in pixels of viewport area 469 | */ 470 | public applyViewPort(x: number, y: number, width: number, height: number) { 471 | this.gl.viewport(x, y, width, height); 472 | } 473 | /** 474 | * Apply new scissor rectangle. 475 | * 476 | * @param {number} x - horizontal pixel position of scissor rect 477 | * @param {number} y - vertical pixel position of scissor rect 478 | * @param {number} width - width in pixels of viewport area 479 | * @param {number} height - height in pixels of viewport area 480 | */ 481 | public applyScissorRect(x: number, y: number, width: number, height: number) { 482 | this.gl.scissor(x, y, width, height); 483 | } 484 | /** 485 | * Apply new resource bindings. 486 | * 487 | * @param {DrawState} drawState - a DrawState object with the new resource bindings 488 | */ 489 | public applyDrawState(drawState: DrawState) { 490 | const gl = this.gl; 491 | 492 | // some validity checks 493 | if ((drawState.IndexBuffer != null) && (drawState.Pipeline.indexFormat === IndexFormat.None)) { 494 | console.warn("altai.applyDrawState(): index buffer bound but pipeline.indexFormat is none!"); 495 | } 496 | if ((drawState.IndexBuffer == null) && (drawState.Pipeline.indexFormat !== IndexFormat.None)) { 497 | console.warn("altai.applyDrawState(): pipeline.indexFormat is not none, but no index buffer bound!"); 498 | } 499 | 500 | this.curPrimType = drawState.Pipeline.primitiveType; 501 | 502 | // update render state 503 | this.applyState(drawState.Pipeline.state, false); 504 | 505 | // apply shader program 506 | if (this.curProgram !== drawState.Pipeline.shader.glProgram) { 507 | this.curProgram = drawState.Pipeline.shader.glProgram; 508 | gl.useProgram(this.curProgram); 509 | } 510 | 511 | // apply index and vertex data 512 | this.curIndexFormat = drawState.Pipeline.indexFormat; 513 | this.curIndexSize = drawState.Pipeline.indexSize; 514 | if (drawState.IndexBuffer != null) { 515 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, drawState.IndexBuffer.glBuffer); 516 | } 517 | else { 518 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 519 | } 520 | let curVB: WebGLBuffer = null; 521 | for (let attrIndex = 0; attrIndex < MaxNumVertexAttribs; attrIndex++) { 522 | const attrib = drawState.Pipeline.glAttribs[attrIndex]; 523 | // FIXME: implement a state cache for vertex attrib bindings 524 | if (attrib.enabled) { 525 | if (drawState.VertexBuffers[attrib.vbIndex].glBuffer !== curVB) { 526 | curVB = drawState.VertexBuffers[attrib.vbIndex].glBuffer; 527 | gl.bindBuffer(gl.ARRAY_BUFFER, curVB); 528 | } 529 | gl.vertexAttribPointer(attrIndex, attrib.size, attrib.type, attrib.normalized, attrib.stride, attrib.offset); 530 | gl.enableVertexAttribArray(attrIndex); 531 | // FIMXE: WebGL2 vertex attrib divisor! 532 | } 533 | else { 534 | gl.disableVertexAttribArray(attrIndex); 535 | } 536 | } 537 | 538 | // apply texture uniforms 539 | let texSlot = 0; 540 | for (const key in drawState.Textures) { 541 | const tex = drawState.Textures[key]; 542 | const loc = gl.getUniformLocation(this.curProgram, key); 543 | gl.activeTexture(gl.TEXTURE0+texSlot); 544 | gl.bindTexture(tex.type, tex.glTexture); 545 | gl.uniform1i(loc, texSlot); 546 | texSlot++; 547 | } 548 | } 549 | 550 | /** 551 | * Apply shader uniforms by name and value. Only the following 552 | * types are allowed: float, vec2, vec3, vec4, mat4. Textures 553 | * are applied via applyDrawState. 554 | * 555 | * @param uniforms - uniform name/value pairs 556 | */ 557 | applyUniforms(uniforms: {[key: string]: number[] | number}) { 558 | let gl: WebGLRenderingContext = this.gl; 559 | for (let key in uniforms) { 560 | const val = uniforms[key]; 561 | const loc = gl.getUniformLocation(this.curProgram, key); 562 | if (loc !== null) { 563 | if (typeof val === "number") { 564 | gl.uniform1f(loc, val); 565 | } 566 | else { 567 | switch (val.length) { 568 | case 1: gl.uniform1fv(loc, val); break; 569 | case 2: gl.uniform2fv(loc, val); break; 570 | case 3: gl.uniform3fv(loc, val); break; 571 | case 4: gl.uniform4fv(loc, val); break; 572 | case 16: gl.uniformMatrix4fv(loc, false, val); break; 573 | default: console.warn('altai.applyUniforms: invalid parameter type!'); 574 | } 575 | } 576 | } 577 | } 578 | } 579 | 580 | /** 581 | * Draw primitive range with current draw settings. 582 | * 583 | * @param {number} baseElement - index of first vertex or index 584 | * @param {number} numElements - number of vertices or indices 585 | * @param {number} numInstances - number of instances (default: 1) 586 | */ 587 | draw(baseElement: number, numElements: number, numInstances: number = 1) { 588 | if (IndexFormat.None == this.curIndexFormat) { 589 | // non-indexed rendering 590 | if (1 == numInstances) { 591 | this.gl.drawArrays(this.curPrimType, baseElement, numElements); 592 | } 593 | else { 594 | // FIXME: instanced rendering! 595 | } 596 | } 597 | else { 598 | // indexed rendering 599 | let indexOffset = baseElement * this.curIndexSize; 600 | if (1 == numInstances) { 601 | this.gl.drawElements(this.curPrimType, numElements, this.curIndexFormat, indexOffset); 602 | } 603 | else { 604 | // FIXME: instanced rendering! 605 | } 606 | } 607 | } 608 | /** 609 | * Finish current frame, pass function pointer of next frame's draw function. 610 | * 611 | * @param {() => void} drawFunc - the next frame's draw function 612 | */ 613 | commitFrame(drawFunc: () => void) { 614 | requestAnimationFrame(drawFunc); 615 | } 616 | 617 | private applyState(state: PipelineState, force: boolean) { 618 | let gl = this.gl; 619 | // apply depth-stencil state changes 620 | if (force || (this.cache.depthCmpFunc != state.depthCmpFunc)) { 621 | this.cache.depthCmpFunc = state.depthCmpFunc; 622 | gl.depthFunc(state.depthCmpFunc); 623 | } 624 | if (force || (this.cache.depthWriteEnabled != state.depthWriteEnabled)) { 625 | this.cache.depthWriteEnabled = state.depthWriteEnabled; 626 | gl.depthMask(state.depthWriteEnabled); 627 | } 628 | if (force || (this.cache.stencilEnabled != state.stencilEnabled)) { 629 | this.cache.stencilEnabled = state.stencilEnabled; 630 | if (state.stencilEnabled) gl.enable(gl.STENCIL_TEST); 631 | else gl.disable(gl.STENCIL_TEST); 632 | } 633 | let sCmpFunc = state.frontStencilCmpFunc; 634 | let sReadMask = state.frontStencilReadMask; 635 | let sRef = state.frontStencilRef; 636 | if (force || 637 | (this.cache.frontStencilCmpFunc != sCmpFunc) || 638 | (this.cache.frontStencilReadMask != sReadMask) || 639 | (this.cache.frontStencilRef != sRef)) 640 | { 641 | this.cache.frontStencilCmpFunc = sCmpFunc; 642 | this.cache.frontStencilReadMask = sReadMask; 643 | this.cache.frontStencilRef = sRef; 644 | gl.stencilFuncSeparate(gl.FRONT, sCmpFunc, sRef, sReadMask); 645 | } 646 | sCmpFunc = state.backStencilCmpFunc; 647 | sReadMask = state.backStencilReadMask; 648 | sRef = state.backStencilRef; 649 | if (force || 650 | (this.cache.backStencilCmpFunc != sCmpFunc) || 651 | (this.cache.backStencilReadMask != sReadMask) || 652 | (this.cache.backStencilRef != sRef)) 653 | { 654 | this.cache.backStencilCmpFunc = sCmpFunc; 655 | this.cache.backStencilReadMask = sReadMask; 656 | this.cache.backStencilRef = sRef; 657 | gl.stencilFuncSeparate(gl.BACK, sCmpFunc, sRef, sReadMask); 658 | } 659 | let sFailOp = state.frontStencilFailOp; 660 | let sDepthFailOp = state.frontStencilDepthFailOp; 661 | let sPassOp = state.frontStencilPassOp; 662 | if (force || 663 | (this.cache.frontStencilFailOp != sFailOp) || 664 | (this.cache.frontStencilDepthFailOp != sDepthFailOp) || 665 | (this.cache.frontStencilPassOp != sPassOp)) 666 | { 667 | this.cache.frontStencilFailOp = sFailOp; 668 | this.cache.frontStencilDepthFailOp = sDepthFailOp; 669 | this.cache.frontStencilPassOp = sPassOp; 670 | gl.stencilOpSeparate(gl.FRONT, sFailOp, sDepthFailOp, sPassOp); 671 | } 672 | sFailOp = state.backStencilFailOp; 673 | sDepthFailOp = state.backStencilDepthFailOp; 674 | sPassOp = state.backStencilPassOp; 675 | if (force || 676 | (this.cache.backStencilFailOp != sFailOp) || 677 | (this.cache.backStencilDepthFailOp != sDepthFailOp) || 678 | (this.cache.backStencilPassOp != sPassOp)) 679 | { 680 | this.cache.backStencilFailOp = sFailOp; 681 | this.cache.backStencilDepthFailOp = sDepthFailOp; 682 | this.cache.backStencilPassOp = sPassOp; 683 | gl.stencilOpSeparate(gl.BACK, sFailOp, sDepthFailOp, sPassOp); 684 | } 685 | if (force || (this.cache.frontStencilWriteMask != state.frontStencilWriteMask)) { 686 | this.cache.frontStencilWriteMask = state.frontStencilWriteMask; 687 | gl.stencilMaskSeparate(gl.FRONT, state.frontStencilWriteMask) 688 | } 689 | if (force || (this.cache.backStencilWriteMask != state.backStencilWriteMask)) { 690 | this.cache.backStencilWriteMask = state.backStencilWriteMask; 691 | gl.stencilMaskSeparate(gl.BACK, state.backStencilWriteMask); 692 | } 693 | 694 | // apply blend state changes 695 | if (force || (this.cache.blendEnabled != state.blendEnabled)) { 696 | this.cache.blendEnabled = state.blendEnabled; 697 | gl.enable(gl.BLEND); 698 | } 699 | if (force || 700 | (this.cache.blendSrcFactorRGB != state.blendSrcFactorRGB) || 701 | (this.cache.blendDstFactorRGB != state.blendDstFactorRGB) || 702 | (this.cache.blendSrcFactorAlpha != state.blendSrcFactorAlpha) || 703 | (this.cache.blendDstFactorAlpha != state.blendDstFactorAlpha)) 704 | { 705 | this.cache.blendSrcFactorRGB = state.blendSrcFactorRGB; 706 | this.cache.blendDstFactorRGB = state.blendDstFactorRGB; 707 | this.cache.blendSrcFactorAlpha = state.blendSrcFactorAlpha; 708 | this.cache.blendDstFactorAlpha = state.blendDstFactorAlpha; 709 | gl.blendFuncSeparate(state.blendSrcFactorRGB, 710 | state.blendDstFactorRGB, 711 | state.blendSrcFactorAlpha, 712 | state.blendDstFactorAlpha); 713 | } 714 | if (force || 715 | (this.cache.blendOpRGB != state.blendOpRGB) || 716 | (this.cache.blendOpAlpha != state.blendOpAlpha)) 717 | { 718 | this.cache.blendOpRGB = state.blendOpRGB; 719 | this.cache.blendOpAlpha = state.blendOpAlpha; 720 | gl.blendEquationSeparate(state.blendOpRGB, state.blendOpAlpha); 721 | } 722 | if (force || 723 | (this.cache.colorWriteMask[0] != state.colorWriteMask[0]) || 724 | (this.cache.colorWriteMask[1] != state.colorWriteMask[1]) || 725 | (this.cache.colorWriteMask[2] != state.colorWriteMask[2]) || 726 | (this.cache.colorWriteMask[3] != state.colorWriteMask[3])) 727 | { 728 | this.cache.colorWriteMask[0] = state.colorWriteMask[0]; 729 | this.cache.colorWriteMask[1] = state.colorWriteMask[1]; 730 | this.cache.colorWriteMask[2] = state.colorWriteMask[2]; 731 | this.cache.colorWriteMask[3] = state.colorWriteMask[3]; 732 | gl.colorMask(state.colorWriteMask[0], 733 | state.colorWriteMask[1], 734 | state.colorWriteMask[2], 735 | state.colorWriteMask[3]); 736 | } 737 | if (force || 738 | (this.cache.blendColor[0] != state.blendColor[0]) || 739 | (this.cache.blendColor[1] != state.blendColor[1]) || 740 | (this.cache.blendColor[2] != state.blendColor[2]) || 741 | (this.cache.blendColor[3] != state.blendColor[3])) 742 | { 743 | this.cache.blendColor[0] = state.blendColor[0]; 744 | this.cache.blendColor[1] = state.blendColor[1]; 745 | this.cache.blendColor[2] = state.blendColor[2]; 746 | this.cache.blendColor[3] = state.blendColor[3]; 747 | gl.blendColor(state.blendColor[0], 748 | state.blendColor[1], 749 | state.blendColor[2], 750 | state.blendColor[3]); 751 | } 752 | 753 | // apply rasterizer state 754 | if (force || (this.cache.cullFaceEnabled != state.cullFaceEnabled)) { 755 | this.cache.cullFaceEnabled = state.cullFaceEnabled; 756 | if (state.cullFaceEnabled) gl.enable(gl.CULL_FACE); 757 | else gl.disable(gl.CULL_FACE); 758 | } 759 | if (force || (this.cache.cullFace != state.cullFace)) { 760 | this.cache.cullFace = state.cullFace; 761 | gl.cullFace(state.cullFace); 762 | } 763 | if (force || (this.cache.scissorTestEnabled != state.scissorTestEnabled)) { 764 | this.cache.scissorTestEnabled = state.scissorTestEnabled; 765 | if (state.scissorTestEnabled) gl.enable(gl.SCISSOR_TEST); 766 | else gl.disable(gl.SCISSOR_TEST); 767 | } 768 | } 769 | 770 | } 771 | 772 | } -------------------------------------------------------------------------------- /src/enums.ts: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module altai { 4 | 5 | /** 6 | * Buffer types (vertex or index buffers). 7 | */ 8 | export enum BufferType { 9 | VertexBuffer = WebGLRenderingContext.ARRAY_BUFFER, 10 | IndexBuffer = WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, 11 | } 12 | 13 | 14 | /** 15 | * Vertex index formats. 16 | */ 17 | export enum IndexFormat { 18 | /** no vertex indices */ 19 | None = WebGLRenderingContext.NONE, 20 | /** 16-bit indices */ 21 | UInt16 = WebGLRenderingContext.UNSIGNED_SHORT, 22 | /** 32-bit indices */ 23 | UInt32 = WebGLRenderingContext.UNSIGNED_INT, 24 | } 25 | 26 | /** 27 | * Texture pixel formats. 28 | */ 29 | export enum PixelFormat { 30 | /** undefined/none/unused */ 31 | NONE, 32 | /** RGBA with 8 bits per channel */ 33 | RGBA8, 34 | /** RGB with 8 bits per channel */ 35 | RGB8, 36 | /** RGBA with 4 bits per channel */ 37 | RGBA4, 38 | /** RGB with 5/6/5 bits per channel */ 39 | RGB565, 40 | /** RGBA with 5-bit color channels, and 1-bit alpha */ 41 | RGB5_A1, 42 | /** RGBA with 10-bits color channels and 1-bit alpha */ 43 | RGB10_A2, 44 | /** RGBA with 32-bit floating point channels */ 45 | RGBA32F, 46 | /** RGBA with 16-bit floating point channels */ 47 | RGBA16F, 48 | /** R component only, 32-bit floating point */ 49 | R32F, 50 | /** R component only, 16-bit floating point */ 51 | R16F, 52 | } 53 | 54 | /** 55 | * Depth/stencil surface formats. 56 | */ 57 | export enum DepthStencilFormat { 58 | /** depth-only */ 59 | DEPTH, 60 | /** combined depth-stencil */ 61 | DEPTHSTENCIL, 62 | /** no depth/stencil buffer */ 63 | NONE, 64 | } 65 | 66 | /** 67 | * Vertex component formats. 68 | */ 69 | export enum VertexFormat { 70 | /** 32-bit float, single component in X */ 71 | Float, 72 | /** 32-bit floats, 2 components in XY */ 73 | Float2, 74 | /** 32-bit floats, 3 components in XYZ */ 75 | Float3, 76 | /** 32-bit floats, 4 components in XYZW */ 77 | Float4, 78 | /** 4 packed bytes, signed (-128 .. 127) */ 79 | Byte4, 80 | /** 4 packed bytes, signed, normalized (-1.0 .. +1.0) */ 81 | Byte4N, 82 | /** 4 packed bytes, unsigned (0 .. 255) */ 83 | UByte4, 84 | /** 4 packed bytes, unsigned, normalized (0.0 .. 1.0) */ 85 | UByte4N, 86 | /** 2 packed 16-bit shorts, signed (-32767 .. +32768) */ 87 | Short2, 88 | /** 2 packed 16-bit shorts, signed (-1.0 .. +1.0) */ 89 | Short2N, 90 | /** 4 packed 16-bit shorts, signed (-32767 .. +32768) */ 91 | Short4, 92 | /** 4 packed 16-bit shorts, signed (-1.0 .. +1.0) */ 93 | Short4N, 94 | } 95 | 96 | /** 97 | * 3D primitive types. 98 | */ 99 | export enum PrimitiveType { 100 | /** point list */ 101 | Points = WebGLRenderingContext.POINTS, 102 | /** line list */ 103 | Lines = WebGLRenderingContext.LINES, 104 | /** line strip */ 105 | LineStrip = WebGLRenderingContext.LINE_STRIP, 106 | /** triangle list */ 107 | Triangles = WebGLRenderingContext.TRIANGLES, 108 | /** triangle strip */ 109 | TriangleStrip = WebGLRenderingContext.TRIANGLE_STRIP, 110 | } 111 | 112 | /** 113 | * texture sampling filters (minification/magnification and mipmapping) 114 | */ 115 | export enum Filter { 116 | /** use nearest-filtering (aka point-filtering) */ 117 | Nearest = WebGLRenderingContext.NEAREST, 118 | /** use linear filtering */ 119 | Linear = WebGLRenderingContext.LINEAR, 120 | /** nearest within mipmap and between mipmaps */ 121 | NearestMipmapNearest = WebGLRenderingContext.NEAREST_MIPMAP_NEAREST, 122 | /** nearest within mipmap, linear between mipmaps */ 123 | NearestMipmapLinear = WebGLRenderingContext.NEAREST_MIPMAP_LINEAR, 124 | /** linear within mipmap, nearest between mipmaps */ 125 | LinearMipmapNearest = WebGLRenderingContext.LINEAR_MIPMAP_NEAREST, 126 | /** linear within and between mipmaps */ 127 | LinearMipmapLinear = WebGLRenderingContext.LINEAR_MIPMAP_LINEAR 128 | } 129 | 130 | /** 131 | * texture addressing wrap mode (aka UV wrap) 132 | */ 133 | export enum Wrap { 134 | /** clamp texture coords to (0.0 .. 1.0) */ 135 | ClampToEdge = WebGLRenderingContext.CLAMP_TO_EDGE, 136 | /** repeat texture coords within (0.0 .. 1.0) */ 137 | Repeat = WebGLRenderingContext.REPEAT, 138 | /** mirror-repeat texture coords (0.0 .. 1.0 .. 0.0) */ 139 | MirroredRepeat = WebGLRenderingContext.MIRRORED_REPEAT, 140 | } 141 | 142 | /** 143 | * texture object types 144 | */ 145 | export enum TextureType { 146 | /** 2D texture */ 147 | Texture2D = WebGLRenderingContext.TEXTURE_2D, 148 | /** cubemap texture */ 149 | TextureCube = WebGLRenderingContext.TEXTURE_CUBE_MAP, 150 | /** 3D texture */ 151 | Texture3D = WebGL2RenderingContext.TEXTURE_3D, 152 | /** 2D-array texture */ 153 | TextureArray2D = WebGL2RenderingContext.TEXTURE_2D_ARRAY 154 | } 155 | 156 | /** 157 | * buffer and texture data usage hint 158 | */ 159 | export enum Usage { 160 | /** data is immutable, cannot be modified after creation */ 161 | Immutable = WebGLRenderingContext.STATIC_DRAW, 162 | /** data is updated infrequently */ 163 | Dynamic = WebGLRenderingContext.DYNAMIC_DRAW, 164 | /** data is overwritten each frame */ 165 | Stream = WebGLRenderingContext.STREAM_DRAW, 166 | } 167 | 168 | /** 169 | * identify front/back sides for face culling. 170 | */ 171 | export enum Face { 172 | /** cull front side */ 173 | Front = WebGLRenderingContext.FRONT, 174 | /** cull back side */ 175 | Back = WebGLRenderingContext.BACK, 176 | /** cull both sides */ 177 | Both = WebGLRenderingContext.FRONT_AND_BACK, 178 | } 179 | 180 | /** 181 | * Comparision functions for depth and stencil checks. 182 | */ 183 | export enum CompareFunc { 184 | /** new value never passes comparion test */ 185 | Never = WebGLRenderingContext.NEVER, 186 | /** new value passses if it is less than the existing value */ 187 | Less = WebGLRenderingContext.LESS, 188 | /** new value passes if it is equal to existing value */ 189 | Equal = WebGLRenderingContext.EQUAL, 190 | /** new value passes if it is less than or equal to existing value */ 191 | LessEqual = WebGLRenderingContext.LEQUAL, 192 | /** new value passes if it is greater than existing value */ 193 | Greater = WebGLRenderingContext.GREATER, 194 | /** new value passes if it is not equal to existing value */ 195 | NotEqual = WebGLRenderingContext.NOTEQUAL, 196 | /** new value passes if it is greater than or equal to existing value */ 197 | GreaterEqual = WebGLRenderingContext.GEQUAL, 198 | /** new value always passes */ 199 | Always = WebGLRenderingContext.ALWAYS, 200 | } 201 | 202 | /** 203 | * Stencil-buffer operations. 204 | */ 205 | export enum StencilOp { 206 | /** keep the current stencil value */ 207 | Keep = WebGLRenderingContext.KEEP, 208 | /** set the stencil value to zero */ 209 | Zero = WebGLRenderingContext.ZERO, 210 | /** replace the stencil value with stencil reference value */ 211 | Replace = WebGLRenderingContext.REPLACE, 212 | /** increment the current stencil value, clamp to max */ 213 | IncrClamp = WebGLRenderingContext.INCR, 214 | /** decrement the current stencil value, clamp to zero */ 215 | DecrClamp = WebGLRenderingContext.DECR, 216 | /** perform a logical bitwise invert operation on the stencil value */ 217 | Invert = WebGLRenderingContext.INVERT, 218 | /** increment the current stencil value, with wrap-around */ 219 | IncrWrap = WebGLRenderingContext.INCR_WRAP, 220 | /** decrement the current stencil value, with wrap-around */ 221 | DecrWrap = WebGLRenderingContext.DECR_WRAP, 222 | } 223 | 224 | /** 225 | * Alpha-blending factors. 226 | */ 227 | export enum BlendFactor { 228 | /** blend factor of zero */ 229 | Zero = WebGLRenderingContext.ZERO, 230 | /** blend factor of one */ 231 | One = WebGLRenderingContext.ONE, 232 | /** blend factor of source color */ 233 | SrcColor = WebGLRenderingContext.SRC_COLOR, 234 | /** blend factor of one minus source color */ 235 | OneMinusSrcColor = WebGLRenderingContext.ONE_MINUS_SRC_COLOR, 236 | /** blend factor of source alpha */ 237 | SrcAlpha = WebGLRenderingContext.SRC_ALPHA, 238 | /** blend factor of one minus source alpha */ 239 | OneMinusSrcAlpha = WebGLRenderingContext.ONE_MINUS_SRC_ALPHA, 240 | /** blend factor of destination color */ 241 | DstColor = WebGLRenderingContext.DST_COLOR, 242 | /** blend factor of one minus destination alpha */ 243 | OneMinusDstColor = WebGLRenderingContext.ONE_MINUS_DST_COLOR, 244 | /** blend factor of destination alpha */ 245 | DstAlpha = WebGLRenderingContext.DST_ALPHA, 246 | /** blend factor of one minus destination alpha */ 247 | OneMinusDstAlpha = WebGLRenderingContext.ONE_MINUS_DST_ALPHA, 248 | /** blend factor of the minimum of either source alpha or one minus destination alpha */ 249 | SrcAlphaSaturated = WebGLRenderingContext.SRC_ALPHA_SATURATE, 250 | /** blend factor of constant color */ 251 | BlendColor = WebGLRenderingContext.CONSTANT_COLOR, 252 | /** blend factor of one minus constant color */ 253 | OneMinusBlendColor = WebGLRenderingContext.ONE_MINUS_CONSTANT_COLOR, 254 | /** blend factor of constant alpha */ 255 | BlendAlpha = WebGLRenderingContext.CONSTANT_ALPHA, 256 | /** blend factor of one minus destination alpha */ 257 | OneMinusBlendAlpha = WebGLRenderingContext.ONE_MINUS_CONSTANT_ALPHA, 258 | } 259 | 260 | export enum BlendOp { 261 | /** add source and destination pixel values */ 262 | Add = WebGLRenderingContext.FUNC_ADD, 263 | /** subtract destination from source pixel values 264 | Subtract = WebGLRenderingContext.FUNC_SUBTRACT, 265 | /** subtract source from destination pixel values */ 266 | ReverseSubtract = WebGLRenderingContext.FUNC_REVERSE_SUBTRACT, 267 | } 268 | 269 | export enum StepFunc { 270 | PerVertex, 271 | PerInstance, 272 | } 273 | 274 | export enum LoadAction { 275 | DontCare, 276 | Load, 277 | Clear, 278 | } 279 | 280 | } -------------------------------------------------------------------------------- /src/options.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 'use strict' 3 | 4 | module altai { 5 | 6 | /** 7 | * WebGL and canvas initialization options. 8 | */ 9 | export interface GfxOptions { 10 | /** use WebGL2? (default: false) */ 11 | UseWebGL2?: boolean; 12 | /** name of existing HTML canvas (default: 'canvas') */ 13 | Canvas?: string; 14 | /** new width of canvas (default: don't change canvas width) */ 15 | Width?: number; 16 | /** new height of canvas (default: don't change canvas height) */ 17 | Height?: number; 18 | /** whether drawing buffer should have alpha channel (default: true) */ 19 | Alpha?: boolean; 20 | /** whether drawing buffer should have a depth buffer (default: true) */ 21 | Depth?: boolean; 22 | /** whether drawing buffer should have a stencil buffer (default: false) */ 23 | Stencil?: boolean; 24 | /** whether drawing buffer should be anti-aliased (default: true) */ 25 | AntiAlias?: boolean; 26 | /** whether drawing buffer contains pre-multiplied-alpha colors (default: true) */ 27 | PreMultipliedAlpha?: boolean; 28 | /** whether content of drawing buffer should be preserved (default: false) */ 29 | PreserveDrawingBuffer?: boolean; 30 | /** whether to create a context for low-power-consumption (default: false) */ 31 | PreferLowPowerToHighPerformance?: boolean; 32 | /** whether context creation fails if performance would be low (default: false) */ 33 | FailIfMajorPerformanceCaveat?: boolean; 34 | /** whether to create a high-resolution context on Retina-type displays (default: false) */ 35 | HighDPI?: boolean; 36 | } 37 | 38 | /** 39 | * Buffer creation options. 40 | */ 41 | export interface BufferOptions { 42 | /** whether the buffer contains vertex- or index-data */ 43 | Type: BufferType; 44 | /** whether the buffer is immutable or can be updated after creation */ 45 | Usage?: Usage; 46 | /** optional content initialization data */ 47 | Data?: ArrayBufferView | ArrayBuffer; 48 | /** buffer size in bytes if no init data provided */ 49 | LengthInBytes?: number; 50 | } 51 | 52 | /** 53 | * Texture creation options 54 | */ 55 | export interface TextureOptions { 56 | /** the textue type (2D, 3D, Cube...) */ 57 | Type: TextureType; 58 | /** texture usage (immutable, , ..) */ 59 | Usage?: Usage; 60 | /** width of the texture */ 61 | Width: number; 62 | /** height of the texture */ 63 | Height: number; 64 | /** optional depth of the texture (for 3D or Array textures) */ 65 | Depth?: number; 66 | /** optional number of mipmaps */ 67 | NumMipMaps?: number; 68 | /** pixel format of the texture */ 69 | ColorFormat: PixelFormat; 70 | /** optional depth-buffer format (for render target textures) */ 71 | DepthFormat?: DepthStencilFormat; 72 | /** optional sample-count (for MSAA render targets) */ 73 | SampleCount?: number; 74 | /** optional texture wrap mode for U dimension */ 75 | WrapU?: Wrap; 76 | /** optional texture wrap mode for V dimension */ 77 | WrapV?: Wrap; 78 | /** optional texture wrap mode for W dimension */ 79 | WrapW?: Wrap; 80 | /** optional texture filter mode for minimifaction */ 81 | MinFilter?: Filter; 82 | /** optional texture filter mode for magnification */ 83 | MagFilter?: Filter; 84 | // FIXME: initialization data 85 | } 86 | 87 | /** 88 | * Vertex input layout description. 89 | */ 90 | export interface VertexLayoutOptions { 91 | /** vertex component names and formats */ 92 | Components: [ string, VertexFormat ][]; 93 | /** advance per-vertex or per-instance */ 94 | StepFunc?: StepFunc; 95 | /** the vertex step-rate (divisor) for instancing */ 96 | StepRate?: number; 97 | } 98 | 99 | /** 100 | * Options for creating a Pipeline object. 101 | */ 102 | export interface PipelineOptions { 103 | 104 | /** described the structure of input vertex data */ 105 | VertexLayouts: VertexLayoutOptions[]; 106 | /** the shader object, with matching vertex inputs */ 107 | Shader: Shader; 108 | /** rendering primitive type (triangle, triangle strip, lines, ..)*/ 109 | PrimitiveType?: PrimitiveType; 110 | /** index data format (none, 16- or 32-bit) */ 111 | IndexFormat?: IndexFormat; 112 | 113 | /** is alpha-blending enabled? (default: false) */ 114 | BlendEnabled?: boolean; 115 | /** the blend source factor (both RGB and Alpha, default: One) */ 116 | BlendSrcFactor?: BlendFactor; 117 | /** the blend destination factor (both RGB and Alpha, default: Zero) */ 118 | BlendDstFactor?: BlendFactor; 119 | /** the blend operation (both RGB and Alpha, default: Add) */ 120 | BlendOp?: BlendOp; 121 | /** what color-channels to write (default: all true) */ 122 | ColorWriteMask?: [boolean, boolean, boolean, boolean]; 123 | /** blend-constant color (default: all 1.0) */ 124 | BlendColor?: [number, number, number, number]; 125 | 126 | /** separate RGB blend source factor (default: One) */ 127 | BlendSrcFactorRGB?: BlendFactor; 128 | /** separate RGB blend destination factor (default: Zero) */ 129 | BlendDstFactorRGB?: BlendFactor; 130 | /** separate RGB blend operation (default: Add) */ 131 | BlendOpRGB?: BlendOp; 132 | 133 | /** separate Alpha blend source factor (default: One) */ 134 | BlendSrcFactorAlpha?: BlendFactor; 135 | /** separate Alpha blend destination factor (default: Zero) */ 136 | BlendDstFactorAlpha?: BlendFactor; 137 | /** separate Alpha blend operation (default: Add) */ 138 | BlendOpAlpha?: BlendOp; 139 | 140 | /** stencil operations enabled? (default: false) */ 141 | StencilEnabled?: boolean; 142 | /** common front/back stencil-fail operation (default: Keep) */ 143 | StencilFailOp?: StencilOp; 144 | /** common front/back stencil-depth-fail operation (default: Keep) */ 145 | StencilDepthFailOp?: StencilOp; 146 | /** common front/back stencil-pass operation (default: Keep) */ 147 | StencilPassOp?: StencilOp; 148 | /** common front/back stencil-compare function (default: Always) */ 149 | StencilCmpFunc?: CompareFunc; 150 | /** common front/back stencil read mask (default: 0xFF) */ 151 | StencilReadMask?: number; 152 | /** common front/back stencil write mask (default: 0xFF) */ 153 | StencilWriteMask?: number; 154 | /** common front/back stencil ref value (default: 0) */ 155 | StencilRef?: number; 156 | 157 | /** separate front stencil-fail operation (default: Keep) */ 158 | FrontStencilFailOp?: StencilOp; 159 | /** separate front stencil-depth-fail operation (default: Keep) */ 160 | FrontStencilDepthFailOp?: StencilOp; 161 | /** separate front stencil-pass operation (default: Keep) */ 162 | FrontStencilPassOp?: StencilOp; 163 | /** separate front stencil-compare function (default: Always) */ 164 | FrontStencilCmpFunc?: CompareFunc; 165 | /** separate front stencil read mask (default: 0xFF) */ 166 | FrontStencilReadMask?: number; 167 | /** separate front stencil write mask (default: 0xFF) */ 168 | FrontStencilWriteMask?: number; 169 | /** separate front stencil ref value (default: 0) */ 170 | FrontStencilRef?: number; 171 | 172 | /** separate back stencil-fail operation (default: Keep) */ 173 | BackStencilFailOp?: StencilOp; 174 | /** separate back stencil-depth-fail operation (default: Keep) */ 175 | BackStencilDepthFailOp?: StencilOp; 176 | /** separate back stencil-pass operation (default: Keep) */ 177 | BackStencilPassOp?: StencilOp; 178 | /** separate back stencil-compare function (default: Always) */ 179 | BackStencilCmpFunc?: CompareFunc; 180 | /** separate back stencil read mask (default: 0xFF) */ 181 | BackStencilReadMask?: number; 182 | /** separate back stencil write mask (default: 0xFF) */ 183 | BackStencilWriteMask?: number; 184 | /** separate back stencil ref value (default: 0) */ 185 | BackStencilRef?: number; 186 | 187 | /** depth-compare function (default: Always) */ 188 | DepthCmpFunc?: CompareFunc; 189 | /** depth-writes enabled? (default: false) */ 190 | DepthWriteEnabled?: boolean; 191 | 192 | /** face-culling enabled? (default: false) */ 193 | CullFaceEnabled?: boolean; 194 | /** face side to be culled (default: Back) */ 195 | CullFace?: Face; 196 | /** scissor test enabled? (default: false) */ 197 | ScissorTestEnabled?: boolean; 198 | } 199 | 200 | /** 201 | * Options for creating a Shader object. 202 | */ 203 | export interface ShaderOptions { 204 | /** GLSL vertex shader source code */ 205 | VertexShader: string, 206 | /** GLSL fragment shader source code */ 207 | FragmentShader: string, 208 | } 209 | 210 | /** 211 | * Options for creating a DrawState object. 212 | */ 213 | export interface DrawStateOptions { 214 | /** a Pipeline object */ 215 | Pipeline: Pipeline; 216 | /** one or multiple VertexBuffer objects */ 217 | VertexBuffers: Buffer[]; 218 | /** an optional index buffer object */ 219 | IndexBuffer?: Buffer; 220 | /** optional texture objects */ 221 | Textures?: {[key: string]: Texture; }; 222 | } 223 | 224 | /** 225 | * Options for initializing pass color-attachments 226 | */ 227 | export interface ColorAttachmentOptions { 228 | /** the backing texture of the color attachment */ 229 | Texture?: Texture; 230 | /** optional rendering mip level (default: 0) */ 231 | MipLevel?: number; 232 | /** optional rendering texture slice (default: 0) */ 233 | Slice?: number; 234 | LoadAction?: LoadAction; 235 | ClearColor?: [number, number, number, number]; 236 | } 237 | 238 | export interface DepthAttachmentOptions { 239 | Texture?: Texture, 240 | LoadAction?: LoadAction, 241 | ClearDepth?: number, 242 | ClearStencil?: number, 243 | } 244 | 245 | export interface PassOptions { 246 | ColorAttachments?: ColorAttachmentOptions[]; 247 | DepthAttachment?: DepthAttachmentOptions; 248 | } 249 | 250 | } -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | 'use strict' 4 | 5 | module altai { 6 | 7 | export const MaxNumColorAttachments = 4; 8 | export const MaxNumVertexAttribs = 16; 9 | 10 | export function some(opt0: T, opt1: T): T { 11 | return opt0 != null ? opt0 : opt1; 12 | } 13 | export function some3(opt0: T, opt1: T, opt2: T): T { 14 | return opt0 != null ? opt0 : (opt1 != null ? opt1 : opt2); 15 | } 16 | 17 | /** 18 | * A Buffer object for vertex- or index-data. 19 | */ 20 | export class Buffer { 21 | readonly type: BufferType; 22 | readonly usage: Usage; 23 | readonly glBuffer: WebGLBuffer; 24 | 25 | constructor(o: BufferOptions, glBuffer: WebGLBuffer) { 26 | this.type = o.Type; 27 | this.usage = some(o.Usage, Usage.Immutable); 28 | this.glBuffer = glBuffer; 29 | } 30 | } 31 | 32 | /** 33 | * A Texture object. 34 | */ 35 | export class Texture { 36 | readonly type: TextureType; 37 | readonly usage: Usage; 38 | readonly width: number; 39 | readonly height: number; 40 | readonly depth: number; 41 | readonly numMipMaps: number; 42 | readonly colorFormat: PixelFormat; 43 | readonly depthFormat: DepthStencilFormat; 44 | readonly sampleCount: number; 45 | readonly wrapU: Wrap; 46 | readonly wrapV: Wrap; 47 | readonly wrapW: Wrap; 48 | readonly minFilter: Filter; 49 | readonly magFilter: Filter; 50 | readonly glTexture: WebGLTexture; 51 | readonly glMSAARenderBuffer: WebGLRenderbuffer; 52 | readonly glDepthRenderBuffer: WebGLRenderbuffer; 53 | 54 | constructor(o: TextureOptions, gl: WebGLRenderingContext|WebGL2RenderingContext) { 55 | this.type = o.Type; 56 | this.usage = some(o.Usage, Usage.Immutable); 57 | this.width = o.Width; 58 | this.height = o.Height; 59 | this.depth = some(o.Depth, 1); 60 | this.numMipMaps = some(o.NumMipMaps, 1); 61 | this.colorFormat = o.ColorFormat; 62 | this.depthFormat = some(o.DepthFormat, DepthStencilFormat.NONE); 63 | this.sampleCount = some(o.SampleCount, 1); 64 | this.wrapU = some(o.WrapU, Wrap.ClampToEdge); 65 | this.wrapV = some(o.WrapV, Wrap.ClampToEdge); 66 | this.wrapW = some(o.WrapW, Wrap.ClampToEdge); 67 | this.minFilter = some(o.MinFilter, Filter.Nearest); 68 | this.magFilter = some(o.MagFilter, Filter.Nearest); 69 | this.glTexture = gl.createTexture(); 70 | if (this.sampleCount > 1) { 71 | this.glMSAARenderBuffer = gl.createRenderbuffer(); 72 | } 73 | else { 74 | this.glMSAARenderBuffer = null; 75 | } 76 | if (this.depthFormat != DepthStencilFormat.NONE) { 77 | this.glDepthRenderBuffer = gl.createRenderbuffer(); 78 | } 79 | else { 80 | this.glDepthRenderBuffer = null; 81 | } 82 | } 83 | } 84 | 85 | export class VertexLayout { 86 | components: [string, VertexFormat][]; 87 | stepFunc?: StepFunc; 88 | stepRate?: number; 89 | 90 | constructor(o: VertexLayoutOptions) { 91 | this.components = o.Components; 92 | this.stepFunc = some(o.StepFunc, StepFunc.PerVertex); 93 | this.stepRate = some(o.StepRate, 1); 94 | } 95 | 96 | static vertexFormatByteSize(fmt: VertexFormat): number { 97 | switch (fmt) { 98 | case VertexFormat.Float: 99 | case VertexFormat.Byte4: 100 | case VertexFormat.Byte4N: 101 | case VertexFormat.UByte4: 102 | case VertexFormat.UByte4N: 103 | case VertexFormat.Short2: 104 | case VertexFormat.Short2N: 105 | return 4; 106 | case VertexFormat.Float2: 107 | case VertexFormat.Short4: 108 | case VertexFormat.Short4N: 109 | return 8; 110 | case VertexFormat.Float3: 111 | return 12; 112 | case VertexFormat.Float4: 113 | return 16; 114 | } 115 | } 116 | 117 | byteSize(): number { 118 | let size = 0; 119 | for (let comp of this.components) { 120 | size += VertexLayout.vertexFormatByteSize(comp[1]); 121 | } 122 | return size; 123 | } 124 | 125 | componentByteOffset(compIndex: number): number { 126 | let offset = 0; 127 | for (let i = 0; i < compIndex; i++) { 128 | offset += VertexLayout.vertexFormatByteSize(this.components[i][1]); 129 | } 130 | return offset; 131 | } 132 | } 133 | 134 | export class PipelineState { 135 | blendEnabled: boolean; 136 | blendSrcFactorRGB: BlendFactor; 137 | blendDstFactorRGB: BlendFactor; 138 | blendOpRGB: BlendOp; 139 | blendSrcFactorAlpha: BlendFactor; 140 | blendDstFactorAlpha: BlendFactor; 141 | blendOpAlpha: BlendOp; 142 | colorWriteMask: [boolean, boolean, boolean, boolean]; 143 | blendColor: [number, number, number, number]; 144 | 145 | stencilEnabled: boolean; 146 | 147 | frontStencilFailOp: StencilOp; 148 | frontStencilDepthFailOp: StencilOp; 149 | frontStencilPassOp: StencilOp; 150 | frontStencilCmpFunc: CompareFunc; 151 | frontStencilReadMask: number; 152 | frontStencilWriteMask: number; 153 | frontStencilRef: number; 154 | 155 | backStencilFailOp: StencilOp; 156 | backStencilDepthFailOp: StencilOp; 157 | backStencilPassOp: StencilOp; 158 | backStencilCmpFunc: CompareFunc; 159 | backStencilReadMask: number; 160 | backStencilWriteMask: number; 161 | backStencilRef: number; 162 | 163 | depthCmpFunc: CompareFunc; 164 | depthWriteEnabled: boolean; 165 | 166 | cullFaceEnabled: boolean; 167 | cullFace: Face; 168 | scissorTestEnabled: boolean; 169 | 170 | constructor(o: PipelineOptions) { 171 | this.blendEnabled = some(o.BlendEnabled, false); 172 | this.blendSrcFactorRGB = some3(o.BlendSrcFactorRGB, o.BlendSrcFactor, BlendFactor.One); 173 | this.blendDstFactorRGB = some3(o.BlendDstFactorRGB, o.BlendDstFactor, BlendFactor.Zero); 174 | this.blendOpRGB = some3(o.BlendOpRGB, o.BlendOp, BlendOp.Add); 175 | this.blendSrcFactorAlpha = some3(o.BlendSrcFactorAlpha, o.BlendSrcFactor, BlendFactor.One); 176 | this.blendDstFactorAlpha = some3(o.BlendDstFactorAlpha, o.BlendDstFactor, BlendFactor.Zero); 177 | this.blendOpAlpha = some3(o.BlendOpAlpha, o.BlendOp, BlendOp.Add); 178 | this.colorWriteMask = some(o.ColorWriteMask, [true, true, true, true] as [boolean, boolean, boolean, boolean]); 179 | this.blendColor = some(o.BlendColor, [1.0, 1.0, 1.0, 1.0] as [number, number, number, number]); 180 | 181 | this.stencilEnabled = some(o.StencilEnabled, false); 182 | 183 | this.frontStencilFailOp = some3(o.FrontStencilFailOp, o.StencilFailOp, StencilOp.Keep); 184 | this.frontStencilDepthFailOp = some3(o.FrontStencilDepthFailOp, o.StencilDepthFailOp, StencilOp.Keep); 185 | this.frontStencilPassOp = some3(o.FrontStencilPassOp, o.StencilPassOp, StencilOp.Keep); 186 | this.frontStencilCmpFunc = some3(o.FrontStencilCmpFunc, o.StencilCmpFunc, CompareFunc.Always); 187 | this.frontStencilReadMask = some3(o.FrontStencilReadMask, o.StencilReadMask, 0xFF); 188 | this.frontStencilWriteMask = some3(o.FrontStencilWriteMask, o.StencilWriteMask, 0xFF); 189 | this.frontStencilRef = some3(o.FrontStencilRef, o.StencilRef, 0); 190 | 191 | this.backStencilFailOp = some3(o.BackStencilFailOp, o.StencilFailOp, StencilOp.Keep); 192 | this.backStencilDepthFailOp = some3(o.BackStencilDepthFailOp, o.StencilDepthFailOp, StencilOp.Keep); 193 | this.backStencilPassOp = some3(o.BackStencilPassOp, o.StencilPassOp, StencilOp.Keep); 194 | this.backStencilCmpFunc = some3(o.BackStencilCmpFunc, o.StencilCmpFunc, CompareFunc.Always); 195 | this.backStencilReadMask = some3(o.BackStencilReadMask, o.StencilReadMask, 0xFF); 196 | this.backStencilWriteMask = some3(o.BackStencilWriteMask, o.StencilWriteMask, 0xFF); 197 | this.backStencilRef = some3(o.BackStencilRef, o.StencilRef, 0); 198 | 199 | this.depthCmpFunc = some(o.DepthCmpFunc, CompareFunc.Always); 200 | this.depthWriteEnabled = some(o.DepthWriteEnabled, false); 201 | 202 | this.cullFaceEnabled = some(o.CullFaceEnabled, false); 203 | this.cullFace = some(o.CullFace, Face.Back); 204 | this.scissorTestEnabled = some(o.ScissorTestEnabled, false); 205 | } 206 | } 207 | 208 | class glAttrib { 209 | enabled: boolean = false; 210 | vbIndex: number = 0; 211 | divisor: number = 0; 212 | stride: number = 0; 213 | size: number = 0; 214 | normalized: boolean = false; 215 | offset: number = 0; 216 | type: GLenum = 0; 217 | } 218 | 219 | /** 220 | * Opaque pipeline-state-object. 221 | */ 222 | export class Pipeline { 223 | readonly vertexLayouts: VertexLayout[]; 224 | readonly shader: Shader; 225 | readonly primitiveType: PrimitiveType; 226 | readonly state: PipelineState; 227 | readonly glAttribs: glAttrib[]; 228 | readonly indexFormat: IndexFormat; 229 | readonly indexSize: number; 230 | 231 | constructor(o: PipelineOptions) { 232 | this.vertexLayouts = []; 233 | for (let vlOpt of o.VertexLayouts) { 234 | this.vertexLayouts.push(new VertexLayout(vlOpt)); 235 | } 236 | this.shader = o.Shader; 237 | this.primitiveType = some(o.PrimitiveType, PrimitiveType.Triangles); 238 | this.state = new PipelineState(o); 239 | this.glAttribs = []; 240 | for (let i = 0; i < MaxNumVertexAttribs; i++) { 241 | this.glAttribs.push(new glAttrib()); 242 | } 243 | this.indexFormat = some(o.IndexFormat, IndexFormat.None); 244 | switch (this.indexFormat) { 245 | case IndexFormat.UInt16: this.indexSize = 2; break; 246 | case IndexFormat.UInt32: this.indexSize = 4; break; 247 | default: this.indexSize = 0; break; 248 | } 249 | } 250 | } 251 | 252 | /** 253 | * Opaque shader object. 254 | */ 255 | export class Shader { 256 | readonly glProgram: WebGLProgram; 257 | 258 | constructor(glProgram: WebGLProgram) { 259 | this.glProgram = glProgram; 260 | } 261 | } 262 | 263 | /** 264 | * A DrawState object is a bundle of resource binding slots, 265 | * create with Gfx.makePass(). DrawState objects area 266 | * mutable, the resource binding slots can be 267 | * reconfigured on existing DrawState objects. 268 | */ 269 | export class DrawState { 270 | Pipeline: Pipeline; 271 | VertexBuffers: Buffer[]; 272 | IndexBuffer: Buffer; 273 | Textures: {[key: string]: Texture; }; 274 | 275 | constructor(o: DrawStateOptions) { 276 | this.Pipeline = o.Pipeline; 277 | this.VertexBuffers = o.VertexBuffers; 278 | this.IndexBuffer = some(o.IndexBuffer, null); 279 | this.Textures = some(o.Textures, null); 280 | } 281 | } 282 | 283 | export class ColorAttachment { 284 | texture: Texture; 285 | mipLevel: number; 286 | slice: number; 287 | loadAction: LoadAction; 288 | clearColor: [number, number, number, number]; 289 | readonly glMSAAResolveFramebuffer: WebGLFramebuffer; 290 | 291 | constructor(o: ColorAttachmentOptions, glMsaaFb: WebGLFramebuffer) { 292 | this.texture = some(o.Texture, null); 293 | this.mipLevel = some(o.MipLevel, 0); 294 | this.slice = some(o.Slice, 0); 295 | this.loadAction = some(o.LoadAction, LoadAction.Clear); 296 | this.clearColor = some(o.ClearColor, [0.0, 0.0, 0.0, 1.0] as [number, number, number, number]); 297 | this.glMSAAResolveFramebuffer = glMsaaFb; 298 | } 299 | } 300 | 301 | export class DepthAttachment { 302 | texture: Texture; 303 | loadAction: LoadAction; 304 | clearDepth: number; 305 | clearStencil: number; 306 | 307 | constructor(o: DepthAttachmentOptions) { 308 | this.texture = some(o.Texture, null); 309 | this.loadAction = some(o.LoadAction, LoadAction.Clear); 310 | this.clearDepth = some(o.ClearDepth, 1.0); 311 | this.clearStencil = some(o.ClearStencil, 0); 312 | } 313 | } 314 | 315 | export class Pass { 316 | ColorAttachments: ColorAttachment[]; 317 | DepthAttachment: DepthAttachment; 318 | readonly glFramebuffer: WebGLFramebuffer; 319 | 320 | constructor(o: PassOptions, glFb: WebGLFramebuffer, glMsaaFbs: WebGLFramebuffer[]) { 321 | this.glFramebuffer = glFb; 322 | this.ColorAttachments = []; 323 | if (o.ColorAttachments == null) { 324 | this.ColorAttachments.push(new ColorAttachment({}, null)); 325 | } 326 | else { 327 | for (let i = 0; i < o.ColorAttachments.length; i++) { 328 | const glMsaaFb = (glMsaaFbs && glMsaaFbs[i]) ? glMsaaFbs[i]:null; 329 | this.ColorAttachments.push(new ColorAttachment(o.ColorAttachments[i], glMsaaFb)) 330 | } 331 | } 332 | this.DepthAttachment = new DepthAttachment(some(o.DepthAttachment, {})); 333 | } 334 | } 335 | 336 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "sourceMap": true, 5 | "outFile": "examples/examples.js" 6 | }, 7 | "include": [ 8 | "src/altai.ts", 9 | "examples/*.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-reference": false, 9 | "no-console": false, 10 | "variable-name": { 11 | "options": [ 12 | "allow-leading-underscore", 13 | "allow-snake-case", 14 | "allow-pascal-case" 15 | ] 16 | }, 17 | "max-classes-per-file": false, 18 | "no-trailing-whitespace": false, 19 | "no-bitwise": false, 20 | "no-namespace": false, 21 | "object-literal-sort-keys": false, 22 | "max-line-length": false, 23 | "quotemark": false, 24 | "member-ordering": false, 25 | "array-type": false, 26 | "interface-name": false, 27 | }, 28 | "rulesDirectory": [] 29 | } --------------------------------------------------------------------------------