/*
 * Copyright © 2010 Luca Barbieri
 *
 * 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
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * 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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS 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.
 *
 */

/** @file spec/arb_color_buffer_float/render.c
 *
 * Tests that vertex and fragment color clamping affects rendering as
 * specified by ARB_color_buffer_float.
 *
 * It also checks that fog, blending and logic op are done as specified by
 * ARB_color_buffer_float.
 *
 * Note that it's unclear what should happen when ARB_fog is specified in
 * the fragment program and fragment clamping is disabled: does the
 * color still get clamped before applying fog?
 *
 * Both ATI and nVidia do that, but they have fixed function fog in the tested
 * cards.
 * TODO: what happens on GeForce 8xxx, GTX 2xx and GTX 4xx?
 */

#include "common.h"

const char *blend_strings[] = { "disabled     ", "(ONE, ZERO)  ", "(CONST, ZERO)", "(ONE, ONE)   " };
GLenum blend_src[] = { 0, GL_ONE, GL_CONSTANT_COLOR, GL_ONE };
GLenum blend_dst[] = { 0, GL_ZERO, GL_ZERO, GL_ONE };

const char *vp_strings[] = {
	"!!ARBvp1.0\n"
	"MOV result.position, vertex.position;\n"
	"MOV result.fogcoord, 0;\n"
	"MOV result.color, {7, -2.75, -0.25, 0.75};\n" "END\n",

	"!!ARBvp1.0\n"
	"MOV result.position, vertex.position;\n"
	"MOV result.fogcoord, 0;\n"
	"MOV result.texcoord[0], {7, -2.75, -0.25, 0.75};\n" "END\n"
};

const char *fp_strings[] = {
	"!!ARBfp1.0\n"
	"MOV result.color, fragment.color;\n"
	"END\n",

	"!!ARBfp1.0\n"
	"MOV result.color, fragment.texcoord[0];\n"
	"END\n",

	"!!ARBfp1.0\n"
	"OPTION ARB_fog_linear;\n"
	"MOV result.color, fragment.color;\n"
	"END\n",

	"!!ARBfp1.0\n"
	"OPTION ARB_fog_linear;\n"
	"MOV result.color, fragment.texcoord[0];\n"
	"END\n",
};

unsigned vps[2];
unsigned fps[4];

GLboolean test_one(unsigned vert_clamp, unsigned frag_clamp, unsigned semantic,
                   unsigned blend, unsigned logicop, unsigned vpmode, unsigned fpmode)
{
	GLfloat probe[4];
	char test_name[4096];
	unsigned clamped = (semantic == 0 && (clamp_enums[vert_clamp] == GL_TRUE || (clamp_enums[vert_clamp] == GL_FIXED_ONLY_ARB && fixed))) || clamp_enums[frag_clamp] == GL_TRUE || (clamp_enums[frag_clamp] == GL_FIXED_ONLY_ARB && fixed);
	float *expected;
	GLboolean cpass;
	GLboolean opass;

	glFogi(GL_FOG_MODE, GL_LINEAR);

	sprintf(test_name, "%s: Attrib %s  VertClamp %s  FragClamp %s  Blending %s  LogicOp %s  %s  %s  Fog %s (expecting %sclamping)",
		format_name,
		semantic ? "TEXCOORD0" : "COLOR    ",
		clamp_strings[vert_clamp],
		clamp_strings[frag_clamp],
		blend_strings[blend],
		logicop ? "Yes" : "No ",
		vpmode ? "ARB_vp" : "ffvp  ",
		fpmode ? "ARB_fp" : "fffp  ",
		test_fog ? "Yes" : "No ",
		clamped ? "" : "no ");
	if (!sanity) {
		glClampColorARB(GL_CLAMP_VERTEX_COLOR_ARB, clamp_enums[vert_clamp]);
		glClampColorARB(GL_CLAMP_FRAGMENT_COLOR_ARB, clamp_enums[frag_clamp]);
	}

	glColor4f(0.1f, 0.2f, 0.3f, 0.4f);
	glTexCoord4f(0.5f, 0.6f, 0.7f, 0.8f);

	if (vpmode) {
		glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vps[semantic]);
		glEnable(GL_VERTEX_PROGRAM_ARB);
	}
	else {
		if (semantic == 0)
			glColor4f(pixels[0], pixels[1], pixels[2], pixels[3]);
		else
			glTexCoord4f(pixels[0], pixels[1], pixels[2], pixels[3]);
	}

	if (fpmode) {
		glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, fps[semantic + (test_fog ? 2 : 0)]);
		glEnable(GL_FRAGMENT_PROGRAM_ARB);
	}
	else {
		if (test_fog)
			glEnable(GL_FOG);
	}

	glClearColor(0.5, 0.5, 0.5, 0.5);
	glClear(GL_COLOR_BUFFER_BIT);

	if (blend) {
		glEnable(GL_BLEND);
		glBlendFunc(blend_src[blend], blend_dst[blend]);
		glBlendColor(2.0f, 2.0f, 2.0f, 2.0f);
	}

	if (logicop)
		glEnable(GL_COLOR_LOGIC_OP);

	piglit_draw_rect(-1, -1, 1, 1);

	if (logicop)
		glDisable(GL_COLOR_LOGIC_OP);
	if (blend)
		glDisable(GL_BLEND);
	if (vpmode)
		glDisable(GL_VERTEX_PROGRAM_ARB);
	if (fpmode)
		glDisable(GL_FRAGMENT_PROGRAM_ARB);
	else if (test_fog)
		glDisable(GL_FOG);

	if (blend == 2 && !logicop) {
		if (fixed_snorm)
			expected = clamped ? clamped_pixels : signed_clamped_pixels;
		else if (fixed)
			expected = clamped_pixels;
		else
			expected = clamped ? clamped_pixels_mul_2 : pixels_mul_2;
	}
	else if (blend == 3 && !logicop) {
		if (fixed_snorm)
			expected = clamped ? clamped_pixels_plus_half_signed_clamped : signed_clamped_pixels_plus_half_signed_clamped;
		else if (fixed)
			expected = clamped_pixels_plus_half_clamped;
		else
			expected = clamped ? clamped_pixels_plus_half : pixels_plus_half;
	}
	else {
		expected = clamped ? clamped_pixels :
			fixed_snorm ? signed_clamped_pixels :
			fixed ? clamped_pixels :
			pixels;
	}

	opass = cpass = piglit_probe_pixel_rgba_silent(0, 0, expected, probe);

	if (nvidia_driver && clamped && !(semantic == 0 && clamp_enums[vert_clamp] == GL_TRUE) && clamp_enums[frag_clamp] == GL_TRUE && !fixed && fpmode && (!blend || logicop || format == GL_RGBA16F_ARB)) {
		printf("nVidia driver known *** MAJOR BUG ***: they don't clamp fragment program results with ARB_fp on either fp32 with no blending or fp16!\n");
		opass = GL_TRUE;
	}
	if (nvidia_driver && clamped && !fixed && !fpmode && semantic == 0 && clamp_enums[vert_clamp] != GL_TRUE && clamp_enums[frag_clamp] == GL_TRUE) {
		printf("nVidia driver known *** MAJOR BUG ***: they don't clamp fragment program results with fffp, vertex clamp off and fragment clamp on fp16/fp32!\n");
		opass = GL_TRUE;
	}

	if (test_fog && fpmode)	{
		//printf("Unclear specification on GL_ARB_fog_*\n");
		opass = GL_TRUE;
	}

	if (!opass) {
		printf("%s: %s\n", (cpass ? "PASS" : "FAIL"), test_name);
		printf("  Expected: %f %f %f %f\n", expected[0], expected[1], expected[2], expected[3]);
		printf("  Observed: %f %f %f %f\n", probe[0], probe[1], probe[2], probe[3]);
		
	}

	return opass;
}


