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

#include "OCLGLBuffer.h"

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

const static char* strKernel =
    "__kernel void glbuffer_test( __global uint4 *source, __global uint4 "
    "*glDest, __global uint4 *clDest)   \n"
    "{                                                                         "
    "                             \n"
    "    int  tid = get_global_id(0);                                          "
    "                             \n"
    "    clDest[ tid ] = source[ tid ] + (uint4)(1);                           "
    "                             \n"
    "    glDest[ tid ] = source[ tid ] + (uint4)(2);                           "
    "                             \n"
    "}                                                                         "
    "                             \n";

OCLGLBuffer::OCLGLBuffer() : inGLBuffer_(0), outGLBuffer_(0) { _numSubTests = 1; }

OCLGLBuffer::~OCLGLBuffer() {}

void OCLGLBuffer::open(unsigned int test, char* units, double& conversion, unsigned int deviceId) {
  // Initialize random number seed
  srand((unsigned int)time(NULL));

  OCLGLCommon::open(test, units, conversion, deviceId);
  if (_errorFlag) return;

  // Build the kernel
  program_ = _wrapper->clCreateProgramWithSource(context_, 1, &strKernel, NULL, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clCreateProgramWithSource()  failed (%d)", error_);

  error_ = _wrapper->clBuildProgram(program_, 1, &devices_[deviceId], NULL, 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_, "glbuffer_test", &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clCreateKernel() failed (%d)", error_);
}

void OCLGLBuffer::run(void) {
  if (_errorFlag) {
    return;
  }

  cl_mem buffer;
  cl_uint4 inData[c_numOfElements] = {{{0}}};
  cl_uint4 outDataCL[c_numOfElements] = {{{0}}};
  cl_uint4 outDataGL[c_numOfElements] = {{{0}}};

  // Initialize input data with random values
  for (unsigned int i = 0; i < c_numOfElements; i++) {
    for (unsigned int j = 0; j < sizeof(cl_uint4) / sizeof(cl_uint); j++) {
      inData[i].s[j] = (unsigned int)rand();
    }
  }

  // Generate and Bind in & out OpenGL buffers
  glGenBuffers(1, &inGLBuffer_);
  glGenBuffers(1, &outGLBuffer_);

  glBindBuffer(GL_ARRAY_BUFFER, inGLBuffer_);
  glBufferData(GL_ARRAY_BUFFER, c_numOfElements * sizeof(cl_uint4), inData, GL_STATIC_DRAW);

  glBindBuffer(GL_ARRAY_BUFFER, outGLBuffer_);
  glBufferData(GL_ARRAY_BUFFER, c_numOfElements * sizeof(cl_uint4), outDataGL, GL_STATIC_DRAW);

  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glFinish();

  // Create input buffer from GL input buffer
  buffer = _wrapper->clCreateFromGLBuffer(context_, CL_MEM_READ_ONLY, inGLBuffer_, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "Unable to create input GL buffer (%d)", error_);
  buffers_.push_back(buffer);

  // Create output buffer from GL output buffer
  buffer = _wrapper->clCreateFromGLBuffer(context_, CL_MEM_WRITE_ONLY, outGLBuffer_, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "Unable to create output GL buffer (%d)", error_);
  buffers_.push_back(buffer);

  // Create a CL output buffer
  buffer = _wrapper->clCreateBuffer(context_, CL_MEM_READ_WRITE, c_numOfElements * sizeof(cl_uint4),
                                    NULL, &error_);
  CHECK_RESULT((error_ != CL_SUCCESS), "clCreateBuffer() failed (%d)", error_);
  buffers_.push_back(buffer);

  // Assign args and execute
  for (unsigned int i = 0; i < buffers_.size(); i++) {
    error_ = _wrapper->clSetKernelArg(kernel_, i, sizeof(cl_mem), &buffers()[i]);
    CHECK_RESULT((error_ != CL_SUCCESS), "clSetKernelArg() failed (%d)", error_);
  }

  error_ =
      _wrapper->clEnqueueAcquireGLObjects(cmdQueues_[_deviceId], 2, &buffers()[0], 0, NULL, NULL);
  CHECK_RESULT((error_ != CL_SUCCESS), "Unable to acquire GL objects (%d)", error_);

  size_t gws[1] = {c_numOfElements};
  error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1, NULL, gws, NULL, 0,
                                            NULL, NULL);
  CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueNDRangeKernel() failed (%d)", error_);

  error_ =
      _wrapper->clEnqueueReleaseGLObjects(cmdQueues_[_deviceId], 2, &buffers()[0], 0, NULL, NULL);
  CHECK_RESULT((error_ != CL_SUCCESS), "clEnqueueReleaseGLObjects failed (%d)", error_);

  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_RESULT((error_ != CL_SUCCESS), "clFinish() failed (%d)", error_);

  // Get the results from both CL and GL buffers
  error_ =
      _wrapper->clEnqueueReadBuffer(cmdQueues_[_deviceId], buffers()[2], CL_TRUE, 0,
                                    c_numOfElements * sizeof(cl_uint4), outDataCL, 0, NULL, NULL);
  CHECK_RESULT((error_ != CL_SUCCESS), "Unable to read output CL array! (%d)", error_);

  glBindBuffer(GL_ARRAY_BUFFER, outGLBuffer_);
  void* glMem = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);
  memcpy(outDataGL, glMem, c_numOfElements * sizeof(cl_uint4));
  glUnmapBuffer(GL_ARRAY_BUFFER);

  cl_uint4 expectedCL = {{0}};
  cl_uint4 expectedGL = {{0}};

  // Check output
  for (unsigned int i = 0; i < c_numOfElements; ++i) {
    // Calculate expected value in CL output buffer (input + 1)
    expectedCL = inData[i];
    expectedCL.s[0]++;
    expectedCL.s[1]++;
    expectedCL.s[2]++;
    expectedCL.s[3]++;

    // Calculate expected value in GL output buffer (input + 2)
    expectedGL = inData[i];
    expectedGL.s[0] += 2;
    expectedGL.s[1] += 2;
    expectedGL.s[2] += 2;
    expectedGL.s[3] += 2;

    // Compare expected output with actual data received
    for (unsigned int j = 0; j < sizeof(cl_uint4) / sizeof(cl_uint); j++) {
      CHECK_RESULT((outDataCL[i].s[j] != expectedCL.s[j]),
                   "Element %d in CL output buffer is incorrect!\n\t \
                         expected:{%d, %d, %d, %d} differs from actual:{%d, %d, %d, %d}",
                   i, expectedCL.s[0], expectedCL.s[1], expectedCL.s[2], expectedCL.s[3],
                   outDataCL[i].s[0], outDataCL[i].s[1], outDataCL[i].s[2], outDataCL[i].s[3]);
      CHECK_RESULT((outDataGL[i].s[j] != expectedGL.s[j]),
                   "Element %d in GL output buffer is incorrect!\n\t \
                         expected:{%d, %d, %d, %d} differs from actual:{%d, %d, %d, %d}",
                   i, expectedGL.s[0], expectedGL.s[1], expectedGL.s[2], expectedGL.s[3],
                   outDataGL[i].s[0], outDataGL[i].s[1], outDataGL[i].s[2], outDataGL[i].s[3]);
    }
  }
}

unsigned int OCLGLBuffer::close(void) {
  for (unsigned int i = 0; i < buffers().size(); ++i) {
    clReleaseMemObject(buffers()[i]);
  }
  buffers_.clear();

  // Delete GL in & out buffers
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glDeleteBuffers(1, &inGLBuffer_);
  inGLBuffer_ = 0;
  glDeleteBuffers(1, &outGLBuffer_);
  outGLBuffer_ = 0;

  return OCLGLCommon::close();
}
