Skip to content

Instantly share code, notes, and snippets.

@greggman
Created July 24, 2025 17:58
Show Gist options
  • Save greggman/e87882efa6022ff44f16a97890f914e6 to your computer and use it in GitHub Desktop.
Save greggman/e87882efa6022ff44f16a97890f914e6 to your computer and use it in GitHub Desktop.
WebGL2: Generate Mips Test - show mips
:root {
color-scheme: light dark;
}
canvas { border: 1px solid gray; }
img {
border: 1px solid gray;
image-rendering: pixelated;
width: 128px;
height: 128px;
margin: 5px;
}
/*bug-in-github-api-content-can-not-be-empty*/
import 'https://greggman.github.io/webgl-lint/webgl-lint.js';
import * as twgl from 'https://twgljs.org/dist/5.x/twgl-full.module.js';
const vsView = `#version 300 es
out vec2 v_texcoord;
void main() {
vec2 pos[3];
pos[0] = vec2(-1, -1);
pos[1] = vec2(-1, 3);
pos[2] = vec2( 3, -1);
vec2 xy = pos[gl_VertexID];
gl_Position = vec4(xy, 0, 1);
v_texcoord = xy * vec2(0.5, -0.5) + vec2(0.5);
gl_PointSize = 16.0;
}
`;
const fsView = `#version 300 es
precision highp float;
in vec2 v_texcoord;
uniform highp sampler2D tex;
uniform uint mipLevel;
out vec4 fragColor;
void main() {
fragColor = textureLod(tex, v_texcoord, float(mipLevel));
}
`;
/** @type HTMLCanvasElement */
const el = document.createElement('canvas');
const gl = el.getContext('webgl2');
const r = [255, 0, 0, 255];
const g = [0, 255, 0, 255];
const b = [0, 0, 255, 255];
const y = [255, 255, 0, 255];
const c = [0, 255, 255, 255];
const m = [255, 0, 255, 255];
const colors = [r, y, g, c, b, m];
function makeTestData(w, h, colors) {
const data = new Uint8ClampedArray(w * h * 4);
for (let y = 0; y < h; ++y) {
for (let x = 0; x < w; ++x) {
data.set(colors[(x + y) % colors.length], (y * h + x) * 4);
}
}
return new ImageData(data, w, h);
}
const range = (num, fn) => new Array(num).fill(0).map((_, i) => fn(i));
const numMipLevels = (...sizes) => {
const maxSize = Math.max(...sizes);
return 1 + Math.log2(maxSize) | 0;
};
const tests = [
...range(63, i => {
const s = 64 - i;
return makeTestData(s, s, colors);
})
];
for (const imgData of tests) {
test(gl, imgData);
}
function test(gl, imgData) {
const mipLevelCount = numMipLevels(imgData.width, imgData.height);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texStorage2D(gl.TEXTURE_2D, mipLevelCount, gl.RGBA8, imgData.width, imgData.height);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, imgData.width, imgData.height, gl.RGBA, gl.UNSIGNED_BYTE, imgData.data);
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
const prgInfo = twgl.createProgramInfo(gl, [vsView, fsView]);
gl.useProgram(prgInfo.program);
log(`${imgData.width}x${imgData.height}`);
for (let mipLevel = 0; mipLevel < mipLevelCount; ++mipLevel) {
const width = Math.max(1, imgData.width >> mipLevel);
const height = Math.max(1, imgData.height >> mipLevel);
gl.canvas.width = width;
gl.canvas.height = height;
gl.viewport(0, 0, width, height);
twgl.setUniforms(prgInfo, { mipLevel, tex });
gl.drawArrays(gl.TRIANGLES, 0, 3);
const img = new Image();
document.body.append(img);
gl.canvas.toBlob(blob => {
img.src = URL.createObjectURL(blob);
});
}
document.body.append(document.createElement('hr'));
}
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = args.join(' ');
document.body.appendChild(elem);
}
{"name":"WebGL2: Generate Mips Test - show mips","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment