├── README.md ├── WebGPU.png ├── compute-crash └── index.html ├── cube.html ├── d3d12-bug ├── gl-matrix │ ├── LICENSE.md │ └── src │ │ ├── gl-matrix.js │ │ └── gl-matrix │ │ ├── common.js │ │ ├── mat2.js │ │ ├── mat2d.js │ │ ├── mat3.js │ │ ├── mat4.js │ │ ├── quat.js │ │ ├── quat2.js │ │ ├── vec2.js │ │ ├── vec3.js │ │ └── vec4.js └── index.html ├── device-loss.html ├── favicon-16.png ├── favicon-32.png ├── favicon-48.png ├── flashing-bug └── index.html ├── iframe-raf ├── iframe.html └── index.html ├── iframe.html ├── index.html ├── js ├── camera.js ├── mini-gltf2.js ├── pbr-shader.js ├── renderer.js ├── third-party │ ├── gl-matrix │ │ ├── LICENSE.md │ │ └── src │ │ │ ├── gl-matrix.js │ │ │ └── gl-matrix │ │ │ ├── common.js │ │ │ ├── mat2.js │ │ │ ├── mat2d.js │ │ │ ├── mat3.js │ │ │ ├── mat4.js │ │ │ ├── quat.js │ │ │ ├── quat2.js │ │ │ ├── vec2.js │ │ │ ├── vec3.js │ │ │ └── vec4.js │ └── stats.module.js ├── webgl-renderer │ ├── shader-program.js │ └── webgl-renderer.js ├── webgl2-renderer │ └── webgl2-renderer.js └── webgpu-renderer │ ├── pbr-shader-wgsl.js │ ├── webgpu-renderer.js │ ├── webgpu-texture-helper.js │ └── wgsl-debug-helper.js ├── matrix-shader-crash.html ├── media ├── models │ └── sponza │ │ ├── 10381718147657362067.jpg │ │ ├── 10388182081421875623.jpg │ │ ├── 11474523244911310074.jpg │ │ ├── 11490520546946913238.jpg │ │ ├── 11872827283454512094.jpg │ │ ├── 11968150294050148237.jpg │ │ ├── 1219024358953944284.jpg │ │ ├── 12501374198249454378.jpg │ │ ├── 13196865903111448057.jpg │ │ ├── 13824894030729245199.jpg │ │ ├── 13982482287905699490.jpg │ │ ├── 14118779221266351425.jpg │ │ ├── 14170708867020035030.jpg │ │ ├── 14267839433702832875.jpg │ │ ├── 14650633544276105767.jpg │ │ ├── 15295713303328085182.jpg │ │ ├── 15722799267630235092.jpg │ │ ├── 16275776544635328252.png │ │ ├── 16299174074766089871.jpg │ │ ├── 16885566240357350108.jpg │ │ ├── 17556969131407844942.jpg │ │ ├── 17876391417123941155.jpg │ │ ├── 2051777328469649772.jpg │ │ ├── 2185409758123873465.jpg │ │ ├── 2299742237651021498.jpg │ │ ├── 2374361008830720677.jpg │ │ ├── 2411100444841994089.jpg │ │ ├── 2775690330959970771.jpg │ │ ├── 2969916736137545357.jpg │ │ ├── 332936164838540657.jpg │ │ ├── 3371964815757888145.jpg │ │ ├── 3455394979645218238.jpg │ │ ├── 3628158980083700836.jpg │ │ ├── 3827035219084910048.jpg │ │ ├── 4477655471536070370.jpg │ │ ├── 4601176305987539675.jpg │ │ ├── 466164707995436622.jpg │ │ ├── 4675343432951571524.jpg │ │ ├── 4871783166746854860.jpg │ │ ├── 4910669866631290573.jpg │ │ ├── 4975155472559461469.jpg │ │ ├── 5061699253647017043.png │ │ ├── 5792855332885324923.jpg │ │ ├── 5823059166183034438.jpg │ │ ├── 6047387724914829168.jpg │ │ ├── 6151467286084645207.jpg │ │ ├── 6593109234861095314.jpg │ │ ├── 6667038893015345571.jpg │ │ ├── 6772804448157695701.jpg │ │ ├── 7056944414013900257.jpg │ │ ├── 715093869573992647.jpg │ │ ├── 7268504077753552595.jpg │ │ ├── 7441062115984513793.jpg │ │ ├── 755318871556304029.jpg │ │ ├── 759203620573749278.jpg │ │ ├── 7645212358685992005.jpg │ │ ├── 7815564343179553343.jpg │ │ ├── 8006627369776289000.png │ │ ├── 8051790464816141987.jpg │ │ ├── 8114461559286000061.jpg │ │ ├── 8481240838833932244.jpg │ │ ├── 8503262930880235456.jpg │ │ ├── 8747919177698443163.jpg │ │ ├── 8750083169368950601.jpg │ │ ├── 8773302468495022225.jpg │ │ ├── 8783994986360286082.jpg │ │ ├── 9288698199695299068.jpg │ │ ├── 9916269861720640319.jpg │ │ ├── README.md │ │ ├── Sponza.bin │ │ ├── Sponza.gltf │ │ └── white.png └── webgpu-logo.svg ├── pipeline-fails ├── index.html ├── pipeline-test.js ├── simple.html └── spookyball-pbr.html ├── playcanvas-crash ├── crash1.html ├── crash2.html └── index.html ├── resize-crash ├── index.html └── query-args.js ├── sample-mask-error └── index.html ├── shader-of-doom └── index.html ├── skybox-bug ├── README.md ├── index.html └── skybox.js ├── thumb.png ├── webgpu-css-logo.html └── webgpu-tilemap ├── index.html ├── spelunky-tiles.png ├── spelunky0.png ├── spelunky1.png └── webgpu-tilemap.js /README.md: -------------------------------------------------------------------------------- 1 | # Web Graphics API Test 2 | 3 | Live page at [https://toji.github.io/webgpu-test/](https://toji.github.io/webgpu-test/). 4 | 5 | This project renders a scene using WebGL, WebGL 2.0, and WebGPU as implemented in Chrome Canary circa Feb 2020. The purpose was mostly educational for me, I wanted to learn about the current state of WebGPU, but I also wanted to create a page that would allow for simple comparisons and profiling between the APIs. 6 | 7 | The renderer for each API loads resources from a glTF file and renders them using best practices for each API (without extensions). The scene was selected to be interesting and reasonably real-world in terms of geometry and materials, but it isn't particularly challenging for many modern GPUs, so don't expect to see framerate differences between the renderers. Additionally since the scene is the same and shaders are roughly equivalent across renderers the GPU time spent will probably be about the same. 8 | 9 | The more interesting thing to look at is how much time each API spends submitting commands on the JavaScript main thread, which is what the stats counter in the upper left corner is configured to show by default. Even then, though, it's worth noting that this is a relatively simple usage of each API rendering a largely static scene without much of the overhead that would come from more realistic app logic, animation, audio, etc. Also WebGPU is still a work in progress and is expected to undergo both API changes and implementation optimizations prior to shipping. As such any performance observations made with this project should be taken with a grain of salt. 10 | 11 | ## WebGL 12 | The most verbose API, so it's easily the slowest. In real world apps you'd definitely want to use at least the OES_vertex_array_object and ANGLE_instanced_arrays if applicable to reduce the number of API calls needed. Here it mostly serves as a baseline to compare the performance of the other renderers to. 13 | 14 | ## WebGL 2.0 15 | The biggest gains with this renderer vs. the WebGL renderer come from using Vertex Array Objects and Uniform Buffer Objects to drastically reduce the number of calls in the render loop. Instancing is also used to reduce the number of calls needed to render the light orbs. (The main scene does not use instancing because the source asset was not configured to use it.) 16 | 17 | ## WebGPU 18 | The nature of the API means that most of the work is done at initialization time, so the number of calls needed to dispatch draw commands is lower than WebGL 2.0 to begin with, but the render loop is reduced down to almost nothing by using the GPURenderBundles to record the draw commands used at load time and replay them with a single call during the frame callback. 19 | 20 | It should be noted that the WebGPU code path uses [WGSL shaders](https://gpuweb.github.io/gpuweb/wgsl.html), which is WebGPU's native shading language but is not yet finalized. Some breakage is expected in the future, but I'll generally keep it up-to-date with Chrome Canary's implementation. 21 | 22 | To test the WebGPU renderer use Chrome Canary on Windows or MacOS, navigate to about:flags, and turn on the "Unsafe WebGPU" flag. 23 | -------------------------------------------------------------------------------- /WebGPU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/WebGPU.png -------------------------------------------------------------------------------- /d3d12-bug/gl-matrix/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2018, Brandon Jones, Colin MacKenzie IV. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /d3d12-bug/gl-matrix/src/gl-matrix.js: -------------------------------------------------------------------------------- 1 | import * as glMatrix from "./gl-matrix/common.js"; 2 | import * as mat2 from "./gl-matrix/mat2.js"; 3 | import * as mat2d from "./gl-matrix/mat2d.js"; 4 | import * as mat3 from "./gl-matrix/mat3.js"; 5 | import * as mat4 from "./gl-matrix/mat4.js"; 6 | import * as quat from "./gl-matrix/quat.js"; 7 | import * as quat2 from "./gl-matrix/quat2.js"; 8 | import * as vec2 from "./gl-matrix/vec2.js"; 9 | import * as vec3 from "./gl-matrix/vec3.js"; 10 | import * as vec4 from "./gl-matrix/vec4.js"; 11 | 12 | export { 13 | glMatrix, 14 | mat2, mat2d, mat3, mat4, 15 | quat, quat2, 16 | vec2, vec3, vec4, 17 | }; 18 | -------------------------------------------------------------------------------- /d3d12-bug/gl-matrix/src/gl-matrix/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Common utilities 3 | * @module glMatrix 4 | */ 5 | 6 | // Configuration Constants 7 | export const EPSILON = 0.000001; 8 | export let ARRAY_TYPE = (typeof Float32Array !== 'undefined') ? Float32Array : Array; 9 | export const RANDOM = Math.random; 10 | 11 | /** 12 | * Sets the type of array used when creating new vectors and matrices 13 | * 14 | * @param {Type} type Array type, such as Float32Array or Array 15 | */ 16 | export function setMatrixArrayType(type) { 17 | ARRAY_TYPE = type; 18 | } 19 | 20 | const degree = Math.PI / 180; 21 | 22 | /** 23 | * Convert Degree To Radian 24 | * 25 | * @param {Number} a Angle in Degrees 26 | */ 27 | export function toRadian(a) { 28 | return a * degree; 29 | } 30 | 31 | /** 32 | * Tests whether or not the arguments have approximately the same value, within an absolute 33 | * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less 34 | * than or equal to 1.0, and a relative tolerance is used for larger values) 35 | * 36 | * @param {Number} a The first number to test. 37 | * @param {Number} b The second number to test. 38 | * @returns {Boolean} True if the numbers are approximately equal, false otherwise. 39 | */ 40 | export function equals(a, b) { 41 | return Math.abs(a - b) <= EPSILON*Math.max(1.0, Math.abs(a), Math.abs(b)); 42 | } 43 | -------------------------------------------------------------------------------- /d3d12-bug/gl-matrix/src/gl-matrix/mat2.js: -------------------------------------------------------------------------------- 1 | import * as glMatrix from "./common.js" 2 | 3 | /** 4 | * 2x2 Matrix 5 | * @module mat2 6 | */ 7 | 8 | /** 9 | * Creates a new identity mat2 10 | * 11 | * @returns {mat2} a new 2x2 matrix 12 | */ 13 | export function create() { 14 | let out = new glMatrix.ARRAY_TYPE(4); 15 | if(glMatrix.ARRAY_TYPE != Float32Array) { 16 | out[1] = 0; 17 | out[2] = 0; 18 | } 19 | out[0] = 1; 20 | out[3] = 1; 21 | return out; 22 | } 23 | 24 | /** 25 | * Creates a new mat2 initialized with values from an existing matrix 26 | * 27 | * @param {mat2} a matrix to clone 28 | * @returns {mat2} a new 2x2 matrix 29 | */ 30 | export function clone(a) { 31 | let out = new glMatrix.ARRAY_TYPE(4); 32 | out[0] = a[0]; 33 | out[1] = a[1]; 34 | out[2] = a[2]; 35 | out[3] = a[3]; 36 | return out; 37 | } 38 | 39 | /** 40 | * Copy the values from one mat2 to another 41 | * 42 | * @param {mat2} out the receiving matrix 43 | * @param {mat2} a the source matrix 44 | * @returns {mat2} out 45 | */ 46 | export function copy(out, a) { 47 | out[0] = a[0]; 48 | out[1] = a[1]; 49 | out[2] = a[2]; 50 | out[3] = a[3]; 51 | return out; 52 | } 53 | 54 | /** 55 | * Set a mat2 to the identity matrix 56 | * 57 | * @param {mat2} out the receiving matrix 58 | * @returns {mat2} out 59 | */ 60 | export function identity(out) { 61 | out[0] = 1; 62 | out[1] = 0; 63 | out[2] = 0; 64 | out[3] = 1; 65 | return out; 66 | } 67 | 68 | /** 69 | * Create a new mat2 with the given values 70 | * 71 | * @param {Number} m00 Component in column 0, row 0 position (index 0) 72 | * @param {Number} m01 Component in column 0, row 1 position (index 1) 73 | * @param {Number} m10 Component in column 1, row 0 position (index 2) 74 | * @param {Number} m11 Component in column 1, row 1 position (index 3) 75 | * @returns {mat2} out A new 2x2 matrix 76 | */ 77 | export function fromValues(m00, m01, m10, m11) { 78 | let out = new glMatrix.ARRAY_TYPE(4); 79 | out[0] = m00; 80 | out[1] = m01; 81 | out[2] = m10; 82 | out[3] = m11; 83 | return out; 84 | } 85 | 86 | /** 87 | * Set the components of a mat2 to the given values 88 | * 89 | * @param {mat2} out the receiving matrix 90 | * @param {Number} m00 Component in column 0, row 0 position (index 0) 91 | * @param {Number} m01 Component in column 0, row 1 position (index 1) 92 | * @param {Number} m10 Component in column 1, row 0 position (index 2) 93 | * @param {Number} m11 Component in column 1, row 1 position (index 3) 94 | * @returns {mat2} out 95 | */ 96 | export function set(out, m00, m01, m10, m11) { 97 | out[0] = m00; 98 | out[1] = m01; 99 | out[2] = m10; 100 | out[3] = m11; 101 | return out; 102 | } 103 | 104 | /** 105 | * Transpose the values of a mat2 106 | * 107 | * @param {mat2} out the receiving matrix 108 | * @param {mat2} a the source matrix 109 | * @returns {mat2} out 110 | */ 111 | export function transpose(out, a) { 112 | // If we are transposing ourselves we can skip a few steps but have to cache 113 | // some values 114 | if (out === a) { 115 | let a1 = a[1]; 116 | out[1] = a[2]; 117 | out[2] = a1; 118 | } else { 119 | out[0] = a[0]; 120 | out[1] = a[2]; 121 | out[2] = a[1]; 122 | out[3] = a[3]; 123 | } 124 | 125 | return out; 126 | } 127 | 128 | /** 129 | * Inverts a mat2 130 | * 131 | * @param {mat2} out the receiving matrix 132 | * @param {mat2} a the source matrix 133 | * @returns {mat2} out 134 | */ 135 | export function invert(out, a) { 136 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 137 | 138 | // Calculate the determinant 139 | let det = a0 * a3 - a2 * a1; 140 | 141 | if (!det) { 142 | return null; 143 | } 144 | det = 1.0 / det; 145 | 146 | out[0] = a3 * det; 147 | out[1] = -a1 * det; 148 | out[2] = -a2 * det; 149 | out[3] = a0 * det; 150 | 151 | return out; 152 | } 153 | 154 | /** 155 | * Calculates the adjugate of a mat2 156 | * 157 | * @param {mat2} out the receiving matrix 158 | * @param {mat2} a the source matrix 159 | * @returns {mat2} out 160 | */ 161 | export function adjoint(out, a) { 162 | // Caching this value is nessecary if out == a 163 | let a0 = a[0]; 164 | out[0] = a[3]; 165 | out[1] = -a[1]; 166 | out[2] = -a[2]; 167 | out[3] = a0; 168 | 169 | return out; 170 | } 171 | 172 | /** 173 | * Calculates the determinant of a mat2 174 | * 175 | * @param {mat2} a the source matrix 176 | * @returns {Number} determinant of a 177 | */ 178 | export function determinant(a) { 179 | return a[0] * a[3] - a[2] * a[1]; 180 | } 181 | 182 | /** 183 | * Multiplies two mat2's 184 | * 185 | * @param {mat2} out the receiving matrix 186 | * @param {mat2} a the first operand 187 | * @param {mat2} b the second operand 188 | * @returns {mat2} out 189 | */ 190 | export function multiply(out, a, b) { 191 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 192 | let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; 193 | out[0] = a0 * b0 + a2 * b1; 194 | out[1] = a1 * b0 + a3 * b1; 195 | out[2] = a0 * b2 + a2 * b3; 196 | out[3] = a1 * b2 + a3 * b3; 197 | return out; 198 | } 199 | 200 | /** 201 | * Rotates a mat2 by the given angle 202 | * 203 | * @param {mat2} out the receiving matrix 204 | * @param {mat2} a the matrix to rotate 205 | * @param {Number} rad the angle to rotate the matrix by 206 | * @returns {mat2} out 207 | */ 208 | export function rotate(out, a, rad) { 209 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 210 | let s = Math.sin(rad); 211 | let c = Math.cos(rad); 212 | out[0] = a0 * c + a2 * s; 213 | out[1] = a1 * c + a3 * s; 214 | out[2] = a0 * -s + a2 * c; 215 | out[3] = a1 * -s + a3 * c; 216 | return out; 217 | } 218 | 219 | /** 220 | * Scales the mat2 by the dimensions in the given vec2 221 | * 222 | * @param {mat2} out the receiving matrix 223 | * @param {mat2} a the matrix to rotate 224 | * @param {vec2} v the vec2 to scale the matrix by 225 | * @returns {mat2} out 226 | **/ 227 | export function scale(out, a, v) { 228 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 229 | let v0 = v[0], v1 = v[1]; 230 | out[0] = a0 * v0; 231 | out[1] = a1 * v0; 232 | out[2] = a2 * v1; 233 | out[3] = a3 * v1; 234 | return out; 235 | } 236 | 237 | /** 238 | * Creates a matrix from a given angle 239 | * This is equivalent to (but much faster than): 240 | * 241 | * mat2.identity(dest); 242 | * mat2.rotate(dest, dest, rad); 243 | * 244 | * @param {mat2} out mat2 receiving operation result 245 | * @param {Number} rad the angle to rotate the matrix by 246 | * @returns {mat2} out 247 | */ 248 | export function fromRotation(out, rad) { 249 | let s = Math.sin(rad); 250 | let c = Math.cos(rad); 251 | out[0] = c; 252 | out[1] = s; 253 | out[2] = -s; 254 | out[3] = c; 255 | return out; 256 | } 257 | 258 | /** 259 | * Creates a matrix from a vector scaling 260 | * This is equivalent to (but much faster than): 261 | * 262 | * mat2.identity(dest); 263 | * mat2.scale(dest, dest, vec); 264 | * 265 | * @param {mat2} out mat2 receiving operation result 266 | * @param {vec2} v Scaling vector 267 | * @returns {mat2} out 268 | */ 269 | export function fromScaling(out, v) { 270 | out[0] = v[0]; 271 | out[1] = 0; 272 | out[2] = 0; 273 | out[3] = v[1]; 274 | return out; 275 | } 276 | 277 | /** 278 | * Returns a string representation of a mat2 279 | * 280 | * @param {mat2} a matrix to represent as a string 281 | * @returns {String} string representation of the matrix 282 | */ 283 | export function str(a) { 284 | return 'mat2(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')'; 285 | } 286 | 287 | /** 288 | * Returns Frobenius norm of a mat2 289 | * 290 | * @param {mat2} a the matrix to calculate Frobenius norm of 291 | * @returns {Number} Frobenius norm 292 | */ 293 | export function frob(a) { 294 | return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2))) 295 | } 296 | 297 | /** 298 | * Returns L, D and U matrices (Lower triangular, Diagonal and Upper triangular) by factorizing the input matrix 299 | * @param {mat2} L the lower triangular matrix 300 | * @param {mat2} D the diagonal matrix 301 | * @param {mat2} U the upper triangular matrix 302 | * @param {mat2} a the input matrix to factorize 303 | */ 304 | 305 | export function LDU(L, D, U, a) { 306 | L[2] = a[2]/a[0]; 307 | U[0] = a[0]; 308 | U[1] = a[1]; 309 | U[3] = a[3] - L[2] * U[1]; 310 | return [L, D, U]; 311 | } 312 | 313 | /** 314 | * Adds two mat2's 315 | * 316 | * @param {mat2} out the receiving matrix 317 | * @param {mat2} a the first operand 318 | * @param {mat2} b the second operand 319 | * @returns {mat2} out 320 | */ 321 | export function add(out, a, b) { 322 | out[0] = a[0] + b[0]; 323 | out[1] = a[1] + b[1]; 324 | out[2] = a[2] + b[2]; 325 | out[3] = a[3] + b[3]; 326 | return out; 327 | } 328 | 329 | /** 330 | * Subtracts matrix b from matrix a 331 | * 332 | * @param {mat2} out the receiving matrix 333 | * @param {mat2} a the first operand 334 | * @param {mat2} b the second operand 335 | * @returns {mat2} out 336 | */ 337 | export function subtract(out, a, b) { 338 | out[0] = a[0] - b[0]; 339 | out[1] = a[1] - b[1]; 340 | out[2] = a[2] - b[2]; 341 | out[3] = a[3] - b[3]; 342 | return out; 343 | } 344 | 345 | /** 346 | * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) 347 | * 348 | * @param {mat2} a The first matrix. 349 | * @param {mat2} b The second matrix. 350 | * @returns {Boolean} True if the matrices are equal, false otherwise. 351 | */ 352 | export function exactEquals(a, b) { 353 | return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; 354 | } 355 | 356 | /** 357 | * Returns whether or not the matrices have approximately the same elements in the same position. 358 | * 359 | * @param {mat2} a The first matrix. 360 | * @param {mat2} b The second matrix. 361 | * @returns {Boolean} True if the matrices are equal, false otherwise. 362 | */ 363 | export function equals(a, b) { 364 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 365 | let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; 366 | return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) && 367 | Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) && 368 | Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)) && 369 | Math.abs(a3 - b3) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a3), Math.abs(b3))); 370 | } 371 | 372 | /** 373 | * Multiply each element of the matrix by a scalar. 374 | * 375 | * @param {mat2} out the receiving matrix 376 | * @param {mat2} a the matrix to scale 377 | * @param {Number} b amount to scale the matrix's elements by 378 | * @returns {mat2} out 379 | */ 380 | export function multiplyScalar(out, a, b) { 381 | out[0] = a[0] * b; 382 | out[1] = a[1] * b; 383 | out[2] = a[2] * b; 384 | out[3] = a[3] * b; 385 | return out; 386 | } 387 | 388 | /** 389 | * Adds two mat2's after multiplying each element of the second operand by a scalar value. 390 | * 391 | * @param {mat2} out the receiving vector 392 | * @param {mat2} a the first operand 393 | * @param {mat2} b the second operand 394 | * @param {Number} scale the amount to scale b's elements by before adding 395 | * @returns {mat2} out 396 | */ 397 | export function multiplyScalarAndAdd(out, a, b, scale) { 398 | out[0] = a[0] + (b[0] * scale); 399 | out[1] = a[1] + (b[1] * scale); 400 | out[2] = a[2] + (b[2] * scale); 401 | out[3] = a[3] + (b[3] * scale); 402 | return out; 403 | } 404 | 405 | /** 406 | * Alias for {@link mat2.multiply} 407 | * @function 408 | */ 409 | export const mul = multiply; 410 | 411 | /** 412 | * Alias for {@link mat2.subtract} 413 | * @function 414 | */ 415 | export const sub = subtract; 416 | -------------------------------------------------------------------------------- /d3d12-bug/gl-matrix/src/gl-matrix/mat2d.js: -------------------------------------------------------------------------------- 1 | import * as glMatrix from "./common.js"; 2 | 3 | /** 4 | * 2x3 Matrix 5 | * @module mat2d 6 | * 7 | * @description 8 | * A mat2d contains six elements defined as: 9 | *
 10 |  * [a, c, tx,
 11 |  *  b, d, ty]
 12 |  * 
