From 4d63b5513958d695dd195e392cd0e654d2e8273d Mon Sep 17 00:00:00 2001 From: Greggman Date: Wed, 26 Jun 2024 18:22:27 -0700 Subject: [PATCH] Consider making optimal WebGL versions (#132) 1. Add more textures to all examples 2. Optimize WebGL examples --- 3rdparty/twgl-full.module.js | 72 +- webgpu/lessons/webgpu-from-webgl.md | 6 +- webgpu/lessons/webgpu-optimization.md | 12 +- ...l-material-per-object-uniform-buffers.html | 537 +++++++++++++++ ...bgl-optimization-none-uniform-buffers.html | 84 ++- webgpu/webgl-optimization-none.html | 42 +- ...ptimization-uniform-buffers-one-large.html | 548 ++++++++++++++++ webgpu/webgpu-optimization-all.html | 603 ----------------- webgpu/webgpu-optimization-none.html | 2 +- ...n-step3-global-vs-per-object-uniforms.html | 2 +- ...-optimization-step4-material-uniforms.html | 2 +- ...ly-updated-uniform-buffers-pre-submit.html | 615 ----------------- ...er-frequently-updated-uniform-buffers.html | 615 ----------------- ...optimization-step5-use-buffer-offsets.html | 4 +- ...-use-mapped-buffers-2-command-buffers.html | 619 ------------------ ...p6-use-mapped-buffers-dyanmic-offsets.html | 2 +- ...ep6-use-mapped-buffers-math-w-offsets.html | 2 +- ...optimization-step6-use-mapped-buffers.html | 4 +- ...mization-step7-double-buffer-2-submit.html | 2 +- ...on-step7-double-buffer-typedarray-set.html | 2 +- ...bgpu-optimization-step7-double-buffer.html | 2 +- 21 files changed, 1251 insertions(+), 2526 deletions(-) create mode 100644 webgpu/webgl-optimization-global-material-per-object-uniform-buffers.html create mode 100644 webgpu/webgl-optimization-uniform-buffers-one-large.html delete mode 100644 webgpu/webgpu-optimization-all.html delete mode 100644 webgpu/webgpu-optimization-step5-double-buffer-frequently-updated-uniform-buffers-pre-submit.html delete mode 100644 webgpu/webgpu-optimization-step5-double-buffer-frequently-updated-uniform-buffers.html delete mode 100644 webgpu/webgpu-optimization-step6-use-mapped-buffers-2-command-buffers.html diff --git a/3rdparty/twgl-full.module.js b/3rdparty/twgl-full.module.js index 9702f31a..d7338867 100644 --- a/3rdparty/twgl-full.module.js +++ b/3rdparty/twgl-full.module.js @@ -1,4 +1,4 @@ -/* @license twgl.js 5.3.1 Copyright (c) 2015, Gregg Tavares All Rights Reserved. +/* @license twgl.js 5.6.0 Copyright (c) 2015, Gregg Tavares All Rights Reserved. Available via the MIT license. see: http://github.com/greggman/twgl.js for details */ /* @@ -5104,7 +5104,8 @@ const TEXTURE_MIN_LOD = 0x813a; const TEXTURE_MAX_LOD = 0x813b; const TEXTURE_BASE_LEVEL = 0x813c; const TEXTURE_MAX_LEVEL = 0x813d; - +const TEXTURE_COMPARE_MODE = 0x884C; +const TEXTURE_COMPARE_FUNC = 0x884D; /* Pixel store */ const UNPACK_ALIGNMENT = 0x0cf5; @@ -5526,6 +5527,8 @@ function setDefaults$1(newDefaults) { * @property {number} [maxLod] TEXTURE_MAX_LOD setting * @property {number} [baseLevel] TEXTURE_BASE_LEVEL setting * @property {number} [maxLevel] TEXTURE_MAX_LEVEL setting + * @property {number} [compareFunc] TEXTURE_COMPARE_FUNC setting + * @property {number} [compareMode] TEXTURE_COMPARE_MODE setting * @property {number} [unpackAlignment] The `gl.UNPACK_ALIGNMENT` used when uploading an array. Defaults to 1. * @property {number[]|ArrayBufferView} [color] Color to initialize this texture with if loading an image asynchronously. * The default use a blue 1x1 pixel texture. You can set another default by calling `twgl.setDefaults` @@ -5661,18 +5664,24 @@ function setTextureSamplerParameters(gl, target, parameteriFn, options) { if (options.wrapT) { parameteriFn.call(gl, target, TEXTURE_WRAP_T, options.wrapT); } - if (options.minLod) { + if (options.minLod !== undefined) { parameteriFn.call(gl, target, TEXTURE_MIN_LOD, options.minLod); } - if (options.maxLod) { + if (options.maxLod !== undefined) { parameteriFn.call(gl, target, TEXTURE_MAX_LOD, options.maxLod); } - if (options.baseLevel) { + if (options.baseLevel !== undefined) { parameteriFn.call(gl, target, TEXTURE_BASE_LEVEL, options.baseLevel); } - if (options.maxLevel) { + if (options.maxLevel !== undefined) { parameteriFn.call(gl, target, TEXTURE_MAX_LEVEL, options.maxLevel); } + if (options.compareFunc !== undefined) { + parameteriFn.call(gl, target, TEXTURE_COMPARE_FUNC, options.compareFunc); + } + if (options.compareMode !== undefined) { + parameteriFn.call(gl, target, TEXTURE_COMPARE_MODE, options.compareMode); + } } /** @@ -7554,7 +7563,7 @@ function createProgramNoCheck(gl, shaders, programOptions) { * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. * @param {WebGLShader[]|string[]} shaders The shaders to attach, or element ids for their source, or strings that contain their source * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in - * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {number[]|module:twgl.ErrorCallback} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @return {WebGLProgram?} the created program or null if error of a callback was provided. @@ -7622,7 +7631,7 @@ function wrapCallbackFnToAsyncFn(fn) { * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. * @param {WebGLShader[]|string[]} shaders The shaders to attach, or element ids for their source, or strings that contain their source * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in - * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {number[]|module:twgl.ErrorCallback} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @return {Promise} The created program @@ -7639,7 +7648,7 @@ const createProgramAsync = wrapCallbackFnToAsyncFn(createProgram); * shaders or ids. The first is assumed to be the vertex shader, * the second the fragment shader. * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in - * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {number[]|module:twgl.ErrorCallback} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @return {Promise} The created ProgramInfo @@ -7706,7 +7715,7 @@ function getProgramErrors(gl, program, errFn) { * tags for the shaders. The first is assumed to be the * vertex shader, the second the fragment shader. * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in - * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {number[]|module:twgl.ErrorCallback} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @return {WebGLProgram?} the created program or null if error or a callback was provided. @@ -7742,7 +7751,7 @@ function createProgramFromScripts( * shaders. The first is assumed to be the vertex shader, * the second the fragment shader. * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in - * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {number[]|module:twgl.ErrorCallback} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @return {WebGLProgram?} the created program or null if error or a callback was provided. @@ -8092,6 +8101,7 @@ function createUniformBlockUniformSetter(view, isArray, rows, cols) { * @property {ArrayBuffer} array The array buffer that contains the uniform values * @property {Float32Array} asFloat A float view on the array buffer. This is useful * inspecting the contents of the buffer in the debugger. + * @property {Uint8Array} asUint8t A uint8 view on the array buffer. * @property {WebGLBuffer} buffer A WebGL buffer that will hold a copy of the uniform values for rendering. * @property {number} [offset] offset into buffer * @property {Object} uniforms A uniform name to ArrayBufferView map. @@ -8110,6 +8120,15 @@ function createUniformBlockUniformSetter(view, isArray, rows, cols) { * @memberOf module:twgl */ +/** + * Options to allow createUniformBlockInfo to use an existing buffer and arrayBuffer at an offset + * @typedef {Object} UniformBlockInfoOptions + * @property {ArrayBuffer} [array] an existing array buffer to use for values + * @property {number} [offset] the offset in bytes to use in the array buffer (default = 0) + * @property {WebGLBuffer} [buffer] the buffer to use for this uniform block info + * @property {number} [bufferOffset] the offset in bytes in the buffer to use (default = use offset above) + */ + /** * Creates a `UniformBlockInfo` for the specified block * @@ -8124,10 +8143,11 @@ function createUniformBlockUniformSetter(view, isArray, rows, cols) { * @param {module:twgl.UniformBlockSpec} uniformBlockSpec. A UniformBlockSpec as returned * from {@link module:twgl.createUniformBlockSpecFromProgram}. * @param {string} blockName The name of the block. + * @param {module:twgl.UniformBlockInfoOptions} [options] Optional options for using existing an existing buffer and arrayBuffer * @return {module:twgl.UniformBlockInfo} The created UniformBlockInfo * @memberOf module:twgl/programs */ -function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockName) { +function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockName, options = {}) { const blockSpecs = uniformBlockSpec.blockSpecs; const uniformData = uniformBlockSpec.uniformData; const blockSpec = blockSpecs[blockName]; @@ -8138,10 +8158,14 @@ function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockN uniforms: {}, }; } - const array = new ArrayBuffer(blockSpec.size); - const buffer = gl.createBuffer(); + const offset = options.offset ?? 0; + const array = options.array ?? new ArrayBuffer(blockSpec.size); + const buffer = options.buffer ?? gl.createBuffer(); const uniformBufferIndex = blockSpec.index; gl.bindBuffer(UNIFORM_BUFFER, buffer); + if (!options.buffer) { + gl.bufferData(UNIFORM_BUFFER, array.byteLength, DYNAMIC_DRAW); + } gl.uniformBlockBinding(program, blockSpec.index, uniformBufferIndex); let prefix = blockName + "."; @@ -8166,7 +8190,7 @@ function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockN const byteLength = isArray ? pad(typeInfo.size, 16) * data.size : typeInfo.size * data.size; - const uniformView = new Type(array, data.offset, byteLength / Type.BYTES_PER_ELEMENT); + const uniformView = new Type(array, offset + data.offset, byteLength / Type.BYTES_PER_ELEMENT); uniforms[name] = uniformView; // Note: I'm not sure what to do here. The original // idea was to create TypedArray views into each part @@ -8201,9 +8225,12 @@ function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockN name: blockName, array, asFloat: new Float32Array(array), // for debugging + asUint8: new Uint8Array(array), // needed for gl.bufferSubData because it doesn't take an array buffer buffer, uniforms, setters, + offset: options.bufferOffset ?? offset, + size: blockSpec.size, }; } @@ -8220,11 +8247,12 @@ function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockN * @param {module:twgl.ProgramInfo} programInfo a `ProgramInfo` * as returned from {@link module:twgl.createProgramInfo} * @param {string} blockName The name of the block. + * @param {module:twgl.UniformBlockInfoOptions} [options] Optional options for using existing an existing buffer and arrayBuffer * @return {module:twgl.UniformBlockInfo} The created UniformBlockInfo * @memberOf module:twgl/programs */ -function createUniformBlockInfo(gl, programInfo, blockName) { - return createUniformBlockInfoFromProgram(gl, programInfo.program, programInfo.uniformBlockSpec, blockName); +function createUniformBlockInfo(gl, programInfo, blockName, options = {}) { + return createUniformBlockInfoFromProgram(gl, programInfo.program, programInfo.uniformBlockSpec, blockName, options); } /** @@ -8250,7 +8278,7 @@ function bindUniformBlock(gl, programInfo, uniformBlockInfo) { const blockSpec = uniformBlockSpec.blockSpecs[uniformBlockInfo.name]; if (blockSpec) { const bufferBindIndex = blockSpec.index; - gl.bindBufferRange(UNIFORM_BUFFER, bufferBindIndex, uniformBlockInfo.buffer, uniformBlockInfo.offset || 0, uniformBlockInfo.array.byteLength); + gl.bindBufferRange(UNIFORM_BUFFER, bufferBindIndex, uniformBlockInfo.buffer, uniformBlockInfo.offset || 0, uniformBlockInfo.size ?? uniformBlockInfo.array.byteLength); return true; } return false; @@ -8273,7 +8301,7 @@ function bindUniformBlock(gl, programInfo, uniformBlockInfo) { */ function setUniformBlock(gl, programInfo, uniformBlockInfo) { if (bindUniformBlock(gl, programInfo, uniformBlockInfo)) { - gl.bufferData(UNIFORM_BUFFER, uniformBlockInfo.array, DYNAMIC_DRAW); + gl.bufferSubData(UNIFORM_BUFFER, 0, uniformBlockInfo.asUint8, uniformBlockInfo.offset || 0, uniformBlockInfo.size || 0); } } @@ -8718,6 +8746,8 @@ function setBuffersAndAttributes(gl, programInfo, buffers) { /** * @typedef {Object} ProgramInfo * @property {WebGLProgram} program A shader program + * @property {Object} uniformLocations The uniform locations of each uniform + * @property {Object} attribLocations The locations of each attribute * @property {Object} uniformSetters object of setters as returned from createUniformSetters, * @property {Object} attribSetters object of setters as returned from createAttribSetters, * @property {module:twgl.UniformBlockSpec} [uniformBlockSpec] a uniform block spec for making UniformBlockInfos with createUniformBlockInfo etc.. @@ -8749,6 +8779,8 @@ function createProgramInfoFromProgram(gl, program) { program, uniformSetters, attribSetters, + uniformLocations: Object.fromEntries(Object.entries(uniformSetters).map(([k, v]) => [k, v.location])), + attribLocations: Object.fromEntries(Object.entries(attribSetters).map(([k, v]) => [k, v.location])), }; if (isWebGL2(gl)) { @@ -8785,7 +8817,7 @@ const notIdRE = /\s|{|}|;/; * shaders or ids. The first is assumed to be the vertex shader, * the second the fragment shader. * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in - * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {number[]|module:twgl.ErrorCallback} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @return {module:twgl.ProgramInfo?} The created ProgramInfo or null if it failed to link or compile diff --git a/webgpu/lessons/webgpu-from-webgl.md b/webgpu/lessons/webgpu-from-webgl.md index a9801fdb..463732cd 100644 --- a/webgpu/lessons/webgpu-from-webgl.md +++ b/webgpu/lessons/webgpu-from-webgl.md @@ -1420,8 +1420,10 @@ ideas. Note: If you are comparing WebGL to WebGPU in [the article on optimization](webgpu-optimization.html) here are 2 WebGL samples you can use to compare -* [Drawing up to 20000 objects in WebGL using standard WebGL uniforms](../webgl-optimization-none.html) -* [Drawing up to 20000 objects in WebGL using uniform blocks](../webgl-optimization-none-uniform-buffers.html) +* [Drawing up to 30000 objects in WebGL using standard WebGL uniforms](../webgl-optimization-none.html) +* [Drawing up to 30000 objects in WebGL using uniform blocks](../webgl-optimization-none-uniform-buffers.html) +* [Drawing up to 30000 objects in WebGL using global/material/per object uniform blocks](../webgl-optimization-global-material-per-object-uniform-buffers.html) +* [Drawing up to 30000 objects in WebGL using one large uniform buffer](../webgl-optimization-uniform-buffers-one-large.html) Another article, if you're comparing performance of WebGL vs WebGPU see [this article](https://toji.dev/webgpu-best-practices/webgl-performance-comparison). diff --git a/webgpu/lessons/webgpu-optimization.md b/webgpu/lessons/webgpu-optimization.md index 0eda2c32..b42c96e7 100644 --- a/webgpu/lessons/webgpu-optimization.md +++ b/webgpu/lessons/webgpu-optimization.md @@ -351,13 +351,13 @@ We'll make 20 "materials" and then pick a material at random for each cube. ``` Now let's make data for each thing (cube) we want to draw. We'll support a -maximum of 20000. Like we have in the past, we'll make a uniform buffer for each +maximum of 30000. Like we have in the past, we'll make a uniform buffer for each object as well as a typed array we can update with uniform values. We'll also make a bind group for each object. And we'll pick some random values we can use to position and animate each object. ```js - const maxObjects = 20000; + const maxObjects = 30000; const objectInfos = []; for (let i = 0; i < maxObjects; ++i) { @@ -1265,7 +1265,7 @@ Then we can removed these uniforms from our perObject uniform buffer and add the global uniform buffer to each object's bind group. ```js - const maxObjects = 20000; + const maxObjects = 30000; const objectInfos = []; for (let i = 0; i < maxObjects; ++i) { @@ -1585,7 +1585,7 @@ settings. Instead we just need to add the material's uniform buffer to the object's bind group. ```js - const maxObjects = 20000; + const maxObjects = 30000; const objectInfos = []; for (let i = 0; i < maxObjects; ++i) { @@ -2079,7 +2079,7 @@ Other things that *might* help This is why, in our loop where we update our per object uniform values, for each object we have to create 2 `Float32Array` views into our mapped buffer. - For 10000 objects that's creating 20000 of these temporary views. + For 20000 objects that's creating 40000 of these temporary views. Adding offsets to every input would make them burdensome to use in my opinion but, just as a test, I wrote a modified version of the math functions that @@ -2097,7 +2097,7 @@ Other things that *might* help [It appears to be about 7% faster to use the offsets](../webgpu-optimization-step6-use-mapped-buffers-math-w-offsets.html). - It's up to you if you feel that's worthß it. For me personally, like I + It's up to you if you feel that's worth it. For me personally, like I mentioned at the top of the article, I'd prefer to keep it simple to use. I'm rarely trying to draw 10000 things. But, it's good to know, if I wanted to squeeze out more performance, this is one place I might find some. More likely diff --git a/webgpu/webgl-optimization-global-material-per-object-uniform-buffers.html b/webgpu/webgl-optimization-global-material-per-object-uniform-buffers.html new file mode 100644 index 00000000..35875444 --- /dev/null +++ b/webgpu/webgl-optimization-global-material-per-object-uniform-buffers.html @@ -0,0 +1,537 @@ + + + + + + WebGL Optimization - Uniform Blocks (global/material/perObject) + + + + +

+  
+  
+
diff --git a/webgpu/webgl-optimization-none-uniform-buffers.html b/webgpu/webgl-optimization-none-uniform-buffers.html
index 56cafeee..ca8aba91 100644
--- a/webgpu/webgl-optimization-none-uniform-buffers.html
+++ b/webgpu/webgl-optimization-none-uniform-buffers.html
@@ -304,7 +304,7 @@
     });
   }
 
