<!--
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/desktop-gl-constants.js"></script>
<script src="../../js/webgl-test-utils.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>

<script>
"use strict";
description("Tests texture uploads with ArrayBufferView+offsets");

var wtu = WebGLTestUtils;
var gl = wtu.create3DContext(null, undefined, 2);
console.log(gl.getParameter(gl.VERSION));

////

function arrToStr(arr) {
  return "[" + arr.map(x => x.toString()).join(", ") + "]";
}

function shouldBeWas(shouldBe, was, info) {
  var text = "Should be " + shouldBe + ", was " + was + ".";
  if (info) {
    text = info + ": " + text;
  }

  if (shouldBe == was) {
    testPassed(text);
    return true;
  } else {
    testFailed(text);
    return false;
  }
}

function shouldBeWasArr(shouldBe, was, info) {
  if (shouldBe.length != was.length) {
    testFailed("Length should be " + shouldBe.length + ", was " + was.length + ".");
    return false;
  }

  return shouldBeWas(arrToStr(shouldBe), arrToStr(was), info);
}

////

// Textures

var fibArr = [
  0, 1, 1, 2,
  3, 5, 8, 13,
  21, 34, 55, 89,
  144, 233,
];

var fb = gl.createFramebuffer();

function probeWithBadOffset(fnTest, info) {
  fnTest(+(-1|0));
  if (!gl.getError()) {
    testFailed("Does not support " + info + " with offsets into views.");
    return false;
  }
  return true;
}

// fn(view, offset, expectedError, expectedResult)

do {
  var readPixelView = new Uint8Array(4);
  var testView = new Uint8Array(fibArr);

  function testTexOrSubImage(funcName, fnTexOrSubImage) {
    debug("");
    debug(funcName);

    var fnProbe = function(viewOffset) {
      fnTexOrSubImage(gl.RGBA, gl.UNSIGNED_BYTE, testView, viewOffset);
    };

    if (!probeWithBadOffset(fnProbe, funcName))
      return;

    for (var i = 0; i <= testView.length+1; i++) {
      debug("offset=" + i);

      fnTexOrSubImage(gl.RGBA, gl.UNSIGNED_BYTE, testView, i);

      var effectiveViewLen = testView.length - i;

      if (effectiveViewLen >= 4) {
        wtu.glErrorShouldBe(gl, gl.NO_ERROR);

        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, readPixelView);
        wtu.glErrorShouldBe(gl, gl.NO_ERROR);
        shouldBeWasArr(testView.slice(i, i+4), readPixelView);

      } else {
        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
      }
    }

    debug("");

    var yellow565 = (0x1f << 11) | (0x3f << 5);
    var cyan565 = (0x3f << 5) | 0x1f;
    var arr565 = [yellow565, cyan565];
    var view565 = new Uint16Array(arr565);

    function rgb888to565(arr888) {
      return ((arr888[0] >> 3) << 11) | ((arr888[1] >> 2) << 5) | (arr888[2] >> 3);
    }

    for (var i = 0; i <= arr565.length+1; i++) {
      debug("rgb565, offset=" + i);

      fnTexOrSubImage(gl.RGB, gl.UNSIGNED_SHORT_5_6_5, view565, i);

      var effectiveViewLen = arr565.length - i;

      if (effectiveViewLen >= 1) {
        wtu.glErrorShouldBe(gl, gl.NO_ERROR);

        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, readPixelView);
        debug(arrToStr(readPixelView));
        wtu.glErrorShouldBe(gl, gl.NO_ERROR);
        shouldBeWas(arr565[i], rgb888to565(readPixelView));

      } else {
        wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
      }
    }
  }

  var fn2D = function(format, type, view, viewOffset) {
    gl.texImage2D(gl.TEXTURE_2D, 0, format, 1, 1, 0, format, type, view, viewOffset);
  }

  var fnSub2D = function(format, type, view, viewOffset) {
    gl.texImage2D(gl.TEXTURE_2D, 0, format, 1, 1, 0, format, type, null);
    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 1, 1, format, type, view, viewOffset);
  }

  var fn3D = function(format, type, view, viewOffset) {
    gl.texImage3D(gl.TEXTURE_3D, 0, format, 1, 1, 1, 0, format, type, view, viewOffset);
  }

  var fnSub3D = function(format, type, view, viewOffset) {
    gl.texImage3D(gl.TEXTURE_3D, 0, format, 1, 1, 1, 0, format, type, null);
    gl.texSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, 1, 1, 1, format, type, view, viewOffset);
  }

  ////

  var tex2d = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex2d);

  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex2d, 0);

  testTexOrSubImage("texImage2D", fn2D);
  testTexOrSubImage("texSubImage2D", fnSub2D);

  ////

  var tex3d = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_3D, tex3d);
  gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA, 1, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex3d, 0, 0);

  testTexOrSubImage("texImage3D", fn3D);
  testTexOrSubImage("texSubImage3D", fnSub3D);
} while (false);