13 | * This is a short form for the 3x3 matrix: 14 | *
 15 |  * [a, c, tx,
 16 |  *  b, d, ty,
 17 |  *  0, 0, 1]
 18 |  * 
19 | * The last row is ignored so the array is shorter and operations are faster. 20 | */ 21 | 22 | /** 23 | * Creates a new identity mat2d 24 | * 25 | * @returns {mat2d} a new 2x3 matrix 26 | */ 27 | export function create() { 28 | let out = new glMatrix.ARRAY_TYPE(6); 29 | if(glMatrix.ARRAY_TYPE != Float32Array) { 30 | out[1] = 0; 31 | out[2] = 0; 32 | out[4] = 0; 33 | out[5] = 0; 34 | } 35 | out[0] = 1; 36 | out[3] = 1; 37 | return out; 38 | } 39 | 40 | /** 41 | * Creates a new mat2d initialized with values from an existing matrix 42 | * 43 | * @param {mat2d} a matrix to clone 44 | * @returns {mat2d} a new 2x3 matrix 45 | */ 46 | export function clone(a) { 47 | let out = new glMatrix.ARRAY_TYPE(6); 48 | out[0] = a[0]; 49 | out[1] = a[1]; 50 | out[2] = a[2]; 51 | out[3] = a[3]; 52 | out[4] = a[4]; 53 | out[5] = a[5]; 54 | return out; 55 | } 56 | 57 | /** 58 | * Copy the values from one mat2d to another 59 | * 60 | * @param {mat2d} out the receiving matrix 61 | * @param {mat2d} a the source matrix 62 | * @returns {mat2d} out 63 | */ 64 | export function copy(out, a) { 65 | out[0] = a[0]; 66 | out[1] = a[1]; 67 | out[2] = a[2]; 68 | out[3] = a[3]; 69 | out[4] = a[4]; 70 | out[5] = a[5]; 71 | return out; 72 | } 73 | 74 | /** 75 | * Set a mat2d to the identity matrix 76 | * 77 | * @param {mat2d} out the receiving matrix 78 | * @returns {mat2d} out 79 | */ 80 | export function identity(out) { 81 | out[0] = 1; 82 | out[1] = 0; 83 | out[2] = 0; 84 | out[3] = 1; 85 | out[4] = 0; 86 | out[5] = 0; 87 | return out; 88 | } 89 | 90 | /** 91 | * Create a new mat2d with the given values 92 | * 93 | * @param {Number} a Component A (index 0) 94 | * @param {Number} b Component B (index 1) 95 | * @param {Number} c Component C (index 2) 96 | * @param {Number} d Component D (index 3) 97 | * @param {Number} tx Component TX (index 4) 98 | * @param {Number} ty Component TY (index 5) 99 | * @returns {mat2d} A new mat2d 100 | */ 101 | export function fromValues(a, b, c, d, tx, ty) { 102 | let out = new glMatrix.ARRAY_TYPE(6); 103 | out[0] = a; 104 | out[1] = b; 105 | out[2] = c; 106 | out[3] = d; 107 | out[4] = tx; 108 | out[5] = ty; 109 | return out; 110 | } 111 | 112 | /** 113 | * Set the components of a mat2d to the given values 114 | * 115 | * @param {mat2d} out the receiving matrix 116 | * @param {Number} a Component A (index 0) 117 | * @param {Number} b Component B (index 1) 118 | * @param {Number} c Component C (index 2) 119 | * @param {Number} d Component D (index 3) 120 | * @param {Number} tx Component TX (index 4) 121 | * @param {Number} ty Component TY (index 5) 122 | * @returns {mat2d} out 123 | */ 124 | export function set(out, a, b, c, d, tx, ty) { 125 | out[0] = a; 126 | out[1] = b; 127 | out[2] = c; 128 | out[3] = d; 129 | out[4] = tx; 130 | out[5] = ty; 131 | return out; 132 | } 133 | 134 | /** 135 | * Inverts a mat2d 136 | * 137 | * @param {mat2d} out the receiving matrix 138 | * @param {mat2d} a the source matrix 139 | * @returns {mat2d} out 140 | */ 141 | export function invert(out, a) { 142 | let aa = a[0], ab = a[1], ac = a[2], ad = a[3]; 143 | let atx = a[4], aty = a[5]; 144 | 145 | let det = aa * ad - ab * ac; 146 | if(!det){ 147 | return null; 148 | } 149 | det = 1.0 / det; 150 | 151 | out[0] = ad * det; 152 | out[1] = -ab * det; 153 | out[2] = -ac * det; 154 | out[3] = aa * det; 155 | out[4] = (ac * aty - ad * atx) * det; 156 | out[5] = (ab * atx - aa * aty) * det; 157 | return out; 158 | } 159 | 160 | /** 161 | * Calculates the determinant of a mat2d 162 | * 163 | * @param {mat2d} a the source matrix 164 | * @returns {Number} determinant of a 165 | */ 166 | export function determinant(a) { 167 | return a[0] * a[3] - a[1] * a[2]; 168 | } 169 | 170 | /** 171 | * Multiplies two mat2d's 172 | * 173 | * @param {mat2d} out the receiving matrix 174 | * @param {mat2d} a the first operand 175 | * @param {mat2d} b the second operand 176 | * @returns {mat2d} out 177 | */ 178 | export function multiply(out, a, b) { 179 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5]; 180 | let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5]; 181 | out[0] = a0 * b0 + a2 * b1; 182 | out[1] = a1 * b0 + a3 * b1; 183 | out[2] = a0 * b2 + a2 * b3; 184 | out[3] = a1 * b2 + a3 * b3; 185 | out[4] = a0 * b4 + a2 * b5 + a4; 186 | out[5] = a1 * b4 + a3 * b5 + a5; 187 | return out; 188 | } 189 | 190 | /** 191 | * Rotates a mat2d by the given angle 192 | * 193 | * @param {mat2d} out the receiving matrix 194 | * @param {mat2d} a the matrix to rotate 195 | * @param {Number} rad the angle to rotate the matrix by 196 | * @returns {mat2d} out 197 | */ 198 | export function rotate(out, a, rad) { 199 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5]; 200 | let s = Math.sin(rad); 201 | let c = Math.cos(rad); 202 | out[0] = a0 * c + a2 * s; 203 | out[1] = a1 * c + a3 * s; 204 | out[2] = a0 * -s + a2 * c; 205 | out[3] = a1 * -s + a3 * c; 206 | out[4] = a4; 207 | out[5] = a5; 208 | return out; 209 | } 210 | 211 | /** 212 | * Scales the mat2d by the dimensions in the given vec2 213 | * 214 | * @param {mat2d} out the receiving matrix 215 | * @param {mat2d} a the matrix to translate 216 | * @param {vec2} v the vec2 to scale the matrix by 217 | * @returns {mat2d} out 218 | **/ 219 | export function scale(out, a, v) { 220 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5]; 221 | let v0 = v[0], v1 = v[1]; 222 | out[0] = a0 * v0; 223 | out[1] = a1 * v0; 224 | out[2] = a2 * v1; 225 | out[3] = a3 * v1; 226 | out[4] = a4; 227 | out[5] = a5; 228 | return out; 229 | } 230 | 231 | /** 232 | * Translates the mat2d by the dimensions in the given vec2 233 | * 234 | * @param {mat2d} out the receiving matrix 235 | * @param {mat2d} a the matrix to translate 236 | * @param {vec2} v the vec2 to translate the matrix by 237 | * @returns {mat2d} out 238 | **/ 239 | export function translate(out, a, v) { 240 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5]; 241 | let v0 = v[0], v1 = v[1]; 242 | out[0] = a0; 243 | out[1] = a1; 244 | out[2] = a2; 245 | out[3] = a3; 246 | out[4] = a0 * v0 + a2 * v1 + a4; 247 | out[5] = a1 * v0 + a3 * v1 + a5; 248 | return out; 249 | } 250 | 251 | /** 252 | * Creates a matrix from a given angle 253 | * This is equivalent to (but much faster than): 254 | * 255 | * mat2d.identity(dest); 256 | * mat2d.rotate(dest, dest, rad); 257 | * 258 | * @param {mat2d} out mat2d receiving operation result 259 | * @param {Number} rad the angle to rotate the matrix by 260 | * @returns {mat2d} out 261 | */ 262 | export function fromRotation(out, rad) { 263 | let s = Math.sin(rad), c = Math.cos(rad); 264 | out[0] = c; 265 | out[1] = s; 266 | out[2] = -s; 267 | out[3] = c; 268 | out[4] = 0; 269 | out[5] = 0; 270 | return out; 271 | } 272 | 273 | /** 274 | * Creates a matrix from a vector scaling 275 | * This is equivalent to (but much faster than): 276 | * 277 | * mat2d.identity(dest); 278 | * mat2d.scale(dest, dest, vec); 279 | * 280 | * @param {mat2d} out mat2d receiving operation result 281 | * @param {vec2} v Scaling vector 282 | * @returns {mat2d} out 283 | */ 284 | export function fromScaling(out, v) { 285 | out[0] = v[0]; 286 | out[1] = 0; 287 | out[2] = 0; 288 | out[3] = v[1]; 289 | out[4] = 0; 290 | out[5] = 0; 291 | return out; 292 | } 293 | 294 | /** 295 | * Creates a matrix from a vector translation 296 | * This is equivalent to (but much faster than): 297 | * 298 | * mat2d.identity(dest); 299 | * mat2d.translate(dest, dest, vec); 300 | * 301 | * @param {mat2d} out mat2d receiving operation result 302 | * @param {vec2} v Translation vector 303 | * @returns {mat2d} out 304 | */ 305 | export function fromTranslation(out, v) { 306 | out[0] = 1; 307 | out[1] = 0; 308 | out[2] = 0; 309 | out[3] = 1; 310 | out[4] = v[0]; 311 | out[5] = v[1]; 312 | return out; 313 | } 314 | 315 | /** 316 | * Returns a string representation of a mat2d 317 | * 318 | * @param {mat2d} a matrix to represent as a string 319 | * @returns {String} string representation of the matrix 320 | */ 321 | export function str(a) { 322 | return 'mat2d(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + 323 | a[3] + ', ' + a[4] + ', ' + a[5] + ')'; 324 | } 325 | 326 | /** 327 | * Returns Frobenius norm of a mat2d 328 | * 329 | * @param {mat2d} a the matrix to calculate Frobenius norm of 330 | * @returns {Number} Frobenius norm 331 | */ 332 | export function frob(a) { 333 | return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + 1)) 334 | } 335 | 336 | /** 337 | * Adds two mat2d's 338 | * 339 | * @param {mat2d} out the receiving matrix 340 | * @param {mat2d} a the first operand 341 | * @param {mat2d} b the second operand 342 | * @returns {mat2d} out 343 | */ 344 | export function add(out, a, b) { 345 | out[0] = a[0] + b[0]; 346 | out[1] = a[1] + b[1]; 347 | out[2] = a[2] + b[2]; 348 | out[3] = a[3] + b[3]; 349 | out[4] = a[4] + b[4]; 350 | out[5] = a[5] + b[5]; 351 | return out; 352 | } 353 | 354 | /** 355 | * Subtracts matrix b from matrix a 356 | * 357 | * @param {mat2d} out the receiving matrix 358 | * @param {mat2d} a the first operand 359 | * @param {mat2d} b the second operand 360 | * @returns {mat2d} out 361 | */ 362 | export function subtract(out, a, b) { 363 | out[0] = a[0] - b[0]; 364 | out[1] = a[1] - b[1]; 365 | out[2] = a[2] - b[2]; 366 | out[3] = a[3] - b[3]; 367 | out[4] = a[4] - b[4]; 368 | out[5] = a[5] - b[5]; 369 | return out; 370 | } 371 | 372 | /** 373 | * Multiply each element of the matrix by a scalar. 374 | * 375 | * @param {mat2d} out the receiving matrix 376 | * @param {mat2d} a the matrix to scale 377 | * @param {Number} b amount to scale the matrix's elements by 378 | * @returns {mat2d} out 379 | */ 380 | export function multiplyScalar(out, a, b) { 381 | out[0] = a[0] * b; 382 | out[1] = a[1] * b; 383 | out[2] = a[2] * b; 384 | out[3] = a[3] * b; 385 | out[4] = a[4] * b; 386 | out[5] = a[5] * b; 387 | return out; 388 | } 389 | 390 | /** 391 | * Adds two mat2d's after multiplying each element of the second operand by a scalar value. 392 | * 393 | * @param {mat2d} out the receiving vector 394 | * @param {mat2d} a the first operand 395 | * @param {mat2d} b the second operand 396 | * @param {Number} scale the amount to scale b's elements by before adding 397 | * @returns {mat2d} out 398 | */ 399 | export function multiplyScalarAndAdd(out, a, b, scale) { 400 | out[0] = a[0] + (b[0] * scale); 401 | out[1] = a[1] + (b[1] * scale); 402 | out[2] = a[2] + (b[2] * scale); 403 | out[3] = a[3] + (b[3] * scale); 404 | out[4] = a[4] + (b[4] * scale); 405 | out[5] = a[5] + (b[5] * scale); 406 | return out; 407 | } 408 | 409 | /** 410 | * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) 411 | * 412 | * @param {mat2d} a The first matrix. 413 | * @param {mat2d} b The second matrix. 414 | * @returns {Boolean} True if the matrices are equal, false otherwise. 415 | */ 416 | export function exactEquals(a, b) { 417 | return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5]; 418 | } 419 | 420 | /** 421 | * Returns whether or not the matrices have approximately the same elements in the same position. 422 | * 423 | * @param {mat2d} a The first matrix. 424 | * @param {mat2d} b The second matrix. 425 | * @returns {Boolean} True if the matrices are equal, false otherwise. 426 | */ 427 | export function equals(a, b) { 428 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5]; 429 | let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5]; 430 | return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) && 431 | Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) && 432 | Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)) && 433 | Math.abs(a3 - b3) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a3), Math.abs(b3)) && 434 | Math.abs(a4 - b4) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a4), Math.abs(b4)) && 435 | Math.abs(a5 - b5) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a5), Math.abs(b5))); 436 | } 437 | 438 | /** 439 | * Alias for {@link mat2d.multiply} 440 | * @function 441 | */ 442 | export const mul = multiply; 443 | 444 | /** 445 | * Alias for {@link mat2d.subtract} 446 | * @function 447 | */ 448 | export const sub = subtract; 449 | -------------------------------------------------------------------------------- /d3d12-bug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | WebGPU Dissapearing Geometry Bug Repro 11 | 12 | 33 | 34 | 35 | 36 | 325 | 326 | 327 | -------------------------------------------------------------------------------- /device-loss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | WebGPU test 14 | 15 | 29 | 30 | 31 | 32 | 33 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/favicon-16.png -------------------------------------------------------------------------------- /favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/favicon-32.png -------------------------------------------------------------------------------- /favicon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/favicon-48.png -------------------------------------------------------------------------------- /flashing-bug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | WebGPU test 11 | 12 | 13 | 14 |
15 | 16 |

