/*
 * Copyright 2019 Intel Corporation
 *
 * 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: dsa-texelfetch.c
 * Tests that texelFetch() gets texels from texture with different
 * internal types.
 *
 *
 * We populate an MS texture then we draw into
 * a rectangle texture with values from the MS texture.
 * We check that the data from rectangle texture is equal
 * with the data in the MS texture.
 *
 * Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=109057
 */

#include "piglit-util-gl.h"


PIGLIT_GL_TEST_CONFIG_BEGIN

    config.supports_gl_core_version = 31;
    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 *vs_src =
   "#version 130\n"
   "#extension GL_ARB_explicit_attrib_location : require\n"
   "layout (location = 0) in vec2 position;\n"
   "void main() {\n" " gl_Position = vec4(position, 0.0, 1.0);\n" "} \n";


static const char *fs_float_draw =
   "#version 130\n"
   "#extension GL_ARB_explicit_attrib_location : require\n"
   "layout (location = 0) out float my_output;\n"
   "void main() {\n" "  my_output = 1;\n" "} \n";

static const char *fs_float_read =
   "#version 130\n"
   "#extension GL_ARB_texture_multisample : require\n"
   "uniform sampler2DMS sampler;\n"
   "out float my_output;\n"
   "void main() {\n"
   "  my_output = texelFetch(sampler, ivec2(0,0), 0).r;\n" "} \n";

static const char *fs_int_draw =
   "#version 130\n"
   "#extension GL_ARB_explicit_attrib_location : require\n"
   "layout (location = 0) out uint my_output;\n"
   "void main() {\n" "  my_output = 1u;\n" "} \n";

static const char *fs_int_read =
   "#version 130\n"
   "#extension GL_ARB_texture_multisample : require\n"
   "uniform usampler2DMS sampler;\n"
   "out uint my_output;\n"
   "void main() {\n"
   "  my_output = uint(texelFetch(sampler, ivec2(0,0), 0).r);\n" "} \n";

struct texture_type
{
   const char *name;
   GLenum internal_type;
} types_float[] = {
   {"GL_R8", GL_R8 },
   {"GL_R8_SNORM", GL_R8_SNORM },
   {"GL_R16", GL_R16 },
   {"GL_R16_SNORM", GL_R16_SNORM },
   {"GL_RG8", GL_RG8 },
   {"GL_RG8_SNORM", GL_RG8_SNORM },
   {"GL_RG16", GL_RG16 },
   {"GL_RG16_SNORM", GL_RG16_SNORM },
   {"GL_R3_G3_B2", GL_R3_G3_B2 },
   {"GL_RGB4", GL_RGB4 },
   {"GL_RGB5", GL_RGB5 },
   {"GL_RGB8", GL_RGB8 },
   {"GL_RGB8_SNORM", GL_RGB8_SNORM },
   {"GL_RGB10", GL_RGB10 },
   {"GL_RGB12", GL_RGB12 },
   {"GL_RGB16_SNORM", GL_RGB16_SNORM },
   {"GL_RGBA2", GL_RGBA2 },
   {"GL_RGBA4", GL_RGBA4 },
   {"GL_RGB5_A1", GL_RGB5_A1 },
   {"GL_RGBA8", GL_RGBA8 },
   {"GL_RGBA8_SNORM", GL_RGBA8_SNORM },
   {"GL_RGB10_A2", GL_RGB10_A2 },
   {"GL_RGBA12", GL_RGBA12 },
   {"GL_RGBA16", GL_RGBA16 },
   {"GL_SRGB8", GL_SRGB8 },
   {"GL_SRGB8_ALPHA8", GL_SRGB8_ALPHA8 },
   {"GL_R16F", GL_R16F },
   {"GL_RG16F", GL_RG16F },
   {"GL_RGB16F", GL_RGB16F },
   {"GL_RGBA16F", GL_RGBA16F },
   {"GL_R32F", GL_R32F },
   {"GL_RG32F", GL_RG32F },
   {"GL_RGB32F", GL_RGB32F },
   {"GL_RGBA32F", GL_RGBA32F },
   {"GL_R11F_G11F_B10F", GL_R11F_G11F_B10F },
   {"GL_RGB9_E5", GL_RGB9_E5 }
},
types_int[]={
   {"GL_R8I", GL_R8I },
   {"GL_R8UI", GL_R8UI },
   {"GL_R16I", GL_R16I },
   {"GL_R16UI", GL_R16UI },
   {"GL_R32I", GL_R32I },
   {"GL_R32UI", GL_R32UI },
   {"GL_RG8I", GL_RG8I },
   {"GL_RG8UI", GL_RG8UI },
   {"GL_RG16I", GL_RG16I },
   {"GL_RG16UI", GL_RG16UI },
   {"GL_RG32I", GL_RG32I },
   {"GL_RG32UI", GL_RG32UI },
   {"GL_RGB8I", GL_RGB8I },
   {"GL_RGB8UI", GL_RGB8UI },
   {"GL_RGB16I", GL_RGB16I },
   {"GL_RGB16UI", GL_RGB16UI },
   {"GL_RGB32I", GL_RGB32I },
   {"GL_RGB32UI", GL_RGB32UI },
   {"GL_RGBA8I", GL_RGBA8I },
   {"GL_RGBA8UI", GL_RGBA8UI },
   {"GL_RGBA16I", GL_RGBA16I },
   {"GL_RGBA16UI", GL_RGBA16UI },
   {"GL_RGBA32I", GL_RGBA32I },
   {"GL_RGBA32UI", GL_RGBA32UI },
   {"GL_RGB10_A2UI", GL_RGB10_A2UI }
   };

