blob: d771ef609d321ccd4005ecc678d044aefb81e6ee [file] [log] [blame]
<!--
/*
** Copyright (c) 2012 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../resources/js-test-pre.js"></script>
<script src="../resources/pnglib.js"></script>
<script src="../resources/webgl-test.js"></script>
<script src="../resources/webgl-test-utils.js"></script>
<script>
"use strict";
var wtu = WebGLTestUtils;
var gl = null;
var textureLoc = null;
var successfullyParsed = false;
//----------------------------------------------------------------------
// Harness
var testCases = [];
var DataMode = {
IMAGE: 0,
IMAGE_DATA: 1,
NUM_HTML_MODES: 2,
RAW_DATA: 2,
// This must remain the last mode.
NUM_MODES: 3
};
function init()
{
initTestingHarnessWaitUntilDone();
description('Verify texImage2D and texSubImage2D code paths taking both HTML and user-specified data with all format/type combinations');
var canvas = document.getElementById("example");
gl = wtu.create3DContext(canvas);
gl.disable(gl.DITHER);
var program = wtu.setupTexturedQuad(gl);
gl.clearColor(0,0,0,1);
gl.clearDepth(1);
gl.disable(gl.BLEND);
textureLoc = gl.getUniformLocation(program, "tex");
initializeTests();
}
function initializeTests()
{
// Verify that uploading to packed pixel formats performs the
// required conversion and associated loss of precision.
for (var dataMode = 0; dataMode < DataMode.NUM_HTML_MODES; ++dataMode) {
for (var useTexSubImage2D = 0; useTexSubImage2D < 2; ++useTexSubImage2D) {
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateOpaqueGrayscaleRamp,
premultiplyAlpha: false,
format: gl.RGBA,
type: gl.UNSIGNED_BYTE,
verifier: allChannelsIncreaseByNoMoreThan,
threshold: 1,
numOccurrences: 1,
description: "RGBA/UNSIGNED_BYTE should maintain full precision of data"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateOpaqueGrayscaleRamp,
premultiplyAlpha: false,
format: gl.RGBA,
type: gl.UNSIGNED_SHORT_4_4_4_4,
verifier: allChannelsIncreaseByAtLeast,
threshold: 15,
numOccurrences: 10,
description: "RGBA/UNSIGNED_SHORT_4_4_4_4 must drop low four bits of precision"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateOpaqueGrayscaleRamp,
premultiplyAlpha: false,
format: gl.RGBA,
type: gl.UNSIGNED_SHORT_5_5_5_1,
verifier: allChannelsIncreaseByAtLeast,
threshold: 7,
numOccurrences: 20,
description: "RGBA/UNSIGNED_SHORT_5_5_5_1 must drop low three bits of precision"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateOpaqueGrayscaleRamp,
premultiplyAlpha: false,
format: gl.RGB,
type: gl.UNSIGNED_BYTE,
verifier: allChannelsIncreaseByNoMoreThan,
threshold: 1,
numOccurrences: 1,
description: "RGB/UNSIGNED_BYTE should maintain full precision of data"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateOpaqueGrayscaleRamp,
premultiplyAlpha: false,
format: gl.RGB,
type: gl.UNSIGNED_SHORT_5_6_5,
verifier: allChannelsIncreaseByAtLeast,
threshold: 3,
numOccurrences: 20,
description: "RGB/UNSIGNED_SHORT_5_6_5 must drop low two or three bits of precision"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTranslucentGrayscaleRamp,
premultiplyAlpha: false,
format: gl.ALPHA,
type: gl.UNSIGNED_BYTE,
verifier: alphaChannelIncreasesByNoMoreThan,
threshold: 1,
numOccurrences: 1,
description: "ALPHA/UNSIGNED_BYTE should maintain full precision of data"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateOpaqueGrayscaleRamp,
premultiplyAlpha: false,
format: gl.LUMINANCE,
type: gl.UNSIGNED_BYTE,
verifier: allChannelsIncreaseByNoMoreThan,
threshold: 1,
numOccurrences: 1,
description: "LUMINANCE/UNSIGNED_BYTE should maintain full precision of data"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateOpaqueGrayscaleRamp,
premultiplyAlpha: false,
format: gl.LUMINANCE_ALPHA,
type: gl.UNSIGNED_BYTE,
verifier: allChannelsIncreaseByNoMoreThan,
threshold: 1,
numOccurrences: 1,
description: "LUMINANCE_ALPHA/UNSIGNED_BYTE should maintain full precision of data"
});
}
}
// Verify that setting the UNPACK_PREMULTIPLY_ALPHA_WEBGL pixel
// store parameter and sending down a zero alpha causes the color
// channels to go to zero.
for (var dataMode = 0; dataMode < DataMode.NUM_MODES; ++dataMode) {
for (var useTexSubImage2D = 0; useTexSubImage2D < 2; ++useTexSubImage2D) {
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTransparentGrayscaleRamp,
premultiplyAlpha: true,
format: gl.RGBA,
type: gl.UNSIGNED_BYTE,
verifier: colorChannelsAreZero,
description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGBA/UNSIGNED_BYTE"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTransparentGrayscaleRamp,
premultiplyAlpha: true,
format: gl.RGBA,
type: gl.UNSIGNED_SHORT_4_4_4_4,
verifier: colorChannelsAreZero,
description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGBA/UNSIGNED_SHORT_4_4_4_4"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTransparentGrayscaleRamp,
premultiplyAlpha: true,
format: gl.RGBA,
type: gl.UNSIGNED_SHORT_5_5_5_1,
verifier: colorChannelsAreZero,
description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGBA/UNSIGNED_SHORT_5_5_5_1"
});
// The following few tests are invalid for the raw data
// mode because there is either no alpha channel or no
// separate alpha channel.
if (dataMode != DataMode.RAW_DATA) {
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTransparentGrayscaleRamp,
premultiplyAlpha: true,
format: gl.RGB,
type: gl.UNSIGNED_BYTE,
verifier: colorChannelsAreZero,
description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGB/UNSIGNED_BYTE"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTransparentGrayscaleRamp,
premultiplyAlpha: true,
format: gl.RGB,
type: gl.UNSIGNED_SHORT_5_6_5,
verifier: colorChannelsAreZero,
description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGB/UNSIGNED_SHORT_5_6_5"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTransparentGrayscaleRamp,
premultiplyAlpha: true,
format: gl.ALPHA,
type: gl.UNSIGNED_BYTE,
verifier: colorChannelsAreZero,
description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with ALPHA/UNSIGNED_BYTE"
});
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTransparentGrayscaleRamp,
premultiplyAlpha: true,
format: gl.LUMINANCE,
type: gl.UNSIGNED_BYTE,
verifier: colorChannelsAreZero,
description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with LUMINANCE/UNSIGNED_BYTE"
});
}
testCases.push({
dataMode: dataMode,
useTexSubImage2D: !!useTexSubImage2D,
width: 256,
height: 1,
generator: generateTransparentGrayscaleRamp,
premultiplyAlpha: true,
format: gl.LUMINANCE_ALPHA,
type: gl.UNSIGNED_BYTE,
verifier: colorChannelsAreZero,
description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with LUMINANCE_ALPHA/UNSIGNED_BYTE"
});
}
}
// Produce data for all testcases. Because we load images, some of
// these may generate their data asynchronously.
generateTestData();
}
function generateTestData()
{
for (var i = 0; i < testCases.length; i++) {
var testCase = testCases[i];
var wrapper = null;
switch (testCase.dataMode) {
case DataMode.IMAGE:
wrapper = new ImageWrapper(testCase.width, testCase.height);
break;
case DataMode.IMAGE_DATA:
wrapper = new ImageDataWrapper(testCase.width, testCase.height);
break;
case DataMode.RAW_DATA:
switch (testCase.type) {
case gl.UNSIGNED_BYTE:
switch (testCase.format) {
case gl.RGBA:
wrapper = new RGBA8DataWrapper(testCase.width, testCase.height);
break;
case gl.RGB:
wrapper = new RGB8DataWrapper(testCase.width, testCase.height);
break;
case gl.ALPHA:
wrapper = new A8DataWrapper(testCase.width, testCase.height);
break;
case gl.LUMINANCE:
wrapper = new L8DataWrapper(testCase.width, testCase.height);
break;
case gl.LUMINANCE_ALPHA:
wrapper = new LA8DataWrapper(testCase.width, testCase.height);
break;
}
break;
case gl.UNSIGNED_SHORT_4_4_4_4:
wrapper = new RGBA4444DataWrapper(testCase.width, testCase.height);
break;
case gl.UNSIGNED_SHORT_5_5_5_1:
wrapper = new RGBA5551DataWrapper(testCase.width, testCase.height);
break;
case gl.UNSIGNED_SHORT_5_6_5:
wrapper = new RGB565DataWrapper(testCase.width, testCase.height);
break;
}
}
testCase.wrapper = wrapper;
testCase.generator(wrapper);
testCase.wrapper.generateData();
}
// See whether we need to run the tests, in case all of them
// generated their results synchronously.
maybeRunTests();
}
var ranTests = false;
function maybeRunTests()
{
if (!ranTests)
for (var i = 0; i < testCases.length; ++i)
if (!testCases[i].wrapper || !testCases[i].wrapper.data)
return;
ranTests = true;
for (var i = 0; i < testCases.length; ++i)
runOneTest(testCases[i]);
finishTest();
}
function testCaseToString(testCase)
{
var mode;
switch (testCase.dataMode) {
case DataMode.IMAGE:
mode = "Image";
break;
case DataMode.IMAGE_DATA:
mode = "ImageData";
break;
case DataMode.RAW_DATA:
mode = "raw data";
break;
}
return (testCase.useTexSubImage2D ? "texSubImage2D" : "texImage2D") +
" with " + mode + " at " + testCase.width + "x" + testCase.height;
}
function runOneTest(testCase)
{
debug("Testing " + testCaseToString(testCase));
var data = testCase.wrapper.data;
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var texture = gl.createTexture();
// Bind the texture to texture unit 0.
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set up texture parameters.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Set up pixel store parameters.
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, testCase.premultiplyAlpha);
// Upload the image into the texture.
if (testCase.useTexSubImage2D) {
// Initialize the texture to black first.
gl.texImage2D(gl.TEXTURE_2D, 0, testCase.format, testCase.width, testCase.height, 0,
testCase.format, testCase.type, null);
}
switch (testCase.dataMode) {
case DataMode.IMAGE:
case DataMode.IMAGE_DATA:
if (testCase.useTexSubImage2D)
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, testCase.format, testCase.type, data);
else
gl.texImage2D(gl.TEXTURE_2D, 0, testCase.format, testCase.format, testCase.type, data);
break;
case DataMode.RAW_DATA:
if (testCase.useTexSubImage2D)
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, testCase.width, testCase.height, testCase.format, testCase.type, data);
else
gl.texImage2D(gl.TEXTURE_2D, 0, testCase.format, testCase.width, testCase.height, 0, testCase.format, testCase.type, data);
break;
}
// Point the uniform sampler to texture unit 0.
gl.uniform1i(textureLoc, 0);
// Draw the triangles.
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Clean up the texture.
gl.deleteTexture(texture);
// Read back the rendering results.
var buf = new Uint8Array(testCase.width * testCase.height * 4);
gl.readPixels(0, 0, testCase.width, testCase.height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
// Run the verification routine.
if (testCase.verifier(buf, testCase.threshold, testCase.numOccurrences))
testPassed(testCase.description);
else
testFailed(testCase.description);
}
//----------------------------------------------------------------------
// Wrappers for programmatic construction of Image, ImageData and raw texture data
//
function ImageWrapper(width, height)
{
this.pngBuilder_ = new PNGlib(width, height, 256);
}
ImageWrapper.prototype.getWidth = function() {
return this.pngBuilder_.width;
};
ImageWrapper.prototype.getHeight = function() {
return this.pngBuilder_.height;
};
ImageWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
this.pngBuilder_.buffer[this.pngBuilder_.index(x, y)] = this.pngBuilder_.color(r, g, b, a);
};
// Generates data into "data" property, possibly asynchronously.
ImageWrapper.prototype.generateData = function() {
var that = this;
var img = new Image();
img.onload = function() {
that.data = img;
maybeRunTests();
};
img.src = "data:image/png;base64," + this.pngBuilder_.getBase64();
};
function ImageDataWrapper(width, height)
{
if (!ImageDataWrapper.tempCanvas) {
ImageDataWrapper.tempCanvas = document.createElement("canvas");
}
this.imageData_ = ImageDataWrapper.tempCanvas.getContext("2d").createImageData(width, height);
}
ImageDataWrapper.tempCanvas = null;
ImageDataWrapper.prototype.getWidth = function() {
return this.imageData_.width;
};
ImageDataWrapper.prototype.getHeight = function() {
return this.imageData_.height;
};
ImageDataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
var index = 4 * (this.imageData_.width * y + x);
this.imageData_.data[index] = r;
this.imageData_.data[index + 1] = g;
this.imageData_.data[index + 2] = b;
this.imageData_.data[index + 3] = a;
};
ImageDataWrapper.prototype.generateData = function() {
this.data = this.imageData_;
maybeRunTests();
};
function TextureDataWrapper(width, height)
{
this.width_ = width;
this.height_ = height;
}
TextureDataWrapper.prototype.getWidth = function() {
return this.width_;
};
TextureDataWrapper.prototype.getHeight = function() {
return this.height_;
};
TextureDataWrapper.prototype.generateData = function() {
this.data = this.data_;
maybeRunTests();
};
function RGBA8DataWrapper(width, height)
{
TextureDataWrapper.call(this, width, height);
this.data_ = new Uint8Array(4 * width * height);
}
RGBA8DataWrapper.prototype = new TextureDataWrapper;
RGBA8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
var index = 4 * (this.width_ * y + x);
this.data_[index] = r;
this.data_[index + 1] = g;
this.data_[index + 2] = b;
this.data_[index + 3] = a;
};
function RGBA5551DataWrapper(width, height)
{
TextureDataWrapper.call(this, width, height);
this.data_ = new Uint16Array(width * height);
}
RGBA5551DataWrapper.prototype = new TextureDataWrapper;
RGBA5551DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
var value = (((r & 0xF8) << 8)
| ((g & 0xF8) << 3)
| ((b & 0xF8) >> 2)
| (a >> 7));
this.data_[this.width_ * y + x] = value;
};
function RGBA4444DataWrapper(width, height)
{
TextureDataWrapper.call(this, width, height);
this.data_ = new Uint16Array(width * height);
}
RGBA4444DataWrapper.prototype = new TextureDataWrapper;
RGBA4444DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
var value = (((r & 0xF0) << 8)
| ((g & 0xF0) << 4)
| (b & 0xF0)
| (a >> 4));
this.data_[this.width_ * y + x] = value;
};
function RGB8DataWrapper(width, height)
{
TextureDataWrapper.call(this, width, height);
this.data_ = new Uint8Array(3 * width * height);
}
RGB8DataWrapper.prototype = new TextureDataWrapper;
RGB8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
var index = 3 * (this.width_ * y + x);
this.data_[index] = r;
this.data_[index + 1] = g;
this.data_[index + 2] = b;
};
function RGB565DataWrapper(width, height)
{
TextureDataWrapper.call(this, width, height);
this.data_ = new Uint16Array(width * height);
}
RGB565DataWrapper.prototype = new TextureDataWrapper;
RGB565DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
var value = (((r & 0xF8) << 8)
| ((g & 0xFC) << 3)
| ((b & 0xF8) >> 3));
this.data_[this.width_ * y + x] = value;
};
function A8DataWrapper(width, height)
{
TextureDataWrapper.call(this, width, height);
this.data_ = new Uint8Array(width * height);
}
A8DataWrapper.prototype = new TextureDataWrapper;
A8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
this.data_[this.width_ * y + x] = a;
};
function L8DataWrapper(width, height)
{
TextureDataWrapper.call(this, width, height);
this.data_ = new Uint8Array(width * height);
}
L8DataWrapper.prototype = new TextureDataWrapper;
L8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
this.data_[this.width_ * y + x] = r;
};
function LA8DataWrapper(width, height)
{
TextureDataWrapper.call(this, width, height);
this.data_ = new Uint8Array(2 * width * height);
}
LA8DataWrapper.prototype = new TextureDataWrapper;
LA8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
var index = 2 * (this.width_ * y + x);
this.data_[index] = r;
this.data_[index + 1] = a;
};
//----------------------------------------------------------------------
// Color ramp generation functions
//
function generateOpaqueGrayscaleRamp(wrapper)
{
var width = wrapper.getWidth();
var height = wrapper.getHeight();
for (var x = 0; x < width; ++x) {
var value = Math.round(255.0 * x / width);
for (var y = 0; y < height; ++y)
wrapper.setPixel(x, y, value, value, value, 255);
}
}
function generateTranslucentGrayscaleRamp(wrapper)
{
var width = wrapper.getWidth();
var height = wrapper.getHeight();
for (var x = 0; x < width; ++x) {
var value = Math.round(255.0 * x / width);
for (var y = 0; y < height; ++y)
wrapper.setPixel(x, y, value, value, value, value);
}
}
function generateTransparentGrayscaleRamp(wrapper)
{
var width = wrapper.getWidth();
var height = wrapper.getHeight();
for (var x = 0; x < width; ++x) {
var value = Math.round(255.0 * x / width);
for (var y = 0; y < height; ++y)
wrapper.setPixel(x, y, value, value, value, 0);
}
}
//----------------------------------------------------------------------
// Verification routines
//
function allChannelsIncreaseByNoMoreThan(array, threshold, numOccurrences) {
var numFound = 0;
for (var i = 4; i < array.length; i += 4)
for (var j = 0; j < 4; j++)
if (array[i + j] - array[i + j - 4] > threshold)
++numFound;
return numFound < numOccurrences;
}
function alphaChannelIncreasesByNoMoreThan(array, threshold, numOccurrences) {
var numFound = 0;
for (var i = 7; i < array.length; i += 4)
if (array[i] - array[i - 4] > threshold)
++numFound;
return numFound < numOccurrences;
}
function allChannelsIncreaseByAtLeast(array, threshold, numOccurrences) {
var numFound = 0;
for (var i = 4; i < array.length; i += 4)
for (var j = 0; j < 4; ++j)
if (array[i + j] - array[i + j - 4] > threshold)
++numFound;
return numFound > numOccurrences;
}
function colorChannelsAreZero(array, threshold, numOccurrences) {
var passed = true;
var numFailures = 0;
for (var i = 4; i < array.length; i += 4)
for (var j = 0; j < 3; ++j)
if (array[i + j] != 0) {
passed = false;
if (++numFailures <= 5)
debug(" array[" + (i + j) + "] should have been 0, was " + array[i + j]);
}
return passed;
}
</script>
</head>
<body onload="init()">
<canvas id="example" width="256" height="1"></canvas>
<div id="description"></div>
<div id="console"></div>
</body>
</html>