Skip to content

Commit

Permalink
Consider making optimal WebGL versions (#132)
Browse files Browse the repository at this point in the history
1. Add more textures to all examples

2. Optimize WebGL examples
  • Loading branch information
greggman authored Jun 27, 2024
1 parent 5387587 commit 4d63b55
Show file tree
Hide file tree
Showing 21 changed files with 1,251 additions and 2,526 deletions.
72 changes: 52 additions & 20 deletions 3rdparty/twgl-full.module.js
Original file line number Diff line number Diff line change
@@ -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 */
/*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<WebGLProgram>} The created program
Expand All @@ -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<module:twgl.ProgramInfo>} The created ProgramInfo
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<string, ArrayBufferView>} uniforms A uniform name to ArrayBufferView map.
Expand All @@ -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
*
Expand All @@ -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];
Expand All @@ -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 + ".";
Expand All @@ -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
Expand Down Expand Up @@ -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,
};
}

Expand All @@ -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);
}

/**
Expand All @@ -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;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -8718,6 +8746,8 @@ function setBuffersAndAttributes(gl, programInfo, buffers) {
/**
* @typedef {Object} ProgramInfo
* @property {WebGLProgram} program A shader program
* @property {Object<string, WebGLUniformLocation>} uniformLocations The uniform locations of each uniform
* @property {Object<string, number>} attribLocations The locations of each attribute
* @property {Object<string, function>} uniformSetters object of setters as returned from createUniformSetters,
* @property {Object<string, function>} attribSetters object of setters as returned from createAttribSetters,
* @property {module:twgl.UniformBlockSpec} [uniformBlockSpec] a uniform block spec for making UniformBlockInfos with createUniformBlockInfo etc..
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions webgpu/lessons/webgpu-from-webgl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
12 changes: 6 additions & 6 deletions webgpu/lessons/webgpu-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit 4d63b55

Please sign in to comment.