/* Copyright (c) 2010 - 2021 Advanced Micro Devices, 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 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 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. */

#include "top.hpp"

#ifdef _WIN32
#include <d3d10_1.h>
#include <d3d9.h>
#include <dxgi.h>
// This is necessary since there are common GL/D3D10 functions
#include "cl_d3d9_amd.hpp"
#include "cl_d3d10_amd.hpp"
#include "cl_d3d11_amd.hpp"
#endif  //_WIN32

#include <GL/gl.h>
#include <GL/glext.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>

#include "cl_common.hpp"
#include "platform/interop_gl.hpp"

#include "device/device.hpp"

/* The pixel internal format for DOPP texture defined in gl_enum.h */
#define GL_BGR8_ATI 0x8083
#define GL_BGRA8_ATI 0x8088

#include <cstring>
#include <vector>


/*! \addtogroup API
 *  @{
 *
 *  \addtogroup CL_GL_Interops
 *
 * This section discusses OpenCL functions that allow applications to
 * use OpenGL buffer/texture/render-buffer objects as OpenCL memory
 * objects. This allows efficient sharing of data between these OpenCL
 * and OpenGL. The OpenCL API can be used to execute kernels that read
 * and/or write memory objects that are also an OpenGL buffer object
 * or a texture.  An OpenCL image object can be created from an OpenGL
 * texture or renderbuffer object. An OpenCL buffer object can be
 * created from an OpenGL buffer object.  An OpenCL memory object can
 * be created from an OpenGL texture/buffer/render-buffer object or
 * the default system provided framebuffer if any only if the OpenCL
 * clContext has been created from a GL clContext. OpenGL contexts are
 * created using platform specific APIs (EGL, CGL, WGL, GLX are some
 * of the platform specific APIs that allow applications to create GL
 * contexts). The appropriate platform API (such as EGL, CGL, WGL,
 * GLX) will be extended to allow a CL clContext to be created from a
 * GL clContext. Creating an OpenCL memory object from the default
 * system provided framebuffer will also require an appropriate
 * extension to the platform API. Refer to the appropriate platform
 * API documentation to understand how to create a CL clContext from a
 * GL clContext and creating a CL memory object from the default
 * system provided framebuffer.
 *
 *  @{
 *
 *  \addtogroup clCreateFromGLBuffer
 *
 *  @{
 */

/*! \brief Creates an OpenCL buffer object from an OpenGL buffer object.
 *
 *  \param clContext is a valid OpenCL clContext created from an OpenGL clContext.
 *
 *  \param clFlags is a bit-field that is used to specify usage information. Only
 *  CL_MEM_READ_ONLY, CL_MEM_WRITE_ONLY and CL_MEM_READ_WRITE can be used.
 *
 *  \param glBufferName is a GL buffer object name. The GL buffer
 *  object must have a data store created though it does not need to
 *  be initialized. The size of the data store will be used to
 *  determine the size of the CL buffer object.
 *
 *  \param pCpuMem is a pointer to the buffer data that may already be
 *  allocated by the application. The size of the buffer that pCpuMem points
 *  to must be >= \a size bytes. Passing in a pointer to an already allocated
 *  buffer on the host and using it as a buffer object allows applications to
 *  share data efficiently with kernels and the host.
 *
 *  \param errcode_ret will return an appropriate error code. If errcode_ret
 *  is NULL, no error code is returned.
 *
 *  \return valid non-zero OpenCL buffer object and errcode_ret is set
 *  to CL_SUCCESS if the buffer object is created successfully. It
 *  returns a NULL value with one of the following error values
 *  returned in \a errcode_ret:
 *  - CL_INVALID_CONTEXT if \a clContext is not a valid clContext.
 *  - CL_INVALID_VALUE if values specified in \a clFlags are not valid.
 *  - CL_INVALID_GL_OBJECT if glBufferName is not a GL buffer object or is a
 *    GL buffer object but does not have a data store created.
 *  - CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources required
 *    by the runtime.
 *
 *  \version 1.0r29
 */
RUNTIME_ENTRY_RET(cl_mem, clCreateFromGLBuffer,
                  (cl_context context, cl_mem_flags flags, GLuint bufobj, cl_int* errcode_ret)) {
  cl_mem clMemObj = NULL;

  if (!is_valid(context)) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("invalid parameter \"context\"");
    return clMemObj;
  }

  if (!(((flags & CL_MEM_READ_ONLY) == CL_MEM_READ_ONLY) ||
        ((flags & CL_MEM_WRITE_ONLY) == CL_MEM_WRITE_ONLY) ||
        ((flags & CL_MEM_READ_WRITE) == CL_MEM_READ_WRITE))) {
    *not_null(errcode_ret) = CL_INVALID_VALUE;
    LogWarning("invalid parameter \"flags\"");
    return clMemObj;
  }

  return (amd::clCreateFromGLBufferAMD(*as_amd(context), flags, bufobj, errcode_ret));
}
RUNTIME_EXIT

/*! \brief creates the following:
 *  - an OpenCL 2D image object from an OpenGL 2D texture object
 *    or a single face of an OpenGL cubemap texture object,
 *  - an OpenCL 2D image array object from an OpenGL 2D texture array object,
 *  - an OpenCL 1D image object from an OpenGL 1D texture object,
 *  - an OpenCL 1D image buffer object from an OpenGL texture buffer object,
 *  - an OpenCL 1D image array object from an OpenGL 1D texture array object,
 *  - an OpenCL 3D image object from an OpenGL 3D texture object.
 *
 *  \param clContext is a valid OpenCL clContext created from an OpenGL clContext.
 *
 *  \param clFlags is a bit-field that is used to specify usage information.
 *  Only CL_MEM_READ_ONLY, CL_MEM_WRITE_ONLY and CL_MEM_READ_WRITE values
 *  can be used.
 *
 *  \param texture_target must be GL_TEXTURE_1D, GL_TEXTURE_1D_ARRAY,
 *  GL_TEXTURE_BUFFER, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D,
 *  GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
 *  GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
 *  GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
 *  GL_TEXTURE_CUBE_MAP_NEGATIVE_Z or GL_TEXTURE_RECTANGLE_ARB.
 *
 *  \param miplevel is the mipmap level to be used. If \a texture_target
 *  is GL_TEXTURE_BUFFER, \a miplevel must be 0.
 *
 *  \param texture is a GL 1D, 2D, 3D, 1D array, 2D array, cubemap,
 *  rectangle or buffer texture object.
 *  The texture object must be a complete texture as per
 *  OpenGL rules on texture completeness. The texture format and dimensions
 *  defined by OpenGL for the specified miplevel of the texture will be
 *  used to create the OpenCL image memory object. Only GL texture formats
 *  that map to appropriate image channel order and data type can be used
 *  to create the the OpenCL image memory object.
 *
 *  \param errcode_ret will return an appropriate error code. If \a
 *  errcode_ret is NULL, no error code is returned.
 *
 *  \return A valid non-zero OpenCL image object and \a errcode_ret is set to
 *  CL_SUCCESS if the image object is created successfully. It returns a NULL value
 *  with one of the following error values returned in \a errcode_ret:
 *  - CL_INVALID_CONTEXT if \a clContext is not a valid clContext or was not
 *    created from a GL clContext.
 *  - CL_INVALID_VALUE if values specified in \a clFlags are not valid.
 *  - CL_INVALID_MIP_LEVEL if \a miplevel is not a valid mip-level for \a texture.
 *  - CL_INVALID_GL_OBJECT if \a texture is not an appropriate GL 2D texture,
 *    cubemap or texture rectangle.
 *  - CL_INVALID_IMAGE_FORMAT_DESCRIPTOR if the OpenGL texture format does not
 *    map to an appropriate OpenCL image format.
 *  - CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources required
 *    by the runtime.
 *
 *  \version 1.2r07
 */
RUNTIME_ENTRY_RET(cl_mem, clCreateFromGLTexture,
                  (cl_context context, cl_mem_flags flags, GLenum texture_target, GLint miplevel,
                   GLuint texture, cl_int* errcode_ret)) {
  cl_mem clMemObj = NULL;

  if (!is_valid(context)) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("invalid parameter \"context\"");
    return clMemObj;
  }

  if (!(((flags & CL_MEM_READ_ONLY) == CL_MEM_READ_ONLY) ||
        ((flags & CL_MEM_WRITE_ONLY) == CL_MEM_WRITE_ONLY) ||
        ((flags & CL_MEM_READ_WRITE) == CL_MEM_READ_WRITE))) {
    *not_null(errcode_ret) = CL_INVALID_VALUE;
    LogWarning("invalid parameter \"flags\"");
    return clMemObj;
  }

  const std::vector<amd::Device*>& devices = as_amd(context)->devices();
  bool supportPass = false;
  bool sizePass = false;
  for (const auto& it : devices) {
    if (it->info().imageSupport_) {
      supportPass = true;
    }
  }
  if (!supportPass) {
    *not_null(errcode_ret) = CL_INVALID_OPERATION;
    LogWarning("there are no devices in context to support images");
    return static_cast<cl_mem>(0);
  }

  return amd::clCreateFromGLTextureAMD(*as_amd(context), flags, texture_target, miplevel, texture,
                                       errcode_ret);
}
RUNTIME_EXIT

