<!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>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<canvas id="canvas" width="16" height="16" style="width: 50px; height: 50px; border: 1px solid black;"></canvas>

<!-- Shaders to test output -->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 aPosition;
void main() {
  gl_Position = aPosition;
}
</script>

<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform float uColor;
void main() {
  gl_FragColor = vec4(uColor, uColor, uColor, 1);
}
</script>

<script id="vshader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec2 texCoord0;
varying vec2 texCoord;
void main()
{
    gl_Position = vPosition;
    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";

var wtu = WebGLTestUtils;
var canvas;
var gl;
var ext = null;

var extConstants = {
  "SRGB_EXT": 0x8C40,
  "SRGB_ALPHA_EXT": 0x8C42,
  "SRGB8_ALPHA8_EXT": 0x8C43,
  "FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT": 0x8210
};

function getExtension() {
  ext = gl.getExtension("EXT_sRGB");
}

function listsExtension() {
  var supported = gl.getSupportedExtensions();
  return (supported.indexOf("EXT_sRGB") >= 0);
}

function toVec3String(val) {
  if (typeof(val) == 'number') {
    return toVec3String([val, val, val]);
  }
  return '[' + val[0] + ', ' + val[1] + ', ' + val[2] + ']';
}

var e = 2; // Amount of variance to allow in result pixels - may need to be tweaked higher

function expectResult(target) {
  wtu.checkCanvasRect(gl,
                      Math.floor(gl.drawingBufferWidth / 2),
                      Math.floor(gl.drawingBufferHeight / 2),
                      1,
                      1,
                      [target, target, target, 255],
                      undefined,
                      e);
}

function createGreysRGBTexture(gl, color, format) {
  var numPixels = gl.drawingBufferWidth * gl.drawingBufferHeight;
  var elements;
  switch (format) {
    case ext.SRGB_EXT: elements = 3; break;
    case ext.SRGB_ALPHA_EXT: elements = 4; break;
    default: return null;
  }

  var size = numPixels * elements;
  var buf = new Uint8Array(size);
  for (var ii = 0; ii < numPixels; ++ii) {
    var off = ii * elements;
    buf[off + 0] = color;
    buf[off + 1] = color;
    buf[off + 2] = color;
    if (format == ext.SRGB_ALPHA_EXT) {
      buf[off + 3] = 255;
    }
  }

  var tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  gl.texImage2D(gl.TEXTURE_2D,
                0,
                format,
                gl.drawingBufferWidth,
                gl.drawingBufferHeight,
                0,
                format,
                gl.UNSIGNED_BYTE,
                buf);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_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);
  return tex;
}

function testValidFormat(fn, internalFormat, formatName, enabled) {
  if (enabled) {
    fn(internalFormat);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "was able to create type " + formatName);
  } else {
    testInvalidFormat(fn, internalFormat, formatName, enabled);
  }
}

function testInvalidFormat(fn, internalFormat, formatName, enabled) {
  fn(internalFormat);
  var err = gl.getError();
  if (err == gl.NO_ERROR) {
    testFailed("should NOT be able to create type " + formatName);
  } else if (err == gl.INVALID_ENUM || err == gl.INVALID_VALUE) {
    testPassed("not able to create invalid format: " + formatName);
  }
}

var textureFormatFixture = {
  desc: "Checking texture formats",
  create: function(format) {
    var tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D,
                  0,                      // level
                  format,                 // internalFormat
                  gl.drawingBufferWidth,  // width
                  gl.drawingBufferHeight, // height
                  0,                      // border
                  format,                 // format
                  gl.UNSIGNED_BYTE,       // type
                  null);                  // data
  },
  tests: [
    {
      desc: "Checking valid formats",
      fn: testValidFormat,
      formats: [ 'SRGB_EXT', 'SRGB_ALPHA_EXT' ]
    },
    {
      desc: "Checking invalid formats",
      fn: testInvalidFormat,
      formats: [ 'SRGB8_ALPHA8_EXT' ]
    }
  ]
};

