/*
 * Copyright (c) 2012 VMware, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * on the rights to use, copy, modify, merge, publish, distribute, sub
 * license, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT.  IN NO EVENT SHALL VMWARE AND/OR THEIR SUPPLIERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
 * Test glGetTexImage for luminance formats.
 * Brian Paul
 * 8 Mar 2012
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

	config.window_visual = PIGLIT_GL_VISUAL_RGB | PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static const char *TestName = "getteximage-luminance";
static float tolerance = 3.0 / 255.0;


static bool
rgba_equal(const float *c1, const float *c2)
{
	return ((fabs(c1[0] - c2[0]) < tolerance) &&
		(fabs(c1[1] - c2[1]) < tolerance) &&
		(fabs(c1[2] - c2[2]) < tolerance) &&
		(fabs(c1[3] - c2[3]) < tolerance));
}


static bool
lum_equal(const float *l1, const float *l2)
{
	return fabs(*l1 - *l2) < tolerance;
}


/*
 * Test reading back a luminance texture as luminance and RGBA.
 */
static bool
test_luminance(void)
{
	static const GLfloat lumImage[2*2] = { 0.25, 0.25, 0.25, 0.25 };
	static const GLfloat rgbaImage[4] = { 0.25, 0.0, 0.0, 1.0 };
	GLuint tex, pbo;
	GLfloat  *test_pbo = NULL;
	bool pass = true;
	GLfloat test[2*2*4];

	/* create 2x2 GL_LUMINANCE texture */
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 2, 2, 0,
		     GL_LUMINANCE, GL_FLOAT, lumImage);

	/* Get and check luminance image */
	glGetTexImage(GL_TEXTURE_2D, 0, GL_LUMINANCE, GL_FLOAT, test);
	if (!lum_equal(lumImage, test)) {
		printf("%s: glGetTexImage(GL_LUMINANCE as"
		       " GL_LUMINANCE) failed\n", TestName);
		printf("  Expected %g  Found %g\n", lumImage[0], test[0]);
		return false;
	}

	/* Get and check rgba image */
	glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &test);
	if (!rgba_equal(rgbaImage, test)) {
		printf("%s: glGetTexImage(GL_LUMINANCE as GL_RGBA) failed\n",
		       TestName);
		printf("  Expected %g, %g, %g, %g  Found %g, %g, %g, %g\n",
		       rgbaImage[0], rgbaImage[1], rgbaImage[2], rgbaImage[3],
		       test[0], test[1], test[2], test[3]);
		pass = false;
	}

	/* Test reading in to a PBO. */
	if (!piglit_is_extension_supported("GL_ARB_pixel_buffer_object"))
		return pass;

	glGenBuffersARB(1, &pbo);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER, pbo);
	glBufferDataARB(GL_PIXEL_PACK_BUFFER, 2*2*4*4, NULL, GL_STREAM_DRAW_ARB);
	glPixelStorei(GL_PACK_ALIGNMENT, 1);

	/* Get in to a PBO and check rgba image */
	glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, NULL);

	test_pbo = glMapBufferARB(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY_ARB);

	if (test_pbo && !rgba_equal(rgbaImage, test_pbo)) {
		printf("%s: glGetTexImage(GL_LUMINANCE as GL_RGBA) in pbo failed\n",
		       TestName);
		printf("  Expected %g, %g, %g, %g  Found %g, %g, %g, %g\n",
		       rgbaImage[0], rgbaImage[1], rgbaImage[2], rgbaImage[3],
		       test_pbo[0], test_pbo[1], test_pbo[2], test_pbo[3]);
		pass = false;
	}

	glUnmapBufferARB(GL_PIXEL_PACK_BUFFER);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER, 0);
	glDeleteBuffersARB(1, &pbo);
	return pass;
}


/*
 * Test reading back an RGBA texture as luminance.
 */