/*! @}
 *  \addtogroup clCreateFromGLTexture2D
 *  @{
 */

/*! \brief Create an OpenCL 2D image object from an OpenGL 2D texture object.
 *
 *  \param clContext is a valid OpenCL clContext created from an OpenGL clContext.
 *
 *  \param clFlags is a bit-field that is used to specify usage information.
 *  Only CL_MEM_READ_ONLY, CL_MEM_WRITE_ONLY and CL_MEM_READ_WRITE values
 *  can be used.
 *
 *  \param target must be GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
 *  GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
 *  GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
 *  GL_TEXTURE_CUBE_MAP_NEGATIVE_Z or GL_TEXTURE_RECTANGLE_ARB.
 *
 *  \param miplevel is the mipmap level to be used.
 *
 *  \param texture is a GL 2D texture, cubemap or texture rectangle
 *  object name.  The texture object must be a complete texture as per
 *  OpenGL rules on texture completeness. The \a texture format and
 *  dimensions specified using appropriate glTexImage2D call for \a
 *  miplevel will be used to create the 2D image object.  Only GL
 *  texture formats that map to appropriate image channel order and
 *  data type can be used to create the 2D image object.
 *
 *  \param errcode_ret will return an appropriate error code. If \a
 *  errcode_ret is NULL, no error code is returned.
 *
 *  \return A valid non-zero OpenCL image object and \a errcode_ret is set to
 *  CL_SUCCESS if the image object is created successfully. It returns a NULL value
 *  with one of the following error values returned in \a errcode_ret:
 *  - CL_INVALID_CONTEXT if \a clContext is not a valid clContext or was not
 *    created from a GL clContext.
 *  - CL_INVALID_VALUE if values specified in \a clFlags are not valid.
 *  - CL_INVALID_MIP_LEVEL if \a miplevel is not a valid mip-level for \a texture.
 *  - CL_INVALID_GL_OBJECT if \a texture is not an appropriate GL 2D texture,
 *    cubemap or texture rectangle.
 *  - CL_INVALID_IMAGE_FORMAT_DESCRIPTOR if the OpenGL texture format does not
 *    map to an appropriate OpenCL image format.
 *  - CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources required
 *    by the runtime.
 *
 *  \version 1.0r29
 */
RUNTIME_ENTRY_RET(cl_mem, clCreateFromGLTexture2D,
                  (cl_context context, cl_mem_flags flags, GLenum target, GLint miplevel,
                   GLuint texture, cl_int* errcode_ret)) {
  cl_mem clMemObj = NULL;

  if (!is_valid(context)) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("invalid parameter \"context\"");
    return clMemObj;
  }

  if (!(((flags & CL_MEM_READ_ONLY) == CL_MEM_READ_ONLY) ||
        ((flags & CL_MEM_WRITE_ONLY) == CL_MEM_WRITE_ONLY) ||
        ((flags & CL_MEM_READ_WRITE) == CL_MEM_READ_WRITE))) {
    *not_null(errcode_ret) = CL_INVALID_VALUE;
    LogWarning("invalid parameter \"flags\"");
    return clMemObj;
  }

  const std::vector<amd::Device*>& devices = as_amd(context)->devices();
  bool supportPass = false;
  bool sizePass = false;
  for (const auto& it : devices) {
    if (it->info().imageSupport_) {
      supportPass = true;
    }
  }
  if (!supportPass) {
    *not_null(errcode_ret) = CL_INVALID_OPERATION;
    LogWarning("there are no devices in context to support images");
    return static_cast<cl_mem>(0);
  }

  return amd::clCreateFromGLTextureAMD(*as_amd(context), flags, target, miplevel, texture,
                                       errcode_ret);
}
RUNTIME_EXIT

/*! @}
 *  \addtogroup clCreateFromGLTexture3D
 *  @{
 */

/*! \brief Create an OpenCL 3D image object from an OpenGL 3D texture object.
 *
 *  \param clContext is a valid OpenCL clContext created from an OpenGL clContext.
 *
 *  \param clFlags is a bit-field that is used to specify usage information.
 *  Only CL_MEM_READ_ONLY, CL_MEM_WRITE_ONLY and CL_MEM_READ_WRITE values
 *  can be used.
 *
 *  \param target must be GL_TEXTURE_3D.
 *
 *  \param miplevel is the mipmap level to be used.
 *
 *  \param texture is a GL 3D texture object [name].
 *  The texture object must be a complete texture as per OpenGL rules on texture
 *  completeness. The \a texture format and dimensions specified using appropriate
 *  glTexImage3D call for \a miplevel will be used to create the 3D image object.
 *  Only GL texture formats that map to appropriate image channel order and
 *  data type can be used to create the 3D image object.
 *
 *  \param errcode_ret will return an appropriate error code. If \a errcode_ret
 *  is NULL, no error code is returned.
 *
 *  \return A valid non-zero OpenCL image object and \a errcode_ret is set to
 *  CL_SUCCESS if the image object is created successfully. It returns a NULL value
 *  with one of the following error values returned in \a errcode_ret:
 *  - CL_INVALID_CONTEXT if \a clContext is not a valid clContext or was not
 *    created from a GL clContext.
 *  - CL_INVALID_VALUE if values specified in \a clFlags are not valid.
 *  - CL_INVALID_MIP_LEVEL if \a miplevel is not a valid mip-level for \a texture.
 *  - CL_INVALID_GL_OBJECT if \a texture is not an GL 3D texture.
 *  - CL_INVALID_IMAGE_FORMAT_DESCRIPTOR if the OpenGL texture format does not
 *    map to an appropriate OpenCL image format.
 *  - CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources required
 *    by the runtime.
 *
 *  \version 1.0r29
 */
RUNTIME_ENTRY_RET(cl_mem, clCreateFromGLTexture3D,
                  (cl_context context, cl_mem_flags flags, GLenum target, GLint miplevel,
                   GLuint texture, cl_int* errcode_ret)) {
  cl_mem clMemObj = NULL;

  if (!is_valid(context)) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("invalid parameter \"context\"");
    return clMemObj;
  }

  if (!(((flags & CL_MEM_READ_ONLY) == CL_MEM_READ_ONLY) ||
        ((flags & CL_MEM_WRITE_ONLY) == CL_MEM_WRITE_ONLY) ||
        ((flags & CL_MEM_READ_WRITE) == CL_MEM_READ_WRITE))) {
    *not_null(errcode_ret) = CL_INVALID_VALUE;
    LogWarning("invalid parameter \"flags\"");
    return clMemObj;
  }

  const std::vector<amd::Device*>& devices = as_amd(context)->devices();
  bool supportPass = false;
  bool sizePass = false;
  for (const auto& it : devices) {
    if (it->info().imageSupport_) {
      supportPass = true;
    }
  }
  if (!supportPass) {
    *not_null(errcode_ret) = CL_INVALID_OPERATION;
    LogWarning("there are no devices in context to support images");
    return static_cast<cl_mem>(0);
  }

  return amd::clCreateFromGLTextureAMD(*as_amd(context), flags, target, miplevel, texture,
                                       errcode_ret);
}
RUNTIME_EXIT

/*! @}
 *  \addtogroup clCreateFromGLRenderbuffer
 *  @{
 */

/*! \brief Create an OpenCL 2D image object from an OpenGL renderbuffer object.
 *
 *  \param clContext is a valid OpenCL clContext created from an OpenGL clContext.
 *
 *  \param clFlags is a bit-field that is used to specify usage information.
 *  Only CL_MEM_READ_ONLY, CL_MEM_WRITE_ONLY and CL_MEM_READ_WRITE values
 *  can be used.
 *
 *  \param renderbuffer is a GL renderbuffer object name. The renderbuffer
 *  storage must be specified before the image object can be created. Only
 *  GL renderbuffer formats that map to appropriate image channel order and
 *  data type can be used to create the 2D image object.
 *
 *  \param errcode_ret will return an appropriate error code. If \a errcode_ret
 *  is NULL, no error code is returned.
 *
 *  \return A valid non-zero OpenCL image object and \a errcode_ret is set
 *  to CL_SUCCESS if the image object is created successfully. It returns a
 *  NULL value with one of the following error values returned in \a errcode_ret:
 *  - CL_INVALID_CONTEXT if \a clContext is not a valid clContext or was not
 *    created from a GL clContext.
 *  - CL_INVALID_VALUE if values specified in \a clFlags are not valid.
 *  - CL_INVALID_GL_OBJECT if \a renderbuffer is not an GL renderbuffer object.
 *  - CL_INVALID_IMAGE_FORMAT_DESCRIPTOR if the OpenGL renderbuffer format
 *    does not map to an appropriate OpenCL image format.
 *  - CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources required
 *    by the runtime.
 *
 *  \version 1.0r29
 */