-  const maxObjects = 20000;
+  const maxObjects = 30000;
   const objectInfos = [];
 
   for (let i = 0; i < maxObjects; ++i) {
@@ -338,8 +338,6 @@
 
   const canvasToSizeMap = new WeakMap();
   const degToRad = d => d * Math.PI / 180;
-  const worldValue = mat4.create();
-  const normalMatrix = mat3.identity();
 
   const settings = {
     numObjects: 1000,
@@ -398,6 +396,8 @@
     const viewProjectionMatrix = mat4.multiply(projection, viewMatrix);
 
     let mathElapsedTimeMs = 0;
+    let currentActiveTexture = -1;
+    const currentTextures = [];
 
     for (let i = 0; i < settings.numObjects; ++i) {
       const {
@@ -411,38 +411,72 @@
         rotationSpeed,
         scale,
       } = objectInfos[i];
+      const {
+        world,
+        normalMatrix,
+        viewProjection,
+        color,
+        lightWorldPosition,
+        viewWorldPosition,
+        shininess,
+      } = uboInfo.uniforms;
       const mathTimeStartMs = performance.now();
 
       // Compute a world matrix
-      mat4.identity(worldValue);
-      mat4.axisRotate(worldValue, axis, i + time * speed, worldValue);
-      mat4.translate(worldValue, [0, 0, Math.sin(i * 3.721 + time * speed) * radius], worldValue);
-      mat4.translate(worldValue, [0, 0, Math.sin(i * 9.721 + time * 0.1) * radius], worldValue);
-      mat4.rotateX(worldValue, time * rotationSpeed + i, worldValue);
-      mat4.scale(worldValue, [scale, scale, scale], worldValue);
+      mat4.identity(world);
+      mat4.axisRotate(world, axis, i + time * speed, world);
+      mat4.translate(world, [0, 0, Math.sin(i * 3.721 + time * speed) * radius], world);
+      mat4.translate(world, [0, 0, Math.sin(i * 9.721 + time * 0.1) * radius], world);
+      mat4.rotateX(world, time * rotationSpeed + i, world);
+      mat4.scale(world, [scale, scale, scale], world);
 
       // Inverse and transpose it into the normalMatrix value
-      mat3.fromMat4(mat4.transpose(mat4.inverse(worldValue)), normalMatrix);
-
-      const {color, shininess} = material;
-
-      twgl.setBlockUniforms(uboInfo, {
-        world: worldValue,
-        normalMatrix,
-        viewProjection: viewProjectionMatrix,
-        color,
-        lightWorldPosition: [-10, 30, 300],
-        viewWorldPosition: eye,
-        shininess,
-      });
+      mat3.fromMat4(mat4.transpose(mat4.inverse(world)), normalMatrix);
+
+      // const {color, shininess} = material;
+      color.set(material.color);
+      shininess[0] = material.shininess;
+
+      // do this manually since we're doing it manually in WebGPU
+      //twgl.setBlockUniforms(uboInfo, {
+      //  world: worldValue,
+      //  normalMatrix,
+      //  viewProjection: viewProjectionMatrix,
+      //  color,
+      //  lightWorldPosition: [-10, 30, 300],
+      //  viewWorldPosition: eye,
+      //  shininess,
+      //});
+      viewProjection.set(viewProjectionMatrix);
+      lightWorldPosition.set([-10, 30, 300]);
+      viewWorldPosition.set(eye);
 
       mathElapsedTimeMs += performance.now() - mathTimeStartMs;
 
       // upload the uniform values to the uniform buffer
-      twgl.setUniformBlock(gl, prgInfo, uboInfo);
-      twgl.setUniforms(prgInfo, uniforms);
+      //twgl.setUniformBlock(gl, prgInfo, uboInfo);
+      gl.bindBuffer(gl.UNIFORM_BUFFER, uboInfo.buffer);
+      gl.bufferSubData(gl.UNIFORM_BUFFER, 0, uboInfo.asUint8, 0, uboInfo.size);
+      const blockSpec = prgInfo.uniformBlockSpec.blockSpecs[uboInfo.name];
+      gl.bindBufferRange(gl.UNIFORM_BUFFER, blockSpec.index, uboInfo.buffer, uboInfo.offset, uboInfo.size);
+
+      //twgl.setUniforms(prgInfo, uniforms);
+
+      // Do it manually since we're doing it manually in WebGPU
+      const loc = prgInfo.uniformLocations;
+
+      if (currentTextures[0] !== uniforms.diffuseTexture) {
+        if (currentActiveTexture !== 0) {
+          currentActiveTexture = 0;
+          gl.activeTexture(gl.TEXTURE0);
+        }
+        currentTextures[0] = uniforms.diffuseTexture;
+        gl.bindTexture(gl.TEXTURE_2D, uniforms.diffuseTexture);
+        gl.uniform1i(loc.diffuseTexture, 0);
+      }
 
-      twgl.drawBufferInfo(gl, bufferInfo);
+      //twgl.drawBufferInfo(gl, bufferInfo);
+      gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
     }
     timingHelper.end();
 
diff --git a/webgpu/webgl-optimization-none.html b/webgpu/webgl-optimization-none.html
index 3148b3a4..f68d4c27 100644
--- a/webgpu/webgl-optimization-none.html
+++ b/webgpu/webgl-optimization-none.html
@@ -299,7 +299,7 @@
     });
   }
 
