4 ** Copyright (c) 2012 The Khronos Group Inc.
6 ** Permission is hereby granted, free of charge, to any person obtaining a
7 ** copy of this software and/or associated documentation files (the
8 ** "Materials"), to deal in the Materials without restriction, including
9 ** without limitation the rights to use, copy, modify, merge, publish,
10 ** distribute, sublicense, and/or sell copies of the Materials, and to
11 ** permit persons to whom the Materials are furnished to do so, subject to
12 ** the following conditions:
14 ** The above copyright notice and this permission notice shall be included
15 ** in all copies or substantial portions of the Materials.
17 ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20 ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22 ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23 ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
31 <meta charset="utf-8">
32 <link rel="stylesheet" href="../../resources/js-test-style.css"/>
33 <script src="../../resources/js-test-pre.js"></script>
34 <script src="../resources/pnglib.js"></script>
35 <script src="../resources/webgl-test-utils.js"></script>
39 var wtu = WebGLTestUtils;
41 var textureLoc = null;
42 var successfullyParsed = false;
44 //----------------------------------------------------------------------
57 // This must remain the last mode.
63 description('Verify texImage2D and texSubImage2D code paths taking both HTML and user-specified data with all format/type combinations');
65 var canvas = document.getElementById("example");
66 gl = wtu.create3DContext(canvas);
67 gl.disable(gl.DITHER);
68 var program = wtu.setupTexturedQuad(gl);
70 gl.clearColor(0,0,0,1);
74 textureLoc = gl.getUniformLocation(program, "tex");
79 function initializeTests()
81 // Verify that uploading to packed pixel formats performs the
82 // required conversion and associated loss of precision.
83 for (var dataMode = 0; dataMode < DataMode.NUM_HTML_MODES; ++dataMode) {
84 for (var useTexSubImage2D = 0; useTexSubImage2D < 2; ++useTexSubImage2D) {
87 useTexSubImage2D: !!useTexSubImage2D,
90 generator: generateOpaqueGrayscaleRamp,
91 premultiplyAlpha: false,
93 type: gl.UNSIGNED_BYTE,
94 verifier: allChannelsIncreaseByNoMoreThan,
97 description: "RGBA/UNSIGNED_BYTE should maintain full precision of data"
101 useTexSubImage2D: !!useTexSubImage2D,
104 generator: generateOpaqueGrayscaleRamp,
105 premultiplyAlpha: false,
107 type: gl.UNSIGNED_SHORT_4_4_4_4,
108 verifier: allChannelsIncreaseByAtLeast,
111 description: "RGBA/UNSIGNED_SHORT_4_4_4_4 must drop low four bits of precision"
115 useTexSubImage2D: !!useTexSubImage2D,
118 generator: generateOpaqueGrayscaleRamp,
119 premultiplyAlpha: false,
121 type: gl.UNSIGNED_SHORT_5_5_5_1,
122 verifier: allChannelsIncreaseByAtLeast,
125 description: "RGBA/UNSIGNED_SHORT_5_5_5_1 must drop low three bits of precision"
129 useTexSubImage2D: !!useTexSubImage2D,
132 generator: generateOpaqueGrayscaleRamp,
133 premultiplyAlpha: false,
135 type: gl.UNSIGNED_BYTE,
136 verifier: allChannelsIncreaseByNoMoreThan,
139 description: "RGB/UNSIGNED_BYTE should maintain full precision of data"
143 useTexSubImage2D: !!useTexSubImage2D,
146 generator: generateOpaqueGrayscaleRamp,
147 premultiplyAlpha: false,
149 type: gl.UNSIGNED_SHORT_5_6_5,
150 verifier: allChannelsIncreaseByAtLeast,
153 description: "RGB/UNSIGNED_SHORT_5_6_5 must drop low two or three bits of precision"
157 useTexSubImage2D: !!useTexSubImage2D,
160 generator: generateTranslucentGrayscaleRamp,
161 premultiplyAlpha: false,
163 type: gl.UNSIGNED_BYTE,
164 verifier: alphaChannelIncreasesByNoMoreThan,
167 description: "ALPHA/UNSIGNED_BYTE should maintain full precision of data"
171 useTexSubImage2D: !!useTexSubImage2D,
174 generator: generateOpaqueGrayscaleRamp,
175 premultiplyAlpha: false,
176 format: gl.LUMINANCE,
177 type: gl.UNSIGNED_BYTE,
178 verifier: allChannelsIncreaseByNoMoreThan,
181 description: "LUMINANCE/UNSIGNED_BYTE should maintain full precision of data"
185 useTexSubImage2D: !!useTexSubImage2D,
188 generator: generateOpaqueGrayscaleRamp,
189 premultiplyAlpha: false,
190 format: gl.LUMINANCE_ALPHA,
191 type: gl.UNSIGNED_BYTE,
192 verifier: allChannelsIncreaseByNoMoreThan,
195 description: "LUMINANCE_ALPHA/UNSIGNED_BYTE should maintain full precision of data"
200 // Verify that setting the UNPACK_PREMULTIPLY_ALPHA_WEBGL pixel
201 // store parameter and sending down a zero alpha causes the color
202 // channels to go to zero.
203 for (var dataMode = 0; dataMode < DataMode.NUM_MODES; ++dataMode) {
204 for (var useTexSubImage2D = 0; useTexSubImage2D < 2; ++useTexSubImage2D) {
207 useTexSubImage2D: !!useTexSubImage2D,
210 generator: generateTransparentGrayscaleRamp,
211 premultiplyAlpha: true,
213 type: gl.UNSIGNED_BYTE,
214 verifier: colorChannelsAreZero,
215 description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGBA/UNSIGNED_BYTE"
219 useTexSubImage2D: !!useTexSubImage2D,
222 generator: generateTransparentGrayscaleRamp,
223 premultiplyAlpha: true,
225 type: gl.UNSIGNED_SHORT_4_4_4_4,
226 verifier: colorChannelsAreZero,
227 description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGBA/UNSIGNED_SHORT_4_4_4_4"
231 useTexSubImage2D: !!useTexSubImage2D,
234 generator: generateTransparentGrayscaleRamp,
235 premultiplyAlpha: true,
237 type: gl.UNSIGNED_SHORT_5_5_5_1,
238 verifier: colorChannelsAreZero,
239 description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGBA/UNSIGNED_SHORT_5_5_5_1"
241 // The following few tests are invalid for the raw data
242 // mode because there is either no alpha channel or no
243 // separate alpha channel.
244 if (dataMode != DataMode.RAW_DATA) {
247 useTexSubImage2D: !!useTexSubImage2D,
250 generator: generateTransparentGrayscaleRamp,
251 premultiplyAlpha: true,
253 type: gl.UNSIGNED_BYTE,
254 verifier: colorChannelsAreZero,
255 description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGB/UNSIGNED_BYTE"
259 useTexSubImage2D: !!useTexSubImage2D,
262 generator: generateTransparentGrayscaleRamp,
263 premultiplyAlpha: true,
265 type: gl.UNSIGNED_SHORT_5_6_5,
266 verifier: colorChannelsAreZero,
267 description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with RGB/UNSIGNED_SHORT_5_6_5"
271 useTexSubImage2D: !!useTexSubImage2D,
274 generator: generateTransparentGrayscaleRamp,
275 premultiplyAlpha: true,
277 type: gl.UNSIGNED_BYTE,
278 verifier: colorChannelsAreZero,
279 description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with ALPHA/UNSIGNED_BYTE"
283 useTexSubImage2D: !!useTexSubImage2D,
286 generator: generateTransparentGrayscaleRamp,
287 premultiplyAlpha: true,
288 format: gl.LUMINANCE,
289 type: gl.UNSIGNED_BYTE,
290 verifier: colorChannelsAreZero,
291 description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with LUMINANCE/UNSIGNED_BYTE"
296 useTexSubImage2D: !!useTexSubImage2D,
299 generator: generateTransparentGrayscaleRamp,
300 premultiplyAlpha: true,
301 format: gl.LUMINANCE_ALPHA,
302 type: gl.UNSIGNED_BYTE,
303 verifier: colorChannelsAreZero,
304 description: "UNPACK_PREMULTIPLY_ALPHA_WEBGL with LUMINANCE_ALPHA/UNSIGNED_BYTE"
309 // Produce data for all testcases. Because we load images, some of
310 // these may generate their data asynchronously.
314 function generateTestData()
316 for (var i = 0; i < testCases.length; i++) {
317 var testCase = testCases[i];
319 switch (testCase.dataMode) {
321 wrapper = new ImageWrapper(testCase.width, testCase.height);
323 case DataMode.IMAGE_DATA:
324 wrapper = new ImageDataWrapper(testCase.width, testCase.height);
326 case DataMode.RAW_DATA:
327 switch (testCase.type) {
328 case gl.UNSIGNED_BYTE:
329 switch (testCase.format) {
331 wrapper = new RGBA8DataWrapper(testCase.width, testCase.height);
334 wrapper = new RGB8DataWrapper(testCase.width, testCase.height);
337 wrapper = new A8DataWrapper(testCase.width, testCase.height);
340 wrapper = new L8DataWrapper(testCase.width, testCase.height);
342 case gl.LUMINANCE_ALPHA:
343 wrapper = new LA8DataWrapper(testCase.width, testCase.height);
347 case gl.UNSIGNED_SHORT_4_4_4_4:
348 wrapper = new RGBA4444DataWrapper(testCase.width, testCase.height);
350 case gl.UNSIGNED_SHORT_5_5_5_1:
351 wrapper = new RGBA5551DataWrapper(testCase.width, testCase.height);
353 case gl.UNSIGNED_SHORT_5_6_5:
354 wrapper = new RGB565DataWrapper(testCase.width, testCase.height);
358 testCase.wrapper = wrapper;
359 testCase.generator(wrapper);
360 testCase.wrapper.generateData();
363 // See whether we need to run the tests, in case all of them
364 // generated their results synchronously.
368 var ranTests = false;
370 function maybeRunTests()
373 for (var i = 0; i < testCases.length; ++i)
374 if (!testCases[i].wrapper || !testCases[i].wrapper.data)
379 for (var i = 0; i < testCases.length; ++i)
380 runOneTest(testCases[i]);
385 function testCaseToString(testCase)
388 switch (testCase.dataMode) {
392 case DataMode.IMAGE_DATA:
395 case DataMode.RAW_DATA:
399 return (testCase.useTexSubImage2D ? "texSubImage2D" : "texImage2D") +
400 " with " + mode + " at " + testCase.width + "x" + testCase.height;
403 function runOneTest(testCase)
405 debug("Testing " + testCaseToString(testCase));
406 var data = testCase.wrapper.data;
407 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
408 var texture = gl.createTexture();
409 // Bind the texture to texture unit 0.
410 gl.bindTexture(gl.TEXTURE_2D, texture);
411 // Set up texture parameters.
412 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
413 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
414 // Set up pixel store parameters.
415 gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, testCase.premultiplyAlpha);
416 // Upload the image into the texture.
417 if (testCase.useTexSubImage2D) {
418 // Initialize the texture to black first.
419 gl.texImage2D(gl.TEXTURE_2D, 0, testCase.format, testCase.width, testCase.height, 0,
420 testCase.format, testCase.type, null);
422 switch (testCase.dataMode) {
424 case DataMode.IMAGE_DATA:
425 if (testCase.useTexSubImage2D)
426 gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, testCase.format, testCase.type, data);
428 gl.texImage2D(gl.TEXTURE_2D, 0, testCase.format, testCase.format, testCase.type, data);
430 case DataMode.RAW_DATA:
431 if (testCase.useTexSubImage2D)
432 gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, testCase.width, testCase.height, testCase.format, testCase.type, data);
434 gl.texImage2D(gl.TEXTURE_2D, 0, testCase.format, testCase.width, testCase.height, 0, testCase.format, testCase.type, data);
437 // Point the uniform sampler to texture unit 0.
438 gl.uniform1i(textureLoc, 0);
439 // Draw the triangles.
440 gl.drawArrays(gl.TRIANGLES, 0, 6);
441 // Clean up the texture.
442 gl.deleteTexture(texture);
444 // Read back the rendering results.
445 var buf = new Uint8Array(testCase.width * testCase.height * 4);
446 gl.readPixels(0, 0, testCase.width, testCase.height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
447 // Run the verification routine.
448 if (testCase.verifier(buf, testCase.threshold, testCase.numOccurrences))
449 testPassed(testCase.description);
451 testFailed(testCase.description);
454 //----------------------------------------------------------------------
455 // Wrappers for programmatic construction of Image, ImageData and raw texture data
458 function ImageWrapper(width, height)
460 this.pngBuilder_ = new PNGlib(width, height, 256);
463 ImageWrapper.prototype.getWidth = function() {
464 return this.pngBuilder_.width;
467 ImageWrapper.prototype.getHeight = function() {
468 return this.pngBuilder_.height;
471 ImageWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
472 this.pngBuilder_.buffer[this.pngBuilder_.index(x, y)] = this.pngBuilder_.color(r, g, b, a);
475 // Generates data into "data" property, possibly asynchronously.
476 ImageWrapper.prototype.generateData = function() {
478 var url = "data:image/png;base64," + this.pngBuilder_.getBase64();
479 var img = wtu.makeImage(url, function() {
485 function ImageDataWrapper(width, height)
487 if (!ImageDataWrapper.tempCanvas) {
488 ImageDataWrapper.tempCanvas = document.createElement("canvas");
490 this.imageData_ = ImageDataWrapper.tempCanvas.getContext("2d").createImageData(width, height);
493 ImageDataWrapper.tempCanvas = null;
495 ImageDataWrapper.prototype.getWidth = function() {
496 return this.imageData_.width;
499 ImageDataWrapper.prototype.getHeight = function() {
500 return this.imageData_.height;
503 ImageDataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
504 var index = 4 * (this.imageData_.width * y + x);
505 this.imageData_.data[index] = r;
506 this.imageData_.data[index + 1] = g;
507 this.imageData_.data[index + 2] = b;
508 this.imageData_.data[index + 3] = a;
511 ImageDataWrapper.prototype.generateData = function() {
512 this.data = this.imageData_;
516 function TextureDataWrapper(width, height)
519 this.height_ = height;
522 TextureDataWrapper.prototype.getWidth = function() {
526 TextureDataWrapper.prototype.getHeight = function() {
530 TextureDataWrapper.prototype.generateData = function() {
531 this.data = this.data_;
535 function RGBA8DataWrapper(width, height)
537 TextureDataWrapper.call(this, width, height);
538 this.data_ = new Uint8Array(4 * width * height);
541 RGBA8DataWrapper.prototype = new TextureDataWrapper;
543 RGBA8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
544 var index = 4 * (this.width_ * y + x);
545 this.data_[index] = r;
546 this.data_[index + 1] = g;
547 this.data_[index + 2] = b;
548 this.data_[index + 3] = a;
551 function RGBA5551DataWrapper(width, height)
553 TextureDataWrapper.call(this, width, height);
554 this.data_ = new Uint16Array(width * height);
557 RGBA5551DataWrapper.prototype = new TextureDataWrapper;
559 RGBA5551DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
560 var value = (((r & 0xF8) << 8)
564 this.data_[this.width_ * y + x] = value;
567 function RGBA4444DataWrapper(width, height)
569 TextureDataWrapper.call(this, width, height);
570 this.data_ = new Uint16Array(width * height);
573 RGBA4444DataWrapper.prototype = new TextureDataWrapper;
575 RGBA4444DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
576 var value = (((r & 0xF0) << 8)
580 this.data_[this.width_ * y + x] = value;
583 function RGB8DataWrapper(width, height)
585 TextureDataWrapper.call(this, width, height);
586 this.data_ = new Uint8Array(3 * width * height);
589 RGB8DataWrapper.prototype = new TextureDataWrapper;
591 RGB8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
592 var index = 3 * (this.width_ * y + x);
593 this.data_[index] = r;
594 this.data_[index + 1] = g;
595 this.data_[index + 2] = b;
598 function RGB565DataWrapper(width, height)
600 TextureDataWrapper.call(this, width, height);
601 this.data_ = new Uint16Array(width * height);
604 RGB565DataWrapper.prototype = new TextureDataWrapper;
606 RGB565DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
607 var value = (((r & 0xF8) << 8)
609 | ((b & 0xF8) >> 3));
610 this.data_[this.width_ * y + x] = value;
613 function A8DataWrapper(width, height)
615 TextureDataWrapper.call(this, width, height);
616 this.data_ = new Uint8Array(width * height);
619 A8DataWrapper.prototype = new TextureDataWrapper;
621 A8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
622 this.data_[this.width_ * y + x] = a;
625 function L8DataWrapper(width, height)
627 TextureDataWrapper.call(this, width, height);
628 this.data_ = new Uint8Array(width * height);
631 L8DataWrapper.prototype = new TextureDataWrapper;
633 L8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
634 this.data_[this.width_ * y + x] = r;
637 function LA8DataWrapper(width, height)
639 TextureDataWrapper.call(this, width, height);
640 this.data_ = new Uint8Array(2 * width * height);
643 LA8DataWrapper.prototype = new TextureDataWrapper;
645 LA8DataWrapper.prototype.setPixel = function(x, y, r, g, b, a) {
646 var index = 2 * (this.width_ * y + x);
647 this.data_[index] = r;
648 this.data_[index + 1] = a;
651 //----------------------------------------------------------------------
652 // Color ramp generation functions
655 function generateOpaqueGrayscaleRamp(wrapper)
657 var width = wrapper.getWidth();
658 var height = wrapper.getHeight();
659 for (var x = 0; x < width; ++x) {
660 var value = Math.round(255.0 * x / width);
661 for (var y = 0; y < height; ++y)
662 wrapper.setPixel(x, y, value, value, value, 255);
666 function generateTranslucentGrayscaleRamp(wrapper)
668 var width = wrapper.getWidth();
669 var height = wrapper.getHeight();
670 for (var x = 0; x < width; ++x) {
671 var value = Math.round(255.0 * x / width);
672 for (var y = 0; y < height; ++y)
673 wrapper.setPixel(x, y, value, value, value, value);
677 function generateTransparentGrayscaleRamp(wrapper)
679 var width = wrapper.getWidth();
680 var height = wrapper.getHeight();
681 for (var x = 0; x < width; ++x) {
682 var value = Math.round(255.0 * x / width);
683 for (var y = 0; y < height; ++y)
684 wrapper.setPixel(x, y, value, value, value, 0);
688 //----------------------------------------------------------------------
689 // Verification routines
692 function allChannelsIncreaseByNoMoreThan(array, threshold, numOccurrences) {
694 for (var i = 4; i < array.length; i += 4)
695 for (var j = 0; j < 4; j++)
696 if (array[i + j] - array[i + j - 4] > threshold)
699 return numFound < numOccurrences;
702 function alphaChannelIncreasesByNoMoreThan(array, threshold, numOccurrences) {
704 for (var i = 7; i < array.length; i += 4)
705 if (array[i] - array[i - 4] > threshold)
708 return numFound < numOccurrences;
711 function allChannelsIncreaseByAtLeast(array, threshold, numOccurrences) {
713 for (var i = 4; i < array.length; i += 4)
714 for (var j = 0; j < 4; ++j)
715 if (array[i + j] - array[i + j - 4] > threshold)
718 return numFound > numOccurrences;
721 function colorChannelsAreZero(array, threshold, numOccurrences) {
725 for (var i = 4; i < array.length; i += 4)
726 for (var j = 0; j < 3; ++j)
727 if (array[i + j] != 0) {
729 if (++numFailures <= 5)
730 debug(" array[" + (i + j) + "] should have been 0, was " + array[i + j]);
738 <body onload="init()">
739 <canvas id="example" width="256" height="1"></canvas>
740 <div id="description"></div>
741 <div id="console"></div>