RUNTIME_ENTRY_RET(cl_mem, clCreateFromGLRenderbuffer, (cl_context context, cl_mem_flags flags,
                                                       GLuint renderbuffer, cl_int* errcode_ret)) {
  cl_mem clMemObj = NULL;

  if (!is_valid(context)) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("invalid parameter \"context\"");
    return clMemObj;
  }

  if (!(((flags & CL_MEM_READ_ONLY) == CL_MEM_READ_ONLY) ||
        ((flags & CL_MEM_WRITE_ONLY) == CL_MEM_WRITE_ONLY) ||
        ((flags & CL_MEM_READ_WRITE) == CL_MEM_READ_WRITE))) {
    *not_null(errcode_ret) = CL_INVALID_VALUE;
    LogWarning("invalid parameter \"flags\"");
    return clMemObj;
  }

  return (amd::clCreateFromGLRenderbufferAMD(*as_amd(context), flags, renderbuffer, errcode_ret));
}
RUNTIME_EXIT

/*! @}
 *  \addtogroup clGetGLObjectInfo
 *  @{
 */

/*! \brief Query GL object type from a CL memory object.
 *
 *  \param memobj [is a valid cl_mem object created from a GL object].
 *
 *  \param gl_object_type returns the type of GL object attached to memobj
 *  and can be CL_GL_OBJECT_BUFFER, CL_GL_OBJECT_TEXTURE2D,
 *  CL_GL_OBJECT_TEXTURE_RECTANGLE, CL_GL_OBJECT_TEXTURE3D, or
 *  CL_GL_OBJECT_RENDERBUFFER. If \a gl_object_type is NULL, it is ignored.
 *
 *  \param gl_object_name returns the GL object name used to create memobj.
 *  If \a gl_object_name is NULL, it is ignored.
 *
 *  \return One of the following values is returned:
 *  - CL_SUCCESS if the call was executed successfully.
 *  - CL_INVALID_MEM_OBJECT if \a memobj is not a valid OpenCL memory object.
 *  - CL_INVALID_GL_OBJECT if there is no GL object associated with \a memobj.
 *
 *  \version 1.0r29
 */
RUNTIME_ENTRY(cl_int, clGetGLObjectInfo,
              (cl_mem memobj, cl_gl_object_type* gl_object_type, GLuint* gl_object_name)) {
  if (!is_valid(memobj)) {
    LogWarning("\"memobj\" is not a  valid cl_mem object");
    return CL_INVALID_MEM_OBJECT;
  }

  amd::InteropObject* interop = as_amd(memobj)->getInteropObj();
  if (NULL == interop) {
    LogWarning("CL object \"memobj\" is not created from GL object");
    return CL_INVALID_GL_OBJECT;
  }

  amd::GLObject* glObject = interop->asGLObject();
  if (NULL == glObject) {
    LogWarning("CL object \"memobj\" is not created from GL object");
    return CL_INVALID_GL_OBJECT;
  }

  cl_int result;

  cl_gl_object_type clGLType = glObject->getCLGLObjectType();
  result = amd::clGetInfo(clGLType, sizeof(cl_gl_object_type), gl_object_type, NULL);

  GLuint glName = glObject->getGLName();
  result |= amd::clGetInfo(glName, sizeof(GLuint), gl_object_name, NULL);

  return result;
}
RUNTIME_EXIT

/*! @}
 *  \addtogroup clGetGLTextureInfo
 *  @{
 */

/*! \brief Query additional information about the GL texture object associated
 *  with \a memobj.
 *
 *  \param memobj [is a valid cl_mem object created from a GL object].
 *
 *  \param param_name specifies what additional information about the GL
 *  texture object associated with \a memobj to query:
 *  - CL_GL_TEXTURE_TARGET (GLenum) to query the \a target argument specified
 *    in clCreateGLTexture2D or clCreateGLTexture3D calls.
 *  - CL_GL_MIPMAP_LEVEL (GLint) to query the \a miplevel argument specified
 *    in clCreateGLTexture2D or clCreateGLTexture3D calls.
 *
 *  \param param_value is a pointer to memory where the appropriate result
 *  being queried is returned. If \a param_value is NULL, it is ignored.
 *
 *  \param param_value_size is used to specify the size in bytes of memory
 *  pointed to by \a param_value. This size must be >= size of return type as
 *  described for \a param_name argumnet (GLenum or GLint).
 *  \a param_value_size_ret returns the actual size in bytes of data copied to
 *  \a param_value. If \a param_value_size_ret is NULL, it is ignored
 *
 *  \return One of the following values is returned:
 *  - CL_SUCCESS if the function is executed successfully.
 *  - CL_INVALID_MEM_OBJECT if \a memobj is not a valid OpenCL memory object.
 *  - CL_INVALID_GL_OBJECT if there is no GL texture object (2D or 3D texture)
 *    associated with \a memobj.
 *  - CL_INVALID_VALUE if \a param_name is not valid, or if size in bytes
 *    specified by \a param_value_size is < size of return type required by
 *    \a param_name and \a param_value is not NULL, or if \a param_value and
 *    \a param_value_size_ret are NULL.
 *
 *  \version 1.0r29
 */
RUNTIME_ENTRY(cl_int, clGetGLTextureInfo,
              (cl_mem memobj, cl_gl_texture_info param_name, size_t param_value_size,
               void* param_value, size_t* param_value_size_ret)) {
  if (!is_valid(memobj)) {
    LogWarning("\"memobj\" is not a  valid cl_mem object");
    return CL_INVALID_MEM_OBJECT;
  }
  amd::InteropObject* interop = as_amd(memobj)->getInteropObj();
  if (NULL == interop) {
    LogWarning("CL object \"memobj\" is not created from GL object");
    return CL_INVALID_GL_OBJECT;
  }
  amd::GLObject* glObject = interop->asGLObject();
  if ((NULL == glObject) || (NULL != glObject->asBufferGL())) {
    LogWarning("CL object \"memobj\" is not created from GL texture");
    return CL_INVALID_GL_OBJECT;
  }

  switch (param_name) {
    case CL_GL_TEXTURE_TARGET: {
      GLenum glTarget = glObject->getGLTarget();
      if (glTarget == GL_TEXTURE_CUBE_MAP) {
        glTarget = glObject->getCubemapFace();
      }
      return amd::clGetInfo(glTarget, param_value_size, param_value, param_value_size_ret);
    }
    case CL_GL_MIPMAP_LEVEL: {
      GLint mipLevel = glObject->getGLMipLevel();
      return amd::clGetInfo(mipLevel, param_value_size, param_value, param_value_size_ret);
    }
    case CL_GL_NUM_SAMPLES: {
      GLsizei numSamples = glObject->getNumSamples();
      return amd::clGetInfo(numSamples, param_value_size, param_value, param_value_size_ret);
    }
    default:
      LogWarning("Unknown param_name in clGetGLTextureInfoAMD");
      break;
  }

  return CL_INVALID_VALUE;
}
RUNTIME_EXIT

/*! @}
 *  \addtogroup clEnqueueAcquireExtObjects
 *  @{
 */

/*! \brief Acquire OpenCL memory objects that have been created from external
 *  objects (OpenGL, D3D).
 *
 *  \param command_queue is a valid command-queue.
 *
 *  \param num_objects is the number of memory objects to be acquired
 *  in \a mem_objects.
 *
 *  \param mem_objects is a pointer to a list of CL memory objects that refer
 *  to a GL object (buffer/texture/renderbuffer objects or the framebuffer).
 *
 *  \param event_wait_list specify [is a pointer to] events that need to
 *  complete before this particular command can be executed.
 *  If \a event_wait_list is NULL, then this particular command does not wait
 *  on any event to complete. If \a event_wait_list is NULL,
 *  \a num_events_in_wait_list must be 0. If \a event_wait_list is not NULL,
 *  the list of events pointed to by \a event_wait_list must be valid and
 *  \a num_events_in_wait_list must be greater than 0. The events specified in
 *  \a event_wait_list act as synchronization points.
 *
 *  \param num_events_in_wait_list specify the number of events in
 *  \a event_wait_list. It must be 0 if \a event_wait_list is NULL. It  must be
 *  greater than 0 if \a event_wait_list is not NULL.
 *
 *  \param event returns an event object that identifies this particular
 *  command and can be used to query or queue a wait for this particular
 *  command to complete. \a event can be NULL in which case it will not be
 *  possible for the application to query the status of this command or queue a
 *  wait for this command to complete.
 *
 *  \return One of the following values is returned:
 *  - CL_SUCCESS if the function is executed successfully.
 *  - CL_SUCCESS if \a num_objects is 0 and \a mem_objects is NULL; the
 *    function does nothing.
 *  - CL_INVALID_VALUE if \a num_objects is zero and \a mem_objects is not a
 *    NULL value or if \a num_objects > 0 and \a mem_objects is NULL.
 *  - CL_INVALID_MEM_OBJECT if memory objects in \a mem_objects are not valid
 *    OpenCL memory objects.
 *  - CL_INVALID_COMMAND_QUEUE if \a command_queue is not a valid command-queue.
 *  - CL_INVALID_CONTEXT if clContext associated with \a command_queue was not
 *    created from an OpenGL clContext.
 *  - CL_INVALID_GL_OBJECT if memory objects in \a mem_objects have not been
 *    created from a GL object(s).
 *  - CL_INVALID_EVENT_WAIT_LIST if \a event_wait_list is NULL and
 *    \a num_events_in_wait_list > 0, or \a event_wait_list is not NULL and
 *    \a num_events_in_wait_list is 0, or if event objects in \a event_wait_list
 *    are not valid events.
 *  - CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources
 *    required by the OpenCL implementation on the host.
 *
 *  \version 1.0r29
 */
