From f4c0634e97744749818924f18ce7c0e8d5b1f2f3 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 31 Mar 2019 14:46:07 +0100 Subject: [PATCH 001/404] feat(webgl): import webgl pkg, ported & updated from CLJ thi.ng/geom --- packages/webgl/.npmignore | 12 + packages/webgl/LICENSE | 201 +++++++++++ packages/webgl/README.md | 82 +++++ packages/webgl/package.json | 55 +++ packages/webgl/src/api.ts | 494 ++++++++++++++++++++++++++ packages/webgl/src/buffer.ts | 107 ++++++ packages/webgl/src/draw.ts | 106 ++++++ packages/webgl/src/error.ts | 9 + packages/webgl/src/fbo.ts | 123 +++++++ packages/webgl/src/glsl/assemble.ts | 52 +++ packages/webgl/src/glsl/lighting.ts | 48 +++ packages/webgl/src/glsl/syntax.ts | 117 ++++++ packages/webgl/src/glsl/vertex.ts | 28 ++ packages/webgl/src/index.ts | 18 + packages/webgl/src/material.ts | 23 ++ packages/webgl/src/normal-mat.ts | 43 +++ packages/webgl/src/renderbuffer.ts | 57 +++ packages/webgl/src/shader.ts | 333 +++++++++++++++++ packages/webgl/src/shaders/lambert.ts | 70 ++++ packages/webgl/src/shaders/phong.ts | 69 ++++ packages/webgl/src/texture.ts | 113 ++++++ packages/webgl/src/uniforms.ts | 101 ++++++ packages/webgl/src/utils.ts | 23 ++ packages/webgl/test/index.ts | 6 + packages/webgl/test/tsconfig.json | 11 + packages/webgl/tsconfig.json | 11 + 26 files changed, 2312 insertions(+) create mode 100644 packages/webgl/.npmignore create mode 100644 packages/webgl/LICENSE create mode 100644 packages/webgl/README.md create mode 100644 packages/webgl/package.json create mode 100644 packages/webgl/src/api.ts create mode 100644 packages/webgl/src/buffer.ts create mode 100644 packages/webgl/src/draw.ts create mode 100644 packages/webgl/src/error.ts create mode 100644 packages/webgl/src/fbo.ts create mode 100644 packages/webgl/src/glsl/assemble.ts create mode 100644 packages/webgl/src/glsl/lighting.ts create mode 100644 packages/webgl/src/glsl/syntax.ts create mode 100644 packages/webgl/src/glsl/vertex.ts create mode 100644 packages/webgl/src/index.ts create mode 100644 packages/webgl/src/material.ts create mode 100644 packages/webgl/src/normal-mat.ts create mode 100644 packages/webgl/src/renderbuffer.ts create mode 100644 packages/webgl/src/shader.ts create mode 100644 packages/webgl/src/shaders/lambert.ts create mode 100644 packages/webgl/src/shaders/phong.ts create mode 100644 packages/webgl/src/texture.ts create mode 100644 packages/webgl/src/uniforms.ts create mode 100644 packages/webgl/src/utils.ts create mode 100644 packages/webgl/test/index.ts create mode 100644 packages/webgl/test/tsconfig.json create mode 100644 packages/webgl/tsconfig.json diff --git a/packages/webgl/.npmignore b/packages/webgl/.npmignore new file mode 100644 index 0000000000..74ea62d1fa --- /dev/null +++ b/packages/webgl/.npmignore @@ -0,0 +1,12 @@ +.meta +.nyc_output +*.html +*.tgz +build +coverage +dev +doc +export +src* +test +tsconfig.json diff --git a/packages/webgl/LICENSE b/packages/webgl/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/webgl/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/webgl/README.md b/packages/webgl/README.md new file mode 100644 index 0000000000..d0121291a8 --- /dev/null +++ b/packages/webgl/README.md @@ -0,0 +1,82 @@ +# @thi.ng/webgl + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/webgl.svg)](https://www.npmjs.com/package/@thi.ng/webgl) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/webgl.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) + - [Features](#features) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [Authors](#authors) +- [License](#license) + + + +## About + +Declarative WebGL 1.0 / 2.0 low-level abstraction layer, largely ported +& updated from Clojure/ClojureScript versions of +[thi.ng/geom](http://thi.ng/geom) & +[thi.ng/shadergraph](http://thi.ng/shadergraph). + +### Features + +- declarative shader spec + - attribute, varying, uniform & output type declarations via a simple config object + - GLSL code generation of data type declarations + - automatic support for GLES 1.0 & 3.0 + - optional layout attrib layout support for GLES 3 (WebGL2) + - automatic & typed uniform setters + - pre-declared desired GL draw state flags / settings + - customizable shader presets +- declarative geometry / attribute buffer specs +- declarative instancing (always in WebGL2, in WebGL1, via ANGLE ext) +- 2D texture wrapper & config +- FBO (needs updating WebGL2) +- RenderBuffer (needs updating for WebGL2) +- Declarative GLSL shader assembly via library of pure function snippets + - automatic resolution of transitive dependencies + +Status: Alpha / WIP + +## Installation + +```bash +yarn add @thi.ng/webgl +``` + +## Dependencies + +- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) +- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/master/packages/checks) +- [@thi.ng/dgraph](https://github.com/thi-ng/umbrella/tree/master/packages/dgraph) +- [@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/master/packages/matrices) +- [@thi.ng/vectors](https://github.com/thi-ng/umbrella/tree/master/packages/vectors) + +## Usage examples + +TODO + +Initial test (phong shading & instancing): + +[Live demo](https://demo.thi.ng/umbrella/webgl-basics/) / [Source +code](https://gist.github.com/postspectacular/c38741e0a60899a860a241be663cbc81) + +```ts +import * as w from "@thi.ng/webgl"; +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2014 - 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/webgl/package.json b/packages/webgl/package.json new file mode 100644 index 0000000000..fc5e20b4e6 --- /dev/null +++ b/packages/webgl/package.json @@ -0,0 +1,55 @@ +{ + "name": "@thi.ng/webgl", + "version": "0.0.1", + "description": "WebGL abstraction layer", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/webgl", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && yarn build:bundle", + "build:es6": "tsc --declaration", + "build:bundle": "../../scripts/bundle-module", + "test": "rimraf build && tsc -p test/tsconfig.json && nyc mocha build/test/*.js", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib glsl shaders", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn build && yarn publish --access public" + }, + "devDependencies": { + "@types/mocha": "^5.2.5", + "@types/node": "^10.12.15", + "mocha": "^5.2.0", + "nyc": "^13.1.0", + "typedoc": "^0.14.0", + "typescript": "^3.2.2" + }, + "dependencies": { + "@thi.ng/api": "^6.0.0", + "@thi.ng/checks": "^2.1.4", + "@thi.ng/dgraph": "^1.0.13", + "@thi.ng/matrices": "^0.1.14", + "@thi.ng/vectors": "^2.5.0" + }, + "keywords": [ + "abstraction", + "ES6", + "FBO", + "graphics", + "shader", + "texture", + "typescript", + "webgl" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts new file mode 100644 index 0000000000..be5fd793f5 --- /dev/null +++ b/packages/webgl/src/api.ts @@ -0,0 +1,494 @@ +import { + Fn, + Fn2, + Fn3, + IBind, + IObjectOf, + IRelease, + Tuple, + TypedArray +} from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export enum GLSL { + bool, + int, + uint, + float, + vec2, + vec3, + vec4, + bvec2, + bvec3, + bvec4, + ivec2, + ivec3, + ivec4, + mat2, + mat3, + mat4, + sampler2D, + samplerCube, + bool_array, + float_array, + int_array, + uint_array, + vec2_array, + vec3_array, + vec4_array, + bvec2_array, + bvec3_array, + bvec4_array, + ivec2_array, + ivec3_array, + ivec4_array, + mat2_array, + mat3_array, + mat4_array, + sampler2D_array, + samplerCube_array +} + +export type GLVec = number[] | Float32Array; +export type GLVec2 = Tuple | Float32Array; +export type GLVec3 = Tuple | Float32Array; +export type GLVec4 = Tuple | Float32Array; + +export type GLIntVec = number[] | Int32Array; +export type GLUintVec = number[] | Uint32Array; +export type GLIntVec2 = Tuple | Int32Array; +export type GLIntVec3 = Tuple | Int32Array; +export type GLIntVec4 = Tuple | Int32Array; + +export type GLMat2 = Tuple | Float32Array; +export type GLMat3 = Tuple | Float32Array; +export type GLMat4 = Tuple | Float32Array; + +export type AttribType = + | GLSL.bool + | GLSL.float + | GLSL.int + | GLSL.vec2 + | GLSL.vec3 + | GLSL.vec4; + +export type AttribBufferData = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Float32Array; + +export type IndexBufferData = Uint16Array | Uint32Array; + +export type ModelAttributeSpecs = IObjectOf; + +export type UniformValue = number | number[] | TypedArray; + +export type UniformValues = IObjectOf< + UniformValue | Fn2 +>; + +export type ShaderType = "vs" | "fs"; + +export type ScalarType = + | GLSL.bool + | GLSL.float + | GLSL.int + | GLSL.uint + | GLSL.sampler2D + | GLSL.samplerCube; + +export type UniformDefault = + | T + | Fn2, T>; + +export type UniformDecl = + | GLSL + | [ScalarType, UniformDefault] + | [GLSL.bvec2, UniformDefault] + | [GLSL.bvec3, UniformDefault] + | [GLSL.bvec4, UniformDefault] + | [GLSL.ivec2, UniformDefault] + | [GLSL.ivec3, UniformDefault] + | [GLSL.ivec4, UniformDefault] + | [GLSL.vec2, UniformDefault] + | [GLSL.vec3, UniformDefault] + | [GLSL.vec4, UniformDefault] + | [GLSL.mat2, UniformDefault] + | [GLSL.mat3, UniformDefault] + | [GLSL.mat4, UniformDefault] + | [GLSL.bool_array, number, UniformDefault] + | [GLSL.int_array, number, UniformDefault] + | [GLSL.uint_array, number, UniformDefault] + | [GLSL.float_array, number, UniformDefault] + | [GLSL.vec2_array, number, UniformDefault] + | [GLSL.vec3_array, number, UniformDefault] + | [GLSL.vec4_array, number, UniformDefault]; + +/** + * Object of attribute types w/ optional locations. + */ +export type ShaderAttribSpecs = IObjectOf; + +export type ShaderAttribSpec = AttribType | [AttribType, number]; + +/** + * Object of instantiated shader attributes. + */ +export type ShaderAttribs = IObjectOf; + +export interface ShaderAttrib { + type: AttribType; + loc: number; +} + +export type ShaderVaryingSpecs = IObjectOf; + +export type ShaderVaryingSpec = GLSL; + +export type ShaderUniformSpecs = IObjectOf; + +export type ShaderUniforms = IObjectOf; + +export type ShaderOutputSpecs = IObjectOf; + +export type ShaderOutputSpec = GLSL | [GLSL, number]; + +export interface ShaderUniform { + type: GLSL; + loc: WebGLUniformLocation; + setter: Fn; + defaultFn: (shaderUnis: any, specUnis: any) => UniformValue; + defaultVal: UniformValue; +} + +export enum GLSLVersion { + GLES_100 = "100", + GLES_300 = "300 es" +} + +export interface GLSLSyntax { + number: number; + attrib: Fn3; + uniform: Fn3; + varying: Record< + ShaderType, + Fn3 + >; + output: Fn3; +} + +export interface GLSLDeclPrefixes { + a: string; + v: string; + u: string; + o: string; +} + +export interface ShaderSnippet { + /** + * Array of dependent snippets. + */ + deps?: ShaderSnippet[]; + /** + * Snippet source code. + */ + src: string; +} + +export interface ShaderSpec { + /** + * Vertex shader GLSL source code. + */ + vs: string; + /** + * Fragment shader GLSL source code. + */ + fs: string; + /** + * Attribute type declarations. + */ + attribs: ShaderAttribSpecs; + /** + * Varying type declarations. + */ + varying?: ShaderVaryingSpecs; + /** + * Uniform type declarations with optional defaults. + */ + uniforms?: ShaderUniformSpecs; + /** + * WebGL2 only. Fragment shader output variable type declarations. + * Default: `{ fragColor: GLSL.vec4 }` + */ + outputs?: ShaderOutputSpecs; + /** + * GLSL version. Default: GLSLVersion.GLES_100 + */ + version?: GLSLVersion; + /** + * Flag to indicate code generation for attribs, vaarying, uniforms + * and outputs. Default: true. + */ + generateDecls?: boolean; + /** + * Variable naming convention variable prefixes for GLSL code gen. + * + * Defaults: + * + * - Attributes: `a_` + * - Varying: `v_` + * - Uniforms: `u_` + * - Outputs: `o_` + */ + declPrefixes?: Partial; + /** + * Optional prelude source, prepended before main shader code, the + * default prelude (unless disabled) and any other generated code. + */ + pre?: string; + /** + * Optional source code to be appended after main shader code. + */ + post?: string; + /** + * If true, disables default prelude. Default: false + */ + replacePrelude?: boolean; + /** + * Optional shader drawing state flags. Default: none. + */ + state?: Partial; +} + +export interface ShaderState { + /** + * Enable depth test + */ + depth: boolean; + /** + * Cull faces + */ + cull: boolean; + /** + * Cull mode + */ + cullMode: GLenum; + /** + * Enable blending + */ + blend: boolean; + /** + * 2-element array of glBlendFunction coefficients + * (default: `[gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA]`) + */ + blendFn: [GLenum, GLenum]; + /** + * glBlendEquation mode + */ + blendEq: GLenum; + /** + * Enable stencil test + */ + stencil: boolean; + /** + * glStencilFn params + */ + stencilFn: Tuple; + /** + * glStencilOp params + */ + stencilOp: Tuple; + /** + * glStencilMask arg + */ + stencilMask: GLenum; +} + +export interface ShaderOpts { + instancePos: string; + instanceColor: string; + color: string; + material: Partial; + state: Partial; + version: GLSLVersion; +} + +export interface IShader extends IBind, IRelease { + gl: WebGLRenderingContext; + attribs: IObjectOf; + uniforms: ShaderUniforms; + + bindAttribs(specAttribs: ModelAttributeSpecs): void; +} + +export interface IWebGLBuffer extends IBind, IRelease { + set(data: T, mode?: GLenum): void; + setChunk(data: T, offset: number): void; +} + +export interface IConfigure { + configure(opts: T): boolean; +} + +export interface ITexture + extends IBind, + IConfigure>, + IRelease { + tex: WebGLTexture; +} + +export interface IFbo + extends IBind, + IConfigure>, + IRelease {} + +export interface IRenderBuffer extends IBind, IRelease { + buffer: WebGLRenderbuffer; + format: GLenum; + width: number; + height: number; +} + +export interface ModelSpec { + /** + * Initialized `IShader` instance + */ + shader: IShader; + /** + * GLSL attribute declarations + */ + attribs: ModelAttributeSpecs; + /** + * GLSL uniform value overrides + */ + uniforms?: UniformValues; + /** + * Buffer spec for indexed geometry + */ + indices?: IndexBufferSpec; + /** + * Array of initialized `ITexture` instances. + * Each non-null item will be auto-bound to its respective texture unit, + * each time the model is drawn via `draw()` + */ + textures?: ITexture[]; + /** + * Extra configuration for instanced geometry + */ + instances?: InstancingSpec; + /** + * WebGL draw mode + */ + mode: GLenum; + /** + * Number of primitives to draw + */ + numItems: number; +} + +/** + * Data specification of a single WebGL attribute + */ +export interface ModelAttributeSpec { + /** + * Backing `WebGLArrayBuffer` instance. Usually this will be + * auto-initialized by `compileBuffers()` + */ + buffer?: IWebGLBuffer; + /** + * Raw attribute data from which `buffer` will be initialized + */ + data: AttribBufferData; + /** + * Attribute element size (in component values, not bytes). + * Default: 3 + */ + size?: number; + /** + * Auto-normalization flag when writing buffer data. + * Default: false + */ + normalized?: boolean; + /** + * Byte offset of 1st attrib component. + * Default: 0 + */ + offset?: number; + /** + * Attribute stride in bytes. + * Default: 0 = densely packed + */ + stride?: number; + /** + * Attribute's WebGL data type. + * Default: gl.FLOAT + */ + type?: GLenum; + /** + * Only used for instanced attributes. + * See: https://www.khronos.org/registry/OpenGL/extensions/ANGLE/ANGLE_instanced_arrays.txt + */ + divisor?: number; +} + +export interface IndexBufferSpec { + /** + * Backing `WebGLBuffer` instance. Usually this will be + * auto-initialized by `makeBuffersInSpec()` + */ + buffer?: IWebGLBuffer; + /** + * Raw attribute data from which `buffer` will be initialized + */ + data: IndexBufferData; +} + +export interface InstancingSpec { + attribs: IObjectOf; + numItems: number; +} + +export interface TextureOpts { + image: ArrayBufferView | TexImageSource; + target: GLenum; + type: GLenum; + filter: GLenum | GLenum[]; + wrap: GLenum | GLenum[]; + format: GLenum; + mipmap: GLenum; + width: number; + height: number; + flip: boolean; + premultiply: boolean; +} + +export interface FboOpts { + /** + * Array of Texture instances to be used as color attachments. + * Multiple attachments are only allowed if the `webgl_draw_buffers` + * extension is available. The texture at `[0]` will be mapped to + * `COLOR_ATTACHMENT0` (or `COLOR_ATTACHMENT0_WEBGL`), other indices + * are mapped to their respective attachment IDs. + */ + tex: ITexture[]; + /** + * Optional pre-instantiated `RenderBuffer` to be used as depth + * buffer for this FBO. + */ + depth?: IRenderBuffer; +} + +export interface RenderBufferOpts { + format: number; + width: number; + height: number; +} + +export interface Material { + ambientCol: GLVec3; + diffuseCol: GLVec3; + specularCol: GLVec3; +} diff --git a/packages/webgl/src/buffer.ts b/packages/webgl/src/buffer.ts new file mode 100644 index 0000000000..da751ec5ff --- /dev/null +++ b/packages/webgl/src/buffer.ts @@ -0,0 +1,107 @@ +import { TypedArray } from "@thi.ng/api"; +import { + IndexBufferSpec, + IWebGLBuffer, + ModelAttributeSpecs, + ModelSpec +} from "./api"; + +export class WebGLArrayBuffer implements IWebGLBuffer { + gl: WebGLRenderingContext; + buffer: WebGLBuffer; + target: number; + mode: number; + + constructor( + gl: WebGLRenderingContext, + target = gl.ARRAY_BUFFER, + mode = gl.STATIC_DRAW, + data?: T + ) { + this.gl = gl; + this.buffer = gl.createBuffer(); + this.target = target; + this.mode = mode; + data && this.set(data); + } + + bind() { + this.gl.bindBuffer(this.target, this.buffer); + return true; + } + + unbind() { + this.gl.bindBuffer(this.target, null); + return true; + } + + release() { + if (this.buffer) { + this.gl.deleteBuffer(this.buffer); + delete this.buffer; + } + return true; + } + + set(data: T, mode = this.mode) { + this.bind(); + this.gl.bufferData(this.target, data, mode); + } + + setChunk(data: T, offset = 0) { + this.bind(); + this.gl.bufferSubData(this.target, offset, data); + } +} + +export const compileBuffers = ( + gl: WebGLRenderingContext, + spec: ModelSpec, + mode = gl.STATIC_DRAW +) => { + compileAttribs(gl, spec.attribs, mode); + spec.instances && compileAttribs(gl, spec.instances.attribs, mode); + compileIndices(gl, spec.indices, mode); + return spec; +}; + +const compileAttribs = ( + gl: WebGLRenderingContext, + attribs: ModelAttributeSpecs, + mode: GLenum +) => { + if (!attribs) return; + for (let id in attribs) { + if (attribs.hasOwnProperty(id)) { + const attr = attribs[id]; + if (attr.buffer) { + attr.buffer.set(attr.data); + } else { + attr.buffer = new WebGLArrayBuffer( + gl, + gl.ARRAY_BUFFER, + mode, + attr.data + ); + } + } + } +}; + +const compileIndices = ( + gl: WebGLRenderingContext, + index: IndexBufferSpec, + mode: GLenum +) => { + if (!index) return; + if (index.buffer) { + index.buffer.set(index.data); + } else { + index.buffer = new WebGLArrayBuffer( + gl, + gl.ELEMENT_ARRAY_BUFFER, + mode, + index.data + ); + } +}; diff --git a/packages/webgl/src/draw.ts b/packages/webgl/src/draw.ts new file mode 100644 index 0000000000..0393995d22 --- /dev/null +++ b/packages/webgl/src/draw.ts @@ -0,0 +1,106 @@ +import { isArray } from "@thi.ng/checks"; +import { ModelSpec } from "./api"; +import { error } from "./error"; +import { bindTextures } from "./texture"; +import { isGL2Context } from "./utils"; + +export const draw = (specs: ModelSpec | ModelSpec[]) => { + const _specs = isArray(specs) ? specs : [specs]; + for (let i = 0, n = _specs.length; i < n; i++) { + const spec = _specs[i]; + const indices = spec.indices; + const gl = spec.shader.gl; + bindTextures(spec.textures); + spec.shader.bind(spec); + if (indices) { + indices.buffer.bind(); + if (spec.instances) { + drawInstanced(gl, spec); + } else { + gl.drawElements( + spec.mode, + spec.numItems, + indices.data instanceof Uint32Array + ? gl.UNSIGNED_INT + : gl.UNSIGNED_SHORT, + 0 + ); + } + } else { + if (spec.instances) { + drawInstanced(gl, spec); + } else { + gl.drawArrays(spec.mode, 0, spec.numItems); + } + } + spec.shader.unbind(null); + } +}; + +const drawInstanced = (gl: WebGLRenderingContext, spec: ModelSpec) => { + const isGL2 = isGL2Context(gl); + const ext = !isGL2 && gl.getExtension("ANGLE_instanced_arrays"); + if (!(isGL2 || ext)) { + error("instancing not supported"); + } + const sattribs = spec.shader.attribs; + const iattribs = spec.instances.attribs; + spec.shader.bindAttribs(iattribs); + for (let id in iattribs) { + const attr = sattribs[id]; + if (attr) { + let div = iattribs[id].divisor; + div = div !== undefined ? div : 1; + isGL2 + ? (gl).vertexAttribDivisor( + attr.loc, + div + ) + : ext.vertexAttribDivisorANGLE(attr.loc, div); + } + } + if (spec.indices) { + const type = + spec.indices.data instanceof Uint32Array + ? gl.UNSIGNED_INT + : gl.UNSIGNED_SHORT; + isGL2 + ? (gl).drawElementsInstanced( + spec.mode, + spec.numItems, + type, + 0, + spec.instances.numItems + ) + : ext.drawElementsInstancedANGLE( + spec.mode, + spec.numItems, + type, + 0, + spec.instances.numItems + ); + } else { + isGL2 + ? (gl).drawArraysInstanced( + spec.mode, + 0, + spec.numItems, + spec.instances.numItems + ) + : ext.drawArraysInstancedANGLE( + spec.mode, + 0, + spec.numItems, + spec.instances.numItems + ); + } + // reset attrib divisors to allow non-instanced draws later on + for (let id in iattribs) { + const attr = sattribs[id]; + attr && + (isGL2 + ? (gl).vertexAttribDivisor(attr.loc, 0) + : ext.vertexAttribDivisorANGLE(attr.loc, 0)); + } + spec.shader.unbind(null); +}; diff --git a/packages/webgl/src/error.ts b/packages/webgl/src/error.ts new file mode 100644 index 0000000000..4df70f8b6e --- /dev/null +++ b/packages/webgl/src/error.ts @@ -0,0 +1,9 @@ +export class WebGLError extends Error { + constructor(msg?: string) { + super(`WebGL error ${msg ? ": " + msg : ""}`); + } +} + +export const error = (msg?: string) => { + throw new WebGLError(msg); +}; diff --git a/packages/webgl/src/fbo.ts b/packages/webgl/src/fbo.ts new file mode 100644 index 0000000000..fb83716c1d --- /dev/null +++ b/packages/webgl/src/fbo.ts @@ -0,0 +1,123 @@ +import { assert } from "@thi.ng/api"; +import { FboOpts, IFbo } from "./api"; +import { error } from "./error"; + +const GL_MAX_COLOR_ATTACHMENTS_WEBGL = 0x8cdf; +const COLOR_ATTACHMENT0_WEBGL = 0x8ce0; + +/** + * WebGL framebuffer wrapper w/ automatic detection & support for + * multiple render targets (color attachments) and optional depth + * buffer. Multiple targets are only supported if the + * `WEBGL_draw_buffers` extension is available. The max. number of + * attachments can be obtained via the `maxAttachments` property of the + * FBO instance. + * + * ``` + * // create FBO w/ 2 render targets + * fbo = new FBO(gl, { tex: [tex1, tex2] }); + * ``` + * + * https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_draw_buffers + */ +export class FBO implements IFbo { + gl: WebGLRenderingContext; + fbo: WebGLFramebuffer; + ext: WEBGL_draw_buffers; + readonly maxAttachments: number; + + constructor(gl: WebGLRenderingContext, opts?: Partial) { + this.gl = gl; + this.fbo = gl.createFramebuffer(); + this.ext = gl.getExtension("WEBGL_draw_buffers"); + // TODO WebGL2 support + this.maxAttachments = this.ext + ? gl.getParameter(GL_MAX_COLOR_ATTACHMENTS_WEBGL) + : 1; + opts && this.configure(opts); + } + + bind() { + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo); + return true; + } + + unbind() { + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + return true; + } + + release() { + this.gl.deleteFramebuffer(this.fbo); + delete this.fbo; + delete this.ext; + return true; + } + + configure(opts: Partial) { + const gl = this.gl; + this.bind(); + if (opts.tex) { + if (this.ext) { + assert( + opts.tex.length < this.maxAttachments, + `too many attachments (max. ${this.maxAttachments})` + ); + const attachments: number[] = []; + for (let i = 0; i < opts.tex.length; i++) { + const attach = COLOR_ATTACHMENT0_WEBGL + i; + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + attach, + gl.TEXTURE_2D, + opts.tex[i].tex, + 0 + ); + attachments[i] = attach; + } + // TODO WebGL2 support + this.ext.drawBuffersWEBGL(attachments); + } else { + assert( + opts.tex.length === 1, + "only single color attachment allowed (webgl_draw_buffers ext unavailable)" + ); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + opts.tex[0].tex, + 0 + ); + } + } + if (opts.depth) { + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, + opts.depth.buffer + ); + } + return this.validate(); + } + + validate() { + const gl = this.gl; + const err = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + switch (err) { + case gl.FRAMEBUFFER_COMPLETE: + return true; + case gl.FRAMEBUFFER_UNSUPPORTED: + error("FBO unsupported"); + case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + error("FBO incomplete attachment"); + case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + error("FBO incomplete dimensions"); + case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + error("FBO incomplete missing attachment"); + default: + error(`FBO error: ${err}`); + } + } +} diff --git a/packages/webgl/src/glsl/assemble.ts b/packages/webgl/src/glsl/assemble.ts new file mode 100644 index 0000000000..c4dd19b40f --- /dev/null +++ b/packages/webgl/src/glsl/assemble.ts @@ -0,0 +1,52 @@ +import { DGraph } from "@thi.ng/dgraph"; +import { ShaderSnippet } from "../api"; + +/** + * Build shader snippet dependency graph. + * + * @param spec + * @param graph + */ +const buildGraph = (spec: ShaderSnippet, graph?: DGraph) => + spec.deps + ? spec.deps.reduce( + (graph, d) => buildGraph(d, graph.addDependency(spec, d)), + graph || new DGraph() + ) + : graph; + +/** + * Takes given shader snippet, resolves all transitive dependencies and + * assembles source code in dependency order. + * + * @param spec + */ +export const assemble = (spec: ShaderSnippet) => + spec.deps + ? buildGraph(spec) + .sort() + .map((s) => s.src) + .join("\n") + : spec.src; + +/** + * Combines given GLSL `src` and optional `ShaderSnippet` `deps` into a + * new snippet. + * + * @param src + * @param deps + */ +export const defglsl = (src: string, deps?: ShaderSnippet[]) => + { src, deps }; + +/** + * Like `defglsl`, but then immediately assembles the full source code, + * incl. all transitive dependency snippets (in correct order). + * + * @see assemble + * + * @param src + * @param deps + */ +export const defglslA = (src: string, deps?: ShaderSnippet[]) => + assemble(defglsl(src, deps)); diff --git a/packages/webgl/src/glsl/lighting.ts b/packages/webgl/src/glsl/lighting.ts new file mode 100644 index 0000000000..b793cbbd99 --- /dev/null +++ b/packages/webgl/src/glsl/lighting.ts @@ -0,0 +1,48 @@ +import { defglsl } from "./assemble"; + +/** + * Computes lambert coefficient. + * + * @param surfNormal + * @param lightDir + * @param bidir + */ +export const lambert = defglsl( + `float lambert(vec3 surfNormal, vec3 lightDir, bool bidir) { + float d = dot(surfNormal, lightDir); + return clamp(bidir ? abs(d) : d, 0.0, 1.0); +}` +); + +export const phong = defglsl( + `float phong(vec3 lightDir, vec3 eyeDir, vec3 surfaceNormal) { + return dot(reflect(-lightDir, surfaceNormal), eyeDir); +}` +); + +export const blinnPhong = defglsl( + `float blinnPhong(vec3 lightDir, vec3 eyeDir, vec3 surfaceNormal) { + return dot(normalize(lightDir + eyeDir), surfaceNormal); +}` +); + +export const beckmannDistribution = defglsl( + `float beckmannDistribution(float x, float roughness) { + float NdotH = max(x, 1e-4); + float cos2Alpha = NdotH * NdotH; + float tan2Alpha = (cos2Alpha - 1.0) / cos2Alpha; + float roughness2 = roughness * roughness; + float denom = PI * roughness2 * cos2Alpha * cos2Alpha; + return exp(tan2Alpha / roughness2) / denom; +}` +); + +export const beckmannSpecular = defglsl( + `float beckmannSpecular(vec3 lightDir, + vec3 viewDir, + vec3 surfNormal, + float roughness) { + return beckmannDistribution(dot(surfNormal, normalize(lightDir + viewDir)), roughness); +}`, + [beckmannDistribution] +); diff --git a/packages/webgl/src/glsl/syntax.ts b/packages/webgl/src/glsl/syntax.ts new file mode 100644 index 0000000000..fd4bc049d9 --- /dev/null +++ b/packages/webgl/src/glsl/syntax.ts @@ -0,0 +1,117 @@ +import { isArray } from "@thi.ng/checks"; +import { + GLSL, + GLSLDeclPrefixes, + GLSLSyntax, + GLSLVersion +} from "../api"; + +export const PREFIXES: GLSLDeclPrefixes = { + a: "a_", + v: "v_", + u: "u_", + o: "o_" +}; + +export const NO_PREFIXES: GLSLDeclPrefixes = { + a: "", + v: "", + u: "", + o: "" +}; + +/** + * GLSL data declaration code generators. + */ +export const SYNTAX: Record = { + /** + * WebGL (GLSL ES 1.0) + */ + [GLSLVersion.GLES_100]: { + number: 100, + attrib: (id, type, pre) => + `attribute ${GLSL[isArray(type) ? type[0] : type]} ${pre.a}${id};`, + varying: { + vs: (id, type, pre) => `varying ${GLSL[type]} ${pre.v}${id};`, + fs: (id, type, pre) => `varying ${GLSL[type]} ${pre.v}${id};` + }, + uniform: (id, u, pre) => { + const type = isArray(u) ? u[0] : u; + return `uniform ${GLSL[type]}${ + type >= GLSL.bool_array ? `[${u[1]}]` : "" + } ${pre.u}${id};`; + }, + output: () => "" + }, + /** + * WebGL 2 (GLSL ES 3) + */ + [GLSLVersion.GLES_300]: { + number: 300, + attrib: (id, type, pre) => + isArray(type) + ? `layout(location=${type[1]}) in ${GLSL[type[0]]} a_${ + pre.a + }${id};` + : `in ${GLSL[type]} ${id};`, + varying: { + vs: (id, type, pre) => `out ${GLSL[type]} v_${pre.v}${id};`, + fs: (id, type, pre) => `in ${GLSL[type]} v_${pre.v}${id};` + }, + uniform: (id, u, pre) => { + const type = isArray(u) ? u[0] : u; + return `uniform ${GLSL[type]}${ + type >= GLSL.bool_array ? `[${u[1]}]` : "" + } u_${pre.u}${id};`; + }, + output: (id, type, pre) => + isArray(type) + ? `layout(location=${type[1]}) out ${GLSL[type[0]]} ${ + pre.o + }${id};` + : `out ${GLSL[type]} ${pre.o}${id};` + } +}; + +/** + * GLSL preprocessor macro for conditional execution based on `__VERSION__`. + * + * @param ver + * @param ok + * @param fail + */ +export const VERSION_CHECK = (ver: number, ok: string, fail = "") => + `#if __VERSION__ >= ${ver} +${ok}${fail ? `\n#else\n${fail}` : ""} +#endif`; + +/** + * GLSL version specific fragment shader output. If `__VERSION__ >= 300` + * assigns `body` to `out`, else to `gl_FragColor`. + * + * @param body + * @param out + */ +export const EXPORT_FRAGCOL = (body = "col", out = "fragColor") => + VERSION_CHECK(300, `${out}=${body};`, `gl_FragColor=${body};`); + +/** + * Default GLSL prelude. + */ +export const GLSL_HEADER = `#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp int; +precision highp float; +#else +precision mediump int; +precision mediump float; +#endif +#ifndef PI +#define PI 3.141592653589793 +#endif +#ifndef TAU +#define TAU 6.283185307179586 +#endif +#ifndef HALF_PI +#define HALF_PI 1.570796326794896 +#endif +`; diff --git a/packages/webgl/src/glsl/vertex.ts b/packages/webgl/src/glsl/vertex.ts new file mode 100644 index 0000000000..2740e071d9 --- /dev/null +++ b/packages/webgl/src/glsl/vertex.ts @@ -0,0 +1,28 @@ +import { defglsl } from "./assemble"; + +/** + * Multiplies `pos` with model, view & projection matrices (in order). + * + * @param pos + * @param model + * @param view + * @param proj + */ +export const mvp = defglsl( + `vec4 mvp(vec3 pos, mat4 model, mat4 view, mat4 proj) { + return proj * view * model * vec4(pos, 1.0); +}` +); + +/** + * Multiplies `normal` with given 4x4 normal matrix (e.g. transpose + * inverse of view * model). + * + * @param normal + * @param normalMat + */ +export const surfaceNormal = defglsl( + `vec3 surfaceNormal(vec3 normal, mat4 normalMat) { + return normalize((normalMat * vec4(normal, 0.0)).xyz); +}` +); diff --git a/packages/webgl/src/index.ts b/packages/webgl/src/index.ts new file mode 100644 index 0000000000..73fcdeaade --- /dev/null +++ b/packages/webgl/src/index.ts @@ -0,0 +1,18 @@ +export * from "./api"; +export * from "./buffer"; +export * from "./draw"; +export * from "./error"; +export * from "./fbo"; +export * from "./material"; +export * from "./normal-mat"; +export * from "./shader"; +export * from "./texture"; + +export * from "./shaders/lambert"; +export * from "./shaders/phong"; + +export * from "./glsl/assemble"; +export * from "./glsl/syntax"; + +export * from "./glsl/lighting"; +export * from "./glsl/vertex"; diff --git a/packages/webgl/src/material.ts b/packages/webgl/src/material.ts new file mode 100644 index 0000000000..9797253ac4 --- /dev/null +++ b/packages/webgl/src/material.ts @@ -0,0 +1,23 @@ +import { GLSL, Material, ShaderUniformSpecs } from "./api"; + +export const DEFAULT_MATERIAL: Material = { + ambientCol: [0.1, 0.1, 0.1], + diffuseCol: [0.8, 0.8, 0.8], + specularCol: [1, 1, 1] +}; + +const TYPES: Record = { + ambientCol: GLSL.vec3, + diffuseCol: GLSL.vec3, + specularCol: GLSL.vec3 +}; + +export const defMaterial = ( + mat: Partial = {}, + flags: Partial> = {}, + base = DEFAULT_MATERIAL +): ShaderUniformSpecs => + Object.keys(base).reduce((acc, id) => { + flags[id] !== false && (acc[id] = [TYPES[id], mat[id] || base[id]]); + return acc; + }, {}); diff --git a/packages/webgl/src/normal-mat.ts b/packages/webgl/src/normal-mat.ts new file mode 100644 index 0000000000..05b11eb0a0 --- /dev/null +++ b/packages/webgl/src/normal-mat.ts @@ -0,0 +1,43 @@ +import { IObjectOf } from "@thi.ng/api"; +import { + IDENT44, + invert44, + mulM44, + transpose44 +} from "@thi.ng/matrices"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { GLMat4, ShaderUniforms } from "./api"; + +const $ = (a: any, b: any, id: string) => a[id] || b[id].defaultVal || IDENT44; + +/** + * Computes the inverse transpose of given 4x4 matrix uniform, i.e. + * `transpose(invert(m))`. + * + * @param model + */ +export const autoNormalMatrix1 = (model = "model") => ( + shaderU: ShaderUniforms, + specU: IObjectOf +) => transpose44(null, invert44([], $(specU, shaderU, model))); + +/** + * Computes the inverse transpose of the matrix product of given 4x4 + * matrix uniforms, i.e. `transpose(invert(view * model))`. + * + * @param model + * @param view + */ +export const autoNormalMatrix2 = (model = "model", view = "view") => ( + shaderU: ShaderUniforms, + specU: IObjectOf +) => + ( + transpose44( + null, + invert44( + null, + mulM44([], $(specU, shaderU, view), $(specU, shaderU, model)) + ) + ) + ); diff --git a/packages/webgl/src/renderbuffer.ts b/packages/webgl/src/renderbuffer.ts new file mode 100644 index 0000000000..91d1fce8aa --- /dev/null +++ b/packages/webgl/src/renderbuffer.ts @@ -0,0 +1,57 @@ +import { IRenderBuffer, RenderBufferOpts } from "./api"; + +export class RenderBuffer implements IRenderBuffer { + static newDepthBuffer( + gl: WebGLRenderingContext, + width: number, + height: number + ) { + return new RenderBuffer(gl, { + format: gl.DEPTH_COMPONENT16, + width, + height + }); + } + + gl: WebGLRenderingContext; + buffer: WebGLRenderbuffer; + format: number; + width: number; + height: number; + + constructor(gl: WebGLRenderingContext, opts?: Partial) { + this.gl = gl; + this.buffer = gl.createRenderbuffer(); + opts && this.configure(opts); + } + + bind() { + this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.buffer); + return true; + } + + unbind() { + this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null); + return true; + } + + release() { + this.gl.deleteRenderbuffer(this.buffer); + delete this.buffer; + return true; + } + + configure(opts: Partial) { + const gl = this.gl; + this.bind(); + gl.renderbufferStorage( + gl.RENDERBUFFER, + opts.format, + opts.width, + opts.height + ); + this.unbind(); + Object.assign(this, opts); + return true; + } +} diff --git a/packages/webgl/src/shader.ts b/packages/webgl/src/shader.ts new file mode 100644 index 0000000000..c40a024073 --- /dev/null +++ b/packages/webgl/src/shader.ts @@ -0,0 +1,333 @@ +import { Fn3, IObjectOf, Tuple } from "@thi.ng/api"; +import { existsAndNotNull, isArray, isFunction } from "@thi.ng/checks"; +import { + GLSL, + GLSLDeclPrefixes, + GLSLVersion, + IShader, + ModelAttributeSpecs, + ModelSpec, + ShaderAttrib, + ShaderAttribSpecs, + ShaderSpec, + ShaderState, + ShaderType, + ShaderUniform, + ShaderUniforms, + ShaderUniformSpecs, + UniformValues +} from "./api"; +import { error } from "./error"; +import { GLSL_HEADER, PREFIXES, SYNTAX } from "./glsl/syntax"; +import { UNIFORM_SETTERS } from "./uniforms"; + +// [SRC_ALPHA, ONE_MINUS_SRC_ALPHA]; +export const DEFAULT_BLEND: Tuple = [0x302, 0x303]; + +const ERROR_REGEXP = /ERROR: \d+:(\d+): (.*)/; + +export class Shader implements IShader { + static fromSpec(gl: WebGLRenderingContext, spec: ShaderSpec) { + const srcVS = prepareShaderSource(spec, "vs"); + const srcFS = prepareShaderSource(spec, "fs"); + console.log(srcVS); + console.log(srcFS); + const vs = compileShader(gl, gl.VERTEX_SHADER, srcVS); + const fs = compileShader(gl, gl.FRAGMENT_SHADER, srcFS); + const program = gl.createProgram(); + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + if (gl.getProgramParameter(program, gl.LINK_STATUS)) { + const attribs = initAttributes( + gl, + program, + spec.attribs, + spec.version + ); + const uniforms = initUniforms(gl, program, spec.uniforms); + gl.deleteShader(vs); + gl.deleteShader(fs); + return new Shader(gl, program, attribs, uniforms, spec.state); + } + throw new Error( + `Error linking shader: ${gl.getProgramInfoLog(program)}` + ); + } + + gl: WebGLRenderingContext; + program: WebGLProgram; + attribs: IObjectOf; + uniforms: ShaderUniforms; + state: Partial; + + constructor( + gl: WebGLRenderingContext, + program: WebGLProgram, + attribs: IObjectOf, + uniforms: ShaderUniforms, + state: Partial + ) { + this.gl = gl; + this.program = program; + this.attribs = attribs; + this.uniforms = uniforms; + this.state = state || {}; + } + + bind(spec: ModelSpec) { + if (this.program) { + this.prepareShaderState(); + this.gl.useProgram(this.program); + this.bindAttribs(spec.attribs); + this.bindUniforms(spec.uniforms); + return true; + } + return false; + } + + unbind() { + this.gl.useProgram(null); + return true; + } + + release() { + if (this.program) { + this.gl.deleteProgram(this.program); + delete this.program; + return true; + } + return false; + } + + bindAttribs(specAttribs: ModelAttributeSpecs) { + const gl = this.gl; + let shaderAttrib; + for (let id in specAttribs) { + if (specAttribs.hasOwnProperty(id)) { + if ((shaderAttrib = this.attribs[id])) { + const attr = specAttribs[id]; + attr.buffer.bind(); + gl.enableVertexAttribArray(shaderAttrib.loc); + gl.vertexAttribPointer( + shaderAttrib.loc, + attr.size || 3, + attr.type || gl.FLOAT, + attr.normalized, + attr.stride || 0, + attr.offset || 0 + ); + } else { + console.warn(`unknown attrib: ${id}`); + } + } + } + } + + bindUniforms(specUnis: UniformValues) { + const shaderUnis = this.uniforms; + for (let id in specUnis) { + if (specUnis.hasOwnProperty(id)) { + const u = shaderUnis[id]; + if (u) { + const val = specUnis[id]; + u.setter(isFunction(val) ? val(shaderUnis, specUnis) : val); + } else { + console.warn(`unknown uniform: ${id}`); + } + } + } + // apply defaults for non-specified uniforms in user spec + for (let id in shaderUnis) { + if ( + shaderUnis.hasOwnProperty(id) && + (!specUnis || !existsAndNotNull(specUnis[id])) + ) { + const u = shaderUnis[id]; + u.setter( + u.defaultFn ? u.defaultFn(shaderUnis, specUnis) : undefined + ); + } + } + } + + prepareShaderState(state = this.state) { + const gl = this.gl; + state.depth !== undefined && this.setState(gl.DEPTH_TEST, state.depth); + if (state.cull !== undefined) { + this.setState(gl.CULL_FACE, state.cull); + state.cullMode && gl.cullFace(state.cullMode); + } + if (state.blend !== undefined) { + this.setState(gl.BLEND, state.blend); + state.blendFn && gl.blendFunc(state.blendFn[0], state.blendFn[1]); + state.blendEq !== undefined && gl.blendEquation(state.blendEq); + } + if (state.stencil !== undefined) { + this.setState(gl.STENCIL_TEST, state.stencil); + state.stencilFn && + gl.stencilFunc( + state.stencilFn[0], + state.stencilFn[1], + state.stencilFn[2] + ); + state.stencilOp && + gl.stencilOp( + state.stencilOp[0], + state.stencilOp[1], + state.stencilOp[2] + ); + state.stencilMask !== undefined && + gl.stencilMask(state.stencilMask); + } + } + + setState(id: number, val: GLenum | boolean) { + if (val) { + this.gl.enable(id); + } else { + this.gl.disable(id); + } + } +} + +const compileVars = ( + attribs: any, + syntax: Fn3, + prefixes: GLSLDeclPrefixes +) => { + let decls: string[] = []; + for (let id in attribs) { + if (attribs.hasOwnProperty(id)) { + decls.push(syntax(id, attribs[id], prefixes)); + } + } + decls.push(""); + return decls.join("\n"); +}; + +export const prepareShaderSource = (spec: ShaderSpec, type: ShaderType) => { + const syntax = SYNTAX[spec.version || GLSLVersion.GLES_100]; + const prefixes = { ...PREFIXES, ...spec.declPrefixes }; + const isVS = type === "vs"; + let src = ""; + spec.version && (src += `#version ${spec.version}\n`); + src += spec.pre + ? spec.replacePrelude + ? spec.pre + : spec.pre + "\n" + GLSL_HEADER + : GLSL_HEADER; + if (spec.generateDecls !== false) { + src += isVS + ? compileVars(spec.attribs, syntax.attrib, prefixes) + : compileVars( + spec.outputs || { fragColor: GLSL.vec4 }, + syntax.output, + prefixes + ); + src += compileVars(spec.varying, syntax.varying[type], prefixes); + src += compileVars(spec.uniforms, syntax.uniform, prefixes); + } + src += spec[type]; + spec.post && (src += "\n" + spec.post); + return src; +}; + +export const compileShader = ( + gl: WebGLRenderingContext, + type: GLenum, + src: string +) => { + const shader = gl.createShader(type); + gl.shaderSource(shader, src); + gl.compileShader(shader); + if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + return shader; + } + parseAndThrowShaderError(gl, shader, src); +}; + +const parseAndThrowShaderError = ( + gl: WebGLRenderingContext, + shader: WebGLShader, + src: string +) => { + const lines = src.split("\n"); + const log = gl.getShaderInfoLog(shader).split("\n"); + const errors = log + .map((line) => { + const matches = ERROR_REGEXP.exec(line); + const ln = matches ? matches[1] : null; + if (ln) { + return `line ${ln}: ${matches[2]}\n${lines[parseInt(ln) - 1]}`; + } + }) + .filter(existsAndNotNull) + .join("\n"); + error(`Error compiling shader:\n${errors}`); +}; + +const initAttributes = ( + gl: WebGLRenderingContext, + prog: WebGLProgram, + attribs: ShaderAttribSpecs, + version: GLSLVersion +) => { + const res = >{}; + for (let id in attribs) { + if (attribs.hasOwnProperty(id)) { + const val = attribs[id]; + const [type, loc] = isArray(val) ? val : [val, null]; + const aid = `a_${id}`; + if (loc != null && version && version >= "300") { + gl.bindAttribLocation(prog, loc, aid); + res[id] = { type, loc }; + } else { + res[id] = { + type, + loc: gl.getAttribLocation(prog, aid) + }; + } + } + } + return res; +}; + +const initUniforms = ( + gl: WebGLRenderingContext, + prog: WebGLProgram, + uniforms: ShaderUniformSpecs +) => { + const res = >{}; + for (let id in uniforms) { + if (uniforms.hasOwnProperty(id)) { + const val = uniforms[id]; + let type: GLSL; + let t1, t2, defaultVal, defaultFn; + if (isArray(val)) { + [type, t1, t2] = val; + defaultVal = type < GLSL.bool_array ? t1 : t2; + if (isFunction(defaultVal)) { + defaultFn = defaultVal; + defaultVal = undefined; + } + } else { + type = val; + } + const loc = gl.getUniformLocation(prog, `u_${id}`); + const setter = UNIFORM_SETTERS[type]; + if (setter) { + res[id] = { + loc, + setter: setter(gl, loc, defaultVal), + defaultFn, + defaultVal, + type + }; + } else { + error(`invalid uniform type: ${GLSL[type]}`); + } + } + } + return res; +}; diff --git a/packages/webgl/src/shaders/lambert.ts b/packages/webgl/src/shaders/lambert.ts new file mode 100644 index 0000000000..d925f8651b --- /dev/null +++ b/packages/webgl/src/shaders/lambert.ts @@ -0,0 +1,70 @@ +import { normalize } from "@thi.ng/vectors"; +import { + GLSL, + GLVec3, + Material, + ShaderOpts, + ShaderSpec +} from "../api"; +import { defglslA } from "../glsl/assemble"; +import { lambert } from "../glsl/lighting"; +import { EXPORT_FRAGCOL } from "../glsl/syntax"; +import { mvp, surfaceNormal } from "../glsl/vertex"; +import { defMaterial } from "../material"; +import { autoNormalMatrix2 } from "../normal-mat"; +import { colorAttrib, positionAttrib } from "../utils"; + +export type LambertOpts = ShaderOpts< + Pick +>; + +export const LAMBERT = (opts: Partial = {}): ShaderSpec => ({ + vs: defglslA( + `void main(){ + v_col = ${colorAttrib(opts)}; + v_normal = surfaceNormal(a_normal, u_normalMat); + gl_Position = mvp(${positionAttrib(opts)}, u_model, u_view, u_proj); +}`, + [surfaceNormal, mvp] + ), + fs: defglslA( + `void main(){ + float lam = lambert(normalize(v_normal), u_lightDir, u_bidir); + vec4 col = vec4(u_ambientCol + v_col * u_lightCol * lam, u_alpha); +${EXPORT_FRAGCOL("col")} +}`, + [lambert] + ), + attribs: { + position: GLSL.vec3, + normal: GLSL.vec3, + ...(opts.color && !opts.instanceColor + ? { [opts.color]: GLSL.vec3 } + : null), + ...(opts.instancePos ? { [opts.instancePos]: GLSL.vec3 } : null), + ...(opts.instanceColor ? { [opts.instanceColor]: GLSL.vec3 } : null) + }, + varying: { + col: GLSL.vec3, + normal: GLSL.vec3 + }, + uniforms: { + model: GLSL.mat4, + view: GLSL.mat4, + proj: GLSL.mat4, + normalMat: [GLSL.mat4, autoNormalMatrix2()], + lightDir: [GLSL.vec3, normalize(null, [-1, 1, 1])], + lightCol: [GLSL.vec3, [1, 1, 1]], + ...defMaterial( + { diffuseCol: [1, 1, 1], ...opts.material }, + { specularCol: false } + ), + alpha: [GLSL.float, 1], + bidir: [GLSL.bool, 0] + }, + state: { + depth: true, + ...opts.state + }, + version: opts.version +}); diff --git a/packages/webgl/src/shaders/phong.ts b/packages/webgl/src/shaders/phong.ts new file mode 100644 index 0000000000..9e3d16c29c --- /dev/null +++ b/packages/webgl/src/shaders/phong.ts @@ -0,0 +1,69 @@ +import { + GLSL, + Material, + ShaderOpts, + ShaderSpec +} from "../api"; +import { defglslA } from "../glsl/assemble"; +import { EXPORT_FRAGCOL } from "../glsl/syntax"; +import { surfaceNormal } from "../glsl/vertex"; +import { defMaterial } from "../material"; +import { autoNormalMatrix1 } from "../normal-mat"; +import { colorAttrib, positionAttrib } from "../utils"; + +export type PhongOpts = ShaderOpts< + Pick +>; + +export const PHONG = (opts: Partial = {}): ShaderSpec => ({ + vs: defglslA( + `void main(){ + vec3 pos = ${positionAttrib(opts)}; + vec4 worldPos = u_model * vec4(pos, 1.0); + v_normal = surfaceNormal(a_normal, u_normalMat); + v_light = u_lightPos - worldPos.xyz; + v_eye = u_eyePos - worldPos.xyz; + v_col = ${colorAttrib(opts)}; + gl_Position = u_proj * u_view * worldPos; +}`, + [surfaceNormal] + ), + fs: `void main() { + vec3 N = normalize(v_normal); + vec3 L = normalize(v_light); + float directional = max(0.0, dot(N, L)); + float specular = directional > 0.0 + ? pow(dot(N, normalize(L + normalize(v_eye))), u_shininess) + : 0.0; + vec3 col = u_ambientCol + v_col * directional * u_lightCol + u_specularCol * specular; +${EXPORT_FRAGCOL("vec4(col, 1.0)")} +}`, + attribs: { + position: GLSL.vec3, + normal: GLSL.vec3, + ...(opts.color && !opts.instanceColor + ? { [opts.color]: GLSL.vec3 } + : null), + ...(opts.instancePos ? { [opts.instancePos]: GLSL.vec3 } : null), + ...(opts.instanceColor ? { [opts.instanceColor]: GLSL.vec3 } : null) + }, + varying: { + normal: GLSL.vec3, + eye: GLSL.vec3, + light: GLSL.vec3, + col: GLSL.vec3 + }, + uniforms: { + model: GLSL.mat4, + normalMat: [GLSL.mat4, autoNormalMatrix1()], + view: GLSL.mat4, + proj: GLSL.mat4, + shininess: [GLSL.float, 32], + eyePos: GLSL.vec3, + lightPos: [GLSL.vec3, [0, 0, 2]], + lightCol: [GLSL.vec3, [1, 1, 1]], + ...defMaterial(opts.material) + }, + state: { depth: true, ...opts.state }, + version: opts.version +}); diff --git a/packages/webgl/src/texture.ts b/packages/webgl/src/texture.ts new file mode 100644 index 0000000000..da489945d4 --- /dev/null +++ b/packages/webgl/src/texture.ts @@ -0,0 +1,113 @@ +import { isArray } from "@thi.ng/checks"; +import { ITexture, TextureOpts } from "./api"; + +export const bindTextures = (textures: ITexture[]) => { + if (!textures) return; + for (let i = textures.length, tex; --i >= 0; ) { + (tex = textures[i]) && tex.bind(i); + } +}; + +export class Texture implements ITexture { + gl: WebGLRenderingContext; + tex: WebGLTexture; + target: GLenum; + + constructor(gl: WebGLRenderingContext, opts?: Partial) { + this.gl = gl; + this.tex = gl.createTexture(); + this.target = opts.target || gl.TEXTURE_2D; + this.configure(opts); + } + + configure(opts: Partial) { + const gl = this.gl; + const target = (this.target = opts.target || gl.TEXTURE_2D); + const format = opts.format || gl.RGBA; + const type = opts.type || gl.UNSIGNED_BYTE; + let t1: GLenum, t2: GLenum; + + gl.bindTexture(target, this.tex); + + if (opts.filter) { + const flt = opts.filter; + if (isArray(flt)) { + t1 = flt[0]; + t2 = flt[1]; + } else { + t1 = t2 = flt; + } + gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, t1); + gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, t2); + } + + if (opts.wrap) { + const wrap = opts.wrap; + if (isArray(wrap)) { + t1 = wrap[0]; + t2 = wrap[1]; + } else { + t1 = t2 = wrap; + } + gl.texParameteri(target, gl.TEXTURE_WRAP_S, t1); + gl.texParameteri(target, gl.TEXTURE_WRAP_T, t2); + } + + opts.flip !== undefined && + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, opts.flip ? 1 : 0); + + opts.premultiply !== undefined && + gl.pixelStorei( + gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, + opts.premultiply ? 1 : 0 + ); + + if (opts.image) { + if (opts.width && opts.height) { + gl.texImage2D( + target, + 0, + format, + opts.width, + opts.height, + 0, + format, + type, + opts.image + ); + } else { + gl.texImage2D(target, 0, format, format, type, ( + opts.image + )); + } + } + + opts.mipmap !== undefined && gl.generateMipmap(opts.mipmap); + + return true; + } + + bind(id = 0) { + const gl = this.gl; + gl.activeTexture(gl.TEXTURE0 + id); + gl.bindTexture(this.target, this.tex); + return true; + } + + unbind(id = 0) { + const gl = this.gl; + gl.activeTexture(gl.TEXTURE0 + id); + gl.bindTexture(this.target, null); + return true; + } + + release() { + if (this.tex) { + this.gl.deleteTexture(this.tex); + delete this.tex; + delete this.gl; + return true; + } + return false; + } +} diff --git a/packages/webgl/src/uniforms.ts b/packages/webgl/src/uniforms.ts new file mode 100644 index 0000000000..418f337dfb --- /dev/null +++ b/packages/webgl/src/uniforms.ts @@ -0,0 +1,101 @@ +import { Fn, Fn3, IObjectOf } from "@thi.ng/api"; +import { IDENT22, IDENT33, IDENT44 } from "@thi.ng/matrices"; +import { + ReadonlyVec, + ZERO2, + ZERO3, + ZERO4 +} from "@thi.ng/vectors"; +import { GLSL, UniformValue } from "./api"; + +type SetterS = "f" | "i" | "ui"; + +type SetterV = + | "1fv" + | "2fv" + | "3fv" + | "4fv" + | "1iv" + | "2iv" + | "3iv" + | "4iv" + | "1uiv" + | "2uiv" + | "3uiv" + | "4uiv"; + +type SetterM = "2fv" | "3fv" | "4fv"; + +const uniformS = (fn: SetterS) => ( + gl: WebGLRenderingContext, + loc: WebGLUniformLocation, + defaultVal = 0 +) => { + // const set = gl["uniform1" + fn]; + return (x: number) => + gl["uniform1" + fn](loc, x === undefined ? defaultVal : x); +}; + +const uniformV = (fn: SetterV, sysDefault?: ReadonlyVec) => ( + gl: WebGLRenderingContext, + loc: WebGLUniformLocation, + defaultVal = sysDefault +) => { + // const set = gl["uniform" + fn]; + return (x: any) => + gl["uniform" + fn](loc, x === undefined ? defaultVal : x.buffer || x); +}; + +const uniformM = (fn: SetterM, sysDefault?: ReadonlyVec) => ( + gl: WebGLRenderingContext, + loc: WebGLUniformLocation, + defaultVal = sysDefault +) => { + // const set = gl["uniformMatrix" + fn]; + return (x: any) => + gl["uniformMatrix" + fn]( + loc, + false, + x === undefined ? defaultVal : x.buffer || x + ); +}; + +export const UNIFORM_SETTERS: IObjectOf< + Fn3< + WebGLRenderingContext, + WebGLUniformLocation, + number | ReadonlyVec, + Fn + > +> = { + [GLSL.bool]: uniformS("i"), + [GLSL.float]: uniformS("f"), + [GLSL.int]: uniformS("i"), + [GLSL.uint]: uniformS("ui"), + [GLSL.bvec2]: uniformV("2iv", ZERO2), + [GLSL.bvec3]: uniformV("3iv", ZERO3), + [GLSL.bvec4]: uniformV("4iv", ZERO4), + [GLSL.ivec2]: uniformV("2iv", ZERO2), + [GLSL.ivec3]: uniformV("3iv", ZERO3), + [GLSL.ivec4]: uniformV("4iv", ZERO4), + [GLSL.vec2]: uniformV("2fv", ZERO2), + [GLSL.vec3]: uniformV("3fv", ZERO3), + [GLSL.vec4]: uniformV("4fv", ZERO4), + [GLSL.mat2]: uniformM("2fv", IDENT22), + [GLSL.mat3]: uniformM("3fv", IDENT33), + [GLSL.mat4]: uniformM("4fv", IDENT44), + [GLSL.sampler2D]: uniformS("i"), + [GLSL.samplerCube]: uniformS("i"), + [GLSL.bool_array]: uniformV("1iv"), + [GLSL.float_array]: uniformV("1fv"), + [GLSL.int_array]: uniformV("1iv"), + [GLSL.uint_array]: uniformV("1uiv"), + [GLSL.vec2_array]: uniformV("2fv"), + [GLSL.vec3_array]: uniformV("3fv"), + [GLSL.vec4_array]: uniformV("4fv"), + [GLSL.mat2_array]: uniformM("2fv"), + [GLSL.mat3_array]: uniformM("3fv"), + [GLSL.mat4_array]: uniformM("4fv"), + [GLSL.sampler2D_array]: uniformV("1iv"), + [GLSL.samplerCube_array]: uniformV("1iv") +}; diff --git a/packages/webgl/src/utils.ts b/packages/webgl/src/utils.ts new file mode 100644 index 0000000000..fcb304cd0d --- /dev/null +++ b/packages/webgl/src/utils.ts @@ -0,0 +1,23 @@ +import { ShaderOpts } from "./api"; + +export const isGL2Context = ( + gl: WebGLRenderingContext +): gl is WebGL2RenderingContext => + typeof WebGL2RenderingContext !== "undefined" && + gl instanceof WebGL2RenderingContext; + +export const positionAttrib = ( + opts: Partial>, + pos = "a_position" +) => `${pos}${opts.instancePos ? ` + a_${opts.instancePos}` : ""}`; + +export const colorAttrib = ( + opts: Partial>, + fallback = "u_diffuseCol", + post = ` * ${fallback}` +) => + opts.instanceColor + ? `a_${opts.instanceColor}${post}` + : opts.color + ? `a_${opts.color}${post}` + : fallback; diff --git a/packages/webgl/test/index.ts b/packages/webgl/test/index.ts new file mode 100644 index 0000000000..b4b0d407d2 --- /dev/null +++ b/packages/webgl/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import * as w from "../src/index"; + +describe("webgl", () => { + it("tests pending"); +}); diff --git a/packages/webgl/test/tsconfig.json b/packages/webgl/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/webgl/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/webgl/tsconfig.json b/packages/webgl/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/webgl/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} From dc5eb755753dc98fc2f82e14c784d9af20a48135 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 2 Apr 2019 19:44:53 +0100 Subject: [PATCH 002/404] build(webgl): update pkg deps --- packages/webgl/package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/webgl/package.json b/packages/webgl/package.json index fc5e20b4e6..0af7573c37 100644 --- a/packages/webgl/package.json +++ b/packages/webgl/package.json @@ -24,19 +24,19 @@ "pub": "yarn build && yarn publish --access public" }, "devDependencies": { - "@types/mocha": "^5.2.5", - "@types/node": "^10.12.15", - "mocha": "^5.2.0", - "nyc": "^13.1.0", - "typedoc": "^0.14.0", - "typescript": "^3.2.2" + "@types/mocha": "^5.2.6", + "@types/node": "^11.13.0", + "mocha": "^6.0.2", + "nyc": "^13.3.0", + "typedoc": "^0.14.2", + "typescript": "^3.4.1" }, "dependencies": { - "@thi.ng/api": "^6.0.0", - "@thi.ng/checks": "^2.1.4", - "@thi.ng/dgraph": "^1.0.13", - "@thi.ng/matrices": "^0.1.14", - "@thi.ng/vectors": "^2.5.0" + "@thi.ng/api": "^6.0.1", + "@thi.ng/checks": "^2.1.5", + "@thi.ng/dgraph": "^1.1.1", + "@thi.ng/matrices": "^0.2.1", + "@thi.ng/vectors": "^2.5.1" }, "keywords": [ "abstraction", From 131e5514627ece64a7193af93c5abd92d236d1df Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 5 Apr 2019 20:57:40 +0100 Subject: [PATCH 003/404] feat(webgl): add webgl resource factory fns, update buffer() arg order --- packages/webgl/src/buffer.ts | 21 ++++++++++++++------- packages/webgl/src/fbo.ts | 3 +++ packages/webgl/src/renderbuffer.ts | 5 +++++ packages/webgl/src/shader.ts | 5 ++++- packages/webgl/src/texture.ts | 5 +++++ 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/webgl/src/buffer.ts b/packages/webgl/src/buffer.ts index da751ec5ff..e8a2bf273d 100644 --- a/packages/webgl/src/buffer.ts +++ b/packages/webgl/src/buffer.ts @@ -14,9 +14,9 @@ export class WebGLArrayBuffer implements IWebGLBuffer { constructor( gl: WebGLRenderingContext, + data?: T, target = gl.ARRAY_BUFFER, - mode = gl.STATIC_DRAW, - data?: T + mode = gl.STATIC_DRAW ) { this.gl = gl; this.buffer = gl.createBuffer(); @@ -54,7 +54,14 @@ export class WebGLArrayBuffer implements IWebGLBuffer { } } -export const compileBuffers = ( +export const buffer = ( + gl: WebGLRenderingContext, + data?: TypedArray, + target = gl.ARRAY_BUFFER, + mode = gl.STATIC_DRAW +) => new WebGLArrayBuffer(gl, data, target, mode); + +export const compileModel = ( gl: WebGLRenderingContext, spec: ModelSpec, mode = gl.STATIC_DRAW @@ -79,9 +86,9 @@ const compileAttribs = ( } else { attr.buffer = new WebGLArrayBuffer( gl, + attr.data, gl.ARRAY_BUFFER, - mode, - attr.data + mode ); } } @@ -99,9 +106,9 @@ const compileIndices = ( } else { index.buffer = new WebGLArrayBuffer( gl, + index.data, gl.ELEMENT_ARRAY_BUFFER, - mode, - index.data + mode ); } }; diff --git a/packages/webgl/src/fbo.ts b/packages/webgl/src/fbo.ts index fb83716c1d..3a495fd721 100644 --- a/packages/webgl/src/fbo.ts +++ b/packages/webgl/src/fbo.ts @@ -121,3 +121,6 @@ export class FBO implements IFbo { } } } + +export const fbo = (gl: WebGLRenderingContext, opts?: Partial) => + new FBO(gl, opts); diff --git a/packages/webgl/src/renderbuffer.ts b/packages/webgl/src/renderbuffer.ts index 91d1fce8aa..cd63428791 100644 --- a/packages/webgl/src/renderbuffer.ts +++ b/packages/webgl/src/renderbuffer.ts @@ -55,3 +55,8 @@ export class RenderBuffer implements IRenderBuffer { return true; } } + +export const renderBuffer = ( + gl: WebGLRenderingContext, + opts?: Partial +) => new RenderBuffer(gl, opts); diff --git a/packages/webgl/src/shader.ts b/packages/webgl/src/shader.ts index c40a024073..7e7e2f2e0c 100644 --- a/packages/webgl/src/shader.ts +++ b/packages/webgl/src/shader.ts @@ -182,7 +182,7 @@ export class Shader implements IShader { } } - setState(id: number, val: GLenum | boolean) { + protected setState(id: number, val: GLenum | boolean) { if (val) { this.gl.enable(id); } else { @@ -191,6 +191,9 @@ export class Shader implements IShader { } } +export const shader = (gl: WebGLRenderingContext, spec: ShaderSpec) => + Shader.fromSpec(gl, spec); + const compileVars = ( attribs: any, syntax: Fn3, diff --git a/packages/webgl/src/texture.ts b/packages/webgl/src/texture.ts index da489945d4..f7dda39ea8 100644 --- a/packages/webgl/src/texture.ts +++ b/packages/webgl/src/texture.ts @@ -111,3 +111,8 @@ export class Texture implements ITexture { return false; } } + +export const texture = ( + gl: WebGLRenderingContext, + opts?: Partial +) => new Texture(gl, opts); From 1f19196c58fdd1dbce899df4c72dda499c5a9e2e Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Apr 2019 02:16:13 +0100 Subject: [PATCH 004/404] feat(webgl): update/add array type UniformDecl's --- packages/webgl/src/api.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index be5fd793f5..4e64c32f1f 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -119,13 +119,15 @@ export type UniformDecl = | [GLSL.mat2, UniformDefault] | [GLSL.mat3, UniformDefault] | [GLSL.mat4, UniformDefault] - | [GLSL.bool_array, number, UniformDefault] - | [GLSL.int_array, number, UniformDefault] - | [GLSL.uint_array, number, UniformDefault] - | [GLSL.float_array, number, UniformDefault] - | [GLSL.vec2_array, number, UniformDefault] - | [GLSL.vec3_array, number, UniformDefault] - | [GLSL.vec4_array, number, UniformDefault]; + | [GLSL.bool_array, number, UniformDefault?] + | [GLSL.int_array, number, UniformDefault?] + | [GLSL.uint_array, number, UniformDefault?] + | [GLSL.float_array, number, UniformDefault?] + | [GLSL.vec2_array, number, UniformDefault?] + | [GLSL.vec3_array, number, UniformDefault?] + | [GLSL.vec4_array, number, UniformDefault?] + | [GLSL.sampler2D_array, number, UniformDefault?] + | [GLSL.samplerCube_array, number, UniformDefault?]; /** * Object of attribute types w/ optional locations. From a60eb2e7e7463b0bfe29195045bcc0d0ac093e0c Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Apr 2019 02:16:43 +0100 Subject: [PATCH 005/404] feat(webgl): add glCanvas() factory fn & WebGLCanvasOpts --- packages/webgl/src/api.ts | 11 +++++++ packages/webgl/src/canvas.ts | 64 ++++++++++++++++++++++++++++++++++++ packages/webgl/src/index.ts | 2 ++ 3 files changed, 77 insertions(+) create mode 100644 packages/webgl/src/canvas.ts diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index 4e64c32f1f..cb13591bf6 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -494,3 +494,14 @@ export interface Material { diffuseCol: GLVec3; specularCol: GLVec3; } + +export interface WeblGLCanvasOpts { + canvas: string | HTMLCanvasElement; + parent: HTMLElement; + opts: Partial; + version: 1 | 2; + width: number; + height: number; + autoScale: boolean; + onContextLost: EventListener; +} diff --git a/packages/webgl/src/canvas.ts b/packages/webgl/src/canvas.ts new file mode 100644 index 0000000000..b49f7bcb72 --- /dev/null +++ b/packages/webgl/src/canvas.ts @@ -0,0 +1,64 @@ +import { isString } from "@thi.ng/checks"; +import { WeblGLCanvasOpts } from "./api"; +import { error } from "./error"; + +const defaultOpts: WebGLContextAttributes = { + alpha: true, + antialias: true, + depth: true, + premultipliedAlpha: true, + preserveDrawingBuffer: false, + stencil: false +}; + +export const glCanvas = (opts: Partial = {}) => { + const canvas = opts.canvas + ? isString(opts.canvas) + ? document.getElementById(opts.canvas) + : opts.canvas + : document.createElement("canvas"); + opts.width && (canvas.width = opts.width); + opts.height && (canvas.height = opts.height); + opts.autoScale !== false && adaptDPI(canvas, canvas.width, canvas.height); + opts.parent && opts.parent.appendChild(canvas); + const gl = canvas.getContext( + opts.version === 2 ? "webgl2" : "webgl", + { + ...defaultOpts, + ...opts.opts + } + ); + if (!gl) { + error("WebGL unavailable"); + } + opts.onContextLost && + canvas.addEventListener("webglcontextlost", opts.onContextLost); + return { + canvas, + gl + }; +}; + +/** + * Sets the canvas size to given `width` & `height` and adjusts style to + * compensate for HDPI devices. Note: For 2D canvases, this will + * automatically clear any prior canvas content. + * + * @param canvas + * @param width uncompensated pixel width + * @param height uncompensated pixel height + */ +export const adaptDPI = ( + canvas: HTMLCanvasElement, + width: number, + height: number +) => { + const dpr = window.devicePixelRatio || 1; + if (dpr != 1) { + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + } + canvas.width = width * dpr; + canvas.height = height * dpr; + return dpr; +}; diff --git a/packages/webgl/src/index.ts b/packages/webgl/src/index.ts index 73fcdeaade..a7e9e310d3 100644 --- a/packages/webgl/src/index.ts +++ b/packages/webgl/src/index.ts @@ -1,5 +1,6 @@ export * from "./api"; export * from "./buffer"; +export * from "./canvas"; export * from "./draw"; export * from "./error"; export * from "./fbo"; @@ -7,6 +8,7 @@ export * from "./material"; export * from "./normal-mat"; export * from "./shader"; export * from "./texture"; +export * from "./utils"; export * from "./shaders/lambert"; export * from "./shaders/phong"; From 648ed527d4aaa9dd0ed318665aec042997a2ab9d Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Apr 2019 23:51:33 +0100 Subject: [PATCH 006/404] feat(webgl): add more GLSL/WebGL2 types --- packages/webgl/src/api.ts | 100 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index cb13591bf6..9b1b6a732c 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -24,10 +24,20 @@ export enum GLSL { ivec2, ivec3, ivec4, + uvec2, + uvec3, + uvec4, mat2, mat3, mat4, + mat2x3, + mat2x4, + mat3x2, + mat3x4, + mat4x2, + mat4x3, sampler2D, + sampler3D, samplerCube, bool_array, float_array, @@ -42,10 +52,20 @@ export enum GLSL { ivec2_array, ivec3_array, ivec4_array, + uvec2_array, + uvec3_array, + uvec4_array, mat2_array, mat3_array, mat4_array, + mat2x3_array, + mat2x4_array, + mat3x2_array, + mat3x4_array, + mat4x2_array, + mat4x3_array, sampler2D_array, + sampler3D_array, samplerCube_array } @@ -63,6 +83,9 @@ export type GLIntVec4 = Tuple | Int32Array; export type GLMat2 = Tuple | Float32Array; export type GLMat3 = Tuple | Float32Array; export type GLMat4 = Tuple | Float32Array; +export type GLMat23 = Tuple | Float32Array; +export type GLMat24 = Tuple | Float32Array; +export type GLMat34 = Tuple | Float32Array; export type AttribType = | GLSL.bool @@ -92,7 +115,7 @@ export type UniformValues = IObjectOf< export type ShaderType = "vs" | "fs"; -export type ScalarType = +export type GLSLScalarType = | GLSL.bool | GLSL.float | GLSL.int @@ -100,13 +123,43 @@ export type ScalarType = | GLSL.sampler2D | GLSL.samplerCube; +export type GLSLArrayType = + | GLSL.bool_array + | GLSL.int_array + | GLSL.uint_array + | GLSL.float_array + | GLSL.bvec2_array + | GLSL.bvec3_array + | GLSL.bvec4_array + | GLSL.ivec2_array + | GLSL.ivec3_array + | GLSL.ivec4_array + | GLSL.uvec2_array + | GLSL.uvec3_array + | GLSL.uvec4_array + | GLSL.vec2_array + | GLSL.vec3_array + | GLSL.vec4_array + | GLSL.mat2_array + | GLSL.mat3_array + | GLSL.mat4_array + | GLSL.mat2x3_array + | GLSL.mat2x4_array + | GLSL.mat3x2_array + | GLSL.mat3x4_array + | GLSL.mat4x2_array + | GLSL.mat4x3_array + | GLSL.sampler2D_array + | GLSL.sampler3D_array + | GLSL.samplerCube_array; + export type UniformDefault = | T | Fn2, T>; export type UniformDecl = | GLSL - | [ScalarType, UniformDefault] + | [GLSLScalarType, UniformDefault] | [GLSL.bvec2, UniformDefault] | [GLSL.bvec3, UniformDefault] | [GLSL.bvec4, UniformDefault] @@ -119,14 +172,39 @@ export type UniformDecl = | [GLSL.mat2, UniformDefault] | [GLSL.mat3, UniformDefault] | [GLSL.mat4, UniformDefault] + | [GLSL.mat2x3, UniformDefault] + | [GLSL.mat2x4, UniformDefault] + | [GLSL.mat3x2, UniformDefault] + | [GLSL.mat3x4, UniformDefault] + | [GLSL.mat4x2, UniformDefault] + | [GLSL.mat4x3, UniformDefault] | [GLSL.bool_array, number, UniformDefault?] | [GLSL.int_array, number, UniformDefault?] | [GLSL.uint_array, number, UniformDefault?] | [GLSL.float_array, number, UniformDefault?] + | [GLSL.bvec2_array, number, UniformDefault?] + | [GLSL.bvec3_array, number, UniformDefault?] + | [GLSL.bvec4_array, number, UniformDefault?] + | [GLSL.ivec2_array, number, UniformDefault?] + | [GLSL.ivec3_array, number, UniformDefault?] + | [GLSL.ivec4_array, number, UniformDefault?] + | [GLSL.uvec2_array, number, UniformDefault?] + | [GLSL.uvec3_array, number, UniformDefault?] + | [GLSL.uvec4_array, number, UniformDefault?] | [GLSL.vec2_array, number, UniformDefault?] | [GLSL.vec3_array, number, UniformDefault?] | [GLSL.vec4_array, number, UniformDefault?] + | [GLSL.mat2_array, number, UniformDefault?] + | [GLSL.mat3_array, number, UniformDefault?] + | [GLSL.mat4_array, number, UniformDefault?] + | [GLSL.mat2x3_array, number, UniformDefault?] + | [GLSL.mat2x4_array, number, UniformDefault?] + | [GLSL.mat3x2_array, number, UniformDefault?] + | [GLSL.mat3x4_array, number, UniformDefault?] + | [GLSL.mat4x2_array, number, UniformDefault?] + | [GLSL.mat4x3_array, number, UniformDefault?] | [GLSL.sampler2D_array, number, UniformDefault?] + | [GLSL.sampler3D_array, number, UniformDefault?] | [GLSL.samplerCube_array, number, UniformDefault?]; /** @@ -148,7 +226,7 @@ export interface ShaderAttrib { export type ShaderVaryingSpecs = IObjectOf; -export type ShaderVaryingSpec = GLSL; +export type ShaderVaryingSpec = GLSL | [GLSLArrayType, number]; export type ShaderUniformSpecs = IObjectOf; @@ -189,6 +267,8 @@ export interface GLSLDeclPrefixes { o: string; } +export type GLSLExtensionBehavior = "require" | "warn" | boolean; + export interface ShaderSnippet { /** * Array of dependent snippets. @@ -263,6 +343,13 @@ export interface ShaderSpec { * Optional shader drawing state flags. Default: none. */ state?: Partial; + /** + * WebGL extension config for code generation. Keys in this object + * are extension names and their values specify the desired + * behavior. Boolean values will be translated in "enable" / + * "disable". + */ + ext?: IObjectOf; } export interface ShaderState { @@ -460,6 +547,7 @@ export interface TextureOpts { filter: GLenum | GLenum[]; wrap: GLenum | GLenum[]; format: GLenum; + internalFormat: GLenum; mipmap: GLenum; width: number; height: number; @@ -504,4 +592,10 @@ export interface WeblGLCanvasOpts { height: number; autoScale: boolean; onContextLost: EventListener; + ext: string[]; } + +export const GL_COLOR_ATTACHMENT0_WEBGL = 0x8ce0; +export const GL_MAX_COLOR_ATTACHMENTS_WEBGL = 0x8cdf; +export const GL_RGBA = 0x1908; +export const GL_RGBA32F = 0x8814; From 02f94d799dfee030ac1c7c15a7dee9b9a50d41e4 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Apr 2019 23:54:14 +0100 Subject: [PATCH 007/404] feat(webgl): update GLSL syntax impls, migrate Shader.fromSpec() - add support for WebGL extensions in shader spec - support array types for varying vars - move Shader.fromSpec() to shader() --- packages/webgl/src/glsl/syntax.ts | 37 ++++++++-------- packages/webgl/src/shader.ts | 74 ++++++++++++++++++------------- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/packages/webgl/src/glsl/syntax.ts b/packages/webgl/src/glsl/syntax.ts index fd4bc049d9..ecc7411a73 100644 --- a/packages/webgl/src/glsl/syntax.ts +++ b/packages/webgl/src/glsl/syntax.ts @@ -32,15 +32,10 @@ export const SYNTAX: Record = { attrib: (id, type, pre) => `attribute ${GLSL[isArray(type) ? type[0] : type]} ${pre.a}${id};`, varying: { - vs: (id, type, pre) => `varying ${GLSL[type]} ${pre.v}${id};`, - fs: (id, type, pre) => `varying ${GLSL[type]} ${pre.v}${id};` - }, - uniform: (id, u, pre) => { - const type = isArray(u) ? u[0] : u; - return `uniform ${GLSL[type]}${ - type >= GLSL.bool_array ? `[${u[1]}]` : "" - } ${pre.u}${id};`; + vs: (id, type, pre) => arrayDecl("varying", type, pre.v + id), + fs: (id, type, pre) => arrayDecl("varying", type, pre.v + id) }, + uniform: (id, u, pre) => arrayDecl("uniform", u, pre.u + id), output: () => "" }, /** @@ -50,20 +45,15 @@ export const SYNTAX: Record = { number: 300, attrib: (id, type, pre) => isArray(type) - ? `layout(location=${type[1]}) in ${GLSL[type[0]]} a_${ + ? `layout(location=${type[1]}) in ${GLSL[type[0]]} ${ pre.a }${id};` - : `in ${GLSL[type]} ${id};`, + : `in ${GLSL[type]} ${pre.a}${id};`, varying: { - vs: (id, type, pre) => `out ${GLSL[type]} v_${pre.v}${id};`, - fs: (id, type, pre) => `in ${GLSL[type]} v_${pre.v}${id};` - }, - uniform: (id, u, pre) => { - const type = isArray(u) ? u[0] : u; - return `uniform ${GLSL[type]}${ - type >= GLSL.bool_array ? `[${u[1]}]` : "" - } u_${pre.u}${id};`; + vs: (id, type, pre) => arrayDecl("out", type, pre.v + id), + fs: (id, type, pre) => arrayDecl("in", type, pre.v + id) }, + uniform: (id, u, pre) => arrayDecl("uniform", u, pre.u + id), output: (id, type, pre) => isArray(type) ? `layout(location=${type[1]}) out ${GLSL[type[0]]} ${ @@ -73,6 +63,17 @@ export const SYNTAX: Record = { } }; +const arrayDecl = ( + qualifier: string, + decl: GLSL | [GLSL, number], + id: string +) => { + const type = isArray(decl) ? decl[0] : decl; + return type >= GLSL.bool_array + ? `${qualifier} ${GLSL[type].replace("_array", "")} ${id}[${decl[1]}];` + : `${qualifier} ${GLSL[type]} ${id};`; +}; + /** * GLSL preprocessor macro for conditional execution based on `__VERSION__`. * diff --git a/packages/webgl/src/shader.ts b/packages/webgl/src/shader.ts index 7e7e2f2e0c..8a954dc8fe 100644 --- a/packages/webgl/src/shader.ts +++ b/packages/webgl/src/shader.ts @@ -1,8 +1,14 @@ import { Fn3, IObjectOf, Tuple } from "@thi.ng/api"; -import { existsAndNotNull, isArray, isFunction } from "@thi.ng/checks"; +import { + existsAndNotNull, + isArray, + isBoolean, + isFunction +} from "@thi.ng/checks"; import { GLSL, GLSLDeclPrefixes, + GLSLExtensionBehavior, GLSLVersion, IShader, ModelAttributeSpecs, @@ -20,6 +26,7 @@ import { import { error } from "./error"; import { GLSL_HEADER, PREFIXES, SYNTAX } from "./glsl/syntax"; import { UNIFORM_SETTERS } from "./uniforms"; +import { isGL2Context } from "./utils"; // [SRC_ALPHA, ONE_MINUS_SRC_ALPHA]; export const DEFAULT_BLEND: Tuple = [0x302, 0x303]; @@ -27,34 +34,6 @@ export const DEFAULT_BLEND: Tuple = [0x302, 0x303]; const ERROR_REGEXP = /ERROR: \d+:(\d+): (.*)/; export class Shader implements IShader { - static fromSpec(gl: WebGLRenderingContext, spec: ShaderSpec) { - const srcVS = prepareShaderSource(spec, "vs"); - const srcFS = prepareShaderSource(spec, "fs"); - console.log(srcVS); - console.log(srcFS); - const vs = compileShader(gl, gl.VERTEX_SHADER, srcVS); - const fs = compileShader(gl, gl.FRAGMENT_SHADER, srcFS); - const program = gl.createProgram(); - gl.attachShader(program, vs); - gl.attachShader(program, fs); - gl.linkProgram(program); - if (gl.getProgramParameter(program, gl.LINK_STATUS)) { - const attribs = initAttributes( - gl, - program, - spec.attribs, - spec.version - ); - const uniforms = initUniforms(gl, program, spec.uniforms); - gl.deleteShader(vs); - gl.deleteShader(fs); - return new Shader(gl, program, attribs, uniforms, spec.state); - } - throw new Error( - `Error linking shader: ${gl.getProgramInfoLog(program)}` - ); - } - gl: WebGLRenderingContext; program: WebGLProgram; attribs: IObjectOf; @@ -191,8 +170,31 @@ export class Shader implements IShader { } } -export const shader = (gl: WebGLRenderingContext, spec: ShaderSpec) => - Shader.fromSpec(gl, spec); +export const shader = (gl: WebGLRenderingContext, spec: ShaderSpec) => { + if (!spec.version) { + spec.version = isGL2Context(gl) + ? GLSLVersion.GLES_300 + : GLSLVersion.GLES_100; + } + const srcVS = prepareShaderSource(spec, "vs"); + const srcFS = prepareShaderSource(spec, "fs"); + console.log(srcVS); + console.log(srcFS); + const vs = compileShader(gl, gl.VERTEX_SHADER, srcVS); + const fs = compileShader(gl, gl.FRAGMENT_SHADER, srcFS); + const program = gl.createProgram(); + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + if (gl.getProgramParameter(program, gl.LINK_STATUS)) { + const attribs = initAttributes(gl, program, spec.attribs, spec.version); + const uniforms = initUniforms(gl, program, spec.uniforms); + gl.deleteShader(vs); + gl.deleteShader(fs); + return new Shader(gl, program, attribs, uniforms, spec.state); + } + throw new Error(`Error linking shader: ${gl.getProgramInfoLog(program)}`); +}; const compileVars = ( attribs: any, @@ -209,6 +211,11 @@ const compileVars = ( return decls.join("\n"); }; +const compileExtensionPragma = (id: string, behavior: GLSLExtensionBehavior) => + `#extension ${id} : ${ + isBoolean(behavior) ? (behavior ? "enable" : "disable") : behavior + }\n`; + export const prepareShaderSource = (spec: ShaderSpec, type: ShaderType) => { const syntax = SYNTAX[spec.version || GLSLVersion.GLES_100]; const prefixes = { ...PREFIXES, ...spec.declPrefixes }; @@ -220,6 +227,11 @@ export const prepareShaderSource = (spec: ShaderSpec, type: ShaderType) => { ? spec.pre : spec.pre + "\n" + GLSL_HEADER : GLSL_HEADER; + if (spec.ext) { + for (let id in spec.ext) { + src += compileExtensionPragma(id, spec.ext[id]); + } + } if (spec.generateDecls !== false) { src += isVS ? compileVars(spec.attribs, syntax.attrib, prefixes) From 07edcd0a71e5a49801c012374c61372248ed719d Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Apr 2019 23:54:39 +0100 Subject: [PATCH 008/404] feat(webgl): add glCanvas() extension support --- packages/webgl/src/canvas.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/webgl/src/canvas.ts b/packages/webgl/src/canvas.ts index b49f7bcb72..6ec6c32de5 100644 --- a/packages/webgl/src/canvas.ts +++ b/packages/webgl/src/canvas.ts @@ -35,10 +35,21 @@ export const glCanvas = (opts: Partial = {}) => { canvas.addEventListener("webglcontextlost", opts.onContextLost); return { canvas, - gl + gl, + ext: getExtensions(gl, opts.ext) }; }; +export const getExtensions = (gl: WebGLRenderingContext, ids: string[]) => { + const ext = {}; + if (ids) { + for (let id of ids) { + ext[id] = gl.getExtension(id); + } + } + return ext; +}; + /** * Sets the canvas size to given `width` & `height` and adjusts style to * compensate for HDPI devices. Note: For 2D canvases, this will From 8941d8263f8f24f5f09de21d70434e8fa2782d6a Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 6 Apr 2019 23:55:35 +0100 Subject: [PATCH 009/404] feat(webgl): add WebGL2 support for FBO/Texture, add floatTexture() ctor fn --- packages/webgl/src/fbo.ts | 58 +++++++++++--------------- packages/webgl/src/texture.ts | 78 +++++++++++++++++++++++++++++++---- 2 files changed, 93 insertions(+), 43 deletions(-) diff --git a/packages/webgl/src/fbo.ts b/packages/webgl/src/fbo.ts index 3a495fd721..0f634b99e5 100644 --- a/packages/webgl/src/fbo.ts +++ b/packages/webgl/src/fbo.ts @@ -1,9 +1,12 @@ import { assert } from "@thi.ng/api"; -import { FboOpts, IFbo } from "./api"; +import { + FboOpts, + GL_COLOR_ATTACHMENT0_WEBGL, + GL_MAX_COLOR_ATTACHMENTS_WEBGL, + IFbo +} from "./api"; import { error } from "./error"; - -const GL_MAX_COLOR_ATTACHMENTS_WEBGL = 0x8cdf; -const COLOR_ATTACHMENT0_WEBGL = 0x8ce0; +import { isGL2Context } from "./utils"; /** * WebGL framebuffer wrapper w/ automatic detection & support for @@ -29,11 +32,10 @@ export class FBO implements IFbo { constructor(gl: WebGLRenderingContext, opts?: Partial) { this.gl = gl; this.fbo = gl.createFramebuffer(); - this.ext = gl.getExtension("WEBGL_draw_buffers"); - // TODO WebGL2 support - this.maxAttachments = this.ext - ? gl.getParameter(GL_MAX_COLOR_ATTACHMENTS_WEBGL) - : 1; + this.ext = !isGL2Context(gl) + ? gl.getExtension("WEBGL_draw_buffers") + : undefined; + this.maxAttachments = gl.getParameter(GL_MAX_COLOR_ATTACHMENTS_WEBGL); opts && this.configure(opts); } @@ -58,38 +60,24 @@ export class FBO implements IFbo { const gl = this.gl; this.bind(); if (opts.tex) { - if (this.ext) { - assert( - opts.tex.length < this.maxAttachments, - `too many attachments (max. ${this.maxAttachments})` - ); - const attachments: number[] = []; - for (let i = 0; i < opts.tex.length; i++) { - const attach = COLOR_ATTACHMENT0_WEBGL + i; - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - attach, - gl.TEXTURE_2D, - opts.tex[i].tex, - 0 - ); - attachments[i] = attach; - } - // TODO WebGL2 support - this.ext.drawBuffersWEBGL(attachments); - } else { - assert( - opts.tex.length === 1, - "only single color attachment allowed (webgl_draw_buffers ext unavailable)" - ); + assert( + opts.tex.length < this.maxAttachments, + `too many attachments (max. ${this.maxAttachments})` + ); + const attachments: number[] = []; + for (let i = 0; i < opts.tex.length; i++) { + const attach = GL_COLOR_ATTACHMENT0_WEBGL + i; gl.framebufferTexture2D( gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, + attach, gl.TEXTURE_2D, - opts.tex[0].tex, + opts.tex[i].tex, 0 ); + attachments[i] = attach; } + this.ext && this.ext.drawBuffersWEBGL(attachments); + isGL2Context(gl) && gl.drawBuffers(attachments); } if (opts.depth) { gl.framebufferRenderbuffer( diff --git a/packages/webgl/src/texture.ts b/packages/webgl/src/texture.ts index f7dda39ea8..e4ca50f2e8 100644 --- a/packages/webgl/src/texture.ts +++ b/packages/webgl/src/texture.ts @@ -1,5 +1,6 @@ import { isArray } from "@thi.ng/checks"; import { ITexture, TextureOpts } from "./api"; +import { isGL2Context } from "./utils"; export const bindTextures = (textures: ITexture[]) => { if (!textures) return; @@ -24,6 +25,7 @@ export class Texture implements ITexture { const gl = this.gl; const target = (this.target = opts.target || gl.TEXTURE_2D); const format = opts.format || gl.RGBA; + const internalFormat = opts.internalFormat || format; const type = opts.type || gl.UNSIGNED_BYTE; let t1: GLenum, t2: GLenum; @@ -62,23 +64,48 @@ export class Texture implements ITexture { opts.premultiply ? 1 : 0 ); - if (opts.image) { - if (opts.width && opts.height) { + if (isGL2Context(gl)) { + if (opts.image && opts.width && opts.height) { gl.texImage2D( target, 0, - format, + internalFormat, opts.width, opts.height, 0, format, type, - opts.image + opts.image, + 0 ); - } else { - gl.texImage2D(target, 0, format, format, type, ( - opts.image - )); + } else if (opts.width && opts.height) { + gl.texStorage2D( + target, + 0, + internalFormat, + opts.width, + opts.height + ); + } + } else { + if (opts.image) { + if (opts.width && opts.height) { + gl.texImage2D( + target, + 0, + internalFormat, + opts.width, + opts.height, + 0, + format, + type, + opts.image + ); + } else { + gl.texImage2D(target, 0, internalFormat, format, type, < + TexImageSource + >opts.image); + } } } @@ -116,3 +143,38 @@ export const texture = ( gl: WebGLRenderingContext, opts?: Partial ) => new Texture(gl, opts); + +/** + * Creates & configure a new FLOAT texture. + * + * **Important:** Since each texel will hold 4x 32-bit float values, the + * `data` buffer needs to have a length of at least `4 * width * + * height`. + * + * Under WebGL 1.0, we assume the caller has previously enabled the + * `OES_texture_float` extension. + * + * @param gl GL context + * @param data texture data + * @param width width + * @param height height + */ +export const floatTexture = ( + gl: WebGLRenderingContext, + data: Float32Array, + width: number, + height: number, + internalFormat?: GLenum, + format?: GLenum +) => + new Texture(gl, { + filter: gl.NEAREST, + wrap: gl.CLAMP_TO_EDGE, + internalFormat: + internalFormat || (isGL2Context(gl) ? gl.RGBA32F : gl.RGBA), + format: format || gl.RGBA, + type: gl.FLOAT, + image: data, + width, + height + }); From 62dce7d751792971ca73386e19999f0c8c23d16b Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 00:49:44 +0100 Subject: [PATCH 010/404] refactor(webgl): replace RenderBuffer.newDepthBuffer() - extract as own fn: `depthBuffer()` - minor update FBO.configure() --- packages/webgl/src/fbo.ts | 9 ++++++--- packages/webgl/src/renderbuffer.ts | 23 +++++++++++------------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/webgl/src/fbo.ts b/packages/webgl/src/fbo.ts index 0f634b99e5..9caa913114 100644 --- a/packages/webgl/src/fbo.ts +++ b/packages/webgl/src/fbo.ts @@ -27,7 +27,7 @@ export class FBO implements IFbo { gl: WebGLRenderingContext; fbo: WebGLFramebuffer; ext: WEBGL_draw_buffers; - readonly maxAttachments: number; + maxAttachments: number; constructor(gl: WebGLRenderingContext, opts?: Partial) { this.gl = gl; @@ -76,8 +76,11 @@ export class FBO implements IFbo { ); attachments[i] = attach; } - this.ext && this.ext.drawBuffersWEBGL(attachments); - isGL2Context(gl) && gl.drawBuffers(attachments); + if (this.ext) { + this.ext.drawBuffersWEBGL(attachments); + } else if (isGL2Context(gl)) { + gl.drawBuffers(attachments); + } } if (opts.depth) { gl.framebufferRenderbuffer( diff --git a/packages/webgl/src/renderbuffer.ts b/packages/webgl/src/renderbuffer.ts index cd63428791..a10cc8fd55 100644 --- a/packages/webgl/src/renderbuffer.ts +++ b/packages/webgl/src/renderbuffer.ts @@ -1,18 +1,6 @@ import { IRenderBuffer, RenderBufferOpts } from "./api"; export class RenderBuffer implements IRenderBuffer { - static newDepthBuffer( - gl: WebGLRenderingContext, - width: number, - height: number - ) { - return new RenderBuffer(gl, { - format: gl.DEPTH_COMPONENT16, - width, - height - }); - } - gl: WebGLRenderingContext; buffer: WebGLRenderbuffer; format: number; @@ -60,3 +48,14 @@ export const renderBuffer = ( gl: WebGLRenderingContext, opts?: Partial ) => new RenderBuffer(gl, opts); + +export const depthBuffer = ( + gl: WebGLRenderingContext, + width: number, + height: number +) => + new RenderBuffer(gl, { + format: gl.DEPTH_COMPONENT16, + width, + height + }); From 2a5b87fdf3235388de651dcdb6a724282002d872 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 01:49:07 +0100 Subject: [PATCH 011/404] fix(webgl): update texture mipmap config & handling --- packages/webgl/src/api.ts | 2 +- packages/webgl/src/texture.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index 9b1b6a732c..abef9549bc 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -548,9 +548,9 @@ export interface TextureOpts { wrap: GLenum | GLenum[]; format: GLenum; internalFormat: GLenum; - mipmap: GLenum; width: number; height: number; + mipmap: boolean; flip: boolean; premultiply: boolean; } diff --git a/packages/webgl/src/texture.ts b/packages/webgl/src/texture.ts index e4ca50f2e8..9223f79deb 100644 --- a/packages/webgl/src/texture.ts +++ b/packages/webgl/src/texture.ts @@ -109,7 +109,7 @@ export class Texture implements ITexture { } } - opts.mipmap !== undefined && gl.generateMipmap(opts.mipmap); + opts.mipmap && gl.generateMipmap(target); return true; } From e84882d0a74b0b1d9b652f71a582ef5d051a1212 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 02:12:28 +0100 Subject: [PATCH 012/404] build(webgl): update deps --- packages/webgl/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/webgl/package.json b/packages/webgl/package.json index 0af7573c37..f868c4caff 100644 --- a/packages/webgl/package.json +++ b/packages/webgl/package.json @@ -34,9 +34,9 @@ "dependencies": { "@thi.ng/api": "^6.0.1", "@thi.ng/checks": "^2.1.5", - "@thi.ng/dgraph": "^1.1.1", - "@thi.ng/matrices": "^0.2.1", - "@thi.ng/vectors": "^2.5.1" + "@thi.ng/dgraph": "^1.1.4", + "@thi.ng/matrices": "^0.3.0", + "@thi.ng/vectors": "^2.5.2" }, "keywords": [ "abstraction", From d54f74629ff33a41721360eda925f9fdfcc08df0 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 02:29:45 +0100 Subject: [PATCH 013/404] feat(matrices): rename normal44 => normal33, add new normal44 (w/ M44 result) --- packages/matrices/src/normal-mat.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/matrices/src/normal-mat.ts b/packages/matrices/src/normal-mat.ts index c3570a5e85..f6dc66d126 100644 --- a/packages/matrices/src/normal-mat.ts +++ b/packages/matrices/src/normal-mat.ts @@ -1,14 +1,23 @@ import { MatOpM } from "./api"; +import { invert33, invert44 } from "./invert"; import { mat44to33 } from "./m44-m33"; -import { invert33 } from "./invert"; -import { transpose33 } from "./transpose"; +import { transpose33, transpose44 } from "./transpose"; /** * Converts given M44 to a M33 normal matrix, i.e. the transposed - * inverted version of its upper-left 3x3 region. + * inverse of its upper-left 3x3 region. * * @param out * @param m */ -export const normal44: MatOpM = (out, m) => +export const normal33: MatOpM = (out, m) => transpose33(null, invert33(null, mat44to33(out, m))); + +/** + * Converts given M44 to a M44 normal matrix, i.e. the transposed + * inverse. + * + * @param out + * @param m + */ +export const normal44: MatOpM = (out, m) => transpose44(null, invert44(out, m)); From 684e7fccec12bb11d2d37396fa585511b7edca1b Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 02:30:25 +0100 Subject: [PATCH 014/404] fix(webgl): EXPORT_FRAGCOL webgl2 default out --- packages/webgl/src/glsl/syntax.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webgl/src/glsl/syntax.ts b/packages/webgl/src/glsl/syntax.ts index ecc7411a73..a0136d39d2 100644 --- a/packages/webgl/src/glsl/syntax.ts +++ b/packages/webgl/src/glsl/syntax.ts @@ -93,7 +93,7 @@ ${ok}${fail ? `\n#else\n${fail}` : ""} * @param body * @param out */ -export const EXPORT_FRAGCOL = (body = "col", out = "fragColor") => +export const EXPORT_FRAGCOL = (body = "col", out = "o_fragColor") => VERSION_CHECK(300, `${out}=${body};`, `gl_FragColor=${body};`); /** From 6d8aad90e0f16e98c847f5e587c1256673785821 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 02:36:12 +0100 Subject: [PATCH 015/404] refactor(webgl): update autoNormalMatrix* fns to use normal44() --- packages/webgl/src/normal-mat.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/webgl/src/normal-mat.ts b/packages/webgl/src/normal-mat.ts index 05b11eb0a0..b92ecac02d 100644 --- a/packages/webgl/src/normal-mat.ts +++ b/packages/webgl/src/normal-mat.ts @@ -1,10 +1,5 @@ import { IObjectOf } from "@thi.ng/api"; -import { - IDENT44, - invert44, - mulM44, - transpose44 -} from "@thi.ng/matrices"; +import { IDENT44, mulM44, normal44 } from "@thi.ng/matrices"; import { ReadonlyVec } from "@thi.ng/vectors"; import { GLMat4, ShaderUniforms } from "./api"; @@ -19,7 +14,7 @@ const $ = (a: any, b: any, id: string) => a[id] || b[id].defaultVal || IDENT44; export const autoNormalMatrix1 = (model = "model") => ( shaderU: ShaderUniforms, specU: IObjectOf -) => transpose44(null, invert44([], $(specU, shaderU, model))); +) => normal44([], $(specU, shaderU, model)); /** * Computes the inverse transpose of the matrix product of given 4x4 @@ -33,11 +28,8 @@ export const autoNormalMatrix2 = (model = "model", view = "view") => ( specU: IObjectOf ) => ( - transpose44( + normal44( null, - invert44( - null, - mulM44([], $(specU, shaderU, view), $(specU, shaderU, model)) - ) + mulM44([], $(specU, shaderU, view), $(specU, shaderU, model)) ) ); From 33109d0ee1057b65f2e81be8b67e26ec94ee989d Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 17:34:04 +0100 Subject: [PATCH 016/404] feat(vector-pools): update AttribPool, add tests, update readme - add AttribPool.setAttribs() - add opt start index arg for setAttribValues() - update AttribSpec to include opt. initial values - fix readme example - add missing deps --- packages/vector-pools/README.md | 28 +++++----- packages/vector-pools/package.json | 3 ++ packages/vector-pools/src/api.ts | 4 +- packages/vector-pools/src/attrib-pool.ts | 41 +++++++++++---- packages/vector-pools/test/attribs.ts | 67 +++++++++++++++++++----- packages/vector-pools/test/index.ts | 6 --- 6 files changed, 104 insertions(+), 45 deletions(-) delete mode 100644 packages/vector-pools/test/index.ts diff --git a/packages/vector-pools/README.md b/packages/vector-pools/README.md index 5fd76ddb71..1c9b71c840 100644 --- a/packages/vector-pools/README.md +++ b/packages/vector-pools/README.md @@ -54,34 +54,37 @@ import * as v from "@thi.ng/vectors"; import * as tx from "@thi.ng/transducers"; // create an interleaved (AOS layout) attribute buffer w/ default values -const geo = new AttribPool( +const geo = new AttribPool({ // initial size in bytes (or provide ArrayBuffer or @thi.ng/malloc/MemPool) - 0x200, + mem: { size: 0x200 }, // num elements - 4, + num: 4, // attrib specs (data mapping layout) - { - pos: { type: GLType.F32, size: 3, default: [0, 0, 0], byteOffset: 0 }, - uv: { type: GLType.F32, size: 2, default: [0, 0], byteOffset: 12 }, + attribs: { + pos: { type: GLType.F32, size: 3, byteOffset: 0 }, + uv: { type: GLType.F32, size: 2, byteOffset: 12 }, col: { type: GLType.F32, size: 3, default: [1, 1, 1], byteOffset: 20 }, - id: { type: GLType.U16, size: 1, default: 0, byteOffset: 32 } + id: { type: GLType.U16, size: 1, byteOffset: 32 } } -); +}); // computed overall stride length geo.byteStride // 36 // set attrib values -geo.setAttribValues("pos", [[-5, 0, 0], [5, 0, 0], [5, 5, 0], [-5, 5, 0]]); -geo.setAttribValues("uv", [[0, 0], [1, 0], [1, 1], [0, 1]]); +geo.setAttribs({ + pos: { data: [[-5, 0, 0], [5, 0, 0], [5, 5, 0], [-5, 5, 0]]}, + uv: { data: [[0, 0], [1, 0], [1, 1], [0, 1]] } +}); +// ...or individually geo.setAttribValues("id", [0, 1, 2, 3]); // get view of individual attrib val geo.attribValue("pos", 3) // Float32Array [ -5, 5, 0 ] -// zero-copy direct manipulation of attrib val +// zero-copy direct manipulation of mapped attrib val v.mulN(null, geo.attribValue("pos", 3), 2); // Float32Array [ -10, 10, 0 ] @@ -95,7 +98,7 @@ v.mulN(null, geo.attribValue("pos", 3), 2); // use with transducers, e.g. to map positions to colors tx.run( tx.map(([pos, col]) => v.maddN(col, [0.5, 0.5, 0.5], v.normalize(col, pos), 0.5)), - tx.tuples(geo.attribValues("pos"), geo.attribValues("col")) + tx.zip(geo.attribValues("pos"), geo.attribValues("col")) ); // updated colors @@ -140,7 +143,6 @@ const initAttrib = (gl, loc, attrib) => { initAttrib(gl, attribLocPosition, geo.specs.pos); initAttrib(gl, attribLocNormal, geo.specs.normal); initAttrib(gl, attribLocUV, geo.specs.uv); -initAttrib(gl, attribLocID, geo.specs.id); ``` ### WASM interop diff --git a/packages/vector-pools/package.json b/packages/vector-pools/package.json index f26386a57a..d1d3abd7e9 100644 --- a/packages/vector-pools/package.json +++ b/packages/vector-pools/package.json @@ -33,7 +33,10 @@ }, "dependencies": { "@thi.ng/api": "^6.0.1", + "@thi.ng/binary": "^1.0.5", + "@thi.ng/checks": "^2.1.5", "@thi.ng/malloc": "^2.0.8", + "@thi.ng/transducers": "^5.3.3", "@thi.ng/vectors": "^2.5.2" }, "keywords": [ diff --git a/packages/vector-pools/src/api.ts b/packages/vector-pools/src/api.ts index 5216a5dd13..6d2a9418ec 100644 --- a/packages/vector-pools/src/api.ts +++ b/packages/vector-pools/src/api.ts @@ -5,9 +5,11 @@ import { ReadonlyVec, StridedVec, Vec } from "@thi.ng/vectors"; export interface AttribSpec { type: GLType | Type; size: number; - default?: number | ReadonlyVec; byteOffset: number; stride?: number; + default?: number | ReadonlyVec; + data?: ReadonlyVec | ReadonlyVec[]; + index?: number; } export interface AttribPoolOpts { diff --git a/packages/vector-pools/src/attrib-pool.ts b/packages/vector-pools/src/attrib-pool.ts index 36c82eefeb..a2920b5eea 100644 --- a/packages/vector-pools/src/attrib-pool.ts +++ b/packages/vector-pools/src/attrib-pool.ts @@ -1,19 +1,26 @@ -import { assert, IObjectOf, IRelease, TypedArray } from "@thi.ng/api"; +import { + assert, + IObjectOf, + IRelease, + TypedArray +} from "@thi.ng/api"; import { align, Pow2 } from "@thi.ng/binary"; import { MemPool, SIZEOF, wrap } from "@thi.ng/malloc"; import { range } from "@thi.ng/transducers"; import { ReadonlyVec, Vec, zeroes } from "@thi.ng/vectors"; -import { asNativeType } from "./convert"; import { AttribPoolOpts, AttribSpec } from "./api"; +import { asNativeType } from "./convert"; /* + * 0x00 0x08 0x10 0x18 + * ^ ^ ^ ^ * WASM mem : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... * typedarr : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... global offset = 4 (bytes) * pos (f32) : X X X X Y Y Y Y X ... offset = 0 (bytes), size = 2 (f32) * uv (f32) : U U U U V V V V ... offset = 8 (bytes), size = 2 (f32) * col (u16) : R R G G B B A A ... offset = 16 (bytes), size = 4 (u16) * - * global stride: 24 + * stride : 24 */ export class AttribPool implements IRelease { attribs: IObjectOf; @@ -77,6 +84,7 @@ export class AttribPool implements IRelease { this.addr = addr; } this.initDefaults(specs); + this.setAttribs(specs); } attribValue(id: string, i: number): number | Vec { @@ -131,17 +139,17 @@ export class AttribPool implements IRelease { return this; } - setAttribValues(id: string, vals: (number | ReadonlyVec)[]) { + setAttribValues(id: string, vals: ReadonlyVec | ReadonlyVec[], index = 0) { const spec = this.specs[id]; assert(!!spec, `invalid attrib: ${id}`); const n = vals.length; - const v = vals[0]; + this.ensure(index + n); const stride = spec.stride; - this.ensure(n); const buf = this.attribs[id]; + const v = vals[0]; const isNum = typeof v === "number"; assert( - () => (!isNum && spec.size > 1) || (isNum && spec.size === 1), + (!isNum && spec.size > 1) || (isNum && spec.size === 1), `incompatible value(s) for attrib: ${id}` ); if (!isNum) { @@ -151,16 +159,27 @@ export class AttribPool implements IRelease { (v).length }` ); - for (let i = 0; i < n; i++) { - buf.set(vals[i], i * stride); + for (let i = 0, j = index * stride; i < n; i++, j += stride) { + buf.set(vals[i], j); } } else { - for (let i = 0; i < n; i++) { - buf[i * stride] = vals[i]; + for (let i = 0, j = index * stride; i < n; i++, j += stride) { + buf[j] = vals[i]; } } } + setAttribs( + specs: IObjectOf< + Partial<{ data: ReadonlyVec | ReadonlyVec[]; index: number }> + > + ) { + for (let id in specs) { + const spec = specs[id]; + spec.data && this.setAttribValues(id, spec.data, spec.index || 0); + } + } + removeAttrib(id: string) { if (!this.attribs[id]) return false; delete this.attribs[id]; diff --git a/packages/vector-pools/test/attribs.ts b/packages/vector-pools/test/attribs.ts index 8fe1a65413..323ea40ec4 100644 --- a/packages/vector-pools/test/attribs.ts +++ b/packages/vector-pools/test/attribs.ts @@ -1,6 +1,6 @@ -import { AttribPool } from "../src/attrib-pool"; import { Type } from "@thi.ng/malloc"; -// import * as assert from "assert"; +import * as assert from "assert"; +import { AttribPool } from "../src/attrib-pool"; describe("vector-pools", () => { it("attribs", () => { @@ -8,19 +8,58 @@ describe("vector-pools", () => { mem: { size: 0x100 }, num: 8, attribs: { - pos: { type: Type.F32, default: [0, 0], size: 2, byteOffset: 0 }, - id: { type: Type.U32, default: 0, size: 1, byteOffset: 8 }, - index: { type: Type.U16, default: 0, size: 1, byteOffset: 12 }, - col: { type: Type.I8, default: [0, 0, 0, 0], size: 4, byteOffset: 14 }, + pos: { + type: Type.F32, + size: 2, // 8 bytes + byteOffset: 0, + data: [[1, 2], [3, 4]] + }, + id: { + type: Type.U32, + size: 1, // 4 bytes + byteOffset: 8, + data: [1, 2], + index: 4 + }, + index: { + type: Type.U16, + size: 1, // 2 bytes + byteOffset: 12, + data: [10, 20] + }, + col: { + type: Type.U8, + size: 4, // 4 bytes + byteOffset: 14, + data: [[128, 129, 130, 131], [255, 254, 253, 252]], + index: 6 + } } }); - pool.setAttribValue("pos", 0, [1, 2]); - pool.setAttribValue("id", 0, 1); - pool.setAttribValue("index", 0, 10); - pool.setAttribValue("col", 0, [128, 129, 130, 131]); - pool.setAttribValue("pos", 1, [3, 4]); - pool.setAttribValue("id", 1, 2); - pool.setAttribValue("index", 1, 20); - pool.setAttribValue("col", 1, [255, 254, 253, 252]); + assert.deepEqual( + [...pool.attribValues("pos")], + [[1, 2], [3, 4], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]] + ); + assert.deepEqual( + [...pool.attribValues("id")], + [0, 0, 0, 0, 1, 2, 0, 0] + ); + assert.deepEqual( + [...pool.attribValues("index")], + [10, 20, 0, 0, 0, 0, 0, 0] + ); + assert.deepEqual( + [...pool.attribValues("col")], + [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [128, 129, 130, 131], + [255, 254, 253, 252] + ] + ); }); }); diff --git a/packages/vector-pools/test/index.ts b/packages/vector-pools/test/index.ts deleted file mode 100644 index b8c58a8837..0000000000 --- a/packages/vector-pools/test/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// import * as assert from "assert"; -// import * as vp from "../src/index"; - -describe("vector-pools", () => { - it("tests pending"); -}); From 41cf85f75450aa4119d59e4f665ea438effe1b6f Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 17:36:44 +0100 Subject: [PATCH 017/404] feat(webgl): add initial AttribPool & VAO support, update ModelSpec & draw fns --- packages/webgl/package.json | 1 + packages/webgl/src/api.ts | 12 ++-- packages/webgl/src/buffer.ts | 58 +++++++++++++++- packages/webgl/src/draw.ts | 32 ++++----- packages/webgl/src/shader.ts | 126 ++++++++++++++++------------------- 5 files changed, 139 insertions(+), 90 deletions(-) diff --git a/packages/webgl/package.json b/packages/webgl/package.json index f868c4caff..2b2b4e7cdb 100644 --- a/packages/webgl/package.json +++ b/packages/webgl/package.json @@ -36,6 +36,7 @@ "@thi.ng/checks": "^2.1.5", "@thi.ng/dgraph": "^1.1.4", "@thi.ng/matrices": "^0.3.0", + "@thi.ng/vector-pools": "^0.2.12", "@thi.ng/vectors": "^2.5.2" }, "keywords": [ diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index abef9549bc..9ead863e43 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -411,6 +411,8 @@ export interface IShader extends IBind, IRelease { uniforms: ShaderUniforms; bindAttribs(specAttribs: ModelAttributeSpecs): void; + bindUniforms(specUnis: UniformValues): void; + prepareState(state?: Partial): void; } export interface IWebGLBuffer extends IBind, IRelease { @@ -469,13 +471,13 @@ export interface ModelSpec { */ instances?: InstancingSpec; /** - * WebGL draw mode + * WebGL draw mode. Defaults to `TRIANGLES` */ - mode: GLenum; + mode?: GLenum; /** - * Number of primitives to draw + * Number of vertices/indices to draw */ - numItems: number; + num: number; } /** @@ -537,7 +539,7 @@ export interface IndexBufferSpec { export interface InstancingSpec { attribs: IObjectOf; - numItems: number; + num: number; } export interface TextureOpts { diff --git a/packages/webgl/src/buffer.ts b/packages/webgl/src/buffer.ts index e8a2bf273d..dd29e76c54 100644 --- a/packages/webgl/src/buffer.ts +++ b/packages/webgl/src/buffer.ts @@ -1,10 +1,12 @@ import { TypedArray } from "@thi.ng/api"; +import { AttribPool } from "@thi.ng/vector-pools"; import { IndexBufferSpec, IWebGLBuffer, ModelAttributeSpecs, ModelSpec } from "./api"; +import { isGL2Context } from "./utils"; export class WebGLArrayBuffer implements IWebGLBuffer { gl: WebGLRenderingContext; @@ -69,6 +71,7 @@ export const compileModel = ( compileAttribs(gl, spec.attribs, mode); spec.instances && compileAttribs(gl, spec.instances.attribs, mode); compileIndices(gl, spec.indices, mode); + // TODO auto-create VAO & inject into model spec? return spec; }; @@ -82,7 +85,7 @@ const compileAttribs = ( if (attribs.hasOwnProperty(id)) { const attr = attribs[id]; if (attr.buffer) { - attr.buffer.set(attr.data); + attr.data && attr.buffer.set(attr.data); } else { attr.buffer = new WebGLArrayBuffer( gl, @@ -102,7 +105,7 @@ const compileIndices = ( ) => { if (!index) return; if (index.buffer) { - index.buffer.set(index.data); + index.data && index.buffer.set(index.data); } else { index.buffer = new WebGLArrayBuffer( gl, @@ -112,3 +115,54 @@ const compileIndices = ( ); } }; + +export const compileVAO = (gl: WebGLRenderingContext, spec: ModelSpec) => { + if (spec.shader) { + const isGL2 = isGL2Context(gl); + const ext = !isGL2 && gl.getExtension("OES_vertex_array_object"); + if (isGL2 || ext) { + let vao: WebGLVertexArrayObject; + if (isGL2) { + vao = (gl).createVertexArray(); + (gl).bindVertexArray(vao); + } else { + vao = ext.createVertexArrayOES(); + ext.bindVertexArrayOES(vao); + } + spec.shader.bindAttribs(spec.attribs); + if (spec.indices) { + spec.indices.buffer.bind(); + } + spec.shader.unbind(null); + if (isGL2) { + (gl).bindVertexArray(null); + } else { + ext.bindVertexArrayOES(null); + } + return vao; + } + } +}; + +export const compileAttribPool = ( + gl: WebGLRenderingContext, + pool: AttribPool, + ids?: string[], + target = gl.ARRAY_BUFFER, + mode = gl.STATIC_DRAW +) => { + const buf = buffer(gl, pool.bytes(), target, mode); + const spec = {}; + for (let id of ids || Object.keys(pool.specs)) { + const attr = pool.specs[id]; + spec[id] = { + buffer: buf, + data: null, + size: attr.size, + type: attr.type, + stride: pool.byteStride, + offset: attr.byteOffset + }; + } + return spec; +}; diff --git a/packages/webgl/src/draw.ts b/packages/webgl/src/draw.ts index 0393995d22..1f1401c893 100644 --- a/packages/webgl/src/draw.ts +++ b/packages/webgl/src/draw.ts @@ -11,6 +11,7 @@ export const draw = (specs: ModelSpec | ModelSpec[]) => { const indices = spec.indices; const gl = spec.shader.gl; bindTextures(spec.textures); + spec.shader.prepareState(); spec.shader.bind(spec); if (indices) { indices.buffer.bind(); @@ -18,8 +19,8 @@ export const draw = (specs: ModelSpec | ModelSpec[]) => { drawInstanced(gl, spec); } else { gl.drawElements( - spec.mode, - spec.numItems, + spec.mode || gl.TRIANGLES, + spec.num, indices.data instanceof Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT, @@ -30,7 +31,7 @@ export const draw = (specs: ModelSpec | ModelSpec[]) => { if (spec.instances) { drawInstanced(gl, spec); } else { - gl.drawArrays(spec.mode, 0, spec.numItems); + gl.drawArrays(spec.mode || gl.TRIANGLES, 0, spec.num); } } spec.shader.unbind(null); @@ -59,6 +60,7 @@ const drawInstanced = (gl: WebGLRenderingContext, spec: ModelSpec) => { : ext.vertexAttribDivisorANGLE(attr.loc, div); } } + const mode = spec.mode || gl.TRIANGLES; if (spec.indices) { const type = spec.indices.data instanceof Uint32Array @@ -66,32 +68,32 @@ const drawInstanced = (gl: WebGLRenderingContext, spec: ModelSpec) => { : gl.UNSIGNED_SHORT; isGL2 ? (gl).drawElementsInstanced( - spec.mode, - spec.numItems, + mode, + spec.num, type, 0, - spec.instances.numItems + spec.instances.num ) : ext.drawElementsInstancedANGLE( - spec.mode, - spec.numItems, + mode, + spec.num, type, 0, - spec.instances.numItems + spec.instances.num ); } else { isGL2 ? (gl).drawArraysInstanced( - spec.mode, + mode, 0, - spec.numItems, - spec.instances.numItems + spec.num, + spec.instances.num ) : ext.drawArraysInstancedANGLE( - spec.mode, + mode, 0, - spec.numItems, - spec.instances.numItems + spec.num, + spec.instances.num ); } // reset attrib divisors to allow non-instanced draws later on diff --git a/packages/webgl/src/shader.ts b/packages/webgl/src/shader.ts index 8a954dc8fe..0e78d6ae23 100644 --- a/packages/webgl/src/shader.ts +++ b/packages/webgl/src/shader.ts @@ -56,7 +56,6 @@ export class Shader implements IShader { bind(spec: ModelSpec) { if (this.program) { - this.prepareShaderState(); this.gl.useProgram(this.program); this.bindAttribs(spec.attribs); this.bindUniforms(spec.uniforms); @@ -83,22 +82,20 @@ export class Shader implements IShader { const gl = this.gl; let shaderAttrib; for (let id in specAttribs) { - if (specAttribs.hasOwnProperty(id)) { - if ((shaderAttrib = this.attribs[id])) { - const attr = specAttribs[id]; - attr.buffer.bind(); - gl.enableVertexAttribArray(shaderAttrib.loc); - gl.vertexAttribPointer( - shaderAttrib.loc, - attr.size || 3, - attr.type || gl.FLOAT, - attr.normalized, - attr.stride || 0, - attr.offset || 0 - ); - } else { - console.warn(`unknown attrib: ${id}`); - } + if ((shaderAttrib = this.attribs[id])) { + const attr = specAttribs[id]; + attr.buffer.bind(); + gl.enableVertexAttribArray(shaderAttrib.loc); + gl.vertexAttribPointer( + shaderAttrib.loc, + attr.size || 3, + attr.type || gl.FLOAT, + attr.normalized || false, + attr.stride || 0, + attr.offset || 0 + ); + } else { + console.warn(`unknown attrib: ${id}`); } } } @@ -106,14 +103,12 @@ export class Shader implements IShader { bindUniforms(specUnis: UniformValues) { const shaderUnis = this.uniforms; for (let id in specUnis) { - if (specUnis.hasOwnProperty(id)) { - const u = shaderUnis[id]; - if (u) { - const val = specUnis[id]; - u.setter(isFunction(val) ? val(shaderUnis, specUnis) : val); - } else { - console.warn(`unknown uniform: ${id}`); - } + const u = shaderUnis[id]; + if (u) { + const val = specUnis[id]; + u.setter(isFunction(val) ? val(shaderUnis, specUnis) : val); + } else { + console.warn(`unknown uniform: ${id}`); } } // apply defaults for non-specified uniforms in user spec @@ -130,7 +125,7 @@ export class Shader implements IShader { } } - prepareShaderState(state = this.state) { + prepareState(state = this.state) { const gl = this.gl; state.depth !== undefined && this.setState(gl.DEPTH_TEST, state.depth); if (state.cull !== undefined) { @@ -187,7 +182,7 @@ export const shader = (gl: WebGLRenderingContext, spec: ShaderSpec) => { gl.attachShader(program, fs); gl.linkProgram(program); if (gl.getProgramParameter(program, gl.LINK_STATUS)) { - const attribs = initAttributes(gl, program, spec.attribs, spec.version); + const attribs = initAttributes(gl, program, spec.attribs); const uniforms = initUniforms(gl, program, spec.uniforms); gl.deleteShader(vs); gl.deleteShader(fs); @@ -285,24 +280,21 @@ const parseAndThrowShaderError = ( const initAttributes = ( gl: WebGLRenderingContext, prog: WebGLProgram, - attribs: ShaderAttribSpecs, - version: GLSLVersion + attribs: ShaderAttribSpecs ) => { const res = >{}; for (let id in attribs) { - if (attribs.hasOwnProperty(id)) { - const val = attribs[id]; - const [type, loc] = isArray(val) ? val : [val, null]; - const aid = `a_${id}`; - if (loc != null && version && version >= "300") { - gl.bindAttribLocation(prog, loc, aid); - res[id] = { type, loc }; - } else { - res[id] = { - type, - loc: gl.getAttribLocation(prog, aid) - }; - } + const val = attribs[id]; + const [type, loc] = isArray(val) ? val : [val, null]; + const aid = `a_${id}`; + if (loc != null) { + gl.bindAttribLocation(prog, loc, aid); + res[id] = { type, loc }; + } else { + res[id] = { + type, + loc: gl.getAttribLocation(prog, aid) + }; } } return res; @@ -315,33 +307,31 @@ const initUniforms = ( ) => { const res = >{}; for (let id in uniforms) { - if (uniforms.hasOwnProperty(id)) { - const val = uniforms[id]; - let type: GLSL; - let t1, t2, defaultVal, defaultFn; - if (isArray(val)) { - [type, t1, t2] = val; - defaultVal = type < GLSL.bool_array ? t1 : t2; - if (isFunction(defaultVal)) { - defaultFn = defaultVal; - defaultVal = undefined; - } - } else { - type = val; - } - const loc = gl.getUniformLocation(prog, `u_${id}`); - const setter = UNIFORM_SETTERS[type]; - if (setter) { - res[id] = { - loc, - setter: setter(gl, loc, defaultVal), - defaultFn, - defaultVal, - type - }; - } else { - error(`invalid uniform type: ${GLSL[type]}`); + const val = uniforms[id]; + let type: GLSL; + let t1, t2, defaultVal, defaultFn; + if (isArray(val)) { + [type, t1, t2] = val; + defaultVal = type < GLSL.bool_array ? t1 : t2; + if (isFunction(defaultVal)) { + defaultFn = defaultVal; + defaultVal = undefined; } + } else { + type = val; + } + const loc = gl.getUniformLocation(prog, `u_${id}`); + const setter = UNIFORM_SETTERS[type]; + if (setter) { + res[id] = { + loc, + setter: setter(gl, loc, defaultVal), + defaultFn, + defaultVal, + type + }; + } else { + error(`invalid uniform type: ${GLSL[type]}`); } } return res; From 8e19948126833289c122bd60a560eedfb4508236 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 21:25:37 +0100 Subject: [PATCH 018/404] refactor(webgl): update shader output var handling, update presets - emit #define macros for GLES100 syntax - remove `version` from ShaderOpts (always auto-injected, based on GL context) --- packages/webgl/src/api.ts | 3 ++- packages/webgl/src/glsl/syntax.ts | 3 ++- packages/webgl/src/shader.ts | 3 ++- packages/webgl/src/shaders/lambert.ts | 7 ++----- packages/webgl/src/shaders/phong.ts | 7 ++----- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index 9ead863e43..1686595d9e 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -280,6 +280,8 @@ export interface ShaderSnippet { src: string; } +export const DEFAULT_OUTPUT: ShaderOutputSpecs = { fragColor: [GLSL.vec4, 0] }; + export interface ShaderSpec { /** * Vertex shader GLSL source code. @@ -402,7 +404,6 @@ export interface ShaderOpts { color: string; material: Partial; state: Partial; - version: GLSLVersion; } export interface IShader extends IBind, IRelease { diff --git a/packages/webgl/src/glsl/syntax.ts b/packages/webgl/src/glsl/syntax.ts index a0136d39d2..005739326a 100644 --- a/packages/webgl/src/glsl/syntax.ts +++ b/packages/webgl/src/glsl/syntax.ts @@ -36,7 +36,8 @@ export const SYNTAX: Record = { fs: (id, type, pre) => arrayDecl("varying", type, pre.v + id) }, uniform: (id, u, pre) => arrayDecl("uniform", u, pre.u + id), - output: () => "" + output: (id, type, pre) => + isArray(type) ? `#define ${pre.o}${id} gl_FragData[${type[1]}]` : "" }, /** * WebGL 2 (GLSL ES 3) diff --git a/packages/webgl/src/shader.ts b/packages/webgl/src/shader.ts index 0e78d6ae23..03644454b8 100644 --- a/packages/webgl/src/shader.ts +++ b/packages/webgl/src/shader.ts @@ -6,6 +6,7 @@ import { isFunction } from "@thi.ng/checks"; import { + DEFAULT_OUTPUT, GLSL, GLSLDeclPrefixes, GLSLExtensionBehavior, @@ -231,7 +232,7 @@ export const prepareShaderSource = (spec: ShaderSpec, type: ShaderType) => { src += isVS ? compileVars(spec.attribs, syntax.attrib, prefixes) : compileVars( - spec.outputs || { fragColor: GLSL.vec4 }, + spec.outputs || DEFAULT_OUTPUT, syntax.output, prefixes ); diff --git a/packages/webgl/src/shaders/lambert.ts b/packages/webgl/src/shaders/lambert.ts index d925f8651b..746e92515f 100644 --- a/packages/webgl/src/shaders/lambert.ts +++ b/packages/webgl/src/shaders/lambert.ts @@ -8,7 +8,6 @@ import { } from "../api"; import { defglslA } from "../glsl/assemble"; import { lambert } from "../glsl/lighting"; -import { EXPORT_FRAGCOL } from "../glsl/syntax"; import { mvp, surfaceNormal } from "../glsl/vertex"; import { defMaterial } from "../material"; import { autoNormalMatrix2 } from "../normal-mat"; @@ -30,8 +29,7 @@ export const LAMBERT = (opts: Partial = {}): ShaderSpec => ({ fs: defglslA( `void main(){ float lam = lambert(normalize(v_normal), u_lightDir, u_bidir); - vec4 col = vec4(u_ambientCol + v_col * u_lightCol * lam, u_alpha); -${EXPORT_FRAGCOL("col")} + o_fragColor = vec4(u_ambientCol + v_col * u_lightCol * lam, u_alpha); }`, [lambert] ), @@ -65,6 +63,5 @@ ${EXPORT_FRAGCOL("col")} state: { depth: true, ...opts.state - }, - version: opts.version + } }); diff --git a/packages/webgl/src/shaders/phong.ts b/packages/webgl/src/shaders/phong.ts index 9e3d16c29c..effa395824 100644 --- a/packages/webgl/src/shaders/phong.ts +++ b/packages/webgl/src/shaders/phong.ts @@ -5,7 +5,6 @@ import { ShaderSpec } from "../api"; import { defglslA } from "../glsl/assemble"; -import { EXPORT_FRAGCOL } from "../glsl/syntax"; import { surfaceNormal } from "../glsl/vertex"; import { defMaterial } from "../material"; import { autoNormalMatrix1 } from "../normal-mat"; @@ -35,8 +34,7 @@ export const PHONG = (opts: Partial = {}): ShaderSpec => ({ float specular = directional > 0.0 ? pow(dot(N, normalize(L + normalize(v_eye))), u_shininess) : 0.0; - vec3 col = u_ambientCol + v_col * directional * u_lightCol + u_specularCol * specular; -${EXPORT_FRAGCOL("vec4(col, 1.0)")} + o_fragColor = u_ambientCol + v_col * directional * u_lightCol + u_specularCol * specular; }`, attribs: { position: GLSL.vec3, @@ -64,6 +62,5 @@ ${EXPORT_FRAGCOL("vec4(col, 1.0)")} lightCol: [GLSL.vec3, [1, 1, 1]], ...defMaterial(opts.material) }, - state: { depth: true, ...opts.state }, - version: opts.version + state: { depth: true, ...opts.state } }); From 3d365276d69b08bedb1514123baaaf00530f0a84 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 7 Apr 2019 23:25:58 +0100 Subject: [PATCH 019/404] fix(webgl): gl2 texStorage2D() levels --- packages/webgl/src/texture.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webgl/src/texture.ts b/packages/webgl/src/texture.ts index 9223f79deb..16664da6d6 100644 --- a/packages/webgl/src/texture.ts +++ b/packages/webgl/src/texture.ts @@ -81,7 +81,7 @@ export class Texture implements ITexture { } else if (opts.width && opts.height) { gl.texStorage2D( target, - 0, + 1, internalFormat, opts.width, opts.height From 4a1a5b9ba4b40622e4b501b5efc98f67c4e5b41a Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 8 Apr 2019 20:57:51 +0100 Subject: [PATCH 020/404] feat(webgl): add texture gens, add opt uv support for lambert shader --- packages/webgl/package.json | 4 +++ packages/webgl/src/api.ts | 21 ++++++++++++ packages/webgl/src/glsl/syntax.ts | 12 +++++-- packages/webgl/src/index.ts | 3 ++ packages/webgl/src/shader.ts | 5 +-- packages/webgl/src/shaders/lambert.ts | 9 +++-- packages/webgl/src/textures/checkerboard.ts | 37 +++++++++++++++++++++ packages/webgl/src/textures/stripes.ts | 28 ++++++++++++++++ packages/webgl/src/textures/utils.ts | 18 ++++++++++ 9 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 packages/webgl/src/textures/checkerboard.ts create mode 100644 packages/webgl/src/textures/stripes.ts create mode 100644 packages/webgl/src/textures/utils.ts diff --git a/packages/webgl/package.json b/packages/webgl/package.json index 2b2b4e7cdb..4ebe847b58 100644 --- a/packages/webgl/package.json +++ b/packages/webgl/package.json @@ -33,9 +33,13 @@ }, "dependencies": { "@thi.ng/api": "^6.0.1", + "@thi.ng/associative": "^2.1.2", + "@thi.ng/binary": "^1.0.5", "@thi.ng/checks": "^2.1.5", "@thi.ng/dgraph": "^1.1.4", + "@thi.ng/errors": "^1.0.5", "@thi.ng/matrices": "^0.3.0", + "@thi.ng/transducers": "^5.3.3", "@thi.ng/vector-pools": "^0.2.12", "@thi.ng/vectors": "^2.5.2" }, diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index 1686595d9e..9747ba8061 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -402,6 +402,7 @@ export interface ShaderOpts { instancePos: string; instanceColor: string; color: string; + uv: string; material: Partial; state: Partial; } @@ -602,3 +603,23 @@ export const GL_COLOR_ATTACHMENT0_WEBGL = 0x8ce0; export const GL_MAX_COLOR_ATTACHMENTS_WEBGL = 0x8cdf; export const GL_RGBA = 0x1908; export const GL_RGBA32F = 0x8814; + +// [SRC_ALPHA, ONE_MINUS_SRC_ALPHA] +export const DEFAULT_BLEND: Tuple = [0x302, 0x303]; + +export const FULLSCREEN_QUAD: ModelSpec = { + attribs: { + position: { + data: new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), + size: 2 + }, + uv: { + data: new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), + size: 2 + } + }, + uniforms: {}, + shader: null, + mode: 5, // TRIANGLE_STRIP, + num: 4 +}; diff --git a/packages/webgl/src/glsl/syntax.ts b/packages/webgl/src/glsl/syntax.ts index 005739326a..f7b2033148 100644 --- a/packages/webgl/src/glsl/syntax.ts +++ b/packages/webgl/src/glsl/syntax.ts @@ -82,10 +82,17 @@ const arrayDecl = ( * @param ok * @param fail */ -export const VERSION_CHECK = (ver: number, ok: string, fail = "") => - `#if __VERSION__ >= ${ver} +export const VERSION_CHECK = (ver: number, ok: string, fail = "") => { + let cmp = ">="; + if (!ok) { + ok = fail; + fail = null; + cmp = "<"; + } + return `#if __VERSION__ ${cmp} ${ver} ${ok}${fail ? `\n#else\n${fail}` : ""} #endif`; +}; /** * GLSL version specific fragment shader output. If `__VERSION__ >= 300` @@ -116,4 +123,5 @@ precision mediump float; #ifndef HALF_PI #define HALF_PI 1.570796326794896 #endif +${VERSION_CHECK(300, "", "#define texture texture2D")} `; diff --git a/packages/webgl/src/index.ts b/packages/webgl/src/index.ts index a7e9e310d3..d8fa350e99 100644 --- a/packages/webgl/src/index.ts +++ b/packages/webgl/src/index.ts @@ -13,6 +13,9 @@ export * from "./utils"; export * from "./shaders/lambert"; export * from "./shaders/phong"; +export * from "./textures/checkerboard"; +export * from "./textures/stripes"; + export * from "./glsl/assemble"; export * from "./glsl/syntax"; diff --git a/packages/webgl/src/shader.ts b/packages/webgl/src/shader.ts index 03644454b8..e96e578adb 100644 --- a/packages/webgl/src/shader.ts +++ b/packages/webgl/src/shader.ts @@ -1,4 +1,4 @@ -import { Fn3, IObjectOf, Tuple } from "@thi.ng/api"; +import { Fn3, IObjectOf } from "@thi.ng/api"; import { existsAndNotNull, isArray, @@ -29,9 +29,6 @@ import { GLSL_HEADER, PREFIXES, SYNTAX } from "./glsl/syntax"; import { UNIFORM_SETTERS } from "./uniforms"; import { isGL2Context } from "./utils"; -// [SRC_ALPHA, ONE_MINUS_SRC_ALPHA]; -export const DEFAULT_BLEND: Tuple = [0x302, 0x303]; - const ERROR_REGEXP = /ERROR: \d+:(\d+): (.*)/; export class Shader implements IShader { diff --git a/packages/webgl/src/shaders/lambert.ts b/packages/webgl/src/shaders/lambert.ts index 746e92515f..eee4bb7f53 100644 --- a/packages/webgl/src/shaders/lambert.ts +++ b/packages/webgl/src/shaders/lambert.ts @@ -20,6 +20,7 @@ export type LambertOpts = ShaderOpts< export const LAMBERT = (opts: Partial = {}): ShaderSpec => ({ vs: defglslA( `void main(){ + ${opts.uv ? `v_uv = a_${opts.uv};` : ""} v_col = ${colorAttrib(opts)}; v_normal = surfaceNormal(a_normal, u_normalMat); gl_Position = mvp(${positionAttrib(opts)}, u_model, u_view, u_proj); @@ -29,13 +30,15 @@ export const LAMBERT = (opts: Partial = {}): ShaderSpec => ({ fs: defglslA( `void main(){ float lam = lambert(normalize(v_normal), u_lightDir, u_bidir); - o_fragColor = vec4(u_ambientCol + v_col * u_lightCol * lam, u_alpha); + vec3 col = ${opts.uv ? `texture(u_tex, v_uv).xyz * v_col` : "v_col"}; + o_fragColor = vec4(u_ambientCol + col * u_lightCol * lam, u_alpha); }`, [lambert] ), attribs: { position: GLSL.vec3, normal: GLSL.vec3, + ...(opts.uv ? { [opts.uv]: GLSL.vec2 } : null), ...(opts.color && !opts.instanceColor ? { [opts.color]: GLSL.vec3 } : null), @@ -44,7 +47,8 @@ export const LAMBERT = (opts: Partial = {}): ShaderSpec => ({ }, varying: { col: GLSL.vec3, - normal: GLSL.vec3 + normal: GLSL.vec3, + ...(opts.uv ? { uv: GLSL.vec2 } : null) }, uniforms: { model: GLSL.mat4, @@ -57,6 +61,7 @@ export const LAMBERT = (opts: Partial = {}): ShaderSpec => ({ { diffuseCol: [1, 1, 1], ...opts.material }, { specularCol: false } ), + ...(opts.uv ? { tex: GLSL.sampler2D } : null), alpha: [GLSL.float, 1], bidir: [GLSL.bool, 0] }, diff --git a/packages/webgl/src/textures/checkerboard.ts b/packages/webgl/src/textures/checkerboard.ts new file mode 100644 index 0000000000..156b3d0dea --- /dev/null +++ b/packages/webgl/src/textures/checkerboard.ts @@ -0,0 +1,37 @@ +import { abgr, canvas2d } from "./utils"; + +export interface CheckerboardOpts { + size: number; + col1: number; + col2: number; + corners: boolean; + cornerCols: number[]; +} + +export const checkerboard = (opts: Partial) => { + opts = { + size: 16, + col1: 0xffffffff, + col2: 0xff000000, + cornerCols: [0xffff0000, 0xff00ff00, 0xff0000ff, 0xffffff00], + ...opts + }; + const size = opts.size; + const col1 = abgr(opts.col1); + const col2 = abgr(opts.col2); + const { canvas, ctx, img, pix } = canvas2d(size); + for (let y = 0, i = 0; y < size; y++) { + for (let x = 0; x < size; x++, i++) { + pix[i] = (y & 1) ^ (x & 1) ? col1 : col2; + } + } + if (opts.corners) { + const corners = opts.cornerCols.map(abgr); + pix[0] = corners[0]; + pix[size - 1] = corners[1]; + pix[pix.length - size] = corners[2]; + pix[pix.length - 1] = corners[3]; + } + ctx.putImageData(img, 0, 0); + return canvas; +}; diff --git a/packages/webgl/src/textures/stripes.ts b/packages/webgl/src/textures/stripes.ts new file mode 100644 index 0000000000..efe50afd23 --- /dev/null +++ b/packages/webgl/src/textures/stripes.ts @@ -0,0 +1,28 @@ +import { abgr, canvas2d } from "./utils"; + +export interface StripeOpts { + size: number; + col1: number; + col2: number; + horizontal: boolean; +} + +export const stripes = (opts: Partial) => { + opts = { + size: 16, + col1: 0xffffffff, + col2: 0xff000000, + ...opts + }; + const size = opts.size; + const col1 = abgr(opts.col1); + const col2 = abgr(opts.col2); + const { canvas, ctx, img, pix } = opts.horizontal + ? canvas2d(1, size) + : canvas2d(size, 1); + for (let x = size; --x >= 0; ) { + pix[x] = x & 1 ? col1 : col2; + } + ctx.putImageData(img, 0, 0); + return canvas; +}; diff --git a/packages/webgl/src/textures/utils.ts b/packages/webgl/src/textures/utils.ts new file mode 100644 index 0000000000..47e92b04e8 --- /dev/null +++ b/packages/webgl/src/textures/utils.ts @@ -0,0 +1,18 @@ +import { swizzle8 } from "@thi.ng/binary"; + +export const canvas2d = (width: number, height = width) => { + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext("2d"); + const img = ctx.getImageData(0, 0, width, height); + const pix = new Uint32Array(img.data.buffer); + return { + canvas, + ctx, + img, + pix + }; +}; + +export const abgr = (argb: number) => swizzle8(argb, 0, 3, 1, 2); From 29791fd406c8042f2c32318401208e35fd9d96b7 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 9 Apr 2019 01:23:14 +0100 Subject: [PATCH 021/404] feat(webgl): add cube modelspec factory --- packages/webgl/src/geo/cube.ts | 33 +++++++++++++++++++++++++++++++++ packages/webgl/src/index.ts | 9 ++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 packages/webgl/src/geo/cube.ts diff --git a/packages/webgl/src/geo/cube.ts b/packages/webgl/src/geo/cube.ts new file mode 100644 index 0000000000..66501a3040 --- /dev/null +++ b/packages/webgl/src/geo/cube.ts @@ -0,0 +1,33 @@ +import { ModelSpec } from "../api"; + +export interface CubeOpts { + size: number; + normal: boolean; + uv: boolean; +} + +// prettier-ignore +export const cube = (opts?: Partial) => { + opts = { size: 1, normal: true, uv: true, ...opts}; + const s = opts.size; + const spec: ModelSpec = { + attribs: { + position: { + data: new Float32Array([s, s, -s, s, s, s, s, -s, s, s, -s, -s, -s, s, s, -s, s, -s, -s, -s, -s, -s, -s, s, -s, s, s, s, s, s, s, s, -s, -s, s, -s, -s, -s, -s, s, -s, -s, s, -s, s, -s, -s, s, s, s, s, -s, s, s, -s, -s, s, s, -s, s, -s, s, -s, s, s, -s, s, -s, -s, -s, -s, -s]) + }, + }, + indices:{ + data: new Uint16Array([0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23]) + }, + shader: null, + num: 36 + }; + opts.normal && (spec.attribs.normal = { + data: new Float32Array([1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1]) + }); + opts.uv && (spec.attribs.uv = { + data: new Float32Array([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1]), + size: 2 + }); + return spec; +}; diff --git a/packages/webgl/src/index.ts b/packages/webgl/src/index.ts index d8fa350e99..18e98fa8bd 100644 --- a/packages/webgl/src/index.ts +++ b/packages/webgl/src/index.ts @@ -4,6 +4,7 @@ export * from "./canvas"; export * from "./draw"; export * from "./error"; export * from "./fbo"; +export * from "./gpgpu"; export * from "./material"; export * from "./normal-mat"; export * from "./shader"; @@ -13,11 +14,13 @@ export * from "./utils"; export * from "./shaders/lambert"; export * from "./shaders/phong"; -export * from "./textures/checkerboard"; -export * from "./textures/stripes"; - export * from "./glsl/assemble"; export * from "./glsl/syntax"; export * from "./glsl/lighting"; export * from "./glsl/vertex"; + +export * from "./textures/checkerboard"; +export * from "./textures/stripes"; + +export * from "./geo/cube"; From 582b57a3c1a42ecc7a02ff93b78a5f29875f37bb Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 9 Apr 2019 01:24:19 +0100 Subject: [PATCH 022/404] feat(webgl): add gpgpu skeleton --- packages/webgl/src/api.ts | 22 ++++ packages/webgl/src/gpgpu.ts | 242 ++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 packages/webgl/src/gpgpu.ts diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index 9747ba8061..4e2a04e571 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -599,6 +599,28 @@ export interface WeblGLCanvasOpts { ext: string[]; } +export interface GPGPUOpts { + size: number; + inputs?: number; + outputs?: number; + gl?: WebGLRenderingContext; + version?: 1 | 2; +} + +export interface GPGPUJobConfig { + shader: ShaderSpec; + src: string; + uniforms: ShaderUniformSpecs; + inputs: number; + outputs?: number; +} + +export interface GPGPUJobExecOpts { + inputs: (ITexture | Float32Array)[]; + outputs?: number[]; + uniforms?: UniformValues; +} + export const GL_COLOR_ATTACHMENT0_WEBGL = 0x8ce0; export const GL_MAX_COLOR_ATTACHMENTS_WEBGL = 0x8cdf; export const GL_RGBA = 0x1908; diff --git a/packages/webgl/src/gpgpu.ts b/packages/webgl/src/gpgpu.ts new file mode 100644 index 0000000000..3283104632 --- /dev/null +++ b/packages/webgl/src/gpgpu.ts @@ -0,0 +1,242 @@ +import { assert, IRelease } from "@thi.ng/api"; +import { mergeDeepObj } from "@thi.ng/associative"; +import { ceilPow2 } from "@thi.ng/binary"; +import { illegalArgs } from "@thi.ng/errors"; +import { + assocObj, + every, + map, + range, + transduce +} from "@thi.ng/transducers"; +import { + FULLSCREEN_QUAD, + GL_RGBA, + GL_RGBA32F, + GLSL, + GPGPUJobConfig, + GPGPUJobExecOpts, + GPGPUOpts, + ITexture, + ModelSpec, + ShaderSpec +} from "./api"; +import { compileModel } from "./buffer"; +import { getExtensions, glCanvas } from "./canvas"; +import { draw } from "./draw"; +import { FBO } from "./fbo"; +import { VERSION_CHECK } from "./glsl/syntax"; +import { shader } from "./shader"; +import { floatTexture } from "./texture"; +import { isGL2Context } from "./utils"; + +export const GPGPU_SHADER_TEMPLATE: ShaderSpec = { + vs: `void main(){v_uv=a_uv;gl_Position=vec4(a_position,0.0,1.0);}`, + fs: "", + pre: VERSION_CHECK(300, "#define read texture", "#define read texture2D"), + attribs: { + position: GLSL.vec2, + uv: GLSL.vec2 + }, + varying: { + uv: GLSL.vec2 + }, + uniforms: {}, + ext: {} +}; + +export const gpgpu = (opts: GPGPUOpts) => new GPGPU(opts); + +export class GPGPU implements IRelease { + canvas: HTMLCanvasElement; + gl: WebGLRenderingContext; + fbo: FBO; + inputs: ITexture[]; + outputs: ITexture[]; + spec: ModelSpec; + opts: GPGPUOpts; + width: number; + size: number; + + constructor(opts: GPGPUOpts) { + opts = { version: 1, inputs: 1, outputs: 1, ...opts }; + const width = ceilPow2(Math.ceil(Math.sqrt(opts.size / 4))); + let gl; + if (opts.gl) { + gl = opts.gl; + opts.version = isGL2Context(gl) ? 2 : 1; + } else { + const res = glCanvas({ + opts: { antialias: false, alpha: false, depth: false }, + width: 1, + height: 1, + autoScale: false, + version: opts.version + }); + this.canvas = res.canvas; + gl = res.gl; + } + const ext = getExtensions( + gl, + opts.version === 1 + ? ["WEBGL_color_buffer_float", "OES_texture_float"] + : ["EXT_color_buffer_float"] + ); + assert( + every((id) => !!ext[id], Object.keys(ext)), + "WebGL float extension unavailable" + ); + this.gl = gl; + this.opts = opts; + this.width = width; + this.size = width * width * 4; + let tmp = new Float32Array(this.size); + this.inputs = [ + ...map( + () => floatTexture(gl, tmp, width, width), + range(opts.inputs) + ) + ]; + this.outputs = [ + ...map( + () => floatTexture(gl, tmp, width, width), + range(opts.outputs) + ) + ]; + tmp = null; + this.fbo = new FBO(gl); + this.spec = compileModel(gl, { + ...FULLSCREEN_QUAD, + textures: this.inputs + }); + } + + newJob(opts: Partial) { + return new GPGPUJob(this, opts); + } + + release() { + this.fbo.release(); + for (let t of this.inputs) { + t.release(); + } + for (let t of this.outputs) { + t.release(); + } + delete this.inputs; + delete this.outputs; + delete this.spec; + delete this.canvas; + delete this.gl; + delete this.fbo; + delete this.opts; + return true; + } +} + +export class GPGPUJob implements IRelease { + ctx: GPGPU; + spec: ModelSpec; + opts: GPGPUJobConfig; + + constructor(ctx: GPGPU, opts: Partial) { + opts = { + inputs: 1, + outputs: 1, + ...opts + }; + assert( + opts.inputs <= ctx.opts.inputs, + `context only supports max. ${ctx.opts.inputs} inputs` + ); + assert( + opts.outputs <= ctx.opts.outputs, + `context only supports max. ${ctx.opts.outputs} outputs` + ); + this.ctx = ctx; + this.opts = opts; + this.spec = this.buildSpec(); + } + + run(runOpts: GPGPUJobExecOpts) { + const inputs = runOpts.inputs; + const outputs = runOpts.outputs || [...range(this.opts.outputs)]; + assert(inputs.length <= this.opts.inputs, "too many inputs"); + assert(outputs.length <= this.opts.outputs, "too many outputs"); + const ctx = this.ctx; + const gl = ctx.gl; + const width = ctx.width; + const format = ctx.opts.version === 2 ? GL_RGBA32F : GL_RGBA; + const spec = this.spec; + for (let i = 0; i < inputs.length; i++) { + let tex = inputs[i]; + if (tex instanceof Float32Array) { + assert(tex.length <= ctx.size, `input #${i} too large`); + ctx.inputs[i].configure({ + image: tex, + type: gl.FLOAT, + internalFormat: format, + height: width, + width + }); + tex = ctx.inputs[i]; + } + spec.textures[i] = tex; + } + spec.uniforms = { ...spec.uniforms, ...runOpts.uniforms }; + ctx.fbo.configure({ tex: outputs.map((i) => ctx.outputs[i]) }); + gl.viewport(0, 0, width, width); + draw(spec); + return this; + } + + result(out?: Float32Array, id = 0) { + const ctx = this.ctx; + const gl = ctx.gl; + const fbo = new FBO(gl, { tex: [ctx.outputs[id]] }); + out = out || new Float32Array(ctx.size); + gl.readPixels(0, 0, ctx.width, ctx.width, gl.RGBA, gl.FLOAT, out); + fbo.release(); + return out; + } + + release() { + this.spec.shader.release(); + delete this.spec; + delete this.ctx; + return true; + } + + protected buildSpec() { + const opts = this.opts; + const ctx = this.ctx; + const spec: ModelSpec = mergeDeepObj({}, ctx.spec); + let shaderSpec: ShaderSpec; + if (opts.src) { + shaderSpec = mergeDeepObj({}, GPGPU_SHADER_TEMPLATE); + shaderSpec.fs += opts.src; + shaderSpec.outputs = transduce( + map((i) => [`output${i}`, [GLSL.vec4, i]]), + assocObj(), + range(opts.outputs) + ); + if (opts.uniforms) { + Object.assign(shaderSpec.uniforms, opts.uniforms); + } + } else if (opts.shader) { + shaderSpec = opts.shader; + shaderSpec.uniforms = shaderSpec.uniforms || {}; + shaderSpec.ext = shaderSpec.ext || {}; + } else { + illegalArgs("require either `src` or `shader` option"); + } + shaderSpec.uniforms.inputs = [GLSL.sampler2D_array, opts.inputs]; + if (ctx.fbo.ext && opts.outputs > 1) { + shaderSpec.ext["GL_EXT_draw_buffers"] = "require"; + } + spec.uniforms.inputs = [...range(opts.inputs)]; + spec.textures = ctx.inputs.slice(0, opts.inputs); + spec.shader = shader(ctx.gl, shaderSpec); + return spec; + } +} From ad43a1c25139e6b01a4d8c881db72be1f4557eb4 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 9 Apr 2019 01:24:54 +0100 Subject: [PATCH 023/404] feat(webgl): add cubemap support & cubeMap() factory fn --- packages/webgl/src/texture.ts | 40 +++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/webgl/src/texture.ts b/packages/webgl/src/texture.ts index 16664da6d6..6db5f32b0b 100644 --- a/packages/webgl/src/texture.ts +++ b/packages/webgl/src/texture.ts @@ -1,3 +1,4 @@ +import { withoutKeysObj } from "@thi.ng/associative"; import { isArray } from "@thi.ng/checks"; import { ITexture, TextureOpts } from "./api"; import { isGL2Context } from "./utils"; @@ -23,13 +24,14 @@ export class Texture implements ITexture { configure(opts: Partial) { const gl = this.gl; - const target = (this.target = opts.target || gl.TEXTURE_2D); + const target = this.target; + const imgTarget = opts.target || target; const format = opts.format || gl.RGBA; const internalFormat = opts.internalFormat || format; const type = opts.type || gl.UNSIGNED_BYTE; let t1: GLenum, t2: GLenum; - gl.bindTexture(target, this.tex); + gl.bindTexture(this.target, this.tex); if (opts.filter) { const flt = opts.filter; @@ -67,7 +69,7 @@ export class Texture implements ITexture { if (isGL2Context(gl)) { if (opts.image && opts.width && opts.height) { gl.texImage2D( - target, + imgTarget, 0, internalFormat, opts.width, @@ -80,7 +82,7 @@ export class Texture implements ITexture { ); } else if (opts.width && opts.height) { gl.texStorage2D( - target, + imgTarget, 1, internalFormat, opts.width, @@ -91,7 +93,7 @@ export class Texture implements ITexture { if (opts.image) { if (opts.width && opts.height) { gl.texImage2D( - target, + imgTarget, 0, internalFormat, opts.width, @@ -102,7 +104,7 @@ export class Texture implements ITexture { opts.image ); } else { - gl.texImage2D(target, 0, internalFormat, format, type, < + gl.texImage2D(imgTarget, 0, internalFormat, format, type, < TexImageSource >opts.image); } @@ -144,6 +146,32 @@ export const texture = ( opts?: Partial ) => new Texture(gl, opts); +/** + * Creates cube map texture from given 6 `face` texture sources. The + * given options are shared by each each side/face of the cube map. + * + * @param gl + * @param faces in order: +x,-x,+y,-y,+z,-z + * @param opts + */ +export const cubeMap = ( + gl: WebGLRenderingContext, + faces: (ArrayBufferView | TexImageSource)[], + opts?: Partial +) => { + const tex = new Texture(gl, { target: gl.TEXTURE_CUBE_MAP }); + const faceOpts = withoutKeysObj(opts, ["target", "image", "mipmap"]); + for (let i = 0; i < 6; i++) { + faceOpts.target = gl.TEXTURE_CUBE_MAP_POSITIVE_X + i; + faceOpts.image = faces[i]; + tex.configure(faceOpts); + } + if (opts.mipmap) { + gl.generateMipmap(tex.target); + } + return tex; +}; + /** * Creates & configure a new FLOAT texture. * From 05f505982968d83d85c069e11ed8bcf2e5048758 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 9 Apr 2019 01:27:02 +0100 Subject: [PATCH 024/404] feat(webgl): update GLSL_HEADER & LAMBERT shader preset --- packages/webgl/src/glsl/syntax.ts | 1 - packages/webgl/src/shaders/lambert.ts | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/webgl/src/glsl/syntax.ts b/packages/webgl/src/glsl/syntax.ts index f7b2033148..b8e2d46be5 100644 --- a/packages/webgl/src/glsl/syntax.ts +++ b/packages/webgl/src/glsl/syntax.ts @@ -123,5 +123,4 @@ precision mediump float; #ifndef HALF_PI #define HALF_PI 1.570796326794896 #endif -${VERSION_CHECK(300, "", "#define texture texture2D")} `; diff --git a/packages/webgl/src/shaders/lambert.ts b/packages/webgl/src/shaders/lambert.ts index eee4bb7f53..6bd2531bbe 100644 --- a/packages/webgl/src/shaders/lambert.ts +++ b/packages/webgl/src/shaders/lambert.ts @@ -8,6 +8,7 @@ import { } from "../api"; import { defglslA } from "../glsl/assemble"; import { lambert } from "../glsl/lighting"; +import { VERSION_CHECK } from "../glsl/syntax"; import { mvp, surfaceNormal } from "../glsl/vertex"; import { defMaterial } from "../material"; import { autoNormalMatrix2 } from "../normal-mat"; @@ -35,6 +36,7 @@ export const LAMBERT = (opts: Partial = {}): ShaderSpec => ({ }`, [lambert] ), + pre: VERSION_CHECK(300, "", "#define texture texture2D"), attribs: { position: GLSL.vec3, normal: GLSL.vec3, From 1f07e4556d7b02f71f9d2665006705fdcb7e02ea Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 9 Apr 2019 10:19:19 +0100 Subject: [PATCH 025/404] feat(webgl):update TextureOpts & texture config --- packages/webgl/src/api.ts | 5 + packages/webgl/src/texture.ts | 180 ++++++++++++++++++++++------------ 2 files changed, 123 insertions(+), 62 deletions(-) diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts index 4e2a04e571..63a61db1da 100644 --- a/packages/webgl/src/api.ts +++ b/packages/webgl/src/api.ts @@ -550,6 +550,9 @@ export interface TextureOpts { type: GLenum; filter: GLenum | GLenum[]; wrap: GLenum | GLenum[]; + lod: GLenum[]; + minMaxLevel: GLenum[]; + level: GLenum; format: GLenum; internalFormat: GLenum; width: number; @@ -557,6 +560,8 @@ export interface TextureOpts { mipmap: boolean; flip: boolean; premultiply: boolean; + sub: boolean; + pos: number[]; } export interface FboOpts { diff --git a/packages/webgl/src/texture.ts b/packages/webgl/src/texture.ts index 6db5f32b0b..c06a6e8dfe 100644 --- a/packages/webgl/src/texture.ts +++ b/packages/webgl/src/texture.ts @@ -24,15 +24,76 @@ export class Texture implements ITexture { configure(opts: Partial) { const gl = this.gl; + const isGL2 = isGL2Context(gl); const target = this.target; const imgTarget = opts.target || target; const format = opts.format || gl.RGBA; const internalFormat = opts.internalFormat || format; const type = opts.type || gl.UNSIGNED_BYTE; - let t1: GLenum, t2: GLenum; + let t1: GLenum, t2: GLenum, t3: GLenum; gl.bindTexture(this.target, this.tex); + opts.flip !== undefined && + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, opts.flip ? 1 : 0); + + opts.premultiply !== undefined && + gl.pixelStorei( + gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, + opts.premultiply ? 1 : 0 + ); + + if (opts.image !== undefined) { + const level = opts.level || 0; + const pos = opts.pos || [0, 0]; + if (opts.width && opts.height) { + opts.sub + ? gl.texSubImage2D( + imgTarget, + level, + pos[0], + pos[1], + opts.width, + opts.height, + format, + type, + opts.image + ) + : gl.texImage2D( + imgTarget, + level, + internalFormat, + opts.width, + opts.height, + 0, + format, + type, + opts.image + ); + } else { + opts.sub + ? gl.texSubImage2D( + imgTarget, + level, + pos[0], + pos[1], + format, + type, + opts.image + ) + : gl.texImage2D( + imgTarget, + level, + internalFormat, + format, + type, + opts.image + ); + } + } + + opts.mipmap && gl.generateMipmap(target); + if (opts.filter) { const flt = opts.filter; if (isArray(flt)) { @@ -41,8 +102,8 @@ export class Texture implements ITexture { } else { t1 = t2 = flt; } - gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, t1); - gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, t2); + t1 && gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, t1); + t2 && gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, t2); } if (opts.wrap) { @@ -50,68 +111,51 @@ export class Texture implements ITexture { if (isArray(wrap)) { t1 = wrap[0]; t2 = wrap[1]; + t3 = wrap[2]; } else { - t1 = t2 = wrap; + t1 = t2 = t3 = wrap; } - gl.texParameteri(target, gl.TEXTURE_WRAP_S, t1); - gl.texParameteri(target, gl.TEXTURE_WRAP_T, t2); + t1 && gl.texParameteri(target, gl.TEXTURE_WRAP_S, t1); + t2 && gl.texParameteri(target, gl.TEXTURE_WRAP_T, t2); + t3 && + isGL2 && + target === (gl).TEXTURE_3D && + gl.texParameteri( + target, + (gl).TEXTURE_WRAP_R, + t3 + ); } - opts.flip !== undefined && - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, opts.flip ? 1 : 0); - - opts.premultiply !== undefined && - gl.pixelStorei( - gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, - opts.premultiply ? 1 : 0 - ); - - if (isGL2Context(gl)) { - if (opts.image && opts.width && opts.height) { - gl.texImage2D( - imgTarget, - 0, - internalFormat, - opts.width, - opts.height, - 0, - format, - type, - opts.image, - 0 + if (opts.lod) { + const [t1, t2] = opts.lod; + t1 && + gl.texParameteri( + target, + (gl).TEXTURE_MIN_LOD, + t1 ); - } else if (opts.width && opts.height) { - gl.texStorage2D( - imgTarget, - 1, - internalFormat, - opts.width, - opts.height + t2 && + gl.texParameteri( + target, + (gl).TEXTURE_MAX_LOD, + t2 ); - } - } else { - if (opts.image) { - if (opts.width && opts.height) { - gl.texImage2D( - imgTarget, - 0, - internalFormat, - opts.width, - opts.height, - 0, - format, - type, - opts.image - ); - } else { - gl.texImage2D(imgTarget, 0, internalFormat, format, type, < - TexImageSource - >opts.image); - } - } } - opts.mipmap && gl.generateMipmap(target); + if (opts.minMaxLevel) { + const [t1, t2] = opts.minMaxLevel; + gl.texParameteri( + target, + (gl).TEXTURE_BASE_LEVEL, + t1 + ); + gl.texParameteri( + target, + (gl).TEXTURE_MAX_LEVEL, + t2 + ); + } return true; } @@ -148,7 +192,16 @@ export const texture = ( /** * Creates cube map texture from given 6 `face` texture sources. The - * given options are shared by each each side/face of the cube map. + * given options are shared by each each side/face of the cube map. The + * following options are applied to the cube map directly: + * + * - `filter` + * - `mipmap` + * + * The following options are ignored entirely: + * + * - `target` + * - `image` * * @param gl * @param faces in order: +x,-x,+y,-y,+z,-z @@ -160,15 +213,18 @@ export const cubeMap = ( opts?: Partial ) => { const tex = new Texture(gl, { target: gl.TEXTURE_CUBE_MAP }); - const faceOpts = withoutKeysObj(opts, ["target", "image", "mipmap"]); + const faceOpts = withoutKeysObj(opts, [ + "target", + "image", + "filter", + "mipmap" + ]); for (let i = 0; i < 6; i++) { faceOpts.target = gl.TEXTURE_CUBE_MAP_POSITIVE_X + i; faceOpts.image = faces[i]; tex.configure(faceOpts); } - if (opts.mipmap) { - gl.generateMipmap(tex.target); - } + tex.configure({ filter: opts.filter, mipmap: opts.mipmap }); return tex; }; From d57cb5bedaab3603939b245f8cb3ed979f8834d5 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 9 Apr 2019 10:32:14 +0100 Subject: [PATCH 026/404] feat(examples): add cubemap demo --- examples/webgl-cubemap/.gitignore | 5 + examples/webgl-cubemap/README.md | 24 ++++ .../webgl-cubemap/assets/langholmen2/negx.jpg | Bin 0 -> 99276 bytes .../webgl-cubemap/assets/langholmen2/negy.jpg | Bin 0 -> 52449 bytes .../webgl-cubemap/assets/langholmen2/negz.jpg | Bin 0 -> 96039 bytes .../webgl-cubemap/assets/langholmen2/posx.jpg | Bin 0 -> 101125 bytes .../webgl-cubemap/assets/langholmen2/posy.jpg | Bin 0 -> 132374 bytes .../webgl-cubemap/assets/langholmen2/posz.jpg | Bin 0 -> 101448 bytes .../assets/langholmen2/readme.txt | 13 ++ examples/webgl-cubemap/index.html | 16 +++ examples/webgl-cubemap/package.json | 32 +++++ examples/webgl-cubemap/src/index.ts | 118 ++++++++++++++++++ examples/webgl-cubemap/tsconfig.json | 11 ++ 13 files changed, 219 insertions(+) create mode 100644 examples/webgl-cubemap/.gitignore create mode 100644 examples/webgl-cubemap/README.md create mode 100644 examples/webgl-cubemap/assets/langholmen2/negx.jpg create mode 100644 examples/webgl-cubemap/assets/langholmen2/negy.jpg create mode 100644 examples/webgl-cubemap/assets/langholmen2/negz.jpg create mode 100644 examples/webgl-cubemap/assets/langholmen2/posx.jpg create mode 100644 examples/webgl-cubemap/assets/langholmen2/posy.jpg create mode 100644 examples/webgl-cubemap/assets/langholmen2/posz.jpg create mode 100755 examples/webgl-cubemap/assets/langholmen2/readme.txt create mode 100644 examples/webgl-cubemap/index.html create mode 100644 examples/webgl-cubemap/package.json create mode 100644 examples/webgl-cubemap/src/index.ts create mode 100644 examples/webgl-cubemap/tsconfig.json diff --git a/examples/webgl-cubemap/.gitignore b/examples/webgl-cubemap/.gitignore new file mode 100644 index 0000000000..0c5abcab62 --- /dev/null +++ b/examples/webgl-cubemap/.gitignore @@ -0,0 +1,5 @@ +.cache +out +node_modules +yarn.lock +*.js diff --git a/examples/webgl-cubemap/README.md b/examples/webgl-cubemap/README.md new file mode 100644 index 0000000000..d195e78c10 --- /dev/null +++ b/examples/webgl-cubemap/README.md @@ -0,0 +1,24 @@ +# webgl-cubemap + +WebGL 360Ëš panorama / cube map example. Images by [Emil +Persson](http://www.humus.name/index.php?page=Textures). + +If you want to use your own cube maps, make sure the images are of a +power of 2 or disable mip-mapping in the source code. + +By default, the demo uses WebGL 1. To switch to WebGL2, replace the +`canvasWebGL` component with `canvasWebGL2`. + +[Live demo](http://demo.thi.ng/umbrella/webgl-cubemap/) + +Please refer to the [example build +instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) +on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/webgl-cubemap/assets/langholmen2/negx.jpg b/examples/webgl-cubemap/assets/langholmen2/negx.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4628453e59d8f54ca0d61f1c79a21851226c7363 GIT binary patch literal 99276 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%0A$G># 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 K6yBu2Bmdcefc2pO literal 0 HcmV?d00001 diff --git a/examples/webgl-cubemap/assets/langholmen2/negy.jpg b/examples/webgl-cubemap/assets/langholmen2/negy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..abbdb85db4f9dbed89418fc35827516588d63af9 GIT binary patch literal 52449 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%0DIQ#o2h4htQJEe7Q`gq5LlPV_oy78e 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�py(5&pP627CLq3iC}w!cr7PHits>O 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