/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#include "OCLGLPerfSepia.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#define WIDTH 1024
#define HEIGHT 1024

// Quiet pesky warnings
#ifdef WIN_OS
#define SNPRINTF sprintf_s
#else
#define SNPRINTF snprintf
#endif

#define MAX(a, b) (a > b ? a : b)

const char* sepiaVertexProgram =
    "!!ARBvp1.0\n"
    "\n"
    "\n"
    "OPTION ARB_position_invariant;\n"
    "\n"
    "PARAM p0 = program.local[2];\n"
    "PARAM p1 = program.local[3];\n"
    "ATTRIB a0 = vertex.texcoord[0];\n"
    "OUTPUT o0 = result.texcoord[0];\n"
    "OUTPUT o1 = result.texcoord[1];\n"
    "TEMP r0, r1;\n"
    "\n"
    "MOV o0, a0;\n"
    "#SWZ r1, a0, x, y, 0, 0;\n"
    "#DPH r0.x, r1, p0;\n"
    "#DPH r0.y, r1, p1;\n"
    "#MOV o1, r0;\n"
    "MOV o1, a0;\n"
    "\n"
    "END\n";

const char* sepiaFragmentProgram =
    "!!ARBfp1.0\n"
    "\n"
    "\n"
    "PARAM p0 = {1e-4, 0.085, 0.0, 0.0};\n"
    "PARAM p1 = {0.2125, 0.7154, 0.0721, 0.0};\n"
    "PARAM p2 = {-3605.984, 0.1323156, 0.0, -0.1991615};\n"
    "PARAM p3 = {708.7939, -0.3903106, -0.05854013, 0.6621023};\n"
    "PARAM p4 = {-50.93341, 0.4654831, 1.027555, -0.9069088};\n"
    "PARAM p5 = {3.116672, 0.7926372, 0.03219686, 1.411847};\n"
    "PARAM p6 = {8.95663e-4, -0.001104567, -6.0827e-4, 0.03277428};\n"
    "PARAM p7 = program.local[0];\n"
    "PARAM p8 = program.local[1];\n"
    "ATTRIB a0 = fragment.texcoord[1];\n"
    "OUTPUT o0 = result.color;\n"
    "TEMP r0, r1, r2, r3;\n"
    "\n"
    "TEX r1, a0, texture[0], RECT;\n"
    "#MAX r0, p0.x, r1.w;\n"
    "#RCP r2, r0.x;\n"
    "#DP3 r3, r1, p1;\n"
    "#MUL r0, r3, r2;\n"
    "#MAD r2, r0, p2, p3;\n"
    "#MAD r2, r2, r0, p4;\n"
    "#MAD r0, r2, r0, p5;\n"
    "#MUL r2, r1.w, p6;\n"
    "#MAD r2, r0, r3, r2;\n"
    "#MAD r0, r1.w, p0.y, -r3;\n"
    "#CMP r2.x, -r0, r2.x, r2.w;\n"
    "#MAD r0, r3, r3, -r3;\n"
    "#CMP r0, r0.x, r2, r3;\n"
    "#MOV r0.w, r1;\n"
    "#MUL r0, r0, p7;\n"
    "#LRP o0, p8.x, r0, r1;\n"
    "MOV o0, r1;\n"
    "\n"
    "END\n";