RUNTIME_ENTRY(cl_int, clEnqueueAcquireGLObjects,
              (cl_command_queue command_queue, cl_uint num_objects, const cl_mem* mem_objects,
               cl_uint num_events_in_wait_list, const cl_event* event_wait_list, cl_event* event)) {
  return amd::clEnqueueAcquireExtObjectsAMD(command_queue, num_objects, mem_objects,
                                            num_events_in_wait_list, event_wait_list, event,
                                            CL_COMMAND_ACQUIRE_GL_OBJECTS);
}
RUNTIME_EXIT

/*! @}
 *  \addtogroup clEnqueueReleaseGLObjects
 *  @{
 */

/*! \brief Release OpenCL memory objects that have been created from OpenGL
 *  objects.
 *
 *  \param command_queue is a valid command-queue [which is associated with the
 *  OpenCL clContext releasing the OpenGL objects].
 *
 *  \param num_objects is the number of memory objects to be released
 *  in \a mem_objects.
 *
 *  \param mem_objects is a pointer to a list of CL memory objects that refer
 *  to a GL object (buffer/texture/renderbuffer objects or the framebuffer).
 *
 *  \param event_wait_list specify [is a pointer to] events that need to
 *  complete before this particular command can be executed.
 *  If \a event_wait_list is NULL, then this particular command does not wait
 *  on any event to complete. If \a event_wait_list is NULL,
 *  \a num_events_in_wait_list must be 0. If \a event_wait_list is not NULL,
 *  the list of events pointed to by \a event_wait_list must be valid and
 *  \a num_events_in_wait_list must be greater than 0. The events specified in
 *  \a event_wait_list act as synchronization points.
 *
 *  \param num_events_in_wait_list specify the number of events in
 *  \a event_wait_list. It must be 0 if \a event_wait_list is NULL. It  must be
 *  greater than 0 if \a event_wait_list is not NULL.
 *
 *  \param event returns an event object that identifies this particular
 *  command and can be used to query or queue a wait for this particular
 *  command to complete. \a event can be NULL in which case it will not be
 *  possible for the application to query the status of this command or queue a
 *  wait for this command to complete.
 *
 *  \return One of the following values is returned:
 *  - CL_SUCCESS if the function is executed successfully.
 *  - CL_SUCCESS if \a num_objects is 0 and \a mem_objects is NULL; the
 *    function does nothing.
 *  - CL_INVALID_VALUE if \a num_objects is zero and \a mem_objects is not a
 *    NULL value or if \a num_objects > 0 and \a mem_objects is NULL.
 *  - CL_INVALID_MEM_OBJECT if memory objects in \a mem_objects are not valid
 *    OpenCL memory objects.
 *  - CL_INVALID_COMMAND_QUEUE if \a command_queue is not a valid command-queue.
 *  - CL_INVALID_CONTEXT if clContext associated with \a command_queue was not
 *    created from an OpenGL clContext.
 *  - CL_INVALID_GL_OBJECT if memory objects in \a mem_objects have not been
 *    created from a GL object(s).
 *  - CL_INVALID_EVENT_WAIT_LIST if \a event_wait_list is NULL and
 *    \a num_events_in_wait_list > 0, or \a event_wait_list is not NULL and
 *    \a num_events_in_wait_list is 0, or if event objects in \a event_wait_list
 *    are not valid events.
 *  - CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources
 *    required by the OpenCL implementation on the host.
 *
 *  \version 1.0r29
 */
RUNTIME_ENTRY(cl_int, clEnqueueReleaseGLObjects,
              (cl_command_queue command_queue, cl_uint num_objects, const cl_mem* mem_objects,
               cl_uint num_events_in_wait_list, const cl_event* event_wait_list, cl_event* event)) {
  return amd::clEnqueueReleaseExtObjectsAMD(command_queue, num_objects, mem_objects,
                                            num_events_in_wait_list, event_wait_list, event,
                                            CL_COMMAND_RELEASE_GL_OBJECTS);
}
RUNTIME_EXIT

/*! @}
*  \addtogroup clCreateEventFromGLsyncKHR
*  @{
*/

/*! \brief Creates an event object linked to an OpenGL sync object.
*  Completion of such an event object is equivalent to waiting for completion
*  of the fence command associated with the linked GL sync object.
*
*  \param context is valid OpenCL context created from an OpenGL context
*  or share group, using the cl_khr_gl_sharing extension.
*
*  \param sync is the 'name' of a sync object in the GL share group associated
*  with context.
*
*  \param errcode_ret Returns an appropriate error code as described below.
*  If errcode_ret is NULL, no error code is returned.
*
*  \return a valid OpenCL event object and errcode_ret is set to CL_SUCCESS
*  if the event object is created successfully.Otherwise, it returns a NULL
*  value with one of the following error values returned in errcode_ret:
*  - CL_INVALID_CONTEXT if context is not a valid context or was not created
*    from a GL context.
*  - CL_INVALID_GL_OBJECT if sync is not the name of a sync object in the
*    GL share group associated with context.
*
*  \version 1.1
*/

RUNTIME_ENTRY_RET(cl_event, clCreateEventFromGLsyncKHR,
                  (cl_context context, cl_GLsync clGLsync, cl_int* errcode_ret)) {
  if (!is_valid(context)) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("invalid parameter \"context\"");
    return nullptr;
  }
  // create event of fence sync type
  amd::ClGlEvent* clglEvent = new amd::ClGlEvent(*as_amd(context));
  if (clglEvent == nullptr) {
    *not_null(errcode_ret) = CL_OUT_OF_HOST_MEMORY;
    LogWarning("Memory allocation of clglEvent object failed");
    return nullptr;
  }

  // Add this scope to bound the scoped lock
  {
    amd::GLFunctions::Lock lock(clglEvent->context().glenv());
    clglEvent->context().glenv()->glFlush_();
  }  // Release scoped lock

  // initially set the status of fence as queued
  clglEvent->setStatus(CL_SUBMITTED);
  // store GLsync id of the fence in event in order to associate them together
  clglEvent->data().emplace_back(clGLsync);
  amd::Event* evt = clglEvent;
  evt->retain();
  *not_null(errcode_ret) = CL_SUCCESS;
  return as_cl(evt);
}
RUNTIME_EXIT

/*! @}
 *  \addtogroup clGetGLContextInfoKHR
 *  @{
 */

