summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/conformance/extensions/webgl-compressed-texture-s3tc-srgb.html
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance/extensions/webgl-compressed-texture-s3tc-srgb.html912
1 files changed, 912 insertions, 0 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance/extensions/webgl-compressed-texture-s3tc-srgb.html b/dom/canvas/test/webgl-conf/checkout/conformance/extensions/webgl-compressed-texture-s3tc-srgb.html
new file mode 100644
index 0000000000..91fc3f0b1c
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance/extensions/webgl-compressed-texture-s3tc-srgb.html
@@ -0,0 +1,912 @@
+<!--
+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">
+<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>
+<script src="../../js/tests/compressed-texture-utils.js"></script>
+<title>WebGL WEBGL_compressed_texture_s3tc_srgb Conformance Tests</title>
+<style>
+img {
+ border: 1px solid black;
+ margin-right: 1em;
+}
+
+.testimages br {
+ clear: both;
+}
+
+.testimages > div {
+ float: left;
+ margin: 1em;
+}
+</style>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_compressed_texture_s3tc_srgb extension, if it is available.");
+
+debug("");
+
+/*
+These tests use the same payloads as a non-sRGB version. When running side-by-side,
+images from these tests must appear much darker than linear counterparts.
+*/
+
+/*
+BC1 (DXT1) block
+e0 = [ 0, 255, 0]
+e1 = [255, 0, 0]
+e0 < e1, so it uses 3-color mode
+
+local palette
+ 0: [ 0, 255, 0, 255]
+ 1: [255, 0, 0, 255]
+ 2: [128, 128, 0, 255]
+ 3: [ 0, 0, 0, 255] // for BC1 RGB
+ 3: [ 0, 0, 0, 0] // for BC1 RGBA
+selectors
+ 3 2 1 0
+ 2 2 1 0
+ 1 1 1 0
+ 0 0 0 0
+
+Extending this block with opaque alpha and uploading as BC2 or BC3
+will generate wrong colors because BC2 and BC3 do not have 3-color mode.
+*/
+var img_4x4_rgba_dxt1 = new Uint8Array([
+ 0xE0, 0x07, 0x00, 0xF8, 0x1B, 0x1A, 0x15, 0x00
+]);
+
+/*
+BC2 (DXT3) block
+
+Quantized alpha values
+ 0 1 2 3
+ 4 5 6 7
+ 8 9 A B
+ C D E F
+
+RGB block
+e0 = [255, 0, 0]
+e1 = [ 0, 255, 0]
+BC2 has only 4-color mode
+
+local palette
+ 0: [255, 0, 0]
+ 1: [ 0, 255, 0]
+ 2: [170, 85, 0]
+ 3: [ 85, 170, 0]
+selectors
+ 0 1 2 3
+ 1 1 2 3
+ 2 2 2 3
+ 3 3 3 3
+*/
+var img_4x4_rgba_dxt3 = new Uint8Array([
+ 0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE,
+ 0x00, 0xF8, 0xE0, 0x07, 0xE4, 0xE5, 0xEA, 0xFF
+]);
+
+/*
+BC3 (DXT5) block
+
+Alpha block (aka DXT5A)
+e0 = 255
+e1 = 0
+e0 > e1, so using 6 intermediate points
+local palette
+ 255, 0, 219, 182, 146, 109, 73, 36
+selectors
+ 0 1 2 3
+ 1 2 3 4
+ 2 3 4 5
+ 3 4 5 6
+
+RGB block
+e0 = [255, 0, 0]
+e1 = [ 0, 255, 0]
+BC3 has only 4-color mode
+
+local palette
+ 0: [255, 0, 0]
+ 1: [ 0, 255, 0]
+ 2: [170, 85, 0]
+ 3: [ 85, 170, 0]
+selectors
+ 3 2 1 0
+ 3 2 1 1
+ 3 2 2 2
+ 3 3 3 3
+*/
+var img_4x4_rgba_dxt5 = new Uint8Array([
+ 0xFF, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
+ 0x00, 0xF8, 0xE0, 0x07, 0x1B, 0x5B, 0xAB, 0xFF
+]);
+
+/*
+8x8 block endpoints use half-intensity values (appear darker than 4x4)
+*/
+var img_8x8_rgba_dxt1 = new Uint8Array([
+ 0xe0,0x03,0x00,0x78,0x13,0x10,0x15,0x00,
+ 0x0f,0x00,0xe0,0x7b,0x11,0x10,0x15,0x00,
+ 0xe0,0x03,0x0f,0x78,0x44,0x45,0x40,0x55,
+ 0x0f,0x00,0xef,0x03,0x44,0x45,0x40,0x55
+]);
+var img_8x8_rgba_dxt3 = new Uint8Array([
+ 0xf6,0xff,0xf6,0xff,0xff,0xff,0xff,0xff,0x00,0x78,0xe0,0x03,0x44,0x45,0x40,0x55,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x7b,0x0f,0x00,0x44,0x45,0x40,0x55,
+ 0xff,0xff,0xff,0xff,0xf6,0xff,0xf6,0xff,0x0f,0x78,0xe0,0x03,0x11,0x10,0x15,0x00,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xef,0x03,0x0f,0x00,0x11,0x10,0x15,0x00
+]);
+var img_8x8_rgba_dxt5 = new Uint8Array([
+0xff,0x69,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x78,0xe0,0x03,0x44,0x45,0x40,0x55,
+ 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x7b,0x0f,0x00,0x44,0x45,0x40,0x55,
+ 0xff,0x69,0x00,0x00,0x00,0x01,0x10,0x00,0x0f,0x78,0xe0,0x03,0x11,0x10,0x15,0x00,
+ 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0x03,0xef,0x00,0x11,0x10,0x15,0x00
+]);
+
+var wtu = WebGLTestUtils;
+var ctu = CompressedTextureUtils;
+var contextVersion = wtu.getDefault3DContextVersion();
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, {antialias: false});
+var program = wtu.setupTexturedQuad(gl);
+var ext = null;
+var vao = null;
+var validFormats = {
+ COMPRESSED_SRGB_S3TC_DXT1_EXT : 0x8C4C,
+ COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT : 0x8C4D,
+ COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT : 0x8C4E,
+ COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : 0x8C4F,
+};
+var name;
+var supportedFormats;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ // Run tests with extension disabled
+ ctu.testCompressedFormatsUnavailableWhenExtensionDisabled(gl, validFormats, expectedByteLength, 4);
+
+ // Query the extension and store globally so shouldBe can access it
+ ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_s3tc_srgb");
+ if (!ext) {
+ testPassed("No WEBGL_compressed_texture_s3tc_srgb support -- this is legal");
+ wtu.runExtensionSupportedTest(gl, "WEBGL_compressed_texture_s3tc_srgb", false);
+ } else {
+ testPassed("Successfully enabled WEBGL_compressed_texture_s3tc_srgb extension");
+
+ wtu.runExtensionSupportedTest(gl, "WEBGL_compressed_texture_s3tc_srgb", true);
+ runTestExtension();
+ }
+}
+
+function expectedByteLength(width, height, format) {
+ if (format == validFormats.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT || format == validFormats.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
+ return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16;
+ }
+ return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;
+}
+
+function getBlockDimensions(format) {
+ return {width: 4, height: 4};
+}
+
+function runTestExtension() {
+ debug("");
+ debug("Testing WEBGL_compressed_texture_s3tc_srgb");
+
+ // Test that enum values are listed correctly in supported formats and in the extension object.
+ ctu.testCompressedFormatsListed(gl, validFormats);
+ ctu.testCorrectEnumValuesInExt(ext, validFormats);
+ // Test that texture upload buffer size is validated correctly.
+ ctu.testFormatRestrictionsOnBufferSize(gl, validFormats, expectedByteLength, getBlockDimensions);
+
+ // Test each format
+ testDXT1_SRGB();
+ testDXT1_SRGB_ALPHA();
+ testDXT3_SRGB_ALPHA();
+ testDXT5_SRGB_ALPHA();
+
+ // Test compressed PBOs with a single format
+ if (contextVersion >= 2) {
+ testDXT5_SRGB_ALPHA_PBO();
+ }
+
+ // Test TexImage validation on level dimensions combinations.
+ debug("");
+ debug("When level equals 0, width and height must be a multiple of 4.");
+ debug("When level is larger than 0, this constraint doesn't apply.");
+ ctu.testTexImageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
+ [
+ { level: 0, width: 4, height: 3, expectation: gl.INVALID_OPERATION, message: "0: 4x3" },
+ { level: 0, width: 3, height: 4, expectation: gl.INVALID_OPERATION, message: "0: 3x4" },
+ { level: 0, width: 2, height: 2, expectation: gl.INVALID_OPERATION, message: "0: 2x2" },
+ { level: 0, width: 4, height: 4, expectation: gl.NO_ERROR, message: "0: 4x4" },
+ { level: 1, width: 2, height: 2, expectation: gl.NO_ERROR, message: "1: 2x2" },
+ { level: 2, width: 1, height: 1, expectation: gl.NO_ERROR, message: "2: 1x1" },
+ ]);
+
+ ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 16, 16,
+ [
+ { xoffset: 0, yoffset: 0, width: 4, height: 3,
+ expectation: gl.INVALID_OPERATION, message: "height is not a multiple of 4" },
+ { xoffset: 0, yoffset: 0, width: 3, height: 4,
+ expectation: gl.INVALID_OPERATION, message: "width is not a multiple of 4" },
+ { xoffset: 1, yoffset: 0, width: 4, height: 4,
+ expectation: gl.INVALID_OPERATION, message: "xoffset is not a multiple of 4" },
+ { xoffset: 0, yoffset: 1, width: 4, height: 4,
+ expectation: gl.INVALID_OPERATION, message: "yoffset is not a multiple of 4" },
+ { xoffset: 12, yoffset: 12, width: 4, height: 4,
+ expectation: gl.NO_ERROR, message: "is valid" },
+ ]);
+
+ if (contextVersion >= 2) {
+ debug("");
+ debug("Testing NPOT textures");
+ ctu.testTexImageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
+ [
+ { level: 0, width: 12, height: 12, expectation: gl.NO_ERROR, message: "0: 12x12 is valid" },
+ { level: 1, width: 6, height: 6, expectation: gl.NO_ERROR, message: "1: 6x6, is valid" },
+ { level: 2, width: 3, height: 3, expectation: gl.NO_ERROR, message: "2: 3x3, is valid" },
+ { level: 3, width: 1, height: 1, expectation: gl.NO_ERROR, message: "3: 1x1, is valid" },
+ ]);
+
+ debug("");
+ debug("Testing partial updates");
+ ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 12, 12,
+ [
+ { xoffset: 0, yoffset: 0, width: 4, height: 3,
+ expectation: gl.INVALID_OPERATION, message: "height is not a multiple of 4" },
+ { xoffset: 0, yoffset: 0, width: 3, height: 4,
+ expectation: gl.INVALID_OPERATION, message: "width is not a multiple of 4" },
+ { xoffset: 1, yoffset: 0, width: 4, height: 4,
+ expectation: gl.INVALID_OPERATION, message: "xoffset is not a multiple of 4" },
+ { xoffset: 0, yoffset: 1, width: 4, height: 4,
+ expectation: gl.INVALID_OPERATION, message: "yoffset is not a multiple of 4" },
+ { xoffset: 8, yoffset: 8, width: 4, height: 4,
+ expectation: gl.NO_ERROR, message: "is valid" },
+ ]);
+
+ debug("");
+ debug("Testing immutable NPOT textures");
+ ctu.testTexStorageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
+ [
+ { width: 12, height: 12, expectation: gl.NO_ERROR, message: "0: 12x12 is valid" },
+ { width: 6, height: 6, expectation: gl.NO_ERROR, message: "1: 6x6, is valid" },
+ { width: 3, height: 3, expectation: gl.NO_ERROR, message: "2: 3x3, is valid" },
+ { width: 1, height: 1, expectation: gl.NO_ERROR, message: "3: 1x1, is valid" },
+ ]);
+ }
+}
+
+function testDXT1_SRGB() {
+ var tests = [
+ { width: 4,
+ height: 4,
+ channels: 3,
+ data: img_4x4_rgba_dxt1,
+ format: ext.COMPRESSED_SRGB_S3TC_DXT1_EXT,
+ hasAlpha: false,
+ },
+ { width: 8,
+ height: 8,
+ channels: 3,
+ data: img_8x8_rgba_dxt1,
+ format: ext.COMPRESSED_SRGB_S3TC_DXT1_EXT,
+ hasAlpha: false,
+ subX0: 0,
+ subY0: 0,
+ subWidth: 4,
+ subHeight: 4,
+ subData: img_4x4_rgba_dxt1
+ }
+ ];
+ testDXTTextures(tests);
+}
+
+function testDXT1_SRGB_ALPHA() {
+ var tests = [
+ { width: 4,
+ height: 4,
+ channels: 4,
+ data: img_4x4_rgba_dxt1,
+ format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT,
+ // This is a special case -- the texture is still opaque
+ // though it's RGBA.
+ hasAlpha: false,
+ },
+ { width: 8,
+ height: 8,
+ channels: 4,
+ data: img_8x8_rgba_dxt1,
+ format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT,
+ // This is a special case -- the texture is still opaque
+ // though it's RGBA.
+ hasAlpha: false,
+ }
+ ];
+ testDXTTextures(tests);
+}
+
+function testDXT3_SRGB_ALPHA() {
+ var tests = [
+ { width: 4,
+ height: 4,
+ channels: 4,
+ data: img_4x4_rgba_dxt3,
+ format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT,
+ hasAlpha: true,
+ },
+ { width: 8,
+ height: 8,
+ channels: 4,
+ data: img_8x8_rgba_dxt3,
+ format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT,
+ hasAlpha: true,
+ subX0: 0,
+ subY0: 0,
+ subWidth: 4,
+ subHeight: 4,
+ subData: img_4x4_rgba_dxt3
+ }
+ ];
+ testDXTTextures(tests);
+}
+
+function testDXT5_SRGB_ALPHA() {
+ var tests = [
+ { width: 4,
+ height: 4,
+ channels: 4,
+ data: img_4x4_rgba_dxt5,
+ format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT,
+ hasAlpha: true,
+ },
+ { width: 8,
+ height: 8,
+ channels: 4,
+ data: img_8x8_rgba_dxt5,
+ format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT,
+ hasAlpha: true,
+ subX0: 0,
+ subY0: 0,
+ subWidth: 4,
+ subHeight: 4,
+ subData: img_4x4_rgba_dxt5
+ }
+ ];
+ testDXTTextures(tests);
+}
+
+function testDXTTextures(tests) {
+ debug("<hr/>");
+ for (var ii = 0; ii < tests.length; ++ii) {
+ testDXTTexture(tests[ii], false);
+ if (contextVersion >= 2) {
+ debug("<br/>");
+ testDXTTexture(tests[ii], true);
+ }
+ }
+}
+
+function uncompressDXTBlockSRGB(
+ destBuffer, destX, destY, destWidth, src, srcOffset, format) {
+ // Decoding routines follow D3D11 functional spec wrt
+ // endpoints unquantization and interpolation.
+ // Some hardware may produce slightly different values - it's normal.
+
+ function make565(src, offset) {
+ return src[offset + 0] + (src[offset + 1] << 8);
+ }
+ function make8888From565(c) {
+ // These values exactly match hw decoder when selectors are 0 or 1.
+ function replicateBits(v, w) {
+ return (v << (8 - w)) | (v >> (w + w - 8));
+ }
+ return [
+ replicateBits((c >> 11) & 0x1F, 5),
+ replicateBits((c >> 5) & 0x3F, 6),
+ replicateBits((c >> 0) & 0x1F, 5),
+ 255
+ ];
+ }
+ function mix(mult, c0, c1, div) {
+ var r = [];
+ for (var ii = 0; ii < c0.length; ++ii) {
+ // For green channel (6 bits), this interpolation exactly matches hw decoders
+
+ // For red and blue channels (5 bits), this interpolation exactly
+ // matches only some hw decoders and stays within acceptable range for others.
+ r[ii] = Math.floor((c0[ii] * mult + c1[ii]) / div + 0.5);
+ }
+ return r;
+ }
+ var isDXT1 = format == ext.COMPRESSED_SRGB_S3TC_DXT1_EXT ||
+ format == ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
+ var colorOffset = srcOffset + (isDXT1 ? 0 : 8);
+ var color0 = make565(src, colorOffset + 0);
+ var color1 = make565(src, colorOffset + 2);
+ var c0gtc1 = color0 > color1 || !isDXT1;
+ var rgba0 = make8888From565(color0);
+ var rgba1 = make8888From565(color1);
+ var colors = [
+ rgba0,
+ rgba1,
+ c0gtc1 ? mix(2, rgba0, rgba1, 3) : mix(1, rgba0, rgba1, 2),
+ c0gtc1 ? mix(2, rgba1, rgba0, 3) : [0, 0, 0, 255]
+ ];
+
+ // yea I know there is a lot of math in this inner loop.
+ // so sue me.
+ for (var yy = 0; yy < 4; ++yy) {
+ var pixels = src[colorOffset + 4 + yy];
+ for (var xx = 0; xx < 4; ++xx) {
+ var dstOff = ((destY + yy) * destWidth + destX + xx) * 4;
+ var code = (pixels >> (xx * 2)) & 0x3;
+ var srcColor = colors[code];
+ var alpha;
+ switch (format) {
+ case ext.COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ alpha = 255;
+ break;
+ case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ alpha = (code == 3 && !c0gtc1) ? 0 : 255;
+ break;
+ case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ {
+ var alpha0 = src[srcOffset + yy * 2 + (xx >> 1)];
+ var alpha1 = (alpha0 >> ((xx % 2) * 4)) & 0xF;
+ alpha = alpha1 | (alpha1 << 4);
+ }
+ break;
+ case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ {
+ var alpha0 = src[srcOffset + 0];
+ var alpha1 = src[srcOffset + 1];
+ var alphaOff = (yy >> 1) * 3 + 2;
+ var alphaBits =
+ src[srcOffset + alphaOff + 0] +
+ src[srcOffset + alphaOff + 1] * 256 +
+ src[srcOffset + alphaOff + 2] * 65536;
+ var alphaShift = (yy % 2) * 12 + xx * 3;
+ var alphaCode = (alphaBits >> alphaShift) & 0x7;
+ if (alpha0 > alpha1) {
+ switch (alphaCode) {
+ case 0:
+ alpha = alpha0;
+ break;
+ case 1:
+ alpha = alpha1;
+ break;
+ default:
+ alpha = Math.floor(((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 7.0 + 0.5);
+ break;
+ }
+ } else {
+ switch (alphaCode) {
+ case 0:
+ alpha = alpha0;
+ break;
+ case 1:
+ alpha = alpha1;
+ break;
+ case 6:
+ alpha = 0;
+ break;
+ case 7:
+ alpha = 255;
+ break;
+ default:
+ alpha = Math.floor(((6 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 5.0 + 0.5);
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ throw "bad format";
+ }
+ destBuffer[dstOff + 0] = sRGBChannelToLinear(srcColor[0]);
+ destBuffer[dstOff + 1] = sRGBChannelToLinear(srcColor[1]);
+ destBuffer[dstOff + 2] = sRGBChannelToLinear(srcColor[2]);
+ destBuffer[dstOff + 3] = alpha;
+ }
+ }
+}
+
+function getBlockSize(format) {
+ var isDXT1 = format == ext.COMPRESSED_SRGB_S3TC_DXT1_EXT ||
+ format == ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
+ return isDXT1 ? 8 : 16;
+}
+
+function uncompressDXTSRGB(width, height, data, format) {
+ if (width % 4 || height % 4) throw "bad width or height";
+
+ var dest = new Uint8Array(width * height * 4);
+ var blocksAcross = width / 4;
+ var blocksDown = height / 4;
+ var blockSize = getBlockSize(format);
+ for (var yy = 0; yy < blocksDown; ++yy) {
+ for (var xx = 0; xx < blocksAcross; ++xx) {
+ uncompressDXTBlockSRGB(
+ dest, xx * 4, yy * 4, width, data,
+ (yy * blocksAcross + xx) * blockSize, format);
+ }
+ }
+ return dest;
+}
+
+function uncompressDXTIntoSubRegionSRGB(width, height, subX0, subY0, subWidth, subHeight, data, format)
+{
+ if (width % 4 || height % 4 || subX0 % 4 || subY0 % 4 || subWidth % 4 || subHeight % 4)
+ throw "bad dimension";
+
+ var dest = new Uint8Array(width * height * 4);
+ // Zero-filled DXT1 texture represents [0, 0, 0, 255]
+ if (format == ext.COMPRESSED_SRGB_S3TC_DXT1_EXT || format == ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
+ for (var i = 3; i < dest.length; i += 4) dest[i] = 255;
+ }
+ var blocksAcross = subWidth / 4;
+ var blocksDown = subHeight / 4;
+ var blockSize = getBlockSize(format);
+ for (var yy = 0; yy < blocksDown; ++yy) {
+ for (var xx = 0; xx < blocksAcross; ++xx) {
+ uncompressDXTBlockSRGB(
+ dest, subX0 + xx * 4, subY0 + yy * 4, width, data,
+ (yy * blocksAcross + xx) * blockSize, format);
+ }
+ }
+ return dest;
+}
+
+function copyRect(data, srcX, srcY, dstX, dstY, width, height, stride) {
+ var bytesPerLine = width * 4;
+ var srcOffset = srcX * 4 + srcY * stride;
+ var dstOffset = dstX * 4 + dstY * stride;
+ for (; height > 0; --height) {
+ for (var ii = 0; ii < bytesPerLine; ++ii) {
+ data[dstOffset + ii] = data[srcOffset + ii];
+ }
+ srcOffset += stride;
+ dstOffset += stride;
+ }
+}
+
+function testDXTTexture(test, useTexStorage) {
+ var data = new Uint8Array(test.data);
+ var width = test.width;
+ var height = test.height;
+ var format = test.format;
+
+ var uncompressedData = uncompressDXTSRGB(width, height, data, format);
+
+ canvas.width = width;
+ canvas.height = height;
+ gl.viewport(0, 0, width, height);
+ debug("testing " + ctu.formatToString(ext, format) + " " + width + "x" + height +
+ (useTexStorage ? " via texStorage2D" : " via compressedTexImage2D"));
+
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ if (useTexStorage) {
+ if (test.subData) {
+ var uncompressedDataSub = uncompressDXTIntoSubRegionSRGB(
+ width, height, test.subX0, test.subY0, test.subWidth, test.subHeight, test.subData, format);
+ var tex1 = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex1);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+
+ gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "allocating compressed texture via texStorage2D");
+ gl.compressedTexSubImage2D(
+ gl.TEXTURE_2D, 0, test.subX0, test.subY0, test.subWidth, test.subHeight, format, test.subData);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture data via compressedTexSubImage2D");
+
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 1");
+ compareRect(width, height, test.channels, uncompressedDataSub, "NEAREST");
+
+ // Clean up and recover
+ gl.deleteTexture(tex1);
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ }
+
+ gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "allocating compressed texture via texStorage2D");
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
+ var clearColor = (test.hasAlpha ? [0, 0, 0, 0] : [0, 0, 0, 255]);
+ wtu.checkCanvas(gl, clearColor, "texture should be initialized to black");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture data via compressedTexSubImage2D");
+ } else {
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+ }
+ gl.generateMipmap(gl.TEXTURE_2D);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "trying to generate mipmaps from compressed texture");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after clearing generateMipmap error");
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 1");
+ compareRect(width, height, test.channels, uncompressedData, "NEAREST");
+ // Test again with linear filtering.
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 2");
+ compareRect(width, height, test.channels, uncompressedData, "LINEAR");
+
+ if (!useTexStorage) {
+ // It's not allowed to redefine textures defined via texStorage2D.
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 1, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "non 0 border");
+
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width + 4, height, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height + 4, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 4, height, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 4, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 1, height, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 2, height, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 1, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 2, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+
+ if (width == 4) {
+ // The width/height of the implied base level must be a multiple of the block size.
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 1, height, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions for level > 0");
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 2, height, 0, data);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+ }
+ if (height == 4) {
+ // The width/height of the implied base level must be a multiple of the block size.
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 1, 0, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions for level > 0");
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 2, 0, data);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+ }
+ }
+
+ // pick a wrong format that uses the same amount of data.
+ var wrongFormat;
+ switch (format) {
+ case ext.COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ wrongFormat = ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
+ break;
+ case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ wrongFormat = ext.COMPRESSED_SRGB_S3TC_DXT1_EXT;
+ break;
+ case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ wrongFormat = ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+ break;
+ case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ wrongFormat = ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
+ break;
+ }
+
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, wrongFormat, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "format does not match");
+
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 4, 0, width, height, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "dimension out of range");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 4, width, height, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "dimension out of range");
+
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width + 4, height, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height + 4, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 4, height, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 4, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 1, height, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 2, height, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 1, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 2, format, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+
+ var subData = new Uint8Array(data.buffer, 0, getBlockSize(format));
+
+ if (width == 8 && height == 8) {
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 1, 0, 4, 4, format, subData);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid offset");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 1, 4, 4, format, subData);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid offset");
+ }
+
+ var stride = width * 4;
+ for (var yoff = 0; yoff < height; yoff += 4) {
+ for (var xoff = 0; xoff < width; xoff += 4) {
+ copyRect(uncompressedData, 0, 0, xoff, yoff, 4, 4, stride);
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, xoff, yoff, 4, 4, format, subData);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+ // First test NEAREST filtering.
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ wtu.clearAndDrawUnitQuad(gl);
+ compareRect(width, height, test.channels, uncompressedData, "NEAREST");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
+ // Next test LINEAR filtering.
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
+ compareRect(width, height, test.channels, uncompressedData, "LINEAR");
+ }
+ }
+}
+
+function testDXT5_SRGB_ALPHA_PBO() {
+ debug("");
+ debug("testing PBO uploads");
+ var width = 8;
+ var height = 8;
+ var channels = 4;
+ var data = img_8x8_rgba_dxt5;
+ var format = ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+ var uncompressedData = uncompressDXTSRGB(width, height, data, format);
+
+ var tex = gl.createTexture();
+
+ // First, PBO size = image size
+ var pbo1 = gl.createBuffer();
+ gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo1);
+ gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data, gl.STATIC_DRAW);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a PBO");
+
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data.length, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a texture from a PBO");
+
+ gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
+ compareRect(width, height, channels, uncompressedData, "NEAREST");
+
+ // Clear the texture before the next test
+ gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, new Uint8Array(data.length));
+
+ // Second, image is just a subrange of the PBO
+ var pbo2 = gl.createBuffer();
+ gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo2);
+ gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data.length*3, gl.STATIC_DRAW);
+ gl.bufferSubData(gl.PIXEL_UNPACK_BUFFER, data.length, data);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a PBO subrange");
+ gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data.length, data.length);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a texture from a PBO subrange");
+ gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
+ compareRect(width, height, channels, uncompressedData, "NEAREST");
+}
+
+// See EXT_texture_sRGB, Section 3.8.x, sRGB Texture Color Conversion.
+function sRGBChannelToLinear(value) {
+ value = value / 255;
+ if (value <= 0.04045) {
+ value = value / 12.92;
+ } else {
+ value = Math.pow((value + 0.055) / 1.055, 2.4);
+ }
+ return Math.trunc(value * 255 + 0.5);
+}
+
+function compareRect(width, height, channels, expectedData, filteringMode) {
+ var actual = new Uint8Array(width * height * 4);
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, actual);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "reading back pixels");
+
+ var div = document.createElement("div");
+ div.className = "testimages";
+ ctu.insertCaptionedImg(div, "expected", ctu.makeScaledImage(width, height, width, expectedData, true));
+ ctu.insertCaptionedImg(div, "actual", ctu.makeScaledImage(width, height, width, actual, true));
+ div.appendChild(document.createElement('br'));
+ document.getElementById("console").appendChild(div);
+
+ var failed = false;
+ for (var yy = 0; yy < height; ++yy) {
+ for (var xx = 0; xx < width; ++xx) {
+ var offset = (yy * width + xx) * 4;
+ var expected = expectedData.slice(offset, offset + 4);
+ // Compare RGB values
+ for (var jj = 0; jj < 3; ++jj) {
+ // Acceptable interpolation error depends on endpoints:
+ // 1.0 / 255.0 + 0.03 * max(abs(endpoint0 - endpoint1), abs(endpoint0_p - endpoint1_p))
+ // For simplicity, assume the worst case (e0 is 0.0, e1 is 1.0). After conversion to unorm8, it is 9.
+ if (Math.abs(actual[offset + jj] - expected[jj]) > 9) {
+ var was = actual[offset + 0].toString();
+ for (var j = 1; j < 3; ++j) {
+ was += "," + actual[offset + j];
+ }
+ failed = true;
+ testFailed('RGB at (' + xx + ', ' + yy +
+ ') expected: ' + expected + ' ± 9 was ' + was);
+ }
+ }
+
+ if (channels == 3) {
+ // BC1 RGB is allowed to be mapped to BC1 RGBA.
+ // In such a case, 3-color mode black value can be transparent:
+ // [0, 0, 0, 0] instead of [0, 0, 0, 255].
+
+ if (actual[offset + 3] != expected[3]) {
+ // Got non-opaque value for opaque format
+
+ // Check RGB values. Notice, that the condition here
+ // is more permissive than needed since we don't have
+ // compressed data at this point.
+ if (actual[offset] == 0 &&
+ actual[offset + 1] == 0 &&
+ actual[offset + 2] == 0 &&
+ actual[offset + 3] == 0) {
+ debug("<b>DXT1 SRGB is mapped to DXT1 SRGB ALPHA</b>");
+ } else {
+ failed = true;
+ testFailed('Alpha at (' + xx + ', ' + yy +
+ ') expected: ' + expected[3] + ' was ' + actual[offset + 3]);
+ }
+ }
+ } else {
+ // Compare Alpha values
+ // Acceptable interpolation error depends on endpoints:
+ // 1.0 / 65535.0 + 0.03 * max(abs(endpoint0 - endpoint1), abs(endpoint0_p - endpoint1_p))
+ // For simplicity, assume the worst case (e0 is 0.0, e1 is 1.0). After conversion to unorm8, it is 8.
+ if (Math.abs(actual[offset + 3] - expected[3]) > 8) {
+ var was = actual[offset + 3].toString();
+ failed = true;
+ testFailed('Alpha at (' + xx + ', ' + yy +
+ ') expected: ' + expected + ' ± 8 was ' + was);
+ }
+ }
+ }
+ }
+ if (!failed) {
+ testPassed("texture rendered correctly with " + filteringMode + " filtering");
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>