/*
 * Copyright 2009, VMware, Inc.
 * Copyright (C) 2010 LunarG Inc.
 * Copyright © Microsoft Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "st_interop.h"
#include "st_cb_texture.h"
#include "st_cb_flush.h"
#include "st_texture.h"

#include "bufferobj.h"
#include "texobj.h"
#include "teximage.h"
#include "syncobj.h"

int
st_interop_query_device_info(struct st_context *st,
                             struct mesa_glinterop_device_info *out)
{
   struct pipe_screen *screen = st->pipe->screen;

   /* There is no version 0, thus we do not support it */
   if (out->version == 0)
      return MESA_GLINTEROP_INVALID_VERSION;

   if (!screen->resource_get_handle && !screen->interop_export_object)
      return MESA_GLINTEROP_UNSUPPORTED;

   /* PCI values are obsolete on version >= 4 of the interface */
   if (out->version < 4) {
      out->pci_segment_group = screen->get_param(screen, PIPE_CAP_PCI_GROUP);
      out->pci_bus = screen->get_param(screen, PIPE_CAP_PCI_BUS);
      out->pci_device = screen->get_param(screen, PIPE_CAP_PCI_DEVICE);
      out->pci_function = screen->get_param(screen, PIPE_CAP_PCI_FUNCTION);
   }

   out->vendor_id = screen->get_param(screen, PIPE_CAP_VENDOR_ID);
   out->device_id = screen->get_param(screen, PIPE_CAP_DEVICE_ID);

   if (out->version > 1 && screen->interop_query_device_info)
      out->driver_data_size = screen->interop_query_device_info(screen,
                                                                out->driver_data_size,
                                                                out->driver_data);

   if (out->version >= 3 && screen->get_device_uuid)
      screen->get_device_uuid(screen, out->device_uuid);

   /* Instruct the caller that we support up-to version four of the interface */
   out->version = MIN2(out->version, 4);

   return MESA_GLINTEROP_SUCCESS;
}

static int
lookup_object(struct gl_context *ctx,
              struct mesa_glinterop_export_in *in,
              struct mesa_glinterop_export_out *out, struct pipe_resource **res)
{
   unsigned target = in->target;
   /* Validate the target. */
   switch (in->target) {
   case GL_TEXTURE_BUFFER:
   case GL_TEXTURE_1D:
   case GL_TEXTURE_2D:
   case GL_TEXTURE_3D:
   case GL_TEXTURE_RECTANGLE:
   case GL_TEXTURE_1D_ARRAY:
   case GL_TEXTURE_2D_ARRAY:
   case GL_TEXTURE_CUBE_MAP_ARRAY:
   case GL_TEXTURE_CUBE_MAP:
   case GL_TEXTURE_2D_MULTISAMPLE:
   case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
   case GL_TEXTURE_EXTERNAL_OES:
   case GL_RENDERBUFFER:
   case GL_ARRAY_BUFFER:
      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:
      target = GL_TEXTURE_CUBE_MAP;
      break;
   default:
      return MESA_GLINTEROP_INVALID_TARGET;
   }

   /* Validate the simple case of miplevel. */
   if ((target == GL_RENDERBUFFER || target == GL_ARRAY_BUFFER) &&
       in->miplevel != 0)
      return MESA_GLINTEROP_INVALID_MIP_LEVEL;

