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 2 ReadPixels 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>
<div id="description"></div>
<div id="console"></div>
<script>
"use strict";
description("Checks that ReadPixels from a fbo works as expected.");
var wtu = WebGLTestUtils;
var gl = wtu.create3DContext(undefined, undefined, 2);
gl.pixelStorei(gl.PACK_ALIGNMENT, 1);
function getChannelCount(format) {
switch (format) {
case gl.RED:
case gl.RED_INTEGER:
case gl.ALPHA:
case gl.LUMINANCE:
return 1;
case gl.RB:
case gl.RB_INTEGER:
case gl.LUMINANCE_ALPHA:
return 2;
case gl.RGB:
case gl.RGB_INTEGER:
return 3;
case gl.RGBA:
case gl.RGBA_INTEGER:
return 4;
default:
return 0;
}
}
function getUnpackInfo(type) {
switch (type) {
case gl.UNSIGNED_SHORT_5_6_5:
return {bitsCount: [5, 6, 5], isReverse: false};
case gl.UNSIGNED_SHORT_4_4_4_4:
return {bitsCount: [4, 4, 4, 4], isReverse: false};
case gl.UNSIGNED_SHORT_5_5_5_1:
return {bitsCount: [5, 5, 5, 1], isReverse: false};
case gl.UNSIGNED_INT_2_10_10_10_REV:
return {bitsCount: [2, 10, 10, 10], isReverse: true};
case gl.UNSIGNED_INT_10F_11F_11F_REV:
return {bitsCount: [10, 11, 11], isReverse: true};
case gl.UNSIGNED_INT_5_9_9_9_REV:
return {bitsCount: [5, 9, 9, 9], isReverse: true};
default:
return null;
}
}
// bitsCount is an array contains bit count for each component.
function unpack(value, channelCount, bitsCount, isReverse) {
var result = new Array(channelCount);
var accumBitsCount = 0;
for (var i = channelCount - 1; i >= 0; --i) {
var currentChannel = isReverse ? (channelCount - i - 1) : i;
var mask = 0xFFFFFFFF >>> (32 - bitsCount[i]);
result[currentChannel] = ((value >> accumBitsCount) & mask);
accumBitsCount += bitsCount[i];
}
return result;
}
function getColor(buf, index, readFormat, readType) {
var channelCount = getChannelCount(readFormat);
var result = new Array(channelCount);
var unpackInfo = getUnpackInfo(readType);
if (unpackInfo) {
result = unpack(buf[index], channelCount, unpackInfo.bitsCount, unpackInfo.isReverse);
} else {
for (var i = 0; i < channelCount; ++i) {
result[i] = buf[index + i];
}
}
return result;
}
function convertToSRGB(val) {
if (val <= 0) {
return 0;
} else if (val < 0.0031308) {
return 12.92 * val;
} else if (val < 1) {
return 1.055 * Math.pow(val, 0.41666) - 0.055;
} else {
return 1;
}
}
function denormalizeColor(srcInternalFormat, destType, color) {
var result = color.slice();
var tol = 0;
var srcIsNormalized = false;
switch (srcInternalFormat) {
case gl.R8:
case gl.RG8:
case gl.RGB8:
case gl.RGBA8:
case gl.RGB5_A1:
case gl.SRGB8_ALPHA8:
case gl.RGB10_A2:
srcIsNormalized = true;
tol = 6;
break;
case gl.RGB565:
// RGB565 needs slightly extra tolerance, at least on Google Pixel. crbug.com/682753
srcIsNormalized = true;
tol = 7;
break;
case gl.RGBA4:
srcIsNormalized = true;
tol = 10;
break;
}
if (!srcIsNormalized) {
return { color: result, tol: tol };
}
if (srcInternalFormat == gl.SRGB8_ALPHA8) {
for (var i = 0; i < 3; ++i) {
result[i] = convertToSRGB(result[i]);
}
}
switch (destType) {
case gl.UNSIGNED_BYTE:
result = result.map(val => { return val * 0xFF});
break;
case gl.UNSIGNED_SHORT:
// On Linux NVIDIA, tol of 33 is necessary to pass the test.
tol = 40;
result = result.map(val => { return val * 0xFFFF});
break;
case gl.UNSIGNED_INT:
tol = 40;
result = result.map(val => { return val * 0xFFFFFFFF});
break;
case gl.UNSIGNED_SHORT_4_4_4_4:
result = result.map(val => { return val * 0xF});
break;
case gl.UNSIGNED_SHORT_5_5_5_1:
result[0] = result[0] * 0x1F;
result[1] = result[1] * 0x1F;
result[2] = result[2] * 0x1F;
result[3] = result[3] * 0x1;
break;
case gl.UNSIGNED_SHORT_5_6_5:
result[0] = result[0] * 0x1F;
result[1] = result[1] * 0x3F;
result[2] = result[2] * 0x1F;
break;
case gl.UNSIGNED_INT_2_10_10_10_REV:
tol = 25;
result[0] = result[0] * 0x3FF;
result[1] = result[1] * 0x3FF;
result[2] = result[2] * 0x3FF;
result[3] = result[3] * 0x3;
break;
case gl.UNSIGNED_INT_5_9_9_9_REV:
result[0] = result[0] * 0x1FF;
result[1] = result[1] * 0x1FF;
result[2] = result[2] * 0x1FF;
result[3] = result[3] * 0x1F;
break;
}
return { color: result, tol: tol };
}
function compareColor(buf, index, expectedColor, srcInternalFormat,
srcFormat, srcType, readFormat, readType) {
var srcChannelCount = getChannelCount(srcFormat);
var readChannelCount = getChannelCount(readFormat);
var color = getColor(buf, index, readFormat, readType);
expectedColor = denormalizeColor(srcInternalFormat, readType, expectedColor);
var minChannel = Math.min(srcChannelCount, readChannelCount);
for (var i = 0; i < minChannel; ++i) {
if (Math.abs(expectedColor.color[i] - color[i]) > expectedColor.tol) {
testFailed("Expected color = " + expectedColor.color + ", was = " + color);
return false;
}
}
return true;
}
var textureTestCases = [
{
texInternalFormat: 'R8', texFormat: 'RED', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0.0, 0.0, 0],
},
{
texInternalFormat: 'R8UI', texFormat: 'RED_INTEGER', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [250, 0, 0, 0],
},
{
texInternalFormat: 'R8I', texFormat: 'RED_INTEGER', texType: 'BYTE',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [-126, 0, 0, 0],
},
{
texInternalFormat: 'R16UI', texFormat: 'RED_INTEGER', texType: 'UNSIGNED_SHORT',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [30001, 0, 0, 0],
},
{
texInternalFormat: 'R16I', texFormat: 'RED_INTEGER', texType: 'SHORT',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [-14189, 0, 0, 0],
},
{
texInternalFormat: 'R32UI', texFormat: 'RED_INTEGER', texType: 'UNSIGNED_INT',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [126726, 0, 0, 0],
},
{
texInternalFormat: 'R32I', texFormat: 'RED_INTEGER', texType: 'INT',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [-126726, 0, 0, 0],
},
{
texInternalFormat: 'RG8', texFormat: 'RG', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0.7, 0.0, 0],
},
{
texInternalFormat: 'RG8UI', texFormat: 'RG_INTEGER', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [250, 124, 0, 0],
},
{
texInternalFormat: 'RG8I', texFormat: 'RG_INTEGER', texType: 'BYTE',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [-55, 124, 0, 0],
},
{
texInternalFormat: 'RG16UI', texFormat: 'RG_INTEGER', texType: 'UNSIGNED_SHORT',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [30001, 18, 0, 0],
},
{
texInternalFormat: 'RG16I', texFormat: 'RG_INTEGER', texType: 'SHORT',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [-14189, 6735, 0, 0],
},
{
texInternalFormat: 'RG32UI', texFormat: 'RG_INTEGER', texType: 'UNSIGNED_INT',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [126726, 1976, 0, 0],
},
{
texInternalFormat: 'RG32I', texFormat: 'RG_INTEGER', texType: 'INT',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [-126726, 126726, 0, 0],
},
{
texInternalFormat: 'RGB8', texFormat: 'RGB', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 1, 0, 0],
},
{
texInternalFormat: 'RGB565', texFormat: 'RGB', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0.7, 0.2, 0],
},
{
texInternalFormat: 'RGB565', texFormat: 'RGB', texType: 'UNSIGNED_SHORT_5_6_5',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0.7, 0.2, 0],
},
{
texInternalFormat: 'RGBA8', texFormat: 'RGBA', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0, 1, 0.7],
},
{
texInternalFormat: 'SRGB8_ALPHA8', texFormat: 'RGBA', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0, 1, 0.7],
},
{
texInternalFormat: 'RGB5_A1', texFormat: 'RGBA', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0, 0.7, 1],
},
{
texInternalFormat: 'RGB5_A1', texFormat: 'RGBA', texType: 'UNSIGNED_SHORT_5_5_5_1',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0.7, 1, 0],
},
{
texInternalFormat: 'RGB5_A1', texFormat: 'RGBA', texType: 'UNSIGNED_INT_2_10_10_10_REV',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0.7, 0, 1],
},
{
texInternalFormat: 'RGBA4', texFormat: 'RGBA', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.5, 0.7, 1, 0],
},
{
texInternalFormat: 'RGBA4', texFormat: 'RGBA', texType: 'UNSIGNED_SHORT_4_4_4_4',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [1, 0, 0.5, 0.7],
},
{
texInternalFormat: 'RGBA8UI', texFormat: 'RGBA_INTEGER', texType: 'UNSIGNED_BYTE',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [127, 0, 255, 178],
},
{
texInternalFormat: 'RGBA8I', texFormat: 'RGBA_INTEGER', texType: 'BYTE',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [-55, 56, 80, 127],
},
{
texInternalFormat: 'RGB10_A2UI', texFormat: 'RGBA_INTEGER', texType: 'UNSIGNED_INT_2_10_10_10_REV',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [178, 0, 127, 3],
},
{
texInternalFormat: 'RGBA16UI', texFormat: 'RGBA_INTEGER', texType: 'UNSIGNED_SHORT',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [14189, 6735, 0, 19],
},
{
texInternalFormat: 'RGBA16I', texFormat: 'RGBA_INTEGER', texType: 'SHORT',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [14189, -6735, 0, 19],
},
{
texInternalFormat: 'RGBA32UI', texFormat: 'RGBA_INTEGER', texType: 'UNSIGNED_INT',
readFormat: 'RGBA_INTEGER', readType: 'UNSIGNED_INT',
clearColor: [126726, 6726, 98765, 2015],
},
{
texInternalFormat: 'RGBA32I', texFormat: 'RGBA_INTEGER', texType: 'INT',
readFormat: 'RGBA_INTEGER', readType: 'INT',
clearColor: [126726, -6726, -98765, 2015],
},
{
texInternalFormat: 'RGB10_A2', texFormat: 'RGBA', texType: 'UNSIGNED_INT_2_10_10_10_REV',
readFormat: 'RGBA', readType: 'UNSIGNED_BYTE',
clearColor: [0.7, 0, 0.5, 1],
},
// TODO(zmo): add float/half_float test cases with extension supports.
];
function getArrayTypeFromReadPixelsType(gl, type) {
switch (type) {
case gl.UNSIGNED_BYTE:
return Uint8Array;
case gl.BYTE:
return Int8Array;
case gl.UNSIGNED_SHORT:
case gl.UNSIGNED_SHORT_5_6_5:
case gl.UNSIGNED_SHORT_4_4_4_4:
case gl.UNSIGNED_SHORT_5_5_5_1:
return Uint16Array;
case gl.SHORT:
return Int16Array;
case gl.UNSIGNED_INT:
case gl.UNSIGNED_INT_2_10_10_10_REV:
case gl.UNSIGNED_INT_10F_11F_11F_REV:
case gl.UNSIGNED_INT_5_9_9_9_REV:
return Uint32Array;
case gl.INT:
return Int32Array;
case gl.HALF_FLOAT:
return Uint16Array;
case gl.FLOAT:
return Float32Array;
default:
return null;
}
}
function getFormatString(gl, format) {
switch (format) {
case gl.RED:
return 'RED';
case gl.RED_INTEGER:
return 'RED_INTEGER';
case gl.RG:
return 'RG';
case gl.RG_INTEGER:
return 'RG_INTEGER';
case gl.RGB:
return 'RGB';
case gl.RGB_INTEGER:
return 'RGB_INTEGER';
case gl.RGBA:
return 'RGBA';
case gl.RGBA_INTEGER:
return 'RGBA_INTEGER';
case gl.LUMINANCE:
return 'LUMINANCE';
case gl.LUMINANCE_ALPHA:
return 'LUMINANCE_ALPHA';
case gl.ALPHA:
return 'ALPHA';
default:
return '';
};
}
function getTypeString(gl, type) {
switch (type) {
case gl.UNSIGNED_BYTE:
return 'UNSIGNED_BYTE';
case gl.BYTE:
return 'BYTE';
case gl.UNSIGNED_SHORT:
return 'UNSIGNED_SHORT';
case gl.SHORT:
return 'SHORT';
case gl.UNSIGNED_INT:
return 'UNSIGNED_INT';
case gl.INT:
return 'INT';
case gl.UNSIGNED_SHORT_5_6_5:
return 'UNSIGNED_SHORT_5_6_5';
case gl.UNSIGNED_SHORT_5_5_5_1:
return 'UNSIGNED_SHORT_5_5_5_1';
case gl.UNSIGNED_INT_2_10_10_10_REV:
return 'UNSIGNED_INT_2_10_10_10_REV';
case gl.UNSIGNED_SHORT_4_4_4_4:
return 'UNSIGNED_SHORT_4_4_4_4';
case gl.UNSIGNED_INT_10F_11F_11F_REV:
return 'UNSIGNED_INT_10F_11F_11F_REV';
case gl.UNSIGNED_INT_5_9_9_9_REV:
return 'UNSIGNED_INT_5_9_9_9_REV';
default:
return '';
};
}
function elementCountPerPixel(gl, readFormat, readType) {
switch (readFormat) {
case gl.RED:
case gl.RED_INTEGER:
case gl.ALPHA:
case gl.LUMINANCE:
return 1;
case gl.RG:
case gl.RG_INTEGER:
case gl.LUMINANCE_ALPHA:
return 2;
case gl.RGB:
case gl.RGB_INTEGER:
switch (readType) {
case gl.UNSIGNED_SHORT_5_6_5:
return 1;
default:
return 3;
}
case gl.RGBA:
case gl.RGBA_INTEGER:
switch (readType) {
case gl.UNSIGNED_SHORT_4_4_4_4:
case gl.UNSIGNED_SHORT_5_5_5_1:
case gl.UNSIGNED_INT_2_10_10_10_REV:
case gl.UNSIGNED_INT_10F_11F_11F_REV:
case gl.UNSIGNED_INT_5_9_9_9_REV:
return 1;
default:
return 4;
}
default:
testFailed("Unexpected read format/type = " + readFormat + "/" + readType);
return 0;
}
}
function testReadPixels(gl, srcInternalFormat, srcFormat, srcType,
readFormat, readType, expectedColor) {
var arrayType = getArrayTypeFromReadPixelsType(gl, readType);
var buf = new arrayType(width * height * 4);
gl.readPixels(0, 0, width, height, readFormat, readType, buf);
wtu.glErrorShouldBe(
gl, gl.NO_ERROR, "readPixels should generate no error");
var diffFound = false;
for (var ii = 0; ii < width * height; ++ii) {
var offset = ii * elementCountPerPixel(gl, readFormat, readType);
if (!compareColor(buf, offset, expectedColor, srcInternalFormat, srcFormat, srcType,
readFormat, readType)) {
diffFound = true;
break;
}
}
if (!diffFound) {
testPassed("Color read back as expected");
}
}
function clearBuffer(gl, texInternalFormat, clearColor) {
var value;
switch (texInternalFormat) {
case gl.R8UI:
case gl.R16UI:
case gl.R32UI:
case gl.RG8UI:
case gl.RG16UI:
case gl.RG32UI:
case gl.RGBA8UI:
case gl.RGBA16UI:
case gl.RGBA32UI:
case gl.RGB10_A2UI:
value = new Uint32Array(4);
for (var ii = 0; ii < 4; ++ii)
value[ii] = clearColor[ii];
gl.clearBufferuiv(gl.COLOR, 0, value);
break;
case gl.R8I:
case gl.R16I:
case gl.R32I:
case gl.RG8I:
case gl.RG16I:
case gl.RG32I:
case gl.RGBA8I:
case gl.RGBA16I:
case gl.RGBA32I:
value = new Int32Array(4);
for (var ii = 0; ii < 4; ++ii)
value[ii] = clearColor[ii];
gl.clearBufferiv(gl.COLOR, 0, value);
break;
default:
gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
gl.clear(gl.COLOR_BUFFER_BIT);
break;
}
}
for (var tt = 0; tt < textureTestCases.length; ++tt) {
var test = textureTestCases[tt];
debug("");
debug("ReadPixels from fbo with texture = (" + test.texInternalFormat +
", " + test.texFormat + ", " + test.texType +
"), format = " + test.readFormat + ", type = " + test.readType);
var width = 2;
var height = 2;
var fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
var colorImage = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, colorImage);
gl.texImage2D(gl.TEXTURE_2D, 0, gl[test.texInternalFormat], width, height, 0,
gl[test.texFormat], gl[test.texType], null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, colorImage, 0);
wtu.glErrorShouldBe(
gl, gl.NO_ERROR, "Setting up fbo should generate no error");
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
debug("fbo is not complete, skip");
continue;
}
clearBuffer(gl, gl[test.texInternalFormat], test.clearColor);
wtu.glErrorShouldBe(
gl, gl.NO_ERROR, "Clear color should generate no error");
var implFormat = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
var implType = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE);
var implFormatString = getFormatString(gl, implFormat);
var implTypeString = getTypeString(gl, implType);
if (gl[test.texInternalFormat] == gl.RGB10_A2) {
// This is a special case where three read format/type are supported.
var readTypes = [gl.UNSIGNED_BYTE, gl.UNSIGNED_INT_2_10_10_10_REV];
var readTypeStrings = ['UNSIGNED_BYTE', 'UNSIGNED_INT_2_10_10_10_REV'];
if (implFormat == gl.RGBA && implTypeString != '') {
readTypes.push(implType);
readTypeStrings.push(implTypeString);
} else {
testFailed("Unimplemented Implementation dependent color read format/type = " +
implFormat + "/" + implType);
}
for (var rr = 0; rr < readTypes.length; ++rr) {
debug("Special case RGB10_A2, format = RGBA, type = " + readTypeStrings[rr]);
testReadPixels(gl, gl[test.texInternalFormat], gl[test.texFormat], gl[test.texType],
gl.RGBA, readTypes[rr], test.clearColor);
}
} else {
testReadPixels(gl, gl[test.texInternalFormat], gl[test.texFormat], gl[test.texType],
gl[test.readFormat], gl[test.readType], test.clearColor);
debug("Testing implementation dependent color read format = " + implFormatString +
", type = " + implTypeString);
if (implFormatString == '') {
testFailed("Invalid IMPLEMENTATION_COLOR_READ_FORMAT = " + implFormat);
continue;
}
if (implTypeString == '') {
testFailed("Invalid IMPLEMENTATION_COLOR_READ_TYPE = " + implType);
continue;
}
testReadPixels(gl, gl[test.texInternalFormat], gl[test.texFormat], gl[test.texType],
implFormat, implType, test.clearColor);
gl.deleteTexture(colorImage);
gl.deleteFramebuffer(fbo);
}
}
debug("")
var successfullyParsed = true;
</script>
<script src="../../js/js-test-post.js"></script>
</body>
</html>