<!--
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 Big FBO Test</title>
<link rel="stylesheet" href="../resources/js-test-style.css"/>
<script src="../../devtools/src/debug/webgl-debug.js"></script>
<script src="../js/js-test-pre.js"></script>
<script src="../js/webgl-test-utils.js"></script>
</head>
<body>
<canvas id="canvas" width="256" height="256"> </canvas>
<div id="description"></div>
<div id="console"></div>
<script id="vshader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec2 texCoord0;
varying vec2 texCoord;
void main()
{
  gl_Position = vec4(vPosition.xyz, 1.0);
  texCoord = texCoord0;
}
</script>

<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D tex;
varying vec2 texCoord;
void main()
{
  gl_FragColor = texture2D(tex, texCoord);
}
</script>
<script>
"use strict";
window.onload = init;

var g_textures = [];

debug("Tests the performance of using lots of large FBOs");

function init() {
  if (confirm(
      "After clicking OK your machine may become unresponsive or crash.")) {
    main();
  } else {
    debug("cancelled");
  }
}

function checkFBOStatus(gl) {
  var err = gl.getError();
  if (err != gl.NO_ERROR) {
    if (err != gl.OUT_OF_MEMORY)
      testFailed("gl.getError returned " + err);
    else
      testPassed("OUT-OF-MEMORY");
    return false;
  }
  var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  if (status != gl.FRAMEBUFFER_COMPLETE) {
    testFailed("gl.checkFramebufferStatus() returned " + WebGLTestUtils.glEnumToString(gl, status));
    return false;
  }
  return true;
}

function setupFBO(gl, size) {
  var tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  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);

  var fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);

  gl.texImage2D(gl.TEXTURE_2D,
                0,                 // level
                gl.RGBA,           // internalFormat
                size,              // width
                size,              // height
                0,                 // border
                gl.RGBA,           // format
                gl.UNSIGNED_BYTE,  // type
                null);             // data
  if (!checkFBOStatus(gl))
    return null;

  return { fb: fb, tex: tex };
}

function checkPixels(gl) {
  var width = 256;
  var height = 256;

  var thresh = 3;

  var buf = new Uint8Array(width * height * 4);
  gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
  for (var yy = 0; yy < height; ++yy) {
    for (var xx = 0; xx < width; ++xx) {
      var offset = (yy * width + xx) * 4;
      if (Math.abs(buf[offset] - 255) > thresh ||
          Math.abs(buf[offset + 1] - 0) > thresh ||
          Math.abs(buf[offset + 2] - 0) > thresh) {
        testFailed("drawing results incorrect");
        return false;
      }
    }
  }
  return true;
}

function handleContextLost() {
  debug("context lost");
}

function main() {
  debug("");
  debug("Checking for out of memory handling.");

  var canvas = document.getElementById("canvas");
  canvas.addEventListener('webglcontextlost', handleContextLost);
  var wtu = WebGLTestUtils;
  var gl = wtu.create3DContext("canvas");
  var prog = wtu.setupProgram(gl, ["vshader", "fshader"], ["vPosition", "texCoord0"]);

  WebGLDebugUtils.init(gl);

  gl.disable(gl.DEPTH_TEST);
  gl.disable(gl.BLEND);

  var vertexObject = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    -1,1,0, 1,1,0, -1,-1,0,
    -1,-1,0, 1,1,0, 1,-1,0
  ]), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(0);
  gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);

  var vertexObject = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0, 1,0, 0,1,
    0,1, 1,0, 1,1
  ]), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(1);
  gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);

  var texLoc = gl.getUniformLocation(prog, "tex");
  gl.uniform1i(texLoc, 0);

  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setup should succeed");

  var size = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
  debug("max render buffer size: " + size +
        ", size used: " + (size / 2));
  size /= 2;

  var maxFBOs = 200;
  var numFBOs = 0;
  allocateNextFBO();

  function allocateNextFBO() {
    if (numFBOs >= maxFBOs) {
      phase2();
      return;
    }
    if (!allocateFBO()) {
      phase2();
      return;
    }
    ++numFBOs;
    setTimeout(allocateNextFBO, 100);
  }

  function allocateFBO() {
    debug("");
    debug("trying to create fbo #" + (numFBOs + 1));
    var t = setupFBO(gl, 2);
    if (!t) {
      return false;
    }

    var tex = t.tex;
    var fb = t.fb;

    debug("allocating fbo color buffer of size " + size + " x " + size);
    gl.texImage2D(gl.TEXTURE_2D,
                  0,                 // level
                  gl.RGBA,           // internalFormat
                  size,              // width
                  size,              // height
                  0,                 // border
                  gl.RGBA,           // format
                  gl.UNSIGNED_BYTE,  // type
                  null);             // data
    if (!checkFBOStatus(gl)) {
      return false;
    }
    g_textures.push(tex);
    debug("succeeded in creating fbo");

    debug("clearing the fbo with red color");
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    gl.clearColor(1, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    debug("deleting fbo, but the now red texture should be untouched");
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.deleteFramebuffer(fb);

    debug("drawing to the canvas using the red texture");
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    if (!checkPixels(gl)) {
      return false;
    }

    debug("succeeded in drawing");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "each run with no error");
    return true;
  }

  function phase2() {
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    debug("");
    debug("fbos allocated:" + numFBOs);
    if (!checkPixels(gl)) {
      testFailed("final check of canvas drawing buffer pixels failed");
    }
    debug("");
    finishTest();
  }
}

var successfullyParsed = true;
</script>

</body>
</html>