static const float quad_points[] = {
   1.0f, -1.0f,
   1.0f, 1.0f,
   -1.0f, -1.0f,
   -1.0f, 1.0f
};

static GLuint prog_float_draw, prog_float_read, prog_int_draw, prog_int_read;

enum piglit_result
read_from_texture(GLenum internal_type, bool is_int)
{
   GLuint init_fbo, result_fbo;
   GLuint init_texture, result_texture;
   enum piglit_result subtest_result = PIGLIT_PASS;
   float *data_f;
   unsigned char *data_i;
   GLenum format = is_int ? GL_R8UI : GL_R16;

   glCreateFramebuffers(1, &init_fbo);
   glBindFramebuffer(GL_FRAMEBUFFER, init_fbo);

   glCreateTextures(GL_TEXTURE_2D_MULTISAMPLE, 1, &init_texture);
   glTextureStorage2DMultisample(init_texture, 4, internal_type, piglit_width,
                                 piglit_height, GL_FALSE);

   if (glGetError() == GL_INVALID_OPERATION) {
      /* The spec says:
       *    "An INVALID_OPERATION error is generated if samples is greater
       *     than the maximum  number  of  samples  supported  for  this target
       *     and internalformat"
       *
       * So skip the "internal_type" + 4 samples combination in this case.
       */
      return PIGLIT_SKIP;
   }

   glNamedFramebufferTexture(init_fbo, GL_COLOR_ATTACHMENT0, init_texture, 0);
   glNamedFramebufferDrawBuffer(init_fbo, GL_COLOR_ATTACHMENT0);

   GLenum status = glCheckNamedFramebufferStatus(init_fbo, GL_DRAW_FRAMEBUFFER);
   if (status == GL_FRAMEBUFFER_UNSUPPORTED)
      return PIGLIT_SKIP;
   if (status != GL_FRAMEBUFFER_COMPLETE)
      return PIGLIT_FAIL;

   if (is_int)
      glUseProgram(prog_int_draw);
   else
      glUseProgram(prog_float_draw);

   glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

   glCreateFramebuffers(1, &result_fbo);
   glBindFramebuffer(GL_FRAMEBUFFER, result_fbo);

   glCreateTextures(GL_TEXTURE_RECTANGLE, 1, &result_texture);
   glTextureStorage2D(result_texture, 1, format, piglit_width, piglit_height);

   glNamedFramebufferTexture(result_fbo, GL_COLOR_ATTACHMENT0, result_texture, 0);
   glNamedFramebufferDrawBuffer(result_fbo, GL_COLOR_ATTACHMENT0);

   glBindTextureUnit(0, init_texture);

   if (is_int)
      glUseProgram(prog_int_read);
   else
      glUseProgram(prog_float_read);

   glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

   if (is_int) {
      data_i = (unsigned char *) malloc(piglit_width * piglit_height *
                                  sizeof(unsigned char));
      glGetTextureImage(result_texture, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE,
                        piglit_width * piglit_height * sizeof(unsigned char),
                        data_i);
   }
   else {
      data_f = (float *) malloc(piglit_width * piglit_height * sizeof(float));
      glGetTextureImage(result_texture, 0, GL_RED, GL_FLOAT,
                        piglit_width * piglit_height * sizeof(float), data_f);
   }

   for (int i = 0; i < piglit_width * piglit_height; ++i) {
      if (is_int) {
         if (data_i[i] != 1) {
            subtest_result = PIGLIT_FAIL;
            break;
         }
      }
      else {
         if (data_f[i] != 1.0) {
            subtest_result = PIGLIT_FAIL;
            break;
         }
      }
   }

   if (is_int)
      free(data_i);
   else
      free(data_f);

   return subtest_result;
}

void
run_subtest(struct texture_type type, bool is_int,
            enum piglit_result *piglit_test_state)
{
   enum piglit_result piglit_subtest_state = PIGLIT_PASS;

   piglit_subtest_state = read_from_texture(type.internal_type, is_int);
   piglit_report_subtest_result(piglit_subtest_state, "Texture type: %s",
                                type.name);
   piglit_merge_result(piglit_test_state, piglit_subtest_state);
}

enum piglit_result
piglit_display(void)
{
   enum piglit_result piglit_test_state = PIGLIT_PASS;

   for (int i = 0; i < ARRAY_SIZE(types_int); ++i)
         run_subtest(types_int[i],true, &piglit_test_state);

   for (int i = 0; i < ARRAY_SIZE(types_float); ++i)
         run_subtest(types_float[i],false, &piglit_test_state);

   return piglit_test_state;
}



void
piglit_init(int argc, char *argv[])
{

   piglit_require_extension("GL_ARB_direct_state_access");
   piglit_require_extension("GL_ARB_texture_multisample");

   GLuint vao;
   GLuint vbo;

   glGenVertexArrays(1, &vao);
   glBindVertexArray(vao);
   prog_float_draw = piglit_build_simple_program(vs_src, fs_float_draw);
   prog_float_read = piglit_build_simple_program(vs_src, fs_float_read);
   prog_int_draw = piglit_build_simple_program(vs_src, fs_int_draw);
   prog_int_read = piglit_build_simple_program(vs_src, fs_int_read);

   glGenBuffers(1, &vbo);
   glBindBuffer(GL_ARRAY_BUFFER, vbo);
   glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(float), quad_points,
                GL_STATIC_DRAW);
   glEnableVertexAttribArray(0);
   glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
}