const static char* strKernel =
    "\n"
    "__kernel void program(write_only image2d_t dest, int flipped, int4 dim, "
    "float2 st_origin, float4 st_delta, float4 l0, float4 l1, float4 l2, "
    "float4 l3, read_only image2d_t t0, sampler_t t_sampler0)\n"
    "{\n"
    "      const sampler_t sam = CLK_NORMALIZED_COORDS_FALSE | "
    "CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST;\n"
    "//    const float4 p0  = (float4)( 0x1.b33334p-3, 0x1.6e48e8p-1, "
    "0x1.275254p-4, 0x0p+0 );\n"
    "//    const float4 p1  = (float4)( 0x1.a36e2ep-14, 0x1.5c28f6p-4, 0x0p+0, "
    "0x0p+0 );\n"
    "//    const float4 p2  = (float4)( 0x1.d595dap-11, -0x1.218e3cp-10, "
    "-0x1.3ee89ep-11, 0x1.0c7ca6p-5 );\n"
    "//    const float4 p3  = (float4)( -0x1.c2bf7cp+11, 0x1.0efb7cp-3, "
    "0x0p+0, -0x1.97e1fcp-3 );\n"
    "//    const float4 p4  = (float4)( 0x1.62659ep+9, -0x1.8fad94p-2, "
    "-0x1.df8f8cp-5, 0x1.52ff12p-1 );\n"
    "//   const float4 p5  = (float4)( -0x1.9777ap+5, 0x1.dca79ap-2, "
    "0x1.070dd8p+0, -0x1.d0565ap-1 );\n"
    "//    const float4 p6  = (float4)( 0x1.8eef1cp+1, 0x1.95d48cp-1, "
    "0x1.07c1b6p-5, 0x1.696ecep+0 );\n"
    "//    int          dest_width = dim.x;\n"
    "//    int          dest_height = dim.y;\n"
    "    float4       o0, r0, r1, r2, r3, r4;\n"
    "//    float4       false_vector = (float4) 0.0f;\n"
    "//    float4       true_vector = (float4) 1.0f;\n"
    "    int2         loc = (int2)( get_global_id(0), get_global_id(1) );\n"
    "//    if ((loc.x >= dim.x) || loc.y >= dim.y) return;\n"
    "//    float4 f0 = (float4)( st_origin.x + ((float)loc.x + 0.5f) * "
    "st_delta.x + ((float)loc.y + 0.5f) * st_delta.z, st_origin.y + "
    "((float)loc.x + 0.5f) * st_delta.y + ((float)loc.y + 0.5f) * st_delta.w, "
    "0.0f, 0.0f );\n"
    "//    r2 = f0;\n"
    "//    r0.x = dot(r2.xy,l2.xy) + l2.w;\n"
    "//    r0.y = dot(r2.xy,l3.xy) + l3.w;\n"
    "//    r4 = r0;\n"
    "    r1 = read_imagef(t0, sam/*t_sampler0*/, r4.xy);\n"
    "//    r3 = dot(r1.xyz,p0.xyz);\n"
    "//    r2 = max(p1.xxxx, r1.wwww);\n"
    "//    r0 = native_recip(r2.xxxx);\n"
    "//    r4 = r3*r0;\n"
    "//    r2 = r1.wwww*p2;\n"
    "//    r0 = mad(r4,p3,p4);\n"
    "//    r0 = mad(r0,r4,p5);\n"
    "//    r0 = mad(r0,r4,p6);\n"
    "//    r2 = mad(r0,r3,r2);\n"
    "//    r0 = mad(r1.wwww,p1.yyyy,-r3);\n"
    "//    r2.x = select(r2.w,r2.x, isless(-r0.x, 0.0f));\n"
    "//    r0 = mad(r3,r3,-r3);\n"
    "//    r0 = select(r3,r2, isless(r0.xxxx, 0.0f));\n"
    "//    r0.w = r1.w;\n"
    "//    r0 = r0*l0;\n"
    "//    r0 = mix(r1,r0, l1.xxxx);\n"
    "//    r0.xyz = min(r0.xyz, r0.www);\n"
    "//    o0 = r0;\n"
    "    write_imagef(dest, loc /*(int2)( loc.x + dim.z , flipped ? "
    "get_image_height(dest) - (loc.y + dim.w + 1) : loc.y + dim.w )*/, r1 "
    "/*o0*/);\n"
    "}\n";

OCLGLPerfSepia::OCLGLPerfSepia() { _numSubTests = 2; }

OCLGLPerfSepia::~OCLGLPerfSepia() {}

void OCLGLPerfSepia::open(unsigned int test, char* units, double& conversion,
                          unsigned int deviceId) {
  bVerify_ = false;
  silentFailure_ = false;
  iterations_ = 50000;
  bpr_ = 0;
  data_ = 0;
  result_ = 0;
  width_ = 0;
  height_ = 0;
  _crcword = 0;
  conversion = 1.0f;
  _deviceId = deviceId;
  _openTest = test;
  texId = 0;
  format_.image_channel_order = CL_RGBA;
  format_.image_channel_data_type = CL_UNORM_INT8;

  srand(0x8956);  // some constant instead of time() so that we get same random
                  // numbers

  if (!IsGLEnabled(test, units, conversion, deviceId)) {
    silentFailure_ = true;
    return;
  }
  OCLGLCommon::open(test, units, conversion, deviceId);
  if (_errorFlag) return;
  if (test == 0) {
    // Build the kernel
    program_ = _wrapper->clCreateProgramWithSource(context_, 1, &strKernel, NULL, &error_);
    CHECK_RESULT((error_ != CL_SUCCESS), "clCreateProgramWithSource()  failed (%d)", error_);
    const char* optionsGPU = "-cl-denorms-are-zero -cl-mad-enable";
    error_ = _wrapper->clBuildProgram(program_, 1, &devices_[deviceId], optionsGPU, NULL, NULL);
    if (error_ != CL_SUCCESS) {
      char programLog[1024];
      _wrapper->clGetProgramBuildInfo(program_, devices_[deviceId], CL_PROGRAM_BUILD_LOG, 1024,
                                      programLog, 0);
      printf("\n%s\n", programLog);
      fflush(stdout);
    }
    CHECK_RESULT((error_ != CL_SUCCESS), "clBuildProgram() failed (%d)", error_);

    kernel_ = _wrapper->clCreateKernel(program_, "program", &error_);
    CHECK_RESULT((error_ != CL_SUCCESS), "clCreateKernel() failed (%d)", error_);
  }
}