GLboolean test(void)
{
	GLboolean all_pass = GL_TRUE;
	int npass = 0, total = 0;
	unsigned semantic, blend, logicop, vpmode, fpmode;
	unsigned vpmodes = 1 + !!piglit_is_extension_supported("GL_ARB_vertex_program");
	unsigned fpmodes = 1 + !!piglit_is_extension_supported("GL_ARB_fragment_program");
	unsigned vert_clamp, frag_clamp;

	glFogi(GL_FOG_MODE, GL_LINEAR);

	for (vert_clamp = 0; vert_clamp < (sanity ? 1 : 3); ++vert_clamp)
	for (frag_clamp = sanity ? 1 : 0; frag_clamp < (sanity ? 2 : 3); ++frag_clamp)
	for (semantic = 0; semantic < 2; ++semantic)
	for (blend = 0; blend < 4; ++blend)
	for (logicop = 0; logicop < 2; ++logicop)
	for (vpmode = 0; vpmode < vpmodes; ++vpmode)
	for (fpmode = 0; fpmode < fpmodes; ++fpmode)
	{
		GLboolean pass;

		if (!fpmode && semantic)
			continue;

		pass = test_one(vert_clamp, frag_clamp, semantic,
				blend, logicop, vpmode, fpmode);

		if (pass) {
			npass++;
		}
		else {
#if 0
			/* Enable this code to re-run the failed test.
			 * It's easy to set a breakpoint here to start debugging.
			 */
			pass = test_one(vert_clamp, frag_clamp, semantic,
					blend, logicop, vpmode, fpmode);
#endif
		}

		total++;

		all_pass = all_pass && pass;
	}

	printf("Summary: %i/%i passed.\n", npass, total);
	return all_pass;
}

unsigned
init(void)
{
	if (piglit_is_extension_supported("GL_ARB_vertex_program"))
	{
		unsigned i;
		for (i = 0; i < 2; ++i)
			vps[i] = piglit_compile_program(GL_VERTEX_PROGRAM_ARB, vp_strings[i]);
	}

	if (piglit_is_extension_supported("GL_ARB_fragment_program"))
	{
		unsigned i;
		for (i = 0; i < 4; ++i)
			fps[i] = piglit_compile_program(GL_FRAGMENT_PROGRAM_ARB, fp_strings[i]);
	}

	return TEST_SRT;
}