static bool
test_rgba(void)
{
	static const GLfloat rgbaImage[4] = { 0.5, 0.25, 0.125, 1.0 };
	static const GLfloat lumImage[1] = { 0.5 };
	GLuint tex, pbo;
	GLfloat  *test_pbo = NULL;
	bool pass = true;
	GLfloat test[2*2*4];

	/* create 1x1 GL_RGBA texture */
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
		     GL_RGBA, GL_FLOAT, rgbaImage);

	/* Get and check luminance image */
	glGetTexImage(GL_TEXTURE_2D, 0, GL_LUMINANCE, GL_FLOAT, test);
	if (!lum_equal(lumImage, test)) {
		printf("%s: glGetTexImage(GL_RGBA as GL_LUMINANCE) failed\n",
		       TestName);
		printf("  Expected %g  Found %g\n", lumImage[0], test[0]);
		pass = false;
	}

	/* Test reading in to a PBO. */
	if (!piglit_is_extension_supported("GL_ARB_pixel_buffer_object"))
		return pass;

	glGenBuffersARB(1, &pbo);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER, pbo);
	glBufferDataARB(GL_PIXEL_PACK_BUFFER, 2*2*4*4, NULL, GL_STREAM_DRAW_ARB);
	glPixelStorei(GL_PACK_ALIGNMENT, 1);

	/* Get in to a PBO and check luminance image */
	glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, NULL);

	test_pbo = glMapBufferARB(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY_ARB);

	if (test_pbo && !rgba_equal(rgbaImage, test_pbo)) {
		printf("%s: glGetTexImage(GL_RGBA as GL_LUMINANCE) in pbo failed\n",
		       TestName);
		printf("  Expected %g  Found %g\n", lumImage[0], test_pbo[0]);
		pass = false;
	}

	glUnmapBufferARB(GL_PIXEL_PACK_BUFFER);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER, 0);
	glDeleteBuffersARB(1, &pbo);
	return pass;
}


/*
 * Test reading back a luminance texture via FBO + glReadPixels as RGBA.
 */
static bool
test_fbo_readpixels_lum_as_rgba(void)
{
	static const GLfloat lumImage[2*2] = { 0.25, 0.25, 0.25, 0.25 };
	static const GLfloat rgbaImage[4] = { 0.25, 0.0, 0.0, 1.0 };
	GLuint tex, fbo, pbo;
	GLfloat  *test_pbo = NULL;
	bool pass = true;
	GLfloat test[2*2*4];
	GLenum status;

	if (!piglit_is_extension_supported("GL_ARB_framebuffer_object"))
		return true;

	/* create 2x2 GL_LUMINANCE texture */
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 2, 2, 0,
		     GL_LUMINANCE, GL_FLOAT, lumImage);

	/* create an FBO to wrap the texture so we can read it back
	 * with glReadPixels
	 */
	glGenFramebuffers(1, &fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT,
			       GL_TEXTURE_2D, tex, 0);

	status = glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT);
	if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
		/* can't test glReadPixels from a luminance fbo/texture */
		if (!piglit_automatic) {
			printf("Skipping FBO ReadPixels test\n");
		}
		return true;
	}

	/* get rgba image (only red should have the lum value) */
	glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, test);
	if (!rgba_equal(rgbaImage, test)) {
		printf("%s: glReadPixels(GL_LUMINANCE as GL_RGBA) failed\n",
		       TestName);
		printf("  Expected %g, %g, %g, %g  Found %g, %g, %g, %g\n",
		       rgbaImage[0], rgbaImage[1], rgbaImage[2], rgbaImage[3],
		       test[0], test[1], test[2], test[3]);
		pass = false;
	}

	/* Test reading in to a PBO. */
	if (!piglit_is_extension_supported("GL_ARB_pixel_buffer_object"))
		return pass;

	glGenBuffersARB(1, &pbo);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER, pbo);
	glBufferDataARB(GL_PIXEL_PACK_BUFFER, 2*2*4, NULL, GL_STREAM_DRAW_ARB);
	glPixelStorei(GL_PACK_ALIGNMENT, 1);

	/* get rgba image in a pbo (only red should have the lum value) */
	glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, NULL);

	test_pbo = glMapBufferARB(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY_ARB);

	if (test_pbo && !rgba_equal(rgbaImage, test_pbo)) {
		printf("%s: glReadPixels(GL_LUMINANCE as GL_RGBA) in pbo failed\n",
		       TestName);
		printf("  Expected %g, %g, %g, %g  Found %g, %g, %g, %g\n",
		       rgbaImage[0], rgbaImage[1], rgbaImage[2], rgbaImage[3],
		       test_pbo[0], test_pbo[1], test_pbo[2], test_pbo[3]);
		pass = false;
	}

	glUnmapBufferARB(GL_PIXEL_PACK_BUFFER);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER, 0);
	glDeleteBuffersARB(1, &pbo);
	return pass;
}


