Backed out changeset b462e7b742d8 (bug 1908261) for causing multiple reftest failures...
[gecko.git] / dom / canvas / test / webgl-conf / checkout / conformance / extensions / webgl-compressed-texture-s3tc-srgb.html
blob91fc3f0b1c6aa1fa12222f53c1cd6a52d1290f24
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 -->
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;
22 .testimages br {
23 clear: both;
26 .testimages > div {
27 float: left;
28 margin: 1em;
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.");
40 debug("");
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.
48 BC1 (DXT1) block
49 e0 = [ 0, 255, 0]
50 e1 = [255, 0, 0]
51 e0 < e1, so it uses 3-color mode
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
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.
68 var img_4x4_rgba_dxt1 = new Uint8Array([
69 0xE0, 0x07, 0x00, 0xF8, 0x1B, 0x1A, 0x15, 0x00
70 ]);
73 BC2 (DXT3) block
75 Quantized alpha values
76 0 1 2 3
77 4 5 6 7
78 8 9 A B
79 C D E F
81 RGB block
82 e0 = [255, 0, 0]
83 e1 = [ 0, 255, 0]
84 BC2 has only 4-color mode
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
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
103 BC3 (DXT5) block
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
117 RGB block
118 e0 = [255, 0, 0]
119 e1 = [ 0, 255, 0]
120 BC3 has only 4-color mode
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
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
139 8x8 block endpoints use half-intensity values (appear darker than 4x4)
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
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
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
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,
174 var name;
175 var supportedFormats;
177 if (!gl) {
178 testFailed("WebGL context does not exist");
179 } else {
180 testPassed("WebGL context exists");
182 // Run tests with extension disabled
183 ctu.testCompressedFormatsUnavailableWhenExtensionDisabled(gl, validFormats, expectedByteLength, 4);
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");
193 wtu.runExtensionSupportedTest(gl, "WEBGL_compressed_texture_s3tc_srgb", true);
194 runTestExtension();
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;
202 return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;
205 function getBlockDimensions(format) {
206 return {width: 4, height: 4};
209 function runTestExtension() {
210 debug("");
211 debug("Testing WEBGL_compressed_texture_s3tc_srgb");
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);
219 // Test each format
220 testDXT1_SRGB();
221 testDXT1_SRGB_ALPHA();
222 testDXT3_SRGB_ALPHA();
223 testDXT5_SRGB_ALPHA();
225 // Test compressed PBOs with a single format
226 if (contextVersion >= 2) {
227 testDXT5_SRGB_ALPHA_PBO();
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,
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" },
244 ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 16, 16,
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" },
258 if (contextVersion >= 2) {
259 debug("");
260 debug("Testing NPOT textures");
261 ctu.testTexImageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
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" },
269 debug("");
270 debug("Testing partial updates");
271 ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 12, 12,
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" },
285 debug("");
286 debug("Testing immutable NPOT textures");
287 ctu.testTexStorageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
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" },
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,
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
319 testDXTTextures(tests);
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,
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,
343 testDXTTextures(tests);
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,
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
368 testDXTTextures(tests);
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,
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
393 testDXTTextures(tests);
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);
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.
413 function make565(src, offset) {
414 return src[offset + 0] + (src[offset + 1] << 8);
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));
421 return [
422 replicateBits((c >> 11) & 0x1F, 5),
423 replicateBits((c >> 5) & 0x3F, 6),
424 replicateBits((c >> 0) & 0x1F, 5),
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
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);
437 return r;
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]
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:
472 var alpha0 = src[srcOffset + yy * 2 + (xx >> 1)];
473 var alpha1 = (alpha0 >> ((xx % 2) * 4)) & 0xF;
474 alpha = alpha1 | (alpha1 << 4);
476 break;
477 case ext.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
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;
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;
520 break;
521 default:
522 throw "bad format";
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;
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;
538 function uncompressDXTSRGB(width, height, data, format) {
539 if (width % 4 || height % 4) throw "bad width or height";
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);
552 return dest;
555 function uncompressDXTIntoSubRegionSRGB(width, height, subX0, subY0, subWidth, subHeight, data, format)
557 if (width % 4 || height % 4 || subX0 % 4 || subY0 % 4 || subWidth % 4 || subHeight % 4)
558 throw "bad dimension";
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;
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);
575 return dest;
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];
586 srcOffset += stride;
587 dstOffset += stride;
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;
597 var uncompressedData = uncompressDXTSRGB(width, height, data, format);
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"));
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);
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");
628 wtu.clearAndDrawUnitQuad(gl);
629 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawing unit quad 1");
630 compareRect(width, height, test.channels, uncompressedDataSub, "NEAREST");
632 // Clean up and recover
633 gl.deleteTexture(tex1);
634 gl.bindTexture(gl.TEXTURE_2D, tex);
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");
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");
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");
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");
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");
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");
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");
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;
718 gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, wrongFormat, data);
719 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "format does not match");
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");
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");
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");
744 var subData = new Uint8Array(data.buffer, 0, getBlockSize(format));
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");
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");
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);
785 var tex = gl.createTexture();
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");
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");
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");
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));
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");
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);
829 return Math.trunc(value * 255 + 0.5);
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");
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);
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];
859 failed = true;
860 testFailed('RGB at (' + xx + ', ' + yy +
861 ') expected: ' + expected + ' ± 9 was ' + was);
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].
870 if (actual[offset + 3] != expected[3]) {
871 // Got non-opaque value for opaque format
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]);
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);
901 if (!failed) {
902 testPassed("texture rendered correctly with " + filteringMode + " filtering");
906 debug("");
907 var successfullyParsed = true;
908 </script>
909 <script src="../../js/js-test-post.js"></script>
911 </body>
912 </html>