   if (target == GL_ARRAY_BUFFER) {
      /* Buffer objects.
      *
      * The error checking is based on the documentation of
      * clCreateFromGLBuffer from OpenCL 2.0 SDK.
      */
      struct gl_buffer_object *buf = _mesa_lookup_bufferobj(ctx, in->obj);

      /* From OpenCL 2.0 SDK, clCreateFromGLBuffer:
      *  "CL_INVALID_GL_OBJECT if bufobj is not a GL buffer object or is
      *   a GL buffer object but does not have an existing data store or
      *   the size of the buffer is 0."
      */
      if (!buf || buf->Size == 0)
         return MESA_GLINTEROP_INVALID_OBJECT;

      *res = buf->buffer;
      /* this shouldn't happen */
      if (!*res)
         return MESA_GLINTEROP_INVALID_OBJECT;

      if (out) {
         out->buf_offset = 0;
         out->buf_size = buf->Size;

         buf->UsageHistory |= USAGE_DISABLE_MINMAX_CACHE;
      }
   } else if (target == GL_RENDERBUFFER) {
      /* Renderbuffers.
      *
      * The error checking is based on the documentation of
      * clCreateFromGLRenderbuffer from OpenCL 2.0 SDK.
      */
      struct gl_renderbuffer *rb = _mesa_lookup_renderbuffer(ctx, in->obj);

      /* From OpenCL 2.0 SDK, clCreateFromGLRenderbuffer:
      *   "CL_INVALID_GL_OBJECT if renderbuffer is not a GL renderbuffer
      *    object or if the width or height of renderbuffer is zero."
      */
      if (!rb || rb->Width == 0 || rb->Height == 0)
         return MESA_GLINTEROP_INVALID_OBJECT;

      /* From OpenCL 2.0 SDK, clCreateFromGLRenderbuffer:
      *   "CL_INVALID_OPERATION if renderbuffer is a multi-sample GL
      *    renderbuffer object."
      */
      if (rb->NumSamples > 1)
         return MESA_GLINTEROP_INVALID_OPERATION;

      /* From OpenCL 2.0 SDK, clCreateFromGLRenderbuffer:
      *   "CL_OUT_OF_RESOURCES if there is a failure to allocate resources
      *    required by the OpenCL implementation on the device."
      */
      *res = rb->texture;
      if (!*res)
         return MESA_GLINTEROP_OUT_OF_RESOURCES;

      if (out) {
         out->internal_format = rb->InternalFormat;
         out->view_minlevel = 0;
         out->view_numlevels = 1;
         out->view_minlayer = 0;
         out->view_numlayers = 1;

         if (out->version >= 2) {
           out->width = rb->Width;
           out->height = rb->Height;
           out->depth = MAX2(1, rb->Depth);
         }
      }
   } else {
      /* Texture objects.
      *
      * The error checking is based on the documentation of
      * clCreateFromGLTexture from OpenCL 2.0 SDK.
      */
      struct gl_texture_object *obj = _mesa_lookup_texture(ctx, in->obj);

      if (obj)
         _mesa_test_texobj_completeness(ctx, obj);

      /* From OpenCL 2.0 SDK, clCreateFromGLTexture:
      *   "CL_INVALID_GL_OBJECT if texture is not a GL texture object whose
      *    type matches texture_target, if the specified miplevel of texture
      *    is not defined, or if the width or height of the specified
      *    miplevel is zero or if the GL texture object is incomplete."
      */
      if (!obj ||
          obj->Target != target ||
          !obj->_BaseComplete ||
          (in->miplevel > 0 && !obj->_MipmapComplete))
         return MESA_GLINTEROP_INVALID_OBJECT;

      if (target == GL_TEXTURE_BUFFER) {
         struct gl_buffer_object *stBuf =
            obj->BufferObject;

         /* this shouldn't happen */
         if (!stBuf || !stBuf->buffer)
            return MESA_GLINTEROP_INVALID_OBJECT;
         *res = stBuf->buffer;

         if (out) {
            out->internal_format = obj->BufferObjectFormat;
            out->buf_offset = obj->BufferOffset;
            out->buf_size = obj->BufferSize == -1 ? obj->BufferObject->Size :
               obj->BufferSize;

            obj->BufferObject->UsageHistory |= USAGE_DISABLE_MINMAX_CACHE;
         }
      } else {
         /* From OpenCL 2.0 SDK, clCreateFromGLTexture:
         *   "CL_INVALID_MIP_LEVEL if miplevel is less than the value of
         *    levelbase (for OpenGL implementations) or zero (for OpenGL ES
         *    implementations); or greater than the value of q (for both OpenGL
         *    and OpenGL ES). levelbase and q are defined for the texture in
         *    section 3.8.10 (Texture Completeness) of the OpenGL 2.1
         *    specification and section 3.7.10 of the OpenGL ES 2.0."
         */
         if (in->miplevel < obj->Attrib.BaseLevel || in->miplevel > obj->_MaxLevel)
            return MESA_GLINTEROP_INVALID_MIP_LEVEL;

         if (!st_finalize_texture(ctx, ctx->st->pipe, obj, 0))
            return MESA_GLINTEROP_OUT_OF_RESOURCES;

         *res = st_get_texobj_resource(obj);
         /* Incomplete texture buffer object? This shouldn't really occur. */
         if (!*res)
            return MESA_GLINTEROP_INVALID_OBJECT;

         if (out) {
            out->internal_format = obj->Image[0][0]->InternalFormat;
            out->view_minlevel = obj->Attrib.MinLevel;
            out->view_numlevels = obj->Attrib.NumLevels;
            out->view_minlayer = obj->Attrib.MinLayer;
            out->view_numlayers = obj->Attrib.NumLayers;

            if (out->version >= 2) {
               const GLuint face = _mesa_tex_target_to_face(in->target);;
               struct gl_texture_image *image = obj->Image[face][in->miplevel];

               out->width = image->Width;
               out->height = image->Height;
               out->depth = image->Depth;
            }
         }
      }
   }
   return MESA_GLINTEROP_SUCCESS;
}

int
st_interop_export_object(struct st_context *st,
                         struct mesa_glinterop_export_in *in,
                         struct mesa_glinterop_export_out *out)
{
   struct pipe_screen *screen = st->pipe->screen;
   struct gl_context *ctx = st->ctx;
   struct pipe_resource *res = NULL;
   struct winsys_handle whandle;
   unsigned usage;
   bool success;
   bool need_export_dmabuf = true;

   /* There is no version 0, thus we do not support it */
   if (in->version == 0 || out->version == 0)
      return MESA_GLINTEROP_INVALID_VERSION;

