<!--
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>
<script src="../../../js/tests/tex-image-and-sub-image-utils.js"></script>
</head>
<body>
<canvas id="example" width="2" height="2"></canvas>
<div id="description"></div>
<div id="console"></div>
<script>
"use strict";
description('Verifies tex{Sub}Image{2|3}D code paths taking partial ArrayBufferView');

var wtu = WebGLTestUtils;
var tiu = TexImageUtils;

function createSource(width, height, depth, viewType, offset) {
  var size = width * height * depth * 4;  // RGBA
  var buf = new window[viewType](size + offset);
  for (var ii = 0; ii < size; ++ii) {
    if (viewType == "Float32Array")
      buf[ii + offset] = ii / 255.0;
    else
      buf[ii + offset] = ii;
  }
  return buf;
}

function comparePixels(ref, refOffset, data, tol) {
  for (var ii = 0; ii < data.length; ++ii) {
    // Skip alpha due to shader's handling of alpha values.
    if (ii % 4 == 3)
      continue;
    var src = ref[ii + refOffset];
    if (ref instanceof Float32Array)
      src *= 255;
    if (Math.abs(src - data[ii]) > tol) {
      testFailed("Element " + ii + ": expected " + src + ", got " + data[ii]);
      return;
    }
  }
  return testPassed("texture data uploaded correctly");
}

function run2DTest(gl, test, width, height, srcOffset, tol) {
  debug("");
  debug("Tesing tex{Sub}Image2D with sub source: internalformat = " + test.internalformat +
        ", format = " + test.format + ", type = " + test.type);

  var program = tiu.setupTexturedQuad(gl, test.internalformat);
  if (!program) {
    testFailed("Failed to set up program");
    return;
  }

  var buf = createSource(width, height, 1, test.viewType, srcOffset);

  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  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.texImage2D(gl.TEXTURE_2D, 0, gl[test.internalformat], width, height, 0,
                gl[test.format], gl[test.type], buf, srcOffset + 1);
  wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "srcOffset too large");
  gl.texImage2D(gl.TEXTURE_2D, 0, gl[test.internalformat], width, height, 0,
                gl[test.format], gl[test.type], buf, srcOffset);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texImage2D succeeds with correct buffer and srcOffset");

  wtu.clearAndDrawUnitQuad(gl, [0, 0, 0, 255]);
  var readBuf = new Uint8Array(width * height * 4);
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, readBuf);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no error");
  var tol = 0.5;
  comparePixels(buf, srcOffset, readBuf, tol);

  gl.texImage2D(gl.TEXTURE_2D, 0, gl[test.internalformat], width, height, 0,
                gl[test.format], gl[test.type], null);
  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl[test.format], gl[test.type],
                   buf, srcOffset + 1);
  wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "srcOffset too large");
  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl[test.format], gl[test.type],
                   buf, srcOffset);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texSubImage2D succeeds with correct buffer and srcOffset");

  wtu.clearAndDrawUnitQuad(gl, [0, 0, 0, 255]);
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, readBuf);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no error");
  comparePixels(buf, srcOffset, readBuf, tol);

  gl.deleteTexture(texture);
  gl.deleteProgram(program);
}

function run3DTest(gl, test, width, height, srcOffset, tol) {
  debug("");
  debug("Tesing tex{Sub}Image3D with sub source: internalformat = " + test.internalformat +
        ", format = " + test.format + ", type = " + test.type);

  var program = tiu.setupTexturedQuadWith3D(gl, test.internalformat);
  if (!program) {
    testFailed("Failed to set up program");
    return;
  }

  var depth = 1;
  var buf = createSource(width, height, depth, test.viewType, srcOffset);

  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_3D, texture);
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texImage3D(gl.TEXTURE_3D, 0, gl[test.internalformat], width, height, depth, 0,
                gl[test.format], gl[test.type], buf, srcOffset + 1);
  wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "srcOffset too large");
  gl.texImage3D(gl.TEXTURE_3D, 0, gl[test.internalformat], width, height, depth, 0,
                gl[test.format], gl[test.type], buf, srcOffset);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texImage3D succeeds with correct buffer and srcOffset");

  wtu.clearAndDrawUnitQuad(gl, [0, 0, 0, 255]);
  var readBuf = new Uint8Array(width * height * 4);
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, readBuf);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no error");
  var tol = 0.5;
  comparePixels(buf, srcOffset, readBuf, tol);

  gl.texImage3D(gl.TEXTURE_3D, 0, gl[test.internalformat], width, height, depth, 0,
                gl[test.format], gl[test.type], null);
  gl.texSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, width, height, depth, gl[test.format], gl[test.type],
                   buf, srcOffset + 1);
  wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "srcOffset too large");
  gl.texSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, width, height, depth, gl[test.format], gl[test.type],
                   buf, srcOffset);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texSubImage3D succeeds with correct buffer and srcOffset");

  wtu.clearAndDrawUnitQuad(gl, [0, 0, 0, 255]);
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, readBuf);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no error");
  comparePixels(buf, srcOffset, readBuf, tol);

  gl.deleteTexture(texture);
  gl.deleteProgram(program);
}

var gl = wtu.create3DContext("example", undefined, 2);
if (!gl) {
  testFailed("Fail to get a WebGL context");
} else {
  var testCases = [
    { internalformat: 'RGBA8', format: 'RGBA', type: 'UNSIGNED_BYTE',
      viewType: 'Uint8Array', },
    { internalformat: 'RGBA8I', format: 'RGBA_INTEGER', type: 'BYTE',
      viewType: 'Int8Array', },
    { internalformat: 'RGBA16UI', format: 'RGBA_INTEGER', type: 'UNSIGNED_SHORT',
      viewType: 'Uint16Array', },
    { internalformat: 'RGBA16I', format: 'RGBA_INTEGER', type: 'SHORT',
      viewType: 'Int16Array', },
    { internalformat: 'RGBA32UI', format: 'RGBA_INTEGER', type: 'UNSIGNED_INT',
      viewType: 'Uint32Array', },
    { internalformat: 'RGBA32I', format: 'RGBA_INTEGER', type: 'INT',
      viewType: 'Int32Array', },
    { internalformat: 'RGBA32F', format: 'RGBA', type: 'FLOAT',
      viewType: 'Float32Array', },
  ];

  var srcOffset = 3;
  var tol = 0.5;

  for (var idx = 0; idx < testCases.length; ++idx) {
    run2DTest(gl, testCases[idx], gl.drawingBufferWidth, gl.drawingBufferHeight, srcOffset, tol);
    run3DTest(gl, testCases[idx], gl.drawingBufferWidth, gl.drawingBufferHeight, srcOffset, tol);
  }
}

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