tor-browser

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

s3tc-and-rgtc.html (43603B)


      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 and EXT_texture_compression_rgtc 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 extension, if it is available. It also tests the related formats from the EXT_texture_compression_rgtc extension.");
     39 
     40 debug("");
     41 
     42 // Acceptable interpolation error depends on endpoints:
     43 // 1.0 / 255.0 + 0.03 * max(abs(endpoint0 - endpoint1), abs(endpoint0_p - endpoint1_p))
     44 // For simplicity, assume the worst case (e0 is 0.0, e1 is 1.0). After conversion to unorm8, it is 9.
     45 const DEFAULT_COLOR_ERROR = 9;
     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 // BC4 - just the alpha block from BC3 above, interpreted as the red channel.
    139 // See http://www.reedbeta.com/blog/understanding-bcn-texture-compression-formats/#bc4
    140 // for format details.
    141 var img_4x4_r_bc4 = new Uint8Array([
    142    0xFF, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    143 ]);
    144 
    145 // BC5 - Two BC3 alpha blocks, interpreted as the red and green channels.
    146 var img_4x4_rg_bc5 = new Uint8Array([
    147    0xFF, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    148    0x00, 0xFF, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    149 ]);
    150 
    151 // Signed BC4 - change endpoints to use full -1 to 1 range.
    152 var img_4x4_signed_r_bc4 = new Uint8Array([
    153    0x7F, 0x80, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    154 ]);
    155 
    156 // Signed BC5 - Two BC3 alpha blocks, interpreted as the red and green channels.
    157 var img_4x4_signed_rg_bc5 = new Uint8Array([
    158    0x7F, 0x80, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    159    0x80, 0x7F, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    160 ]);
    161 
    162 
    163 /*
    164 8x8 block endpoints use half-intensity values (appear darker than 4x4)
    165 */
    166 var img_8x8_rgba_dxt1 = new Uint8Array([
    167    0xe0,0x03,0x00,0x78,0x13,0x10,0x15,0x00,
    168    0x0f,0x00,0xe0,0x7b,0x11,0x10,0x15,0x00,
    169    0xe0,0x03,0x0f,0x78,0x44,0x45,0x40,0x55,
    170    0x0f,0x00,0xef,0x03,0x44,0x45,0x40,0x55
    171 ]);
    172 var img_8x8_rgba_dxt3 = new Uint8Array([
    173    0xf6,0xff,0xf6,0xff,0xff,0xff,0xff,0xff,0x00,0x78,0xe0,0x03,0x44,0x45,0x40,0x55,
    174    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x7b,0x0f,0x00,0x44,0x45,0x40,0x55,
    175    0xff,0xff,0xff,0xff,0xf6,0xff,0xf6,0xff,0x0f,0x78,0xe0,0x03,0x11,0x10,0x15,0x00,
    176    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xef,0x03,0x0f,0x00,0x11,0x10,0x15,0x00
    177 ]);
    178 var img_8x8_rgba_dxt5 = new Uint8Array([
    179    0xff,0x69,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x78,0xe0,0x03,0x44,0x45,0x40,0x55,
    180    0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x7b,0x0f,0x00,0x44,0x45,0x40,0x55,
    181    0xff,0x69,0x00,0x00,0x00,0x01,0x10,0x00,0x0f,0x78,0xe0,0x03,0x11,0x10,0x15,0x00,
    182    0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0x03,0xef,0x00,0x11,0x10,0x15,0x00
    183 ]);
    184 var img_8x8_r_bc4 = new Uint8Array([
    185    0x7F, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    186    0x7F, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    187    0x7F, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    188    0x7F, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    189 ]);
    190 var img_8x8_rg_bc5 = new Uint8Array([
    191    0x7F, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6, 0x00, 0x7F, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    192    0x7F, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6, 0x00, 0x7F, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    193    0x7F, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6, 0x00, 0x7F, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    194    0x7F, 0x00, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6, 0x00, 0x7F, 0x88, 0x16, 0x8D, 0x1A, 0x3B, 0xD6,
    195 ]);
    196 
    197 var wtu = WebGLTestUtils;
    198 var ctu = CompressedTextureUtils;
    199 var contextVersion = wtu.getDefault3DContextVersion();
    200 var canvas = document.getElementById("canvas");
    201 var gl = wtu.create3DContext(canvas, {antialias: false});
    202 var program = wtu.setupTexturedQuad(gl);
    203 var ext = null;
    204 var ext_rgtc = {};
    205 var vao = null;
    206 var validFormats = {
    207    COMPRESSED_RGB_S3TC_DXT1_EXT  : 0x83F0,
    208    COMPRESSED_RGBA_S3TC_DXT1_EXT : 0x83F1,
    209    COMPRESSED_RGBA_S3TC_DXT3_EXT : 0x83F2,
    210    COMPRESSED_RGBA_S3TC_DXT5_EXT : 0x83F3,
    211 };
    212 var name;
    213 var supportedFormats;
    214 
    215 if (!gl) {
    216    testFailed("WebGL context does not exist");
    217 } else {
    218    testPassed("WebGL context exists");
    219 
    220    // Run tests with extension disabled
    221    ctu.testCompressedFormatsUnavailableWhenExtensionDisabled(gl, validFormats, expectedByteLength, 4);
    222 
    223    // Query the extension and store globally so shouldBe can access it
    224    ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_s3tc");
    225    if (!ext) {
    226        testPassed("No WEBGL_compressed_texture_s3tc support -- this is legal");
    227        wtu.runExtensionSupportedTest(gl, "WEBGL_compressed_texture_s3tc", false);
    228    } else {
    229        testPassed("Successfully enabled WEBGL_compressed_texture_s3tc extension");
    230 
    231        wtu.runExtensionSupportedTest(gl, "WEBGL_compressed_texture_s3tc", true);
    232        runTestExtension();
    233    }
    234    ext_rgtc = wtu.getExtensionWithKnownPrefixes(gl, "EXT_texture_compression_rgtc");
    235    if (ext_rgtc) {
    236        ext = ext || {};
    237        // Make ctu.formatToString work for rgtc enums.
    238        for (const name in ext_rgtc)
    239            ext[name] = ext_rgtc[name];
    240        runTestRGTC();
    241    }
    242 }
    243 
    244 function expectedByteLength(width, height, format) {
    245    if (format == validFormats.COMPRESSED_RGBA_S3TC_DXT3_EXT || format == validFormats.COMPRESSED_RGBA_S3TC_DXT5_EXT) {
    246        return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16;
    247    }
    248    return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;
    249 }
    250 
    251 function getBlockDimensions(format) {
    252    return {width: 4, height: 4};
    253 }
    254 
    255 function runTestExtension() {
    256    debug("");
    257    debug("Testing WEBGL_compressed_texture_s3tc");
    258 
    259    // Test that enum values are listed correctly in supported formats and in the extension object.
    260    ctu.testCompressedFormatsListed(gl, validFormats);
    261    ctu.testCorrectEnumValuesInExt(ext, validFormats);
    262    // Test that texture upload buffer size is validated correctly.
    263    ctu.testFormatRestrictionsOnBufferSize(gl, validFormats, expectedByteLength, getBlockDimensions);
    264 
    265    // Test each format
    266    testDXT1_RGB();
    267    testDXT1_RGBA();
    268    testDXT3_RGBA();
    269    testDXT5_RGBA();
    270 
    271    // Test compressed PBOs with a single format
    272    if (contextVersion >= 2) {
    273        testDXT5_RGBA_PBO();
    274    }
    275 
    276    // Test TexImage validation on level dimensions combinations.
    277    debug("");
    278    debug("When level equals 0, width and height must be a multiple of 4.");
    279    debug("When level is larger than 0, this constraint doesn't apply.");
    280    ctu.testTexImageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
    281      [
    282        { level: 0, width: 4, height: 3, expectation: gl.INVALID_OPERATION, message: "0: 4x3" },
    283        { level: 0, width: 3, height: 4, expectation: gl.INVALID_OPERATION, message: "0: 3x4" },
    284        { level: 0, width: 2, height: 2, expectation: gl.INVALID_OPERATION, message: "0: 2x2" },
    285        { level: 0, width: 4, height: 4, expectation: gl.NO_ERROR, message: "0: 4x4" },
    286        { level: 1, width: 2, height: 2, expectation: gl.NO_ERROR, message: "1: 2x2" },
    287        { level: 2, width: 1, height: 1, expectation: gl.NO_ERROR, message: "2: 1x1" },
    288    ]);
    289 
    290    ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 16, 16,
    291      [
    292        { xoffset: 0, yoffset: 0, width: 4, height: 3,
    293          expectation: gl.INVALID_OPERATION, message: "height is not a multiple of 4" },
    294        { xoffset: 0, yoffset: 0, width: 3, height: 4,
    295          expectation: gl.INVALID_OPERATION, message: "width is not a multiple of 4" },
    296        { xoffset: 1, yoffset: 0, width: 4, height: 4,
    297          expectation: gl.INVALID_OPERATION, message: "xoffset is not a multiple of 4" },
    298        { xoffset: 0, yoffset: 1, width: 4, height: 4,
    299          expectation: gl.INVALID_OPERATION, message: "yoffset is not a multiple of 4" },
    300        { xoffset: 12, yoffset: 12, width: 4, height: 4,
    301          expectation: gl.NO_ERROR, message: "is valid" },
    302    ]);
    303 
    304    if (contextVersion >= 2) {
    305        debug("");
    306        debug("Testing NPOT textures");
    307        ctu.testTexImageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
    308          [
    309            { level: 0, width: 0, height: 0, expectation: gl.NO_ERROR, message: "0: 0x0 is valid" },
    310            { level: 0, width: 1, height: 1, expectation: gl.INVALID_OPERATION, message: "0: 1x1 is invalid" },
    311            { level: 0, width: 2, height: 2, expectation: gl.INVALID_OPERATION, message: "0: 2x2 is invalid" },
    312            { level: 0, width: 3, height: 3, expectation: gl.INVALID_OPERATION, message: "0: 3x3 is invalid" },
    313            { level: 0, width: 10, height: 10, expectation: gl.INVALID_OPERATION, message: "0: 10x10 is invalid" },
    314            { level: 0, width: 11, height: 11, expectation: gl.INVALID_OPERATION, message: "0: 11x11 is invalid" },
    315            { level: 0, width: 11, height: 12, expectation: gl.INVALID_OPERATION, message: "0: 11x12 is invalid" },
    316            { level: 0, width: 12, height: 11, expectation: gl.INVALID_OPERATION, message: "0: 12x11 is invalid" },
    317            { level: 0, width: 12, height: 12, expectation: gl.NO_ERROR, message: "0: 12x12 is valid" },
    318            { level: 1, width:  0, height:  0, expectation: gl.NO_ERROR, message: "1: 0x0, is valid" },
    319            { level: 1, width:  3, height:  3, expectation: gl.INVALID_OPERATION, message: "1: 3x3, is invalid" },
    320            { level: 1, width:  5, height:  5, expectation: gl.INVALID_OPERATION, message: "1: 5x5, is invalid" },
    321            { level: 1, width:  5, height:  6, expectation: gl.INVALID_OPERATION, message: "1: 5x6, is invalid" },
    322            { level: 1, width:  6, height:  5, expectation: gl.INVALID_OPERATION, message: "1: 6x5, is invalid" },
    323            { level: 1, width:  6, height:  6, expectation: gl.NO_ERROR, message: "1: 6x6, is valid" },
    324            { level: 2, width:  0, height:  0, expectation: gl.NO_ERROR, message: "2: 0x0, is valid" },
    325            { level: 2, width:  3, height:  3, expectation: gl.NO_ERROR, message: "2: 3x3, is valid" },
    326            { level: 3, width:  1, height:  3, expectation: gl.NO_ERROR, message: "3: 1x3, is valid" },
    327            { level: 3, width:  1, height:  1, expectation: gl.NO_ERROR, message: "3: 1x1, is valid" },
    328        ]);
    329 
    330        debug("");
    331        debug("Testing partial updates");
    332        ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 12, 12,
    333        [
    334          { xoffset: 0, yoffset: 0, width: 4, height: 3,
    335            expectation: gl.INVALID_OPERATION, message: "height is not a multiple of 4" },
    336          { xoffset: 0, yoffset: 0, width: 3, height: 4,
    337            expectation: gl.INVALID_OPERATION, message: "width is not a multiple of 4" },
    338          { xoffset: 1, yoffset: 0, width: 4, height: 4,
    339            expectation: gl.INVALID_OPERATION, message: "xoffset is not a multiple of 4" },
    340          { xoffset: 0, yoffset: 1, width: 4, height: 4,
    341            expectation: gl.INVALID_OPERATION, message: "yoffset is not a multiple of 4" },
    342          { xoffset: 8, yoffset: 8, width: 4, height: 4,
    343            expectation: gl.NO_ERROR, message: "is valid" },
    344        ]);
    345 
    346        debug("");
    347        debug("Testing immutable NPOT textures");
    348        ctu.testTexStorageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
    349          [
    350            { width: 12, height: 12, expectation: gl.NO_ERROR, message: "0: 12x12 is valid" },
    351            { width:  6, height:  6, expectation: gl.NO_ERROR, message: "1: 6x6, is valid" },
    352            { width:  3, height:  3, expectation: gl.NO_ERROR, message: "2: 3x3, is valid" },
    353            { width:  1, height:  1, expectation: gl.NO_ERROR, message: "3: 1x1, is valid" },
    354        ]);
    355    }
    356 }
    357 
    358 function runTestRGTC() {
    359    var tests = [
    360        {   width: 4,
    361            height: 4,
    362            channels: 1,
    363            data: img_4x4_r_bc4,
    364            format: ext_rgtc.COMPRESSED_RED_RGTC1_EXT,
    365            hasAlpha: false,
    366        },
    367        {   width: 4,
    368            height: 4,
    369            channels: 1,
    370            data: img_4x4_signed_r_bc4,
    371            format: ext_rgtc.COMPRESSED_SIGNED_RED_RGTC1_EXT,
    372            hasAlpha: false,
    373        },
    374        {   width: 4,
    375            height: 4,
    376            channels: 2,
    377            data: img_4x4_rg_bc5,
    378            format: ext_rgtc.COMPRESSED_RED_GREEN_RGTC2_EXT,
    379            hasAlpha: false,
    380        },
    381        {   width: 4,
    382            height: 4,
    383            channels: 2,
    384            data: img_4x4_signed_rg_bc5,
    385            format: ext_rgtc.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT,
    386            hasAlpha: false,
    387            error: 18, // Signed, so twice the normal error.
    388                       // Experimentally needed by e.g. RTX 3070.
    389        },
    390        {   width: 8,
    391            height: 8,
    392            channels: 2,
    393            data: img_8x8_r_bc4,
    394            format: ext_rgtc.COMPRESSED_RED_RGTC1_EXT,
    395            hasAlpha: false,
    396            subX0: 0,
    397            subY0: 0,
    398            subWidth: 4,
    399            subHeight: 4,
    400            subData: img_4x4_r_bc4,
    401        },
    402        {   width: 8,
    403            height: 8,
    404            channels: 2,
    405            data: img_8x8_rg_bc5,
    406            format: ext_rgtc.COMPRESSED_RED_GREEN_RGTC2_EXT,
    407            hasAlpha: false,
    408            subX0: 0,
    409            subY0: 0,
    410            subWidth: 4,
    411            subHeight: 4,
    412            subData: img_4x4_rg_bc5,
    413        },
    414    ];
    415    testDXTTextures(tests);
    416 }
    417 
    418 function testDXT1_RGB() {
    419    var tests = [
    420        {   width: 4,
    421            height: 4,
    422            channels: 3,
    423            data: img_4x4_rgba_dxt1,
    424            format: ext.COMPRESSED_RGB_S3TC_DXT1_EXT,
    425            hasAlpha: false,
    426        },
    427        {   width: 8,
    428            height: 8,
    429            channels: 3,
    430            data: img_8x8_rgba_dxt1,
    431            format: ext.COMPRESSED_RGB_S3TC_DXT1_EXT,
    432            hasAlpha: false,
    433            subX0: 0,
    434            subY0: 0,
    435            subWidth: 4,
    436            subHeight: 4,
    437            subData: img_4x4_rgba_dxt1
    438        }
    439    ];
    440    testDXTTextures(tests);
    441 }
    442 
    443 function testDXT1_RGBA() {
    444    var tests = [
    445        {   width: 4,
    446            height: 4,
    447            channels: 4,
    448            data: img_4x4_rgba_dxt1,
    449            format: ext.COMPRESSED_RGBA_S3TC_DXT1_EXT,
    450            // This is a special case -- the texture is still opaque
    451            // though it's RGBA.
    452            hasAlpha: false,
    453        },
    454        {   width: 8,
    455            height: 8,
    456            channels: 4,
    457            data: img_8x8_rgba_dxt1,
    458            format: ext.COMPRESSED_RGBA_S3TC_DXT1_EXT,
    459            // This is a special case -- the texture is still opaque
    460            // though it's RGBA.
    461            hasAlpha: false,
    462        }
    463    ];
    464    testDXTTextures(tests);
    465 }
    466 
    467 function testDXT3_RGBA() {
    468    var tests = [
    469        {   width: 4,
    470            height: 4,
    471            channels: 4,
    472            data: img_4x4_rgba_dxt3,
    473            format: ext.COMPRESSED_RGBA_S3TC_DXT3_EXT,
    474            hasAlpha: true,
    475        },
    476        {   width: 8,
    477            height: 8,
    478            channels: 4,
    479            data: img_8x8_rgba_dxt3,
    480            format: ext.COMPRESSED_RGBA_S3TC_DXT3_EXT,
    481            hasAlpha: true,
    482            subX0: 0,
    483            subY0: 0,
    484            subWidth: 4,
    485            subHeight: 4,
    486            subData: img_4x4_rgba_dxt3
    487        }
    488    ];
    489    testDXTTextures(tests);
    490 }
    491 
    492 function testDXT5_RGBA() {
    493    var tests = [
    494        {   width: 4,
    495            height: 4,
    496            channels: 4,
    497            data: img_4x4_rgba_dxt5,
    498            format: ext.COMPRESSED_RGBA_S3TC_DXT5_EXT,
    499            hasAlpha: true,
    500        },
    501        {   width: 8,
    502            height: 8,
    503            channels: 4,
    504            data: img_8x8_rgba_dxt5,
    505            format: ext.COMPRESSED_RGBA_S3TC_DXT5_EXT,
    506            hasAlpha: true,
    507            subX0: 0,
    508            subY0: 0,
    509            subWidth: 4,
    510            subHeight: 4,
    511            subData: img_4x4_rgba_dxt5
    512        }
    513    ];
    514    testDXTTextures(tests);
    515 }
    516 
    517 function testDXTTextures(tests) {
    518    debug("<hr/>");
    519    for (var ii = 0; ii < tests.length; ++ii) {
    520        testDXTTexture(tests[ii], false);
    521        if (contextVersion >= 2) {
    522            debug("<br/>");
    523            testDXTTexture(tests[ii], true);
    524        }
    525    }
    526 }
    527 
    528 function uncompressDXTBlock(
    529    destBuffer, destX, destY, destWidth, src, srcOffset, format) {
    530    // Decoding routines follow D3D11 functional spec wrt
    531    // endpoints unquantization and interpolation.
    532    // Some hardware may produce slightly different values - it's normal.
    533 
    534    function make565(src, offset) {
    535        return src[offset + 0] + (src[offset + 1] << 8);
    536    }
    537    function make8888From565(c) {
    538        // These values exactly match hw decoder when selectors are 0 or 1.
    539        function replicateBits(v, w) {
    540           return (v << (8 - w)) | (v >> (w + w - 8));
    541        }
    542        return [
    543                replicateBits((c >> 11) & 0x1F, 5),
    544                replicateBits((c >>  5) & 0x3F, 6),
    545                replicateBits((c >>  0) & 0x1F, 5),
    546                255
    547            ];
    548    }
    549    function mix(mult, c0, c1, div) {
    550        var r = [];
    551        for (var ii = 0; ii < c0.length; ++ii) {
    552            // For green channel (6 bits), this interpolation exactly matches hw decoders
    553 
    554            // For red and blue channels (5 bits), this interpolation exactly
    555            // matches only some hw decoders and stays within acceptable range for others.
    556            r[ii] = Math.floor((c0[ii] * mult + c1[ii]) / div + 0.5);
    557        }
    558        return r;
    559    }
    560    var isBC45 = ext_rgtc &&
    561        (format == ext_rgtc.COMPRESSED_RED_RGTC1_EXT ||
    562         format == ext_rgtc.COMPRESSED_RED_GREEN_RGTC2_EXT ||
    563         format == ext_rgtc.COMPRESSED_SIGNED_RED_RGTC1_EXT ||
    564         format == ext_rgtc.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT);
    565    let colorOffset = srcOffset;
    566    if (!isBC45) {
    567        var isDXT1 = format == ext.COMPRESSED_RGB_S3TC_DXT1_EXT ||
    568                     format == ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
    569        if (!isDXT1) {
    570            colorOffset += 8;
    571        }
    572        var color0 = make565(src, colorOffset + 0);
    573        var color1 = make565(src, colorOffset + 2);
    574        var c0gtc1 = color0 > color1 || !isDXT1;
    575        var rgba0 = make8888From565(color0);
    576        var rgba1 = make8888From565(color1);
    577        var colors = [
    578                rgba0,
    579                rgba1,
    580                c0gtc1 ? mix(2, rgba0, rgba1, 3) : mix(1, rgba0, rgba1, 2),
    581                c0gtc1 ? mix(2, rgba1, rgba0, 3) : [0, 0, 0, 255]
    582            ];
    583    }
    584    const isSigned = ext_rgtc && (format == ext_rgtc.COMPRESSED_SIGNED_RED_RGTC1_EXT || format == ext_rgtc.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT);
    585    const signedSrc = new Int8Array(src);
    586 
    587    // yea I know there is a lot of math in this inner loop.
    588    // so sue me.
    589    for (var yy = 0; yy < 4; ++yy) {
    590        var pixels = src[colorOffset + 4 + yy];
    591        for (var xx = 0; xx < 4; ++xx) {
    592            var dstOff = ((destY + yy) * destWidth + destX + xx) * 4;
    593            if (!isBC45) {
    594                var code = (pixels >> (xx * 2)) & 0x3;
    595                var srcColor = colors[code];
    596            }
    597            var alpha;
    598            var rgChannel2 = 0;
    599            let decodeAlpha = (offset) => {
    600                let alpha;
    601                var alpha0 = (isSigned ? signedSrc : src)[offset + 0];
    602                var alpha1 = (isSigned ? signedSrc : src)[offset + 1];
    603                var alphaOff = (yy >> 1) * 3 + 2;
    604                var alphaBits =
    605                     src[offset + alphaOff + 0] +
    606                     src[offset + alphaOff + 1] * 256 +
    607                     src[offset + alphaOff + 2] * 65536;
    608                var alphaShift = (yy % 2) * 12 + xx * 3;
    609                var alphaCode = (alphaBits >> alphaShift) & 0x7;
    610                if (alpha0 > alpha1) {
    611                    switch (alphaCode) {
    612                    case 0:
    613                        alpha = alpha0;
    614                        break;
    615                    case 1:
    616                        alpha = alpha1;
    617                        break;
    618                    default:
    619                        alpha = Math.floor(((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 7.0 + 0.5);
    620                        break;
    621                    }
    622                } else {
    623                    switch (alphaCode) {
    624                    case 0:
    625                        alpha = alpha0;
    626                        break;
    627                    case 1:
    628                        alpha = alpha1;
    629                        break;
    630                    case 6:
    631                        alpha = 0;
    632                        break;
    633                    case 7:
    634                        alpha = 255;
    635                        break;
    636                    default:
    637                        alpha = Math.floor(((6 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 5.0 + 0.5);
    638                        break;
    639                    }
    640                }
    641                return alpha;
    642            }
    643 
    644            switch (format) {
    645            case ext.COMPRESSED_RGB_S3TC_DXT1_EXT:
    646                alpha = 255;
    647                break;
    648            case ext.COMPRESSED_RGBA_S3TC_DXT1_EXT:
    649                alpha = (code == 3 && !c0gtc1) ? 0 : 255;
    650                break;
    651            case ext.COMPRESSED_RGBA_S3TC_DXT3_EXT:
    652                {
    653                    var alpha0 = src[srcOffset + yy * 2 + (xx >> 1)];
    654                    var alpha1 = (alpha0 >> ((xx % 2) * 4)) & 0xF;
    655                    alpha = alpha1 | (alpha1 << 4);
    656                }
    657                break;
    658            case ext_rgtc.COMPRESSED_RED_GREEN_RGTC2_EXT:
    659            case ext_rgtc.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
    660                rgChannel2 = decodeAlpha(srcOffset + 8);
    661                // FALLTHROUGH
    662            case ext.COMPRESSED_RGBA_S3TC_DXT5_EXT:
    663            case ext_rgtc.COMPRESSED_RED_RGTC1_EXT:
    664            case ext_rgtc.COMPRESSED_SIGNED_RED_RGTC1_EXT:
    665                alpha = decodeAlpha(srcOffset);
    666                break;
    667            default:
    668                throw "bad format";
    669            }
    670            if (isBC45) {
    671                destBuffer[dstOff + 0] = alpha;
    672                destBuffer[dstOff + 1] = rgChannel2;
    673                destBuffer[dstOff + 2] = 0;
    674                destBuffer[dstOff + 3] = 255;
    675                if (isSigned) {
    676                    destBuffer[dstOff + 0] = Math.max(0, alpha) * 2;
    677                    destBuffer[dstOff + 1] = Math.max(0, rgChannel2) * 2;
    678                }
    679            } else {
    680                destBuffer[dstOff + 0] = srcColor[0];
    681                destBuffer[dstOff + 1] = srcColor[1];
    682                destBuffer[dstOff + 2] = srcColor[2];
    683                destBuffer[dstOff + 3] = alpha;
    684            }
    685        }
    686    }
    687 }
    688 
    689 function getBlockSize(format) {
    690  var isDXT1 = format == ext.COMPRESSED_RGB_S3TC_DXT1_EXT ||
    691               format == ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
    692  var isBC4 = ext_rgtc && (format == ext_rgtc.COMPRESSED_RED_RGTC1_EXT || format == ext_rgtc.COMPRESSED_SIGNED_RED_RGTC1_EXT);
    693  return isDXT1 || isBC4 ? 8 : 16;
    694 }
    695 
    696 function uncompressDXT(width, height, data, format) {
    697    if (width % 4 || height % 4) throw "bad width or height";
    698 
    699    var dest = new Uint8Array(width * height * 4);
    700    var blocksAcross = width / 4;
    701    var blocksDown = height / 4;
    702    var blockSize = getBlockSize(format);
    703    for (var yy = 0; yy < blocksDown; ++yy) {
    704        for (var xx = 0; xx < blocksAcross; ++xx) {
    705            uncompressDXTBlock(
    706                dest, xx * 4, yy * 4, width, data,
    707                (yy * blocksAcross + xx) * blockSize, format);
    708        }
    709    }
    710    return dest;
    711 }
    712 
    713 function uncompressDXTIntoSubRegion(width, height, subX0, subY0, subWidth, subHeight, data, format)
    714 {
    715    if (width % 4 || height % 4 || subX0 % 4 || subY0 % 4 || subWidth % 4 || subHeight % 4)
    716        throw "bad dimension";
    717 
    718    var dest = new Uint8Array(width * height * 4);
    719    // Zero-filled DXT1 or BC4/5 texture represents [0, 0, 0, 255]
    720    if (format == ext.COMPRESSED_RGB_S3TC_DXT1_EXT || format == ext.COMPRESSED_RGBA_S3TC_DXT1_EXT ||
    721        format == ext.COMPRESSED_RED_RGTC1_EXT || format == ext.COMPRESSED_SIGNED_RED_RGTC1_EXT ||
    722        format == ext.COMPRESSED_RED_GREEN_RGTC2_EXT || format == ext.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT) {
    723        for (var i = 3; i < dest.length; i += 4) dest[i] = 255;
    724    }
    725    var blocksAcross = subWidth / 4;
    726    var blocksDown = subHeight / 4;
    727    var blockSize = getBlockSize(format);
    728    for (var yy = 0; yy < blocksDown; ++yy) {
    729        for (var xx = 0; xx < blocksAcross; ++xx) {
    730            uncompressDXTBlock(
    731                dest, subX0 + xx * 4, subY0 + yy * 4, width, data,
    732                (yy * blocksAcross + xx) * blockSize, format);
    733        }
    734    }
    735    return dest;
    736 }
    737 
    738 function copyRect(data, srcX, srcY, dstX, dstY, width, height, stride) {
    739  var bytesPerLine = width * 4;
    740  var srcOffset = srcX * 4 + srcY * stride;
    741  var dstOffset = dstX * 4 + dstY * stride;
    742  for (; height > 0; --height) {
    743    for (var ii = 0; ii < bytesPerLine; ++ii) {
    744      data[dstOffset + ii] = data[srcOffset + ii];
    745    }
    746    srcOffset += stride;
    747    dstOffset += stride;
    748  }
    749 }
    750 
    751 function testDXTTexture(test, useTexStorage) {
    752    test.error = test.error || DEFAULT_COLOR_ERROR;
    753 
    754    var data = new Uint8Array(test.data);
    755    var width = test.width;
    756    var height = test.height;
    757    var format = test.format;
    758 
    759    var uncompressedData = uncompressDXT(width, height, data, format);
    760 
    761    canvas.width = width;
    762    canvas.height = height;
    763    gl.viewport(0, 0, width, height);
    764    debug("testing " + ctu.formatToString(ext, format) + " " + width + "x" + height +
    765          (useTexStorage ? " via texStorage2D" : " via compressedTexImage2D"));
    766 
    767    var tex = gl.createTexture();
    768    gl.bindTexture(gl.TEXTURE_2D, tex);
    769    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    770    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    771    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    772    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    773    if (useTexStorage) {
    774        if (test.subData) {
    775            var uncompressedDataSub = uncompressDXTIntoSubRegion(
    776                width, height, test.subX0, test.subY0, test.subWidth, test.subHeight, test.subData, format);
    777            var tex1 = gl.createTexture();
    778            gl.bindTexture(gl.TEXTURE_2D, tex1);
    779            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    780            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    781            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    782            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    783 
    784            gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
    785            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "allocating compressed texture via texStorage2D");
    786            gl.compressedTexSubImage2D(
    787                gl.TEXTURE_2D, 0, test.subX0, test.subY0, test.subWidth, test.subHeight, format, test.subData);
    788            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture data via compressedTexSubImage2D");
    789 
    790            wtu.clearAndDrawUnitQuad(gl);
    791            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 1");
    792            compareRect(width, height, test.channels, uncompressedDataSub, "NEAREST", test.error);
    793 
    794            // Clean up and recover
    795            gl.deleteTexture(tex1);
    796            gl.bindTexture(gl.TEXTURE_2D, tex);
    797        }
    798 
    799        gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
    800        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "allocating compressed texture via texStorage2D");
    801        wtu.clearAndDrawUnitQuad(gl);
    802        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    803        var clearColor = (test.hasAlpha ? [0, 0, 0, 0] : [0, 0, 0, 255]);
    804        wtu.checkCanvas(gl, clearColor, "texture should be initialized to black");
    805        gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data);
    806        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture data via compressedTexSubImage2D");
    807    } else {
    808        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
    809        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
    810    }
    811    gl.generateMipmap(gl.TEXTURE_2D);
    812    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "trying to generate mipmaps from compressed texture");
    813    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after clearing generateMipmap error");
    814    wtu.clearAndDrawUnitQuad(gl);
    815    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 1");
    816    compareRect(width, height, test.channels, uncompressedData, "NEAREST", test.error);
    817    // Test again with linear filtering.
    818    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    819    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    820    wtu.clearAndDrawUnitQuad(gl);
    821    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 2");
    822    compareRect(width, height, test.channels, uncompressedData, "LINEAR", test.error);
    823 
    824    if (!useTexStorage) {
    825        // It's not allowed to redefine textures defined via texStorage2D.
    826        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 1, data);
    827        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "non 0 border");
    828 
    829        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width + 4, height, 0, data);
    830        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    831        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height + 4, 0, data);
    832        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    833        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 4, height, 0, data);
    834        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    835        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 4, 0, data);
    836        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    837 
    838        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 1, height, 0, data);
    839        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    840        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 2, height, 0, data);
    841        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    842        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 1, 0, data);
    843        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    844        gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 2, 0, data);
    845        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    846 
    847        if (width == 4) {
    848            // The width/height of the implied base level must be a multiple of the block size.
    849            gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 1, height, 0, data);
    850            wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions for level > 0");
    851            gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 2, height, 0, data);
    852            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
    853        }
    854        if (height == 4) {
    855            // The width/height of the implied base level must be a multiple of the block size.
    856            gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 1, 0, data);
    857            wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions for level > 0");
    858            gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 2, 0, data);
    859            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
    860        }
    861    }
    862 
    863    // pick a wrong format that uses the same amount of data.
    864    var wrongFormat;
    865    switch (format) {
    866    case ext.COMPRESSED_RGB_S3TC_DXT1_EXT:
    867      wrongFormat = ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
    868      break;
    869    case ext.COMPRESSED_RGBA_S3TC_DXT1_EXT:
    870      wrongFormat = ext.COMPRESSED_RGB_S3TC_DXT1_EXT;
    871      break;
    872    case ext.COMPRESSED_RGBA_S3TC_DXT3_EXT:
    873      wrongFormat = ext.COMPRESSED_RGBA_S3TC_DXT5_EXT;
    874      break;
    875    case ext.COMPRESSED_RGBA_S3TC_DXT5_EXT:
    876      wrongFormat = ext.COMPRESSED_RGBA_S3TC_DXT3_EXT;
    877      break;
    878    case ext_rgtc.COMPRESSED_RED_RGTC1_EXT:
    879    case ext_rgtc.COMPRESSED_SIGNED_RED_RGTC1_EXT:
    880      wrongFormat = ext_rgtc.COMPRESSED_RED_GREEN_RGTC2_EXT;
    881      break;
    882    case ext_rgtc.COMPRESSED_RED_GREEN_RGTC2_EXT:
    883    case ext_rgtc.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
    884      wrongFormat = ext_rgtc.COMPRESSED_RED_RGTC1_EXT;
    885      break;
    886    }
    887 
    888    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, wrongFormat, data);
    889    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "format does not match");
    890 
    891    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 4, 0, width, height, format, data);
    892    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "dimension out of range");
    893    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 4, width, height, format, data);
    894    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "dimension out of range");
    895 
    896    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width + 4, height, format, data);
    897    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    898    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height + 4, format, data);
    899    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    900    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 4, height, format, data);
    901    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    902    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 4, format, data);
    903    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
    904 
    905    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 1, height, format, data);
    906    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    907    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 2, height, format, data);
    908    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    909    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 1, format, data);
    910    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    911    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 2, format, data);
    912    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    913 
    914    var subData = new Uint8Array(data.buffer, 0, getBlockSize(format));
    915 
    916    if (width == 8 && height == 8) {
    917        gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 1, 0, 4, 4, format, subData);
    918        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid offset");
    919        gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 1, 4, 4, format, subData);
    920        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid offset");
    921    }
    922 
    923    var stride = width * 4;
    924    for (var yoff = 0; yoff < height; yoff += 4) {
    925        for (var xoff = 0; xoff < width; xoff += 4) {
    926            copyRect(uncompressedData, 0, 0, xoff, yoff, 4, 4, stride);
    927            gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, xoff, yoff, 4, 4, format, subData);
    928            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
    929            // First test NEAREST filtering.
    930            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    931            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    932            wtu.clearAndDrawUnitQuad(gl);
    933            compareRect(width, height, test.channels, uncompressedData, "NEAREST", test.error);
    934            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    935            // Next test LINEAR filtering.
    936            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    937            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    938            wtu.clearAndDrawUnitQuad(gl);
    939            wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    940            compareRect(width, height, test.channels, uncompressedData, "LINEAR", test.error);
    941        }
    942    }
    943 }
    944 
    945 function testDXT5_RGBA_PBO() {
    946    debug("");
    947    debug("testing PBO uploads");
    948    var width = 8;
    949    var height = 8;
    950    var channels = 4;
    951    var data = img_8x8_rgba_dxt5;
    952    var format = ext.COMPRESSED_RGBA_S3TC_DXT5_EXT;
    953    var uncompressedData = uncompressDXT(width, height, data, format);
    954 
    955    var tex = gl.createTexture();
    956 
    957    // First, PBO size = image size
    958    var pbo1 = gl.createBuffer();
    959    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo1);
    960    gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data, gl.STATIC_DRAW);
    961    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a PBO");
    962 
    963    gl.bindTexture(gl.TEXTURE_2D, tex);
    964    gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
    965    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data.length, 0);
    966    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a texture from a PBO");
    967 
    968    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
    969    wtu.clearAndDrawUnitQuad(gl);
    970    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    971    compareRect(width, height, channels, uncompressedData, "NEAREST", DEFAULT_COLOR_ERROR);
    972 
    973    // Clear the texture before the next test
    974    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
    975    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, new Uint8Array(data.length));
    976 
    977    // Second, image is just a subrange of the PBO
    978    var pbo2 = gl.createBuffer();
    979    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo2);
    980    gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data.length*3, gl.STATIC_DRAW);
    981    gl.bufferSubData(gl.PIXEL_UNPACK_BUFFER, data.length, data);
    982    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a PBO subrange");
    983    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data.length, data.length);
    984    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading a texture from a PBO subrange");
    985    gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
    986    wtu.clearAndDrawUnitQuad(gl);
    987    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad");
    988    compareRect(width, height, channels, uncompressedData, "NEAREST", DEFAULT_COLOR_ERROR);
    989 }
    990 
    991 function compareRect(width, height, channels, expectedData, filteringMode, colorError) {
    992    var actual = new Uint8Array(width * height * 4);
    993    gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, actual);
    994    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "reading back pixels");
    995 
    996    var div = document.createElement("div");
    997    div.className = "testimages";
    998    ctu.insertCaptionedImg(div, "expected", ctu.makeScaledImage(width, height, width, expectedData, true));
    999    ctu.insertCaptionedImg(div, "actual", ctu.makeScaledImage(width, height, width, actual, true));
   1000    div.appendChild(document.createElement('br'));
   1001    document.getElementById("console").appendChild(div);
   1002 
   1003    var failed = false;
   1004    for (var yy = 0; yy < height; ++yy) {
   1005        for (var xx = 0; xx < width; ++xx) {
   1006            var offset = (yy * width + xx) * 4;
   1007            var expected = expectedData.slice(offset, offset + 4);
   1008            const was = actual.slice(offset, offset + 4);
   1009 
   1010            // Compare RGB values
   1011            for (var jj = 0; jj < 3; ++jj) {
   1012                if (Math.abs(was[jj] - expected[jj]) > colorError) {
   1013                    failed = true;
   1014                    testFailed(`RGB at (${xx}, ${yy}) expected: ${expected}` +
   1015                               ` +/- ${colorError}, was ${was}`);
   1016                    break;
   1017                }
   1018            }
   1019 
   1020            if (channels == 3) {
   1021                // BC1 RGB is allowed to be mapped to BC1 RGBA.
   1022                // In such a case, 3-color mode black value can be transparent:
   1023                // [0, 0, 0, 0] instead of [0, 0, 0, 255].
   1024 
   1025                if (actual[offset + 3] != expected[3]) {
   1026                    // Got non-opaque value for opaque format
   1027 
   1028                    // Check RGB values. Notice, that the condition here
   1029                    // is more permissive than needed since we don't have
   1030                    // compressed data at this point.
   1031                    if (was[0] == 0 &&
   1032                        was[1] == 0 &&
   1033                        was[2] == 0 &&
   1034                        was[3] == 0) {
   1035                            debug("<b>DXT1 RGB is mapped to DXT1 RGBA</b>");
   1036                    } else {
   1037                        failed = true;
   1038                        testFailed('Alpha at (' + xx + ', ' + yy +
   1039                                              ') expected: ' + expected[3] + ' was ' + was);
   1040                    }
   1041                }
   1042            } else {
   1043                // Compare Alpha values
   1044                // Acceptable interpolation error depends on endpoints:
   1045                // 1.0 / 65535.0 + 0.03 * max(abs(endpoint0 - endpoint1), abs(endpoint0_p - endpoint1_p))
   1046                // For simplicity, assume the worst case (e0 is 0.0, e1 is 1.0). After conversion to unorm8, it is 8.
   1047                if (Math.abs(was[3] - expected[3]) > 8) {
   1048                    failed = true;
   1049                    testFailed('Alpha at (' + xx + ', ' + yy +
   1050                                          ') expected: ' + expected + ' +/- 8 was ' + was);
   1051                }
   1052            }
   1053        }
   1054    }
   1055    if (!failed) {
   1056        testPassed("texture rendered correctly with " + filteringMode + " filtering");
   1057    }
   1058 }
   1059 
   1060 debug("");
   1061 var successfullyParsed = true;
   1062 </script>
   1063 <script src="../../js/js-test-post.js"></script>
   1064 
   1065 </body>
   1066 </html>