diff options
Diffstat (limited to '')
-rw-r--r-- | dom/canvas/test/webgl-conf/checkout/conformance/extensions/webgl-compressed-texture-s3tc-srgb.html | 912 |
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> |