/*
 * Copyright (c) 2011 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.
 */

/**
 * Tests rendering with vertex arrays when neither GL_VERTEX_ARRAY nor generic
 * attribute 0 array is enabled.  Test legacy/fixed-function, GLSL and ARB_vp.
 *
 * Brian Paul
 * May 2011
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

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

PIGLIT_GL_TEST_CONFIG_END

static const char *TestName = "pos-array";

static const GLfloat VertexData[] = {
   /* positions */
   -1, -1,
   1, -1,
   1, 1,
   -1, 1,
   /* colors */
   1, 0, 0,
   1, 0, 0,
   0, 0, 1,
   0, 0, 1
};


static GLuint
setup_vbo(void)
{
   GLuint buf;

   glGenBuffersARB(1, &buf);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB, buf);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(VertexData), VertexData,
                   GL_DYNAMIC_DRAW_ARB);
   return buf;
}


/** Test legacy/fixed-function vertex array drawing */
static GLboolean
test_fixedfunc_arrays(void)
{
   static const GLfloat expected[4] = {0.5, 0.0, 0.5, 1.0};
   static const GLfloat black[4] = {0.0, 0.0, 0.0, 0.0};
   GLuint buf;
   GLboolean p,pass = GL_TRUE;

   buf = setup_vbo();
   glBindBufferARB(GL_ARRAY_BUFFER_ARB, buf);

   /*
    * Draw with conventional arrays.
    */
   {
      glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), (void *) 0);
      glColorPointer(3, GL_FLOAT, 3 * sizeof(GLfloat),
                     (void *) (8 * sizeof(GLfloat)));
      glEnableClientState(GL_VERTEX_ARRAY);
      glEnableClientState(GL_COLOR_ARRAY);

      glClear(GL_COLOR_BUFFER_BIT);
      glDrawArrays(GL_QUADS, 0, 4);

      p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, expected);
      piglit_present_results();
      if (!p) {
         printf("%s: failed when drawing with ", TestName);
         printf("conventional vertex arrays\n");
         pass = GL_FALSE;
      }

      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_COLOR_ARRAY);
   }

   /*
    * Draw with generic array[0]=position
    * XXX this should only work when the driver aliases conventional
    * vertex attributes with the generic attributes (as w/ NVIDIA).
    * XXX check for that and enable this code some day.
    */
   if (0) {
      GLuint attrib = 0;
      glVertexAttribPointerARB(attrib, 2, GL_FLOAT, GL_FALSE,
                               2 * sizeof(GLfloat), (void *) 0);
      glColorPointer(3, GL_FLOAT, 3 * sizeof(GLfloat),
                     (void *) (8 * sizeof(GLfloat)));
      glEnableVertexAttribArrayARB(attrib);
      glEnableClientState(GL_COLOR_ARRAY);

      glClear(GL_COLOR_BUFFER_BIT);
      glDrawArrays(GL_QUADS, 0, 4);

      p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, expected);
      piglit_present_results();
      if (!p) {
         printf("%s: failed when drawing with ", TestName);
         printf("generic array [%u]\n", attrib);
         pass = GL_FALSE;
      }

      glDisableVertexAttribArrayARB(attrib);
      glDisableClientState(GL_COLOR_ARRAY);
   }

   /*
    * Draw without GL_VERTEX or GENERIC[0] array set (should NOT draw)
    */
   {
      GLuint attrib = 3;
      glVertexAttribPointerARB(attrib, 2, GL_FLOAT, GL_FALSE,
                               2 * sizeof(GLfloat), (void *) 0);
      glColorPointer(3, GL_FLOAT, 3 * sizeof(GLfloat),
                     (void *) (8 * sizeof(GLfloat)));
      glEnableVertexAttribArrayARB(attrib);
      glEnableClientState(GL_COLOR_ARRAY);

      glClear(GL_COLOR_BUFFER_BIT);
      glDrawArrays(GL_QUADS, 0, 4);

      p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, black);
      piglit_present_results();
      if (!p) {
         printf("%s: failed when drawing with ", TestName);
         printf("generic array [%u]\n", attrib);
         pass = GL_FALSE;
      }

      glDisableVertexAttribArrayARB(attrib);
      glDisableClientState(GL_COLOR_ARRAY);
   }

   piglit_present_results();

   glDeleteBuffersARB(1, &buf);

   return pass;
}