-  const maxObjects = 20000;
+  const maxObjects = 30000;
   const objectInfos = [];
 
   for (let i = 0; i < maxObjects; ++i) {
@@ -395,6 +395,8 @@
     const viewProjectionMatrix = mat4.multiply(projection, viewMatrix);
 
     let mathElapsedTimeMs = 0;
+    let currentActiveTexture = -1;
+    const currentTextures = [];
 
     for (let i = 0; i < settings.numObjects; ++i) {
       const {
@@ -410,7 +412,7 @@
       const mathTimeStartMs = performance.now();
 
       // Copy the viewProjectionMatrix into the uniform values for this object
-      uniforms.viewProjection.set(viewProjectionMatrix);
+      // uniforms.viewProjection.set(viewProjectionMatrix);
 
       // Compute a world matrix
       const worldValue = uniforms.world;
@@ -426,17 +428,39 @@
 
       const {color, shininess} = material;
 
-      uniforms.color.set(color);
-      uniforms.lightWorldPosition.set([-10, 30, 300]);
-      uniforms.viewWorldPosition.set(eye);
-      uniforms.shininess = shininess;
+      // uniforms.color.set(color);
+      // uniforms.lightWorldPosition.set([-10, 30, 300]);
+      // uniforms.viewWorldPosition.set(eye);
+      // uniforms.shininess = shininess;
 
       mathElapsedTimeMs += performance.now() - mathTimeStartMs;
 
-      // upload the uniform values to the uniform buffer
-      twgl.setUniforms(prgInfo, uniforms);
+      // update the uniforms
+      // twgl.setUniforms(prgInfo, uniforms);
 
-      twgl.drawBufferInfo(gl, bufferInfo);
+      // Do it manually since we're doing it manually in WebGPU
+      const loc = prgInfo.uniformLocations;
+
+      if (currentTextures[0] !== uniforms.diffuseTexture) {
+        if (currentActiveTexture !== 0) {
+          currentActiveTexture = 0;
+          gl.activeTexture(gl.TEXTURE0);
+        }
+        currentTextures[0] = uniforms.diffuseTexture;
+        gl.bindTexture(gl.TEXTURE_2D, uniforms.diffuseTexture);
+        gl.uniform1i(loc.diffuseTexture, 0);
+      }
+
+      gl.uniformMatrix4fv(loc.world, false, worldValue);
+      gl.uniformMatrix4fv(loc.viewProjection, false, viewProjectionMatrix);
+      gl.uniformMatrix3fv(loc.normalMatrix, false, uniforms.normalMatrix);
+      gl.uniform3fv(loc.lightWorldPosition, [-10, 30, 300]);
+      gl.uniform3fv(loc.viewWorldPosition, eye);
+      gl.uniform4fv(loc.color, color);
+      gl.uniform1f(loc.shininess, shininess);
+
+      // twgl.drawBufferInfo(gl, bufferInfo);
+      gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
     }
     timingHelper.end();
 
diff --git a/webgpu/webgl-optimization-uniform-buffers-one-large.html b/webgpu/webgl-optimization-uniform-buffers-one-large.html
new file mode 100644
index 00000000..f1aefcac
--- /dev/null
+++ b/webgpu/webgl-optimization-uniform-buffers-one-large.html
@@ -0,0 +1,548 @@
+
+
+  
+    
+    
+    WebGL Optimization - Uniform Blocks, one large buffer
+    
+  
+  
+    
+    

+  
+  
+
diff --git a/webgpu/webgpu-optimization-all.html b/webgpu/webgpu-optimization-all.html
deleted file mode 100644
index a30a814b..00000000
--- a/webgpu/webgpu-optimization-all.html
+++ /dev/null
@@ -1,603 +0,0 @@
-
-
-  
-    
-    
-    WebGPU Optimization - None
-    
-  
-  
-    
-    

-  
-  
-
diff --git a/webgpu/webgpu-optimization-none.html b/webgpu/webgpu-optimization-none.html
index 9bbdc3fd..cfecd80c 100644
--- a/webgpu/webgpu-optimization-none.html
+++ b/webgpu/webgpu-optimization-none.html
@@ -309,7 +309,7 @@
     });
   }
 