/*! \brief This f-n is defined in CL extension cl_khr_gl_sharing and serves
 *  the purpose of quering current device and all devices that support
 *  CL-GL interoperability.
 *
 *  \param properties points to an <attribute list>, which is a array of
 *  ordered <attribute name, value> pairs terminated with zero. If an
 *  attribute is not specified in <properties>, then its default value
 *  (listed in table 4.attr) is used (it is said to be specified
 *  implicitly). If <properties> is NULL or empty (points to a list
 *  whose first value is zero), all attributes take on their default
 *  values.
 *
 *  \param param_name may accept one of the following enumerated values:
 *  - CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR  0x2006
 *  - CL_DEVICES_FOR_GL_CONTEXT_KHR         0x2007.
 *
 *  \param param_value_size is used to specify the size in bytes of memory
 *  pointed to by \a param_value. This size must be >= size of return type as
 *  described for \a param_name argumnet (GLenum or GLint).
 *  \a param_value_size_ret returns the actual size in bytes of data copied to
 *  \a param_value. If \a param_value_size_ret is NULL, it is ignored
 *
 *  \param param_value is a pointer to memory where the appropriate result
 *  being queried is returned. If \a param_value is NULL, it is ignored.
 *
 *  \param param_value_size is used to specify the size in bytes of memory
 *  pointed to by \a param_value. This size must be >= size of return type as
 *  described for \a param_name argumnet (GLenum or GLint).
 *  \a param_value_size_ret returns the actual size in bytes of data copied to
 *  \a param_value. If \a param_value_size_ret is NULL, it is ignored
 *
 *  \return one of the following values is returned:
 *  - CL_SUCCESS if the function is executed successfully.
 *  - CL_SUCCESS if \a num_objects is 0 and \a mem_objects is NULL; the
 *    function does nothing.
 *  - CL_INVALID_VALUE if \a num_objects is zero and \a mem_objects is not a
 *    NULL value or if \a num_objects > 0 and \a mem_objects is NULL.
 *  - CL_INVALID_MEM_OBJECT if memory objects in \a mem_objects are not valid
 *    OpenCL memory objects.
 *  - CL_INVALID_COMMAND_QUEUE if \a command_queue is not a valid command-queue.
 *  - CL_INVALID_CONTEXT if clContext associated with \a command_queue was not
 *    created from an OpenGL clContext.
 *  - CL_INVALID_GL_OBJECT if memory objects in \a mem_objects have not been
 *    created from a GL object(s).
 *  - CL_INVALID_EVENT_WAIT_LIST if \a event_wait_list is NULL and
 *    \a num_events_in_wait_list > 0, or \a event_wait_list is not NULL and
 *    \a num_events_in_wait_list is 0, or if event objects in \a event_wait_list
 *    are not valid events.
 *  - CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources
 *    required by the OpenCL implementation on the host.
 *  - CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR if
 *
 *  \version 1.0r47
 */
RUNTIME_ENTRY(cl_int, clGetGLContextInfoKHR,
              (const cl_context_properties* properties, cl_gl_context_info param_name,
               size_t param_value_size, void* param_value, size_t* param_value_size_ret)) {
  cl_int errcode;
  cl_device_id* gpu_devices;
  cl_uint num_gpu_devices = 0;
  amd::Context::Info info;
  static const bool VALIDATE_ONLY = true;

  errcode = amd::Context::checkProperties(properties, &info);
  if (CL_SUCCESS != errcode) {
    return errcode;
  }

  if (!(info.flags_ & amd::Context::GLDeviceKhr)) {
    // No GL context is specified
    return CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR;
  }

  // Get devices
  errcode = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_GPU, 0, NULL, &num_gpu_devices);
  if (errcode != CL_SUCCESS && errcode != CL_DEVICE_NOT_FOUND) {
    return CL_INVALID_VALUE;
  }

  if (!num_gpu_devices) {
    return CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR;
  }

  switch (param_name) {
    case CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR:
      // Return the CL device currently associated with the specified OpenGL context.
      if (num_gpu_devices) {
        gpu_devices = (cl_device_id*)alloca(num_gpu_devices * sizeof(cl_device_id));

        errcode = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_GPU, num_gpu_devices, gpu_devices, NULL);
        if (errcode != CL_SUCCESS) {
          return errcode;
        }

        for (cl_uint i = 0; i < num_gpu_devices; ++i) {
          cl_device_id device = gpu_devices[i];
          if (is_valid(device) &&
              as_amd(device)->bindExternalDevice(info.flags_, info.hDev_, info.hCtx_,
                                                 VALIDATE_ONLY)) {
            return amd::clGetInfo(device, param_value_size, param_value, param_value_size_ret);
          }
        }

        *not_null(param_value_size_ret) = 0;
      }
      break;

    case CL_DEVICES_FOR_GL_CONTEXT_KHR: {
      // List of all CL devices that can be associated with the specified OpenGL context.
      cl_uint total_devices = num_gpu_devices;
      size_t size = total_devices * sizeof(cl_device_id);

      cl_device_id* devices = (cl_device_id*)alloca(size);

      errcode = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_GPU, total_devices,
                               devices, NULL);
      if (errcode != CL_SUCCESS) {
        return errcode;
      }

      std::vector<amd::Device*> compatible_devices;

      for (cl_uint i = 0; i < total_devices; ++i) {
        cl_device_id device = devices[i];
        if (is_valid(device) &&
            as_amd(device)->bindExternalDevice(info.flags_, info.hDev_, info.hCtx_,
                                               VALIDATE_ONLY)) {
          compatible_devices.push_back(as_amd(device));
        }
      }

      size_t deviceCount = compatible_devices.size();
      size_t deviceCountSize = deviceCount * sizeof(cl_device_id);

      if (param_value != NULL && param_value_size < deviceCountSize) {
        return CL_INVALID_VALUE;
      }

      *not_null(param_value_size_ret) = deviceCountSize;

      if (param_value != NULL) {
        cl_device_id* deviceList = (cl_device_id*)param_value;
        for (const auto& it : compatible_devices) {
          *deviceList++ = as_cl(it);
        }
      }

      return CL_SUCCESS;
    } break;

    default:
      LogWarning("\"param_name\" is not valid");
      return CL_INVALID_VALUE;
  }
  return CL_SUCCESS;
}
RUNTIME_EXIT

//
//
//          namespace amd
//
//
namespace amd {

typedef struct {
  GLenum glBinding;
  GLenum glTarget;
} TargetBindings_t;

/*! @}
 *  \addtogroup CL-GL interop helper functions
 *  @{
 */

//! Function clearGLErrors() to clear all GL error bits, if any
void clearGLErrors(const Context& amdContext) {
  GLenum glErr, glLastErr = GL_NO_ERROR;
  while (1) {
    glErr = amdContext.glenv()->glGetError_();
    if (glErr == GL_NO_ERROR || glErr == glLastErr) {
      break;
    }
    glLastErr = glErr;
    LogWarning("GL error");
  }
}

GLenum checkForGLError(const Context& amdContext) {
  GLenum glRetErr = GL_NO_ERROR;
  GLenum glErr;
  while (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
    glRetErr = glErr;  // Just return the last GL error
    LogWarning("Check GL error");
  }
  return glRetErr;
}

static GLenum clChannelDataTypeToGlType(cl_channel_type channel_type) {
  // Pick
  // GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT,
  // GL_UNSIGNED_INT, GL_FLOAT, GL_2_BYTES, GL_3_BYTES, GL_4_BYTES
  // or GL_DOUBLE
  switch (channel_type) {
    case CL_SNORM_INT8:
      return GL_BYTE;
    case CL_SNORM_INT16:
      return GL_SHORT;
    case CL_UNORM_INT8:
      return GL_UNSIGNED_BYTE;
    case CL_UNORM_INT16:
      return GL_UNSIGNED_SHORT;
    case CL_SIGNED_INT8:
      return GL_BYTE;
    case CL_SIGNED_INT16:
      return GL_SHORT;
    case CL_SIGNED_INT32:
      return GL_INT;
    case CL_UNSIGNED_INT8:
      return GL_UNSIGNED_BYTE;
    case CL_UNSIGNED_INT16:
      return GL_UNSIGNED_SHORT;
    case CL_UNSIGNED_INT32:
      return GL_UNSIGNED_INT;
    case CL_FLOAT:
      return GL_FLOAT;
    case CL_UNORM_INT_101010:
      return GL_UNSIGNED_INT_10_10_10_2;
    case CL_HALF_FLOAT:
    case CL_UNORM_SHORT_565:
    case CL_UNORM_SHORT_555:
    default:
      guarantee(false, "Unexpected CL type.");
      return 0;
  }
}

static GLenum glInternalFormatToGlFormat(GLenum internalFormat) {
  switch (internalFormat) {
    // Base internal formats
    case GL_RGBA:
    case GL_BGRA:
      return internalFormat;
    // Sized internal formats
    case GL_RGBA8:
    case GL_RGBA16:
    case GL_RGBA16F:
    case GL_RGBA32F:
      return GL_RGBA;
    case GL_RGBA8I:
    case GL_RGBA8UI:
    case GL_RGBA16I:
    case GL_RGBA16UI:
    case GL_RGBA32I:
    case GL_RGBA32UI:
      return GL_RGBA_INTEGER;

    default:
      guarantee(false, "Unexpected GL internal format.");
      return 0;
  }
}

//*******************************************************************
//
// Internal implementation of CL API functions
//
//*******************************************************************

//
//      clCreateFromGLBufferAMD
//
cl_mem clCreateFromGLBufferAMD(Context& amdContext, cl_mem_flags flags, GLuint bufobj,
                               cl_int* errcode_ret) {
  BufferGL* pBufferGL = NULL;
  GLenum glErr;
  GLenum glTarget = GL_ARRAY_BUFFER;
  GLint gliSize = 0;
  GLint gliMapped = 0;

  // Verify context init'ed for interop
  if (!amdContext.glenv() || !amdContext.glenv()->isAssociated()) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("\"amdContext\" is not created from GL context or share list");
    return (cl_mem)0;
  }