/** Test drawing with GLSL shaders */
static GLboolean
test_glsl_arrays(void)
{
   static const char *vertShaderText =
      "attribute vec4 color, pos; \n"
      "varying vec4 colorVar; \n"
      "void main() \n"
      "{ \n"
      "   colorVar = color; \n"
      "   gl_Position = gl_ModelViewProjectionMatrix * pos; \n"
      "} \n";

   static const char *fragShaderText =
      "varying vec4 colorVar; \n"
      "void main() \n"
      "{ \n"
      "   gl_FragColor = colorVar; \n"
      "} \n";

   static const GLfloat expected[4] = {0.5, 0.0, 0.5, 1.0};
   GLuint buf;
   GLboolean p, pass = GL_TRUE;
   GLint posAttrib, colorAttrib;
   GLuint vertShader, fragShader, program;

   buf = setup_vbo();
   glBindBufferARB(GL_ARRAY_BUFFER_ARB, buf);

   vertShader = piglit_compile_shader_text(GL_VERTEX_SHADER, vertShaderText);
   fragShader = piglit_compile_shader_text(GL_FRAGMENT_SHADER, fragShaderText);
   program = piglit_link_simple_program(vertShader, fragShader);

   glUseProgram(program);

   /*
    * Draw with compiler-assigned attribute locations
    */
   {
      posAttrib = glGetAttribLocation(program, "pos");
      colorAttrib = glGetAttribLocation(program, "color");

      if (0)
         printf("%s: GLSL posAttrib = %d  colorAttrib = %d\n",
                TestName, posAttrib, colorAttrib);

      glVertexAttribPointerARB(posAttrib, 2, GL_FLOAT, GL_FALSE,
                               2 * sizeof(GLfloat), (void *) 0);
      glVertexAttribPointerARB(colorAttrib, 3, GL_FLOAT, GL_FALSE,
                               3 * sizeof(GLfloat),
                               (void *) (8 * sizeof(GLfloat)));
      glEnableVertexAttribArrayARB(posAttrib);
      glEnableVertexAttribArrayARB(colorAttrib);

      glClear(GL_COLOR_BUFFER_BIT);
      glDrawArrays(GL_QUADS, 0, 4);

      p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, expected);
      piglit_present_results();
      if (!p) {
         printf("%s: failed when drawing with ", TestName);
         printf("compiler-assigned attribute locations\n");
         pass = GL_FALSE;
      }

      glDisableVertexAttribArrayARB(posAttrib);
      glDisableVertexAttribArrayARB(colorAttrib);
   }

   /*
    * Draw with user-defined attribute bindings, not using 0.
    */
   {
      posAttrib = 5;
      colorAttrib = 7;

      glBindAttribLocation(program, posAttrib, "pos");
      glBindAttribLocation(program, colorAttrib, "color");

      glLinkProgram(program);

      glVertexAttribPointerARB(posAttrib, 2, GL_FLOAT, GL_FALSE,
                               2 * sizeof(GLfloat), (void *) 0);
      glVertexAttribPointerARB(colorAttrib, 3, GL_FLOAT, GL_FALSE,
                               3 * sizeof(GLfloat),
                               (void *) (8 * sizeof(GLfloat)));
      glEnableVertexAttribArrayARB(posAttrib);
      glEnableVertexAttribArrayARB(colorAttrib);

      glClear(GL_COLOR_BUFFER_BIT);
      glDrawArrays(GL_QUADS, 0, 4);

      p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, expected);
      piglit_present_results();
      if (!p) {
         printf("%s: failed when drawing with ", TestName);
         printf("user-assigned attribute locations\n");
         pass = GL_FALSE;
      }

      glDisableVertexAttribArrayARB(posAttrib);
      glDisableVertexAttribArrayARB(colorAttrib);
   }

   glDeleteShader(vertShader);
   glDeleteProgram(program);
   glDeleteBuffersARB(1, &buf);

   return pass;
}


/**
 * Test drawing with GLSL shaders and no vertex arrays.
 * Use a vertex shader with a hard-coded vertex position.
 */
