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 }