Backed out changeset b462e7b742d8 (bug 1908261) for causing multiple reftest failures...
[gecko.git] / dom / canvas / test / webgl-conf / checkout / conformance / extensions / s3tc-and-rgtc.html
blob3b725ffe229710db868c84ae907caaba795eff5f
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 and EXT_texture_compression_rgtc 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 extension, if it is available. It also tests the related formats from the EXT_texture_compression_rgtc extension.");
40 debug("");
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;
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
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,
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,
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,
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,
164 8x8 block endpoints use half-intensity values (appear darker than 4x4)
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
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
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
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,
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,
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,
212 var name;
213 var supportedFormats;
215 if (!gl) {
216 testFailed("WebGL context does not exist");
217 } else {
218 testPassed("WebGL context exists");
220 // Run tests with extension disabled
221 ctu.testCompressedFormatsUnavailableWhenExtensionDisabled(gl, validFormats, expectedByteLength, 4);
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");
231 wtu.runExtensionSupportedTest(gl, "WEBGL_compressed_texture_s3tc", true);
232 runTestExtension();
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();
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;
248 return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;
251 function getBlockDimensions(format) {
252 return {width: 4, height: 4};
255 function runTestExtension() {
256 debug("");
257 debug("Testing WEBGL_compressed_texture_s3tc");
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);
265 // Test each format
266 testDXT1_RGB();
267 testDXT1_RGBA();
268 testDXT3_RGBA();
269 testDXT5_RGBA();
271 // Test compressed PBOs with a single format
272 if (contextVersion >= 2) {
273 testDXT5_RGBA_PBO();
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,
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" },
290 ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 16, 16,
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" },
304 if (contextVersion >= 2) {
305 debug("");
306 debug("Testing NPOT textures");
307 ctu.testTexImageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
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" },
330 debug("");
331 debug("Testing partial updates");
332 ctu.testTexSubImageDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions, 12, 12,
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" },
346 debug("");
347 debug("Testing immutable NPOT textures");
348 ctu.testTexStorageLevelDimensions(gl, ext, validFormats, expectedByteLength, getBlockDimensions,
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" },
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,
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,
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,
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.
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,
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,
415 testDXTTextures(tests);
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,
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
440 testDXTTextures(tests);
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,
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,
464 testDXTTextures(tests);
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,
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
489 testDXTTextures(tests);
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,
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
514 testDXTTextures(tests);
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);
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.
534 function make565(src, offset) {
535 return src[offset + 0] + (src[offset + 1] << 8);
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));
542 return [
543 replicateBits((c >> 11) & 0x1F, 5),
544 replicateBits((c >> 5) & 0x3F, 6),
545 replicateBits((c >> 0) & 0x1F, 5),
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
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);
558 return r;
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;
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]
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);
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];
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;
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;
641 return alpha;
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:
653 var alpha0 = src[srcOffset + yy * 2 + (xx >> 1)];
654 var alpha1 = (alpha0 >> ((xx % 2) * 4)) & 0xF;
655 alpha = alpha1 | (alpha1 << 4);
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";
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;
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;
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;
696 function uncompressDXT(width, height, data, format) {
697 if (width % 4 || height % 4) throw "bad width or height";
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);
710 return dest;
713 function uncompressDXTIntoSubRegion(width, height, subX0, subY0, subWidth, subHeight, data, format)
715 if (width % 4 || height % 4 || subX0 % 4 || subY0 % 4 || subWidth % 4 || subHeight % 4)
716 throw "bad dimension";
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;
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);
735 return dest;
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];
746 srcOffset += stride;
747 dstOffset += stride;
751 function testDXTTexture(test, useTexStorage) {
752 test.error = test.error || DEFAULT_COLOR_ERROR;
754 var data = new Uint8Array(test.data);
755 var width = test.width;
756 var height = test.height;
757 var format = test.format;
759 var uncompressedData = uncompressDXT(width, height, data, format);
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"));
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);
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");
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);
794 // Clean up and recover
795 gl.deleteTexture(tex1);
796 gl.bindTexture(gl.TEXTURE_2D, tex);
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");
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);
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");
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");
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");
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");
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");
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;
888 gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, wrongFormat, data);
889 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "format does not match");
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");
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");
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");
914 var subData = new Uint8Array(data.buffer, 0, getBlockSize(format));
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");
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);
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);
955 var tex = gl.createTexture();
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");
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");
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);
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));
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);
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");
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);
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);
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;
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].
1025 if (actual[offset + 3] != expected[3]) {
1026 // Got non-opaque value for opaque format
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);
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);
1055 if (!failed) {
1056 testPassed("texture rendered correctly with " + filteringMode + " filtering");
1060 debug("");
1061 var successfullyParsed = true;
1062 </script>
1063 <script src="../../js/js-test-post.js"></script>
1065 </body>
1066 </html>