Source code
Revision control
Copy as Markdown
Other Tools
<!--
Copyright (c) 2019 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">
<title>WebGL shader precision format test.</title>
<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>
<canvas id="canvas" width="2" height="2" style="width: 40px; height: 40px;"></canvas>
<div id="description"></div>
<div id="console"></div>
<script>
"use strict";
var wtu = WebGLTestUtils;
description(document.title);
debug("Tests that WebGLShaderPrecisionFormat class and getShaderPrecisionFormat work.");
debug("");
var gl = wtu.create3DContext("canvas");
function verifyShaderPrecisionFormat(shadertype, precisiontype) {
shouldBeTrue('gl.getShaderPrecisionFormat(' + shadertype + ', ' +
precisiontype + ') instanceof WebGLShaderPrecisionFormat');
}
debug("");
debug("Test that getShaderPrecisionFormat returns a WebGLShaderPrecisionFormat object.");
debug("");
verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.LOW_FLOAT');
verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.MEDIUM_FLOAT');
verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.HIGH_FLOAT');
verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.LOW_INT');
verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.MEDIUM_INT');
verifyShaderPrecisionFormat('gl.VERTEX_SHADER', 'gl.HIGH_INT');
verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.LOW_FLOAT');
verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.MEDIUM_FLOAT');
verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.HIGH_FLOAT');
verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.LOW_INT');
verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.MEDIUM_INT');
verifyShaderPrecisionFormat('gl.FRAGMENT_SHADER', 'gl.HIGH_INT');
debug("");
debug("Test that getShaderPrecisionFormat throws an error with invalid parameters.");
debug("");
wtu.shouldGenerateGLError(gl, gl.INVALID_ENUM, 'gl.getShaderPrecisionFormat(gl.HIGH_INT, gl.VERTEX_SHADER)');
debug("");
debug("Test that WebGLShaderPrecisionFormat values are sensible.");
debug("");
// The minimum values are from OpenGL ES Shading Language spec, section 4.5.
var shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 1');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 1');
shouldBeTrue('shaderPrecisionFormat.precision >= 8');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 14');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 14');
shouldBeTrue('shaderPrecisionFormat.precision >= 10');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 62');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 62');
shouldBeTrue('shaderPrecisionFormat.precision >= 16');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_INT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 8');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 8');
shouldBeTrue('shaderPrecisionFormat.precision == 0');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_INT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 10');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 10');
shouldBeTrue('shaderPrecisionFormat.precision == 0');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_INT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 16');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 16');
shouldBeTrue('shaderPrecisionFormat.precision == 0');
var shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_FLOAT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 1');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 1');
shouldBeTrue('shaderPrecisionFormat.precision >= 8');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 14');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 14');
shouldBeTrue('shaderPrecisionFormat.precision >= 10');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_INT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 8');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 8');
shouldBeTrue('shaderPrecisionFormat.precision == 0');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_INT);
shouldBeTrue('shaderPrecisionFormat.rangeMin >= 10');
shouldBeTrue('shaderPrecisionFormat.rangeMax >= 10');
shouldBeTrue('shaderPrecisionFormat.precision == 0');
debug("");
debug("Test optional highp support in fragment shaders.");
debug("");
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
shouldBeTrue('(shaderPrecisionFormat.rangeMin == 0 && shaderPrecisionFormat.rangeMax == 0 && shaderPrecisionFormat.precision == 0) || (shaderPrecisionFormat.rangeMin >= 62 && shaderPrecisionFormat.rangeMax >= 62 && shaderPrecisionFormat.precision >= 16)');
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT);
shouldBeTrue('(shaderPrecisionFormat.rangeMin == 0 && shaderPrecisionFormat.rangeMax == 0 && shaderPrecisionFormat.precision == 0) || (shaderPrecisionFormat.rangeMin >= 16 && shaderPrecisionFormat.rangeMax >= 16 && shaderPrecisionFormat.precision == 0)');
debug("");
debug("Test that getShaderPrecisionFormat returns the same thing every call.");
debug("");
shaderPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT);
var shaderPrecisionFormat2 = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT);
shouldBeTrue('shaderPrecisionFormat.rangeMin == shaderPrecisionFormat2.rangeMin');
shouldBeTrue('shaderPrecisionFormat.rangeMax == shaderPrecisionFormat2.rangeMax');
shouldBeTrue('shaderPrecisionFormat.precision == shaderPrecisionFormat2.precision');
debug("");
debug("Test that specified precision matches rendering results");
debug("");
function testRenderPrecisionSetup(gl, shaderPair) {
const program = wtu.setupProgram(gl, shaderPair);
// Create a buffer and setup an attribute.
// We wouldn't need this except for a bug in Safari and arguably
// this should be removed from the test but we can't test the test itself
// without until the bug is fixed.
{
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, 1, gl.STATIC_DRAW);
const loc = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 1, gl.UNSIGNED_BYTE, false, 0, 0);
}
gl.useProgram(program);
return program;
}
function testRenderPrecision(gl, shaderType, type, precision, expected, msg) {
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
const pixel = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
wtu.checkCanvasRect(gl, 0, 0, 1, 1, expected, msg, 5);
}
function testRenderPrecisionFloat(gl, precisionEnum, precision) {
function test(gl, shaderPair, shaderType) {
const format = gl.getShaderPrecisionFormat(shaderType, precisionEnum);
const value = 2 ** format.precision - 1;
const length = v => Math.sqrt(v.reduce((sum, v) => sum + v * v, 0));
const normalize = (v) => {
const l = length(v);
return v.map(v => v / l);
};
const input = [Math.sqrt(value), Math.sqrt(value), Math.sqrt(value)];
const expected = [...normalize(input).map(v => (v * 0.5 + 0.5) * 255 | 0), 255];
const msg = `${wtu.glEnumToString(gl, shaderType)}: ${precision} float precision: ${format.precision}, rangeMin: ${format.rangeMin}, rangeMax: ${format.rangeMax}`;
const program = testRenderPrecisionSetup(gl, shaderPair);
const vLocation = gl.getUniformLocation(program, 'v');
gl.uniform3fv(vLocation, input);
testRenderPrecision(gl, shaderType, 'float', precision, expected, msg);
}
{
const vs = `
attribute vec4 position;
uniform ${precision} vec3 v;
varying ${precision} vec4 v_result;
void main() {
gl_Position = position;
gl_PointSize = 1.0;
v_result = vec4(normalize(v) * 0.5 + 0.5, 1);
}
`;
const fs = `
precision ${precision} float;
varying ${precision} vec4 v_result;
void main() {
gl_FragColor = v_result;
}
`;
test(gl, [vs, fs], gl.VERTEX_SHADER);
}
{
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 1.0;
}
`;
const fs = `
precision ${precision} float;
uniform ${precision} vec3 v;
void main() {
gl_FragColor = vec4(normalize(v) * 0.5 + 0.5, 1);
}
`;
test(gl, [vs, fs], gl.FRAGMENT_SHADER);
}
}
function testRenderPrecisionInt(gl, precisionEnum, precision) {
function test(gl, shaderPair, shaderType) {
const format = gl.getShaderPrecisionFormat(shaderType, precisionEnum);
const value = 1 << (format.rangeMax - 1);
const input = [value, value, value, value];
const expected = [255, 255, 255, 255];
const msg = `${wtu.glEnumToString(gl, shaderType)}: ${precision} int precision: ${format.precision}, rangeMin: ${format.rangeMin}, rangeMax: ${format.rangeMax}`;
const program = testRenderPrecisionSetup(gl, shaderPair);
gl.uniform1i(gl.getUniformLocation(program, 'v'), value);
gl.uniform1f(gl.getUniformLocation(program, 'f'), value);
testRenderPrecision(gl, shaderType, 'int', precision, expected, msg);
}
{
const vs = `
attribute vec4 position;
uniform ${precision} int v;
uniform highp float f;
varying vec4 v_result;
void main() {
gl_Position = position;
gl_PointSize = 1.0;
float diff = abs(float(v) - f);
bool pass = diff < 1.0;
v_result = vec4(pass);
}
`;
const fs = `
precision mediump float;
varying vec4 v_result;
void main() {
gl_FragColor = v_result;
}
`;
test(gl, [vs, fs], gl.VERTEX_SHADER);
}
{
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 1.0;
}
`;
const fs = `
precision ${precision} float;
uniform ${precision} int v;
uniform mediump float f;
void main() {
mediump float diff = abs(float(v) - f);
bool pass = diff < 1.0;
gl_FragColor = vec4(pass);
}
`;
test(gl, [vs, fs], gl.FRAGMENT_SHADER);
}
}
// because the canvas can be 16 bit IIRC
const fb = gl.createFramebuffer(gl.FRAMEBUFFER);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
tex,
0);
gl.viewport(0, 0, 1, 1);
{
testRenderPrecisionFloat(gl, gl.LOW_FLOAT, 'lowp');
testRenderPrecisionFloat(gl, gl.MEDIUM_FLOAT, 'mediump');
const format = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
if (format.precision !== 0) {
testRenderPrecisionFloat(gl, gl.HIGH_FLOAT, 'highp');
}
}
{
testRenderPrecisionInt(gl, gl.LOW_INT, 'lowp');
testRenderPrecisionInt(gl, gl.MEDIUM_INT, 'mediump');
const format = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT);
if (format.rangeMax !== 0) {
testRenderPrecisionInt(gl, gl.HIGH_INT, 'highp');
}
}
finishTest();
</script>
</body>
</html>