├── .eslintrc.json ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── elm.json ├── examples ├── .gitignore ├── Crate.elm ├── Cube.elm ├── FirstPerson.elm ├── Thwomp.elm ├── Triangle.elm ├── elm.json ├── screenshots │ ├── crate.jpg │ ├── cube.jpg │ ├── first-person.jpg │ ├── thwomp.jpg │ └── triangle.jpg └── texture │ ├── thwomp-face.jpg │ ├── thwomp-side.jpg │ └── wood-crate.jpg ├── gh-pages.sh ├── pipeline.png ├── release.sh ├── sandbox ├── IndexedTriangles.elm ├── Intersection.elm └── elm.json └── src ├── Elm └── Kernel │ ├── Texture.js │ └── WebGL.js ├── WebGL.elm └── WebGL ├── Internal.elm ├── Settings.elm ├── Settings ├── Blend.elm ├── DepthTest.elm └── StencilTest.elm └── Texture.elm /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "rules": { 7 | "indent": [ "error", 2, { "SwitchCase": 1 } ], 8 | "space-infix-ops": [ "error" ], 9 | "linebreak-style": [ "error", "unix" ], 10 | "comma-style": [ "error", "last" ], 11 | "brace-style": [ "error", "1tbs", { "allowSingleLine": true } ], 12 | "curly": "error", 13 | "quotes": [ "error", "single" ], 14 | "semi": [ "error", "always" ], 15 | "space-before-function-paren": [ "error", {"anonymous": "always", "named": "never"} ], 16 | "keyword-spacing": [ "error", { "before": true, "after": true } ], 17 | "space-before-blocks": [ "error", "always" ], 18 | "key-spacing": [ "error", { "beforeColon": false, "afterColon": true, "mode": "strict" } ], 19 | "semi-spacing": [ "error", {"before": false, "after": true} ], 20 | "comma-spacing": [ "error", { "before": false, "after": true } ], 21 | "comma-dangle": ["error", "never"], 22 | "eqeqeq": "error" 23 | }, 24 | "globals" : { 25 | "__Texture_SizeError": false, 26 | "__Texture_LoadError": false, 27 | "__Utils_Tuple2": false, 28 | "__WI_enableSetting": false, 29 | "__WI_enableOption": false, 30 | "__0_ENTITY": false, 31 | "__0_TEXTURE": false, 32 | "__VirtualDom_doc": false, 33 | "__VirtualDom_custom": false, 34 | "__Scheduler_binding": false, 35 | "__Scheduler_succeed": false, 36 | "__Scheduler_fail": false, 37 | "F2": false, 38 | "F3": false, 39 | "F4": false, 40 | "F5": false, 41 | "F6": false, 42 | "A2": false, 43 | "A5": false, 44 | "Float32Array": false, 45 | "Int32Array": false, 46 | "Uint16Array": false, 47 | "WeakMap": false 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | index.html 3 | .DS_Store 4 | gh-pages 5 | release 6 | node_modules 7 | elm.js 8 | documentation.json 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to elm-explorations/webgl 2 | 3 | This repository is kept for maintenance and does not accept most feature requests. In particular, [this](https://github.com/elm/package.elm-lang.org/issues/149#issuecomment-171068020) requires that all 4 | changes be PATCH-level, meaning that the API cannot be modified. 5 | 6 | Any contribution should: 7 | * Compile using `elm make` 8 | * Be recognized as a PATCH change using `elm package diff` 9 | * Not introduce mutable variables 10 | * Justify why the change is needed and why it won't break anything already here 11 | * JavaScript code should be validated using `eslint src` 12 | * Elm code should be formatted with `elm-format` 13 | 14 | Documentation improvements are welcome. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, John P Mayer, Jr 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGL for Elm 2 | 3 | [A simple API](https://package.elm-lang.org/packages/elm-explorations/webgl/latest/WebGL) 4 | for rendering with WebGL. This is useful for both 2D and 3D 5 | rendering because it lets you take advantage of hardware acceleration with the 6 | GPU, meaning you can render things more quickly. 7 | 8 | [Here are some examples](https://github.com/elm-explorations/webgl/tree/main/examples) so you can get a feel for the API, but make sure you 9 | read on to learn how WebGL and the GPU really work! 10 | 11 | [![Triangle](https://elm-explorations.github.io/webgl/examples/screenshots/triangle.jpg)](https://elm-explorations.github.io/webgl/examples/triangle.html) 12 | [![Cube](https://elm-explorations.github.io/webgl/examples/screenshots/cube.jpg)](https://elm-explorations.github.io/webgl/examples/cube.html) 13 | [![Crate](https://elm-explorations.github.io/webgl/examples/screenshots/crate.jpg)](https://elm-explorations.github.io/webgl/examples/crate.html) 14 | [![Thwomp](https://elm-explorations.github.io/webgl/examples/screenshots/thwomp.jpg)](https://elm-explorations.github.io/webgl/examples/thwomp.html) 15 | [![FirstPerson](https://elm-explorations.github.io/webgl/examples/screenshots/first-person.jpg)](https://elm-explorations.github.io/webgl/examples/first-person.html) 16 | 17 | ## Understanding WebGL 18 | 19 | To get the most out of this library and out of the GPU, it is best to pair some 20 | examples with a fairly solid understanding of how information flows through the 21 | rendering pipeline. This section gives a high-level overview of the pipeline 22 | and the corresponding terminology. 23 | 24 | At a high-level, there are two general concepts to understand: meshes and 25 | shaders. The details of each of these are crucial to using WebGL effectively. 26 | 27 | ### Meshes 28 | 29 | A mesh is all about triangles. By placing small triangles side-by-side, you can 30 | build up larger 3D shapes. We define each triangle by associating a bunch of 31 | attributes—like position and color—with each corner of the triangle. 32 | 33 | We create and update our meshes on the CPU, so working with a model does not get 34 | any direct benefits from the GPU. Meshes are sent from the CPU to the GPU to be 35 | rendered. This transfer can be quite expensive, so it is best to try to avoid 36 | creating new meshes. 37 | 38 | Some tricks to minimize this include breaking a mesh up into many smaller 39 | pieces that can be transformed independently. For example, if you want to 40 | render a skeleton, each bone could be a separate mesh, so rather than send 41 | a new version of the entire skeleton on every frame, you just send a 42 | transformation for each bone. 43 | 44 | ### Shaders 45 | 46 | A [shader](https://en.wikipedia.org/wiki/Shader) is all turning meshes into 47 | pictures. A shader is a program that runs on the GPU, so it benefits from 48 | lots of parallelization. As a general rule, you want to be doing computation 49 | here rather than on the CPU if possible. 50 | 51 | In Elm, shaders are defined with a language called 52 | [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language). These are programs 53 | that take in small high-level values and do a bunch of rendering based on that. 54 | For example, you can send over a matrix that represents where the camera should 55 | be and all of the meshes loaded onto the GPU will be transformed accordingly. 56 | 57 | ### Combining Meshes and Shaders 58 | 59 | The following diagram illustrates the entire pipeline. Keep reading past the 60 | diagram, all the terms will be explained! 61 | 62 | ![WebGL Pipeline](https://raw.githubusercontent.com/elm-explorations/webgl/main/pipeline.png) 63 | 64 | We start with a mesh. It's a bunch of raw data points that we want to render on 65 | screen. From there, the data flows through two types of shaders: 66 | 67 | - [**Vertex Shaders**](https://en.wikipedia.org/wiki/Shader#Vertex_shaders) — 68 | Our mesh is made up of lots of triangles. Each corner of a triangle is called a 69 | _vertex_. The vertex shader has access to all of the attributes of each vertex, 70 | like position and color, letting us move triangles around or change their color. 71 | 72 | - [**Fragment Shaders**](https://en.wikipedia.org/wiki/Shader#Pixel_shaders) — 73 | Also known as pixel shaders, these shaders are like filters on individual 74 | pixels. They let you work with pixels to add lighting effects or add 75 | postprocessing effects like blur or edge-detection. 76 | 77 | The flow of data between the CPU and each of our shaders is very well defined. 78 | To send information, there are three kinds of specialized variables: 79 | 80 | - **Uniform** — these are global read-only variables that can be used 81 | in both the vertex and fragment shaders. They are defined on the CPU. 82 | 83 | - **Attribute** — these variables represent a particular vertex in our 84 | mesh. The vertex shader takes in these variables to compute some 85 | transformations on each vertex. 86 | 87 | - **Varying** — these are variables you can write in the vertex shader 88 | which then get passed along into the fragment shader, where they are 89 | read-only. This lets you pass information along as you compute things in 90 | your rendering pipeline. 91 | 92 | ## Making the most of the GPU 93 | 94 | A typical mesh may be quite large, with hundreds or thousands of vertices, each 95 | with a potentially large set of attributes. Working with a mesh on the CPU is 96 | expensive for two major reasons: 97 | 98 | 1. The CPU is sequential, so you must work on each vertex one at a time. 99 | On the GPU, you can work with tons of vertices in parallel with a Vertex 100 | Shader, making things much faster. 101 | 102 | 2. Transfering data from CPU to GPU is expensive. Ideally you want to transfer 103 | all of the vertices once and make any updates to the mesh by passing in 104 | uniform variables to the Vertex Shader. Not only is it cheaper to send a 105 | couple matrices to the GPU, but once they get there, the GPU can use them 106 | in parallel. 107 | 108 | This library facilitates this by caching known meshes on the GPU. If you create 109 | a mesh and use it many times, it only gets transfered to the GPU once. Now if 110 | you update that mesh, the new version will need to be loaded onto the GPU 111 | separately. That means it is best to create a fairly general mesh and modify it 112 | with uniform variables in a Vertex Shader. 113 | 114 | ## Writing Shaders 115 | 116 | Shaders are written in a language called 117 | [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language). This is a widely 118 | used language for shaders with [websites](https://www.shadertoy.com) devoted 119 | to sharing creative demos, so you will often be able to use that shader 120 | code directly in Elm. A basic vertex shader could be defined like this: 121 | 122 | ```elm 123 | vertexShader : Shader { position:Vec3, coord:Vec3 } { u | view:Mat4 } { vcoord:Vec2 } 124 | vertexShader = [glsl| 125 | 126 | attribute vec3 position; 127 | attribute vec3 coord; 128 | uniform mat4 view; 129 | varying vec2 vcoord; 130 | 131 | void main () { 132 | gl_Position = view * vec4(position, 1.0); 133 | vcoord = coord.xy; 134 | } 135 | 136 | |] 137 | ``` 138 | 139 | Within the `[glsl| ... |]` block, you just write plain GLSL. Elm is actually 140 | aware of the types of the attributes, uniforms, and varyings coming in and out 141 | of the shader, so the shader block is given a type that enforces this API. The 142 | type of `vertexShader` says that we have two attributes named `position` and 143 | `coord`, one uniform named `view`, and one varying called `vcoord`. This means 144 | Elm's type checker can make sure you are using the shader in a meaningful way, 145 | avoiding a totally blank screen that can happen if your shader has an error in 146 | it. 147 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "elm-explorations/webgl", 4 | "summary": "Functional rendering with WebGL in Elm", 5 | "license": "BSD-3-Clause", 6 | "version": "1.1.3", 7 | "exposed-modules": [ 8 | "WebGL", 9 | "WebGL.Settings", 10 | "WebGL.Settings.Blend", 11 | "WebGL.Settings.DepthTest", 12 | "WebGL.Settings.StencilTest", 13 | "WebGL.Texture" 14 | ], 15 | "elm-version": "0.19.0 <= v < 0.20.0", 16 | "dependencies": { 17 | "elm/core": "1.0.0 <= v < 2.0.0", 18 | "elm/html": "1.0.0 <= v < 2.0.0" 19 | }, 20 | "test-dependencies": {} 21 | } 22 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | elm.js 2 | -------------------------------------------------------------------------------- /examples/Crate.elm: -------------------------------------------------------------------------------- 1 | module Crate exposing (main) 2 | 3 | {- 4 | This example was inspired by https://open.gl/depthstencils 5 | It demonstrates how to use the stencil buffer. 6 | -} 7 | 8 | import Browser 9 | import Browser.Events exposing (onAnimationFrameDelta) 10 | import Html exposing (Html) 11 | import Html.Attributes exposing (height, style, width) 12 | import Math.Matrix4 as Mat4 exposing (Mat4) 13 | import Math.Vector2 as Vec2 exposing (Vec2, vec2) 14 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 15 | import Task 16 | import WebGL exposing (Mesh, Shader, Entity) 17 | import WebGL.Settings.Blend as Blend 18 | import WebGL.Settings.DepthTest as DepthTest 19 | import WebGL.Settings.StencilTest as StencilTest 20 | import WebGL.Texture as Texture exposing (Error, Texture) 21 | import Json.Decode exposing (Value) 22 | 23 | 24 | type alias Model = 25 | { texture : Maybe Texture 26 | , theta : Float 27 | } 28 | 29 | 30 | type Msg 31 | = TextureLoaded (Result Error Texture) 32 | | Animate Float 33 | 34 | 35 | update : Msg -> Model -> ( Model, Cmd Msg ) 36 | update action model = 37 | case action of 38 | TextureLoaded textureResult -> 39 | ( { model | texture = Result.toMaybe textureResult }, Cmd.none ) 40 | 41 | Animate dt -> 42 | ( { model | theta = model.theta + dt / 10000 }, Cmd.none ) 43 | 44 | 45 | init : ( Model, Cmd Msg ) 46 | init = 47 | ( { texture = Nothing, theta = 0 } 48 | , Task.attempt TextureLoaded (Texture.load "texture/wood-crate.jpg") 49 | ) 50 | 51 | 52 | main : Program Value Model Msg 53 | main = 54 | Browser.element 55 | { init = \_ -> init 56 | , view = view 57 | , subscriptions = (\model -> onAnimationFrameDelta Animate) 58 | , update = update 59 | } 60 | 61 | 62 | 63 | -- View 64 | 65 | 66 | view : Model -> Html Msg 67 | view { texture, theta } = 68 | WebGL.toHtmlWith 69 | [ WebGL.alpha True 70 | , WebGL.antialias 71 | , WebGL.depth 1 72 | , WebGL.stencil 0 73 | ] 74 | [ width 400 75 | , height 400 76 | , style "display" "block" 77 | ] 78 | (texture 79 | |> Maybe.map (scene (perspective theta)) 80 | |> Maybe.withDefault [] 81 | ) 82 | 83 | 84 | perspective : Float -> Mat4 85 | perspective angle = 86 | List.foldr Mat4.mul 87 | Mat4.identity 88 | [ Mat4.makePerspective 45 1 0.01 100 89 | , Mat4.makeLookAt (vec3 0 3 8) (vec3 0 0 0) (vec3 0 1 0) 90 | , Mat4.makeRotate (3 * angle) (vec3 0 1 0) 91 | ] 92 | 93 | 94 | scene : Mat4 -> Texture -> List Entity 95 | scene camera texture = 96 | [ WebGL.entity 97 | crateVertex 98 | crateFragment 99 | crateMesh 100 | { texture = texture 101 | , perspective = camera 102 | } 103 | , WebGL.entityWith 104 | [ DepthTest.less 105 | { write = False 106 | , near = 0 107 | , far = 1 108 | } 109 | , StencilTest.test 110 | { ref = 1 111 | , mask = 0xFF 112 | , test = StencilTest.always 113 | , fail = StencilTest.keep 114 | , zfail = StencilTest.keep 115 | , zpass = StencilTest.replace 116 | , writeMask = 0xFF 117 | } 118 | ] 119 | floorVertex 120 | floorFragment 121 | floorMesh 122 | { texture = texture 123 | , perspective = camera 124 | } 125 | , WebGL.entityWith 126 | [ StencilTest.test 127 | { ref = 1 128 | , mask = 0xFF 129 | , test = StencilTest.equal 130 | , fail = StencilTest.keep 131 | , zfail = StencilTest.keep 132 | , zpass = StencilTest.keep 133 | , writeMask = 0 134 | } 135 | , DepthTest.default 136 | , Blend.custom 137 | { r = 0 138 | , g = 0 139 | , b = 0 140 | , a = 0.5 141 | , color = Blend.customAdd Blend.constantAlpha Blend.zero 142 | , alpha = Blend.customAdd Blend.one Blend.zero 143 | } 144 | ] 145 | crateVertex 146 | crateFragment 147 | crateMesh 148 | { texture = texture 149 | , perspective = 150 | Mat4.mul camera (Mat4.makeScale (vec3 1 -1 1)) 151 | } 152 | ] 153 | 154 | 155 | 156 | -- Meshes 157 | 158 | 159 | type alias Vertex = 160 | { position : Vec3 161 | , coord : Vec2 162 | } 163 | 164 | 165 | crateMesh : Mesh Vertex 166 | crateMesh = 167 | [ ( 0, 0 ), ( 90, 0 ), ( 180, 0 ), ( 270, 0 ), ( 0, 90 ), ( 0, 270 ) ] 168 | |> List.concatMap rotatedFace 169 | |> WebGL.triangles 170 | 171 | 172 | rotatedFace : ( Float, Float ) -> List ( Vertex, Vertex, Vertex ) 173 | rotatedFace ( angleXZ, angleYZ ) = 174 | let 175 | transformMat = 176 | List.foldr Mat4.mul 177 | Mat4.identity 178 | [ Mat4.makeTranslate (vec3 0 1 0) 179 | , Mat4.makeRotate (degrees angleXZ) Vec3.j 180 | , Mat4.makeRotate (degrees angleYZ) Vec3.i 181 | , Mat4.makeTranslate (vec3 0 0 1) 182 | ] 183 | 184 | transform vertex = 185 | { vertex 186 | | position = 187 | Mat4.transform 188 | transformMat 189 | vertex.position 190 | } 191 | 192 | transformTriangle ( a, b, c ) = 193 | ( transform a, transform b, transform c ) 194 | in 195 | List.map transformTriangle square 196 | 197 | 198 | square : List ( Vertex, Vertex, Vertex ) 199 | square = 200 | let 201 | topLeft = 202 | { position = vec3 -1 1 0, coord = vec2 0 1 } 203 | 204 | topRight = 205 | { position = vec3 1 1 0, coord = vec2 1 1 } 206 | 207 | bottomLeft = 208 | { position = vec3 -1 -1 0, coord = vec2 0 0 } 209 | 210 | bottomRight = 211 | { position = vec3 1 -1 0, coord = vec2 1 0 } 212 | in 213 | [ ( topLeft, topRight, bottomLeft ) 214 | , ( bottomLeft, topRight, bottomRight ) 215 | ] 216 | 217 | 218 | floorMesh : Mesh { position : Vec3 } 219 | floorMesh = 220 | let 221 | topLeft = 222 | { position = vec3 -2 0 -2 } 223 | 224 | topRight = 225 | { position = vec3 2 0 -2 } 226 | 227 | bottomLeft = 228 | { position = vec3 -2 0 2 } 229 | 230 | bottomRight = 231 | { position = vec3 2 0 2 } 232 | in 233 | WebGL.triangles 234 | [ ( topLeft, topRight, bottomLeft ) 235 | , ( bottomLeft, topRight, bottomRight ) 236 | ] 237 | 238 | 239 | 240 | -- Shaders 241 | 242 | 243 | type alias Uniforms = 244 | { perspective : Mat4 245 | , texture : Texture 246 | } 247 | 248 | 249 | crateVertex : Shader Vertex Uniforms { vcoord : Vec2 } 250 | crateVertex = 251 | [glsl| 252 | 253 | attribute vec3 position; 254 | attribute vec2 coord; 255 | uniform mat4 perspective; 256 | varying vec2 vcoord; 257 | 258 | void main () { 259 | gl_Position = perspective * vec4(position, 1.0); 260 | vcoord = coord; 261 | } 262 | 263 | |] 264 | 265 | 266 | crateFragment : Shader {} { u | texture : Texture } { vcoord : Vec2 } 267 | crateFragment = 268 | [glsl| 269 | 270 | precision mediump float; 271 | uniform sampler2D texture; 272 | varying vec2 vcoord; 273 | 274 | void main () { 275 | gl_FragColor = texture2D(texture, vcoord); 276 | } 277 | 278 | |] 279 | 280 | 281 | floorVertex : Shader { position : Vec3 } Uniforms {} 282 | floorVertex = 283 | [glsl| 284 | 285 | attribute vec3 position; 286 | uniform mat4 perspective; 287 | 288 | void main () { 289 | gl_Position = perspective * vec4(position, 1.0); 290 | } 291 | 292 | |] 293 | 294 | 295 | floorFragment : Shader attributes Uniforms {} 296 | floorFragment = 297 | [glsl| 298 | 299 | precision mediump float; 300 | 301 | void main () { 302 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 303 | } 304 | 305 | |] 306 | -------------------------------------------------------------------------------- /examples/Cube.elm: -------------------------------------------------------------------------------- 1 | module Cube exposing (main) 2 | 3 | {- 4 | Rotating cube with colored sides. 5 | -} 6 | 7 | import Browser 8 | import Browser.Events exposing (onAnimationFrameDelta) 9 | import Html exposing (Html) 10 | import Html.Attributes exposing (height, style, width) 11 | import Math.Matrix4 as Mat4 exposing (Mat4) 12 | import Math.Vector3 as Vec3 exposing (vec3, Vec3) 13 | import WebGL exposing (Mesh, Shader) 14 | import Json.Decode exposing (Value) 15 | 16 | 17 | main : Program Value Float Float 18 | main = 19 | Browser.element 20 | { init = \_ -> ( 0, Cmd.none ) 21 | , view = view 22 | , subscriptions = (\_ -> onAnimationFrameDelta Basics.identity) 23 | , update = (\dt theta -> ( theta + dt / 5000, Cmd.none )) 24 | } 25 | 26 | 27 | view : Float -> Html Float 28 | view theta = 29 | WebGL.toHtml 30 | [ width 400 31 | , height 400 32 | , style "display" "block" 33 | ] 34 | [ WebGL.entity 35 | vertexShader 36 | fragmentShader 37 | cubeMesh 38 | (uniforms theta) 39 | ] 40 | 41 | 42 | type alias Uniforms = 43 | { rotation : Mat4 44 | , perspective : Mat4 45 | , camera : Mat4 46 | , shade : Float 47 | } 48 | 49 | 50 | uniforms : Float -> Uniforms 51 | uniforms theta = 52 | { rotation = 53 | Mat4.mul 54 | (Mat4.makeRotate (3 * theta) (vec3 0 1 0)) 55 | (Mat4.makeRotate (2 * theta) (vec3 1 0 0)) 56 | , perspective = Mat4.makePerspective 45 1 0.01 100 57 | , camera = Mat4.makeLookAt (vec3 0 0 5) (vec3 0 0 0) (vec3 0 1 0) 58 | , shade = 0.8 59 | } 60 | 61 | 62 | 63 | -- Mesh 64 | 65 | 66 | type alias Vertex = 67 | { color : Vec3 68 | , position : Vec3 69 | } 70 | 71 | 72 | cubeMesh : Mesh Vertex 73 | cubeMesh = 74 | let 75 | rft = 76 | vec3 1 1 1 77 | 78 | lft = 79 | vec3 -1 1 1 80 | 81 | lbt = 82 | vec3 -1 -1 1 83 | 84 | rbt = 85 | vec3 1 -1 1 86 | 87 | rbb = 88 | vec3 1 -1 -1 89 | 90 | rfb = 91 | vec3 1 1 -1 92 | 93 | lfb = 94 | vec3 -1 1 -1 95 | 96 | lbb = 97 | vec3 -1 -1 -1 98 | in 99 | [ face (vec3 115 210 22) rft rfb rbb rbt -- green 100 | , face (vec3 52 101 164) rft rfb lfb lft -- blue 101 | , face (vec3 237 212 0) rft lft lbt rbt -- yellow 102 | , face (vec3 204 0 0) rfb lfb lbb rbb -- red 103 | , face (vec3 117 80 123) lft lfb lbb lbt -- purple 104 | , face (vec3 245 121 0) rbt rbb lbb lbt -- orange 105 | ] 106 | |> List.concat 107 | |> WebGL.triangles 108 | 109 | 110 | face : Vec3 -> Vec3 -> Vec3 -> Vec3 -> Vec3 -> List ( Vertex, Vertex, Vertex ) 111 | face color a b c d = 112 | let 113 | vertex position = 114 | Vertex (Vec3.scale (1 / 255) color) position 115 | in 116 | [ ( vertex a, vertex b, vertex c ) 117 | , ( vertex c, vertex d, vertex a ) 118 | ] 119 | 120 | 121 | 122 | -- Shaders 123 | 124 | 125 | vertexShader : Shader Vertex Uniforms { vcolor : Vec3 } 126 | vertexShader = 127 | [glsl| 128 | 129 | attribute vec3 position; 130 | attribute vec3 color; 131 | uniform mat4 perspective; 132 | uniform mat4 camera; 133 | uniform mat4 rotation; 134 | varying vec3 vcolor; 135 | void main () { 136 | gl_Position = perspective * camera * rotation * vec4(position, 1.0); 137 | vcolor = color; 138 | } 139 | 140 | |] 141 | 142 | 143 | fragmentShader : Shader {} Uniforms { vcolor : Vec3 } 144 | fragmentShader = 145 | [glsl| 146 | 147 | precision mediump float; 148 | uniform float shade; 149 | varying vec3 vcolor; 150 | void main () { 151 | gl_FragColor = shade * vec4(vcolor, 1.0); 152 | } 153 | 154 | |] 155 | -------------------------------------------------------------------------------- /examples/FirstPerson.elm: -------------------------------------------------------------------------------- 1 | module FirstPerson exposing (main) 2 | 3 | {- 4 | Try adding the ability to crouch or to land on top of the crate. 5 | -} 6 | 7 | import Browser 8 | import Browser.Events exposing (onAnimationFrameDelta, onKeyDown, onKeyUp, onResize) 9 | import Browser.Dom exposing (getViewport, Viewport) 10 | import Html exposing (Html, text, div) 11 | import Html.Attributes exposing (width, height, style) 12 | import Html.Events exposing (keyCode) 13 | import Math.Matrix4 as Mat4 exposing (Mat4) 14 | import Math.Vector2 as Vec2 exposing (Vec2, vec2) 15 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 16 | import Task exposing (Task) 17 | import WebGL exposing (Mesh, Shader, Entity) 18 | import WebGL.Texture as Texture exposing (Texture, Error) 19 | import Json.Decode as Decode exposing (Decoder, Value) 20 | 21 | 22 | type alias Model = 23 | { texture : Maybe Texture 24 | , keys : Keys 25 | , size : { width : Float, height : Float } 26 | , person : Person 27 | } 28 | 29 | 30 | type alias Person = 31 | { position : Vec3 32 | , velocity : Vec3 33 | } 34 | 35 | 36 | type Msg 37 | = TextureLoaded (Result Error Texture) 38 | | KeyChange Bool Int 39 | | Animate Float 40 | | GetViewport Viewport 41 | | Resize Int Int 42 | 43 | 44 | type alias Keys = 45 | { left : Bool 46 | , right : Bool 47 | , up : Bool 48 | , down : Bool 49 | , space : Bool 50 | } 51 | 52 | 53 | main : Program Value Model Msg 54 | main = 55 | Browser.element 56 | { init = \_ -> init 57 | , view = view 58 | , subscriptions = subscriptions 59 | , update = update 60 | } 61 | 62 | 63 | eyeLevel : Float 64 | eyeLevel = 65 | 2 66 | 67 | 68 | init : ( Model, Cmd Msg ) 69 | init = 70 | ( { texture = Nothing 71 | , person = Person (vec3 0 eyeLevel -10) (vec3 0 0 0) 72 | , keys = Keys False False False False False 73 | , size = { width = 0, height = 0 } 74 | } 75 | , Cmd.batch 76 | [ Task.attempt TextureLoaded (Texture.load "texture/wood-crate.jpg") 77 | , Task.perform GetViewport getViewport 78 | ] 79 | ) 80 | 81 | 82 | subscriptions : Model -> Sub Msg 83 | subscriptions _ = 84 | Sub.batch 85 | [ onAnimationFrameDelta Animate 86 | , onKeyDown (Decode.map (KeyChange True) keyCode) 87 | , onKeyUp (Decode.map (KeyChange False) keyCode) 88 | , onResize Resize 89 | ] 90 | 91 | 92 | update : Msg -> Model -> ( Model, Cmd Msg ) 93 | update action model = 94 | case action of 95 | TextureLoaded textureResult -> 96 | ( { model | texture = Result.toMaybe textureResult }, Cmd.none ) 97 | 98 | KeyChange on code -> 99 | ( { model | keys = keyFunc on code model.keys }, Cmd.none ) 100 | 101 | GetViewport { viewport } -> 102 | ( { model 103 | | size = 104 | { width = viewport.width 105 | , height = viewport.height 106 | } 107 | } 108 | , Cmd.none 109 | ) 110 | 111 | Resize width height -> 112 | ( { model 113 | | size = 114 | { width = toFloat width 115 | , height = toFloat height 116 | } 117 | } 118 | , Cmd.none 119 | ) 120 | 121 | Animate dt -> 122 | ( { model 123 | | person = 124 | model.person 125 | |> move model.keys 126 | |> gravity (dt / 500) 127 | |> physics (dt / 500) 128 | } 129 | , Cmd.none 130 | ) 131 | 132 | 133 | keyFunc : Bool -> Int -> Keys -> Keys 134 | keyFunc on keyCode keys = 135 | case keyCode of 136 | 32 -> 137 | { keys | space = on } 138 | 139 | 37 -> 140 | { keys | left = on } 141 | 142 | 39 -> 143 | { keys | right = on } 144 | 145 | 38 -> 146 | { keys | up = on } 147 | 148 | 40 -> 149 | { keys | down = on } 150 | 151 | _ -> 152 | keys 153 | 154 | 155 | move : Keys -> Person -> Person 156 | move { left, right, up, down, space } person = 157 | let 158 | direction a b = 159 | if a == b then 160 | 0 161 | 162 | else if a then 163 | 1 164 | 165 | else 166 | -1 167 | 168 | vy = 169 | if space then 170 | 2 171 | 172 | else 173 | Vec3.getY person.velocity 174 | in 175 | if Vec3.getY person.position <= eyeLevel then 176 | { person 177 | | velocity = 178 | vec3 (direction left right) vy (direction up down) 179 | } 180 | 181 | else 182 | person 183 | 184 | 185 | physics : Float -> Person -> Person 186 | physics dt person = 187 | let 188 | position = 189 | Vec3.add person.position (Vec3.scale dt person.velocity) 190 | in 191 | { person 192 | | position = 193 | if Vec3.getY position < eyeLevel then 194 | Vec3.setY eyeLevel position 195 | 196 | else 197 | position 198 | } 199 | 200 | 201 | gravity : Float -> Person -> Person 202 | gravity dt person = 203 | if Vec3.getY person.position > eyeLevel then 204 | { person 205 | | velocity = 206 | Vec3.setY 207 | (Vec3.getY person.velocity - 2 * dt) 208 | person.velocity 209 | } 210 | 211 | else 212 | person 213 | 214 | 215 | 216 | -- View 217 | 218 | 219 | view : Model -> Html Msg 220 | view { size, person, texture } = 221 | div 222 | [ style "width" (String.fromFloat size.width ++ "px") 223 | , style "height" (String.fromFloat size.height ++ "px") 224 | , style "position" "absolute" 225 | , style "left" "0" 226 | , style "top" "0" 227 | ] 228 | [ WebGL.toHtmlWith 229 | [ WebGL.depth 1 230 | ] 231 | [ width (round size.width) 232 | , height (round size.height) 233 | , style "display" "block" 234 | ] 235 | (texture 236 | |> Maybe.map (scene size person) 237 | |> Maybe.withDefault [] 238 | ) 239 | , div 240 | [ style "position" "absolute" 241 | , style "font-family" "monospace" 242 | , style "color" "white" 243 | , style "text-align" "center" 244 | , style "left" "20px" 245 | , style "right" "20px" 246 | , style "top" "20px" 247 | ] 248 | [ text message ] 249 | ] 250 | 251 | 252 | message : String 253 | message = 254 | "Walk around with a first person perspective.\n" 255 | ++ "Arrows keys to move, space bar to jump." 256 | 257 | 258 | scene : { width : Float, height : Float } -> Person -> Texture -> List Entity 259 | scene { width, height } person texture = 260 | let 261 | perspective = 262 | Mat4.mul 263 | (Mat4.makePerspective 45 (width / height) 0.01 100) 264 | (Mat4.makeLookAt person.position (Vec3.add person.position Vec3.k) Vec3.j) 265 | in 266 | [ WebGL.entity 267 | vertexShader 268 | fragmentShader 269 | crate 270 | { texture = texture 271 | , perspective = perspective 272 | } 273 | ] 274 | 275 | 276 | 277 | -- Mesh 278 | 279 | 280 | type alias Vertex = 281 | { position : Vec3 282 | , coord : Vec2 283 | } 284 | 285 | 286 | crate : Mesh Vertex 287 | crate = 288 | [ ( 0, 0 ), ( 90, 0 ), ( 180, 0 ), ( 270, 0 ), ( 0, 90 ), ( 0, -90 ) ] 289 | |> List.concatMap rotatedSquare 290 | |> WebGL.triangles 291 | 292 | 293 | rotatedSquare : ( Float, Float ) -> List ( Vertex, Vertex, Vertex ) 294 | rotatedSquare ( angleXZ, angleYZ ) = 295 | let 296 | transformMat = 297 | Mat4.mul 298 | (Mat4.makeRotate (degrees angleXZ) Vec3.j) 299 | (Mat4.makeRotate (degrees angleYZ) Vec3.i) 300 | 301 | transform vertex = 302 | { vertex 303 | | position = 304 | Mat4.transform transformMat vertex.position 305 | } 306 | 307 | transformTriangle ( a, b, c ) = 308 | ( transform a, transform b, transform c ) 309 | in 310 | List.map transformTriangle square 311 | 312 | 313 | square : List ( Vertex, Vertex, Vertex ) 314 | square = 315 | let 316 | topLeft = 317 | Vertex (vec3 -1 1 1) (vec2 0 1) 318 | 319 | topRight = 320 | Vertex (vec3 1 1 1) (vec2 1 1) 321 | 322 | bottomLeft = 323 | Vertex (vec3 -1 -1 1) (vec2 0 0) 324 | 325 | bottomRight = 326 | Vertex (vec3 1 -1 1) (vec2 1 0) 327 | in 328 | [ ( topLeft, topRight, bottomLeft ) 329 | , ( bottomLeft, topRight, bottomRight ) 330 | ] 331 | 332 | 333 | 334 | -- Shaders 335 | 336 | 337 | type alias Uniforms = 338 | { texture : Texture 339 | , perspective : Mat4 340 | } 341 | 342 | 343 | vertexShader : Shader Vertex Uniforms { vcoord : Vec2 } 344 | vertexShader = 345 | [glsl| 346 | 347 | attribute vec3 position; 348 | attribute vec2 coord; 349 | uniform mat4 perspective; 350 | varying vec2 vcoord; 351 | 352 | void main () { 353 | gl_Position = perspective * vec4(position, 1.0); 354 | vcoord = coord; 355 | } 356 | 357 | |] 358 | 359 | 360 | fragmentShader : Shader {} Uniforms { vcoord : Vec2 } 361 | fragmentShader = 362 | [glsl| 363 | 364 | precision mediump float; 365 | uniform sampler2D texture; 366 | varying vec2 vcoord; 367 | 368 | void main () { 369 | gl_FragColor = texture2D(texture, vcoord); 370 | } 371 | 372 | |] 373 | -------------------------------------------------------------------------------- /examples/Thwomp.elm: -------------------------------------------------------------------------------- 1 | module Thwomp exposing (main) 2 | 3 | {- 4 | Thanks to The PaperNES Guy for the texture: 5 | https://the-papernes-guy.deviantart.com/art/Thwomps-Thwomps-Thwomps-186879685 6 | -} 7 | 8 | import Browser 9 | import Browser.Dom exposing (getViewport, Viewport) 10 | import Browser.Events exposing (onMouseMove, onResize) 11 | import Html exposing (Html, text) 12 | import Html.Attributes exposing (height, style, width) 13 | import Math.Matrix4 as Mat4 exposing (Mat4) 14 | import Math.Vector2 as Vec2 exposing (Vec2, vec2) 15 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 16 | import Task exposing (Task) 17 | import WebGL exposing (Mesh, Shader, Entity) 18 | import WebGL.Texture as Texture exposing (Texture, defaultOptions, Error) 19 | import Json.Decode as Decode exposing (Decoder, Value) 20 | 21 | 22 | type alias Model = 23 | { size : { width : Float, height : Float } 24 | , position : { x : Float, y : Float } 25 | , textures : Maybe ( Texture, Texture ) 26 | } 27 | 28 | 29 | type Action 30 | = TexturesError Error 31 | | TexturesLoaded ( Texture, Texture ) 32 | | GetViewport Viewport 33 | | Resize Int Int 34 | | MouseMove { x : Float, y : Float } 35 | 36 | 37 | main : Program Value Model Action 38 | main = 39 | Browser.element 40 | { init = \_ -> init 41 | , view = view 42 | , subscriptions = subscriptions 43 | , update = update 44 | } 45 | 46 | 47 | init : ( Model, Cmd Action ) 48 | init = 49 | ( { size = { width = 0, height = 0 } 50 | , position = { x = 0, y = 0 } 51 | , textures = Nothing 52 | } 53 | , Cmd.batch 54 | [ Task.perform GetViewport getViewport 55 | , fetchTextures 56 | ] 57 | ) 58 | 59 | 60 | subscriptions : Model -> Sub Action 61 | subscriptions _ = 62 | Sub.batch 63 | [ onResize Resize 64 | , onMouseMove mousePosition 65 | ] 66 | 67 | 68 | mousePosition : Decoder Action 69 | mousePosition = 70 | Decode.map2 (\x y -> MouseMove { x = x, y = y }) 71 | (Decode.field "pageX" Decode.float) 72 | (Decode.field "pageY" Decode.float) 73 | 74 | 75 | update : Action -> Model -> ( Model, Cmd Action ) 76 | update action model = 77 | case action of 78 | TexturesError err -> 79 | ( model, Cmd.none ) 80 | 81 | TexturesLoaded textures -> 82 | ( { model | textures = Just textures }, Cmd.none ) 83 | 84 | GetViewport { viewport } -> 85 | ( { model | size = { width = viewport.width, height = viewport.height } }, Cmd.none ) 86 | 87 | Resize width height -> 88 | ( { model | size = { width = toFloat width, height = toFloat height } }, Cmd.none ) 89 | 90 | MouseMove position -> 91 | ( { model | position = position }, Cmd.none ) 92 | 93 | 94 | fetchTextures : Cmd Action 95 | fetchTextures = 96 | [ "texture/thwomp-face.jpg" 97 | , "texture/thwomp-side.jpg" 98 | ] 99 | |> List.map 100 | (Texture.loadWith 101 | { defaultOptions 102 | | magnify = Texture.nearest 103 | , minify = Texture.nearest 104 | } 105 | ) 106 | |> Task.sequence 107 | |> Task.andThen 108 | (\textures -> 109 | case textures of 110 | face :: side :: _ -> 111 | Task.succeed ( face, side ) 112 | 113 | _ -> 114 | Task.fail Texture.LoadError 115 | ) 116 | |> Task.attempt 117 | (\result -> 118 | case result of 119 | Err error -> 120 | TexturesError error 121 | 122 | Ok textures -> 123 | TexturesLoaded textures 124 | ) 125 | 126 | 127 | 128 | -- Meshes 129 | 130 | 131 | type alias Vertex = 132 | { position : Vec3 133 | , coord : Vec2 134 | } 135 | 136 | 137 | faceMesh : Mesh Vertex 138 | faceMesh = 139 | WebGL.triangles square 140 | 141 | 142 | sidesMesh : Mesh Vertex 143 | sidesMesh = 144 | [ ( 90, 0 ), ( 180, 0 ), ( 270, 0 ), ( 0, 90 ), ( 0, 270 ) ] 145 | |> List.concatMap rotatedSquare 146 | |> WebGL.triangles 147 | 148 | 149 | rotatedSquare : ( Float, Float ) -> List ( Vertex, Vertex, Vertex ) 150 | rotatedSquare ( angleXZ, angleYZ ) = 151 | let 152 | transformMat = 153 | Mat4.mul 154 | (Mat4.makeRotate (degrees angleXZ) Vec3.j) 155 | (Mat4.makeRotate (degrees angleYZ) Vec3.i) 156 | 157 | transform vertex = 158 | { vertex 159 | | position = 160 | Mat4.transform transformMat vertex.position 161 | } 162 | 163 | transformTriangle ( a, b, c ) = 164 | ( transform a, transform b, transform c ) 165 | in 166 | List.map transformTriangle square 167 | 168 | 169 | square : List ( Vertex, Vertex, Vertex ) 170 | square = 171 | let 172 | topLeft = 173 | Vertex (vec3 -1 1 1) (vec2 0 1) 174 | 175 | topRight = 176 | Vertex (vec3 1 1 1) (vec2 1 1) 177 | 178 | bottomLeft = 179 | Vertex (vec3 -1 -1 1) (vec2 0 0) 180 | 181 | bottomRight = 182 | Vertex (vec3 1 -1 1) (vec2 1 0) 183 | in 184 | [ ( topLeft, topRight, bottomLeft ) 185 | , ( bottomLeft, topRight, bottomRight ) 186 | ] 187 | 188 | 189 | 190 | -- VIEW 191 | 192 | 193 | view : Model -> Html Action 194 | view { textures, size, position } = 195 | case textures of 196 | Just ( faceTexture, sideTexture ) -> 197 | WebGL.toHtml 198 | [ width (round size.width) 199 | , height (round size.height) 200 | , style "display" "block" 201 | , style "position" "absolute" 202 | , style "left" "0" 203 | , style "top" "0" 204 | ] 205 | [ toEntity faceMesh faceTexture size position 206 | , toEntity sidesMesh sideTexture size position 207 | ] 208 | 209 | Nothing -> 210 | text "Loading textures..." 211 | 212 | 213 | toEntity : Mesh Vertex -> Texture -> { width : Float, height : Float } -> { x : Float, y : Float } -> Entity 214 | toEntity mesh texture { width, height } { x, y } = 215 | WebGL.entity 216 | vertexShader 217 | fragmentShader 218 | mesh 219 | { texture = texture 220 | , perspective = perspective width height x y 221 | } 222 | 223 | 224 | perspective : Float -> Float -> Float -> Float -> Mat4 225 | perspective width height x y = 226 | let 227 | eye = 228 | vec3 (0.5 - x / width) -(0.5 - y / height) 1 229 | |> Vec3.normalize 230 | |> Vec3.scale 6 231 | in 232 | Mat4.mul 233 | (Mat4.makePerspective 45 (width / height) 0.01 100) 234 | (Mat4.makeLookAt eye (vec3 0 0 0) Vec3.j) 235 | 236 | 237 | 238 | -- SHADERS 239 | 240 | 241 | type alias Uniforms = 242 | { perspective : Mat4 243 | , texture : Texture 244 | } 245 | 246 | 247 | vertexShader : Shader Vertex Uniforms { vcoord : Vec2 } 248 | vertexShader = 249 | [glsl| 250 | 251 | attribute vec3 position; 252 | attribute vec2 coord; 253 | uniform mat4 perspective; 254 | varying vec2 vcoord; 255 | 256 | void main () { 257 | gl_Position = perspective * vec4(position, 1.0); 258 | vcoord = coord.xy; 259 | } 260 | 261 | |] 262 | 263 | 264 | fragmentShader : Shader {} Uniforms { vcoord : Vec2 } 265 | fragmentShader = 266 | [glsl| 267 | 268 | precision mediump float; 269 | uniform sampler2D texture; 270 | varying vec2 vcoord; 271 | 272 | void main () { 273 | gl_FragColor = texture2D(texture, vcoord); 274 | } 275 | 276 | |] 277 | -------------------------------------------------------------------------------- /examples/Triangle.elm: -------------------------------------------------------------------------------- 1 | module Triangle exposing (main) 2 | 3 | {- 4 | Rotating triangle, that is a "hello world" of the WebGL 5 | -} 6 | 7 | import Browser 8 | import Browser.Events exposing (onAnimationFrameDelta) 9 | import Html exposing (Html) 10 | import Html.Attributes exposing (width, height, style) 11 | import WebGL exposing (Mesh, Shader) 12 | import Math.Matrix4 as Mat4 exposing (Mat4) 13 | import Math.Vector3 as Vec3 exposing (vec3, Vec3) 14 | import Json.Decode exposing (Value) 15 | 16 | 17 | main : Program Value Float Float 18 | main = 19 | Browser.element 20 | { init = \_ -> ( 0, Cmd.none ) 21 | , view = view 22 | , subscriptions = (\_ -> onAnimationFrameDelta Basics.identity) 23 | , update = (\elapsed currentTime -> ( elapsed + currentTime, Cmd.none )) 24 | } 25 | 26 | 27 | view : Float -> Html msg 28 | view t = 29 | WebGL.toHtml 30 | [ width 400 31 | , height 400 32 | , style "display" "block" 33 | ] 34 | [ WebGL.entity 35 | vertexShader 36 | fragmentShader 37 | mesh 38 | { perspective = perspective (t / 1000) } 39 | ] 40 | 41 | 42 | perspective : Float -> Mat4 43 | perspective t = 44 | Mat4.mul 45 | (Mat4.makePerspective 45 1 0.01 100) 46 | (Mat4.makeLookAt (vec3 (4 * cos t) 0 (4 * sin t)) (vec3 0 0 0) (vec3 0 1 0)) 47 | 48 | 49 | 50 | -- Mesh 51 | 52 | 53 | type alias Vertex = 54 | { position : Vec3 55 | , color : Vec3 56 | } 57 | 58 | 59 | mesh : Mesh Vertex 60 | mesh = 61 | WebGL.triangles 62 | [ ( Vertex (vec3 0 0 0) (vec3 1 0 0) 63 | , Vertex (vec3 1 1 0) (vec3 0 1 0) 64 | , Vertex (vec3 1 -1 0) (vec3 0 0 1) 65 | ) 66 | ] 67 | 68 | 69 | 70 | -- Shaders 71 | 72 | 73 | type alias Uniforms = 74 | { perspective : Mat4 } 75 | 76 | 77 | vertexShader : Shader Vertex Uniforms { vcolor : Vec3 } 78 | vertexShader = 79 | [glsl| 80 | 81 | attribute vec3 position; 82 | attribute vec3 color; 83 | uniform mat4 perspective; 84 | varying vec3 vcolor; 85 | 86 | void main () { 87 | gl_Position = perspective * vec4(position, 1.0); 88 | vcolor = color; 89 | } 90 | 91 | |] 92 | 93 | 94 | fragmentShader : Shader {} Uniforms { vcolor : Vec3 } 95 | fragmentShader = 96 | [glsl| 97 | 98 | precision mediump float; 99 | varying vec3 vcolor; 100 | 101 | void main () { 102 | gl_FragColor = vec4(vcolor, 1.0); 103 | } 104 | 105 | |] 106 | -------------------------------------------------------------------------------- /examples/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "." 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/browser": "1.0.0", 10 | "elm/core": "1.0.0", 11 | "elm/html": "1.0.0", 12 | "elm/json": "1.0.0", 13 | "elm-explorations/linear-algebra": "1.0.0", 14 | "elm-explorations/webgl": "1.1.1" 15 | }, 16 | "indirect": { 17 | "elm/time": "1.0.0", 18 | "elm/url": "1.0.0", 19 | "elm/virtual-dom": "1.0.0" 20 | } 21 | }, 22 | "test-dependencies": { 23 | "direct": {}, 24 | "indirect": {} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/screenshots/crate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/examples/screenshots/crate.jpg -------------------------------------------------------------------------------- /examples/screenshots/cube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/examples/screenshots/cube.jpg -------------------------------------------------------------------------------- /examples/screenshots/first-person.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/examples/screenshots/first-person.jpg -------------------------------------------------------------------------------- /examples/screenshots/thwomp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/examples/screenshots/thwomp.jpg -------------------------------------------------------------------------------- /examples/screenshots/triangle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/examples/screenshots/triangle.jpg -------------------------------------------------------------------------------- /examples/texture/thwomp-face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/examples/texture/thwomp-face.jpg -------------------------------------------------------------------------------- /examples/texture/thwomp-side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/examples/texture/thwomp-side.jpg -------------------------------------------------------------------------------- /examples/texture/wood-crate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/examples/texture/wood-crate.jpg -------------------------------------------------------------------------------- /gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rm -rf gh-pages || exit 0; 5 | 6 | mkdir -p gh-pages/examples 7 | 8 | # compile JS using Elm 9 | cd examples 10 | for i in crate cube first-person thwomp triangle; do 11 | elm make $i.elm --output ../gh-pages/examples/$i.html 12 | done 13 | 14 | # copy the textures 15 | cp -R texture ../gh-pages/examples 16 | cp -R screenshots ../gh-pages/examples 17 | 18 | # init branch and commit 19 | cd ../gh-pages 20 | git init 21 | git add . 22 | git commit -m "Deploying to GH Pages" 23 | git push --force "git@github.com:elm-explorations/webgl.git" master:gh-pages 24 | -------------------------------------------------------------------------------- /pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-explorations/webgl/cb1a95d002ff89361170c6a60695bafc734ebd65/pipeline.png -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | rm -rf release || exit 0; 5 | 6 | elm bump 7 | 8 | version=$(grep -m1 version elm.json | awk -F: '{ print $2 }' | sed 's/[", ]//g') 9 | 10 | git commit -a -m "Bump to $version" 11 | git push 12 | 13 | cleanup="examples sandbox gh-pages.sh pipeline.png CONTRIBUTING.md .eslintrc.json release.sh" 14 | last_commit=$(git rev-parse HEAD) 15 | 16 | git clone --reference . git@github.com:elm-explorations/webgl.git release 17 | ( 18 | cd release 19 | git checkout $last_commit 20 | git rm -rf --ignore-unmatch $cleanup 21 | git commit -m "Cleanup and release $version" 22 | git tag -a $version -m "Release $version" 23 | git push origin $version 24 | elm publish 25 | ) 26 | -------------------------------------------------------------------------------- /sandbox/IndexedTriangles.elm: -------------------------------------------------------------------------------- 1 | module IndexedTriangles exposing (main) 2 | 3 | {- 4 | This module fills the canvas with many tiny triangles. 5 | 6 | It is used to test WebGL.indexedTriangles 7 | 8 | With OES_element_index_uint extension we can support 9 | element indices that are bigger than 65536 10 | -} 11 | 12 | import Html exposing (Html) 13 | import Html.Attributes exposing (height, style, width) 14 | import Math.Vector2 exposing (Vec2, vec2) 15 | import Math.Vector3 exposing (Vec3, vec3) 16 | import WebGL exposing (Mesh, Shader) 17 | 18 | 19 | main : Html msg 20 | main = 21 | WebGL.toHtml 22 | [ width 400 23 | , height 400 24 | , style "display" "block" 25 | ] 26 | [ WebGL.entity 27 | vertexShader 28 | fragmentShader 29 | mesh 30 | {} 31 | ] 32 | 33 | 34 | 35 | -- Mesh 36 | 37 | 38 | type alias Vertex = 39 | { position : Vec2 40 | , color : Vec3 41 | } 42 | 43 | 44 | scale : Int 45 | scale = 46 | let 47 | -- this is the limit of WebGL 1.0 48 | maxNumberOfIndices = 49 | 65536 50 | in 51 | round (sqrt (toFloat maxNumberOfIndices)) + 1 52 | 53 | 54 | mesh : Mesh Vertex 55 | mesh = 56 | let 57 | dimension = 58 | List.range 0 scale 59 | 60 | vertices = 61 | List.foldl 62 | (\i result -> 63 | List.foldl 64 | (\j -> 65 | let 66 | x = 67 | toFloat i / toFloat scale * 2 - 1 68 | 69 | y = 70 | toFloat j / toFloat scale * 2 - 1 71 | in 72 | (::) 73 | { position = vec2 x y 74 | , color = 75 | if modBy 2 i == 1 then 76 | vec3 1 0 0 77 | 78 | else if modBy 2 j == 1 then 79 | vec3 0 1 0 80 | 81 | else 82 | vec3 0 0 1 83 | } 84 | ) 85 | result 86 | dimension 87 | ) 88 | [] 89 | dimension 90 | 91 | dimensionsToIndex i j = 92 | i * (scale + 1) + j 93 | 94 | indices = 95 | List.foldl 96 | (\i result -> 97 | List.foldl 98 | (\j -> 99 | (++) 100 | [ ( dimensionsToIndex (i - 1) (j - 1) 101 | , dimensionsToIndex i (j - 1) 102 | , dimensionsToIndex (i - 1) j 103 | ) 104 | , ( dimensionsToIndex i (j - 1) 105 | , dimensionsToIndex i j 106 | , dimensionsToIndex (i - 1) j 107 | ) 108 | ] 109 | ) 110 | result 111 | (List.drop 1 dimension) 112 | ) 113 | [] 114 | (List.drop 1 dimension) 115 | in 116 | WebGL.indexedTriangles (Debug.log "v" vertices) (Debug.log "i" indices) 117 | 118 | 119 | 120 | -- Shaders 121 | 122 | 123 | vertexShader : Shader Vertex {} { vcolor : Vec3 } 124 | vertexShader = 125 | [glsl| 126 | 127 | attribute vec2 position; 128 | attribute vec3 color; 129 | varying vec3 vcolor; 130 | 131 | void main () { 132 | gl_Position = vec4(position, 0.0, 1.0); 133 | vcolor = color; 134 | } 135 | 136 | |] 137 | 138 | 139 | fragmentShader : Shader {} {} { vcolor : Vec3 } 140 | fragmentShader = 141 | [glsl| 142 | 143 | precision mediump float; 144 | varying vec3 vcolor; 145 | 146 | void main () { 147 | gl_FragColor = vec4(vcolor, 1.0); 148 | } 149 | 150 | |] 151 | -------------------------------------------------------------------------------- /sandbox/Intersection.elm: -------------------------------------------------------------------------------- 1 | module Intersection exposing (main) 2 | 3 | {- 4 | Draws a red and a green triangles, where the green triangle is only 5 | visible in the intercection with the red triangle. 6 | A green outline marks a hidden area of the green triangle. 7 | 8 | This example helps to test the separate stencil test. 9 | -} 10 | 11 | import Html exposing (Html) 12 | import Html.Attributes exposing (height, style, width) 13 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 14 | import WebGL exposing (Mesh, Shader) 15 | import WebGL.Settings exposing (Setting) 16 | import WebGL.Settings.StencilTest as StencilTest 17 | 18 | 19 | main : Html () 20 | main = 21 | WebGL.toHtmlWith 22 | [ WebGL.stencil 0 ] 23 | [ width 400 24 | , height 400 25 | , style "display" "block" 26 | ] 27 | [ WebGL.entityWith 28 | [ stencilTest ] 29 | vertexShader 30 | fragmentShader 31 | redAndGreenTriangles 32 | {} 33 | , WebGL.entity 34 | vertexShader 35 | fragmentShader 36 | greenOutline 37 | {} 38 | ] 39 | 40 | 41 | {-| Rendering with the following setting will write 1's to the stencil buffer 42 | for all front-facing triangles, and then test the subsequent back-facing 43 | triangles against the stencil buffer, so they be rendered only for the areas 44 | that are marked with 1's in the stencil buffer. 45 | 46 | `StencilTest.testSeparate` takes two options, one for front-, and another for 47 | back-facing triangles: 48 | 49 | - The front-facing stencil test always passes, and replaces the stencil buffer 50 | with 1. 51 | - The back-facing stencil test only passes when the value in the stencil buffer 52 | is equal to 1. It does not modify the stencil buffer. 53 | 54 | -} 55 | stencilTest : Setting 56 | stencilTest = 57 | StencilTest.testSeparate 58 | { ref = 1 59 | , mask = 0xFF 60 | , writeMask = 0xFF 61 | } 62 | { test = StencilTest.always 63 | , fail = StencilTest.keep 64 | , zfail = StencilTest.keep 65 | , zpass = StencilTest.replace 66 | } 67 | { test = StencilTest.equal 68 | , fail = StencilTest.keep 69 | , zfail = StencilTest.keep 70 | , zpass = StencilTest.keep 71 | } 72 | 73 | 74 | 75 | -- Mesh 76 | 77 | 78 | type alias Vertex = 79 | { position : Vec3 80 | , color : Vec3 81 | } 82 | 83 | 84 | redAndGreenTriangles : Mesh Vertex 85 | redAndGreenTriangles = 86 | let 87 | -- the red triangle is front-facing 88 | redTriangle = 89 | ( Vertex (vec3 -1 0.5 0) (vec3 1 0 0) 90 | , Vertex (vec3 0 -0.5 0) (vec3 1 0 0) 91 | , Vertex (vec3 1 0.5 0) (vec3 1 0 0) 92 | ) 93 | 94 | -- the green triangle is back-facing 95 | greenTriangle = 96 | ( Vertex (vec3 -1 -0.5 0) (vec3 0 1 0) 97 | , Vertex (vec3 0 0.5 0) (vec3 0 1 0) 98 | , Vertex (vec3 1 -0.5 0) (vec3 0 1 0) 99 | ) 100 | in 101 | WebGL.triangles 102 | [ redTriangle 103 | , greenTriangle 104 | ] 105 | 106 | 107 | greenOutline : Mesh Vertex 108 | greenOutline = 109 | WebGL.lineLoop 110 | [ Vertex (vec3 -1 -0.5 0) (vec3 0 1 0) 111 | , Vertex (vec3 0 0.5 0) (vec3 0 1 0) 112 | , Vertex (vec3 1 -0.5 0) (vec3 0 1 0) 113 | ] 114 | 115 | 116 | 117 | -- Shaders 118 | 119 | 120 | vertexShader : Shader Vertex {} { vcolor : Vec3 } 121 | vertexShader = 122 | [glsl| 123 | 124 | attribute vec3 position; 125 | attribute vec3 color; 126 | varying vec3 vcolor; 127 | 128 | void main () { 129 | gl_Position = vec4(position, 1.0); 130 | vcolor = color; 131 | } 132 | 133 | |] 134 | 135 | 136 | fragmentShader : Shader {} {} { vcolor : Vec3 } 137 | fragmentShader = 138 | [glsl| 139 | 140 | precision mediump float; 141 | varying vec3 vcolor; 142 | 143 | void main () { 144 | gl_FragColor = vec4(vcolor, 1.0); 145 | } 146 | 147 | |] 148 | -------------------------------------------------------------------------------- /sandbox/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "." 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/browser": "1.0.0", 10 | "elm/core": "1.0.0", 11 | "elm/html": "1.0.0", 12 | "elm/json": "1.0.0", 13 | "elm-explorations/linear-algebra": "1.0.0", 14 | "elm-explorations/webgl": "1.1.2" 15 | }, 16 | "indirect": { 17 | "elm/time": "1.0.0", 18 | "elm/url": "1.0.0", 19 | "elm/virtual-dom": "1.0.0" 20 | } 21 | }, 22 | "test-dependencies": { 23 | "direct": {}, 24 | "indirect": {} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Elm/Kernel/Texture.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | import Elm.Kernel.Utils exposing (Tuple2) 4 | import Elm.Kernel.Scheduler exposing (binding, succeed, fail) 5 | import WebGL.Texture as Texture exposing (LoadError, SizeError) 6 | 7 | */ 8 | 9 | // eslint-disable-next-line no-unused-vars 10 | var _Texture_load = F6(function (magnify, mininify, horizontalWrap, verticalWrap, flipY, url) { 11 | var isMipmap = mininify !== 9728 && mininify !== 9729; 12 | return __Scheduler_binding(function (callback) { 13 | var img = new Image(); 14 | function createTexture(gl) { 15 | var texture = gl.createTexture(); 16 | gl.bindTexture(gl.TEXTURE_2D, texture); 17 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY); 18 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); 19 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magnify); 20 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mininify); 21 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, horizontalWrap); 22 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, verticalWrap); 23 | if (isMipmap) { 24 | gl.generateMipmap(gl.TEXTURE_2D); 25 | } 26 | gl.bindTexture(gl.TEXTURE_2D, null); 27 | return texture; 28 | } 29 | img.onload = function () { 30 | var width = img.width; 31 | var height = img.height; 32 | var widthPowerOfTwo = (width & (width - 1)) === 0; 33 | var heightPowerOfTwo = (height & (height - 1)) === 0; 34 | var isSizeValid = (widthPowerOfTwo && heightPowerOfTwo) || ( 35 | !isMipmap 36 | && horizontalWrap === 33071 // clamp to edge 37 | && verticalWrap === 33071 38 | ); 39 | if (isSizeValid) { 40 | callback(__Scheduler_succeed({ 41 | $: __0_TEXTURE, 42 | __$createTexture: createTexture, 43 | __width: width, 44 | __height: height 45 | })); 46 | } else { 47 | callback(__Scheduler_fail(A2( 48 | __Texture_SizeError, 49 | width, 50 | height 51 | ))); 52 | } 53 | }; 54 | img.onerror = function () { 55 | callback(__Scheduler_fail(__Texture_LoadError)); 56 | }; 57 | if (url.slice(0, 5) !== 'data:') { 58 | img.crossOrigin = 'Anonymous'; 59 | } 60 | img.src = url; 61 | }); 62 | }); 63 | 64 | // eslint-disable-next-line no-unused-vars 65 | var _Texture_size = function (texture) { 66 | return __Utils_Tuple2(texture.__width, texture.__height); 67 | }; 68 | -------------------------------------------------------------------------------- /src/Elm/Kernel/WebGL.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | import Elm.Kernel.VirtualDom exposing (custom, doc) 4 | import WebGL.Internal as WI exposing (enableSetting, enableOption) 5 | 6 | */ 7 | 8 | var _WebGL_guid = 0; 9 | 10 | function _WebGL_listEach(fn, list) { 11 | for (; list.b; list = list.b) { 12 | fn(list.a); 13 | } 14 | } 15 | 16 | function _WebGL_listLength(list) { 17 | var length = 0; 18 | for (; list.b; list = list.b) { 19 | length++; 20 | } 21 | return length; 22 | } 23 | 24 | var _WebGL_rAF = typeof requestAnimationFrame !== 'undefined' ? 25 | requestAnimationFrame : 26 | function (cb) { setTimeout(cb, 1000 / 60); }; 27 | 28 | // eslint-disable-next-line no-unused-vars 29 | var _WebGL_entity = F5(function (settings, vert, frag, mesh, uniforms) { 30 | return { 31 | $: __0_ENTITY, 32 | __settings: settings, 33 | __vert: vert, 34 | __frag: frag, 35 | __mesh: mesh, 36 | __uniforms: uniforms 37 | }; 38 | }); 39 | 40 | // eslint-disable-next-line no-unused-vars 41 | var _WebGL_enableBlend = F2(function (cache, setting) { 42 | var blend = cache.blend; 43 | blend.toggle = cache.toggle; 44 | 45 | if (!blend.enabled) { 46 | cache.gl.enable(cache.gl.BLEND); 47 | blend.enabled = true; 48 | } 49 | 50 | // a b c d e f g h i j 51 | // eq1 f11 f12 eq2 f21 f22 r g b a 52 | if (blend.a !== setting.a || blend.d !== setting.d) { 53 | cache.gl.blendEquationSeparate(setting.a, setting.d); 54 | blend.a = setting.a; 55 | blend.d = setting.d; 56 | } 57 | if (blend.b !== setting.b || blend.c !== setting.c || blend.e !== setting.e || blend.f !== setting.f) { 58 | cache.gl.blendFuncSeparate(setting.b, setting.c, setting.e, setting.f); 59 | blend.b = setting.b; 60 | blend.c = setting.c; 61 | blend.e = setting.e; 62 | blend.f = setting.f; 63 | } 64 | if (blend.g !== setting.g || blend.h !== setting.h || blend.i !== setting.i || blend.j !== setting.j) { 65 | cache.gl.blendColor(setting.g, setting.h, setting.i, setting.j); 66 | blend.g = setting.g; 67 | blend.h = setting.h; 68 | blend.i = setting.i; 69 | blend.j = setting.j; 70 | } 71 | }); 72 | 73 | // eslint-disable-next-line no-unused-vars 74 | var _WebGL_enableDepthTest = F2(function (cache, setting) { 75 | var depthTest = cache.depthTest; 76 | depthTest.toggle = cache.toggle; 77 | 78 | if (!depthTest.enabled) { 79 | cache.gl.enable(cache.gl.DEPTH_TEST); 80 | depthTest.enabled = true; 81 | } 82 | 83 | // a b c d 84 | // func mask near far 85 | if (depthTest.a !== setting.a) { 86 | cache.gl.depthFunc(setting.a); 87 | depthTest.a = setting.a; 88 | } 89 | if (depthTest.b !== setting.b) { 90 | cache.gl.depthMask(setting.b); 91 | depthTest.b = setting.b; 92 | } 93 | if (depthTest.c !== setting.c || depthTest.d !== setting.d) { 94 | cache.gl.depthRange(setting.c, setting.d); 95 | depthTest.c = setting.c; 96 | depthTest.d = setting.d; 97 | } 98 | }); 99 | 100 | // eslint-disable-next-line no-unused-vars 101 | var _WebGL_enableStencilTest = F2(function (cache, setting) { 102 | var stencilTest = cache.stencilTest; 103 | stencilTest.toggle = cache.toggle; 104 | 105 | if (!stencilTest.enabled) { 106 | cache.gl.enable(cache.gl.STENCIL_TEST); 107 | stencilTest.enabled = true; 108 | } 109 | 110 | // a b c d e f g h i j k 111 | // ref mask writeMask test1 fail1 zfail1 zpass1 test2 fail2 zfail2 zpass2 112 | if (stencilTest.d !== setting.d || stencilTest.a !== setting.a || stencilTest.b !== setting.b) { 113 | cache.gl.stencilFuncSeparate(cache.gl.FRONT, setting.d, setting.a, setting.b); 114 | stencilTest.d = setting.d; 115 | // a and b are set in the cache.gl.BACK diffing because they should be the same 116 | } 117 | if (stencilTest.e !== setting.e || stencilTest.f !== setting.f || stencilTest.g !== setting.g) { 118 | cache.gl.stencilOpSeparate(cache.gl.FRONT, setting.e, setting.f, setting.g); 119 | stencilTest.e = setting.e; 120 | stencilTest.f = setting.f; 121 | stencilTest.g = setting.g; 122 | } 123 | if (stencilTest.c !== setting.c) { 124 | cache.gl.stencilMask(setting.c); 125 | stencilTest.c = setting.c; 126 | } 127 | if (stencilTest.h !== setting.h || stencilTest.a !== setting.a || stencilTest.b !== setting.b) { 128 | cache.gl.stencilFuncSeparate(cache.gl.BACK, setting.h, setting.a, setting.b); 129 | stencilTest.h = setting.h; 130 | stencilTest.a = setting.a; 131 | stencilTest.b = setting.b; 132 | } 133 | if (stencilTest.i !== setting.i || stencilTest.j !== setting.j || stencilTest.k !== setting.k) { 134 | cache.gl.stencilOpSeparate(cache.gl.BACK, setting.i, setting.j, setting.k); 135 | stencilTest.i = setting.i; 136 | stencilTest.j = setting.j; 137 | stencilTest.k = setting.k; 138 | } 139 | }); 140 | 141 | // eslint-disable-next-line no-unused-vars 142 | var _WebGL_enableScissor = F2(function (cache, setting) { 143 | var scissor = cache.scissor; 144 | scissor.toggle = cache.toggle; 145 | 146 | if (!scissor.enabled) { 147 | cache.gl.enable(cache.gl.SCISSOR_TEST); 148 | scissor.enabled = true; 149 | } 150 | 151 | if (scissor.a !== setting.a || scissor.b !== setting.b || scissor.c !== setting.c || scissor.d !== setting.d) { 152 | cache.gl.scissor(setting.a, setting.b, setting.c, setting.d); 153 | scissor.a = setting.a; 154 | scissor.b = setting.b; 155 | scissor.c = setting.c; 156 | scissor.d = setting.d; 157 | } 158 | }); 159 | 160 | // eslint-disable-next-line no-unused-vars 161 | var _WebGL_enableColorMask = F2(function (cache, setting) { 162 | var colorMask = cache.colorMask; 163 | colorMask.toggle = cache.toggle; 164 | colorMask.enabled = true; 165 | 166 | if (colorMask.a !== setting.a || colorMask.b !== setting.b || colorMask.c !== setting.c || colorMask.d !== setting.d) { 167 | cache.gl.colorMask(setting.a, setting.b, setting.c, setting.d); 168 | colorMask.a = setting.a; 169 | colorMask.b = setting.b; 170 | colorMask.c = setting.c; 171 | colorMask.d = setting.d; 172 | } 173 | }); 174 | 175 | // eslint-disable-next-line no-unused-vars 176 | var _WebGL_enableCullFace = F2(function (cache, setting) { 177 | var cullFace = cache.cullFace; 178 | cullFace.toggle = cache.toggle; 179 | 180 | if (!cullFace.enabled) { 181 | cache.gl.enable(cache.gl.CULL_FACE); 182 | cullFace.enabled = true; 183 | } 184 | 185 | if (cullFace.a !== setting.a) { 186 | cache.gl.cullFace(setting.a); 187 | cullFace.a = setting.a; 188 | } 189 | }); 190 | 191 | // eslint-disable-next-line no-unused-vars 192 | var _WebGL_enablePolygonOffset = F2(function (cache, setting) { 193 | var polygonOffset = cache.polygonOffset; 194 | polygonOffset.toggle = cache.toggle; 195 | 196 | if (!polygonOffset.enabled) { 197 | cache.gl.enable(cache.gl.POLYGON_OFFSET_FILL); 198 | polygonOffset.enabled = true; 199 | } 200 | 201 | if (polygonOffset.a !== setting.a || polygonOffset.b !== setting.b) { 202 | cache.gl.polygonOffset(setting.a, setting.b); 203 | polygonOffset.a = setting.a; 204 | polygonOffset.b = setting.b; 205 | } 206 | }); 207 | 208 | // eslint-disable-next-line no-unused-vars 209 | var _WebGL_enableSampleCoverage = F2(function (cache, setting) { 210 | var sampleCoverage = cache.sampleCoverage; 211 | sampleCoverage.toggle = cache.toggle; 212 | 213 | if (!sampleCoverage.enabled) { 214 | cache.gl.enable(cache.gl.SAMPLE_COVERAGE); 215 | sampleCoverage.enabled = true; 216 | } 217 | 218 | if (sampleCoverage.a !== setting.a || sampleCoverage.b !== setting.b) { 219 | cache.gl.sampleCoverage(setting.a, setting.b); 220 | sampleCoverage.a = setting.a; 221 | sampleCoverage.b = setting.b; 222 | } 223 | }); 224 | 225 | // eslint-disable-next-line no-unused-vars 226 | var _WebGL_enableSampleAlphaToCoverage = function (cache) { 227 | var sampleAlphaToCoverage = cache.sampleAlphaToCoverage; 228 | sampleAlphaToCoverage.toggle = cache.toggle; 229 | 230 | if (!sampleAlphaToCoverage.enabled) { 231 | cache.gl.enable(cache.gl.SAMPLE_ALPHA_TO_COVERAGE); 232 | sampleAlphaToCoverage.enabled = true; 233 | } 234 | }; 235 | 236 | var _WebGL_disableBlend = function (cache) { 237 | if (cache.blend.enabled) { 238 | cache.gl.disable(cache.gl.BLEND); 239 | cache.blend.enabled = false; 240 | } 241 | }; 242 | 243 | var _WebGL_disableDepthTest = function (cache) { 244 | if (cache.depthTest.enabled) { 245 | cache.gl.disable(cache.gl.DEPTH_TEST); 246 | cache.depthTest.enabled = false; 247 | } 248 | }; 249 | 250 | var _WebGL_disableStencilTest = function (cache) { 251 | if (cache.stencilTest.enabled) { 252 | cache.gl.disable(cache.gl.STENCIL_TEST); 253 | cache.stencilTest.enabled = false; 254 | } 255 | }; 256 | 257 | var _WebGL_disableScissor = function (cache) { 258 | if (cache.scissor.enabled) { 259 | cache.gl.disable(cache.gl.SCISSOR_TEST); 260 | cache.scissor.enabled = false; 261 | } 262 | }; 263 | 264 | var _WebGL_disableColorMask = function (cache) { 265 | var colorMask = cache.colorMask; 266 | if (!colorMask.a || !colorMask.b || !colorMask.c || !colorMask.d) { 267 | cache.gl.colorMask(true, true, true, true); 268 | colorMask.a = true; 269 | colorMask.b = true; 270 | colorMask.c = true; 271 | colorMask.d = true; 272 | } 273 | }; 274 | 275 | var _WebGL_disableCullFace = function (cache) { 276 | cache.gl.disable(cache.gl.CULL_FACE); 277 | }; 278 | 279 | var _WebGL_disablePolygonOffset = function (cache) { 280 | cache.gl.disable(cache.gl.POLYGON_OFFSET_FILL); 281 | }; 282 | 283 | var _WebGL_disableSampleCoverage = function (cache) { 284 | cache.gl.disable(cache.gl.SAMPLE_COVERAGE); 285 | }; 286 | 287 | var _WebGL_disableSampleAlphaToCoverage = function (cache) { 288 | cache.gl.disable(cache.gl.SAMPLE_ALPHA_TO_COVERAGE); 289 | }; 290 | 291 | var _WebGL_settings = ['blend', 'depthTest', 'stencilTest', 'scissor', 'colorMask', 'cullFace', 'polygonOffset', 'sampleCoverage', 'sampleAlphaToCoverage']; 292 | var _WebGL_disableFunctions = [_WebGL_disableBlend, _WebGL_disableDepthTest, _WebGL_disableStencilTest, _WebGL_disableScissor, _WebGL_disableColorMask, _WebGL_disableCullFace, _WebGL_disablePolygonOffset, _WebGL_disableSampleCoverage, _WebGL_disableSampleAlphaToCoverage]; 293 | 294 | function _WebGL_doCompile(gl, src, type) { 295 | var shader = gl.createShader(type); 296 | // Enable OES_standard_derivatives extension 297 | gl.shaderSource(shader, '#extension GL_OES_standard_derivatives : enable\n' + src); 298 | gl.compileShader(shader); 299 | return shader; 300 | } 301 | 302 | function _WebGL_doLink(gl, vshader, fshader) { 303 | var program = gl.createProgram(); 304 | 305 | gl.attachShader(program, vshader); 306 | gl.attachShader(program, fshader); 307 | gl.linkProgram(program); 308 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 309 | throw ('Link failed: ' + gl.getProgramInfoLog(program) + 310 | '\nvs info-log: ' + gl.getShaderInfoLog(vshader) + 311 | '\nfs info-log: ' + gl.getShaderInfoLog(fshader)); 312 | } 313 | 314 | return program; 315 | } 316 | 317 | function _WebGL_getAttributeInfo(gl, type) { 318 | switch (type) { 319 | case gl.FLOAT: 320 | return { size: 1, arraySize: 1, type: Float32Array, baseType: gl.FLOAT }; 321 | case gl.FLOAT_VEC2: 322 | return { size: 2, arraySize: 1, type: Float32Array, baseType: gl.FLOAT }; 323 | case gl.FLOAT_VEC3: 324 | return { size: 3, arraySize: 1, type: Float32Array, baseType: gl.FLOAT }; 325 | case gl.FLOAT_VEC4: 326 | return { size: 4, arraySize: 1, type: Float32Array, baseType: gl.FLOAT }; 327 | case gl.FLOAT_MAT4: 328 | return { size: 4, arraySize: 4, type: Float32Array, baseType: gl.FLOAT }; 329 | case gl.INT: 330 | return { size: 1, arraySize: 1, type: Int32Array, baseType: gl.INT }; 331 | } 332 | } 333 | 334 | /** 335 | * Form the buffer for a given attribute. 336 | * 337 | * @param {WebGLRenderingContext} gl context 338 | * @param {WebGLActiveInfo} attribute the attribute to bind to. 339 | * We use its name to grab the record by name and also to know 340 | * how many elements we need to grab. 341 | * @param {Mesh} mesh The mesh coming in from Elm. 342 | * @param {Object} attributes The mapping between the attribute names and Elm fields 343 | * @return {WebGLBuffer} 344 | */ 345 | function _WebGL_doBindAttribute(gl, attribute, mesh, attributes) { 346 | // The length of the number of vertices that 347 | // complete one 'thing' based on the drawing mode. 348 | // ie, 2 for Lines, 3 for Triangles, etc. 349 | var elemSize = mesh.a.__$elemSize; 350 | 351 | var idxKeys = []; 352 | for (var i = 0; i < elemSize; i++) { 353 | idxKeys.push(String.fromCharCode(97 + i)); 354 | } 355 | 356 | function dataFill(data, cnt, fillOffset, elem, key) { 357 | var i; 358 | if (elemSize === 1) { 359 | for (i = 0; i < cnt; i++) { 360 | data[fillOffset++] = cnt === 1 ? elem[key] : elem[key][i]; 361 | } 362 | } else { 363 | idxKeys.forEach(function (idx) { 364 | for (i = 0; i < cnt; i++) { 365 | data[fillOffset++] = cnt === 1 ? elem[idx][key] : elem[idx][key][i]; 366 | } 367 | }); 368 | } 369 | } 370 | 371 | var attributeInfo = _WebGL_getAttributeInfo(gl, attribute.type); 372 | 373 | if (attributeInfo === undefined) { 374 | throw new Error('No info available for: ' + attribute.type); 375 | } 376 | 377 | var dataIdx = 0; 378 | var dataOffset = attributeInfo.size * attributeInfo.arraySize * elemSize; 379 | var array = new attributeInfo.type(_WebGL_listLength(mesh.b) * dataOffset); 380 | 381 | _WebGL_listEach(function (elem) { 382 | dataFill(array, attributeInfo.size * attributeInfo.arraySize, dataIdx, elem, attributes[attribute.name] || attribute.name); 383 | dataIdx += dataOffset; 384 | }, mesh.b); 385 | 386 | var buffer = gl.createBuffer(); 387 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 388 | gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW); 389 | return buffer; 390 | } 391 | 392 | /** 393 | * This sets up the binding caching buffers. 394 | * 395 | * We don't actually bind any buffers now except for the indices buffer. 396 | * The problem with filling the buffers here is that it is possible to 397 | * have a buffer shared between two webgl shaders; 398 | * which could have different active attributes. If we bind it here against 399 | * a particular program, we might not bind them all. That final bind is now 400 | * done right before drawing. 401 | * 402 | * @param {WebGLRenderingContext} gl context 403 | * @param {Mesh} mesh a mesh object from Elm 404 | * @return {Object} buffer - an object with the following properties 405 | * @return {Number} buffer.numIndices 406 | * @return {WebGLBuffer|null} buffer.indexBuffer - optional index buffer 407 | * @return {Object} buffer.buffers - will be used to buffer attributes 408 | */ 409 | function _WebGL_doBindSetup(gl, mesh) { 410 | if (mesh.a.__$indexSize > 0) { 411 | var indexBuffer = gl.createBuffer(); 412 | var indices = _WebGL_makeIndexedBuffer(mesh.c, mesh.a.__$indexSize); 413 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); 414 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); 415 | return { 416 | numIndices: indices.length, 417 | indexBuffer: indexBuffer, 418 | buffers: {} 419 | }; 420 | } else { 421 | return { 422 | numIndices: mesh.a.__$elemSize * _WebGL_listLength(mesh.b), 423 | indexBuffer: null, 424 | buffers: {} 425 | }; 426 | } 427 | } 428 | 429 | /** 430 | * Create an indices array and fill it from indices 431 | * based on the size of the index 432 | * 433 | * @param {List} indicesList the list of indices 434 | * @param {Number} indexSize the size of the index 435 | * @return {Uint32Array} indices 436 | */ 437 | function _WebGL_makeIndexedBuffer(indicesList, indexSize) { 438 | var indices = new Uint32Array(_WebGL_listLength(indicesList) * indexSize); 439 | var fillOffset = 0; 440 | var i; 441 | _WebGL_listEach(function (elem) { 442 | if (indexSize === 1) { 443 | indices[fillOffset++] = elem; 444 | } else { 445 | for (i = 0; i < indexSize; i++) { 446 | indices[fillOffset++] = elem[String.fromCharCode(97 + i)]; 447 | } 448 | } 449 | }, indicesList); 450 | return indices; 451 | } 452 | 453 | function _WebGL_getProgID(vertID, fragID) { 454 | return vertID + '#' + fragID; 455 | } 456 | 457 | var _WebGL_drawGL = F2(function (model, domNode) { 458 | var cache = model.__cache; 459 | var gl = cache.gl; 460 | 461 | if (!gl) { 462 | return domNode; 463 | } 464 | 465 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 466 | 467 | if (!cache.depthTest.b) { 468 | gl.depthMask(true); 469 | cache.depthTest.b = true; 470 | } 471 | if (cache.stencilTest.c !== cache.STENCIL_WRITEMASK) { 472 | gl.stencilMask(cache.STENCIL_WRITEMASK); 473 | cache.stencilTest.c = cache.STENCIL_WRITEMASK; 474 | } 475 | _WebGL_disableScissor(cache); 476 | _WebGL_disableColorMask(cache); 477 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); 478 | 479 | function drawEntity(entity) { 480 | if (!entity.__mesh.b.b) { 481 | return; // Empty list 482 | } 483 | 484 | var progid; 485 | var program; 486 | var i; 487 | 488 | if (entity.__vert.id && entity.__frag.id) { 489 | progid = _WebGL_getProgID(entity.__vert.id, entity.__frag.id); 490 | program = cache.programs[progid]; 491 | } 492 | 493 | if (!program) { 494 | 495 | var vshader; 496 | if (entity.__vert.id) { 497 | vshader = cache.shaders[entity.__vert.id]; 498 | } else { 499 | entity.__vert.id = _WebGL_guid++; 500 | } 501 | 502 | if (!vshader) { 503 | vshader = _WebGL_doCompile(gl, entity.__vert.src, gl.VERTEX_SHADER); 504 | cache.shaders[entity.__vert.id] = vshader; 505 | } 506 | 507 | var fshader; 508 | if (entity.__frag.id) { 509 | fshader = cache.shaders[entity.__frag.id]; 510 | } else { 511 | entity.__frag.id = _WebGL_guid++; 512 | } 513 | 514 | if (!fshader) { 515 | fshader = _WebGL_doCompile(gl, entity.__frag.src, gl.FRAGMENT_SHADER); 516 | cache.shaders[entity.__frag.id] = fshader; 517 | } 518 | 519 | var glProgram = _WebGL_doLink(gl, vshader, fshader); 520 | 521 | program = { 522 | glProgram: glProgram, 523 | attributes: Object.assign({}, entity.__vert.attributes, entity.__frag.attributes), 524 | currentUniforms: {}, 525 | activeAttributes: [], 526 | activeAttributeLocations: [] 527 | }; 528 | 529 | program.uniformSetters = _WebGL_createUniformSetters( 530 | gl, 531 | model, 532 | program, 533 | Object.assign({}, entity.__vert.uniforms, entity.__frag.uniforms) 534 | ); 535 | 536 | var numActiveAttributes = gl.getProgramParameter(glProgram, gl.ACTIVE_ATTRIBUTES); 537 | for (i = 0; i < numActiveAttributes; i++) { 538 | var attribute = gl.getActiveAttrib(glProgram, i); 539 | var attribLocation = gl.getAttribLocation(glProgram, attribute.name); 540 | program.activeAttributes.push(attribute); 541 | program.activeAttributeLocations.push(attribLocation); 542 | } 543 | 544 | progid = _WebGL_getProgID(entity.__vert.id, entity.__frag.id); 545 | cache.programs[progid] = program; 546 | } 547 | 548 | if (cache.lastProgId !== progid) { 549 | gl.useProgram(program.glProgram); 550 | cache.lastProgId = progid; 551 | } 552 | 553 | _WebGL_setUniforms(program.uniformSetters, entity.__uniforms); 554 | 555 | var buffer = cache.buffers.get(entity.__mesh); 556 | 557 | if (!buffer) { 558 | buffer = _WebGL_doBindSetup(gl, entity.__mesh); 559 | cache.buffers.set(entity.__mesh, buffer); 560 | } 561 | 562 | for (i = 0; i < program.activeAttributes.length; i++) { 563 | attribute = program.activeAttributes[i]; 564 | attribLocation = program.activeAttributeLocations[i]; 565 | 566 | if (buffer.buffers[attribute.name] === undefined) { 567 | buffer.buffers[attribute.name] = _WebGL_doBindAttribute(gl, attribute, entity.__mesh, program.attributes); 568 | } 569 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffers[attribute.name]); 570 | 571 | var attributeInfo = _WebGL_getAttributeInfo(gl, attribute.type); 572 | if (attributeInfo.arraySize === 1) { 573 | gl.enableVertexAttribArray(attribLocation); 574 | gl.vertexAttribPointer(attribLocation, attributeInfo.size, attributeInfo.baseType, false, 0, 0); 575 | } else { 576 | // Point to four vec4 in case of mat4 577 | var offset = attributeInfo.size * 4; // float32 takes 4 bytes 578 | var stride = offset * attributeInfo.arraySize; 579 | for (var m = 0; m < attributeInfo.arraySize; m++) { 580 | gl.enableVertexAttribArray(attribLocation + m); 581 | gl.vertexAttribPointer(attribLocation + m, attributeInfo.size, attributeInfo.baseType, false, stride, offset * m); 582 | } 583 | } 584 | } 585 | 586 | // Apply all the new settings 587 | cache.toggle = !cache.toggle; 588 | _WebGL_listEach(__WI_enableSetting(cache), entity.__settings); 589 | // Disable the settings that were applied in the previous draw call 590 | for (i = 0; i < _WebGL_settings.length; i++) { 591 | var setting = cache[_WebGL_settings[i]]; 592 | if (setting.toggle !== cache.toggle && setting.enabled) { 593 | _WebGL_disableFunctions[i](cache); 594 | setting.enabled = false; 595 | setting.toggle = cache.toggle; 596 | } 597 | } 598 | 599 | if (buffer.indexBuffer) { 600 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.indexBuffer); 601 | gl.drawElements(entity.__mesh.a.__$mode, buffer.numIndices, gl.UNSIGNED_INT, 0); 602 | } else { 603 | gl.drawArrays(entity.__mesh.a.__$mode, 0, buffer.numIndices); 604 | } 605 | } 606 | 607 | _WebGL_listEach(drawEntity, model.__entities); 608 | return domNode; 609 | }); 610 | 611 | function _WebGL_createUniformSetters(gl, model, program, uniformsMap) { 612 | var glProgram = program.glProgram; 613 | var currentUniforms = program.currentUniforms; 614 | var textureCounter = 0; 615 | var cache = model.__cache; 616 | function createUniformSetter(glProgram, uniform) { 617 | var uniformName = uniform.name; 618 | var uniformLocation = gl.getUniformLocation(glProgram, uniformName); 619 | switch (uniform.type) { 620 | case gl.INT: 621 | return function (value) { 622 | if (currentUniforms[uniformName] !== value) { 623 | gl.uniform1i(uniformLocation, value); 624 | currentUniforms[uniformName] = value; 625 | } 626 | }; 627 | case gl.FLOAT: 628 | return function (value) { 629 | if (currentUniforms[uniformName] !== value) { 630 | gl.uniform1f(uniformLocation, value); 631 | currentUniforms[uniformName] = value; 632 | } 633 | }; 634 | case gl.FLOAT_VEC2: 635 | return function (value) { 636 | if (currentUniforms[uniformName] !== value) { 637 | gl.uniform2f(uniformLocation, value[0], value[1]); 638 | currentUniforms[uniformName] = value; 639 | } 640 | }; 641 | case gl.FLOAT_VEC3: 642 | return function (value) { 643 | if (currentUniforms[uniformName] !== value) { 644 | gl.uniform3f(uniformLocation, value[0], value[1], value[2]); 645 | currentUniforms[uniformName] = value; 646 | } 647 | }; 648 | case gl.FLOAT_VEC4: 649 | return function (value) { 650 | if (currentUniforms[uniformName] !== value) { 651 | gl.uniform4f(uniformLocation, value[0], value[1], value[2], value[3]); 652 | currentUniforms[uniformName] = value; 653 | } 654 | }; 655 | case gl.FLOAT_MAT4: 656 | return function (value) { 657 | if (currentUniforms[uniformName] !== value) { 658 | gl.uniformMatrix4fv(uniformLocation, false, new Float32Array(value)); 659 | currentUniforms[uniformName] = value; 660 | } 661 | }; 662 | case gl.SAMPLER_2D: 663 | var currentTexture = textureCounter++; 664 | return function (texture) { 665 | gl.activeTexture(gl.TEXTURE0 + currentTexture); 666 | var tex = cache.textures.get(texture); 667 | if (!tex) { 668 | tex = texture.__$createTexture(gl); 669 | cache.textures.set(texture, tex); 670 | } 671 | gl.bindTexture(gl.TEXTURE_2D, tex); 672 | if (currentUniforms[uniformName] !== texture) { 673 | gl.uniform1i(uniformLocation, currentTexture); 674 | currentUniforms[uniformName] = texture; 675 | } 676 | }; 677 | case gl.BOOL: 678 | return function (value) { 679 | if (currentUniforms[uniformName] !== value) { 680 | gl.uniform1i(uniformLocation, value); 681 | currentUniforms[uniformName] = value; 682 | } 683 | }; 684 | default: 685 | return function () { }; 686 | } 687 | } 688 | 689 | var uniformSetters = {}; 690 | var numUniforms = gl.getProgramParameter(glProgram, gl.ACTIVE_UNIFORMS); 691 | for (var i = 0; i < numUniforms; i++) { 692 | var uniform = gl.getActiveUniform(glProgram, i); 693 | uniformSetters[uniformsMap[uniform.name] || uniform.name] = createUniformSetter(glProgram, uniform); 694 | } 695 | 696 | return uniformSetters; 697 | } 698 | 699 | function _WebGL_setUniforms(setters, values) { 700 | Object.keys(values).forEach(function (name) { 701 | var setter = setters[name]; 702 | if (setter) { 703 | setter(values[name]); 704 | } 705 | }); 706 | } 707 | 708 | // VIRTUAL-DOM WIDGET 709 | 710 | // eslint-disable-next-line no-unused-vars 711 | var _WebGL_toHtml = F3(function (options, factList, entities) { 712 | return __VirtualDom_custom( 713 | factList, 714 | { 715 | __entities: entities, 716 | __cache: {}, 717 | __options: options 718 | }, 719 | _WebGL_render, 720 | _WebGL_diff 721 | ); 722 | }); 723 | 724 | // eslint-disable-next-line no-unused-vars 725 | var _WebGL_enableAlpha = F2(function (options, option) { 726 | options.contextAttributes.alpha = true; 727 | options.contextAttributes.premultipliedAlpha = option.a; 728 | }); 729 | 730 | // eslint-disable-next-line no-unused-vars 731 | var _WebGL_enableDepth = F2(function (options, option) { 732 | options.contextAttributes.depth = true; 733 | options.sceneSettings.push(function (gl) { 734 | gl.clearDepth(option.a); 735 | }); 736 | }); 737 | 738 | // eslint-disable-next-line no-unused-vars 739 | var _WebGL_enableStencil = F2(function (options, option) { 740 | options.contextAttributes.stencil = true; 741 | options.sceneSettings.push(function (gl) { 742 | gl.clearStencil(option.a); 743 | }); 744 | }); 745 | 746 | // eslint-disable-next-line no-unused-vars 747 | var _WebGL_enableAntialias = F2(function (options, option) { 748 | options.contextAttributes.antialias = true; 749 | }); 750 | 751 | // eslint-disable-next-line no-unused-vars 752 | var _WebGL_enableClearColor = F2(function (options, option) { 753 | options.sceneSettings.push(function (gl) { 754 | gl.clearColor(option.a, option.b, option.c, option.d); 755 | }); 756 | }); 757 | 758 | // eslint-disable-next-line no-unused-vars 759 | var _WebGL_enablePreserveDrawingBuffer = F2(function (options, option) { 760 | options.contextAttributes.preserveDrawingBuffer = true; 761 | }); 762 | 763 | /** 764 | * Creates canvas and schedules initial _WebGL_drawGL 765 | * @param {Object} model 766 | * @param {Object} model.__cache that may contain the following properties: 767 | gl, shaders, programs, buffers, textures 768 | * @param {List