  // Add this scope to bound the scoped lock
  {
    GLFunctions::SetIntEnv ie(amdContext.glenv());
    if (!ie.isValid()) {
      *not_null(errcode_ret) = CL_INVALID_CONTEXT;
      LogWarning("\"amdContext\" is not created from GL context or share list");
      return as_cl<Memory>(0);
    }

    // Verify GL buffer object
    clearGLErrors(amdContext);
    if ((GL_FALSE == amdContext.glenv()->glIsBuffer_(bufobj)) ||
        (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_()))) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("\"bufobj\" is not a GL buffer object");
      return (cl_mem)0;
    }

    // It seems that CL spec is not concerned with GL_BUFFER_USAGE, so skip it

    // Check if size is available - data store is created

    amdContext.glenv()->glBindBuffer_(glTarget, bufobj);
    clearGLErrors(amdContext);
    amdContext.glenv()->glGetBufferParameteriv_(glTarget, GL_BUFFER_SIZE, &gliSize);
    if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("cannot get the GL buffer size");
      return (cl_mem)0;
    }
    if (gliSize == 0) {
      //@todo - check why sometime the size is zero
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("the GL buffer's data store is not created");
      return (cl_mem)0;
    }

    // Mapping will be done at acquire time (sync point)

    // Now create BufferGL object
    pBufferGL = new (amdContext) BufferGL(amdContext, flags, gliSize, 0, bufobj);

    if (!pBufferGL) {
      *not_null(errcode_ret) = CL_OUT_OF_HOST_MEMORY;
      LogWarning("cannot create object of class BufferGL");
      return (cl_mem)0;
    }

    if (!pBufferGL->create()) {
      *not_null(errcode_ret) = CL_MEM_OBJECT_ALLOCATION_FAILURE;
      pBufferGL->release();
      return (cl_mem)0;
    }
  }  // Release scoped lock

  *not_null(errcode_ret) = CL_SUCCESS;

  // Create interop object
  if (pBufferGL->getInteropObj() == NULL) {
    *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
    LogWarning("cannot create object of class BufferGL");
    return (cl_mem)0;
  }

  // Fixme: If more than one device is present in the context, we choose the first device.
  // We should come up with a more elegant solution to handle this.
  assert(amdContext.devices().size() == 1);

  const auto it = amdContext.devices().cbegin();
  const amd::Device& dev = *(*it);

  device::Memory* mem = pBufferGL->getDeviceMemory(dev);
  if (NULL == mem) {
    LogPrintfError("Can't allocate memory size - 0x%08X bytes!", pBufferGL->getSize());
    *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
    return (cl_mem)0;
  }
  mem->processGLResource(device::Memory::GLDecompressResource);

  return as_cl<Memory>(pBufferGL);
}