do {
  var compressedFormat = 0;
  var compressedByteCount;

  if (gl.getExtension("WEBGL_compressed_texture_s3tc")) {
    var e = gl.getExtension("WEBGL_compressed_texture_s3tc");
    compressedFormat = e.COMPRESSED_RGB_S3TC_DXT1_EXT;
    compressedByteCount = 8;
  } else if (gl.getExtension("WEBGL_compressed_texture_etc")) {
    var e = gl.getExtension("WEBGL_compressed_texture_etc");
    compressedFormat = e.COMPRESSED_RGB8_ETC2;
    compressedByteCount = 8;
  } else {
    debug("No compressed texture format found. Skipping compressedTex(Sub)Image tests.");
    break;
  }

  ////

  var view = new Uint8Array(compressedByteCount+1);

  var fn2D = function(viewOffset) {
    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, compressedFormat, 4, 4, 0,
                            view, viewOffset, compressedByteCount);
  };

  var fnSub2D = function(viewOffset) {
    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, compressedFormat, 4, 4, 0,
                            view, 0, compressedByteCount);
    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 4, 4, compressedFormat,
                               view, viewOffset, compressedByteCount);
  };

  var fn3D = function(viewOffset) {
    gl.compressedTexImage3D(gl.TEXTURE_2D_ARRAY, 0, compressedFormat, 4, 4, 1, 0,
                            view, viewOffset, compressedByteCount);
  };

  var fnSub3D = function(viewOffset) {
    gl.compressedTexImage3D(gl.TEXTURE_2D_ARRAY, 0, compressedFormat, 4, 4, 1, 0,
                            view, 0, compressedByteCount);
    gl.compressedTexSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, 4, 4, 1, compressedFormat,
                               view, viewOffset, compressedByteCount);
  };

  ////

  var testFunc = function(funcName, fnToTest) {
    debug("");
    debug(funcName);

    if (!probeWithBadOffset(fnToTest, funcName))
      return;

    var viewLength = view.length;
    var subViewLength = compressedByteCount;

    for (var i = 0; i <= viewLength+1; i++) {
      debug("offset=" + i);

      fnToTest(i);
      var effectiveViewLen = viewLength - i;

      if (effectiveViewLen >= subViewLength) {
        wtu.glErrorShouldBe(gl, gl.NO_ERROR);

      } else {
        wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
      }
    }
  };

  var tex2d = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex2d);
  testFunc("compressedTexImage2D"   , fn2D   );
  testFunc("compressedTexSubImage2D", fnSub2D);

  var tex3d = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex3d);
  testFunc("compressedTexImage3D"   , fn3D   );
  testFunc("compressedTexSubImage3D", fnSub3D);
} while (false);

do {
  debug("");
  debug("readPixels");

  gl.bindFramebuffer(gl.FRAMEBUFFER, null);

  var testColor = [10, 20, 30, 40];
  gl.clearColor(testColor[0]/255.0,
                testColor[1]/255.0,
                testColor[2]/255.0,
                testColor[3]/255.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  var readPixelView = new Uint8Array(6);

  function doReadPixels(viewOffset) {
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, readPixelView, viewOffset);
    return readPixelView;
  }

  if (!probeWithBadOffset(doReadPixels, "doReadPixels"))
    break;

  for (var i = 0; i <= readPixelView.length+1; i++) {
    debug("offset=" + i);
    var res = doReadPixels(i);
    var effectiveViewLen = readPixelView.length - i;

    if (effectiveViewLen >= 4) {
      wtu.glErrorShouldBe(gl, gl.NO_ERROR);
      shouldBeWasArr(testColor, res.slice(i,i+4));

    } else if (effectiveViewLen >= 0) {
      wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);

    } else {
      wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
    }
  }
} while (false);

debug("")
var successfullyParsed = true;
</script>

<script src="../../js/js-test-post.js"></script>
</body>
</html>
