blob: df7694b821eb1ef1bc83685434854b2aa98032a7 [file] [log] [blame]
<!--
Copyright (c) 2021 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../../resources/js-test-style.css"/>
<script src="../../../js/js-test-pre.js"></script>
<script src="../../../js/webgl-test-utils.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<script>
"use strict";
const wtu = WebGLTestUtils;
description();
const gl = wtu.create3DContext();
gl.canvas.width = gl.canvas.height = 1;
function makeTexImage(format, unpackFormat, unpackType, data) {
data = data || null;
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl[format], 2, 2, 0,
gl[unpackFormat], gl[unpackType], null);
return tex;
}
const DUMMY_COLOR = makeTexImage('RGBA', 'RGBA', 'UNSIGNED_BYTE');
function makeProgram(gl, vsSrc, fsSrc) {
function makeShader(prog, type, src) {
const shader = gl.createShader(gl[type]);
gl.shaderSource(shader, src.trim());
gl.compileShader(shader);
gl.attachShader(prog, shader);
gl.deleteShader(shader);
};
const prog = gl.createProgram();
makeShader(prog, 'VERTEX_SHADER', vsSrc);
makeShader(prog, 'FRAGMENT_SHADER', fsSrc);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
throw 'Program linking failed' + fsSrc;
}
return prog;
}
const TEX_FILTER_PROG_T = (version, samplerT) => makeProgram(gl, `\
${version}
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0.5,0.5,0,1);
}`,`\
${version}
precision mediump float;
uniform ${samplerT} u_tex0;
#if __VERSION__ == 300
out vec4 o_FragColor;
#else
#define o_FragColor gl_FragColor
#define texture texture2D
#endif
void main() {
o_FragColor = vec4(texture(u_tex0, vec2(0.8)));
}`);
const TEX_FILTER_PROG_BY_TYPEISH = {
'float': TEX_FILTER_PROG_T('', 'sampler2D'),
};
if (wtu.isWebGL2(gl)) {
TEX_FILTER_PROG_BY_TYPEISH['int'] =
TEX_FILTER_PROG_T('#version 300 es', 'highp isampler2D');
TEX_FILTER_PROG_BY_TYPEISH['uint'] =
TEX_FILTER_PROG_T('#version 300 es', 'highp usampler2D');
}
function runPixelProgram(gl, prog) {
gl.useProgram(prog);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
const bytes = new Uint8Array(4);
gl.readPixels(0,0,1,1,gl.RGBA, gl.UNSIGNED_BYTE, bytes);
return [].map.call(bytes, x => x/255.0);
}
// GLES 2.0.25 p63
const FORMAT_INFO_WEBGL1 = {
RGBA8 : { filter: true, render: true , unpack: ['RGBA', 'UNSIGNED_BYTE'] },
RGB8 : { filter: true, render: undefined, unpack: ['RGB', 'UNSIGNED_BYTE'] },
RGBA4 : { filter: true, render: undefined, unpack: ['RGBA', 'UNSIGNED_SHORT_4_4_4_4'] },
RGB5_A1: { filter: true, render: undefined, unpack: ['RGBA', 'UNSIGNED_SHORT_5_5_5_1'] },
RGB565 : { filter: true, render: undefined, unpack: ['RGB', 'UNSIGNED_SHORT_5_6_5'] },
LA8 : { filter: true, render: false , unpack: ['LUMINANCE_ALPHA', 'UNSIGNED_BYTE'] },
L8 : { filter: true, render: false , unpack: ['LUMINANCE', 'UNSIGNED_BYTE'] },
A8 : { filter: true, render: false , unpack: ['ALPHA', 'UNSIGNED_BYTE'] },
};
// GLES 3.0.6 p130-132
const FORMAT_INFO_WEBGL2 = {
R8 : { render: true , filter: true , unpack: ['RED', 'UNSIGNED_BYTE'] },
R8_SNORM : { render: false, filter: true , unpack: ['RED', 'BYTE'] },
RG8 : { render: true , filter: true , unpack: ['RG', 'UNSIGNED_BYTE'] },
RG8_SNORM : { render: false, filter: true , unpack: ['RG', 'BYTE'] },
RGB8 : { render: true , filter: true , unpack: ['RGB', 'UNSIGNED_BYTE'] },
RGB8_SNORM : { render: false, filter: true , unpack: ['RGB', 'BYTE'] },
RGB565 : { render: true , filter: true , unpack: ['RGB', 'UNSIGNED_SHORT_5_6_5'] },
RGBA4 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_SHORT_4_4_4_4'] },
RGB5_A1 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_SHORT_5_5_5_1'] },
RGBA8 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_BYTE'] },
RGBA8_SNORM : { render: false, filter: true , unpack: ['RGBA', 'BYTE'] },
RGB10_A2 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_INT_10_10_10_2'] },
RGB10_A2UI : { render: true , filter: false, unpack: ['RGBA', 'UNSIGNED_INT_10_10_10_2'] },
SRGB8 : { render: false, filter: true , unpack: ['RGB', 'UNSIGNED_BYTE'] },
SRGB8_ALPHA8 : { render: true , filter: true , unpack: ['RGBA', 'UNSIGNED_BYTE'] },
R16F : { render: false, filter: true , unpack: ['RED', 'FLOAT'] },
RG16F : { render: false, filter: true , unpack: ['RG', 'FLOAT'] },
RGB16F : { render: false, filter: true , unpack: ['RGB', 'FLOAT'] },
RGBA16F : { render: false, filter: true , unpack: ['RGBA', 'FLOAT'] },
R32F : { render: false, filter: false, unpack: ['RED', 'FLOAT'] },
RG32F : { render: false, filter: false, unpack: ['RG', 'FLOAT'] },
RGB32F : { render: false, filter: false, unpack: ['RGB', 'FLOAT'] },
RGBA32F : { render: false, filter: false, unpack: ['RGBA', 'FLOAT'] },
R11F_G11F_B10F: { render: false, filter: true , unpack: ['RGB', 'FLOAT'] },
RGB9_E5 : { render: false, filter: true , unpack: ['RGB', 'FLOAT'] },
R8I : { render: true , filter: false, unpack: ['RED', 'BYTE'] },
R8UI : { render: true , filter: false, unpack: ['RED', 'UNSIGNED_BYTE'] },
R16I : { render: true , filter: false, unpack: ['RED', 'BYTE'] },
R16UI : { render: true , filter: false, unpack: ['RED', 'UNSIGNED_BYTE'] },
R32I : { render: true , filter: false, unpack: ['RED', 'BYTE'] },
R32UI : { render: true , filter: false, unpack: ['RED', 'UNSIGNED_BYTE'] },
RG8I : { render: true , filter: false, unpack: ['RG', 'BYTE'] },
RG8UI : { render: true , filter: false, unpack: ['RG', 'UNSIGNED_BYTE'] },
RG16I : { render: true , filter: false, unpack: ['RG', 'SHORT'] },
RG16UI : { render: true , filter: false, unpack: ['RG', 'UNSIGNED_SHORT'] },
RG32I : { render: true , filter: false, unpack: ['RG', 'INT'] },
RG32UI : { render: true , filter: false, unpack: ['RG', 'UNSIGNED_INT'] },
RGB8I : { render: false, filter: false, unpack: ['RGB', 'BYTE'] },
RGB8UI : { render: false, filter: false, unpack: ['RGB', 'UNSIGNED_BYTE'] },
RGB16I : { render: false, filter: false, unpack: ['RGB', 'SHORT'] },
RGB16UI : { render: false, filter: false, unpack: ['RGB', 'UNSIGNED_SHORT'] },
RGB32I : { render: false, filter: false, unpack: ['RGB', 'INT'] },
RGB32UI : { render: false, filter: false, unpack: ['RGB', 'UNSIGNED_INT'] },
RGBA8I : { render: true , filter: false, unpack: ['RGBA', 'BYTE'] },
RGBA8UI : { render: true , filter: false, unpack: ['RGBA', 'UNSIGNED_BYTE'] },
RGBA16I : { render: true , filter: false, unpack: ['RGBA', 'SHORT'] },
RGBA16UI : { render: true , filter: false, unpack: ['RGBA', 'UNSIGNED_SHORT'] },
RGBA32I : { render: true , filter: false, unpack: ['RGBA', 'INT'] },
RGBA32UI : { render: true , filter: false, unpack: ['RGBA', 'UNSIGNED_INT'] },
DEPTH_COMPONENT16: { render: 'DEPTH_ATTACHMENT', filter: false },
DEPTH_COMPONENT24: { render: 'DEPTH_ATTACHMENT', filter: false },
DEPTH_COMPONENT32F: { render: 'DEPTH_ATTACHMENT', filter: false },
DEPTH24_STENCIL8: { render: 'DEPTH_STENCIL_ATTACHMENT', filter: false },
DEPTH32F_STENCIL8: { render: 'DEPTH_STENCIL_ATTACHMENT', filter: false },
};
const ONE_BY_TYPE = {
'BYTE': (1<<7)-1,
'UNSIGNED_BYTE': (1<<8)-1,
'SHORT': (1<<15)-1,
'UNSIGNED_SHORT': (1<<16)-1,
'INT': (1<<31)-1,
'UNSIGNED_INT': Math.pow(2,32)-1,
'FLOAT': 1,
};
const ABV_BY_TYPE = {
'BYTE': Int8Array,
'UNSIGNED_BYTE': Uint8Array,
'SHORT': Int16Array,
'UNSIGNED_SHORT': Uint16Array,
'INT': Int32Array,
'UNSIGNED_INT': Uint32Array,
'FLOAT': Float32Array,
};
function pushBitsUnorm(prev, bitCount, floatVal) {
let ret = prev << bitCount;
ret |= floatVal * ((1 << bitCount)-1);
return ret;
}
const CHANNELS_BY_FORMAT = {
'RED': 1,
'LUMINANCE': 1,
'ALPHA': 1,
'LUMINANCE_ALPHA': 2,
'RG': 2,
'RGB': 3,
'RGBA': 4,
};
function throwv(val) {
throw val;
}
function pixelDataForUnpack(format, type, floatVal) {
switch (type) {
case 'UNSIGNED_SHORT_5_6_5': {
let bits = 0;
bits = pushBitsUnorm(bits, 5, floatVal);
bits = pushBitsUnorm(bits, 6, floatVal);
bits = pushBitsUnorm(bits, 5, floatVal);
return new Uint16Array([bits]);
}
case 'UNSIGNED_SHORT_4_4_4_4': {
let bits = 0;
bits = pushBitsUnorm(bits, 4, floatVal);
bits = pushBitsUnorm(bits, 4, floatVal);
bits = pushBitsUnorm(bits, 4, floatVal);
bits = pushBitsUnorm(bits, 4, floatVal);
return new Uint16Array([bits]);
}
case 'UNSIGNED_SHORT_5_5_5_1': {
let bits = 0;
bits = pushBitsUnorm(bits, 5, floatVal);
bits = pushBitsUnorm(bits, 5, floatVal);
bits = pushBitsUnorm(bits, 5, floatVal);
bits = pushBitsUnorm(bits, 1, floatVal); // Ok, silly for 1 bit here.
return new Uint16Array([bits]);
}
case 'UNSIGNED_INT_10_10_10_2': {
let bits = 0;
bits = pushBitsUnorm(bits, 10, floatVal);
bits = pushBitsUnorm(bits, 10, floatVal);
bits = pushBitsUnorm(bits, 10, floatVal);
bits = pushBitsUnorm(bits, 2, floatVal); // 2 bits isn't much more useful
return new Uint32Array([bits]);
}
}
const channels = CHANNELS_BY_FORMAT[format] || throwv(format);
const one = ONE_BY_TYPE[type] || throwv('240', type);
const abvType = ABV_BY_TYPE[type] || throwv('241', type);
const val = floatVal * one;
const arr = [];
for (const i of range(channels)) {
arr.push(val);
}
return new abvType(arr);
}
function expect(name, was, expected) {
let text = `${name} was ${was}`;
const cond = was == expected;
if (!cond) {
text += `, but expected ${expected}`;
}
expectTrue(cond, text);
}
function toTypeish(sizedFormat) {
if (sizedFormat.endsWith('UI')) {
return 'int';
} else if (sizedFormat.endsWith('I')) {
return 'uint';
}
return 'float';
}
call(async () => {
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
let formatList = FORMAT_INFO_WEBGL1;
if (wtu.isWebGL2(gl)) {
formatList = FORMAT_INFO_WEBGL2;
}
for (const [sizedFormat, info] of Object.entries(formatList)) {
await wtu.dispatchPromise();
debug(``);
debug(`${sizedFormat}: ${JSON.stringify(info)}`);
const typeish = toTypeish(sizedFormat);
// |---|---|
// | 0 | 1 |
// |---|---|
// 0| 0 | 0 |
// |---|---|
// 0
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
if (gl.texStorage2D) {
gl.texStorage2D(gl.TEXTURE_2D, 1, gl[sizedFormat], 2, 2);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl[info.unpack[0]],
2, 2, 0, gl[info.unpack[0]], gl[info.unpack[1]], null);
}
if (info.unpack) {
const one = pixelDataForUnpack(...info.unpack, 1.0);
const data = new one.constructor(one.length*4);
data.set(one, one.length*3);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0,0,
2,2, gl[info.unpack[0]], gl[info.unpack[1]], data);
} else {
info.render || throwv(`${sizedFormat} without unpack or render`);
}
// -
// color-renderable test
{
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
let attach = info.render || true;
const isColor = (attach === true);
if (isColor) {
attach = 'COLOR_ATTACHMENT0';
} else {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, DUMMY_COLOR, 0);
}
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl[attach],
gl.TEXTURE_2D, tex, 0);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
const wasRenderable = (status == gl.FRAMEBUFFER_COMPLETE);
if (info.render === undefined) {
debug(`Non-normative: color-renderable was ${wasRenderable}`);
} else {
expect('color-renderable', wasRenderable, !!info.render);
}
if (wasRenderable) {
gl.clearColor(0,0,0,0);
gl.clearDepth(0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.SCISSOR_TEST);
gl.scissor(1,1,1,1);
gl.clearColor(1,1,1,1);
gl.clearDepth(1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.disable(gl.SCISSOR_TEST);
}
gl.deleteFramebuffer(fb);
if (!wasRenderable && !info.unpack) {
testFailed('No unpack provided and !wasRenderable, skipping filtering subtest...');
continue;
}
}
// -
// filterable test
const prog = TEX_FILTER_PROG_BY_TYPEISH[typeish];
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.clearColor(0,0,0,0);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0,0,1,1);
const v = runPixelProgram(gl, prog);
if (sizedFormat != 'A8') {
v[3] = 0; // Incomplete no-alpha formats put 1 in alpha.
}
const wasFilterable = v.some(x => !!x);
expect('filterable', wasFilterable, info.filter);
}
finishTest();
});
</script>
</body>
</html>