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>