cl_mem clCreateFromGLTextureAMD(Context& amdContext, cl_mem_flags clFlags, GLenum target,
                                GLint miplevel, GLuint texture, int* errcode_ret) {
  ImageGL* pImageGL = NULL;
  GLenum glErr;
  GLenum glTarget = 0;
  GLenum glInternalFormat;
  cl_image_format clImageFormat;
  uint dim = 1;
  cl_mem_object_type clType;
  cl_gl_object_type clGLType;
  GLsizei numSamples = 1;
  GLint gliTexMaxLevel;
  bool wholeMipmap = false;

  // Verify context init'ed for interop
  if (!amdContext.glenv() || !amdContext.glenv()->isAssociated()) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("\"amdContext\" is not created from GL context or share list");
    return static_cast<cl_mem>(0);
  }

  GLint gliTexWidth = 1;
  GLint gliTexHeight = 1;
  GLint gliTexDepth = 1;

  // Add this scope to bound the scoped lock
  {
    GLFunctions::SetIntEnv ie(amdContext.glenv());
    if (!ie.isValid()) {
      *not_null(errcode_ret) = CL_INVALID_CONTEXT;
      LogWarning("\"amdContext\" is not created from GL context or share list");
      return as_cl<Memory>(0);
    }

    // Verify GL texture object
    clearGLErrors(amdContext);
    if ((GL_FALSE == amdContext.glenv()->glIsTexture_(texture)) ||
        (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_()))) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("\"texture\" is not a GL texture object");
      return static_cast<cl_mem>(0);
    }

    bool image = true;

    // Check target value validity
    switch (target) {
      case GL_TEXTURE_BUFFER:
        glTarget = GL_TEXTURE_BUFFER;
        dim = 1;
        clType = CL_MEM_OBJECT_IMAGE1D_BUFFER;
        clGLType = CL_GL_OBJECT_TEXTURE_BUFFER;
        image = false;
        break;

      case GL_TEXTURE_1D:
        glTarget = GL_TEXTURE_1D;
        dim = 1;
        clType = CL_MEM_OBJECT_IMAGE1D;
        clGLType = CL_GL_OBJECT_TEXTURE1D;
        break;

      case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
      case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
      case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
      case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
      case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
      case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
        glTarget = GL_TEXTURE_CUBE_MAP;
        dim = 2;
        clType = CL_MEM_OBJECT_IMAGE2D;
        clGLType = CL_GL_OBJECT_TEXTURE2D;
        break;

      case GL_TEXTURE_1D_ARRAY:
        glTarget = GL_TEXTURE_1D_ARRAY;
        dim = 2;
        clType = CL_MEM_OBJECT_IMAGE1D_ARRAY;
        clGLType = CL_GL_OBJECT_TEXTURE1D_ARRAY;
        break;

      case GL_TEXTURE_2D:
        glTarget = GL_TEXTURE_2D;
        dim = 2;
        clType = CL_MEM_OBJECT_IMAGE2D;
        clGLType = CL_GL_OBJECT_TEXTURE2D;
        break;

      case GL_TEXTURE_2D_MULTISAMPLE:
        glTarget = GL_TEXTURE_2D_MULTISAMPLE;
        dim = 2;
        clType = CL_MEM_OBJECT_IMAGE2D;
        clGLType = CL_GL_OBJECT_TEXTURE2D;
        break;

      case GL_TEXTURE_RECTANGLE_ARB:
        glTarget = GL_TEXTURE_RECTANGLE_ARB;
        dim = 2;
        clType = CL_MEM_OBJECT_IMAGE2D;
        clGLType = CL_GL_OBJECT_TEXTURE2D;
        break;

      case GL_TEXTURE_2D_ARRAY:
        glTarget = GL_TEXTURE_2D_ARRAY;
        dim = 3;
        clType = CL_MEM_OBJECT_IMAGE2D_ARRAY;
        clGLType = CL_GL_OBJECT_TEXTURE2D_ARRAY;
        break;

      case GL_TEXTURE_3D:
        glTarget = GL_TEXTURE_3D;
        dim = 3;
        clType = CL_MEM_OBJECT_IMAGE3D;
        clGLType = CL_GL_OBJECT_TEXTURE3D;
        break;

      default:
        // wrong value
        *not_null(errcode_ret) = CL_INVALID_VALUE;
        LogWarning("invalid \"target\" value");
        return static_cast<cl_mem>(0);
        break;
    }

    amdContext.glenv()->glBindTexture_(glTarget, texture);

    // Check if size is available - data store is created
    if (image) {
      // Check mipmap level for "texture" name
      GLint gliTexBaseLevel;

      clearGLErrors(amdContext);
      amdContext.glenv()->glGetTexParameteriv_(glTarget, GL_TEXTURE_BASE_LEVEL, &gliTexBaseLevel);
      if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
        *not_null(errcode_ret) = CL_INVALID_MIP_LEVEL;
        LogWarning("Cannot get base mipmap level of a GL \"texture\" object");
        return static_cast<cl_mem>(0);
      }
      clearGLErrors(amdContext);
      amdContext.glenv()->glGetTexParameteriv_(glTarget, GL_TEXTURE_MAX_LEVEL, &gliTexMaxLevel);
      if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
        *not_null(errcode_ret) = CL_INVALID_MIP_LEVEL;
        LogWarning("Cannot get max mipmap level of a GL \"texture\" object");
        return static_cast<cl_mem>(0);
      }
      wholeMipmap = miplevel < 0;
      miplevel = wholeMipmap ? gliTexBaseLevel : miplevel;
      if ((gliTexBaseLevel > miplevel) || (miplevel > gliTexMaxLevel)) {
        *not_null(errcode_ret) = CL_INVALID_MIP_LEVEL;
        LogWarning("\"miplevel\" is not a valid mipmap level of the GL \"texture\" object");
        return static_cast<cl_mem>(0);
      }

      // Get GL texture format and check if it's compatible with CL format
      clearGLErrors(amdContext);
      amdContext.glenv()->glGetTexLevelParameteriv_(target, miplevel, GL_TEXTURE_INTERNAL_FORMAT,
                                                    (GLint*)&glInternalFormat);
      if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
        *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
        LogWarning("Cannot get internal format of \"miplevel\" of GL \"texture\" object");
        return static_cast<cl_mem>(0);
      }

      amdContext.glenv()->glGetTexLevelParameteriv_(target, miplevel, GL_TEXTURE_SAMPLES,
                                                    (GLint*)&numSamples);
      if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
        *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
        LogWarning("Cannot get  numbers of samples of GL \"texture\" object");
        return static_cast<cl_mem>(0);
      }
      if (numSamples > 1) {
        *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
        LogWarning("MSAA \"texture\" object is not suppoerted for the device");
        return static_cast<cl_mem>(0);
      }

      // Now get CL format from GL format and bytes per pixel
      int iBytesPerPixel = 0;
      if (!getCLFormatFromGL(amdContext, glInternalFormat, &clImageFormat, &iBytesPerPixel,
                             clFlags)) {
        *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
        LogWarning("\"texture\" format does not map to an appropriate CL image format");
        return static_cast<cl_mem>(0);
      }

      switch (dim) {
        case 3:
          clearGLErrors(amdContext);
          amdContext.glenv()->glGetTexLevelParameteriv_(target, miplevel, GL_TEXTURE_DEPTH,
                                                        &gliTexDepth);
          if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
            *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
            LogWarning("Cannot get the depth of \"miplevel\" of GL \"texure\"");
            return static_cast<cl_mem>(0);
          }
        // Fall trough to process other dimensions...
        case 2:
          clearGLErrors(amdContext);
          amdContext.glenv()->glGetTexLevelParameteriv_(target, miplevel, GL_TEXTURE_HEIGHT,
                                                        &gliTexHeight);
          if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
            *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
            LogWarning("Cannot get the height of \"miplevel\" of GL \"texure\"");
            return static_cast<cl_mem>(0);
          }
        // Fall trough to process other dimensions...
        case 1:
          clearGLErrors(amdContext);
          amdContext.glenv()->glGetTexLevelParameteriv_(target, miplevel, GL_TEXTURE_WIDTH,
                                                        &gliTexWidth);
          if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
            *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
            LogWarning("Cannot get the width of \"miplevel\" of GL \"texure\"");
            return static_cast<cl_mem>(0);
          }
          break;
        default:
          *not_null(errcode_ret) = CL_INVALID_VALUE;
          LogWarning("invalid \"target\" value");
          return static_cast<cl_mem>(0);
      }
    } else {
      GLint size;

      // In case target is GL_TEXTURE_BUFFER
      GLint backingBuffer;
      clearGLErrors(amdContext);
      amdContext.glenv()->glGetTexLevelParameteriv_(
          glTarget, 0, GL_TEXTURE_BUFFER_DATA_STORE_BINDING, &backingBuffer);
      if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
        *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
        LogWarning("Cannot get backing buffer for GL \"texture buffer\" object");
        return static_cast<cl_mem>(0);
      }
      amdContext.glenv()->glBindBuffer_(glTarget, backingBuffer);

      // Get GL texture format and check if it's compatible with CL format
      clearGLErrors(amdContext);
      amdContext.glenv()->glGetIntegerv_(GL_TEXTURE_BUFFER_FORMAT_EXT,
                                         reinterpret_cast<GLint*>(&glInternalFormat));
      if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
        *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
        LogWarning("Cannot get internal format of \"miplevel\" of GL \"texture\" object");
        return static_cast<cl_mem>(0);
      }

      // Now get CL format from GL format and bytes per pixel
      int iBytesPerPixel = 0;
      if (!getCLFormatFromGL(amdContext, glInternalFormat, &clImageFormat, &iBytesPerPixel,
                             clFlags)) {
        *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
        LogWarning("\"texture\" format does not map to an appropriate CL image format");
        return static_cast<cl_mem>(0);
      }

      clearGLErrors(amdContext);
      amdContext.glenv()->glGetBufferParameteriv_(glTarget, GL_BUFFER_SIZE, &size);
      if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
        *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
        LogWarning("Cannot get internal format of \"miplevel\" of GL \"texture\" object");
        return static_cast<cl_mem>(0);
      }

      gliTexWidth = size / iBytesPerPixel;
    }
    size_t imageSize = (clType == CL_MEM_OBJECT_IMAGE1D_ARRAY) ? static_cast<size_t>(gliTexHeight)
                                                               : static_cast<size_t>(gliTexDepth);

    if (!amd::Image::validateDimensions(
            amdContext.devices(), clType, static_cast<size_t>(gliTexWidth),
            static_cast<size_t>(gliTexHeight), static_cast<size_t>(gliTexDepth), imageSize)) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("The GL \"texture\" data store is not created or out of supported dimensions");
      return static_cast<cl_mem>(0);
    }

    // PBO and mapping will be done at "acquire" time (sync point)

    target = (glTarget == GL_TEXTURE_CUBE_MAP) ? target : 0;

    if (wholeMipmap) {
      pImageGL = new (amdContext)
          ImageGL(amdContext, clType, clFlags, clImageFormat, static_cast<size_t>(gliTexWidth),
                static_cast<size_t>(gliTexHeight), static_cast<size_t>(gliTexDepth), glTarget,
                texture, miplevel, glInternalFormat, clGLType, numSamples, gliTexMaxLevel, target);
    } else {
      pImageGL = new (amdContext)
          ImageGL(amdContext, clType, clFlags, clImageFormat, static_cast<size_t>(gliTexWidth),
                static_cast<size_t>(gliTexHeight), static_cast<size_t>(gliTexDepth), glTarget,
                texture, miplevel, glInternalFormat, clGLType, numSamples, target);
    }

    if (!pImageGL) {
      *not_null(errcode_ret) = CL_OUT_OF_HOST_MEMORY;
      LogWarning("Cannot create class ImageGL - out of memory?");
      return static_cast<cl_mem>(0);
    }

    if (!pImageGL->create()) {
      *not_null(errcode_ret) = CL_MEM_OBJECT_ALLOCATION_FAILURE;
      pImageGL->release();
      return static_cast<cl_mem>(0);
    }

  }  // Release scoped lock

  *not_null(errcode_ret) = CL_SUCCESS;
  return as_cl<Memory>(pImageGL);
}

//
//      clCreateFromGLRenderbufferDAMD
//
cl_mem clCreateFromGLRenderbufferAMD(Context& amdContext, cl_mem_flags clFlags, GLuint renderbuffer,
                                     int* errcode_ret) {
  ImageGL* pImageGL = NULL;
  GLenum glErr;

  GLenum glTarget = GL_RENDERBUFFER;
  GLenum glInternalFormat;
  cl_image_format clImageFormat;

  // Verify context init'ed for interop
  if (!amdContext.glenv() || !amdContext.glenv()->isAssociated()) {
    *not_null(errcode_ret) = CL_INVALID_CONTEXT;
    LogWarning("\"amdContext\" is not created from GL context or share list");
    return (cl_mem)0;
  }

  GLint gliRbWidth;
  GLint gliRbHeight;

  // Add this scope to bound the scoped lock
  {
    GLFunctions::SetIntEnv ie(amdContext.glenv());
    if (!ie.isValid()) {
      *not_null(errcode_ret) = CL_INVALID_CONTEXT;
      LogWarning("\"amdContext\" is not created from GL context or share list");
      return as_cl<Memory>(0);
    }

    // Verify GL renderbuffer object
    clearGLErrors(amdContext);
    if ((GL_FALSE == amdContext.glenv()->glIsRenderbufferEXT_(renderbuffer)) ||
        (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_()))) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("\"renderbuffer\" is not a GL texture object");
      return (cl_mem)0;
    }

    amdContext.glenv()->glBindRenderbuffer_(glTarget, renderbuffer);

    // Get GL RB format and check if it's compatible with CL format
    clearGLErrors(amdContext);
    amdContext.glenv()->glGetRenderbufferParameterivEXT_(glTarget, GL_RENDERBUFFER_INTERNAL_FORMAT,
                                                         (GLint*)&glInternalFormat);
    if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
      *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
      LogWarning("Cannot get internal format of GL \"renderbuffer\" object");
      return (cl_mem)0;
    }

    // Now get CL format from GL format and bytes per pixel
    int iBytesPerPixel = 0;
    if (!getCLFormatFromGL(amdContext, glInternalFormat, &clImageFormat, &iBytesPerPixel,
                           clFlags)) {
      *not_null(errcode_ret) = CL_INVALID_IMAGE_FORMAT_DESCRIPTOR;
      LogWarning("\"renderbuffer\" format does not map to an appropriate CL image format");
      return (cl_mem)0;
    }

    // Check if size is available - data store is created
    clearGLErrors(amdContext);
    amdContext.glenv()->glGetRenderbufferParameterivEXT_(glTarget, GL_RENDERBUFFER_WIDTH,
                                                         &gliRbWidth);
    if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("Cannot get the width of GL \"renderbuffer\"");
      return (cl_mem)0;
    }
    if (gliRbWidth == 0) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("The GL \"renderbuffer\" data store is not created");
      return (cl_mem)0;
    }
    clearGLErrors(amdContext);
    amdContext.glenv()->glGetRenderbufferParameterivEXT_(glTarget, GL_RENDERBUFFER_HEIGHT,
                                                         &gliRbHeight);
    if (GL_NO_ERROR != (glErr = amdContext.glenv()->glGetError_())) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("Cannot get the height of GL \"renderbuffer\"");
      return (cl_mem)0;
    }
    if (gliRbHeight == 0) {
      *not_null(errcode_ret) = CL_INVALID_GL_OBJECT;
      LogWarning("The GL \"renderbuffer\" data store is not created");
      return (cl_mem)0;
    }

    // PBO and mapping will be done at "acquire" time (sync point)

    pImageGL =
        new (amdContext) ImageGL(amdContext, CL_MEM_OBJECT_IMAGE2D, clFlags, clImageFormat,
                                 (size_t)gliRbWidth, (size_t)gliRbHeight, 1, glTarget, renderbuffer,
                                 0, glInternalFormat, CL_GL_OBJECT_RENDERBUFFER, 0);

    if (!pImageGL) {
      *not_null(errcode_ret) = CL_OUT_OF_HOST_MEMORY;
      LogWarning("Cannot create class ImageGL from renderbuffer - out of memory?");
      return (cl_mem)0;
    }

    if (!pImageGL->create()) {
      *not_null(errcode_ret) = CL_MEM_OBJECT_ALLOCATION_FAILURE;
      pImageGL->release();
      return (cl_mem)0;
    }
  }  // Release scoped lock

  *not_null(errcode_ret) = CL_SUCCESS;
  return as_cl<Memory>(pImageGL);
}