void OCLGLPerfSepia::populateData(void) {
  width_ = WIDTH;
  height_ = HEIGHT;
  bpr_ = 4 * width_;
  data_ = (cl_uchar*)malloc(height_ * bpr_);
  for (unsigned int n = 0; n < (height_ * bpr_); n++) {
    data_[n] = (n & 3) ? (rand() % 256) : 0xFF;
  }
}

void OCLGLPerfSepia::runGL(void) {
  glDisable(GL_ALPHA_TEST);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_SCISSOR_TEST);
  glDisable(GL_BLEND);
  glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  glDisable(GL_DITHER);
  glDisable(GL_CULL_FACE);
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  glDepthMask(GL_FALSE);
  glStencilMask(0);

  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

  // let's create the textures we need

  glEnable(GL_TEXTURE_RECTANGLE_EXT);
  glGenTextures(1, &texId);
  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, texId);

  // have GL alloc memory for us for our destination texture which we will be
  // rendering into
  glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA, width_, height_, 0, GL_BGRA /*RGBA*/,
               GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
  glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  // for the source texture we will provide a data ptr and hang on to it
  GLuint srcTexture;

  glGenTextures(1, &srcTexture);
  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, srcTexture);

  glPixelStorei(GL_UNPACK_ROW_LENGTH, width_);
  glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, height_);
  glPixelStorei(GL_UNPACK_ALIGNMENT, 8);

  // XXX Alex -- use optimal texture upload format.
  glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA, width_, height_, 0, GL_BGRA, /* GL_RGBA,*/
               format_.image_channel_order == CL_RGBA ? GL_UNSIGNED_INT_8_8_8_8
                                                      : GL_UNSIGNED_INT_8_8_8_8_REV,
               data_);

  glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glPixelStorei(GL_UNPACK_SWAP_BYTES, 0);
  glPixelStorei(GL_UNPACK_LSB_FIRST, 0);
  glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
  glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
  glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
  glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
  glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

  GLuint vertexProgram;
  GLuint fragmentProgram;

  glGenProgramsARB(1, &vertexProgram);
  glGenProgramsARB(1, &fragmentProgram);

  glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vertexProgram);
  glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
                     (GLsizei)strlen(sepiaVertexProgram), sepiaVertexProgram);

  glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, fragmentProgram);
  glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
                     (GLsizei)strlen(sepiaFragmentProgram), sepiaFragmentProgram);

  GLfloat l0[] = {1.0f, 0.99f, 0.92f, 1.0f};
  GLfloat l1[] = {0.5, 0, 0, 0};
  GLfloat l2[] = {1, 0, 0, 0};
  GLfloat l3[] = {0, -1, 0, (GLfloat)height_};

  glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 0, l0);
  glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 1, l1);
  glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 2, l2);
  glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 3, l3);

  glProgramLocalParameter4fvARB(GL_FRAGMENT_PROGRAM_ARB, 0, l0);
  glProgramLocalParameter4fvARB(GL_FRAGMENT_PROGRAM_ARB, 1, l1);
  glProgramLocalParameter4fvARB(GL_FRAGMENT_PROGRAM_ARB, 2, l2);
  glProgramLocalParameter4fvARB(GL_FRAGMENT_PROGRAM_ARB, 3, l3);

  GLuint fbo;

  glGenFramebuffersEXT(1, &fbo);

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB,
                            texId, 0);
  glViewport(0, 0, width_, height_);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0, width_, 0, height_, -1, 1);
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);
  glDisable(GL_BLEND);

  glEnable(GL_VERTEX_PROGRAM_ARB);
  glEnable(GL_FRAGMENT_PROGRAM_ARB);

  // warm up
  for (unsigned int k = 0; k < (iterations_ / 10); k++) {
    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex2f(0, (GLfloat)height_);
    glTexCoord2f((GLfloat)width_, 0);
    glVertex2f((GLfloat)width_, (GLfloat)height_);
    glTexCoord2f((GLfloat)width_, (GLfloat)height_);
    glVertex2f((GLfloat)width_, 0);
    glTexCoord2f(0, (GLfloat)height_);
    glVertex2f(0, 0);
    glEnd();
    glFlush();
    glFinish();
  }

  // actual test
  for (unsigned int k = 0; k < iterations_; k++) {
    if (k == 1) {
      timer_.Reset();
      timer_.Start();
    }

    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex2f(0, (GLfloat)height_);
    glTexCoord2f((GLfloat)width_, 0);
    glVertex2f((GLfloat)width_, (GLfloat)height_);
    glTexCoord2f((GLfloat)width_, (GLfloat)height_);
    glVertex2f((GLfloat)width_, 0);
    glTexCoord2f(0, (GLfloat)height_);
    glVertex2f(0, 0);
    glEnd();
  }

  glFlush();
  glFinish();

  timer_.Stop();

  glDisable(GL_VERTEX_PROGRAM_ARB);
  glDisable(GL_FRAGMENT_PROGRAM_ARB);

  // now let's read back the pixels
  result_ = (cl_uchar*)malloc(width_ * height_ * 4);

  glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, result_);

  // bind back default frame buffer
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

  glDeleteFramebuffersEXT(1, &fbo);
  glDeleteTextures(1, &srcTexture);
  glDeleteProgramsARB(1, &vertexProgram);
  glDeleteProgramsARB(1, &fragmentProgram);
}

