<!--
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">
<title>WebGL CopyTexImage Tests</title>
<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>
</head>
<body>
<canvas id="example" width="2" height="2"></canvas>
<div id="description"></div>
<div id="console"></div>
<script>
"use strict";

var wtu = WebGLTestUtils;
description("This test verifies the functionality of copyTexImage.");

var gl = wtu.create3DContext("example", undefined, 2);

function copytexsubimage3d_invalid_operation_feedbackloops() {
    debug("");
    debug("Testing copytexsubimage3d_invalid_operation_feedbackloops");
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_3D, texture);
    var uint8 = new Uint8Array(32);
    var layer = 0;
    var width = 2;
    var height = 2;
    var depth = 2;
    gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA, width, height, depth, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint8);

    var fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, 0, layer);
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
        gl.copyTexSubImage3D(gl.TEXTURE_3D, 0, 0, 0, layer, 0, 0, width, height);
        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "gl.INVALID_OPERATION is generated");
    } else {
        testFailed("framebuffer not complete");
    }

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.deleteFramebuffer(fbo);
    gl.deleteTexture(texture);
};

function copytexsubimage3d_valid_operation_diff_level() {
    debug("");
    debug("Testing copytexsubimage3d_valid_operation_diff_level");
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_3D, texture);
    var uint8 = new Uint8Array(32);
    var level1 = 0;
    var level2 = 1;
    var width = 2;
    var height = 2;
    var depth = 2;
    gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA, width, height, depth, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint8);
    gl.generateMipmap(gl.TEXTURE_3D);

    var fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, level1, 0);
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
        gl.copyTexSubImage3D(gl.TEXTURE_3D, level2, 0, 0, 0, 0, 0, width/2, height/2);
        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texImage3D should succeed.");
    } else {
        testFailed("framebuffer not complete");
    }

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.deleteFramebuffer(fbo);
    gl.deleteTexture(texture);
};

function copytexsubimage3d_valid_operation_diff_layer() {
    debug("");
    debug("Testing copytexsubimage3d_valid_operation_diff_layer");
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_3D, texture);
    var uint8 = new Uint8Array(32);
    var layer1 = 0;
    var layer2 = 1;
    var width = 2;
    var height = 2;
    var depth = 2;
    gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA, width, height, depth, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint8);

    var fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, 0, layer1);
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
        gl.copyTexSubImage3D(gl.TEXTURE_3D, 0, 0, 0, layer2, 0, 0, width, height);
        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texImage3D should succeed.");
    } else {
        testFailed("framebuffer not complete");
    }

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.deleteFramebuffer(fbo);
    gl.deleteTexture(texture);
}

function copytexsubimage3d_texture_wrongly_initialized() {
    debug("");
    debug("Testing copytexsubimage3d_texture_wrongly_initialized");
    var texture = [];
    texture[0] = gl.createTexture();
    texture[1] = gl.createTexture();
    var layer = 0;
    var width = 2;
    var height = 2;
    var depth = 2;
    gl.bindTexture(gl.TEXTURE_2D, texture[0]);
    var uint = new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint);

    var fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture[0], 0);
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
        gl.bindTexture(gl.TEXTURE_3D, texture[1]);
        gl.texStorage3D(gl.TEXTURE_3D, 1, gl.RGBA8, width, height, depth);
        gl.copyTexSubImage3D(gl.TEXTURE_3D, 0, 0, 0, layer, 0, 0, width, height);
        gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture[1], 0, layer);
        var bytes = new Uint8Array(width * height * 4);
        gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, bytes);
        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readpixel should succeed.");
        for (var x = 0; x < width * height * 4; x++) {
            if (bytes[x] != uint[x]) {
                testFailed("byte comparison failure, byte at "+ x + " was " + bytes[x] +
                ", should be " + uint[x]);
                break;
            }
        }
    } else {
        testFailed("framebuffer not complete");
    }

    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.bindTexture(gl.TEXTURE_3D, null);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.deleteFramebuffer(fbo);
    gl.deleteTexture(texture[0]);
    gl.deleteTexture(texture[1]);
};