static GLboolean
test_glsl_no_arrays(void)
{
   static const char *noVertexVertShaderText =
      "varying vec4 colorVar; \n"
      "void main() \n"
      "{ \n"
      "   colorVar = vec4(1.0, 1.0, 0.0, 1.0); \n"
      "   gl_Position = vec4(0.0, 0.0, 0.0, 1.0); \n"
      "} \n";

   static const char *fragShaderText =
      "varying vec4 colorVar; \n"
      "void main() \n"
      "{ \n"
      "   gl_FragColor = colorVar; \n"
      "} \n";

   static const GLfloat expected[4] = {1.0, 1.0, 0.0, 1.0};
   GLboolean p, pass = GL_TRUE;
   GLuint vertShader, fragShader, program;

   vertShader = piglit_compile_shader_text(GL_VERTEX_SHADER,
                                           noVertexVertShaderText);
   fragShader = piglit_compile_shader_text(GL_FRAGMENT_SHADER, fragShaderText);
   program = piglit_link_simple_program(vertShader, fragShader);

   glUseProgram(program);

   glClear(GL_COLOR_BUFFER_BIT);
   glPointSize(3.0);
   glDrawArrays(GL_POINTS, 0, 1);
   glPointSize(1.0);

   p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, expected);
   piglit_present_results();
   if (!p) {
      printf("%s: failed when drawing with GLSL and no vertex arrays\n",
             TestName);
      pass = GL_FALSE;
   }

   glDeleteShader(vertShader);
   glDeleteProgram(program);

   return pass;
}