void OCLGLPerfSepia::runCL(void) {
  cl_mem dst, src;
  cl_sampler nearestZero;

  glEnable(GL_TEXTURE_RECTANGLE_EXT);
  glGenTextures(1, &texId);
  glBindTexture(GL_TEXTURE_RECTANGLE_EXT, texId);
  // XXX Alex: have GL alloc memory for us ...
  glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA, width_, height_, 0, GL_RGBA /*BGRA*/,
               GL_UNSIGNED_INT_8_8_8_8_REV, NULL);

  dst = _wrapper->clCreateFromGLTexture2D(context_, CL_MEM_READ_WRITE, GL_TEXTURE_RECTANGLE_EXT, 0,
                                          texId, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clCreateFromGLTexture2D error (%d)", error_);
  nearestZero =
      _wrapper->clCreateSampler(context_, CL_FALSE, CL_ADDRESS_CLAMP, CL_FILTER_NEAREST, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clCreateSampler error (%d)", error_);
  src = _wrapper->clCreateImage2D(context_, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, &format_,
                                  width_, height_, bpr_, data_, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clCreateImage2D error (%d)", error_);

  int numArgs = 0;
  int dim[2] = {(int)width_, (int)height_};
  int flipped[] = {1};
  int dims[] = {(int)width_, (int)height_, 0, 0};
  float st_origin[] = {0, 0};
  float st_delta[] = {1, 0, 0, 1};

  _wrapper->clSetKernelArg(kernel_, numArgs++, sizeof(cl_mem),
                           &dst);  // arg is a image2DGL named "dst"
  _wrapper->clSetKernelArg(kernel_, numArgs++, sizeof(int),
                           &flipped);  // arg is a int1 named "flipped"
  _wrapper->clSetKernelArg(kernel_, numArgs++, 4 * sizeof(int),
                           &dims);  // arg is a int4 named "dim"
  _wrapper->clSetKernelArg(kernel_, numArgs++, 2 * sizeof(float),
                           &st_origin);  // arg is a float2 named "st_origin"
  _wrapper->clSetKernelArg(kernel_, numArgs++, 4 * sizeof(float),
                           &st_delta);  // arg is a float4 named "st_delta"

  float l0[] = {1.0f, 0.99f, 0.92f, 1.0f};
  float l1[] = {0.5f, 0.0f, 0.0f, 0.0f};
  float l2[] = {1.0f, 0.0f, 0.0f, 0.0f};
  float l3[] = {0.0f, -1.0f, 0.0f, (float)height_};

  _wrapper->clSetKernelArg(kernel_, numArgs++, 4 * sizeof(float),
                           &l0);  // arg is a float4 named "l0"
  _wrapper->clSetKernelArg(kernel_, numArgs++, 4 * sizeof(float),
                           &l1);  // arg is a float4 named "l1"
  _wrapper->clSetKernelArg(kernel_, numArgs++, 4 * sizeof(float),
                           &l2);  // arg is a float4 named "l2"
  _wrapper->clSetKernelArg(kernel_, numArgs++, 4 * sizeof(float),
                           &l3);  // arg is a float4 named "l3"
  _wrapper->clSetKernelArg(kernel_, numArgs++, sizeof(cl_mem),
                           &src);  // arg is a image2D named "t0"
  _wrapper->clSetKernelArg(kernel_, numArgs++, sizeof(cl_sampler),
                           &nearestZero);  // arg is a sampler named "t_sampler0"

  size_t execution_threads[2];
  size_t execution_local[2];
  cl_uint work_dim = 2;
  error_ =
      _wrapper->clGetKernelWorkGroupInfo(kernel_, devices_[_deviceId], CL_KERNEL_WORK_GROUP_SIZE,
                                         sizeof(execution_local[0]), &execution_local[0], 0);
  CHECK_RESULT((error_ != CL_SUCCESS), "clGetKernelWorkGroupInfo error (%d)", error_);
  execution_local[1] = 1;
  work_dim = 2;
  GetKernelExecDimsForImage((unsigned int)execution_local[0], dim[0], dim[1], execution_threads,
                            execution_local);
  result_ = (cl_uchar*)malloc(height_ * bpr_);

  const size_t origin[] = {0, 0, 0};
  const size_t region[] = {width_, height_, 1};

  // warm up
  for (unsigned int k = 0; k < (iterations_ / 10); k++) {
    error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, work_dim, NULL,
                                              execution_threads, execution_local, 0, NULL, NULL);
    CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueNDRangeKernel error (%d)", error_);
    error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
    CHECK_RESULT((error_ != CL_SUCCESS), "clFinish error (%d)", error_);
  }

  // actual test
  for (unsigned int k = 0; k < iterations_; k++) {
    if (k == 1) {
      timer_.Reset();
      timer_.Start();
    }
    error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, work_dim, NULL,
                                              execution_threads, execution_local, 0, NULL, NULL);
    CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueNDRangeKernel error (%d)", error_);
  }
  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_RESULT((error_ != CL_SUCCESS), "clFinish error (%d)", error_);

  timer_.Stop();

  error_ = _wrapper->clEnqueueReadImage(cmdQueues_[_deviceId], dst, true, origin, region, bpr_, 0,
                                        result_, 0, NULL, NULL);
  CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueReadImage error (%d)", error_);
  _wrapper->clFinish(cmdQueues_[_deviceId]);

  _wrapper->clReleaseMemObject(src), src = NULL;
  _wrapper->clReleaseSampler(nearestZero);
  _wrapper->clReleaseMemObject(dst), dst = NULL;
}

