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

#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 = std::any_of(devices.begin(), devices.end(), [](const amd::Device* device) {
    return device->info().imageSupport_;
  });
  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 = std::any_of(devices.begin(), devices.end(), [](const amd::Device* device) {
    return device->info().imageSupport_;
  });
  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 = std::any_of(devices.begin(), devices.end(), [](const amd::Device* device) {
    return device->info().imageSupport_;
  });
  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;
  }

  size_t originalSize = interopObjects.size();
  interopObjects.reserve(originalSize + num_objects);
  while (num_objects-- > 0) {
    cl_mem obj = *mem_objects++;
    if (!is_valid(obj)) {
      interopObjects.resize(originalSize);
      return CL_INVALID_MEM_OBJECT;
    }

    amd::Memory* mem = as_amd(obj);
    if (mem->getInteropObj() == NULL) {
      interopObjects.resize(originalSize);
      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