var renderbufferFormatFixture = {
  desc: "Checking renderbuffer formats",
  create: function(format) {
    var rbo = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
    gl.renderbufferStorage(gl.RENDERBUFFER,
                           format,
                           gl.drawingBufferWidth,
                           gl.drawingBufferHeight);
  },
  tests: [
    {
      desc: "Checking valid formats",
      fn: testValidFormat,
      formats: [ 'SRGB8_ALPHA8_EXT' ]
    },
    {
      desc: "Checking invalid formats",
      fn: testInvalidFormat,
      formats: [ 'SRGB_EXT', 'SRGB_ALPHA_EXT' ]
    }
  ]
};


description("Test sRGB texture support");

debug("");
debug("Canvas.getContext");

canvas = document.getElementById("canvas");
gl = wtu.create3DContext(canvas);
if (!gl) {
  testFailed("context does not exist");
} else {
  testPassed("context exists");

  debug("");
  debug("Checking sRGB texture support with extension disabled");

  runFormatTest(textureFormatFixture, false);
  runFormatTest(renderbufferFormatFixture, false);

  debug("");
  debug("Checking sRGB texture support");

  // Query the extension and store globally so shouldBe can access it
  ext = gl.getExtension("EXT_sRGB");

  if (!ext) {
    testPassed("No EXT_sRGB support -- this is legal");

    runSupportedTest(false);
    finishTest();
  } else {
    testPassed("Successfully enabled EXT_sRGB extension");

    runSupportedTest(true);

    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

    runConstantsTest();
    runFormatTest(textureFormatFixture, true);
    runFormatTest(renderbufferFormatFixture, true);
    runTextureReadConversionTest();
    runFramebufferTextureConversionTest(ext.SRGB_EXT);
    runFramebufferTextureConversionTest(ext.SRGB_ALPHA_EXT);
    runFramebufferRenderbufferConversionTest();
    runGenerateMipmapTest();
    runLoadFromImageTest(function() {
      finishTest();
    });
  }
}

function runConstantsTest() {
  debug("");
  debug("Checking extension constants values");

  for (var constant in extConstants) {
    if (constant in ext) {
      if (extConstants[constant] != ext[constant]) {
        testFailed("Value of " + constant + " should be: " + extConstants[constant] + ", was: " + ext[constant]);
      } else {
        testPassed("Value of " + constant + " was expected value: " + extConstants[constant]);
      }
    } else {
      testFailed(constant + " not found in extension object");
    }
  }
}

function runSupportedTest(extensionEnabled) {
  if (listsExtension()) {
    if (extensionEnabled) {
      testPassed("EXT_sRGB listed as supported and getExtension succeeded");
    } else {
      testFailed("EXT_sRGB listed as supported but getExtension failed");
    }
  } else {
    if (extensionEnabled) {
      testFailed("EXT_sRGB not listed as supported but getExtension succeeded");
    } else {
      testPassed("EXT_sRGB not listed as supported and getExtension failed -- this is legal");
    }
  }
}

function runFormatTest(fixture, enabled) {
  debug("");
  debug(fixture.desc);

  for (var tt = 0; tt < fixture.tests.length; ++tt) {
    var test = fixture.tests[tt];
    debug(test.desc);

    for (var ii = 0; ii < test.formats.length; ++ii) {
      var formatName = test.formats[ii];
      test.fn(fixture.create, extConstants[formatName], "ext." + formatName, enabled);
    }

    if (tt != fixture.tests.length - 1)
      debug("");
  }
}

function runTextureReadConversionTest() {
  debug("");
  debug("Test the conversion of colors from sRGB to linear on texture read");

  // Draw
  var conversions = [
    [   0,   0 ],
    [  63,  13 ],
    [ 127,  54 ],
    [ 191, 133 ],
    [ 255, 255 ]
  ];

  var program = wtu.setupTexturedQuad(gl);
  gl.uniform1i(gl.getUniformLocation(program, "tex"), 0);

  for (var ii = 0; ii < conversions.length; ii++) {
    var tex = createGreysRGBTexture(gl, conversions[ii][0], ext.SRGB_EXT);
    wtu.drawUnitQuad(gl);
    expectResult(conversions[ii][1]);
  }
}

