Skip to content

Commit b1f3776

Browse files
authored
Add format-filterable-renderable test. (#3357)
Checks all texture formats for filterable and color-renderable. WebGL 1 and 2. WebGL 1's non-RGBA8 formats are considered `render: undefined`.
1 parent 9b9d537 commit b1f3776

File tree

3 files changed

+393
-1
lines changed

3 files changed

+393
-1
lines changed

sdk/tests/conformance/textures/misc/00_test_list.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ copy-tex-image-and-sub-image-2d.html
44
--min-version 1.0.2 copy-tex-image-2d-formats.html
55
--min-version 1.0.4 copy-tex-image-crash.html
66
--min-version 1.0.4 copytexsubimage2d-large-partial-copy-corruption.html
7-
--min-version 1.0.4 copytexsubimage2d-subrects.html
7+
--min-version 1.0.4 copytexsubimage2d-subrects.html
88
--min-version 1.0.4 cube-incomplete-fbo.html
99
--min-version 1.0.4 cube-map-uploads-out-of-order.html
1010
--min-version 1.0.3 default-texture.html
1111
--min-version 1.0.4 exif-orientation.html
12+
--min-version 1.0.4 format-filterable-renderable.html
1213
--min-version 1.0.2 --max-version 1.9.9 gl-get-tex-parameter.html
1314
gl-pixelstorei.html
1415
gl-teximage.html
Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
<!--
2+
Copyright (c) 2021 The Khronos Group Inc.
3+
Use of this source code is governed by an MIT-style license that can be
4+
found in the LICENSE.txt file.
5+
-->
6+
7+
<!DOCTYPE html>
8+
<html>
9+
<head>
10+
<meta charset="utf-8">
11+
<link rel="stylesheet" href="../../../resources/js-test-style.css"/>
12+
<script src="../../../js/js-test-pre.js"></script>
13+
<script src="../../../js/webgl-test-utils.js"></script>
14+
</head>
15+
<body>
16+
<div id="description"></div>
17+
<div id="console"></div>
18+
<script>
19+
"use strict";
20+
21+
const wtu = WebGLTestUtils;
22+
description();
23+
24+
const gl = wtu.create3DContext();
25+
gl.canvas.width = gl.canvas.height = 1;
26+
27+
function makeTexImage(format, unpackFormat, unpackType, data) {
28+
data = data || null;
29+
30+
const tex = gl.createTexture();
31+
gl.bindTexture(gl.TEXTURE_2D, tex);
32+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
33+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
34+
gl.texImage2D(gl.TEXTURE_2D, 0, gl[format], 2, 2, 0,
35+
gl[unpackFormat], gl[unpackType], null);
36+
return tex;
37+
}
38+
39+
const DUMMY_COLOR = makeTexImage('RGBA', 'RGBA', 'UNSIGNED_BYTE');
40+
41+
function makeProgram(gl, vsSrc, fsSrc) {
42+
function makeShader(prog, type, src) {
43+
const shader = gl.createShader(gl[type]);
44+
gl.shaderSource(shader, src.trim());
45+
gl.compileShader(shader);
46+
gl.attachShader(prog, shader);
47+
gl.deleteShader(shader);
48+
};
49+
50+
const prog = gl.createProgram();
51+
makeShader(prog, 'VERTEX_SHADER', vsSrc);
52+
makeShader(prog, 'FRAGMENT_SHADER', fsSrc);
53+
gl.linkProgram(prog);
54+
55+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
56+
throw 'Program linking failed' + fsSrc;
57+
}
58+
return prog;
59+
}
60+
61+
const TEX_FILTER_PROG_T = (version, samplerT) => makeProgram(gl, `\
62+
${version}
63+
void main() {
64+
gl_PointSize = 1.0;
65+
gl_Position = vec4(0.5,0.5,0,1);
66+
}`,`\
67+
${version}
68+
precision mediump float;
69+
uniform ${samplerT} u_tex0;
70+
#if __VERSION__ == 300
71+
out vec4 o_FragColor;
72+
#else
73+
#define o_FragColor gl_FragColor
74+
#define texture texture2D
75+
#endif
76+
void main() {
77+
o_FragColor = vec4(texture(u_tex0, vec2(0.8)));
78+
}`);
79+
const TEX_FILTER_PROG_BY_TYPEISH = {
80+
'float': TEX_FILTER_PROG_T('', 'sampler2D'),
81+
};
82+
if (wtu.isWebGL2(gl)) {
83+
TEX_FILTER_PROG_BY_TYPEISH['int'] =
84+
TEX_FILTER_PROG_T('#version 300 es', 'highp isampler2D');
85+
TEX_FILTER_PROG_BY_TYPEISH['uint'] =
86+
TEX_FILTER_PROG_T('#version 300 es', 'highp usampler2D');
87+
}
88+
89+
function runPixelProgram(gl, prog) {
90+
gl.useProgram(prog);
91+
gl.clear(gl.COLOR_BUFFER_BIT);
92+
gl.drawArrays(gl.POINTS, 0, 1);
93+
const bytes = new Uint8Array(4);
94+
gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, bytes);
95+
return [].map.call(bytes, x => x/255.0);
96+
}
97+
98+
// GLES 2.0.25 p63
99+
const FORMAT_INFO_WEBGL1 = {
100+
RGBA8 : { filter: true, render: true , unpack: ['RGBA', 'UNSIGNED_BYTE'] },
101+
RGB8 : { filter: true, render: undefined, unpack: ['RGB', 'UNSIGNED_BYTE'] },
102+
RGBA4 : { filter: true, render: undefined, unpack: ['RGBA', 'UNSIGNED_SHORT_4_4_4_4'] },
103+
RGB5_A1: { filter: true, render: undefined, unpack: ['RGBA', 'UNSIGNED_SHORT_5_5_5_1'] },
104+
RGB565 : { filter: true, render: undefined, unpack: ['RGB', 'UNSIGNED_SHORT_5_6_5'] },
105+
LA8 : { filter: true, render: false , unpack: ['LUMINANCE_ALPHA', 'UNSIGNED_BYTE'] },
106+
L8 : { filter: true, render: false , unpack: ['LUMINANCE', 'UNSIGNED_BYTE'] },
107+
A8 : { filter: true, render: false , unpack: ['ALPHA', 'UNSIGNED_BYTE'] },
108+
};
109+
110+
// GLES 3.0.6 p130-132
111+
const FORMAT_INFO_WEBGL2 = {
112+
R8 : { render: true , filter: true , unpack: ['RED', 'UNSIGNED_BYTE'] },
113+
R8_SNORM : { render: false, filter: true , unpack: ['RED', 'BYTE'] },
114+
RG8 : { render: true , filter: true , unpack: ['RG', 'UNSIGNED_BYTE'] },
115+
RG8_SNORM : { render: false, filter: true , unpack: ['RG', 'BYTE'] },
116+
RGB8 : { render: true , filter: true , unpack: ['RGB', 'UNSIGNED_BYTE'] },
117+
RGB8_SNORM : { render: false, filter: true , unpack: ['RGB', 'BYTE'] },
118+
RGB565 : { render: true , filter: true , unpack: ['RGB', 'UNSIGNED_SHORT_5_6_5'] },
119+
RGBA4 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_SHORT_4_4_4_4'] },
120+
RGB5_A1 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_SHORT_5_5_5_1'] },
121+
RGBA8 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_BYTE'] },
122+
RGBA8_SNORM : { render: false, filter: true , unpack: ['RGBA', 'BYTE'] },
123+
RGB10_A2 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_INT_10_10_10_2'] },
124+
RGB10_A2UI : { render: true , filter: false, unpack: ['RGBA', 'UNSIGNED_INT_10_10_10_2'] },
125+
SRGB8 : { render: false, filter: true , unpack: ['RGB', 'UNSIGNED_BYTE'] },
126+
SRGB8_ALPHA8 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_BYTE'] },
127+
R16F : { render: false, filter: true , unpack: ['RED', 'FLOAT'] },
128+
RG16F : { render: false, filter: true , unpack: ['RG', 'FLOAT'] },
129+
RGB16F : { render: false, filter: true , unpack: ['RGB', 'FLOAT'] },
130+
RGBA16F : { render: false, filter: true , unpack: ['RGBA', 'FLOAT'] },
131+
R32F : { render: false, filter: false, unpack: ['RED', 'FLOAT'] },
132+
RG32F : { render: false, filter: false, unpack: ['RG', 'FLOAT'] },
133+
RGB32F : { render: false, filter: false, unpack: ['RGB', 'FLOAT'] },
134+
RGBA32F : { render: false, filter: false, unpack: ['RGBA', 'FLOAT'] },
135+
R11F_G11F_B10F: { render: false, filter: true , unpack: ['RGB', 'FLOAT'] },
136+
RGB9_E5 : { render: false, filter: true , unpack: ['RGB', 'FLOAT'] },
137+
R8I : { render: true , filter: false, unpack: ['RED', 'BYTE'] },
138+
R8UI : { render: true , filter: false, unpack: ['RED', 'UNSIGNED_BYTE'] },
139+
R16I : { render: true , filter: false, unpack: ['RED', 'BYTE'] },
140+
R16UI : { render: true , filter: false, unpack: ['RED', 'UNSIGNED_BYTE'] },
141+
R32I : { render: true , filter: false, unpack: ['RED', 'BYTE'] },
142+
R32UI : { render: true , filter: false, unpack: ['RED', 'UNSIGNED_BYTE'] },
143+
RG8I : { render: true , filter: false, unpack: ['RG', 'BYTE'] },
144+
RG8UI : { render: true , filter: false, unpack: ['RG', 'UNSIGNED_BYTE'] },
145+
RG16I : { render: true , filter: false, unpack: ['RG', 'SHORT'] },
146+
RG16UI : { render: true , filter: false, unpack: ['RG', 'UNSIGNED_SHORT'] },
147+
RG32I : { render: true , filter: false, unpack: ['RG', 'INT'] },
148+
RG32UI : { render: true , filter: false, unpack: ['RG', 'UNSIGNED_INT'] },
149+
RGB8I : { render: false, filter: false, unpack: ['RGB', 'BYTE'] },
150+
RGB8UI : { render: false, filter: false, unpack: ['RGB', 'UNSIGNED_BYTE'] },
151+
RGB16I : { render: false, filter: false, unpack: ['RGB', 'SHORT'] },
152+
RGB16UI : { render: false, filter: false, unpack: ['RGB', 'UNSIGNED_SHORT'] },
153+
RGB32I : { render: false, filter: false, unpack: ['RGB', 'INT'] },
154+
RGB32UI : { render: false, filter: false, unpack: ['RGB', 'UNSIGNED_INT'] },
155+
RGBA8I : { render: true , filter: false, unpack: ['RGBA', 'BYTE'] },
156+
RGBA8UI : { render: true , filter: false, unpack: ['RGBA', 'UNSIGNED_BYTE'] },
157+
RGBA16I : { render: true , filter: false, unpack: ['RGBA', 'SHORT'] },
158+
RGBA16UI : { render: true , filter: false, unpack: ['RGBA', 'UNSIGNED_SHORT'] },
159+
RGBA32I : { render: true , filter: false, unpack: ['RGBA', 'INT'] },
160+
RGBA32UI : { render: true , filter: false, unpack: ['RGBA', 'UNSIGNED_INT'] },
161+
162+
DEPTH_COMPONENT16: { render: 'DEPTH_ATTACHMENT', filter: false },
163+
DEPTH_COMPONENT24: { render: 'DEPTH_ATTACHMENT', filter: false },
164+
DEPTH_COMPONENT32F: { render: 'DEPTH_ATTACHMENT', filter: false },
165+
DEPTH24_STENCIL8: { render: 'DEPTH_STENCIL_ATTACHMENT', filter: false },
166+
DEPTH32F_STENCIL8: { render: 'DEPTH_STENCIL_ATTACHMENT', filter: false },
167+
};
168+
169+
const ONE_BY_TYPE = {
170+
'BYTE': (1<<7)-1,
171+
'UNSIGNED_BYTE': (1<<8)-1,
172+
'SHORT': (1<<15)-1,
173+
'UNSIGNED_SHORT': (1<<16)-1,
174+
'INT': (1<<31)-1,
175+
'UNSIGNED_INT': Math.pow(2,32)-1,
176+
'FLOAT': 1,
177+
};
178+
179+
const ABV_BY_TYPE = {
180+
'BYTE': Int8Array,
181+
'UNSIGNED_BYTE': Uint8Array,
182+
'SHORT': Int16Array,
183+
'UNSIGNED_SHORT': Uint16Array,
184+
'INT': Int32Array,
185+
'UNSIGNED_INT': Uint32Array,
186+
'FLOAT': Float32Array,
187+
};
188+
189+
function pushBitsUnorm(prev, bitCount, floatVal) {
190+
let ret = prev << bitCount;
191+
ret |= floatVal * ((1 << bitCount)-1);
192+
return ret;
193+
}
194+
195+
const CHANNELS_BY_FORMAT = {
196+
'RED': 1,
197+
'LUMINANCE': 1,
198+
'ALPHA': 1,
199+
'LUMINANCE_ALPHA': 2,
200+
'RG': 2,
201+
'RGB': 3,
202+
'RGBA': 4,
203+
};
204+
205+
function throwv(val) {
206+
throw val;
207+
}
208+
209+
function pixelDataForUnpack(format, type, floatVal) {
210+
switch (type) {
211+
case 'UNSIGNED_SHORT_5_6_5': {
212+
let bits = 0;
213+
bits = pushBitsUnorm(bits, 5, floatVal);
214+
bits = pushBitsUnorm(bits, 6, floatVal);
215+
bits = pushBitsUnorm(bits, 5, floatVal);
216+
return new Uint16Array([bits]);
217+
}
218+
case 'UNSIGNED_SHORT_4_4_4_4': {
219+
let bits = 0;
220+
bits = pushBitsUnorm(bits, 4, floatVal);
221+
bits = pushBitsUnorm(bits, 4, floatVal);
222+
bits = pushBitsUnorm(bits, 4, floatVal);
223+
bits = pushBitsUnorm(bits, 4, floatVal);
224+
return new Uint16Array([bits]);
225+
}
226+
case 'UNSIGNED_SHORT_5_5_5_1': {
227+
let bits = 0;
228+
bits = pushBitsUnorm(bits, 5, floatVal);
229+
bits = pushBitsUnorm(bits, 5, floatVal);
230+
bits = pushBitsUnorm(bits, 5, floatVal);
231+
bits = pushBitsUnorm(bits, 1, floatVal); // Ok, silly for 1 bit here.
232+
return new Uint16Array([bits]);
233+
}
234+
case 'UNSIGNED_INT_10_10_10_2': {
235+
let bits = 0;
236+
bits = pushBitsUnorm(bits, 10, floatVal);
237+
bits = pushBitsUnorm(bits, 10, floatVal);
238+
bits = pushBitsUnorm(bits, 10, floatVal);
239+
bits = pushBitsUnorm(bits, 2, floatVal); // 2 bits isn't much more useful
240+
return new Uint32Array([bits]);
241+
}
242+
}
243+
244+
const channels = CHANNELS_BY_FORMAT[format] || throwv(format);
245+
const one = ONE_BY_TYPE[type] || throwv('240', type);
246+
const abvType = ABV_BY_TYPE[type] || throwv('241', type);
247+
248+
const val = floatVal * one;
249+
const arr = [];
250+
for (const i of range(channels)) {
251+
arr.push(val);
252+
}
253+
return new abvType(arr);
254+
}
255+
256+
function expect(name, was, expected) {
257+
let text = `${name} was ${was}`;
258+
const cond = was == expected;
259+
if (!cond) {
260+
text += `, but expected ${expected}`;
261+
}
262+
expectTrue(cond, text);
263+
}
264+
265+
function toTypeish(sizedFormat) {
266+
if (sizedFormat.endsWith('UI')) {
267+
return 'int';
268+
} else if (sizedFormat.endsWith('I')) {
269+
return 'uint';
270+
}
271+
return 'float';
272+
}
273+
274+
call(async () => {
275+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
276+
277+
let formatList = FORMAT_INFO_WEBGL1;
278+
if (wtu.isWebGL2(gl)) {
279+
formatList = FORMAT_INFO_WEBGL2;
280+
}
281+
for (const [sizedFormat, info] of Object.entries(formatList)) {
282+
await wtu.dispatchPromise();
283+
debug(``);
284+
debug(`${sizedFormat}: ${JSON.stringify(info)}`);
285+
286+
const typeish = toTypeish(sizedFormat);
287+
288+
// |---|---|
289+
// | 0 | 1 |
290+
// |---|---|
291+
// 0| 0 | 0 |
292+
// |---|---|
293+
// 0
294+
const tex = gl.createTexture();
295+
gl.bindTexture(gl.TEXTURE_2D, tex);
296+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
297+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
298+
299+
if (gl.texStorage2D) {
300+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl[sizedFormat], 2, 2);
301+
} else {
302+
gl.texImage2D(gl.TEXTURE_2D, 0, gl[info.unpack[0]],
303+
2, 2, 0, gl[info.unpack[0]], gl[info.unpack[1]], null);
304+
}
305+
306+
if (info.unpack) {
307+
const one = pixelDataForUnpack(...info.unpack, 1.0);
308+
const data = new one.constructor(one.length*4);
309+
data.set(one, one.length*3);
310+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0,
311+
2,2, gl[info.unpack[0]], gl[info.unpack[1]], data);
312+
} else {
313+
info.render || throwv(`${sizedFormat} without unpack or render`);
314+
}
315+
316+
// -
317+
// color-renderable test
318+
319+
{
320+
const fb = gl.createFramebuffer();
321+
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
322+
let attach = info.render || true;
323+
const isColor = (attach === true);
324+
if (isColor) {
325+
attach = 'COLOR_ATTACHMENT0';
326+
} else {
327+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
328+
gl.TEXTURE_2D, DUMMY_COLOR, 0);
329+
}
330+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl[attach],
331+
gl.TEXTURE_2D, tex, 0);
332+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
333+
const wasRenderable = (status == gl.FRAMEBUFFER_COMPLETE);
334+
if (info.render === undefined) {
335+
debug(`Non-normative: color-renderable was ${wasRenderable}`);
336+
} else {
337+
expect('color-renderable', wasRenderable, !!info.render);
338+
}
339+
if (wasRenderable) {
340+
gl.clearColor(0,0,0,0);
341+
gl.clearDepth(0);
342+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
343+
gl.enable(gl.SCISSOR_TEST);
344+
gl.scissor(1,1,1,1);
345+
gl.clearColor(1,1,1,1);
346+
gl.clearDepth(1);
347+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
348+
gl.disable(gl.SCISSOR_TEST);
349+
}
350+
gl.deleteFramebuffer(fb);
351+
if (!wasRenderable && !info.unpack) {
352+
testFailed('No unpack provided and !wasRenderable, skipping filtering subtest...');
353+
continue;
354+
}
355+
}
356+
357+
// -
358+
// filterable test
359+
360+
const prog = TEX_FILTER_PROG_BY_TYPEISH[typeish];
361+
gl.bindTexture(gl.TEXTURE_2D, tex);
362+
gl.clearColor(0,0,0,0);
363+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
364+
gl.viewport(0,0,1,1);
365+
const v = runPixelProgram(gl, prog);
366+
if (sizedFormat != 'A8') {
367+
v[3] = 0; // Incomplete no-alpha formats put 1 in alpha.
368+
}
369+
const wasFilterable = v.some(x => !!x);
370+
expect('filterable', wasFilterable, info.filter);
371+
}
372+
373+
finishTest();
374+
});
375+
376+
</script>
377+
</body>
378+
</html>

0 commit comments

Comments
 (0)