Add test of manual mipmap chain generation in WebGL 2.0
Opened this issue · 12 comments
After the updates from https://anglebug.com/4690 , ANGLE guarantees support for allocating a texture in WebGL 2.0 via TexStorage2D
and manually generating mipmaps by iteratively rendering to level N+1 while sampling at level N. (Specifically, that this is not considered a rendering feedback loop.)
A test that this works has been added to ANGLE's test suite, but it should be ported to JavaScript and added to the WebGL 2.0 conformance test suite to guarantee that this functionality works across browsers.
Is there something unique to TexStorage2D here? It seems like manually creating mip levels with texImage2D
and then trying to render from one mip to another should also work without being considered a feedback loop.
Not really. It's only known that texStorage2D
works for this purpose. If the new test verifies that textures allocated with texImage2D
work too, that's great. But if that part of the test fails, it will require more adjustments to ANGLE's validation.
I thought this
gl.texStorage2D(gl.TEXTURE_2D, 2, gl.RGBA8, 2, 2);
and this
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texImage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
are equivalent except for the fact that first one is immutable.
I have my own test here
https://jsgist.org/?src=137493160103a6bc85bff8d47eb50045
It's definitely failing using texImage2D
but the error seems wrong, even if it's not supposed to work
GL_INVALID_FRAMEBUFFER_OPERATION: Framebuffer is incomplete: Attachment level is not in the [base level, max level] range
Oh, I had a wrong setting, they both work. Arguably neither should have worked before though and the error message still seems wrong.
For details, here's the current code
Instead of
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LOD, 0);
...
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LOD, 100);
I had
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);
...
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 100);
It's not clear to me which one should work or if both should work but, with it using MAX_LEVEL
, texStorage2D
worked at texImage2D
did not which is arguably a bug. They should either both work or both fail
//import 'https://greggman.github.io/webgl-lint/webgl-lint.js';
import * as twgl from 'https://twgljs.org/dist/5.x/twgl-full.module.js';
function main(useTexStorage) {
log(useTexStorage ? 'texStorage2D' : 'texImage2D');
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) {
return alert("need WebGL2");
}
document.body.appendChild(gl.canvas);
const vs = `#version 300 es
void main() {
gl_PointSize = 300.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// make a 2x2 texture with 2 mip levels
if (useTexStorage) {
gl.texStorage2D(gl.TEXTURE_2D, 2, gl.RGBA8, 2, 2);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texImage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
}
// fill mip level 0 with red
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE,
new Uint8Array([
255, 0, 0, 255,
255, 0, 0, 255,
255, 0, 0, 255,
255, 0, 0, 255,
]));
// fill mip level 1 with yellow
gl.texSubImage2D(gl.TEXTURE_2D, 1, 0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE,
new Uint8Array([
255, 255, 0, 255,
]));
// bind mip level 1 to a framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 1);
console.log(twgl.glEnumToString(gl, gl.checkFramebufferStatus(gl.FRAMEBUFFER)));
checkError(gl);
// render mip level 0 to mip level 1, swapping red and blue
const fs = `#version 300 es
precision mediump float;
uniform highp sampler2D tex;
out vec4 fragColor;
void main() {
fragColor = texture(tex, vec2(0)).bgra;
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
gl.viewport(0, 0, 1, 1);
// set to use only first mip level (so no feedback loop).
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LOD, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// draw a single point
gl.useProgram(program);
gl.drawArrays(gl.POINTS, 0, 1);
checkError(gl);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LOD, 100);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
// render the texture showing both mips.
const fs2 = `#version 300 es
precision mediump float;
uniform sampler2D tex;
out vec4 outColor;
void main() {
outColor = texture(tex, gl_PointCoord.xy, mod(floor(gl_FragCoord.x / 16.0), 2.0) * 1000.0);
}
`;
const prg2 = twgl.createProgram(gl, [vs, fs2]);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(prg2);
gl.drawArrays(gl.POINTS, 0, 1);
checkError(gl);
}
main(true);
main(false);
function checkError(gl) {
const err = gl.getError();
if (err) {
console.error(twgl.glEnumToString(gl, err));
}
}
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = args.join(' ');
document.body.appendChild(elem);
}
Just for my own curiosity I checked in OpenGL on mac. Either MAX_LEVEL and MAX_LOD work there (of course GL doesn't check for feedback IIRC).
Immutable and non-immutable textures are treated differently regarding framebuffer attachment completeness (OpenGL ES 3.0, Section 4.4.4.1):
The framebuffer attachment point attachment is said to be framebuffer attachment complete if the value of
FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE
for attachment isNONE
(i.e., no image is attached), or if all of the following conditions are true:
- ...
- If the value of
FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE
isTEXTURE
and the value ofFRAMEBUFFER_ATTACHMENT_OBJECT_NAME
does not name an immutable-format texture, then the value ofFRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL
must be in the range [levelbase,q], where levelbase is the value ofTEXTURE_BASE_LEVEL
and q is the effective maximum texture level defined in the Mipmapping discussion of section 3.8.10.4.- If the value of
FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE
isTEXTURE
and the value ofFRAMEBUFFER_ATTACHMENT_OBJECT_NAME
does not name an immutable-format texture and the value ofFRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL
is not levelbase, then the texture must be mipmap complete, and ifFRAMEBUFFER_ATTACHMENT_OBJECT_NAME
names a cubemap texture, the texture must also be cube complete.
I'm confused.
The text about is only about non-immutable formats, where as texStorage2D
is about immuatable-formats
AFAICT, the point of the text above is effectively about the fact that non-immutable formats can have non-homogenious levels. Each level can be a different internal format and can have any random size. Vs immutable-formats where this is never true. With immutable formats all levels are already guaranteed to be the same format and all levels are guaranteed to have the correct size for their level.
All it's basically saying is that the range of mips being used has to follow the normal rules (they must all be the same format and all the correct size for a mip-chain within the currently defined range. TEXTURE_BASE_LEVEL and TEXTURE_MAX_LEVEL defines the current range).
So, validation is the same after that. Effectively, check that the range of mips from TEXTURE_BASE_LEVEL to TEXTURE_MAX_LEVEL follows the same rules as an immutable format texture. Otherwise, everything else is the same.
Am I missing something?
It is valid to create an immutable texture and then use a level outside of the base-max range as a framebuffer attachment. It is not valid to do the same with non-immutable textures.
Got it, thanks!