void OCLGLPerfSepia::GetKernelExecDimsForImage(unsigned int work_group_size, unsigned int w,
                                               unsigned int h, size_t* global, size_t* local) {
  unsigned int a, b;
  static const unsigned int tile_size = 16;

  // local[0] and local[1] must be at least 1
  local[0] = tile_size < work_group_size ? tile_size : work_group_size;
  local[1] =
      work_group_size / tile_size > tile_size ? tile_size : MAX(work_group_size / tile_size, 1);

  a = w;
  b = (unsigned int)local[0];

  global[0] = ((a % b) != 0) ? (a / b + 1) : (a / b);
  global[0] *= local[0];

  a = h;
  b = (unsigned int)local[1];

  global[1] = ((a % b) != 0) ? (a / b + 1) : (a / b);
  global[1] *= local[1];
}

void OCLGLPerfSepia::run(void) {
  if (_errorFlag || silentFailure_) {
    return;
  }
  populateData();
  if (_openTest == 0) {
    runCL();
  } else {
    runGL();
  }
  if (bVerify_) {
    verifyResult();
  }
  char buf[100];
  SNPRINTF(buf, sizeof(buf), "%s iterations# %d", (_openTest == 0) ? "CL" : "GL", iterations_);
  testDescString = buf;
  _perfInfo = (float)timer_.GetElapsedTime();
}

void OCLGLPerfSepia::verifyResult(void) {
  int r = 0, g = 0, b = 0, a = 0, d = 0;
  for (unsigned int k = 0; k < height_ * bpr_; k += 4) {
    a = a + result_[k + 0];
    r = r + result_[k + 1];
    g = g + result_[k + 2];
    b = b + result_[k + 3];
  }
  d = abs(r - 152797810) + abs(g - 125868080) + abs(b - 76147833) + abs(a - 267386880);
  CHECK_RESULT(d > 20000, "wrong result");
}
unsigned int OCLGLPerfSepia::close(void) {
  if (silentFailure_) {
    return 0;
  }

  if (data_) {
    free(data_);
  }

  if (result_) {
    free(result_);
  }

  if (texId) {
    glDeleteTextures(1, &texId);
  }

  return OCLGLCommon::close();
}