function runFramebufferTextureConversionTest(format) {
  var formatString;
  var validFormat;
  switch (format) {
    case ext.SRGB_EXT: formatString = "sRGB"; validFormat = false; break;
    case ext.SRGB_ALPHA_EXT: formatString = "sRGB_ALPHA"; validFormat = true; break;
    default: return null;
  }
  debug("");
  debug("Test " + formatString + " framebuffer attachments." + (validFormat ? "" : " (Invalid)"));

  var program = wtu.setupProgram(gl, ['vertexShader', 'fragmentShader'], ['aPosition'], [0]);
  var tex = createGreysRGBTexture(gl, 0, format);
  var fbo = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
  wtu.glErrorShouldBe(gl, gl.NO_ERROR);

  shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, ext.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT)', 'ext.SRGB_EXT');

  if (validFormat) {
    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");

    debug("");
    debug("Test the conversion of colors from linear to " + formatString + " on framebuffer (texture) write");

    // Draw
    var conversions = [
      [   0,   0 ],
      [  13,  63 ],
      [  54, 127 ],
      [ 133, 191 ],
      [ 255, 255 ]
    ];

    wtu.setupUnitQuad(gl, 0);

    for (var ii = 0; ii < conversions.length; ii++) {
      gl.uniform1f(gl.getUniformLocation(program, "uColor"), conversions[ii][0]/255.0);
      wtu.drawUnitQuad(gl, [0, 0, 0, 0]);
      wtu.glErrorShouldBe(gl, gl.NO_ERROR);
      expectResult(conversions[ii][1]);
    }
  } else {
    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT");

    wtu.setupUnitQuad(gl, 0);
    gl.uniform1f(gl.getUniformLocation(program, "uColor"), 0.5);
    wtu.drawUnitQuad(gl, [0, 0, 0, 0]);
    wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION);
  }

  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

function runFramebufferRenderbufferConversionTest() {
  debug("");
  debug("Test the conversion of colors from linear to sRGB on framebuffer (renderbuffer) write");

  function createsRGBFramebuffer(gl, width, height) {
    var rbo = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
    gl.renderbufferStorage(gl.RENDERBUFFER, ext.SRGB8_ALPHA8_EXT, width, height);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);

    var fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
                               gl.RENDERBUFFER, rbo);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);

    shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, ext.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT)', 'ext.SRGB_EXT');
    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");

    return fbo;
  }

  // Draw
  var conversions = [
    [   0,   0 ],
    [  13,  63 ],
    [  54, 127 ],
    [ 133, 191 ],
    [ 255, 255 ]
  ];

  var program = wtu.setupProgram(gl, ['vertexShader', 'fragmentShader'], ['aPosition'], [0]);
  wtu.setupUnitQuad(gl, 0);
  var fbo = createsRGBFramebuffer(gl, gl.drawingBufferWidth, gl.drawingBufferHeight);

  for (var ii = 0; ii < conversions.length; ii++) {
    gl.uniform1f(gl.getUniformLocation(program, "uColor"), conversions[ii][0]/255.0);
    wtu.drawUnitQuad(gl, [0, 0, 0, 0]);
    expectResult(conversions[ii][1]);
  }
}

function runLoadFromImageTest(callback) {
  debug("");
  debug("Tests to ensure that SRGB textures can successfully use image elements as their source");

  var img = wtu.makeImage("../../resources/gray-1024x1024.jpg", function() {
    var tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, ext.SRGB_EXT, ext.SRGB_EXT, gl.UNSIGNED_BYTE, img);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);

    gl.texImage2D(gl.TEXTURE_2D, 0, ext.SRGB_ALPHA_EXT, ext.SRGB_ALPHA_EXT, gl.UNSIGNED_BYTE, img);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);

    callback();
  }, function() {
    testFailed("Image could not be loaded");
    callback();
  });
}

function runGenerateMipmapTest()
{
    debug("");
    debug("GenerateMipmaps for sRGB textures is forbidden");

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

    gl.texImage2D(gl.TEXTURE_2D, 0, ext.SRGB_ALPHA_EXT, 2, 2, 0, ext.SRGB_ALPHA_EXT,
                  gl.UNSIGNED_BYTE, null);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);
    gl.generateMipmap(gl.TEXTURE_2D);
    wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);

    gl.deleteTexture(tex);
}

var successfullyParsed = true;
</script>
</body>
</html>