-  const maxObjects = 20000;
+  const maxObjects = 30000;
   const objectInfos = [];
 
   for (let i = 0; i < maxObjects; ++i) {
diff --git a/webgpu/webgpu-optimization-step3-global-vs-per-object-uniforms.html b/webgpu/webgpu-optimization-step3-global-vs-per-object-uniforms.html
index c7798063..6705c431 100644
--- a/webgpu/webgpu-optimization-step3-global-vs-per-object-uniforms.html
+++ b/webgpu/webgpu-optimization-step3-global-vs-per-object-uniforms.html
@@ -344,7 +344,7 @@
   const viewWorldPositionValue = globalUniformValues.subarray(
       kViewWorldPositionOffset, kViewWorldPositionOffset + 3);
 
-  const maxObjects = 20000;
+  const maxObjects = 30000;
   const objectInfos = [];
 
   for (let i = 0; i < maxObjects; ++i) {
diff --git a/webgpu/webgpu-optimization-step4-material-uniforms.html b/webgpu/webgpu-optimization-step4-material-uniforms.html
index 4457f788..acacb750 100644
--- a/webgpu/webgpu-optimization-step4-material-uniforms.html
+++ b/webgpu/webgpu-optimization-step4-material-uniforms.html
@@ -360,7 +360,7 @@
   const viewWorldPositionValue = globalUniformValues.subarray(
       kViewWorldPositionOffset, kViewWorldPositionOffset + 3);
 
-  const maxObjects = 20000;
+  const maxObjects = 30000;
   const objectInfos = [];
 
   for (let i = 0; i < maxObjects; ++i) {
diff --git a/webgpu/webgpu-optimization-step5-double-buffer-frequently-updated-uniform-buffers-pre-submit.html b/webgpu/webgpu-optimization-step5-double-buffer-frequently-updated-uniform-buffers-pre-submit.html
deleted file mode 100644
index 93416361..00000000
--- a/webgpu/webgpu-optimization-step5-double-buffer-frequently-updated-uniform-buffers-pre-submit.html
+++ /dev/null
@@ -1,615 +0,0 @@
-
-
-  
-    
-    
-    WebGPU Optimization - None
-    
-  
-  
-    
-    

-  
-  
-
diff --git a/webgpu/webgpu-optimization-step5-double-buffer-frequently-updated-uniform-buffers.html b/webgpu/webgpu-optimization-step5-double-buffer-frequently-updated-uniform-buffers.html
deleted file mode 100644
index 93416361..00000000
--- a/webgpu/webgpu-optimization-step5-double-buffer-frequently-updated-uniform-buffers.html
+++ /dev/null
@@ -1,615 +0,0 @@
-
-
-  
-    
-    
-    WebGPU Optimization - None
-    
-  
-  
-    
-    

-  
-  
-
diff --git a/webgpu/webgpu-optimization-step5-use-buffer-offsets.html b/webgpu/webgpu-optimization-step5-use-buffer-offsets.html
index 18e5b0b4..9b3e325c 100644
--- a/webgpu/webgpu-optimization-step5-use-buffer-offsets.html
+++ b/webgpu/webgpu-optimization-step5-use-buffer-offsets.html
@@ -3,7 +3,7 @@
   
     
     
-    WebGPU Optimization - None
+    WebGPU Optimization - Use One Large Uniform Buffer with Offsets
     
-  
-  
-    
-    

-  
-  
-
diff --git a/webgpu/webgpu-optimization-step6-use-mapped-buffers-dyanmic-offsets.html b/webgpu/webgpu-optimization-step6-use-mapped-buffers-dyanmic-offsets.html
index c5076da0..3173092a 100644
--- a/webgpu/webgpu-optimization-step6-use-mapped-buffers-dyanmic-offsets.html
+++ b/webgpu/webgpu-optimization-step6-use-mapped-buffers-dyanmic-offsets.html
@@ -403,7 +403,7 @@
   const viewWorldPositionValue = globalUniformValues.subarray(
       kViewWorldPositionOffset, kViewWorldPositionOffset + 3);
 
-  const maxObjects = 20000;
+  const maxObjects = 30000;
   const objectInfos = [];
 
   const uniformBufferSize = (12 + 16) * 4;
diff --git a/webgpu/webgpu-optimization-step6-use-mapped-buffers-math-w-offsets.html b/webgpu/webgpu-optimization-step6-use-mapped-buffers-math-w-offsets.html
index fecd4017..8f900b02 100644
--- a/webgpu/webgpu-optimization-step6-use-mapped-buffers-math-w-offsets.html
+++ b/webgpu/webgpu-optimization-step6-use-mapped-buffers-math-w-offsets.html
@@ -920,7 +920,7 @@
   const viewWorldPositionValue = globalUniformValues.subarray(
       kViewWorldPositionOffset, kViewWorldPositionOffset + 3);
 
-  const maxObjects = 20000;
+  const maxObjects = 30000;
   const objectInfos = [];
 
   const uniformBufferSize = (12 + 16) * 4;
diff --git a/webgpu/webgpu-optimization-step6-use-mapped-buffers.html b/webgpu/webgpu-optimization-step6-use-mapped-buffers.html
index 63307848..42595485 100644
--- a/webgpu/webgpu-optimization-step6-use-mapped-buffers.html
+++ b/webgpu/webgpu-optimization-step6-use-mapped-buffers.html
@@ -3,7 +3,7 @@
   
     
     
-    WebGPU Optimization - None
+    WebGPU Optimization - Use Mapped Buffers