tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

webgl-compressed-texture-s3tc-srgb.html (36965B)


      1 <!--
      2 Copyright (c) 2019 The Khronos Group Inc.
      3 Use of this source code is governed by an MIT-style license that can be
      4 found in the LICENSE.txt file.
      5 -->
      6 
      7 <!DOCTYPE html>
      8 <html>
      9 <head>
     10 <meta charset="utf-8">
     11 <link rel="stylesheet" href="../../resources/js-test-style.css"/>
     12 <script src="../../js/js-test-pre.js"></script>
     13 <script src="../../js/webgl-test-utils.js"></script>
     14 <script src="../../js/tests/compressed-texture-utils.js"></script>
     15 <title>WebGL WEBGL_compressed_texture_s3tc_srgb Conformance Tests</title>
     16 <style>
     17 img {
     18 border: 1px solid black;
     19 margin-right: 1em;
     20 }
     21 
     22 .testimages br {
     23  clear: both;
     24 }
     25 
     26 .testimages > div {
     27  float: left;
     28  margin: 1em;
     29 }
     30 </style>
     31 </head>
     32 <body>
     33 <div id="description"></div>
     34 <canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
     35 <div id="console"></div>
     36 <script>
     37 "use strict";
     38 description("This test verifies the functionality of the WEBGL_compressed_texture_s3tc_srgb extension, if it is available.");
     39 
     40 debug("");
     41 
     42 /*
     43 These tests use the same payloads as a non-sRGB version. When running side-by-side,
     44 images from these tests must appear much darker than linear counterparts.
     45 */
     46 
     47 /*
     48 BC1 (DXT1) block
     49 e0 = [  0, 255, 0]
     50 e1 = [255,   0, 0]
     51 e0 < e1, so it uses 3-color mode
     52 
     53 local palette
     54    0: [  0, 255, 0, 255]
     55    1: [255,   0, 0, 255]
     56    2: [128, 128, 0, 255]
     57    3: [  0,   0, 0, 255] // for BC1 RGB
     58    3: [  0,   0, 0,   0] // for BC1 RGBA
     59 selectors
     60    3 2 1 0
     61    2 2 1 0
     62    1 1 1 0
     63    0 0 0 0
     64 
     65 Extending this block with opaque alpha and uploading as BC2 or BC3
     66 will generate wrong colors because BC2 and BC3 do not have 3-color mode.
     67 */
     68 var img_4x4_rgba_dxt1 = new Uint8Array([
     69    0xE0, 0x07, 0x00, 0xF8, 0x1B, 0x1A, 0x15, 0x00
     70 ]);
     71 
     72 /*
     73 BC2 (DXT3) block
     74 
     75 Quantized alpha values
     76    0 1 2 3
     77    4 5 6 7
     78    8 9 A B
     79    C D E F
     80 
     81 RGB block
     82 e0 = [255,   0, 0]
     83 e1 = [  0, 255, 0]
     84 BC2 has only 4-color mode
     85 
     86 local palette
     87    0: [255,   0, 0]
     88    1: [  0, 255, 0]
     89    2: [170,  85, 0]
     90    3: [ 85, 170, 0]
     91 selectors
     92    0 1 2 3
     93    1 1 2 3
     94    2 2 2 3
     95    3 3 3 3
     96 */
     97 var img_4x4_rgba_dxt3 = new Uint8Array([
     98    0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE,
     99    0x00, 0xF8, 0xE0, 0x07, 0xE4, 0xE5, 0xEA, 0xFF
    100 ]);
    101 
    102 /*
    103 BC3 (DXT5) block
    104 
    105 Alpha block (aka DXT5A)
    106 e0 = 255
    107 e1 = 0
    108 e0 > e1, so using 6 intermediate points
    109 local palette
    110    255, 0, 219, 182, 146, 109, 73, 36
    111 selectors
    112    0 1 2 3
    113    1 2 3 4
    114    2 3 4 5
    115    3 4 5 6
    116 
    117 RGB block
    118 e0 = [255,   0, 0]
    119 e1 = [  0, 255, 0]
    120 BC3 has only 4-color mode
    121 
    122 local palette
    123    0: [255,   0, 0]
    124    1: [  0, 255, 0]
    125    2: [170,  85, 0]
    126    3: [ 85, 170, 0]
    127 selectors
    128    3 2 1 0
    129    3 2 1 1
    130    3 2 2 2
    131    3 3 3 3
    132 */
    133 var img_4x4_rgba_dxt5 = new Uint8Array([
    134    0xFF, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    135    0x00, 0xF8, 0xE0, 0x07, 0x1B, 0x5B, 0xAB, 0xFF
    136 ]);
    137 
    138 /*
    139 8x8 block endpoints use half-intensity values (appear darker than 4x4)
    140 */
    141 var img_8x8_rgba_dxt1 = new Uint8Array([
    142    0xe0,0x03,0x00,0x78,0x13,0x10,0x15,0x00,
    143    0x0f,0x00,0xe0,0x7b,0x11,0x10,0x15,0x00,
    144    0xe0,0x03,0x0f,0x78,0x44,0x45,0x40,0x55,
    145    0x0f,0x00,0xef,0x03,0x44,0x45,0x40,0x55
    146 ]);
    147 var img_8x8_rgba_dxt3 = new Uint8Array([
    148    0xf6,0xff,0xf6,0xff,0xff,0xff,0xff,0xff,0x00,0x78,0xe0,0x03,0x44,0x45,0x40,0x55,
    149    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x7b,0x0f,0x00,0x44,0x45,0x40,0x55,
    150    0xff,0xff,0xff,0xff,0xf6,0xff,0xf6,0xff,0x0f,0x78,0xe0,0x03,0x11,0x10,0x15,0x00,
    151    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xef,0x03,0x0f,0x00,0x11,0x10,0x15,0x00
    152 ]);
    153 var img_8x8_rgba_dxt5 = new Uint8Array([
    154 0xff,0x69,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x78,0xe0,0x03,0x44,0x45,0x40,0x55,
    155    0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x7b,0x0f,0x00,0x44,0x45,0x40,0x55,
    156    0xff,0x69,0x00,0x00,0x00,0x01,0x10,0x00,0x0f,0x78,0xe0,0x03,0x11,0x10,0x15,0x00,
    157    0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0x03,0xef,0x00,0x11,0x10,0x15,0x00
    158 ]);
    159 
    160 var wtu = WebGLTestUtils;
    161 var ctu = CompressedTextureUtils;
    162 var contextVersion = wtu.getDefault3DContextVersion();
    163 var canvas = document.getElementById("canvas");
    164 var gl = wtu.create3DContext(canvas, {antialias: false});
    165 var program = wtu.setupTexturedQuad(gl);
    166 var ext = null;
    167 var vao = null;
    168 var validFormats = {
    169    COMPRESSED_SRGB_S3TC_DXT1_EXT       : 0x8C4C,
    170    COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT : 0x8C4D,
    171    COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT : 0x8C4E,
    172    COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : 0x8C4F,
    173 };
    174 var name;
    175 var supportedFormats;
    176 
    177 if (!gl) {
    178    testFailed("WebGL context does not exist");
    179 } else {
    180    testPassed("WebGL context exists");
    181 
    182    // Run tests with extension disabled
    183    ctu.testCompressedFormatsUnavailableWhenExtensionDisabled(gl, validFormats, expectedByteLength, 4);
    184 
    185    // Query the extension and store globally so shouldBe can access it
    186    ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_s3tc_srgb");
    187    if (!ext) {
    188        testPassed("No WEBGL_compressed_texture_s3tc_srgb support -- this is legal");
    189        wtu.runExtensionSupportedTest(gl, "WEBGL_compressed_texture_s3tc_srgb", false);
    190    } else {
    191        testPassed("Successfully enabled WEBGL_compressed_texture_s3tc_srgb extension");
    192 
    193        wtu.runExtensionSupportedTest(gl, "WEBGL_compressed_texture_s3tc_srgb", true);
    194        runTestExtension();
    195    }
    196 }
    197 
    198 function expectedByteLength(width, height, format) {
    199    if (format == validFormats.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT || format == validFormats.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
    200        return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16;
    201    }
    202    return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;
    203 }
    204 
    205 function getBlockDimensions(format) {
    206    return {width: 4, height: 4};
    207 }
    208 
    209 function runTestExtension() {
    210    debug("");
    211    debug("Testing WEBGL_compressed_texture_s3tc_srgb");
    212 
    213    // Test that enum values are listed correctly in supported formats and in the extension object.
    214    ctu.testCompressedFormatsListed(gl, validFormats);
    215    ctu.testCorrectEnumValuesInExt(ext, validFormats);
    216    // Test that texture upload buffer size is validated correctly.
    217    ctu.testFormatRestrictionsOnBufferSize(gl, validFormats, expectedByteLength, getBlockDimensions);
    218 
    219    // Test each format
    220    testDXT1_SRGB();
    221    testDXT1_SRGB_ALPHA();
    222    testDXT3_SRGB_ALPHA();
    223    testDXT5_SRGB_ALPHA();
    224 
    225    // Test compressed PBOs with a single format
    226    if (contextVersion >= 2) {
    227        testDXT5_SRGB_ALPHA_PBO();
    228    }
    229 
    230    // Test TexImage validation on level dimensions combinations.
    231    debug("");
    232    debug("When level equals 0, width and height must be a multiple of 4.");
    233    debug("When level is larger than 0, this constraint doesn't apply.");
    234    ctu.testTexImageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
    235      [
    236        { level: 0, width: 4, height: 3, expectation: gl.INVALID_OPERATION, message: "0: 4x3" },
    237        { level: 0, width: 3, height: 4, expectation: gl.INVALID_OPERATION, message: "0: 3x4" },
    238        { level: 0, width: 2, height: 2, expectation: gl.INVALID_OPERATION, message: "0: 2x2" },
    239        { level: 0, width: 4, height: 4, expectation: gl.NO_ERROR, message: "0: 4x4" },
    240        { level: 1, width: 2, height: 2, expectation: gl.NO_ERROR, message: "1: 2x2" },
    241        { level: 2, width: 1, height: 1, expectation: gl.NO_ERROR, message: "2: 1x1" },
    242    ]);
    243 
    244    ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 16, 16,
    245      [
    246        { xoffset: 0, yoffset: 0, width: 4, height: 3,
    247          expectation: gl.INVALID_OPERATION, message: "height is not a multiple of 4" },
    248        { xoffset: 0, yoffset: 0, width: 3, height: 4,
    249          expectation: gl.INVALID_OPERATION, message: "width is not a multiple of 4" },
    250        { xoffset: 1, yoffset: 0, width: 4, height: 4,
    251          expectation: gl.INVALID_OPERATION, message: "xoffset is not a multiple of 4" },
    252        { xoffset: 0, yoffset: 1, width: 4, height: 4,
    253          expectation: gl.INVALID_OPERATION, message: "yoffset is not a multiple of 4" },
    254        { xoffset: 12, yoffset: 12, width: 4, height: 4,
    255          expectation: gl.NO_ERROR, message: "is valid" },
    256    ]);
    257 
    258    if (contextVersion >= 2) {
    259        debug("");
    260        debug("Testing NPOT textures");
    261        ctu.testTexImageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
    262          [
    263            { level: 0, width: 12, height: 12, expectation: gl.NO_ERROR, message: "0: 12x12 is valid" },
    264            { level: 1, width:  6, height:  6, expectation: gl.NO_ERROR, message: "1: 6x6, is valid" },
    265            { level: 2, width:  3, height:  3, expectation: gl.NO_ERROR, message: "2: 3x3, is valid" },
    266            { level: 3, width:  1, height:  1, expectation: gl.NO_ERROR, message: "3: 1x1, is valid" },
    267        ]);
    268 
    269        debug("");
    270        debug("Testing partial updates");
    271        ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 12, 12,
    272        [
    273          { xoffset: 0, yoffset: 0, width: 4, height: 3,
    274            expectation: gl.INVALID_OPERATION, message: "height is not a multiple of 4" },
    275          { xoffset: 0, yoffset: 0, width: 3, height: 4,
    276            expectation: gl.INVALID_OPERATION, message: "width is not a multiple of 4" },
    277          { xoffset: 1, yoffset: 0, width: 4, height: 4,
    278            expectation: gl.INVALID_OPERATION, message: "xoffset is not a multiple of 4" },
    279          { xoffset: 0, yoffset: 1, width: 4, height: 4,
    280            expectation: gl.INVALID_OPERATION, message: "yoffset is not a multiple of 4" },
    281          { xoffset: 8, yoffset: 8, width: 4, height: 4,
    282            expectation: gl.NO_ERROR, message: "is valid" },
    283        ]);
    284 
    285        debug("");
    286        debug("Testing immutable NPOT textures");
    287        ctu.testTexStorageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
    288          [
    289            { width: 12, height: 12, expectation: gl.NO_ERROR, message: "0: 12x12 is valid" },
    290            { width:  6, height:  6, expectation: gl.NO_ERROR, message: "1: 6x6, is valid" },
    291            { width:  3, height:  3, expectation: gl.NO_ERROR, message: "2: 3x3, is valid" },
    292            { width:  1, height:  1, expectation: gl.NO_ERROR, message: "3: 1x1, is valid" },
    293        ]);
    294    }
    295 }
    296 
    297 function testDXT1_SRGB() {
    298    var tests = [
    299        {   width: 4,
    300            height: 4,
    301            channels: 3,
    302            data: img_4x4_rgba_dxt1,
    303            format: ext.COMPRESSED_SRGB_S3TC_DXT1_EXT,
    304            hasAlpha: false,
    305        },
    306        {   width: 8,
    307            height: 8,
    308            channels: 3,
    309            data: img_8x8_rgba_dxt1,
    310            format: ext.COMPRESSED_SRGB_S3TC_DXT1_EXT,
    311            hasAlpha: false,
    312            subX0: 0,
    313            subY0: 0,
    314            subWidth: 4,
    315            subHeight: 4,
    316            subData: img_4x4_rgba_dxt1
    317        }
    318    ];
    319    testDXTTextures(tests);
    320 }
    321 
    322 function testDXT1_SRGB_ALPHA() {
    323    var tests = [
    324        {   width: 4,
    325            height: 4,
    326            channels: 4,
    327            data: img_4x4_rgba_dxt1,
    328            format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT,
    329            // This is a special case -- the texture is still opaque
    330            // though it's RGBA.
    331            hasAlpha: false,
    332        },
    333        {   width: 8,
    334            height: 8,
    335            channels: 4,
    336            data: img_8x8_rgba_dxt1,
    337            format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT,
    338            // This is a special case -- the texture is still opaque
    339            // though it's RGBA.
    340            hasAlpha: false,
    341        }
    342    ];
    343    testDXTTextures(tests);
    344 }
    345 
    346 function testDXT3_SRGB_ALPHA() {
    347    var tests = [
    348        {   width: 4,
    349            height: 4,
    350            channels: 4,
    351            data: img_4x4_rgba_dxt3,
    352            format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT,
    353            hasAlpha: true,
    354        },
    355        {   width: 8,
    356            height: 8,
    357            channels: 4,
    358            data: img_8x8_rgba_dxt3,
    359            format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT,
    360            hasAlpha: true,
    361            subX0: 0,
    362            subY0: 0,
    363            subWidth: 4,
    364            subHeight: 4,
    365            subData: img_4x4_rgba_dxt3
    366        }
    367    ];
    368    testDXTTextures(tests);
    369 }
    370 
    371 function testDXT5_SRGB_ALPHA() {
    372    var tests = [
    373        {   width: 4,
    374            height: 4,
    375            channels: 4,
    376            data: img_4x4_rgba_dxt5,
    377            format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT,
    378            hasAlpha: true,
    379        },
    380        {   width: 8,
    381            height: 8,
    382            channels: 4,
    383            data: img_8x8_rgba_dxt5,
    384            format: ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT,
    385            hasAlpha: true,
    386            subX0: 0,
    387            subY0: 0,
    388            subWidth: 4,
    389            subHeight: 4,
    390            subData: img_4x4_rgba_dxt5
    391        }
    392    ];
    393    testDXTTextures(tests);
    394 }
    395 
    396 function testDXTTextures(tests) {
    397    debug("<hr/>");
    398    for (var ii = 0; ii < tests.length; ++ii) {
    399        testDXTTexture(tests[ii], false);
    400        if (contextVersion >= 2) {
    401            debug("<br/>");
    402            testDXTTexture(tests[ii], true);
    403        }
    404    }
    405 }
    406 
    407 function uncompressDXTBlockSRGB(
    408    destBuffer, destX, destY, destWidth, src, srcOffset, format) {
    409    // Decoding routines follow D3D11 functional spec wrt
    410    // endpoints unquantization and interpolation.
    411    // Some hardware may produce slightly different values - it's normal.
    412 
    413    function make565(src, offset) {
    414        return src[offset + 0] + (src[offset + 1] << 8);
    415    }
    416    function make8888From565(c) {
    417        // These values exactly match hw decoder when selectors are 0 or 1.
    418        function replicateBits(v, w) {
    419           return (v << (8 - w)) | (v >> (w + w - 8));
    420        }
    421        return [
    422                replicateBits((c >> 11) & 0x1F, 5),
    423                replicateBits((c >>  5) & 0x3F, 6),
    424                replicateBits((c >>  0) & 0x1F, 5),
    425                255
    426            ];
    427    }
    428    function mix(mult, c0, c1, div) {
    429        var r = [];
    430        for (var ii = 0; ii < c0.length; ++ii) {
    431            // For green channel (6 bits), this interpolation exactly matches hw decoders
    432 
    433            // For red and blue channels (5 bits), this interpolation exactly
    434            // matches only some hw decoders and stays within acceptable range for others.
    435            r[ii] = Math.floor((c0[ii] * mult + c1[ii]) / div + 0.5);
    436        }
    437        return r;
    438    }
    439    var isDXT1 = format == ext.COMPRESSED_SRGB_S3TC_DXT1_EXT ||
    440                 format == ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
    441    var colorOffset = srcOffset + (isDXT1 ? 0 : 8);
    442    var color0 = make565(src, colorOffset + 0);
    443    var color1 = make565(src, colorOffset + 2);
    444    var c0gtc1 = color0 > color1 || !isDXT1;
    445    var rgba0 = make8888From565(color0);
    446    var rgba1 = make8888From565(color1);
    447    var colors = [
    448            rgba0,
    449            rgba1,
    450            c0gtc1 ? mix(2, rgba0, rgba1, 3) : mix(1, rgba0, rgba1, 2),
    451            c0gtc1 ? mix(2, rgba1, rgba0, 3) : [0, 0, 0, 255]
    452        ];
    453 
    454    // yea I know there is a lot of math in this inner loop.
    455    // so sue me.
    456    for (var yy = 0; yy < 4; ++yy) {
    457        var pixels = src[colorOffset + 4 + yy];
    458        for (var xx = 0; xx < 4; ++xx) {
    459            var dstOff = ((destY + yy) * destWidth + destX + xx) * 4;
    460            var code = (pixels >> (xx * 2)) & 0x3;
    461            var srcColor = colors[code];
    462            var alpha;
    463            switch (format) {
    464            case ext.COMPRESSED_SRGB_S3TC_DXT1_EXT:
    465                alpha = 255;
    466                break;
    467            case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
    468                alpha = (code == 3 && !c0gtc1) ? 0 : 255;
    469                break;
    470            case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
    471                {
    472                    var alpha0 = src[srcOffset + yy * 2 + (xx >> 1)];
    473                    var alpha1 = (alpha0 >> ((xx % 2) * 4)) & 0xF;
    474                    alpha = alpha1 | (alpha1 << 4);
    475                }
    476                break;
    477            case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
    478                {
    479                    var alpha0 = src[srcOffset + 0];
    480                    var alpha1 = src[srcOffset + 1];
    481                    var alphaOff = (yy >> 1) * 3 + 2;
    482                    var alphaBits =
    483                         src[srcOffset + alphaOff + 0] +
    484                         src[srcOffset + alphaOff + 1] * 256 +
    485                         src[srcOffset + alphaOff + 2] * 65536;
    486                    var alphaShift = (yy % 2) * 12 + xx * 3;
    487                    var alphaCode = (alphaBits >> alphaShift) & 0x7;
    488                    if (alpha0 > alpha1) {
    489                        switch (alphaCode) {
    490                        case 0:
    491                            alpha = alpha0;
    492                            break;
    493                        case 1:
    494                            alpha = alpha1;
    495                            break;
    496                        default:
    497                            alpha = Math.floor(((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 7.0 + 0.5);
    498                            break;
    499                        }
    500                    } else {
    501                        switch (alphaCode) {
    502                        case 0:
    503                            alpha = alpha0;
    504                            break;
    505                        case 1:
    506                            alpha = alpha1;
    507                            break;
    508                        case 6:
    509                            alpha = 0;
    510                            break;
    511                        case 7:
    512                            alpha = 255;
    513                            break;
    514                        default:
    515                            alpha = Math.floor(((6 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 5.0 + 0.5);
    516                            break;
    517                        }
    518                    }
    519                }
    520                break;
    521            default:
    522                throw "bad format";
    523            }
    524            destBuffer[dstOff + 0] = sRGBChannelToLinear(srcColor[0]);
    525            destBuffer[dstOff + 1] = sRGBChannelToLinear(srcColor[1]);
    526            destBuffer[dstOff + 2] = sRGBChannelToLinear(srcColor[2]);
    527            destBuffer[dstOff + 3] = alpha;
    528        }
    529    }
    530 }
    531 
    532 function getBlockSize(format) {
    533  var isDXT1 = format == ext.COMPRESSED_SRGB_S3TC_DXT1_EXT ||
    534               format == ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
    535  return isDXT1 ? 8 : 16;
    536 }
    537 
    538 function uncompressDXTSRGB(width, height, data, format) {
    539    if (width % 4 || height % 4) throw "bad width or height";
    540 
    541    var dest = new Uint8Array(width * height * 4);
    542    var blocksAcross = width / 4;
    543    var blocksDown = height / 4;
    544    var blockSize = getBlockSize(format);
    545    for (var yy = 0; yy < blocksDown; ++yy) {
    546        for (var xx = 0; xx < blocksAcross; ++xx) {
    547            uncompressDXTBlockSRGB(
    548                dest, xx * 4, yy * 4, width, data,
    549                (yy * blocksAcross + xx) * blockSize, format);
    550        }
    551    }
    552    return dest;
    553 }
    554 
    555 function uncompressDXTIntoSubRegionSRGB(width, height, subX0, subY0, subWidth, subHeight, data, format)
    556 {
    557    if (width % 4 || height % 4 || subX0 % 4 || subY0 % 4 || subWidth % 4 || subHeight % 4)
    558        throw "bad dimension";
    559 
    560    var dest = new Uint8Array(width * height * 4);
    561    // Zero-filled DXT1 texture represents [0, 0, 0, 255]
    562    if (format == ext.COMPRESSED_SRGB_S3TC_DXT1_EXT || format == ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
    563        for (var i = 3; i < dest.length; i += 4) dest[i] = 255;
    564    }
    565    var blocksAcross = subWidth / 4;
    566    var blocksDown = subHeight / 4;
    567    var blockSize = getBlockSize(format);
    568    for (var yy = 0; yy < blocksDown; ++yy) {
    569        for (var xx = 0; xx < blocksAcross; ++xx) {
    570            uncompressDXTBlockSRGB(
    571                dest, subX0 + xx * 4, subY0 + yy * 4, width, data,
    572                (yy * blocksAcross + xx) * blockSize, format);
    573        }
    574    }
    575    return dest;
    576 }
    577 
    578 function copyRect(data, srcX, srcY, dstX, dstY, width, height, stride) {
    579  var bytesPerLine = width * 4;
    580  var srcOffset = srcX * 4 + srcY * stride;
    581  var dstOffset = dstX * 4 + dstY * stride;
    582  for (; height > 0; --height) {
    583    for (var ii = 0; ii < bytesPerLine; ++ii) {
    584      data[dstOffset + ii] = data[srcOffset + ii];
    585    }
    586    srcOffset += stride;
    587    dstOffset += stride;
    588  }
    589 }
    590 
    591 function testDXTTexture(test, useTexStorage) {
    592    var data = new Uint8Array(test.data);
    593    var width = test.width;
    594    var height = test.height;
    595    var format = test.format;
    596 
    597    var uncompressedData = uncompressDXTSRGB(width, height, data, format);
    598 
    599    canvas.width = width;
    600    canvas.height = height;
    601    gl.viewport(0, 0, width, height);
    602    debug("testing " + ctu.formatToString(ext, format) + " " + width + "x" + height +
    603          (useTexStorage ? " via texStorage2D" : " via compressedTexImage2D"));
    604 
    605    var tex = gl.createTexture();
    606    gl.bindTexture(gl.TEXTURE_2D, tex);
    607    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    608    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    609    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    610    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    611    if (useTexStorage) {
    612        if (test.subData) {
    613            var uncompressedDataSub = uncompressDXTIntoSubRegionSRGB(
    614                width, height, test.subX0, test.subY0, test.subWidth, test.subHeight, test.subData, format);
    615            var tex1 = gl.createTexture();
    616            gl.bindTexture(gl.TEXTURE_2D, tex1);
    617            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    618            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    619            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    620            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    621 
    622            gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
    623            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "allocating compressed texture via texStorage2D");
    624            gl.compressedTexSubImage2D(
    625                gl.TEXTURE_2D, 0, test.subX0, test.subY0, test.subWidth, test.subHeight, format, test.subData);
    626            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture data via compressedTexSubImage2D");
    627 
    628            wtu.clearAndDrawUnitQuad(gl);
    629            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 1");
    630            compareRect(width, height, test.channels, uncompressedDataSub, "NEAREST");
    631 
    632            // Clean up and recover
    633            gl.deleteTexture(tex1);
    634            gl.bindTexture(gl.TEXTURE_2D, tex);
    635        }
    636 
    637        gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
    638        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "allocating compressed texture via texStorage2D");
    639        wtu.clearAndDrawUnitQuad(gl);
    640        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    641        var clearColor = (test.hasAlpha ? [0, 0, 0, 0] : [0, 0, 0, 255]);
    642        wtu.checkCanvas(gl, clearColor, "texture should be initialized to black");
    643        gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data);
    644        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture data via compressedTexSubImage2D");
    645    } else {
    646        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
    647        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
    648    }
    649    gl.generateMipmap(gl.TEXTURE_2D);
    650    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "trying to generate mipmaps from compressed texture");
    651    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after clearing generateMipmap error");
    652    wtu.clearAndDrawUnitQuad(gl);
    653    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 1");
    654    compareRect(width, height, test.channels, uncompressedData, "NEAREST");
    655    // Test again with linear filtering.
    656    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    657    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    658    wtu.clearAndDrawUnitQuad(gl);
    659    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 2");
    660    compareRect(width, height, test.channels, uncompressedData, "LINEAR");
    661 
    662    if (!useTexStorage) {
    663        // It's not allowed to redefine textures defined via texStorage2D.
    664        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 1, data);
    665        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "non 0 border");
    666 
    667        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width + 4, height, 0, data);
    668        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    669        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height + 4, 0, data);
    670        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    671        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 4, height, 0, data);
    672        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    673        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 4, 0, data);
    674        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    675 
    676        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 1, height, 0, data);
    677        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    678        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 2, height, 0, data);
    679        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    680        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 1, 0, data);
    681        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    682        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 2, 0, data);
    683        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    684 
    685        if (width == 4) {
    686            // The width/height of the implied base level must be a multiple of the block size.
    687            gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 1, height, 0, data);
    688            wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions for level > 0");
    689            gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 2, height, 0, data);
    690            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
    691        }
    692        if (height == 4) {
    693            // The width/height of the implied base level must be a multiple of the block size.
    694            gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 1, 0, data);
    695            wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions for level > 0");
    696            gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 2, 0, data);
    697            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
    698        }
    699    }
    700 
    701    // pick a wrong format that uses the same amount of data.
    702    var wrongFormat;
    703    switch (format) {
    704    case ext.COMPRESSED_SRGB_S3TC_DXT1_EXT:
    705      wrongFormat = ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
    706      break;
    707    case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
    708      wrongFormat = ext.COMPRESSED_SRGB_S3TC_DXT1_EXT;
    709      break;
    710    case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
    711      wrongFormat = ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
    712      break;
    713    case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
    714      wrongFormat = ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
    715      break;
    716    }
    717 
    718    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, wrongFormat, data);
    719    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "format does not match");
    720 
    721    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 4, 0, width, height, format, data);
    722    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "dimension out of range");
    723    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 4, width, height, format, data);
    724    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "dimension out of range");
    725 
    726    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width + 4, height, format, data);
    727    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    728    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height + 4, format, data);
    729    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    730    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 4, height, format, data);
    731    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    732    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 4, format, data);
    733    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    734 
    735    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 1, height, format, data);
    736    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    737    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 2, height, format, data);
    738    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    739    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 1, format, data);
    740    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    741    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 2, format, data);
    742    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    743 
    744    var subData = new Uint8Array(data.buffer, 0, getBlockSize(format));
    745 
    746    if (width == 8 && height == 8) {
    747        gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 1, 0, 4, 4, format, subData);
    748        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid offset");
    749        gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 1, 4, 4, format, subData);
    750        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid offset");
    751    }
    752 
    753    var stride = width * 4;
    754    for (var yoff = 0; yoff < height; yoff += 4) {
    755        for (var xoff = 0; xoff < width; xoff += 4) {
    756            copyRect(uncompressedData, 0, 0, xoff, yoff, 4, 4, stride);
    757            gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, xoff, yoff, 4, 4, format, subData);
    758            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
    759            // First test NEAREST filtering.
    760            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    761            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    762            wtu.clearAndDrawUnitQuad(gl);
    763            compareRect(width, height, test.channels, uncompressedData, "NEAREST");
    764            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    765            // Next test LINEAR filtering.
    766            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    767            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    768            wtu.clearAndDrawUnitQuad(gl);
    769            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    770            compareRect(width, height, test.channels, uncompressedData, "LINEAR");
    771        }
    772    }
    773 }
    774 
    775 function testDXT5_SRGB_ALPHA_PBO() {
    776    debug("");
    777    debug("testing PBO uploads");
    778    var width = 8;
    779    var height = 8;
    780    var channels = 4;
    781    var data = img_8x8_rgba_dxt5;
    782    var format = ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
    783    var uncompressedData = uncompressDXTSRGB(width, height, data, format);
    784 
    785    var tex = gl.createTexture();
    786 
    787    // First, PBO size = image size
    788    var pbo1 = gl.createBuffer();
    789    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo1);
    790    gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data, gl.STATIC_DRAW);
    791    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a PBO");
    792 
    793    gl.bindTexture(gl.TEXTURE_2D, tex);
    794    gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
    795    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data.length, 0);
    796    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a texture from a PBO");
    797 
    798    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
    799    wtu.clearAndDrawUnitQuad(gl);
    800    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    801    compareRect(width, height, channels, uncompressedData, "NEAREST");
    802 
    803    // Clear the texture before the next test
    804    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
    805    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, new Uint8Array(data.length));
    806 
    807    // Second, image is just a subrange of the PBO
    808    var pbo2 = gl.createBuffer();
    809    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo2);
    810    gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data.length*3, gl.STATIC_DRAW);
    811    gl.bufferSubData(gl.PIXEL_UNPACK_BUFFER, data.length, data);
    812    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a PBO subrange");
    813    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data.length, data.length);
    814    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a texture from a PBO subrange");
    815    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
    816    wtu.clearAndDrawUnitQuad(gl);
    817    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    818    compareRect(width, height, channels, uncompressedData, "NEAREST");
    819 }
    820 
    821 // See EXT_texture_sRGB, Section 3.8.x, sRGB Texture Color Conversion.
    822 function sRGBChannelToLinear(value) {
    823    value = value / 255;
    824    if (value <= 0.04045) {
    825        value = value / 12.92;
    826    } else {
    827        value = Math.pow((value + 0.055) / 1.055, 2.4);
    828    }
    829    return Math.trunc(value * 255 + 0.5);
    830 }
    831 
    832 function compareRect(width, height, channels, expectedData, filteringMode) {
    833    var actual = new Uint8Array(width * height * 4);
    834    gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, actual);
    835    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "reading back pixels");
    836 
    837    var div = document.createElement("div");
    838    div.className = "testimages";
    839    ctu.insertCaptionedImg(div, "expected", ctu.makeScaledImage(width, height, width, expectedData, true));
    840    ctu.insertCaptionedImg(div, "actual", ctu.makeScaledImage(width, height, width, actual, true));
    841    div.appendChild(document.createElement('br'));
    842    document.getElementById("console").appendChild(div);
    843 
    844    var failed = false;
    845    for (var yy = 0; yy < height; ++yy) {
    846        for (var xx = 0; xx < width; ++xx) {
    847            var offset = (yy * width + xx) * 4;
    848            var expected = expectedData.slice(offset, offset + 4);
    849            // Compare RGB values
    850            for (var jj = 0; jj < 3; ++jj) {
    851                // Acceptable interpolation error depends on endpoints:
    852                // 1.0 / 255.0 + 0.03 * max(abs(endpoint0 - endpoint1), abs(endpoint0_p - endpoint1_p))
    853                // For simplicity, assume the worst case (e0 is 0.0, e1 is 1.0). After conversion to unorm8, it is 9.
    854                if (Math.abs(actual[offset + jj] - expected[jj]) > 9) {
    855                    var was = actual[offset + 0].toString();
    856                    for (var j = 1; j < 3; ++j) {
    857                        was += "," + actual[offset + j];
    858                    }
    859                    failed = true;
    860                    testFailed('RGB at (' + xx + ', ' + yy +
    861                                        ') expected: ' + expected + ' ± 9 was ' + was);
    862                }
    863            }
    864 
    865            if (channels == 3) {
    866                // BC1 RGB is allowed to be mapped to BC1 RGBA.
    867                // In such a case, 3-color mode black value can be transparent:
    868                // [0, 0, 0, 0] instead of [0, 0, 0, 255].
    869 
    870                if (actual[offset + 3] != expected[3]) {
    871                    // Got non-opaque value for opaque format
    872 
    873                    // Check RGB values. Notice, that the condition here
    874                    // is more permissive than needed since we don't have
    875                    // compressed data at this point.
    876                    if (actual[offset] == 0 &&
    877                        actual[offset + 1] == 0 &&
    878                        actual[offset + 2] == 0 &&
    879                        actual[offset + 3] == 0) {
    880                            debug("<b>DXT1 SRGB is mapped to DXT1 SRGB ALPHA</b>");
    881                    } else {
    882                        failed = true;
    883                        testFailed('Alpha at (' + xx + ', ' + yy +
    884                                              ') expected: ' + expected[3] + ' was ' + actual[offset + 3]);
    885                    }
    886                }
    887            } else {
    888                // Compare Alpha values
    889                // Acceptable interpolation error depends on endpoints:
    890                // 1.0 / 65535.0 + 0.03 * max(abs(endpoint0 - endpoint1), abs(endpoint0_p - endpoint1_p))
    891                // For simplicity, assume the worst case (e0 is 0.0, e1 is 1.0). After conversion to unorm8, it is 8.
    892                if (Math.abs(actual[offset + 3] - expected[3]) > 8) {
    893                    var was = actual[offset + 3].toString();
    894                    failed = true;
    895                    testFailed('Alpha at (' + xx + ', ' + yy +
    896                                          ') expected: ' + expected + ' ± 8 was ' + was);
    897                }
    898            }
    899        }
    900    }
    901    if (!failed) {
    902        testPassed("texture rendered correctly with " + filteringMode + " filtering");
    903    }
    904 }
    905 
    906 debug("");
    907 var successfullyParsed = true;
    908 </script>
    909 <script src="../../js/js-test-post.js"></script>
    910 
    911 </body>
    912 </html>