WARNING: Clicking on the toggle button may cause either minor flickering or intense flashing.

17 | 311 | 312 | -------------------------------------------------------------------------------- /iframe-raf/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | iframe rAF test page 6 | 7 | 8 | same-origin iframe 9 | 30 | 31 | -------------------------------------------------------------------------------- /iframe-raf/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Particle System - WebGPU 11 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 56 | 57 | -------------------------------------------------------------------------------- /iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | WebGPU in an iframe test 14 | 15 | 41 | 42 | 43 |
44 | 45 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Web Graphics API Tester 14 | 15 | 42 | 43 | 44 | 50 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /js/camera.js: -------------------------------------------------------------------------------- 1 | import { vec3, mat4 } from './third-party/gl-matrix/src/gl-matrix.js'; 2 | 3 | const DIR = vec3.create(); 4 | 5 | export class FlyingCamera { 6 | constructor() { 7 | this._element = null; 8 | this._angles = vec3.create(); 9 | this._position = vec3.create(); 10 | this._viewMat = mat4.create(); 11 | this._rotMat = mat4.create(); 12 | this._dirty = true; 13 | 14 | this.speed = 3; 15 | 16 | this._pressedKeys = new Array(128); 17 | window.addEventListener('keydown', (event) => { 18 | this._pressedKeys[event.keyCode] = true; 19 | }); 20 | window.addEventListener('keyup', (event) => { 21 | this._pressedKeys[event.keyCode] = false; 22 | }); 23 | 24 | let moving = false; 25 | let lastX, lastY; 26 | this.mousedownCallback = (event) => { 27 | if (event.isPrimary) { 28 | moving = true; 29 | } 30 | lastX = event.pageX; 31 | lastY = event.pageY; 32 | }; 33 | this.mousemoveCallback = (event) => { 34 | let xDelta, yDelta; 35 | 36 | if(document.pointerLockEnabled) { 37 | xDelta = event.movementX; 38 | yDelta = event.movementY; 39 | this.rotateView(xDelta * 0.025, yDelta * 0.025); 40 | } else if (moving) { 41 | xDelta = event.pageX - lastX; 42 | yDelta = event.pageY - lastY; 43 | lastX = event.pageX; 44 | lastY = event.pageY; 45 | this.rotateView(xDelta * 0.025, yDelta * 0.025); 46 | } 47 | }; 48 | this.mouseupCallback = (event) => { 49 | if (event.isPrimary) { 50 | moving = false; 51 | } 52 | }; 53 | 54 | let lastFrameTime = -1; 55 | this.frameCallback = (timestamp) => { 56 | if (lastFrameTime == -1) { 57 | lastFrameTime = timestamp; 58 | } else { 59 | this.update(timestamp - lastFrameTime); 60 | lastFrameTime = timestamp; 61 | } 62 | requestAnimationFrame(this.frameCallback); 63 | } 64 | requestAnimationFrame(this.frameCallback); 65 | } 66 | 67 | set element(value) { 68 | if (this._element && this._element != value) { 69 | this._element.removeEventListener('pointerdown', this.mousedownCallback); 70 | this._element.removeEventListener('pointermove', this.mousemoveCallback); 71 | this._element.removeEventListener('pointerup', this.mouseupCallback); 72 | } 73 | 74 | this._element = value; 75 | if (this._element) { 76 | this._element.addEventListener('pointerdown', this.mousedownCallback); 77 | this._element.addEventListener('pointermove', this.mousemoveCallback); 78 | this._element.addEventListener('pointerup', this.mouseupCallback); 79 | } 80 | } 81 | 82 | get element() { 83 | return this._element; 84 | } 85 | 86 | rotateView(xDelta, yDelta) { 87 | let rot = this._rotMat; 88 | 89 | if(xDelta || yDelta) { 90 | this._angles[1] += xDelta; 91 | // Keep our rotation in the range of [0, 2*PI] 92 | // (Prevents numeric instability if you spin around a LOT.) 93 | while (this._angles[1] < 0) { 94 | this._angles[1] += Math.PI * 2.0; 95 | } 96 | while (this._angles[1] >= Math.PI * 2.0) { 97 | this._angles[1] -= Math.PI * 2.0; 98 | } 99 | 100 | this._angles[0] += yDelta; 101 | // Clamp the up/down rotation to prevent us from flipping upside-down 102 | if (this._angles[0] < -Math.PI * 0.5) { 103 | this._angles[0] = -Math.PI * 0.5; 104 | } 105 | if (this._angles[0] > Math.PI * 0.5) { 106 | this._angles[0] = Math.PI * 0.5; 107 | } 108 | 109 | // Update the directional matrix 110 | mat4.identity(rot); 111 | 112 | mat4.rotateY(rot, rot, -this._angles[1]); 113 | mat4.rotateX(rot, rot, -this._angles[0]); 114 | 115 | this._dirty = true; 116 | } 117 | } 118 | 119 | set position(value) { 120 | vec3.copy(this._position, value); 121 | this._dirty = true; 122 | } 123 | 124 | get position() { 125 | return this._position; 126 | } 127 | 128 | get viewMatrix() { 129 | if (this._dirty) { 130 | let mv = this._viewMat; 131 | mat4.identity(mv); 132 | 133 | //mat4.rotateX(mv, mv, -Math.PI * 0.5); 134 | mat4.rotateX(mv, mv, this._angles[0]); 135 | mat4.rotateY(mv, mv, this._angles[1]); 136 | mat4.translate(mv, mv, [-this._position[0], -this._position[1], -this._position[2]]); 137 | this._dirty = false; 138 | } 139 | 140 | return this._viewMat; 141 | } 142 | 143 | update(frameTime) { 144 | if (!this._element) return; 145 | 146 | const speed = (this.speed / 1000) * frameTime; 147 | 148 | vec3.set(DIR, 0, 0, 0); 149 | 150 | // This is our first person movement code. It's not really pretty, but it works 151 | if (this._pressedKeys['W'.charCodeAt(0)]) { 152 | DIR[2] -= speed; 153 | } 154 | if (this._pressedKeys['S'.charCodeAt(0)]) { 155 | DIR[2] += speed; 156 | } 157 | if (this._pressedKeys['A'.charCodeAt(0)]) { 158 | DIR[0] -= speed; 159 | } 160 | if (this._pressedKeys['D'.charCodeAt(0)]) { 161 | DIR[0] += speed; 162 | } 163 | if (this._pressedKeys[32]) { // Space, moves up 164 | DIR[1] += speed; 165 | } 166 | if (this._pressedKeys[17]) { // Ctrl, moves down 167 | DIR[1] -= speed; 168 | } 169 | 170 | if (DIR[0] !== 0 || DIR[1] !== 0 || DIR[2] !== 0) { 171 | // Move the camera in the direction we are facing 172 | vec3.transformMat4(DIR, DIR, this._rotMat); 173 | vec3.add(this._position, this._position, DIR); 174 | 175 | this._dirty = true; 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /js/pbr-shader.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Brandon Jones 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | export const ATTRIB_MAP = { 22 | POSITION: 1, 23 | NORMAL: 2, 24 | TANGENT: 3, 25 | TEXCOORD_0: 4, 26 | COLOR_0: 5, 27 | }; 28 | 29 | export const SAMPLER_MAP = { 30 | baseColorTexture: 0, 31 | normalTexture: 1, 32 | metallicRoughnessTexture: 2, 33 | emissiveTexture: 3, 34 | occlusionTexture: 4 35 | }; 36 | 37 | export const UNIFORM_BLOCKS = { 38 | FrameUniforms: 0, 39 | MaterialUniforms: 1, 40 | PrimitiveUniforms: 2, 41 | LightUniforms: 3 42 | }; 43 | 44 | const WEBGL_ATTRIBUTES = ` 45 | attribute vec3 POSITION; 46 | attribute vec3 NORMAL; 47 | attribute vec4 TANGENT; 48 | attribute vec2 TEXCOORD_0; 49 | attribute vec4 COLOR_0; 50 | `; 51 | 52 | const ATTRIBUTES_WITH_LAYOUT = ` 53 | layout(location = ${ATTRIB_MAP.POSITION}) in vec3 POSITION; 54 | layout(location = ${ATTRIB_MAP.NORMAL}) in vec3 NORMAL; 55 | #ifdef USE_NORMAL_MAP 56 | layout(location = ${ATTRIB_MAP.TANGENT}) in vec4 TANGENT; 57 | #endif 58 | layout(location = ${ATTRIB_MAP.TEXCOORD_0}) in vec2 TEXCOORD_0; 59 | #ifdef USE_VERTEX_COLOR 60 | layout(location = ${ATTRIB_MAP.COLOR_0}) in vec4 COLOR_0; 61 | #endif 62 | `; 63 | 64 | function WEBGL_VARYINGS(type = 'varying') { 65 | return ` 66 | ${type} vec3 vWorldPos; 67 | ${type} vec3 vView; // Vector from vertex to camera. 68 | ${type} vec2 vTex; 69 | ${type} vec4 vCol; 70 | 71 | #ifdef USE_NORMAL_MAP 72 | ${type} mat3 vTBN; 73 | #else 74 | ${type} vec3 vNorm; 75 | #endif 76 | `; 77 | } 78 | 79 | const WEBGL_VERTEX_UNIFORMS = ` 80 | uniform mat4 projectionMatrix; 81 | uniform mat4 viewMatrix; 82 | uniform vec3 cameraPosition; 83 | 84 | uniform mat4 modelMatrix; 85 | `; 86 | 87 | const WEBGL2_VERTEX_UNIFORMS = ` 88 | layout(std140) uniform FrameUniforms 89 | { 90 | mat4 projectionMatrix; 91 | mat4 viewMatrix; 92 | vec3 cameraPosition; 93 | }; 94 | 95 | uniform mat4 modelMatrix; 96 | `; 97 | 98 | const WEBGL_FRAGMENT_UNIFORMS = ` 99 | uniform vec4 baseColorFactor; 100 | uniform vec2 metallicRoughnessFactor; 101 | uniform vec3 emissiveFactor; 102 | uniform float occlusionStrength; 103 | 104 | uniform sampler2D baseColorTexture; 105 | uniform sampler2D normalTexture; 106 | uniform sampler2D metallicRoughnessTexture; 107 | uniform sampler2D occlusionTexture; 108 | uniform sampler2D emissiveTexture; 109 | 110 | struct Light { 111 | vec4 position; 112 | vec4 color; 113 | }; 114 | 115 | uniform Light lights[LIGHT_COUNT]; 116 | uniform float lightAmbient; 117 | `; 118 | 119 | const WEBGL2_FRAGMENT_UNIFORMS = ` 120 | layout(std140) uniform MaterialUniforms { 121 | vec4 baseColorFactor; 122 | vec2 metallicRoughnessFactor; 123 | vec3 emissiveFactor; 124 | float occlusionStrength; 125 | }; 126 | 127 | uniform sampler2D baseColorTexture; 128 | uniform sampler2D normalTexture; 129 | uniform sampler2D metallicRoughnessTexture; 130 | uniform sampler2D occlusionTexture; 131 | uniform sampler2D emissiveTexture; 132 | 133 | struct Light { 134 | vec4 position; 135 | vec4 color; 136 | }; 137 | 138 | layout(std140) uniform LightUniforms { 139 | Light lights[LIGHT_COUNT]; 140 | float lightAmbient; 141 | }; 142 | `; 143 | 144 | function WEBGL_TEXTURE(texture, texcoord) { 145 | return `texture2D(${texture}, ${texcoord})`; 146 | } 147 | 148 | function WEBGL2_TEXTURE(texture, texcoord) { 149 | return `texture(${texture}, ${texcoord})`; 150 | } 151 | 152 | // Much of the shader used here was pulled from https://learnopengl.com/PBR/Lighting 153 | // Thanks! 154 | const PBR_FUNCTIONS = ` 155 | const float PI = 3.14159265359; 156 | 157 | vec3 FresnelSchlick(float cosTheta, vec3 F0) { 158 | return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); 159 | } 160 | 161 | float DistributionGGX(vec3 N, vec3 H, float roughness) { 162 | float a = roughness*roughness; 163 | float a2 = a*a; 164 | float NdotH = max(dot(N, H), 0.0); 165 | float NdotH2 = NdotH*NdotH; 166 | 167 | float num = a2; 168 | float denom = (NdotH2 * (a2 - 1.0) + 1.0); 169 | denom = PI * denom * denom; 170 | 171 | return num / denom; 172 | } 173 | 174 | float GeometrySchlickGGX(float NdotV, float roughness) { 175 | float r = (roughness + 1.0); 176 | float k = (r*r) / 8.0; 177 | 178 | float num = NdotV; 179 | float denom = NdotV * (1.0 - k) + k; 180 | 181 | return num / denom; 182 | } 183 | 184 | float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { 185 | float NdotV = max(dot(N, V), 0.0); 186 | float NdotL = max(dot(N, L), 0.0); 187 | float ggx2 = GeometrySchlickGGX(NdotV, roughness); 188 | float ggx1 = GeometrySchlickGGX(NdotL, roughness); 189 | 190 | return ggx1 * ggx2; 191 | }`; 192 | 193 | const PBR_VERTEX_MAIN = ` 194 | void main() { 195 | vec3 n = normalize(vec3(modelMatrix * vec4(NORMAL, 0.0))); 196 | #ifdef USE_NORMAL_MAP 197 | vec3 t = normalize(vec3(modelMatrix * vec4(TANGENT.xyz, 0.0))); 198 | vec3 b = cross(n, t) * TANGENT.w; 199 | vTBN = mat3(t, b, n); 200 | #else 201 | vNorm = n; 202 | #endif 203 | 204 | #ifdef USE_VERTEX_COLOR 205 | vCol = COLOR_0; 206 | #endif 207 | 208 | vTex = TEXCOORD_0; 209 | vec4 mPos = modelMatrix * vec4(POSITION, 1.0); 210 | vWorldPos = mPos.xyz; 211 | vView = cameraPosition - mPos.xyz; 212 | gl_Position = projectionMatrix * viewMatrix * mPos; 213 | }`; 214 | 215 | function PBR_FRAGMENT_MAIN(textureFunc) { 216 | return ` 217 | ${PBR_FUNCTIONS} 218 | 219 | const vec3 dielectricSpec = vec3(0.04); 220 | const vec3 black = vec3(0.0); 221 | 222 | vec4 computeColor() { 223 | vec4 baseColor = baseColorFactor; 224 | #ifdef USE_BASE_COLOR_MAP 225 | vec4 baseColorMap = ${textureFunc('baseColorTexture', 'vTex')}; 226 | if (baseColorMap.a < 0.05) { 227 | discard; 228 | } 229 | baseColor *= baseColorMap; 230 | #endif 231 | #ifdef USE_VERTEX_COLOR 232 | baseColor *= vCol; 233 | #endif 234 | 235 | vec3 albedo = baseColor.rgb; //pow(baseColor.rgb, 2.2); 236 | 237 | float metallic = metallicRoughnessFactor.x; 238 | float roughness = metallicRoughnessFactor.y; 239 | 240 | #ifdef USE_METAL_ROUGH_MAP 241 | vec4 metallicRoughness = ${textureFunc('metallicRoughnessTexture', 'vTex')}; 242 | metallic *= metallicRoughness.b; 243 | roughness *= metallicRoughness.g; 244 | #endif 245 | 246 | #ifdef USE_NORMAL_MAP 247 | vec3 N = ${textureFunc('normalTexture', 'vTex')}.rgb; 248 | N = normalize(vTBN * (2.0 * N - 1.0)); 249 | #else 250 | vec3 N = normalize(vNorm); 251 | #endif 252 | 253 | vec3 V = normalize(vView); 254 | 255 | vec3 F0 = vec3(0.04); 256 | F0 = mix(F0, albedo, metallic); 257 | 258 | // reflectance equation 259 | vec3 Lo = vec3(0.0); 260 | 261 | for (int i = 0; i < LIGHT_COUNT; ++i) { 262 | // calculate per-light radiance 263 | vec3 L = normalize(lights[i].position.xyz - vWorldPos); 264 | vec3 H = normalize(V + L); 265 | float distance = length(lights[i].position.xyz - vWorldPos); 266 | float attenuation = 1.0 / (1.0 + distance * distance); 267 | vec3 radiance = lights[i].color.rgb * attenuation; 268 | 269 | // cook-torrance brdf 270 | float NDF = DistributionGGX(N, H, roughness); 271 | float G = GeometrySmith(N, V, L, roughness); 272 | vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0); 273 | 274 | vec3 kS = F; 275 | vec3 kD = vec3(1.0) - kS; 276 | kD *= 1.0 - metallic; 277 | 278 | vec3 numerator = NDF * G * F; 279 | float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); 280 | vec3 specular = numerator / max(denominator, 0.001); 281 | 282 | // add to outgoing radiance Lo 283 | float NdotL = max(dot(N, L), 0.0); 284 | Lo += (kD * albedo / PI + specular) * radiance * NdotL; 285 | } 286 | 287 | #ifdef USE_OCCLUSION 288 | float ao = ${textureFunc('occlusionTexture', 'vTex')}.r * occlusionStrength; 289 | #else 290 | float ao = 1.0; 291 | #endif 292 | 293 | vec3 ambient = vec3(lightAmbient) * albedo * ao; 294 | vec3 color = ambient + Lo; 295 | 296 | vec3 emissive = emissiveFactor; 297 | #ifdef USE_EMISSIVE_TEXTURE 298 | emissive *= ${textureFunc('emissiveTexture', 'vTex')}.rgb; 299 | #endif 300 | color += emissive; 301 | 302 | color = color / (color + vec3(1.0)); 303 | color = pow(color, vec3(1.0/2.2)); 304 | 305 | return vec4(color, baseColor.a); 306 | }`; 307 | }; 308 | 309 | function DEFINES(defines = {}) { 310 | let definesString = ''; 311 | for (let define in defines) { 312 | definesString += `#define ${define} ${defines[define]}\n`; 313 | } 314 | return definesString; 315 | } 316 | 317 | export function WEBGL_VERTEX_SOURCE(defines) { 318 | return ` 319 | ${DEFINES(defines)} 320 | ${WEBGL_ATTRIBUTES} 321 | ${WEBGL_VARYINGS()} 322 | ${WEBGL_VERTEX_UNIFORMS} 323 | ${PBR_VERTEX_MAIN} 324 | `; 325 | } 326 | 327 | export function WEBGL_FRAGMENT_SOURCE(defines) { 328 | return `precision highp float; 329 | ${DEFINES(defines)} 330 | ${WEBGL_VARYINGS()} 331 | ${WEBGL_FRAGMENT_UNIFORMS} 332 | ${PBR_FRAGMENT_MAIN(WEBGL_TEXTURE)} 333 | 334 | void main() { 335 | gl_FragColor = computeColor(); 336 | } 337 | `; 338 | } 339 | 340 | export function WEBGL2_VERTEX_SOURCE(defines) { 341 | return `#version 300 es 342 | ${DEFINES(defines)} 343 | ${ATTRIBUTES_WITH_LAYOUT} 344 | ${WEBGL_VARYINGS('out')} 345 | ${WEBGL2_VERTEX_UNIFORMS} 346 | ${PBR_VERTEX_MAIN} 347 | `; 348 | } 349 | 350 | export function WEBGL2_FRAGMENT_SOURCE(defines) { 351 | return `#version 300 es 352 | precision highp float; 353 | ${DEFINES(defines)} 354 | ${WEBGL_VARYINGS('in')} 355 | ${WEBGL2_FRAGMENT_UNIFORMS} 356 | ${PBR_FRAGMENT_MAIN(WEBGL2_TEXTURE)} 357 | 358 | out vec4 outputColor; 359 | void main() { 360 | outputColor = computeColor();; 361 | } 362 | `; 363 | } 364 | 365 | export function GetDefinesForPrimitive(primitive) { 366 | const attributes = primitive.enabledAttributes; 367 | const material = primitive.material; 368 | const programDefines = {}; 369 | 370 | if (attributes.has('COLOR_0')) { 371 | programDefines['USE_VERTEX_COLOR'] = 1; 372 | } 373 | 374 | if (attributes.has('TEXCOORD_0')) { 375 | if (material.baseColorTexture) { 376 | programDefines['USE_BASE_COLOR_MAP'] = 1; 377 | } 378 | 379 | if (material.normalTexture && (attributes.has('TANGENT'))) { 380 | programDefines['USE_NORMAL_MAP'] = 1; 381 | } 382 | 383 | if (material.metallicRoughnessTexture) { 384 | programDefines['USE_METAL_ROUGH_MAP'] = 1; 385 | } 386 | 387 | if (material.occlusionTexture) { 388 | programDefines['USE_OCCLUSION'] = 1; 389 | } 390 | 391 | if (material.emissiveTexture) { 392 | programDefines['USE_EMISSIVE_TEXTURE'] = 1; 393 | } 394 | } 395 | 396 | if ((!material.metallicRoughnessTexture || 397 | !(attributes.has('TEXCOORD_0'))) && 398 | material.metallicRoughnessFactor[1] == 1.0) { 399 | programDefines['FULLY_ROUGH'] = 1; 400 | } 401 | 402 | return programDefines; 403 | } 404 | -------------------------------------------------------------------------------- /js/renderer.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Brandon Jones 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import { vec3, mat4 } from './third-party/gl-matrix/src/gl-matrix.js'; 22 | 23 | const lightFloatCount = 8; 24 | const lightByteSize = lightFloatCount * 4; 25 | 26 | class Light { 27 | constructor(buffer, offset) { 28 | this.position = new Float32Array(buffer, offset, 4); 29 | this.color = new Float32Array(buffer, offset + 4 * 4, 4); 30 | } 31 | } 32 | 33 | export class Renderer { 34 | constructor() { 35 | this.canvas = document.createElement('canvas'); 36 | this.camera = null; 37 | this.rafId = 0; 38 | this.frameCount = -1; 39 | 40 | // Storage for global uniforms. 41 | // These can either be used individually or as a uniform buffer. 42 | this.frameUniforms = new Float32Array(16 + 16 + 4); 43 | 44 | this.projectionMatrix = new Float32Array(this.frameUniforms.buffer, 0, 16); 45 | this.viewMatrix = new Float32Array(this.frameUniforms.buffer, 16 * 4, 16); 46 | this.cameraPosition = new Float32Array(this.frameUniforms.buffer, 32 * 4, 3); 47 | 48 | this.lightCount = 5; 49 | 50 | this.lightUniforms = new Float32Array(lightFloatCount * this.lightCount + 4); 51 | 52 | this.lights = new Array(this.lightCount); 53 | for (let i = 0; i < this.lightCount; ++i) { 54 | this.lights[i] = new Light(this.lightUniforms.buffer, lightByteSize * i); 55 | } 56 | 57 | this.lightAmbient = new Float32Array(this.lightUniforms.buffer, (lightByteSize * this.lightCount), 4); 58 | 59 | // Central wandering light 60 | vec3.set(this.lights[0].position, 0, 1.5, 0); 61 | vec3.set(this.lights[0].color, 10, 10, 10); 62 | 63 | // Lights in each corner over the birdbath things. 64 | vec3.set(this.lights[1].position, 8.95, 1, -3.55); 65 | vec3.set(this.lights[1].color, 5, 1, 1); 66 | 67 | vec3.set(this.lights[2].position, 8.95, 1, 3.2); 68 | vec3.set(this.lights[2].color, 5, 1, 1); 69 | 70 | vec3.set(this.lights[3].position, -9.65, 1, -3.55); 71 | vec3.set(this.lights[3].color, 1, 1, 5); 72 | 73 | vec3.set(this.lights[4].position, -9.65, 1, 3.2); 74 | vec3.set(this.lights[4].color, 1, 1, 5); 75 | 76 | this.lightAmbient[0] = 0.05; 77 | 78 | this.frameCallback = (timestamp) => { 79 | this.rafId = requestAnimationFrame(this.frameCallback); 80 | this.frameCount++; 81 | if (this.frameCount % 200 == 0) { return; } 82 | 83 | if (this.stats) { 84 | this.stats.begin(); 85 | } 86 | 87 | this.beforeFrame(timestamp); 88 | 89 | this.onFrame(timestamp); 90 | 91 | if (this.stats) { 92 | this.stats.end(); 93 | } 94 | }; 95 | 96 | this.resizeCallback = () => { 97 | this.canvas.width = this.canvas.clientWidth * devicePixelRatio; 98 | this.canvas.height = this.canvas.clientHeight * devicePixelRatio; 99 | 100 | const aspect = this.canvas.width / this.canvas.height; 101 | mat4.perspective(this.projectionMatrix, Math.PI * 0.5, aspect, 0.1, 1000.0); 102 | 103 | this.onResize(this.canvas.width, this.canvas.height); 104 | }; 105 | } 106 | 107 | async init() { 108 | // Override with renderer-specific initialization logic. 109 | } 110 | 111 | setStats(stats) { 112 | this.stats = stats; 113 | } 114 | 115 | setGltf(gltf) { 116 | // Override with renderer-specific mesh loading logic. 117 | } 118 | 119 | setViewMatrix(viewMatrix) { 120 | mat4.copy(this.viewMatrix, viewMatrix); 121 | } 122 | 123 | start() { 124 | window.addEventListener('resize', this.resizeCallback); 125 | this.resizeCallback(); 126 | this.rafId = requestAnimationFrame(this.frameCallback); 127 | } 128 | 129 | stop() { 130 | if (this.rafId) { 131 | cancelAnimationFrame(this.rafId); 132 | this.rafId = 0; 133 | } 134 | window.removeEventListener('resize', this.resizeCallback); 135 | } 136 | 137 | // Handles frame logic that's common to all renderers. 138 | beforeFrame(timestamp) { 139 | // Copy values from the camera into our frame uniform buffers 140 | mat4.copy(this.viewMatrix, this.camera.viewMatrix); 141 | vec3.copy(this.cameraPosition, this.camera.position); 142 | 143 | // Update the lights 144 | vec3.set(this.lights[0].position, 145 | Math.sin(timestamp / 1500) * 4, 146 | Math.cos(timestamp / 600) * 0.25 + 1.5, 147 | Math.cos(timestamp / 500) * 0.75); 148 | 149 | for (let i = 1; i < 5; ++i) { 150 | this.lights[i].position[1] = 1.25 + Math.sin((timestamp + i * 250) / 800) * 0.1; 151 | } 152 | } 153 | 154 | onResize(width, height) { 155 | // Override with renderer-specific resize logic. 156 | } 157 | 158 | onFrame(timestamp) { 159 | // Override with renderer-specific frame logic. 160 | } 161 | 162 | 163 | } -------------------------------------------------------------------------------- /js/third-party/gl-matrix/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2018, Brandon Jones, Colin MacKenzie IV. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /js/third-party/gl-matrix/src/gl-matrix.js: -------------------------------------------------------------------------------- 1 | import * as glMatrix from "./gl-matrix/common.js"; 2 | import * as mat2 from "./gl-matrix/mat2.js"; 3 | import * as mat2d from "./gl-matrix/mat2d.js"; 4 | import * as mat3 from "./gl-matrix/mat3.js"; 5 | import * as mat4 from "./gl-matrix/mat4.js"; 6 | import * as quat from "./gl-matrix/quat.js"; 7 | import * as quat2 from "./gl-matrix/quat2.js"; 8 | import * as vec2 from "./gl-matrix/vec2.js"; 9 | import * as vec3 from "./gl-matrix/vec3.js"; 10 | import * as vec4 from "./gl-matrix/vec4.js"; 11 | 12 | export { 13 | glMatrix, 14 | mat2, mat2d, mat3, mat4, 15 | quat, quat2, 16 | vec2, vec3, vec4, 17 | }; 18 | -------------------------------------------------------------------------------- /js/third-party/gl-matrix/src/gl-matrix/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Common utilities 3 | * @module glMatrix 4 | */ 5 | 6 | // Configuration Constants 7 | export const EPSILON = 0.000001; 8 | export let ARRAY_TYPE = (typeof Float32Array !== 'undefined') ? Float32Array : Array; 9 | export const RANDOM = Math.random; 10 | 11 | /** 12 | * Sets the type of array used when creating new vectors and matrices 13 | * 14 | * @param {Type} type Array type, such as Float32Array or Array 15 | */ 16 | export function setMatrixArrayType(type) { 17 | ARRAY_TYPE = type; 18 | } 19 | 20 | const degree = Math.PI / 180; 21 | 22 | /** 23 | * Convert Degree To Radian 24 | * 25 | * @param {Number} a Angle in Degrees 26 | */ 27 | export function toRadian(a) { 28 | return a * degree; 29 | } 30 | 31 | /** 32 | * Tests whether or not the arguments have approximately the same value, within an absolute 33 | * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less 34 | * than or equal to 1.0, and a relative tolerance is used for larger values) 35 | * 36 | * @param {Number} a The first number to test. 37 | * @param {Number} b The second number to test. 38 | * @returns {Boolean} True if the numbers are approximately equal, false otherwise. 39 | */ 40 | export function equals(a, b) { 41 | return Math.abs(a - b) <= EPSILON*Math.max(1.0, Math.abs(a), Math.abs(b)); 42 | } 43 | -------------------------------------------------------------------------------- /js/third-party/gl-matrix/src/gl-matrix/mat2.js: -------------------------------------------------------------------------------- 1 | import * as glMatrix from "./common.js" 2 | 3 | /** 4 | * 2x2 Matrix 5 | * @module mat2 6 | */ 7 | 8 | /** 9 | * Creates a new identity mat2 10 | * 11 | * @returns {mat2} a new 2x2 matrix 12 | */ 13 | export function create() { 14 | let out = new glMatrix.ARRAY_TYPE(4); 15 | if(glMatrix.ARRAY_TYPE != Float32Array) { 16 | out[1] = 0; 17 | out[2] = 0; 18 | } 19 | out[0] = 1; 20 | out[3] = 1; 21 | return out; 22 | } 23 | 24 | /** 25 | * Creates a new mat2 initialized with values from an existing matrix 26 | * 27 | * @param {mat2} a matrix to clone 28 | * @returns {mat2} a new 2x2 matrix 29 | */ 30 | export function clone(a) { 31 | let out = new glMatrix.ARRAY_TYPE(4); 32 | out[0] = a[0]; 33 | out[1] = a[1]; 34 | out[2] = a[2]; 35 | out[3] = a[3]; 36 | return out; 37 | } 38 | 39 | /** 40 | * Copy the values from one mat2 to another 41 | * 42 | * @param {mat2} out the receiving matrix 43 | * @param {mat2} a the source matrix 44 | * @returns {mat2} out 45 | */ 46 | export function copy(out, a) { 47 | out[0] = a[0]; 48 | out[1] = a[1]; 49 | out[2] = a[2]; 50 | out[3] = a[3]; 51 | return out; 52 | } 53 | 54 | /** 55 | * Set a mat2 to the identity matrix 56 | * 57 | * @param {mat2} out the receiving matrix 58 | * @returns {mat2} out 59 | */ 60 | export function identity(out) { 61 | out[0] = 1; 62 | out[1] = 0; 63 | out[2] = 0; 64 | out[3] = 1; 65 | return out; 66 | } 67 | 68 | /** 69 | * Create a new mat2 with the given values 70 | * 71 | * @param {Number} m00 Component in column 0, row 0 position (index 0) 72 | * @param {Number} m01 Component in column 0, row 1 position (index 1) 73 | * @param {Number} m10 Component in column 1, row 0 position (index 2) 74 | * @param {Number} m11 Component in column 1, row 1 position (index 3) 75 | * @returns {mat2} out A new 2x2 matrix 76 | */ 77 | export function fromValues(m00, m01, m10, m11) { 78 | let out = new glMatrix.ARRAY_TYPE(4); 79 | out[0] = m00; 80 | out[1] = m01; 81 | out[2] = m10; 82 | out[3] = m11; 83 | return out; 84 | } 85 | 86 | /** 87 | * Set the components of a mat2 to the given values 88 | * 89 | * @param {mat2} out the receiving matrix 90 | * @param {Number} m00 Component in column 0, row 0 position (index 0) 91 | * @param {Number} m01 Component in column 0, row 1 position (index 1) 92 | * @param {Number} m10 Component in column 1, row 0 position (index 2) 93 | * @param {Number} m11 Component in column 1, row 1 position (index 3) 94 | * @returns {mat2} out 95 | */ 96 | export function set(out, m00, m01, m10, m11) { 97 | out[0] = m00; 98 | out[1] = m01; 99 | out[2] = m10; 100 | out[3] = m11; 101 | return out; 102 | } 103 | 104 | /** 105 | * Transpose the values of a mat2 106 | * 107 | * @param {mat2} out the receiving matrix 108 | * @param {mat2} a the source matrix 109 | * @returns {mat2} out 110 | */ 111 | export function transpose(out, a) { 112 | // If we are transposing ourselves we can skip a few steps but have to cache 113 | // some values 114 | if (out === a) { 115 | let a1 = a[1]; 116 | out[1] = a[2]; 117 | out[2] = a1; 118 | } else { 119 | out[0] = a[0]; 120 | out[1] = a[2]; 121 | out[2] = a[1]; 122 | out[3] = a[3]; 123 | } 124 | 125 | return out; 126 | } 127 | 128 | /** 129 | * Inverts a mat2 130 | * 131 | * @param {mat2} out the receiving matrix 132 | * @param {mat2} a the source matrix 133 | * @returns {mat2} out 134 | */ 135 | export function invert(out, a) { 136 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 137 | 138 | // Calculate the determinant 139 | let det = a0 * a3 - a2 * a1; 140 | 141 | if (!det) { 142 | return null; 143 | } 144 | det = 1.0 / det; 145 | 146 | out[0] = a3 * det; 147 | out[1] = -a1 * det; 148 | out[2] = -a2 * det; 149 | out[3] = a0 * det; 150 | 151 | return out; 152 | } 153 | 154 | /** 155 | * Calculates the adjugate of a mat2 156 | * 157 | * @param {mat2} out the receiving matrix 158 | * @param {mat2} a the source matrix 159 | * @returns {mat2} out 160 | */ 161 | export function adjoint(out, a) { 162 | // Caching this value is nessecary if out == a 163 | let a0 = a[0]; 164 | out[0] = a[3]; 165 | out[1] = -a[1]; 166 | out[2] = -a[2]; 167 | out[3] = a0; 168 | 169 | return out; 170 | } 171 | 172 | /** 173 | * Calculates the determinant of a mat2 174 | * 175 | * @param {mat2} a the source matrix 176 | * @returns {Number} determinant of a 177 | */ 178 | export function determinant(a) { 179 | return a[0] * a[3] - a[2] * a[1]; 180 | } 181 | 182 | /** 183 | * Multiplies two mat2's 184 | * 185 | * @param {mat2} out the receiving matrix 186 | * @param {mat2} a the first operand 187 | * @param {mat2} b the second operand 188 | * @returns {mat2} out 189 | */ 190 | export function multiply(out, a, b) { 191 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 192 | let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; 193 | out[0] = a0 * b0 + a2 * b1; 194 | out[1] = a1 * b0 + a3 * b1; 195 | out[2] = a0 * b2 + a2 * b3; 196 | out[3] = a1 * b2 + a3 * b3; 197 | return out; 198 | } 199 | 200 | /** 201 | * Rotates a mat2 by the given angle 202 | * 203 | * @param {mat2} out the receiving matrix 204 | * @param {mat2} a the matrix to rotate 205 | * @param {Number} rad the angle to rotate the matrix by 206 | * @returns {mat2} out 207 | */ 208 | export function rotate(out, a, rad) { 209 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 210 | let s = Math.sin(rad); 211 | let c = Math.cos(rad); 212 | out[0] = a0 * c + a2 * s; 213 | out[1] = a1 * c + a3 * s; 214 | out[2] = a0 * -s + a2 * c; 215 | out[3] = a1 * -s + a3 * c; 216 | return out; 217 | } 218 | 219 | /** 220 | * Scales the mat2 by the dimensions in the given vec2 221 | * 222 | * @param {mat2} out the receiving matrix 223 | * @param {mat2} a the matrix to rotate 224 | * @param {vec2} v the vec2 to scale the matrix by 225 | * @returns {mat2} out 226 | **/ 227 | export function scale(out, a, v) { 228 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 229 | let v0 = v[0], v1 = v[1]; 230 | out[0] = a0 * v0; 231 | out[1] = a1 * v0; 232 | out[2] = a2 * v1; 233 | out[3] = a3 * v1; 234 | return out; 235 | } 236 | 237 | /** 238 | * Creates a matrix from a given angle 239 | * This is equivalent to (but much faster than): 240 | * 241 | * mat2.identity(dest); 242 | * mat2.rotate(dest, dest, rad); 243 | * 244 | * @param {mat2} out mat2 receiving operation result 245 | * @param {Number} rad the angle to rotate the matrix by 246 | * @returns {mat2} out 247 | */ 248 | export function fromRotation(out, rad) { 249 | let s = Math.sin(rad); 250 | let c = Math.cos(rad); 251 | out[0] = c; 252 | out[1] = s; 253 | out[2] = -s; 254 | out[3] = c; 255 | return out; 256 | } 257 | 258 | /** 259 | * Creates a matrix from a vector scaling 260 | * This is equivalent to (but much faster than): 261 | * 262 | * mat2.identity(dest); 263 | * mat2.scale(dest, dest, vec); 264 | * 265 | * @param {mat2} out mat2 receiving operation result 266 | * @param {vec2} v Scaling vector 267 | * @returns {mat2} out 268 | */ 269 | export function fromScaling(out, v) { 270 | out[0] = v[0]; 271 | out[1] = 0; 272 | out[2] = 0; 273 | out[3] = v[1]; 274 | return out; 275 | } 276 | 277 | /** 278 | * Returns a string representation of a mat2 279 | * 280 | * @param {mat2} a matrix to represent as a string 281 | * @returns {String} string representation of the matrix 282 | */ 283 | export function str(a) { 284 | return 'mat2(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')'; 285 | } 286 | 287 | /** 288 | * Returns Frobenius norm of a mat2 289 | * 290 | * @param {mat2} a the matrix to calculate Frobenius norm of 291 | * @returns {Number} Frobenius norm 292 | */ 293 | export function frob(a) { 294 | return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2))) 295 | } 296 | 297 | /** 298 | * Returns L, D and U matrices (Lower triangular, Diagonal and Upper triangular) by factorizing the input matrix 299 | * @param {mat2} L the lower triangular matrix 300 | * @param {mat2} D the diagonal matrix 301 | * @param {mat2} U the upper triangular matrix 302 | * @param {mat2} a the input matrix to factorize 303 | */ 304 | 305 | export function LDU(L, D, U, a) { 306 | L[2] = a[2]/a[0]; 307 | U[0] = a[0]; 308 | U[1] = a[1]; 309 | U[3] = a[3] - L[2] * U[1]; 310 | return [L, D, U]; 311 | } 312 | 313 | /** 314 | * Adds two mat2's 315 | * 316 | * @param {mat2} out the receiving matrix 317 | * @param {mat2} a the first operand 318 | * @param {mat2} b the second operand 319 | * @returns {mat2} out 320 | */ 321 | export function add(out, a, b) { 322 | out[0] = a[0] + b[0]; 323 | out[1] = a[1] + b[1]; 324 | out[2] = a[2] + b[2]; 325 | out[3] = a[3] + b[3]; 326 | return out; 327 | } 328 | 329 | /** 330 | * Subtracts matrix b from matrix a 331 | * 332 | * @param {mat2} out the receiving matrix 333 | * @param {mat2} a the first operand 334 | * @param {mat2} b the second operand 335 | * @returns {mat2} out 336 | */ 337 | export function subtract(out, a, b) { 338 | out[0] = a[0] - b[0]; 339 | out[1] = a[1] - b[1]; 340 | out[2] = a[2] - b[2]; 341 | out[3] = a[3] - b[3]; 342 | return out; 343 | } 344 | 345 | /** 346 | * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) 347 | * 348 | * @param {mat2} a The first matrix. 349 | * @param {mat2} b The second matrix. 350 | * @returns {Boolean} True if the matrices are equal, false otherwise. 351 | */ 352 | export function exactEquals(a, b) { 353 | return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; 354 | } 355 | 356 | /** 357 | * Returns whether or not the matrices have approximately the same elements in the same position. 358 | * 359 | * @param {mat2} a The first matrix. 360 | * @param {mat2} b The second matrix. 361 | * @returns {Boolean} True if the matrices are equal, false otherwise. 362 | */ 363 | export function equals(a, b) { 364 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; 365 | let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; 366 | return (Math.abs(a0 - b0) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a0), Math.abs(b0)) && 367 | Math.abs(a1 - b1) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a1), Math.abs(b1)) && 368 | Math.abs(a2 - b2) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a2), Math.abs(b2)) && 369 | Math.abs(a3 - b3) <= glMatrix.EPSILON*Math.max(1.0, Math.abs(a3), Math.abs(b3))); 370 | } 371 | 372 | /** 373 | * Multiply each element of the matrix by a scalar. 374 | * 375 | * @param {mat2} out the receiving matrix 376 | * @param {mat2} a the matrix to scale 377 | * @param {Number} b amount to scale the matrix's elements by 378 | * @returns {mat2} out 379 | */ 380 | export function multiplyScalar(out, a, b) { 381 | out[0] = a[0] * b; 382 | out[1] = a[1] * b; 383 | out[2] = a[2] * b; 384 | out[3] = a[3] * b; 385 | return out; 386 | } 387 | 388 | /** 389 | * Adds two mat2's after multiplying each element of the second operand by a scalar value. 390 | * 391 | * @param {mat2} out the receiving vector 392 | * @param {mat2} a the first operand 393 | * @param {mat2} b the second operand 394 | * @param {Number} scale the amount to scale b's elements by before adding 395 | * @returns {mat2} out 396 | */ 397 | export function multiplyScalarAndAdd(out, a, b, scale) { 398 | out[0] = a[0] + (b[0] * scale); 399 | out[1] = a[1] + (b[1] * scale); 400 | out[2] = a[2] + (b[2] * scale); 401 | out[3] = a[3] + (b[3] * scale); 402 | return out; 403 | } 404 | 405 | /** 406 | * Alias for {@link mat2.multiply} 407 | * @function 408 | */ 409 | export const mul = multiply; 410 | 411 | /** 412 | * Alias for {@link mat2.subtract} 413 | * @function 414 | */ 415 | export const sub = subtract; 416 | -------------------------------------------------------------------------------- /js/third-party/stats.module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | var Stats = function () { 6 | 7 | var mode = 0; 8 | 9 | var container = document.createElement( 'div' ); 10 | container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000'; 11 | container.addEventListener( 'click', function ( event ) { 12 | 13 | event.preventDefault(); 14 | showPanel( ++ mode % container.children.length ); 15 | 16 | }, false ); 17 | 18 | // 19 | 20 | function addPanel( panel ) { 21 | 22 | container.appendChild( panel.dom ); 23 | return panel; 24 | 25 | } 26 | 27 | function showPanel( id ) { 28 | 29 | for ( var i = 0; i < container.children.length; i ++ ) { 30 | 31 | container.children[ i ].style.display = i === id ? 'block' : 'none'; 32 | 33 | } 34 | 35 | mode = id; 36 | 37 | } 38 | 39 | // 40 | 41 | var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0; 42 | var frameTime = 0; 43 | var fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) ); 44 | var msPanel = addPanel( new Stats.Panel( 'MS', '#0f0', '#020' ) ); 45 | 46 | if ( self.performance && self.performance.memory ) { 47 | 48 | var memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) ); 49 | 50 | } 51 | 52 | showPanel( 1 ); 53 | 54 | return { 55 | 56 | REVISION: 16, 57 | 58 | dom: container, 59 | 60 | addPanel: addPanel, 61 | showPanel: showPanel, 62 | 63 | begin: function () { 64 | 65 | beginTime = ( performance || Date ).now(); 66 | 67 | }, 68 | 69 | end: function () { 70 | 71 | frames ++; 72 | 73 | var time = ( performance || Date ).now(); 74 | 75 | frameTime += time - beginTime; 76 | 77 | if ( time >= prevTime + 1000 ) { 78 | 79 | msPanel.update( frameTime / frames, 3 ); 80 | fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 ); 81 | 82 | frameTime = 0; 83 | prevTime = time; 84 | frames = 0; 85 | 86 | if ( memPanel ) { 87 | 88 | var memory = performance.memory; 89 | memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 ); 90 | 91 | } 92 | 93 | } 94 | 95 | return time; 96 | 97 | }, 98 | 99 | update: function () { 100 | 101 | beginTime = this.end(); 102 | 103 | }, 104 | 105 | // Backwards Compatibility 106 | 107 | domElement: container, 108 | setMode: showPanel 109 | 110 | }; 111 | 112 | }; 113 | 114 | Stats.Panel = function ( name, fg, bg ) { 115 | 116 | var min = Infinity, max = 0, round = Math.round; 117 | var PR = round( window.devicePixelRatio || 1 ); 118 | 119 | var WIDTH = 80 * PR, HEIGHT = 48 * PR, 120 | TEXT_X = 3 * PR, TEXT_Y = 2 * PR, 121 | GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR, 122 | GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR; 123 | 124 | var canvas = document.createElement( 'canvas' ); 125 | canvas.width = WIDTH; 126 | canvas.height = HEIGHT; 127 | canvas.style.cssText = 'width:80px;height:48px'; 128 | 129 | var context = canvas.getContext( '2d' ); 130 | context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif'; 131 | context.textBaseline = 'top'; 132 | 133 | context.fillStyle = bg; 134 | context.fillRect( 0, 0, WIDTH, HEIGHT ); 135 | 136 | context.fillStyle = fg; 137 | context.fillText( name, TEXT_X, TEXT_Y ); 138 | context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT ); 139 | 140 | context.fillStyle = bg; 141 | context.globalAlpha = 0.9; 142 | context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT ); 143 | 144 | return { 145 | 146 | dom: canvas, 147 | 148 | update: function ( value, maxValue ) { 149 | 150 | min = Math.min( min, value ); 151 | max = Math.max( max, value ); 152 | 153 | context.fillStyle = bg; 154 | context.globalAlpha = 1; 155 | context.fillRect( 0, 0, WIDTH, GRAPH_Y ); 156 | context.fillStyle = fg; 157 | context.fillText( value.toFixed(2) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y ); 158 | 159 | context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT ); 160 | 161 | context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT ); 162 | 163 | context.fillStyle = bg; 164 | context.globalAlpha = 0.9; 165 | context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) ); 166 | 167 | } 168 | 169 | }; 170 | 171 | }; 172 | 173 | export default Stats; 174 | -------------------------------------------------------------------------------- /js/webgl-renderer/shader-program.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Immersive Web Community Group 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | // Just a helper class to easily compile and link a shader program, then query 22 | // some helpful values such as attribute and uniform locations. 23 | export class ShaderProgram { 24 | constructor(gl, config) { 25 | if (!config || !config.vertexSource || !config.fragmentSource) { 26 | throw new Error('Must provide a vertexSource and fragmentSource'); 27 | } 28 | 29 | this.gl = gl; 30 | this.program = gl.createProgram(); 31 | this.attribute = {}; 32 | this.uniform = {}; 33 | this.uniformBlock = {}; 34 | 35 | const vertShader = gl.createShader(gl.VERTEX_SHADER); 36 | gl.attachShader(this.program, vertShader); 37 | gl.shaderSource(vertShader, config.vertexSource); 38 | gl.compileShader(vertShader); 39 | 40 | const fragShader = gl.createShader(gl.FRAGMENT_SHADER); 41 | gl.attachShader(this.program, fragShader); 42 | gl.shaderSource(fragShader, config.fragmentSource); 43 | gl.compileShader(fragShader); 44 | 45 | if (config.attributeLocations) { 46 | for (let attribName in config.attributeLocations) { 47 | gl.bindAttribLocation(this.program, config.attributeLocations[attribName], attribName); 48 | this.attribute[attribName] = config.attributeLocations[attribName]; 49 | } 50 | } 51 | 52 | gl.linkProgram(this.program); 53 | 54 | if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { 55 | if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { 56 | console.error('Vertex shader compile error: ' + gl.getShaderInfoLog(vertShader)); 57 | } else if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { 58 | console.error('Fragment shader compile error: ' + gl.getShaderInfoLog(fragShader)); 59 | } else { 60 | console.error('Program link error: ' + gl.getProgramInfoLog(this.program)); 61 | } 62 | gl.deleteProgram(this.program); 63 | this.program = null; 64 | return; 65 | } 66 | 67 | gl.deleteShader(vertShader); 68 | gl.deleteShader(fragShader); 69 | 70 | if (!config.attributeLocations) { 71 | let attribCount = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); 72 | for (let i = 0; i < attribCount; i++) { 73 | let attribInfo = gl.getActiveAttrib(this.program, i); 74 | this.attribute[attribInfo.name] = gl.getAttribLocation(this.program, attribInfo.name); 75 | } 76 | } 77 | 78 | let uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); 79 | let uniformName = ''; 80 | for (let i = 0; i < uniformCount; i++) { 81 | let uniformInfo = gl.getActiveUniform(this.program, i); 82 | uniformName = uniformInfo.name; 83 | this.uniform[uniformName] = gl.getUniformLocation(this.program, uniformName); 84 | } 85 | 86 | // Are we using WebGL 2? 87 | if (gl.ACTIVE_UNIFORM_BLOCKS) { 88 | let uniformBlockCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORM_BLOCKS); 89 | for (let i = 0; i < uniformBlockCount; i++) { 90 | let uniformBlockName = gl.getActiveUniformBlockName(this.program, i); 91 | this.uniformBlock[uniformBlockName] = i; 92 | } 93 | } 94 | } 95 | 96 | use() { 97 | this.gl.useProgram(this.program); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /js/webgpu-renderer/pbr-shader-wgsl.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Brandon Jones 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | export const ATTRIB_MAP = { 22 | POSITION: 1, 23 | NORMAL: 2, 24 | TANGENT: 3, 25 | TEXCOORD_0: 4, 26 | COLOR_0: 5, 27 | }; 28 | 29 | export const UNIFORM_BLOCKS = { 30 | FrameUniforms: 0, 31 | MaterialUniforms: 1, 32 | PrimitiveUniforms: 2, 33 | LightUniforms: 3 34 | }; 35 | 36 | function PBR_VARYINGS(defines) { return ` 37 | @location(0) vWorldPos : vec3f, 38 | @location(1) vView : vec3f, // Vector from vertex to camera. 39 | @location(2) vTex : vec2f, 40 | @location(3) vCol : vec4f, 41 | @location(4) vNorm : vec3f, 42 | 43 | ${defines.USE_NORMAL_MAP ? ` 44 | @location(5) vTangent : vec3f, 45 | @location(6) vBitangent : vec3f, 46 | ` : ``} 47 | `; 48 | } 49 | 50 | export function WEBGPU_VERTEX_SOURCE(defines) { return ` 51 | struct VertexInput { 52 | @location(${ATTRIB_MAP.POSITION}) POSITION : vec3f, 53 | @location(${ATTRIB_MAP.NORMAL}) NORMAL : vec3f, 54 | ${defines.USE_NORMAL_MAP ? ` 55 | @location(${ATTRIB_MAP.TANGENT}) TANGENT : vec4f, 56 | ` : ``} 57 | @location(${ATTRIB_MAP.TEXCOORD_0}) TEXCOORD_0 : vec2f, 58 | ${defines.USE_VERTEX_COLOR ? ` 59 | @location(${ATTRIB_MAP.COLOR_0}) COLOR_0 : vec4f, 60 | ` : ``} 61 | }; 62 | 63 | struct FrameUniforms { 64 | projectionMatrix : mat4x4f, 65 | viewMatrix : mat4x4f, 66 | cameraPosition : vec3f, 67 | }; 68 | @binding(0) @group(${UNIFORM_BLOCKS.FrameUniforms}) var frame : FrameUniforms; 69 | 70 | struct PrimitiveUniforms { 71 | modelMatrix : mat4x4f 72 | }; 73 | @binding(0) @group(${UNIFORM_BLOCKS.PrimitiveUniforms}) var primitive : PrimitiveUniforms; 74 | 75 | struct VertexOutput { 76 | @builtin(position) position : vec4f, 77 | ${PBR_VARYINGS(defines)} 78 | }; 79 | 80 | @vertex 81 | fn main(input : VertexInput) -> VertexOutput { 82 | var output : VertexOutput; 83 | output.vNorm = normalize((primitive.modelMatrix * vec4(input.NORMAL, 0.0)).xyz); 84 | ${defines.USE_NORMAL_MAP ? ` 85 | output.vTangent = normalize((primitive.modelMatrix * vec4(input.TANGENT.xyz, 0.0)).xyz); 86 | output.vBitangent = cross(output.vNorm, output.vTangent) * input.TANGENT.w; 87 | ` : ``} 88 | 89 | ${defines.USE_VERTEX_COLOR ? ` 90 | output.vCol = input.COLOR_0; 91 | ` : `` } 92 | 93 | output.vTex = input.TEXCOORD_0; 94 | var mPos = primitive.modelMatrix * vec4(input.POSITION, 1.0); 95 | output.vWorldPos = mPos.xyz; 96 | output.vView = frame.cameraPosition - mPos.xyz; 97 | output.position = frame.projectionMatrix * frame.viewMatrix * mPos; 98 | return output; 99 | }`; 100 | } 101 | 102 | // Much of the shader used here was pulled from https://learnopengl.com/PBR/Lighting 103 | // Thanks! 104 | const PBR_FUNCTIONS = ` 105 | const PI : f32 = ${Math.PI}; 106 | 107 | fn FresnelSchlick(cosTheta : f32, F0 : vec3f) -> vec3f { 108 | return F0 + (vec3(1.0) - F0) * pow(1.0 - cosTheta, 5.0); 109 | } 110 | 111 | fn DistributionGGX(N : vec3f, H : vec3f, roughness : f32) -> f32 { 112 | var a : f32 = roughness*roughness; 113 | var a2 : f32 = a*a; 114 | var NdotH : f32 = max(dot(N, H), 0.0); 115 | var NdotH2 : f32 = NdotH*NdotH; 116 | 117 | var num : f32 = a2; 118 | var denom : f32 = (NdotH2 * (a2 - 1.0) + 1.0); 119 | denom = PI * denom * denom; 120 | 121 | return num / denom; 122 | } 123 | 124 | fn GeometrySchlickGGX(NdotV : f32, roughness : f32) -> f32 { 125 | var r : f32 = (roughness + 1.0); 126 | var k : f32 = (r*r) / 8.0; 127 | 128 | var num : f32 = NdotV; 129 | var denom : f32 = NdotV * (1.0 - k) + k; 130 | 131 | return num / denom; 132 | } 133 | 134 | fn GeometrySmith(N : vec3f, V : vec3f, L : vec3f, roughness : f32) -> f32 { 135 | var NdotV : f32 = max(dot(N, V), 0.0); 136 | var NdotL : f32 = max(dot(N, L), 0.0); 137 | var ggx2 : f32 = GeometrySchlickGGX(NdotV, roughness); 138 | var ggx1 : f32 = GeometrySchlickGGX(NdotL, roughness); 139 | 140 | return ggx1 * ggx2; 141 | }`; 142 | 143 | export function WEBGPU_FRAGMENT_SOURCE(defines) { return ` 144 | ${PBR_FUNCTIONS} 145 | 146 | struct MaterialUniforms { 147 | baseColorFactor : vec4f, 148 | metallicRoughnessFactor : vec2f, 149 | emissiveFactor : vec3f, 150 | occlusionStrength : f32, 151 | }; 152 | @binding(0) @group(${UNIFORM_BLOCKS.MaterialUniforms}) var material : MaterialUniforms; 153 | 154 | @group(1) @binding(1) var defaultSampler : sampler; 155 | @group(1) @binding(2) var baseColorTexture : texture_2d; 156 | @group(1) @binding(3) var normalTexture : texture_2d; 157 | @group(1) @binding(4) var metallicRoughnessTexture : texture_2d; 158 | @group(1) @binding(5) var occlusionTexture : texture_2d; 159 | @group(1) @binding(6) var emissiveTexture : texture_2d; 160 | 161 | struct Light { 162 | position : vec3f, 163 | color : vec3f, 164 | }; 165 | 166 | struct LightUniforms { 167 | lights : array, 168 | lightAmbient : f32, 169 | }; 170 | @binding(0) @group(${UNIFORM_BLOCKS.LightUniforms}) var light : LightUniforms; 171 | 172 | struct VertexOutput { 173 | ${PBR_VARYINGS(defines)} 174 | }; 175 | 176 | const dielectricSpec = vec3(0.04); 177 | const black = vec3(0.0); 178 | 179 | @fragment 180 | fn main(input : VertexOutput) -> @location(0) vec4f { 181 | var baseColor = material.baseColorFactor; 182 | ${defines.USE_BASE_COLOR_MAP ? ` 183 | var baseColorMap = textureSample(baseColorTexture, defaultSampler, input.vTex); 184 | baseColor = baseColor * baseColorMap; 185 | ` : ``} 186 | ${defines.USE_VERTEX_COLOR ? ` 187 | baseColor = baseColor * vCol; 188 | ` : ``} 189 | 190 | var albedo = baseColor.rgb; 191 | 192 | var metallic : f32 = material.metallicRoughnessFactor.x; 193 | var roughness : f32 = material.metallicRoughnessFactor.y; 194 | 195 | ${defines.USE_METAL_ROUGH_MAP ? ` 196 | var metallicRoughness = textureSample(metallicRoughnessTexture, defaultSampler, input.vTex); 197 | metallic = metallic * metallicRoughness.b; 198 | roughness = roughness * metallicRoughness.g; 199 | ` : ``} 200 | 201 | ${defines.USE_NORMAL_MAP ? ` 202 | let tbn = mat3x3(input.vTangent, input.vBitangent, input.vNorm); 203 | var N = textureSample(normalTexture, defaultSampler, input.vTex).rgb; 204 | N = normalize(tbn * (2.0 * N - vec3(1.0))); 205 | ` : ` 206 | var N = normalize(input.vNorm); 207 | `} 208 | 209 | ${defines.USE_OCCLUSION ? ` 210 | var ao : f32 = textureSample(occlusionTexture, defaultSampler, input.vTex).r * material.occlusionStrength; 211 | ` : ` 212 | var ao : f32 = 1.0; 213 | `} 214 | 215 | var emissive = material.emissiveFactor; 216 | ${defines.USE_EMISSIVE_TEXTURE ? ` 217 | emissive = emissive * textureSample(emissiveTexture, defaultSampler, input.vTex).rgb; 218 | ` : ``} 219 | 220 | if (baseColorMap.a < 0.05) { 221 | discard; 222 | } 223 | 224 | var V = normalize(input.vView); 225 | 226 | var F0 = mix(dielectricSpec, albedo, vec3(metallic)); 227 | 228 | // reflectance equation 229 | var Lo = vec3(0.0); 230 | 231 | for (var i : i32 = 0; i < ${defines.LIGHT_COUNT}; i = i + 1) { 232 | // calculate per-light radiance 233 | var L = normalize(light.lights[i].position.xyz - input.vWorldPos); 234 | var H = normalize(V + L); 235 | var distance = length(light.lights[i].position.xyz - input.vWorldPos); 236 | var attenuation = 1.0 / (1.0 + distance * distance); 237 | var radiance = light.lights[i].color.rgb * attenuation; 238 | 239 | // cook-torrance brdf 240 | var NDF = DistributionGGX(N, H, roughness); 241 | var G = GeometrySmith(N, V, L, roughness); 242 | var F = FresnelSchlick(max(dot(H, V), 0.0), F0); 243 | 244 | var kD = vec3(1.0) - F; 245 | kD = kD * (1.0 - metallic); 246 | 247 | var numerator = NDF * G * F; 248 | var denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); 249 | denominator = max(denominator, 0.001); 250 | var specular = numerator / vec3(denominator); 251 | 252 | // add to outgoing radiance Lo 253 | var NdotL = max(dot(N, L), 0.0); 254 | Lo = Lo + (kD * albedo / vec3(PI) + specular) * radiance * NdotL; 255 | } 256 | 257 | var ambient = light.lightAmbient * albedo * ao; 258 | var color = ambient + Lo; 259 | 260 | color = color + emissive; 261 | 262 | color = color / (color + vec3(1.0)); 263 | color = pow(color, vec3(1.0/2.2)); 264 | 265 | return vec4(color, baseColor.a); 266 | } 267 | `; 268 | } 269 | 270 | export function GetDefinesForPrimitive(primitive) { 271 | const attributes = primitive.enabledAttributes; 272 | const material = primitive.material; 273 | const programDefines = {}; 274 | 275 | if (attributes.has('COLOR_0')) { 276 | programDefines['USE_VERTEX_COLOR'] = 1; 277 | } 278 | 279 | if (attributes.has('TEXCOORD_0')) { 280 | if (material.baseColorTexture) { 281 | programDefines['USE_BASE_COLOR_MAP'] = 1; 282 | } 283 | 284 | if (material.normalTexture && (attributes.has('TANGENT'))) { 285 | programDefines['USE_NORMAL_MAP'] = 1; 286 | } 287 | 288 | if (material.metallicRoughnessTexture) { 289 | programDefines['USE_METAL_ROUGH_MAP'] = 1; 290 | } 291 | 292 | if (material.occlusionTexture) { 293 | programDefines['USE_OCCLUSION'] = 1; 294 | } 295 | 296 | if (material.emissiveTexture) { 297 | programDefines['USE_EMISSIVE_TEXTURE'] = 1; 298 | } 299 | } 300 | 301 | if ((!material.metallicRoughnessTexture || 302 | !(attributes.has('TEXCOORD_0'))) && 303 | material.metallicRoughnessFactor[1] == 1.0) { 304 | programDefines['FULLY_ROUGH'] = 1; 305 | } 306 | 307 | return programDefines; 308 | } 309 | -------------------------------------------------------------------------------- /js/webgpu-renderer/webgpu-texture-helper.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Brandon Jones 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | export class GPUTextureHelper { 22 | constructor(device) { 23 | this.device = device; 24 | 25 | const mipmapShaderModule = this.device.createShaderModule({ 26 | code: ` 27 | var pos : array = array( 28 | vec2(-1.0, -1.0), vec2(-1.0, 3.0), vec2(3.0, -1.0)); 29 | 30 | struct VertexOutput { 31 | @builtin(position) position : vec4f, 32 | @location(0) texCoord : vec2f, 33 | }; 34 | 35 | @vertex 36 | fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput { 37 | var output : VertexOutput; 38 | output.texCoord = pos[vertexIndex] * vec2(0.5, -0.5) + vec2(0.5); 39 | output.position = vec4(pos[vertexIndex], 0.0, 1.0); 40 | return output; 41 | } 42 | 43 | @binding(0) @group(0) var imgSampler : sampler; 44 | @binding(1) @group(0) var img : texture_2d; 45 | 46 | @fragment 47 | fn fragmentMain(@location(0) texCoord : vec2f) -> @location(0) vec4f { 48 | return textureSample(img, imgSampler, texCoord); 49 | } 50 | `, 51 | }); 52 | 53 | this.mipmapSampler = device.createSampler({ minFilter: 'linear' }); 54 | 55 | this.mipmapPipeline = device.createRenderPipeline({ 56 | layout: 'auto', 57 | vertex: { 58 | module: mipmapShaderModule, 59 | entryPoint: 'vertexMain', 60 | }, 61 | fragment: { 62 | module: mipmapShaderModule, 63 | entryPoint: 'fragmentMain', 64 | targets: [{ 65 | format: 'rgba8unorm', 66 | }], 67 | } 68 | }); 69 | } 70 | 71 | // TODO: Everything about this is awful. 72 | generateMipmappedTexture(imageBitmap) { 73 | let textureSize = { 74 | width: imageBitmap.width, 75 | height: imageBitmap.height, 76 | } 77 | const mipLevelCount = Math.floor(Math.log2(Math.max(imageBitmap.width, imageBitmap.height))) + 1; 78 | 79 | // Populate the top level of the srcTexture with the imageBitmap. 80 | const srcTexture = this.device.createTexture({ 81 | size: textureSize, 82 | format: 'rgba8unorm', 83 | // TO COMPLAIN ABOUT: Kind of worrying that this style of mipmap generation implies that almost every texture 84 | // generated will be an output attachment. There's gotta be a performance penalty for that. 85 | usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT, 86 | mipLevelCount 87 | }); 88 | this.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture: srcTexture }, textureSize); 89 | 90 | const commandEncoder = this.device.createCommandEncoder({}); 91 | 92 | const bindGroupLayout = this.mipmapPipeline.getBindGroupLayout(0); 93 | 94 | for (let i = 1; i < mipLevelCount; ++i) { 95 | const bindGroup = this.device.createBindGroup({ 96 | layout: bindGroupLayout, 97 | entries: [{ 98 | binding: 0, 99 | resource: this.mipmapSampler, 100 | }, { 101 | binding: 1, 102 | resource: srcTexture.createView({ 103 | baseMipLevel: i-1, 104 | mipLevelCount: 1 105 | }), 106 | }], 107 | }); 108 | 109 | const passEncoder = commandEncoder.beginRenderPass({ 110 | colorAttachments: [{ 111 | view: srcTexture.createView({ 112 | baseMipLevel: i, 113 | mipLevelCount: 1 114 | }), 115 | loadOp: 'load', 116 | storeOp: 'store', 117 | }], 118 | }); 119 | passEncoder.setPipeline(this.mipmapPipeline); 120 | passEncoder.setBindGroup(0, bindGroup); 121 | passEncoder.draw(3); 122 | passEncoder.end(); 123 | 124 | textureSize.width = Math.ceil(textureSize.width / 2); 125 | textureSize.height = Math.ceil(textureSize.height / 2); 126 | } 127 | this.device.queue.submit([commandEncoder.finish()]); 128 | 129 | return srcTexture; 130 | } 131 | 132 | generateTexture(imageBitmap) { 133 | const textureSize = { 134 | width: imageBitmap.width, 135 | height: imageBitmap.height, 136 | }; 137 | 138 | const texture = this.device.createTexture({ 139 | size: textureSize, 140 | format: 'rgba8unorm', 141 | usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING, 142 | }); 143 | this.device.queue.copyImageBitmapToTexture({ imageBitmap }, { texture }, textureSize); 144 | 145 | return texture; 146 | } 147 | 148 | generateColorTexture(r, g, b, a) { 149 | const imageData = new Uint8Array([r * 255, g * 255, b * 255, a * 255]); 150 | 151 | const imageSize = { width: 1, height: 1 }; 152 | const texture = this.device.createTexture({ 153 | size: imageSize, 154 | format: 'rgba8unorm', 155 | usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING, 156 | }); 157 | 158 | const textureDataBuffer = this.device.createBuffer({ 159 | // BUG? WTF is up with this?!? bytesPerRow has to be a multiple of 256? 160 | size: 256, 161 | usage: GPUBufferUsage.COPY_SRC, 162 | mappedAtCreation: true, 163 | }); 164 | const textureDataArray = new Uint8Array(textureDataBuffer.getMappedRange()); 165 | textureDataArray.set(imageData); 166 | textureDataBuffer.unmap(); 167 | 168 | const commandEncoder = this.device.createCommandEncoder({}); 169 | commandEncoder.copyBufferToTexture({ 170 | buffer: textureDataBuffer, 171 | bytesPerRow: 256, 172 | rowsPerImage: 0, // What is this for? 173 | }, { texture: texture }, imageSize); 174 | this.device.queue.submit([commandEncoder.finish()]); 175 | 176 | return texture; 177 | } 178 | } -------------------------------------------------------------------------------- /js/webgpu-renderer/wgsl-debug-helper.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Brandon Jones 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | const LOG_FULL_SHADER_TEXT = true; 22 | 23 | const MESSAGE_STYLE = { 24 | 'info': { 25 | icon: 'ℹ️', 26 | logFn: console.info, 27 | }, 28 | 'warning': { 29 | icon: '⚠️', 30 | logFn: console.warn, 31 | }, 32 | 'error': { 33 | icon: '⛔', 34 | logFn: console.error, 35 | } 36 | } 37 | 38 | /** 39 | * A method that captures errors returned by compiling a WebGPU shader module 40 | * and annotates them with additional information before echoing to the console 41 | * to aid with debugging. 42 | */ 43 | if ('GPUDevice' in window) { 44 | const origCreateShaderModule = GPUDevice.prototype.createShaderModule; 45 | GPUDevice.prototype.createShaderModule = function(descriptor) { 46 | if (!this.pushErrorScope) { 47 | return origCreateShaderModule.call(this, descriptor); 48 | } 49 | 50 | this.pushErrorScope('validation'); 51 | 52 | const shaderModule = origCreateShaderModule.call(this, descriptor); 53 | 54 | const validationPromise = this.popErrorScope().then((error) => { 55 | // If compilationInfo is not available in this browser just echo any error 56 | // messages we get. 57 | if (!shaderModule.compilationInfo && error) { 58 | console.error(error.message); 59 | } else { 60 | return error; 61 | } 62 | }); 63 | 64 | if (shaderModule.compilationInfo) { 65 | shaderModule.compilationInfo().then(async (info) => { 66 | const validationError = await validationPromise; 67 | 68 | if (!info.messages.length && !validationError) { 69 | return; 70 | } 71 | 72 | const messageCount = { 73 | error: 0, 74 | warning: 0, 75 | info: 0, 76 | }; 77 | 78 | for (const message of info.messages) { 79 | messageCount[message.type] += 1; 80 | } 81 | 82 | if (messageCount.error == 0 && validationError) { 83 | messageCount.error = 1; 84 | } 85 | 86 | const label = shaderModule.label; 87 | let groupLabel = (label ? `"${label}"` : 'Shader') + 88 | ' returned compilation messages:'; 89 | for (const type in messageCount) { 90 | if (messageCount[type] > 0) { 91 | groupLabel += ` ${messageCount[type]}${MESSAGE_STYLE[type].icon}`; 92 | } 93 | } 94 | 95 | if (messageCount.error == 0) { 96 | console.groupCollapsed(groupLabel); 97 | } else { 98 | console.group(groupLabel); 99 | } 100 | 101 | const code = descriptor.code; 102 | for (const message of info.messages) { 103 | const type = message.type; 104 | 105 | // If the message doesn't have an associated position in the code just 106 | // repeat the message verbatim 107 | if (message.lineNum == 0 && message.linePos == 0) { 108 | MESSAGE_STYLE[type].logFn(message.message); 109 | continue; 110 | } 111 | 112 | const length = Math.max(message.length, 1); 113 | const lineStartIndex = code.lastIndexOf('\n', message.offset); 114 | const lineStart = code.substring(lineStartIndex+1, message.offset); 115 | const highlightText = code.substr(message.offset, length); 116 | const lineEndIndex = code.indexOf('\n', message.offset+length); 117 | const lineEnd = code.substring(message.offset+length, lineEndIndex == -1 ? undefined : lineEndIndex); 118 | 119 | MESSAGE_STYLE[type].logFn( 120 | `%c${message.lineNum}:${message.linePos} - %c${message.message}\n%c${lineStart}%c${highlightText}%c${lineEnd}`, 121 | 'font-weight: bold;', 122 | 'font-weight: default;', 123 | 'color: green;', 124 | 'color: lightgrey; background-color: darkred; font-weight: bold;', 125 | 'color: green;'); 126 | } 127 | 128 | if (validationError) { 129 | console.groupCollapsed("Validation Error Message"); 130 | console.error(validationError.message); 131 | console.groupEnd(); 132 | } 133 | 134 | if (LOG_FULL_SHADER_TEXT) { 135 | // Output the full shader text with numbered lines for easier reference. 136 | let numberedCodeLines = ''; 137 | const codeLines = code.split('\n'); 138 | const padLength = codeLines.length.toString().length; 139 | for (let i = 0; i < codeLines.length; ++i) { 140 | const lineNum = (i+1).toString().padStart(padLength, ' '); 141 | numberedCodeLines += `${lineNum}: ${codeLines[i]}\n`; 142 | } 143 | 144 | console.groupCollapsed("Full shader text"); 145 | console.log(numberedCodeLines); 146 | console.groupEnd(); 147 | } 148 | 149 | console.groupCollapsed("Stack Trace"); 150 | console.trace(); 151 | console.groupEnd(); 152 | 153 | console.groupEnd(); 154 | }); 155 | } 156 | 157 | return shaderModule; 158 | } 159 | } 160 | 161 | // Template literal tag that offers several preprocessor improvements to WGSL 162 | // shaders. For now it's just preprocessor #if/elif/else/endif statements. 163 | const preprocessorSymbols = /#([a-z]*)\s*/gm 164 | export function wgsl(strings, ...values) { 165 | let stateStack = []; 166 | let state = { string: '', elseIsValid: false, expression: true }; 167 | let depth = 1; 168 | 169 | for (let i = 0; i < strings.length; ++i) { 170 | let string = strings[i]; 171 | let lastIndex = 0; 172 | let valueConsumed = false; 173 | let matchedSymbols = string.matchAll(preprocessorSymbols); 174 | 175 | for (const match of matchedSymbols) { 176 | state.string += string.substring(lastIndex, match.index); 177 | switch (match[1]) { 178 | case 'if': 179 | if (match.index + match[0].length != string.length) { 180 | console.error('WGSL preprocessor error: #if must be immediately followed by a template expression (ie: ${value})'); 181 | break; 182 | } 183 | valueConsumed = true; 184 | stateStack.push(state); 185 | depth++; 186 | state = { string: '', elseIsValid: true, expression: !!values[i] }; 187 | break; 188 | case 'elif': 189 | if (match.index + match[0].length != string.length) { 190 | console.error('WGSL preprocessor error: #elif must be immediately followed by a template expression (ie: ${value})'); 191 | break; 192 | } else if (!state.elseIsValid) { 193 | console.error('WGSL preprocessor error: #elif not preceeded by an #if or #elif'); 194 | break; 195 | } 196 | valueConsumed = true; 197 | if (state.expression && stateStack.length != depth) { 198 | stateStack.push(state); 199 | } 200 | state = { string: '', elseIsValid: true, expression: !!values[i] }; 201 | break; 202 | case 'else': 203 | if (!state.elseIsValid) { 204 | console.error('WGSL preprocessor error: #else not preceeded by an #if or #elif'); 205 | break; 206 | } 207 | if (state.expression && stateStack.length != depth) { 208 | stateStack.push(state); 209 | } 210 | state = { string: '', elseIsValid: false, expression: true }; 211 | break; 212 | case 'endif': 213 | const branchState = stateStack.length == depth ? stateStack.pop() : state; 214 | state = stateStack.pop(); 215 | depth--; 216 | if (branchState.expression) { 217 | state.string += branchState.string; 218 | } 219 | break; 220 | default: 221 | // Unknown preprocessor symbol. Emit it back into the output string unchanged. 222 | state.string += match[0]; 223 | break; 224 | } 225 | 226 | lastIndex = match.index + match[0].length; 227 | } 228 | 229 | // If the string didn't end on one of the preprocessor symbols append the rest of it here. 230 | if (lastIndex != string.length) { 231 | state.string += string.substring(lastIndex, string.length); 232 | } 233 | 234 | // If the next value wasn't consumed by the preprocessor symbol, append it here. 235 | if (!valueConsumed && values.length > i) { 236 | state.string += values[i]; 237 | } 238 | } 239 | 240 | if (stateStack.length) { 241 | console.error('WGSL preprocessor error: Mismatch #if/#endif count'); 242 | } 243 | 244 | return state.string; 245 | } -------------------------------------------------------------------------------- /matrix-shader-crash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGPU Shader Matrix crash 6 | 7 | 8 | 9 | 10 | 11 | 12 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /media/models/sponza/10381718147657362067.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/10381718147657362067.jpg -------------------------------------------------------------------------------- /media/models/sponza/10388182081421875623.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/10388182081421875623.jpg -------------------------------------------------------------------------------- /media/models/sponza/11474523244911310074.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/11474523244911310074.jpg -------------------------------------------------------------------------------- /media/models/sponza/11490520546946913238.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/11490520546946913238.jpg -------------------------------------------------------------------------------- /media/models/sponza/11872827283454512094.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/11872827283454512094.jpg -------------------------------------------------------------------------------- /media/models/sponza/11968150294050148237.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/11968150294050148237.jpg -------------------------------------------------------------------------------- /media/models/sponza/1219024358953944284.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/1219024358953944284.jpg -------------------------------------------------------------------------------- /media/models/sponza/12501374198249454378.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/12501374198249454378.jpg -------------------------------------------------------------------------------- /media/models/sponza/13196865903111448057.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/13196865903111448057.jpg -------------------------------------------------------------------------------- /media/models/sponza/13824894030729245199.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/13824894030729245199.jpg -------------------------------------------------------------------------------- /media/models/sponza/13982482287905699490.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/13982482287905699490.jpg -------------------------------------------------------------------------------- /media/models/sponza/14118779221266351425.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/14118779221266351425.jpg -------------------------------------------------------------------------------- /media/models/sponza/14170708867020035030.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/14170708867020035030.jpg -------------------------------------------------------------------------------- /media/models/sponza/14267839433702832875.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/14267839433702832875.jpg -------------------------------------------------------------------------------- /media/models/sponza/14650633544276105767.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/14650633544276105767.jpg -------------------------------------------------------------------------------- /media/models/sponza/15295713303328085182.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/15295713303328085182.jpg -------------------------------------------------------------------------------- /media/models/sponza/15722799267630235092.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/15722799267630235092.jpg -------------------------------------------------------------------------------- /media/models/sponza/16275776544635328252.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/16275776544635328252.png -------------------------------------------------------------------------------- /media/models/sponza/16299174074766089871.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/16299174074766089871.jpg -------------------------------------------------------------------------------- /media/models/sponza/16885566240357350108.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/16885566240357350108.jpg -------------------------------------------------------------------------------- /media/models/sponza/17556969131407844942.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/17556969131407844942.jpg -------------------------------------------------------------------------------- /media/models/sponza/17876391417123941155.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/17876391417123941155.jpg -------------------------------------------------------------------------------- /media/models/sponza/2051777328469649772.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/2051777328469649772.jpg -------------------------------------------------------------------------------- /media/models/sponza/2185409758123873465.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/2185409758123873465.jpg -------------------------------------------------------------------------------- /media/models/sponza/2299742237651021498.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/2299742237651021498.jpg -------------------------------------------------------------------------------- /media/models/sponza/2374361008830720677.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/2374361008830720677.jpg -------------------------------------------------------------------------------- /media/models/sponza/2411100444841994089.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/2411100444841994089.jpg -------------------------------------------------------------------------------- /media/models/sponza/2775690330959970771.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/2775690330959970771.jpg -------------------------------------------------------------------------------- /media/models/sponza/2969916736137545357.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/2969916736137545357.jpg -------------------------------------------------------------------------------- /media/models/sponza/332936164838540657.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/332936164838540657.jpg -------------------------------------------------------------------------------- /media/models/sponza/3371964815757888145.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/3371964815757888145.jpg -------------------------------------------------------------------------------- /media/models/sponza/3455394979645218238.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/3455394979645218238.jpg -------------------------------------------------------------------------------- /media/models/sponza/3628158980083700836.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/3628158980083700836.jpg -------------------------------------------------------------------------------- /media/models/sponza/3827035219084910048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/3827035219084910048.jpg -------------------------------------------------------------------------------- /media/models/sponza/4477655471536070370.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/4477655471536070370.jpg -------------------------------------------------------------------------------- /media/models/sponza/4601176305987539675.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/4601176305987539675.jpg -------------------------------------------------------------------------------- /media/models/sponza/466164707995436622.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/466164707995436622.jpg -------------------------------------------------------------------------------- /media/models/sponza/4675343432951571524.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/4675343432951571524.jpg -------------------------------------------------------------------------------- /media/models/sponza/4871783166746854860.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/4871783166746854860.jpg -------------------------------------------------------------------------------- /media/models/sponza/4910669866631290573.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/4910669866631290573.jpg -------------------------------------------------------------------------------- /media/models/sponza/4975155472559461469.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/4975155472559461469.jpg -------------------------------------------------------------------------------- /media/models/sponza/5061699253647017043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/5061699253647017043.png -------------------------------------------------------------------------------- /media/models/sponza/5792855332885324923.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/5792855332885324923.jpg -------------------------------------------------------------------------------- /media/models/sponza/5823059166183034438.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/5823059166183034438.jpg -------------------------------------------------------------------------------- /media/models/sponza/6047387724914829168.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/6047387724914829168.jpg -------------------------------------------------------------------------------- /media/models/sponza/6151467286084645207.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/6151467286084645207.jpg -------------------------------------------------------------------------------- /media/models/sponza/6593109234861095314.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/6593109234861095314.jpg -------------------------------------------------------------------------------- /media/models/sponza/6667038893015345571.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/6667038893015345571.jpg -------------------------------------------------------------------------------- /media/models/sponza/6772804448157695701.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/6772804448157695701.jpg -------------------------------------------------------------------------------- /media/models/sponza/7056944414013900257.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/7056944414013900257.jpg -------------------------------------------------------------------------------- /media/models/sponza/715093869573992647.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/715093869573992647.jpg -------------------------------------------------------------------------------- /media/models/sponza/7268504077753552595.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/7268504077753552595.jpg -------------------------------------------------------------------------------- /media/models/sponza/7441062115984513793.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/7441062115984513793.jpg -------------------------------------------------------------------------------- /media/models/sponza/755318871556304029.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/755318871556304029.jpg -------------------------------------------------------------------------------- /media/models/sponza/759203620573749278.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/759203620573749278.jpg -------------------------------------------------------------------------------- /media/models/sponza/7645212358685992005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/7645212358685992005.jpg -------------------------------------------------------------------------------- /media/models/sponza/7815564343179553343.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/7815564343179553343.jpg -------------------------------------------------------------------------------- /media/models/sponza/8006627369776289000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8006627369776289000.png -------------------------------------------------------------------------------- /media/models/sponza/8051790464816141987.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8051790464816141987.jpg -------------------------------------------------------------------------------- /media/models/sponza/8114461559286000061.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8114461559286000061.jpg -------------------------------------------------------------------------------- /media/models/sponza/8481240838833932244.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8481240838833932244.jpg -------------------------------------------------------------------------------- /media/models/sponza/8503262930880235456.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8503262930880235456.jpg -------------------------------------------------------------------------------- /media/models/sponza/8747919177698443163.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8747919177698443163.jpg -------------------------------------------------------------------------------- /media/models/sponza/8750083169368950601.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8750083169368950601.jpg -------------------------------------------------------------------------------- /media/models/sponza/8773302468495022225.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8773302468495022225.jpg -------------------------------------------------------------------------------- /media/models/sponza/8783994986360286082.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/8783994986360286082.jpg -------------------------------------------------------------------------------- /media/models/sponza/9288698199695299068.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/9288698199695299068.jpg -------------------------------------------------------------------------------- /media/models/sponza/9916269861720640319.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/9916269861720640319.jpg -------------------------------------------------------------------------------- /media/models/sponza/README.md: -------------------------------------------------------------------------------- 1 | # Sponza 2 | 3 | ## Screenshot 4 | 5 | Lights are shown here, they are not part of the model. 6 | 7 | ![screenshot](screenshot/screenshot.jpg) 8 | 9 | ## Model notes 10 | 11 | Tangents have been computed using MikkTSpace, as the original OBJ model did not have them. 12 | I have manually inspected the normals, and it looks correct to me. 13 | Be aware that W is -1.0 for most of the tangent signs, you will need to handle tangent W for correct results. 14 | 15 | 16 | ## Sources 17 | 18 | ### http://www.crytek.com/cryengine/cryengine3/downloads 19 | - http://www.crytek.com/download/sponza\_obj.rar 20 | - http://www.crytek.com/download/sponza\_textures.rar 21 | 22 | ### http://www.alexandre-pestana.com/pbr-textures-sponza/ 23 | - http://www.alexandre-pestana.com/downloads/SponzaPBR\_Textures.rar 24 | 25 | I needed to resize some of the alpha mask textures to the 1024x1024 resolution used by the new texture pack, 26 | and merge in diffuse with alpha. 27 | I also repacked the separate metallic/roughness textures into the glTF layout (G - roughness, B - metallic). 28 | The images are also re-encoded as PNG instead of TGA. 29 | All the materials also had a constant diffuse factor of about 0.58. I assume it was supposed to be there, so I kept it. 30 | I also ran the vertices and indices through a mesh optimizer. 31 | 32 | ## Licensing notes 33 | 34 | Taken from copyright.txt in SponzaPBR\_Textures.rar 35 | 36 | ``` 37 | PBR textures for the Sponza model. 38 | For more informations: www.alexandre-pestana.com 39 | 40 | 41 | Original copyright: 42 | 43 | July 14, 2011 Morgan McGuire modified the model from Crytek's OBJ 44 | export to correct some small errors. He computed bump maps from the 45 | normal maps using normal2bump.cpp (since 47 | MTL files expect height bumps, not normals), put the "mask" textures 48 | into the alpha channel of the associated diffuse texture, cleaned up 49 | noise in the masks, created the missing gi_flag.tga texture, and 50 | removed the long untextured banner floating in the middle of the 51 | atrium that appears in the file but in none of the published images of 52 | the model. The banner is in banner.obj. 53 | 54 | 55 | 56 | http://www.crytek.com/cryengine/cryengine3/downloads 57 | 58 | 59 | Sponza Model 60 | August 19, 2010 61 | The Atrium Sponza Palace, Dubrovnik, is an elegant and improved model created by Frank Meinl. The original Sponza model was created by Marko Dabrovic in early 2002. Over the years, the Sponza Atrium scene has become one of the most popular 3D scenes for testing global illumination and radiosity due to it's specific architectural structure which is particularly complex for global illumination light. 62 | 63 | However, nowadays it is considered as a simple model, thus it was decided to crate a new model with highly improved appearance and scene complexity. It is donated to the public for radiosity and is represented in several different formats (3ds, Obj) for use with various commercial 3D applications and renderers. 64 | 65 | 66 | Screenshot from the I3D paper 67 | http://crytek.com/sites/default/files/20100301_lpv.pdf 68 | ``` 69 | -------------------------------------------------------------------------------- /media/models/sponza/Sponza.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/Sponza.bin -------------------------------------------------------------------------------- /media/models/sponza/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/media/models/sponza/white.png -------------------------------------------------------------------------------- /media/webgpu-logo.svg: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /pipeline-fails/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Pipeline failure tests 11 | 12 | 13 |

Pipeline failure tests

14 | 18 | 19 | -------------------------------------------------------------------------------- /pipeline-fails/pipeline-test.js: -------------------------------------------------------------------------------- 1 | const logDiv = document.createElement('div'); 2 | logDiv.classList.add('log'); 3 | logDiv.innerHTML = '

Log:

'; 4 | document.body.appendChild(logDiv); 5 | 6 | function LogMessage(message, style = 'info') { 7 | const messageP = document.createElement('p'); 8 | messageP.classList.add(style); 9 | messageP.innerText = message; 10 | logDiv.appendChild(messageP); 11 | } 12 | 13 | function LogInfo(message) { 14 | LogMessage(message, 'info'); 15 | } 16 | 17 | function LogWarning(message) { 18 | LogMessage(message, 'warn'); 19 | } 20 | 21 | function LogError(message) { 22 | LogMessage(message, 'error'); 23 | document.body.style.backgroundColor = 'red'; 24 | } 25 | 26 | export async function RunPipelineTest(pipelineType, shaderCode, pipelineDesc) { 27 | try { 28 | const adapter = await navigator.gpu?.requestAdapter(); 29 | if (!adapter) { 30 | LogError('Unable to request adapter. WebGPU may not be supported.'); 31 | return; 32 | } 33 | 34 | const device = await adapter.requestDevice(); 35 | device.lost.then((lost) => { 36 | LogError(`WebGPU device lost (Reason - ${lost.reason}): ${lost.message}`); 37 | }); 38 | 39 | const shaderModule = device.createShaderModule({ 40 | code: shaderCode, 41 | }); 42 | shaderModule.getCompilationInfo().then((info) => { 43 | if (!info.messages.length) { return; } 44 | LogMessage('Shader compilation produced messages: '); 45 | for (message of info.messages) { 46 | LogMessage(`${message.lineNum}:${message.linePos} - ${message.message}`, message.type); 47 | } 48 | }); 49 | 50 | const shaderDiv = document.createElement('div'); 51 | shaderDiv.classList.add('shader'); 52 | shaderDiv.innerHTML = `

Shader Code:

${shaderCode}
`; 53 | document.body.appendChild(shaderDiv); 54 | 55 | let pipelinePromise; 56 | switch (pipelineType) { 57 | case 'render': 58 | if (!pipelineDesc) { 59 | pipelineDesc = { 60 | layout: 'auto', 61 | vertex: {}, 62 | fragment: { 63 | targets: [{format: 'rgba8unorm'}] 64 | } 65 | }; 66 | } 67 | 68 | pipelineDesc.vertex.module = shaderModule; 69 | if (pipelineDesc.fragment) { pipelineDesc.fragment.module = shaderModule; } 70 | pipelinePromise = device.createRenderPipelineAsync(pipelineDesc); 71 | break; 72 | 73 | case 'compute': 74 | if (!pipelineDesc) { 75 | pipelineDesc = { 76 | layout: 'auto', 77 | compute: {}, 78 | }; 79 | } 80 | 81 | pipelineDesc.compute.module = shaderModule; 82 | pipelinePromise = device.createRenderPipelineAsync(pipelineDesc); 83 | break; 84 | 85 | default: LogError(`Unknown pipeline type "${pipelineType}"`); return; 86 | } 87 | 88 | const pipelineDiv = document.createElement('div'); 89 | pipelineDiv.classList.add('shader'); 90 | pipelineDiv.innerHTML = `

Pipeline Desc:

${JSON.stringify(pipelineDesc, null, 2)}
`; 91 | document.body.appendChild(pipelineDiv); 92 | 93 | pipelinePromise.then((pipeline) => { 94 | LogInfo(`Pipeline creation succeeded!`); 95 | document.body.style.backgroundColor = 'green'; 96 | }).catch((error) => { 97 | LogError(`Pipeline creation failed: ${error.message}`); 98 | }); 99 | } catch(error) { 100 | LogError(`An error occurred: ${error.message}`); 101 | } 102 | } -------------------------------------------------------------------------------- /pipeline-fails/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | PBR 11 | 12 | 13 | 30 | 31 | -------------------------------------------------------------------------------- /pipeline-fails/spookyball-pbr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Spookyball PBR 11 | 12 | 13 | A reduced version of the main PBR shader from https://spookyball.com
14 | 147 | 148 | -------------------------------------------------------------------------------- /playcanvas-crash/crash1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 43 | 44 | -------------------------------------------------------------------------------- /playcanvas-crash/crash2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 50 | 51 | -------------------------------------------------------------------------------- /playcanvas-crash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

PlayCanvas Android crashes, reduced shaders

4 | 8 | 9 | -------------------------------------------------------------------------------- /resize-crash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | WebGPU Resize Crash Bug Repro 13 | 14 | 25 | 26 | 27 | 28 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /resize-crash/query-args.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Immersive Web Community Group 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /* 22 | Provides a simple way to get values from the query string if they're present 23 | and use a default value if not. Not strictly a "WebGL" utility, but I use it 24 | frequently enough for debugging that I wanted to include it here. 25 | 26 | Example: 27 | For the URL http://example.com/index.html?particleCount=1000 28 | 29 | QueryArgs.getInt("particleCount", 100); // URL overrides, returns 1000 30 | QueryArgs.getInt("particleSize", 10); // Not in URL, returns default of 10 31 | */ 32 | 33 | let urlArgs = null; 34 | window.onhashchange = function() { 35 | // Force re-parsing on next access 36 | urlArgs = null; 37 | }; 38 | 39 | function ensureArgsCached() { 40 | if (!urlArgs) { 41 | urlArgs = {}; 42 | let query = window.location.search.substring(1) || window.location.hash.substring(1); 43 | let vars = query.split('&'); 44 | for (let i = 0; i < vars.length; i++) { 45 | let pair = vars[i].split('='); 46 | urlArgs[pair[0].toLowerCase()] = decodeURIComponent(pair[1]); 47 | } 48 | } 49 | } 50 | 51 | export class QueryArgs { 52 | static getString(name, defaultValue) { 53 | ensureArgsCached(); 54 | let lcaseName = name.toLowerCase(); 55 | if (lcaseName in urlArgs) { 56 | return urlArgs[lcaseName]; 57 | } 58 | return defaultValue; 59 | } 60 | 61 | static getInt(name, defaultValue) { 62 | ensureArgsCached(); 63 | let lcaseName = name.toLowerCase(); 64 | if (lcaseName in urlArgs) { 65 | return parseInt(urlArgs[lcaseName], 10); 66 | } 67 | return defaultValue; 68 | } 69 | 70 | static getFloat(name, defaultValue) { 71 | ensureArgsCached(); 72 | let lcaseName = name.toLowerCase(); 73 | if (lcaseName in urlArgs) { 74 | return parseFloat(urlArgs[lcaseName]); 75 | } 76 | return defaultValue; 77 | } 78 | 79 | static getBool(name, defaultValue) { 80 | ensureArgsCached(); 81 | let lcaseName = name.toLowerCase(); 82 | if (lcaseName in urlArgs) { 83 | return parseInt(urlArgs[lcaseName], 10) != 0; 84 | } 85 | return defaultValue; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /sample-mask-error/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 70 | 71 | -------------------------------------------------------------------------------- /skybox-bug/README.md: -------------------------------------------------------------------------------- 1 | This repro renders a "skybox" by drawing a cube at the far plane by returning the transformed W component for both the Z and W components of the output position and doing a 'less-equal' depth compare. (This is a common trick in game dev to ensure the skybox renders behind everything else.) 2 | 3 | On most devices this renders correctly, but on Pixel 6 and Pixel 7 (ARM Mali GPUs) there's a lot of apparent Z fighting. -------------------------------------------------------------------------------- /skybox-bug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | WebGPU Skybox bug 9 | 10 | 31 | 32 | 33 | 34 | 132 | 133 | -------------------------------------------------------------------------------- /skybox-bug/skybox.js: -------------------------------------------------------------------------------- 1 | const SKYBOX_SHADER = /*wgsl*/` 2 | struct Camera { 3 | projection: mat4x4f, 4 | view: mat4x4f, 5 | }; 6 | @group(0) @binding(0) var camera : Camera; 7 | 8 | struct VertexOutput { 9 | @builtin(position) position : vec4f, 10 | @location(0) texcoord : vec3f, 11 | }; 12 | 13 | @vertex 14 | fn vertexMain(@location(0) position : vec4f) -> VertexOutput { 15 | var output : VertexOutput; 16 | 17 | var modelView = camera.view; 18 | // Drop the translation portion of the modelView matrix 19 | modelView[3] = vec4(0, 0, 0, modelView[3].w); 20 | output.position = camera.projection * modelView * position; 21 | // Returning the W component for both Z and W forces the geometry depth to 22 | // the far plane. When combined with a depth func of "less-equal" this makes 23 | // the sky write to any depth fragment that has not been written to yet. 24 | output.position = output.position.xyww; 25 | output.texcoord = position.xyz; 26 | 27 | return output; 28 | } 29 | 30 | @group(0) @binding(2) var environmentSampler : sampler; 31 | @group(0) @binding(3) var environmentTexture : texture_cube; 32 | 33 | @fragment 34 | fn fragmentMain(@location(0) texcoord : vec3f) -> @location(0) vec4f { 35 | return vec4f(texcoord + 1 * 0.5, 1); 36 | } 37 | `; 38 | 39 | const SKYBOX_VERTS = new Float32Array([ 40 | 1.0, 1.0, 1.0, // 0 41 | -1.0, 1.0, 1.0, // 1 42 | 1.0, -1.0, 1.0, // 2 43 | -1.0, -1.0, 1.0, // 3 44 | 1.0, 1.0, -1.0, // 4 45 | -1.0, 1.0, -1.0, // 5 46 | 1.0, -1.0, -1.0, // 6 47 | -1.0, -1.0, -1.0, // 7 48 | ]); 49 | 50 | const SKYBOX_INDICES = new Uint16Array([ 51 | // PosX (Right) 52 | 0, 2, 4, 53 | 6, 4, 2, 54 | 55 | // NegX (Left) 56 | 5, 3, 1, 57 | 3, 5, 7, 58 | 59 | // PosY (Top) 60 | 4, 1, 0, 61 | 1, 4, 5, 62 | 63 | // NegY (Bottom) 64 | 2, 3, 6, 65 | 7, 6, 3, 66 | 67 | // PosZ (Front) 68 | 0, 1, 2, 69 | 3, 2, 1, 70 | 71 | // NegZ (Back) 72 | 6, 5, 4, 73 | 5, 6, 7, 74 | ]); 75 | 76 | export class SkyboxRenderer { 77 | device; 78 | pipeline; 79 | 80 | skyboxVertexBuffer; 81 | skyboxIndexBuffer; 82 | 83 | constructor(device, frameBindGroupLayout, colorFormat, depthFormat) { 84 | this.device = device; 85 | 86 | const shaderModule = device.createShaderModule({ 87 | label: 'skybox shader', 88 | code: SKYBOX_SHADER, 89 | }); 90 | 91 | // Setup a render pipeline for drawing the skybox 92 | this.pipeline = device.createRenderPipeline({ 93 | label: `skybox pipeline`, 94 | layout: device.createPipelineLayout({ 95 | bindGroupLayouts: [ 96 | frameBindGroupLayout, 97 | ] 98 | }), 99 | vertex: { 100 | module: shaderModule, 101 | entryPoint: 'vertexMain', 102 | buffers: [{ 103 | arrayStride: 3 * Float32Array.BYTES_PER_ELEMENT, 104 | attributes: [{ 105 | shaderLocation: 0, 106 | format: 'float32x3', 107 | offset: 0, 108 | }] 109 | }] 110 | }, 111 | fragment: { 112 | module: shaderModule, 113 | entryPoint: 'fragmentMain', 114 | targets: [{ 115 | format: colorFormat, 116 | }], 117 | }, 118 | depthStencil: { 119 | depthWriteEnabled: false, 120 | depthCompare: 'less-equal', 121 | format: depthFormat, 122 | } 123 | }); 124 | 125 | this.skyboxVertexBuffer = device.createBuffer({ 126 | label: 'skybox vertex buffer', 127 | size: SKYBOX_VERTS.byteLength, 128 | usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, 129 | }); 130 | device.queue.writeBuffer(this.skyboxVertexBuffer, 0, SKYBOX_VERTS); 131 | 132 | this.skyboxIndexBuffer = device.createBuffer({ 133 | label: 'skybox index buffer', 134 | size: SKYBOX_INDICES.byteLength, 135 | usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, 136 | }); 137 | device.queue.writeBuffer(this.skyboxIndexBuffer, 0, SKYBOX_INDICES); 138 | } 139 | 140 | render(renderPass) { 141 | // Skybox is part of the frame bind group, which should already be bound prior to calling this method. 142 | renderPass.setPipeline(this.pipeline); 143 | renderPass.setVertexBuffer(0, this.skyboxVertexBuffer); 144 | renderPass.setIndexBuffer(this.skyboxIndexBuffer, 'uint16'); 145 | renderPass.drawIndexed(SKYBOX_INDICES.length); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/thumb.png -------------------------------------------------------------------------------- /webgpu-css-logo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 80 | 81 | 82 |

SVG:

83 |
84 | 85 |

CSS-only:

86 |
87 | 88 | 89 | -------------------------------------------------------------------------------- /webgpu-tilemap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | WebGPU Tilemap 11 | 12 | 30 | 31 | 32 | 33 | 86 | 87 | -------------------------------------------------------------------------------- /webgpu-tilemap/spelunky-tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/webgpu-tilemap/spelunky-tiles.png -------------------------------------------------------------------------------- /webgpu-tilemap/spelunky0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/webgpu-tilemap/spelunky0.png -------------------------------------------------------------------------------- /webgpu-tilemap/spelunky1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toji/webgpu-test/148e8fe87465777f4994f7998c7f2822e1b9b50a/webgpu-tilemap/spelunky1.png --------------------------------------------------------------------------------