﻿<!--
Copyright (c) 2019 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/webgl-test-utils.js"></script>
<title>WebGL WEBGL_compressed_texture_pvrtc Conformance Tests</title>
<style>
img {
 border: 1px solid black;
 margin-right: 1em;
}
.testimages {
}

.testimages br {
  clear: both;
}

.testimages > div {
  float: left;
  margin: 1em;
}
</style>
</head>
<body>
<div id="description"></div>
<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
<div id="console"></div>
<script>
"use strict";
description("This test verifies the functionality of the WEBGL_compressed_texture_pvrtc extension, if it is available.");

debug("");

var pvrtc_4x4_2bpp = new Uint8Array([
  0x77, 0x22, 0x77, 0x22, 0xbb, 0x2b, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);

var pvrtc_4x4_4bpp = new Uint8Array([
  0x1b, 0x1b, 0x1b, 0x1b, 0xba, 0x2b, 0x00, 0x80, 0x1b, 0x1b, 0x1b, 0x1b, 0xba, 0x2b, 0x00, 0x80,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);

var pvrtc_4x4_rgba_decoded = new Uint8Array([
  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x46, 0xb8, 0x76, 0x76, 0x71, 0x8a, 0xbd, 0xbd, 0xba, 0x44,
  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x43, 0xb8, 0x76, 0x76, 0x71, 0x8a, 0xbd, 0xbd, 0xb5, 0x44,
  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x43, 0xb8, 0x76, 0x76, 0x71, 0x8a, 0xbd, 0xbd, 0xb5, 0x44,
  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x46, 0xb8, 0x76, 0x76, 0x71, 0x8a, 0xbd, 0xbd, 0xb7, 0x44,
]);

var pvrtc_4x4_rgb_decoded = new Uint8Array([
  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x46, 0xff, 0x76, 0x76, 0x71, 0xff, 0xbd, 0xbd, 0xba, 0xff,
  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x43, 0xff, 0x76, 0x76, 0x71, 0xff, 0xbd, 0xbd, 0xb5, 0xff,
  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x43, 0xff, 0x76, 0x76, 0x71, 0xff, 0xbd, 0xbd, 0xb5, 0xff,
  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x46, 0xff, 0x76, 0x76, 0x71, 0xff, 0xbd, 0xbd, 0xb7, 0xff,
]);

var wtu = WebGLTestUtils;
var contextVersion = wtu.getDefault3DContextVersion();
var canvas = document.getElementById("canvas");
var gl = wtu.create3DContext(canvas, {antialias: false});
var program = wtu.setupTexturedQuad(gl);
var ext = null;
var vao = null;
var validFormats = {
    COMPRESSED_RGB_PVRTC_4BPPV1_IMG      : 0x8C00,
    COMPRESSED_RGB_PVRTC_2BPPV1_IMG      : 0x8C01,
    COMPRESSED_RGBA_PVRTC_4BPPV1_IMG     : 0x8C02,
    COMPRESSED_RGBA_PVRTC_2BPPV1_IMG     : 0x8C03,
};
var name;
var supportedFormats;

if (!gl) {
    testFailed("WebGL context does not exist");
} else {
    testPassed("WebGL context exists");

    // Run tests with extension disabled
    runTestDisabled();

    // Query the extension and store globally so shouldBe can access it
    ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_pvrtc");
    if (!ext) {
        testPassed("No WEBGL_compressed_texture_pvrtc support -- this is legal");
        runSupportedTest(false);
    } else {
        testPassed("Successfully enabled WEBGL_compressed_texture_pvrtc extension");

        runSupportedTest(true);
        runTestExtension();
    }
}

function runSupportedTest(extensionEnabled) {
    var name = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_pvrtc");
    if (name !== undefined) {
        if (extensionEnabled) {
            testPassed("WEBGL_compressed_texture_pvrtc listed as supported and getExtension succeeded");
        } else {
            testFailed("WEBGL_compressed_texture_pvrtc listed as supported but getExtension failed");
        }
    } else {
        if (extensionEnabled) {
            testFailed("WEBGL_compressed_texture_pvrtc not listed as supported but getExtension succeeded");
        } else {
            testPassed("WEBGL_compressed_texture_pvrtc not listed as supported and getExtension failed -- this is legal");
        }
    }
}


function runTestDisabled() {
    debug("Testing binding enum with extension disabled");

    supportedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
    shouldBe("supportedFormats", "[]");
}

function formatExists(format, supportedFormats) {
    for (var ii = 0; ii < supportedFormats.length; ++ii) {
        if (format == supportedFormats[ii]) {
            testPassed("supported format " + formatToString(format) + " is exists");
            return;
        }
    }
    testFailed("supported format " + formatToString(format) + " does not exist");
}

function formatToString(format) {
    for (var p in ext) {
        if (ext[p] == format) {
            return p;
        }
    }
    return "0x" + format.toString(16);
}

function runTestExtension() {
    debug("Testing WEBGL_compressed_texture_pvrtc");

    // check that all format enums exist.
    for (name in validFormats) {
        var expected = "0x" + validFormats[name].toString(16);
        var actual = "ext['" + name + "']";
        shouldBe(actual, expected);
    }

    supportedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
    // There should be exactly 4 formats for both WebGL 1.0 and WebGL 2.0.
    shouldBe("supportedFormats.length", "4");

    // check that all 4 formats exist
    for (var name in validFormats.length) {
        formatExists(validFormats[name], supportedFormats);
    }

    // Test each format
    testPVRTC_RGBA_2BPP();
    testPVRTC_RGB_2BPP();
    testPVRTC_RGBA_4BPP();
    testPVRTC_RGB_4BPP();
}

function testPVRTC_RGBA_2BPP() {
    var tests = [
        {   width: 4,
            height: 4,
            channels: 4,
            data: pvrtc_4x4_2bpp,
            raw: pvrtc_4x4_rgba_decoded,
            format: ext.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
        }
    ];
    testPVRTCTextures(tests);
}

function testPVRTC_RGB_2BPP() {
    var tests = [
        {   width: 4,
            height: 4,
            channels: 4,
            data: pvrtc_4x4_2bpp,
            raw: pvrtc_4x4_rgb_decoded,
            format: ext.COMPRESSED_RGB_PVRTC_2BPPV1_IMG
        }
    ];
    testPVRTCTextures(tests);
}

function testPVRTC_RGBA_4BPP() {
    var tests = [
        {   width: 4,
            height: 4,
            channels: 4,
            data: pvrtc_4x4_4bpp,
            raw: pvrtc_4x4_rgba_decoded,
            format: ext.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
        }
    ];
    testPVRTCTextures(tests);
}

function testPVRTC_RGB_4BPP() {
    var tests = [
        {   width: 4,
            height: 4,
            channels: 4,
            data: pvrtc_4x4_4bpp,
            raw: pvrtc_4x4_rgb_decoded,
            format: ext.COMPRESSED_RGB_PVRTC_4BPPV1_IMG
        }
    ];
    testPVRTCTextures(tests);
}

function testPVRTCTextures(tests) {
    debug("<hr/>");
    for (var ii = 0; ii < tests.length; ++ii) {
        testPVRTCTexture(tests[ii]);
    }
}

function testPVRTCTexture(test) {
    var data = new Uint8Array(test.data);
    var width = test.width;
    var height = test.height;
    var format = test.format;
    var uncompressedData = test.raw;

    canvas.width = width;
    canvas.height = height;
    gl.viewport(0, 0, width, height);
    debug("testing " + formatToString(format) + " " + width + "x" + height);

    var tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
    gl.generateMipmap(gl.TEXTURE_2D);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "trying to generate mipmaps from compressed texture");
    wtu.clearAndDrawUnitQuad(gl);
    compareRect(width, height, test.channels, width, height, uncompressedData, data, format, undefined, "NEAREST");
    // Test again with linear filtering.
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    wtu.clearAndDrawUnitQuad(gl);
    compareRect(width, height, test.channels, width, height, uncompressedData, data, format, undefined, "LINEAR");

    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 1, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "non 0 border");

    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 1, height, 0, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 2, height, 0, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 1, 0, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 2, 0, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");

    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "compressedTexSubImage2D allowed for reloading of complete textures");

    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 2, height, format, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed for partial texture updates");
    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 2, format, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed for partial texture updates");
    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 2, 0, width - 2, height, format, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed for partial texture updates");
    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 2, width, height - 2, format, data);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed for partial texture updates");
}