/*
 * Test reading back an RGBA texture via FBO + glReadPixels as luminance.
 */
static bool
test_fbo_readpixels_rgba_as_lum(void)
{
	static const GLfloat rgbaImage[4] = { 0.5, 0.25, 0.125, 1.0 };
	static const GLfloat lumImage[1] = { 0.5 + 0.25 + 0.125 };
	GLuint tex, fbo, pbo;
	GLfloat *test_pbo = NULL;
	bool pass = true;
	GLfloat test[1];
	GLenum status;

	if (!piglit_is_extension_supported("GL_ARB_framebuffer_object"))
		return true;

	/* create 1x1 GL_RGBA texture */
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
		     GL_RGBA, GL_FLOAT, rgbaImage);

	/* create an FBO to wrap the texture so we can read it back
	 * with glReadPixels
	 */
	glGenFramebuffers(1, &fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT,
			       GL_TEXTURE_2D, tex, 0);

	status = glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT);
	if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
		/* something failed with FBO setup, ignore it */
		if (!piglit_automatic) {
			printf("Skipping FBO ReadPixels test\n");
		}
		return true;
	}

	/* get luminance image, should be sum of RGB values */
	glReadPixels(0, 0, 1, 1, GL_LUMINANCE, GL_FLOAT, test);

	if (!lum_equal(lumImage, test)) {
		printf("%s: glReadPixels(GL_RGBA as GL_LUMINANCE) failed\n",
		       TestName);
		printf("  Expected %g  Found %g\n", lumImage[0], test[0]);
		pass = false;
	}

	/* Test reading in to a PBO. */
	if (!piglit_is_extension_supported("GL_ARB_pixel_buffer_object"))
		return pass;

	glGenBuffersARB(1, &pbo);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER, pbo);
	glBufferDataARB(GL_PIXEL_PACK_BUFFER, 4, NULL, GL_STREAM_DRAW_ARB);
	glPixelStorei(GL_PACK_ALIGNMENT, 1);

	/* get luminance image in to pbo, should be sum of RGB values */
	glReadPixels(0, 0, 1, 1, GL_LUMINANCE, GL_FLOAT, NULL);

	test_pbo = glMapBufferARB(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY_ARB);

	if (test_pbo && !lum_equal(lumImage, test_pbo)) {
		printf("%s: glReadPixels(GL_RGBA as GL_LUMINANCE) in pbo failed\n",
		       TestName);
		printf("  Expected %g  Found %g\n", lumImage[0], test_pbo[0]);
		pass = false;
	}

	glUnmapBufferARB(GL_PIXEL_PACK_BUFFER);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER, 0);
	glDeleteBuffersARB(1, &pbo);
	return pass;
}


enum piglit_result
piglit_display(void)
{
	bool pass = true;

	pass = test_luminance() && pass;
	pass = test_rgba() && pass;
	pass = test_fbo_readpixels_lum_as_rgba() && pass;
	pass = test_fbo_readpixels_rgba_as_lum() && pass;

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}


void
piglit_init(int argc, char **argv)
{
	/* empty */
}