   if (!screen->resource_get_handle && !screen->interop_export_object)
      return MESA_GLINTEROP_UNSUPPORTED;

   /* Wait for glthread to finish to get up-to-date GL object lookups. */
   _mesa_glthread_finish(st->ctx);

   /* Validate the OpenGL object and get pipe_resource. */
   simple_mtx_lock(&ctx->Shared->Mutex);

   int ret = lookup_object(ctx, in, out, &res);
   if (ret != MESA_GLINTEROP_SUCCESS) {
      simple_mtx_unlock(&ctx->Shared->Mutex);
      return ret;
   }

   /* Get the handle. */
   switch (in->access) {
   case MESA_GLINTEROP_ACCESS_READ_ONLY:
      usage = 0;
      break;
   case MESA_GLINTEROP_ACCESS_READ_WRITE:
   case MESA_GLINTEROP_ACCESS_WRITE_ONLY:
      usage = PIPE_HANDLE_USAGE_SHADER_WRITE;
      break;
   default:
      usage = 0;
   }

   out->out_driver_data_written = 0;
   if (screen->interop_export_object) {
      out->out_driver_data_written = screen->interop_export_object(screen,
                                                                   res,
                                                                   in->out_driver_data_size,
                                                                   in->out_driver_data,
                                                                   &need_export_dmabuf);
   }

   memset(&whandle, 0, sizeof(whandle));

   if (need_export_dmabuf) {
      whandle.type = WINSYS_HANDLE_TYPE_FD;

      /* OpenCL requires explicit flushes. */
      if (out->version >= 2)
         usage |= PIPE_HANDLE_USAGE_EXPLICIT_FLUSH;

      success = screen->resource_get_handle(screen, st->pipe, res, &whandle,
                                            usage);

      if (!success) {
         simple_mtx_unlock(&ctx->Shared->Mutex);
         return MESA_GLINTEROP_OUT_OF_HOST_MEMORY;
      }

#ifndef _WIN32
      out->dmabuf_fd = whandle.handle;
#else
      out->win32_handle = whandle.handle;
#endif

      if (out->version >= 2) {
         out->modifier = whandle.modifier;
         out->stride = whandle.stride;
      }
   }

   simple_mtx_unlock(&ctx->Shared->Mutex);

   if (res->target == PIPE_BUFFER)
      out->buf_offset += whandle.offset;

   /* Instruct the caller of the version of the interface we support */
   in->version = MIN2(in->version, 2);
   out->version = MIN2(out->version, 2);

   return MESA_GLINTEROP_SUCCESS;
}

static int
flush_object(struct gl_context *ctx,
                        struct mesa_glinterop_export_in *in)
{
   struct pipe_resource *res = NULL;
   /* There is no version 0, thus we do not support it */
   if (in->version == 0)
      return MESA_GLINTEROP_INVALID_VERSION;

   int ret = lookup_object(ctx, in, NULL, &res);
   if (ret != MESA_GLINTEROP_SUCCESS)
      return ret;

   ctx->pipe->flush_resource(ctx->pipe, res);

   /* Instruct the caller of the version of the interface we support */
   in->version = MIN2(in->version, 2);

   return MESA_GLINTEROP_SUCCESS;
}

int
st_interop_flush_objects(struct st_context *st,
                         unsigned count, struct mesa_glinterop_export_in *objects,
                         struct mesa_glinterop_flush_out *out)
{
   struct gl_context *ctx = st->ctx;
   bool flush_out_struct = false;

   if (!ctx->screen->resource_get_handle && !ctx->screen->interop_export_object)
      return MESA_GLINTEROP_UNSUPPORTED;

   /* Wait for glthread to finish to get up-to-date GL object lookups. */
   _mesa_glthread_finish(st->ctx);

   simple_mtx_lock(&ctx->Shared->Mutex);

   for (unsigned i = 0; i < count; ++i) {
      int ret = flush_object(ctx, &objects[i]);

      if (objects[i].version >= 2)
         flush_out_struct = true;

      if (ret != MESA_GLINTEROP_SUCCESS) {
         simple_mtx_unlock(&ctx->Shared->Mutex);
         return ret;
      }
   }

   simple_mtx_unlock(&ctx->Shared->Mutex);

   if (count > 0 && out) {
      if (flush_out_struct) {
         if (out->sync) {
            *out->sync = _mesa_fence_sync(ctx, GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
         }
         if (out->fence_fd) {
            struct pipe_fence_handle *fence = NULL;
            ctx->pipe->flush(ctx->pipe, &fence, PIPE_FLUSH_FENCE_FD | PIPE_FLUSH_ASYNC);
            *out->fence_fd = ctx->screen->fence_get_fd(ctx->screen, fence);
         }
         out->version = MIN2(out->version, 1);
      } else {
         GLsync *sync = (GLsync *)out;
         *sync = _mesa_fence_sync(ctx, GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
      }
   }

   return MESA_GLINTEROP_SUCCESS;
}
