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