tor-browser

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

tex-image-and-sub-image-2d-with-canvas.js (20938B)


      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 function generateTest(internalFormat, pixelFormat, pixelType, prologue, resourcePath, defaultContextVersion) {
      8    var wtu = WebGLTestUtils;
      9    var tiu = TexImageUtils;
     10    var gl = null;
     11    var successfullyParsed = false;
     12    var whiteColor = [255, 255, 255, 255];
     13    var redColor = [255, 0, 0, 255];
     14    var greenColor = [0, 255, 0, 255];
     15    var semiTransparentRedColor = [127, 0, 0, 127];
     16    var semiTransparentGreenColor = [0, 127, 0, 127];
     17    var repeatCount;
     18 
     19    function replicateRedChannel(color)
     20    {
     21        color[1] = color[0];
     22        color[2] = color[0];
     23    }
     24 
     25    function zapColorChannels(color)
     26    {
     27        color[0] = 0;
     28        color[1] = 0;
     29        color[2] = 0;
     30    }
     31 
     32    function setAlphaChannelTo1(color)
     33    {
     34        color[3] = 255;
     35    }
     36 
     37    function replicateAllRedChannels()
     38    {
     39        replicateRedChannel(redColor);
     40        replicateRedChannel(semiTransparentRedColor);
     41        replicateRedChannel(greenColor);
     42        replicateRedChannel(semiTransparentGreenColor);
     43    }
     44 
     45    function setAllAlphaChannelsTo1()
     46    {
     47        setAlphaChannelTo1(redColor);
     48        setAlphaChannelTo1(semiTransparentRedColor);
     49        setAlphaChannelTo1(greenColor);
     50        setAlphaChannelTo1(semiTransparentGreenColor);
     51    }
     52 
     53    function repeatCountForTextureFormat(internalFormat, pixelFormat, pixelType)
     54    {
     55        // There were bugs in early WebGL 1.0 implementations when repeatedly uploading canvas
     56        // elements into textures. In response, this test was changed into a regression test by
     57        // repeating all of the cases multiple times. Unfortunately, this means that adding a new
     58        // case above significantly increases the run time of the test suite. The problem is made
     59        // even worse by the addition of many more texture formats in WebGL 2.0.
     60        //
     61        // Doing repeated runs with just a couple of WebGL 1.0's supported texture formats acts as a
     62        // sufficient regression test for the old bugs. For this reason the test has been changed to
     63        // only repeat for those texture formats.
     64        if ((internalFormat == 'RGBA' && pixelFormat == 'RGBA' && pixelType == 'UNSIGNED_BYTE') ||
     65            (internalFormat == 'RGB' && pixelFormat == 'RGB' && pixelType == 'UNSIGNED_BYTE')) {
     66            return 4;
     67        }
     68 
     69        return 1;
     70    }
     71 
     72    function init()
     73    {
     74        description('Verify texImage2D and texSubImage2D code paths taking canvas elements (' + internalFormat + '/' + pixelFormat + '/' + pixelType + ')');
     75 
     76        // Set the default context version while still allowing the webglVersion URL query string to override it.
     77        wtu.setDefault3DContextVersion(defaultContextVersion);
     78        gl = wtu.create3DContext("example");
     79 
     80        if (!prologue(gl)) {
     81            finishTest();
     82            return;
     83        }
     84 
     85        repeatCount = repeatCountForTextureFormat(internalFormat, pixelFormat, pixelType);
     86 
     87        switch (gl[pixelFormat]) {
     88          case gl.RED:
     89          case gl.RED_INTEGER:
     90            // Zap green and blue channels.
     91            whiteColor[1] = 0;
     92            whiteColor[2] = 0;
     93            greenColor[1] = 0;
     94            semiTransparentGreenColor[1] = 0;
     95            // Alpha channel is 1.0.
     96            setAllAlphaChannelsTo1();
     97            break;
     98          case gl.RG:
     99          case gl.RG_INTEGER:
    100            // Zap blue channel.
    101            whiteColor[2] = 0;
    102            // Alpha channel is 1.0.
    103            setAllAlphaChannelsTo1();
    104            break;
    105          case gl.LUMINANCE:
    106            // Replicate red channels.
    107            replicateAllRedChannels();
    108            // Alpha channel is 1.0.
    109            setAllAlphaChannelsTo1();
    110            break;
    111          case gl.ALPHA:
    112            // Red, green and blue channels are all 0.0.
    113            zapColorChannels(redColor);
    114            zapColorChannels(semiTransparentRedColor);
    115            zapColorChannels(greenColor);
    116            zapColorChannels(semiTransparentGreenColor);
    117            zapColorChannels(whiteColor);
    118            break;
    119          case gl.LUMINANCE_ALPHA:
    120            // Replicate red channels.
    121            replicateAllRedChannels();
    122            break;
    123          case gl.RGB:
    124          case gl.RGB_INTEGER:
    125            // Alpha channel is 1.0.
    126            setAllAlphaChannelsTo1();
    127            break;
    128          default:
    129            break;
    130        }
    131 
    132        switch (gl[internalFormat]) {
    133          case gl.SRGB8:
    134          case gl.SRGB8_ALPHA8:
    135            semiTransparentRedColor = wtu.sRGBToLinear(semiTransparentRedColor);
    136            semiTransparentGreenColor = wtu.sRGBToLinear(semiTransparentGreenColor);
    137            break;
    138          case gl.RGBA8UI:
    139            // For int and uint textures, TexImageUtils outputs the maximum value (in this case,
    140            // 255) for the alpha channel all the time because of differences in behavior when
    141            // sampling integer textures with and without alpha channels. Since changing this
    142            // behavior may have large impact across the test suite, leave it as is for now.
    143            setAllAlphaChannelsTo1();
    144            break;
    145        }
    146 
    147        gl.clearColor(0,0,0,1);
    148        gl.clearDepth(1);
    149 
    150        runTest();
    151    }
    152 
    153    function setCanvasToRedGreen(ctx) {
    154      var width = ctx.canvas.width;
    155      var height = ctx.canvas.height;
    156      var halfHeight = Math.floor(height / 2);
    157      ctx.clearRect(0, 0, width, height);
    158      ctx.fillStyle = "#ff0000";
    159      ctx.fillRect(0, 0, width, halfHeight);
    160      ctx.fillStyle = "#00ff00";
    161      ctx.fillRect(0, halfHeight, width, height - halfHeight);
    162    }
    163 
    164    function setCanvasToSemiTransparentRedGreen(ctx) {
    165      var width = ctx.canvas.width;
    166      var height = ctx.canvas.height;
    167      var halfHeight = Math.floor(height / 2);
    168      ctx.clearRect(0, 0, width, height);
    169      ctx.fillStyle = "rgba(127, 0, 0, 0.5)";
    170      ctx.fillRect(0, 0, width, halfHeight);
    171      ctx.fillStyle = "rgba(0, 127, 0, 0.5)";
    172      ctx.fillRect(0, halfHeight, width, height - halfHeight);
    173    }
    174 
    175    function drawTextInCanvas(ctx, bindingTarget) {
    176      var width = ctx.canvas.width;
    177      var height = ctx.canvas.height;
    178      ctx.fillStyle = "#ffffff";
    179      ctx.fillRect(0, 0, width, height);
    180      ctx.font = '20pt Arial';
    181      ctx.fillStyle = 'black';
    182      ctx.textAlign = "center";
    183      ctx.textBaseline = "middle";
    184      ctx.fillText("1234567890", width / 2, height / 4);
    185    }
    186 
    187    function setCanvasTo257x257(ctx, bindingTarget) {
    188      ctx.canvas.width = 257;
    189      ctx.canvas.height = 257;
    190      setCanvasToRedGreen(ctx);
    191    }
    192 
    193    function setCanvasTo257x257SemiTransparent(ctx, bindingTarget) {
    194      ctx.canvas.width = 257;
    195      ctx.canvas.height = 257;
    196      setCanvasToSemiTransparentRedGreen(ctx);
    197    }
    198 
    199    function setCanvasToMin(ctx, bindingTarget) {
    200      if (bindingTarget == gl.TEXTURE_CUBE_MAP) {
    201        // cube map texture must be square.
    202        ctx.canvas.width = 2;
    203      } else {
    204        ctx.canvas.width = 1;
    205      }
    206      ctx.canvas.height = 2;
    207      setCanvasToRedGreen(ctx);
    208    }
    209 
    210    function setCanvasToMinSemiTransparent(ctx, bindingTarget) {
    211      if (bindingTarget == gl.TEXTURE_CUBE_MAP) {
    212        // cube map texture must be square.
    213        ctx.canvas.width = 2;
    214      } else {
    215        ctx.canvas.width = 1;
    216      }
    217      ctx.canvas.height = 2;
    218      setCanvasToSemiTransparentRedGreen(ctx);
    219    }
    220 
    221    function runOneIteration(canvas, useTexSubImage2D, flipY, semiTransparent, program, bindingTarget, opt_texture, opt_fontTest)
    222    {
    223        var objType = 'canvas';
    224        if (canvas.transferToImageBitmap)
    225          objType = 'OffscreenCanvas';
    226        debug('Testing ' + (useTexSubImage2D ? 'texSubImage2D' : 'texImage2D') +
    227              ' with flipY=' + flipY + ' bindingTarget=' + (bindingTarget == gl.TEXTURE_2D ? 'TEXTURE_2D' : 'TEXTURE_CUBE_MAP') +
    228              ' canvas size: ' + canvas.width + 'x' + canvas.height +
    229              ' source object type: ' + objType +
    230              (opt_fontTest ? " with fonts" : " with" + (semiTransparent ? " semi-transparent" : "") + " red-green"));
    231        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    232        if (!opt_texture) {
    233            var texture = gl.createTexture();
    234            // Bind the texture to texture unit 0
    235            gl.bindTexture(bindingTarget, texture);
    236            // Set up texture parameters
    237            gl.texParameteri(bindingTarget, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    238            gl.texParameteri(bindingTarget, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    239            gl.texParameteri(bindingTarget, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    240            gl.texParameteri(bindingTarget, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    241        } else {
    242            var texture = opt_texture;
    243        }
    244        // Set up pixel store parameters
    245        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
    246        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
    247        wtu.failIfGLError(gl, 'gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);');
    248        var targets = [gl.TEXTURE_2D];
    249        if (bindingTarget == gl.TEXTURE_CUBE_MAP) {
    250            targets = [gl.TEXTURE_CUBE_MAP_POSITIVE_X,
    251                       gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
    252                       gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
    253                       gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
    254                       gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
    255                       gl.TEXTURE_CUBE_MAP_NEGATIVE_Z];
    256        }
    257        // Upload the image into the texture
    258        for (var tt = 0; tt < targets.length; ++tt) {
    259            // Initialize the texture to black first
    260            if (useTexSubImage2D) {
    261                gl.texImage2D(targets[tt], 0, gl[internalFormat], canvas.width, canvas.height, 0,
    262                              gl[pixelFormat], gl[pixelType], null);
    263                gl.texSubImage2D(targets[tt], 0, 0, 0, gl[pixelFormat], gl[pixelType], canvas);
    264            } else {
    265                gl.texImage2D(targets[tt], 0, gl[internalFormat], gl[pixelFormat], gl[pixelType], canvas);
    266            }
    267        }
    268 
    269        var width = gl.canvas.width;
    270        var height = gl.canvas.height;
    271        var halfWidth = Math.floor(width / 2);
    272        var halfHeight = Math.floor(height / 2);
    273        var top = flipY ? 0 : (height - halfHeight);
    274        var bottom = flipY ? (height - halfHeight) : 0;
    275 
    276        var loc;
    277        if (bindingTarget == gl.TEXTURE_CUBE_MAP) {
    278            loc = gl.getUniformLocation(program, "face");
    279        }
    280 
    281        for (var tt = 0; tt < targets.length; ++tt) {
    282            if (bindingTarget == gl.TEXTURE_CUBE_MAP) {
    283                gl.uniform1i(loc, targets[tt]);
    284            }
    285            // Draw the triangles
    286            wtu.clearAndDrawUnitQuad(gl, [0, 255, 0, 255]);
    287 
    288            if (opt_fontTest) {
    289                // check half is a solid color.
    290                wtu.checkCanvasRect(
    291                      gl, 0, top, width, halfHeight,
    292                      whiteColor,
    293                      "should be white");
    294                // check other half is not a solid color.
    295                wtu.checkCanvasRectColor(
    296                      gl, 0, bottom, width, halfHeight,
    297                      whiteColor, 0,
    298                      function() {
    299                        testFailed("font missing");
    300                      },
    301                      function() {
    302                        testPassed("font rendered");
    303                      },
    304                      debug);
    305            } else {
    306                var localRed   = semiTransparent ? semiTransparentRedColor : redColor;
    307                var localGreen = semiTransparent ? semiTransparentGreenColor : greenColor;
    308 
    309                // Allow a tolerance for premultiplication/unmultiplication, especially for texture
    310                // formats with lower bit depths.
    311                var tolerance = 0;
    312                if (semiTransparent) {
    313                    tolerance = 3;
    314                    if (pixelType == 'UNSIGNED_SHORT_5_6_5' || internalFormat == 'RGB565') {
    315                        tolerance = 6;
    316                    } else if (pixelType == 'UNSIGNED_SHORT_4_4_4_4' || internalFormat == 'RGBA4') {
    317                        tolerance = 9;
    318                    } else if (pixelType == 'UNSIGNED_SHORT_5_5_5_1' || internalFormat == 'RGB5_A1') {
    319                        // Semi-transparent values are allowed to convert to either 1 or 0 for this
    320                        // single-bit alpha format per OpenGL ES 3.0.5 section 2.1.6.2, "Conversion
    321                        // from Floating-Point to Normalized Fixed-Point". Ignore alpha for these
    322                        // tests.
    323                        tolerance = 6;
    324                        localRed = localRed.slice(0, 3);
    325                        localGreen = localGreen.slice(0, 3);
    326                    } else if (internalFormat == 'RGB10_A2') {
    327                        // The alpha channel is too low-resolution for any meaningful comparisons.
    328                        localRed = localRed.slice(0, 3);
    329                        localGreen = localGreen.slice(0, 3);
    330                    }
    331                }
    332 
    333                // Check the top and bottom halves and make sure they have the right color.
    334                debug("Checking " + (flipY ? "top" : "bottom"));
    335                wtu.checkCanvasRect(gl, 0, bottom, width, halfHeight, localRed,
    336                                    "shouldBe " + localRed, tolerance);
    337                debug("Checking " + (flipY ? "bottom" : "top"));
    338                wtu.checkCanvasRect(gl, 0, top, width, halfHeight, localGreen,
    339                                    "shouldBe " + localGreen, tolerance);
    340            }
    341 
    342            if (!useTexSubImage2D && pixelFormat == "RGBA") {
    343                if (pixelType == "FLOAT") {
    344                    // Attempt to set a pixel in the texture to ensure the texture was
    345                    // actually created with floats. Regression test for http://crbug.com/484968
    346                    var pixels = new Float32Array([1000.0, 1000.0, 1000.0, 1000.0]);
    347                    gl.texSubImage2D(targets[tt], 0, 0, 0, 1, 1, gl[pixelFormat], gl[pixelType], pixels);
    348                    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Texture should be backed by floats");
    349                } else if (pixelType == "HALF_FLOAT_OES" || pixelType == "HALF_FLOAT") {
    350                    // Attempt to set a pixel in the texture to ensure the texture was
    351                    // actually created with half-floats. Regression test for http://crbug.com/484968
    352                    var halfFloatTenK = 0x70E2; // Half float 10000
    353                    var pixels = new Uint16Array([halfFloatTenK, halfFloatTenK, halfFloatTenK, halfFloatTenK]);
    354                    gl.texSubImage2D(targets[tt], 0, 0, 0, 1, 1, gl[pixelFormat], gl[pixelType], pixels);
    355                    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Texture should be backed by half-floats");
    356                }
    357            }
    358        }
    359 
    360        if (false) {
    361          var m = wtu.makeImageFromCanvas(gl.canvas);
    362          document.getElementById("console").appendChild(m);
    363          document.getElementById("console").appendChild(document.createElement("hr"));
    364        }
    365 
    366        return texture;
    367    }
    368 
    369    function runTest()
    370    {
    371        var canvas = document.createElement('canvas');
    372 
    373        var cases = [
    374            { canvas: canvas, sub: false, flipY: true,  semiTransparent: false, font: false, init: setCanvasToMin },
    375            { canvas: canvas, sub: false, flipY: false, semiTransparent: false, font: false },
    376            { canvas: canvas, sub: true,  flipY: true,  semiTransparent: false, font: false },
    377            { canvas: canvas, sub: true,  flipY: false, semiTransparent: false, font: false },
    378            { canvas: canvas, sub: false, flipY: true,  semiTransparent: true,  font: false, init: setCanvasToMinSemiTransparent },
    379            { canvas: canvas, sub: false, flipY: false, semiTransparent: true,  font: false },
    380            { canvas: canvas, sub: true,  flipY: true,  semiTransparent: true,  font: false },
    381            { canvas: canvas, sub: true,  flipY: false, semiTransparent: true,  font: false },
    382            { canvas: canvas, sub: false, flipY: true,  semiTransparent: false, font: false, init: setCanvasTo257x257 },
    383            { canvas: canvas, sub: false, flipY: false, semiTransparent: false, font: false },
    384            { canvas: canvas, sub: true,  flipY: true,  semiTransparent: false, font: false },
    385            { canvas: canvas, sub: true,  flipY: false, semiTransparent: false, font: false },
    386            { canvas: canvas, sub: false, flipY: true,  semiTransparent: true,  font: false, init: setCanvasTo257x257SemiTransparent },
    387            { canvas: canvas, sub: false, flipY: false, semiTransparent: true,  font: false },
    388            { canvas: canvas, sub: true,  flipY: true,  semiTransparent: true,  font: false },
    389            { canvas: canvas, sub: true,  flipY: false, semiTransparent: true,  font: false },
    390        ];
    391 
    392        // The font tests don't work with ALPHA-only textures since they draw to the color channels.
    393        if (internalFormat != 'ALPHA') {
    394            cases = cases.concat([
    395                { canvas: canvas, sub: false, flipY: true,  semiTransparent: false, font: true, init: drawTextInCanvas },
    396                { canvas: canvas, sub: false, flipY: false, semiTransparent: false, font: true },
    397                { canvas: canvas, sub: true,  flipY: true,  semiTransparent: false, font: true },
    398                { canvas: canvas, sub: true,  flipY: false, semiTransparent: false, font: true },
    399            ]);
    400        }
    401 
    402        if (window.OffscreenCanvas) {
    403            var offscreenCanvas = new OffscreenCanvas(1, 1);
    404            cases = cases.concat([
    405                { canvas: offscreenCanvas, sub: false, flipY: true,  semiTransparent: false, font: false, init: setCanvasToMin },
    406                { canvas: offscreenCanvas, sub: false, flipY: false, semiTransparent: false, font: false },
    407                { canvas: offscreenCanvas, sub: true,  flipY: true,  semiTransparent: false, font: false },
    408                { canvas: offscreenCanvas, sub: true,  flipY: false, semiTransparent: false, font: false },
    409                { canvas: offscreenCanvas, sub: false, flipY: true,  semiTransparent: true,  font: false, init: setCanvasToMinSemiTransparent },
    410                { canvas: offscreenCanvas, sub: false, flipY: false, semiTransparent: true,  font: false },
    411                { canvas: offscreenCanvas, sub: true,  flipY: true,  semiTransparent: true,  font: false },
    412                { canvas: offscreenCanvas, sub: true,  flipY: false, semiTransparent: true,  font: false },
    413            ]);
    414        }
    415 
    416        function runTexImageTest(bindingTarget) {
    417            var program;
    418            if (bindingTarget == gl.TEXTURE_2D) {
    419                program = tiu.setupTexturedQuad(gl, internalFormat);
    420            } else {
    421                program = tiu.setupTexturedQuadWithCubeMap(gl, internalFormat);
    422            }
    423 
    424            return new Promise(function(resolve, reject) {
    425                var count = repeatCount;
    426                var caseNdx = 0;
    427                var texture = undefined;
    428                function runNextTest() {
    429                    var c = cases[caseNdx];
    430                    var imageDataBefore = null;
    431                    if (c.init) {
    432                      c.init(c.canvas.getContext('2d'), bindingTarget);
    433                    }
    434                    texture = runOneIteration(c.canvas, c.sub, c.flipY, c.semiTransparent, program, bindingTarget, texture, c.font);
    435                    // for the first 2 iterations always make a new texture.
    436                    if (count < 2) {
    437                      gl.deleteTexture(texture);
    438                      texture = undefined;
    439                    }
    440                    ++caseNdx;
    441                    if (caseNdx == cases.length) {
    442                        caseNdx = 0;
    443                        --count;
    444                        if (!count) {
    445                            resolve("SUCCESS");
    446                            return;
    447                        }
    448                    }
    449                    // While we are working with Canvases, it's really unlikely that
    450                    // waiting for composition will change anything here, and it's much
    451                    // slower, so just dispatchPromise. If we want to test with composites,
    452                    // we should test a more narrow subset of tests.
    453                    wtu.dispatchPromise(runNextTest);
    454                }
    455                runNextTest();
    456            });
    457        }
    458 
    459        runTexImageTest(gl.TEXTURE_2D).then(function(val) {
    460            runTexImageTest(gl.TEXTURE_CUBE_MAP).then(function(val) {
    461                wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
    462                finishTest();
    463            });
    464        });
    465    }
    466 
    467    return init;
    468 }