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.
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>
18 border:
1px solid black;
33 <div id=
"description"></div>
34 <canvas id=
"canvas" width=
"8" height=
"8" style=
"width: 8px; height: 8px;"></canvas>
35 <div id=
"console"></div>
38 description("This test verifies the functionality of the WEBGL_compressed_texture_s3tc_srgb extension, if it is available.");
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.
51 e0 < e1, so it uses 3-color mode
57 3: [ 0, 0, 0, 255] // for BC1 RGB
58 3: [ 0, 0, 0, 0] // for BC1 RGBA
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
75 Quantized alpha values
84 BC2 has only 4-color mode
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
105 Alpha block (aka DXT5A)
108 e0 > e1, so using 6 intermediate points
110 255, 0, 219, 182, 146, 109, 73, 36
120 BC3 has only 4-color mode
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
);
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,
175 var supportedFormats
;
178 testFailed("WebGL context does not exist");
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");
188 testPassed("No WEBGL_compressed_texture_s3tc_srgb support -- this is legal");
189 wtu
.runExtensionSupportedTest(gl
, "WEBGL_compressed_texture_s3tc_srgb", false);
191 testPassed("Successfully enabled WEBGL_compressed_texture_s3tc_srgb extension");
193 wtu
.runExtensionSupportedTest(gl
, "WEBGL_compressed_texture_s3tc_srgb", true);
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() {
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
);
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.
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) {
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" },
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" },
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() {
302 data
: img_4x4_rgba_dxt1
,
303 format
: ext
.COMPRESSED_SRGB_S3TC_DXT1_EXT
,
309 data
: img_8x8_rgba_dxt1
,
310 format
: ext
.COMPRESSED_SRGB_S3TC_DXT1_EXT
,
316 subData
: img_4x4_rgba_dxt1
319 testDXTTextures(tests
);
322 function testDXT1_SRGB_ALPHA() {
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
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
343 testDXTTextures(tests
);
346 function testDXT3_SRGB_ALPHA() {
351 data
: img_4x4_rgba_dxt3
,
352 format
: ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT
,
358 data
: img_8x8_rgba_dxt3
,
359 format
: ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT
,
365 subData
: img_4x4_rgba_dxt3
368 testDXTTextures(tests
);
371 function testDXT5_SRGB_ALPHA() {
376 data
: img_4x4_rgba_dxt5
,
377 format
: ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT
,
383 data
: img_8x8_rgba_dxt5
,
384 format
: ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT
,
390 subData
: img_4x4_rgba_dxt5
393 testDXTTextures(tests
);
396 function testDXTTextures(tests
) {
398 for (var ii
= 0; ii
< tests
.length
; ++ii
) {
399 testDXTTexture(tests
[ii
], false);
400 if (contextVersion
>= 2) {
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));
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
) {
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);
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
);
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.
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
];
464 case ext
.COMPRESSED_SRGB_S3TC_DXT1_EXT
:
467 case ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT
:
468 alpha
= (code
== 3 && !c0gtc1
) ? 0 : 255;
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);
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;
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
) {
497 alpha
= Math
.floor(((8 - alphaCode
) * alpha0
+ (alphaCode
- 1) * alpha1
) / 7.0 + 0.5);
515 alpha
= Math
.floor(((6 - alphaCode
) * alpha0
+ (alphaCode
- 1) * alpha1
) / 5.0 + 0.5);
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
);
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
);
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
];
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
);
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");
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");
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");
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.
704 case ext
.COMPRESSED_SRGB_S3TC_DXT1_EXT
:
705 wrongFormat
= ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT
;
707 case ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT
:
708 wrongFormat
= ext
.COMPRESSED_SRGB_S3TC_DXT1_EXT
;
710 case ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT
:
711 wrongFormat
= ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT
;
713 case ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT
:
714 wrongFormat
= ext
.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT
;
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() {
777 debug("testing PBO uploads");
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
) {
824 if (value
<= 0.04045) {
825 value
= value
/ 12.92;
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
);
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
];
860 testFailed('RGB at (' + xx
+ ', ' + yy
+
861 ') expected: ' + expected
+ ' ± 9 was ' + was
);
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>");
883 testFailed('Alpha at (' + xx
+ ', ' + yy
+
884 ') expected: ' + expected
[3] + ' was ' + actual
[offset
+ 3]);
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();
895 testFailed('Alpha at (' + xx
+ ', ' + yy
+
896 ') expected: ' + expected
+ ' ± 8 was ' + was
);
902 testPassed("texture rendered correctly with " + filteringMode
+ " filtering");
907 var successfullyParsed
= true;
909 <script src=
"../../js/js-test-post.js"></script>