//
//      clEnqueueAcquireExtObjectsAMD
//

static cl_int clSetInteropObjects(cl_uint num_objects, const cl_mem* mem_objects,
                                  std::vector<amd::Memory*>& interopObjects) {
  if ((num_objects == 0 && mem_objects != NULL) || (num_objects != 0 && mem_objects == NULL)) {
    return CL_INVALID_VALUE;
  }

  while (num_objects-- > 0) {
    cl_mem obj = *mem_objects++;
    if (!is_valid(obj)) {
      return CL_INVALID_MEM_OBJECT;
    }

    amd::Memory* mem = as_amd(obj);
    if (mem->getInteropObj() == NULL) {
      return CL_INVALID_GL_OBJECT;
    }

    interopObjects.push_back(mem);
  }
  return CL_SUCCESS;
}

cl_int clEnqueueAcquireExtObjectsAMD(cl_command_queue command_queue, cl_uint num_objects,
                                     const cl_mem* mem_objects, cl_uint num_events_in_wait_list,
                                     const cl_event* event_wait_list, cl_event* event,
                                     cl_command_type cmd_type) {
  if (!is_valid(command_queue)) {
    return CL_INVALID_COMMAND_QUEUE;
  }

  amd::HostQueue* queue = as_amd(command_queue)->asHostQueue();
  if (NULL == queue) {
    return CL_INVALID_COMMAND_QUEUE;
  }
  amd::HostQueue& hostQueue = *queue;

  if (cmd_type == CL_COMMAND_ACQUIRE_GL_OBJECTS) {
    GLFunctions* gl_functions = hostQueue.context().glenv();
    // Verify context init'ed for interop
    if (!gl_functions || !gl_functions->isAssociated()) {
      LogWarning("\"amdContext\" is not created from GL context or share list");
      return CL_INVALID_CONTEXT;
    }
    // If the cl_khr_gl_event extension is supported, then the OpenCL implementation will ensure
    // that any such pending OpenGL operations are complete for an OpenGL context bound
    // to the same thread as the OpenCL context.
    if (hostQueue.device().settings().checkExtension(ClKhrGlEvent)) {
      GLFunctions::Lock lock(gl_functions);
      gl_functions->WaitCurrentGlContext(hostQueue.context().info());
    }
  }

  std::vector<amd::Memory*> memObjects;
  cl_int err = clSetInteropObjects(num_objects, mem_objects, memObjects);
  if (err != CL_SUCCESS) {
    return err;
  }

  amd::Command::EventWaitList eventWaitList;
  err = amd::clSetEventWaitList(eventWaitList, hostQueue, num_events_in_wait_list,
                                event_wait_list);
  if (err != CL_SUCCESS) {
    return err;
  }

#ifdef _WIN32
  if ((hostQueue.context().info().flags_ & amd::Context::InteropUserSync) == 0) {
    //! Make sure D3D10 queues are flushed and all commands are finished
    //! before CL side would access interop objects
    if (cmd_type == CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR) {
      amd::SyncD3D10Objects(memObjects);
    }
    //! Make sure D3D11 queues are flushed and all commands are finished
    //! before CL side would access interop objects
    if (cmd_type == CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR) {
      amd::SyncD3D11Objects(memObjects);
    }
    //! Make sure D3D9 queues are flushed and all commands are finished
    //! before CL side would access interop objects
    if (cmd_type == CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR) {
      amd::SyncD3D9Objects(memObjects);
    }
  }
#endif  //_WIN32

  //! Now create command and enqueue
  amd::AcquireExtObjectsCommand* command = new amd::AcquireExtObjectsCommand(
      hostQueue, eventWaitList, num_objects, memObjects, cmd_type);
  if (command == NULL) {
    return CL_OUT_OF_HOST_MEMORY;
  }

  // Make sure we have memory for the command execution
  if (!command->validateMemory()) {
    delete command;
    return CL_MEM_OBJECT_ALLOCATION_FAILURE;
  }

  command->enqueue();

  *not_null(event) = as_cl(&command->event());
  if (event == NULL) {
    command->release();
  }
  return CL_SUCCESS;
}


//
//      clEnqueueReleaseExtObjectsAMD
//
cl_int clEnqueueReleaseExtObjectsAMD(cl_command_queue command_queue, cl_uint num_objects,
                                     const cl_mem* mem_objects, cl_uint num_events_in_wait_list,
                                     const cl_event* event_wait_list, cl_event* event,
                                     cl_command_type cmd_type) {
  if (!is_valid(command_queue)) {
    return CL_INVALID_COMMAND_QUEUE;
  }

  amd::HostQueue* queue = as_amd(command_queue)->asHostQueue();
  if (NULL == queue) {
    return CL_INVALID_COMMAND_QUEUE;
  }
  amd::HostQueue& hostQueue = *queue;

  std::vector<amd::Memory*> memObjects;
  cl_int err = clSetInteropObjects(num_objects, mem_objects, memObjects);
  if (err != CL_SUCCESS) {
    return err;
  }

  amd::Command::EventWaitList eventWaitList;
  err = amd::clSetEventWaitList(eventWaitList, hostQueue, num_events_in_wait_list,
                                event_wait_list);
  if (err != CL_SUCCESS) {
    return err;
  }

  //! Now create command and enqueue
  amd::ReleaseExtObjectsCommand* command = new amd::ReleaseExtObjectsCommand(
      hostQueue, eventWaitList, num_objects, memObjects, cmd_type);
  if (command == NULL) {
    return CL_OUT_OF_HOST_MEMORY;
  }

  // Make sure we have memory for the command execution
  if (!command->validateMemory()) {
    delete command;
    return CL_MEM_OBJECT_ALLOCATION_FAILURE;
  }

  command->enqueue();

#ifdef _WIN32
  if ((hostQueue.context().info().flags_ & amd::Context::InteropUserSync) == 0) {
    //! Make sure CL command queue is flushed and all commands are finished
    //! before D3D10 side would access interop resources
    if (cmd_type == CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR ||
        cmd_type == CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR ||
        cmd_type == CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR) {
      command->awaitCompletion();
    }
  }
#endif  //_WIN32
  // If the cl_khr_gl_event extension is supported, then the OpenCL implementation will ensure
  // that any pending OpenCL operations are complete for an OpenGL context bound
  // to the same thread as the OpenCL context.
  if (cmd_type == CL_COMMAND_RELEASE_GL_OBJECTS) {
    GLFunctions* gl_functions = hostQueue.context().glenv();
    // Verify context init'ed for interop
    if (!gl_functions || !gl_functions->isAssociated()) {
      LogWarning("\"amdContext\" is not created from GL context or share list");
      return CL_INVALID_CONTEXT;
    }
    if (hostQueue.device().settings().checkExtension(ClKhrGlEvent) &&
        gl_functions->IsCurrentGlContext(hostQueue.context().info())) {
      command->awaitCompletion();
    }
  }

  *not_null(event) = as_cl(&command->event());

  if (event == NULL) {
    command->release();
  }

  return CL_SUCCESS;
}

}  // namespace amd