function copytexsubimage3d_out_of_bounds_test_helper(xx, yy, copyWidth, copyHeight) {
    var texture = [];
    texture[0] = gl.createTexture();
    texture[1] = gl.createTexture();
    var layer = 0;
    var width = 2;
    var height = 2;
    var depth = 2;
    var width2 = 4;
    var height2 = 4;
    var xoffset = 0;
    var yoffset = 0;
    var uint = new Uint8Array(width * height * 4);
    for (var i = 0; i < uint.length; i++) {
        uint[i] = 0x01;
    }
    var uint2 = new Uint8Array(width2 * height2 * depth * 4);
    for (var i = 0; i < uint2.length; i++) {
        uint2[i] = 0xFF;
    }

    var fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

    gl.bindTexture(gl.TEXTURE_2D, texture[0]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture[0], 0);
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
        gl.bindTexture(gl.TEXTURE_3D, texture[1]);
        gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA, width2, height2, depth, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint2);
        gl.copyTexSubImage3D(gl.TEXTURE_3D, 0, xoffset, yoffset, layer, xx, yy, copyWidth, copyHeight);
        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "using copyTexSubImage3D: x = " + xx + ", y = " + yy + " width = " + copyWidth + ", height = " + copyHeight);

        gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture[1], 0, layer);
        var bytes = new Uint8Array(width2 * height2 * 4);
        gl.readPixels(0, 0, width2, height2, gl.RGBA, gl.UNSIGNED_BYTE, bytes);
        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readpixel should succeed.");

        var sourceX = new Object();
        var sourceY = new Object();
        Clip(xx, copyWidth, width, sourceX);
        Clip(yy, copyHeight, height, sourceY);
        var destX = sourceX.start - xx + xoffset;
        var rangeX = sourceX.range;
        var destY = sourceY.start - yy + yoffset;
        var rangeY = sourceY.range;
        for (var y = 0; y < height2; y++) {
            for (var x = 0; x < width2 * 4; x++) {
                var current = y * height2 * 4 + x;
                // pixels copied from read framebuffer should be 0x01
                if (x >= destX * 4 && x < (destX + rangeX) * 4 && y >= destY && y < destY + rangeY) {
                    if (bytes[current] != 0x01) {
                        testFailed("byte comparison failure, byte at "+ (current) + " was " +
                        bytes[current] +", should be 1");
                        break;
                     }
                // pixels out-of-bounds should be untouched
                } else {
                    if (bytes[current] != 0xFF) {
                        testFailed("byte comparison failure, byte at "+ (current) + " was " +
                        bytes[current] + ", should be 255");
                        break;
                    }
                }
            }
            // Test failed; abort
            if (x < width2 * 4) {
                break;
            }
        }
    } else {
        testFailed("framebuffer not complete");
    }

    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.bindTexture(gl.TEXTURE_3D, null);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.deleteFramebuffer(fbo);
    gl.deleteTexture(texture[0]);
    gl.deleteTexture(texture[1]);
}

function copytexsubimage3d_out_of_bounds() {
    debug("");
    debug("Test pixels outside of read framebuffer for CopyTexSubImage3D");

    for(var i=0; i < testlist.length; i++) {
        copytexsubimage3d_out_of_bounds_test_helper(testlist[i][0], testlist[i][1], testlist[i][2], testlist[i][3]);
    }
};

/**
 * This array defines some copy areas for CopyTexSubImage3D.
 * A copy area is defined by x coordinate, y coordinate, copyWidth and copyHeight.
 * the source read framebuffer is (0, 0, 2, 2).
 */
var testlist = [
    [-1, -1, 4, 4],

    [0, 0, 3, 3],
    [-1, -1, 3, 3],
    [-1, 0, 3, 3],
    [0, -1, 3, 3],

    [0, 0, 2, 3],
    [0, 0, 3, 2],
    [-1, 0, 3, 2],
    [0, -1, 2, 3],

    [-2, -2, 3, 3],
    [-2, 1, 3, 3],
    [1, -2, 3, 3],
    [1, 1, 3, 3],

    [2 , 2 ,3, 3]
];


function Clip(start, range, sourceRange, target) {
    if (start < 0) {
        range += start;
        start = 0;
    }
    var end = start + range;
    if(end > sourceRange) {
        range -= end - sourceRange;
    }
    target.start = start;
    target.range = range;
}

if (!gl) {
    testFailed("WebGL context does not exist");
} else {
    testPassed("WebGL context exists");
    copytexsubimage3d_invalid_operation_feedbackloops();
    copytexsubimage3d_valid_operation_diff_level();
    copytexsubimage3d_valid_operation_diff_layer();
    copytexsubimage3d_texture_wrongly_initialized();
    copytexsubimage3d_out_of_bounds();
}

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

</body>
</html>