function insertImg(element, caption, img) {
    var div = document.createElement("div");
    div.appendChild(img);
    var label = document.createElement("div");
    label.appendChild(document.createTextNode(caption));
    div.appendChild(label);
    element.appendChild(div);
}

function makeImage(imageWidth, imageHeight, dataWidth, data, alpha) {
    var scale = 8;
    var c = document.createElement("canvas");
    c.width = imageWidth * scale;
    c.height = imageHeight * scale;
    var ctx = c.getContext("2d");
    for (var yy = 0; yy < imageHeight; ++yy) {
        for (var xx = 0; xx < imageWidth; ++xx) {
            var offset = (yy * dataWidth + xx) * 4;
            ctx.fillStyle = "rgba(" +
                    data[offset + 0] + "," +
                    data[offset + 1] + "," +
                    data[offset + 2] + "," +
                    (alpha ? data[offset + 3] / 255 : 1) + ")";
            ctx.fillRect(xx * scale, yy * scale, scale, scale);
        }
    }
    return wtu.makeImageFromCanvas(c);
}
function compareRect(
        actualWidth, actualHeight, actualChannels,
        dataWidth, dataHeight, expectedData,
        testData, testFormat, tolerance, filteringMode) {
    if(typeof(tolerance) == 'undefined') { tolerance = 5; }
    var actual = new Uint8Array(actualWidth * actualHeight * 4);
    gl.readPixels(
            0, 0, actualWidth, actualHeight, gl.RGBA, gl.UNSIGNED_BYTE, actual);

    var div = document.createElement("div");
    div.className = "testimages";
    insertImg(div, "expected", makeImage(
            actualWidth, actualHeight, dataWidth, expectedData,
            actualChannels == 4));
    insertImg(div, "actual", makeImage(
            actualWidth, actualHeight, actualWidth, actual,
            actualChannels == 4));
    div.appendChild(document.createElement('br'));
    document.getElementById("console").appendChild(div);

    var failed = false;
    for (var yy = 0; yy < actualHeight; ++yy) {
        for (var xx = 0; xx < actualWidth; ++xx) {
            var actualOffset = (yy * actualWidth + xx) * 4;
            var expectedOffset = (yy * dataWidth + xx) * 4;
            var expected = [
                    expectedData[expectedOffset + 0],
                    expectedData[expectedOffset + 1],
                    expectedData[expectedOffset + 2],
                    (actualChannels == 3 ? 255 : expectedData[expectedOffset + 3])
            ];
            for (var jj = 0; jj < 4; ++jj) {
                if (Math.abs(actual[actualOffset + jj] - expected[jj]) > tolerance) {
                    failed = true;
                    var was = actual[actualOffset + 0].toString();
                    for (var j = 1; j < 4; ++j) {
                        was += "," + actual[actualOffset + j];
                    }
                    testFailed('at (' + xx + ', ' + yy +
                                         ') expected: ' + expected + ' was ' + was);
                }
            }
        }
    }
    if (!failed) {
        testPassed("texture rendered correctly with " + filteringMode + " filtering");
    }
}

debug("");
var successfullyParsed = true;
</script>
<script src="../../js/js-test-post.js"></script>

</body>
</html>