/** Test drawing with GL_ARB_vertex_program */
static GLboolean
test_arbvp_arrays(void)
{
   /* Use legacy vertex array/attribute */
   static const char *legacyVertProgramText =
      "!!ARBvp1.0 \n"
      "ATTRIB iPos = vertex.position;\n"
      "OUTPUT oPos = result.position;\n"
      "PARAM mvp[4] = { state.matrix.mvp };\n"
      "DP4 oPos.x, mvp[0], iPos;\n"
      "DP4 oPos.y, mvp[1], iPos;\n"
      "DP4 oPos.z, mvp[2], iPos;\n"
      "DP4 oPos.w, mvp[3], iPos;\n"
      "MOV result.color, vertex.color;\n"
      "END";

   /* Use generic vertex array/attribute[0] */
   static const char *generic0VertProgramText =
      "!!ARBvp1.0 \n"
      "ATTRIB iPos = vertex.attrib[0];\n"
      "OUTPUT oPos = result.position;\n"
      "PARAM mvp[4] = { state.matrix.mvp };\n"
      "DP4 oPos.x, mvp[0], iPos;\n"
      "DP4 oPos.y, mvp[1], iPos;\n"
      "DP4 oPos.z, mvp[2], iPos;\n"
      "DP4 oPos.w, mvp[3], iPos;\n"
      "MOV result.color, vertex.color;\n"
      "END";

   /* Use generic vertex array/attribute[6] */
   static const char *generic6VertProgramText =
      "!!ARBvp1.0 \n"
      "ATTRIB iPos = vertex.attrib[6];\n"
      "OUTPUT oPos = result.position;\n"
      "PARAM mvp[4] = { state.matrix.mvp };\n"
      "DP4 oPos.x, mvp[0], iPos;\n"
      "DP4 oPos.y, mvp[1], iPos;\n"
      "DP4 oPos.z, mvp[2], iPos;\n"
      "DP4 oPos.w, mvp[3], iPos;\n"
      "MOV result.color, vertex.color;\n"
      "END";

   static const char *fragProgramText =
      "!!ARBfp1.0 \n"
      "MOV result.color, fragment.color;\n"
      "END";

   static const GLfloat expected[4] = {0.5, 0.0, 0.5, 1.0};
   GLuint buf;
   GLboolean p, pass = GL_TRUE;
   GLuint vertProg, fragProg;

   buf = setup_vbo();
   glBindBufferARB(GL_ARRAY_BUFFER_ARB, buf);

   fragProg = piglit_compile_program(GL_FRAGMENT_PROGRAM_ARB, fragProgramText);
   glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, fragProg);

   glEnable(GL_FRAGMENT_PROGRAM_ARB);
   glEnable(GL_VERTEX_PROGRAM_ARB);

   /*
    * Draw with pos in conventional arrays.
    */
   {
      vertProg = piglit_compile_program(GL_VERTEX_PROGRAM_ARB,
                                        legacyVertProgramText);
      glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vertProg);
      glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), (void *) 0);
      glColorPointer(3, GL_FLOAT, 3 * sizeof(GLfloat),
                     (void *) (8 * sizeof(GLfloat)));
      glEnableClientState(GL_VERTEX_ARRAY);
      glEnableClientState(GL_COLOR_ARRAY);

      glClear(GL_COLOR_BUFFER_BIT);
      glDrawArrays(GL_QUADS, 0, 4);

      p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, expected);
      piglit_present_results();
      if (!p) {
         printf("%s: failed when drawing with ", TestName);
         printf("ARB VP and conventional vertex arrays\n");
         pass = GL_FALSE;
      }

      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_COLOR_ARRAY);
      glDeleteProgramsARB(1, &vertProg);
   }

   /*
    * Draw with pos in generic array[0].
    */
   {
      GLuint attrib = 0;
      vertProg = piglit_compile_program(GL_VERTEX_PROGRAM_ARB,
                                        generic0VertProgramText);
      glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vertProg);
      glVertexAttribPointerARB(attrib, 2, GL_FLOAT, GL_FALSE,
                               2 * sizeof(GLfloat), (void *) 0);
      glColorPointer(3, GL_FLOAT, 3 * sizeof(GLfloat),
                     (void *) (8 * sizeof(GLfloat)));
      glEnableVertexAttribArrayARB(attrib);
      glEnableClientState(GL_COLOR_ARRAY);

      glClear(GL_COLOR_BUFFER_BIT);
      glDrawArrays(GL_QUADS, 0, 4);

      p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, expected);
      piglit_present_results();
      if (!p) {
         printf("%s: failed when drawing with ", TestName);
         printf("ARB VP and generic vertex array[%u]\n", attrib);
         pass = GL_FALSE;
      }

      glDisableVertexAttribArrayARB(attrib);
      glDisableClientState(GL_COLOR_ARRAY);
      glDeleteProgramsARB(1, &vertProg);
   }

   /*
    * Draw with pos in generic array[6].
    */
   {
      GLuint attrib = 6;
      vertProg = piglit_compile_program(GL_VERTEX_PROGRAM_ARB,
                                        generic6VertProgramText);
      glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vertProg);
      glVertexAttribPointerARB(attrib, 2, GL_FLOAT, GL_FALSE,
                               2 * sizeof(GLfloat), (void *) 0);
      glColorPointer(3, GL_FLOAT, 3 * sizeof(GLfloat),
                     (void *) (8 * sizeof(GLfloat)));
      glEnableVertexAttribArrayARB(attrib);
      glEnableClientState(GL_COLOR_ARRAY);

      glClear(GL_COLOR_BUFFER_BIT);
      glDrawArrays(GL_QUADS, 0, 4);

      p = piglit_probe_pixel_rgba(piglit_width/2, piglit_height/2, expected);
      piglit_present_results();
      if (!p) {
         printf("%s: failed when drawing with ", TestName);
         printf("ARB VP and generic vertex array[%u]\n", attrib);
         pass = GL_FALSE;
      }

      glDisableVertexAttribArrayARB(attrib);
      glDisableClientState(GL_COLOR_ARRAY);
      glDeleteProgramsARB(1, &vertProg);
   }

   glDisable(GL_FRAGMENT_PROGRAM_ARB);
   glDisable(GL_VERTEX_PROGRAM_ARB);

   glDeleteProgramsARB(1, &fragProg);
   glDeleteBuffersARB(1, &buf);

   return pass;
}


enum piglit_result
piglit_display(void)
{
   GLboolean pass = GL_TRUE;

   pass &= test_fixedfunc_arrays();

   if (piglit_is_extension_supported("GL_ARB_vertex_program") &&
       piglit_is_extension_supported("GL_ARB_fragment_program")) {
      pass &= test_arbvp_arrays();
   }

   if (piglit_is_extension_supported("GL_ARB_shader_objects") &&
       piglit_is_extension_supported("GL_ARB_vertex_shader") &&
       piglit_is_extension_supported("GL_ARB_fragment_shader")) {
      pass &= test_glsl_arrays();
      pass &= test_glsl_no_arrays();
   }

   return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}


void
piglit_init(int argc, char **argv)
{
   piglit_require_extension("GL_ARB_vertex_buffer_object");
}
