/*
 * Copyright © 2012 Intel 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.
 */

/** \file marshal.c
 *
 * Custom functions for marshalling GL calls from the main thread to a worker
 * thread when automatic code generation isn't appropriate.
 */

#include "main/enums.h"
#include "main/macros.h"
#include "marshal.h"
#include "dispatch.h"
#include "marshal_generated.h"

#ifdef HAVE_PTHREAD

struct marshal_cmd_Flush
{
   struct marshal_cmd_base cmd_base;
};


void
_mesa_unmarshal_Flush(struct gl_context *ctx,
                      const struct marshal_cmd_Flush *cmd)
{
   CALL_Flush(ctx->CurrentServerDispatch, ());
}


void GLAPIENTRY
_mesa_marshal_Flush(void)
{
   GET_CURRENT_CONTEXT(ctx);
   struct marshal_cmd_Flush *cmd =
      _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_Flush,
                                      sizeof(struct marshal_cmd_Flush));
   (void) cmd;
   _mesa_post_marshal_hook(ctx);

   /* Flush() needs to be handled specially.  In addition to telling the
    * background thread to flush, we need to ensure that our own buffer is
    * submitted to the background thread so that it will complete in a finite
    * amount of time.
    */
   _mesa_glthread_flush_batch(ctx);
}

/* Enable: marshalled asynchronously */
struct marshal_cmd_Enable
{
   struct marshal_cmd_base cmd_base;
   GLenum cap;
};

void
_mesa_unmarshal_Enable(struct gl_context *ctx,
                       const struct marshal_cmd_Enable *cmd)
{
   const GLenum cap = cmd->cap;
   CALL_Enable(ctx->CurrentServerDispatch, (cap));
}

void GLAPIENTRY
_mesa_marshal_Enable(GLenum cap)
{
   GET_CURRENT_CONTEXT(ctx);
   struct marshal_cmd_Enable *cmd;
   debug_print_marshal("Enable");

   if (cap == GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB) {
      _mesa_glthread_finish(ctx);
      _mesa_glthread_restore_dispatch(ctx);
   } else {
      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_Enable,
                                            sizeof(*cmd));
      cmd->cap = cap;
      _mesa_post_marshal_hook(ctx);
      return;
   }

   _mesa_glthread_finish(ctx);
   debug_print_sync_fallback("Enable");
   CALL_Enable(ctx->CurrentServerDispatch, (cap));
}

struct marshal_cmd_ShaderSource
{
   struct marshal_cmd_base cmd_base;
   GLuint shader;
   GLsizei count;
   /* Followed by GLint length[count], then the contents of all strings,
    * concatenated.
    */
};


void
_mesa_unmarshal_ShaderSource(struct gl_context *ctx,
                             const struct marshal_cmd_ShaderSource *cmd)
{
   const GLint *cmd_length = (const GLint *) (cmd + 1);
   const GLchar *cmd_strings = (const GLchar *) (cmd_length + cmd->count);
   /* TODO: how to deal with malloc failure? */
   const GLchar * *string = malloc(cmd->count * sizeof(const GLchar *));
   int i;

   for (i = 0; i < cmd->count; ++i) {
      string[i] = cmd_strings;
      cmd_strings += cmd_length[i];
   }
   CALL_ShaderSource(ctx->CurrentServerDispatch,
                     (cmd->shader, cmd->count, string, cmd_length));
   free(string);
}


static size_t
measure_ShaderSource_strings(GLsizei count, const GLchar * const *string,
                             const GLint *length_in, GLint *length_out)
{
   int i;
   size_t total_string_length = 0;

   for (i = 0; i < count; ++i) {
      if (length_in == NULL || length_in[i] < 0) {
         if (string[i])
            length_out[i] = strlen(string[i]);
      } else {
         length_out[i] = length_in[i];
      }
      total_string_length += length_out[i];
   }
   return total_string_length;
}


void GLAPIENTRY
_mesa_marshal_ShaderSource(GLuint shader, GLsizei count,
                           const GLchar * const *string, const GLint *length)
{
   /* TODO: how to report an error if count < 0? */

   GET_CURRENT_CONTEXT(ctx);
   /* TODO: how to deal with malloc failure? */
   const size_t fixed_cmd_size = sizeof(struct marshal_cmd_ShaderSource);
   STATIC_ASSERT(sizeof(struct marshal_cmd_ShaderSource) % sizeof(GLint) == 0);
   size_t length_size = count * sizeof(GLint);
   GLint *length_tmp = malloc(length_size);
   size_t total_string_length =
      measure_ShaderSource_strings(count, string, length, length_tmp);
   size_t total_cmd_size = fixed_cmd_size + length_size + total_string_length;

   if (total_cmd_size <= MARSHAL_MAX_CMD_SIZE) {
      struct marshal_cmd_ShaderSource *cmd =
         _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_ShaderSource,
                                         total_cmd_size);
      GLint *cmd_length = (GLint *) (cmd + 1);
      GLchar *cmd_strings = (GLchar *) (cmd_length + count);
      int i;

      cmd->shader = shader;
      cmd->count = count;
      memcpy(cmd_length, length_tmp, length_size);
      for (i = 0; i < count; ++i) {
         memcpy(cmd_strings, string[i], cmd_length[i]);
         cmd_strings += cmd_length[i];
      }
      _mesa_post_marshal_hook(ctx);
   } else {
      _mesa_glthread_finish(ctx);
      CALL_ShaderSource(ctx->CurrentServerDispatch,
                        (shader, count, string, length_tmp));
   }
   free(length_tmp);
}


/* BindBufferBase: marshalled asynchronously */
struct marshal_cmd_BindBufferBase
{
   struct marshal_cmd_base cmd_base;
   GLenum target;
   GLuint index;
   GLuint buffer;
};
static inline void
_mesa_unmarshal_BindBufferBase(struct gl_context *ctx, const struct marshal_cmd_BindBufferBase *cmd)
{
   const GLenum target = cmd->target;
   const GLuint index = cmd->index;
   const GLuint buffer = cmd->buffer;
   CALL_BindBufferBase(ctx->CurrentServerDispatch, (target, index, buffer));
}

/** Tracks the current bindings for the vertex array and index array buffers.
 *
 * This is part of what we need to enable glthread on compat-GL contexts that
 * happen to use VBOs, without also supporting the full tracking of VBO vs
 * user vertex array bindings per attribute on each vertex array for
 * determining what to upload at draw call time.
 *
 * Note that GL core makes it so that a buffer binding with an invalid handle
 * in the "buffer" parameter will throw an error, and then a
 * glVertexAttribPointer() that followsmight not end up pointing at a VBO.
 * However, in GL core the draw call would throw an error as well, so we don't
 * really care if our tracking is wrong for this case -- we never need to
 * marshal user data for draw calls, and the unmarshal will just generate an
 * error or not as appropriate.
 *
 * For compatibility GL, we do need to accurately know whether the draw call
 * on the unmarshal side will dereference a user pointer or load data from a
 * VBO per vertex.  That would make it seem like we need to track whether a
 * "buffer" is valid, so that we can know when an error will be generated
 * instead of updating the binding.  However, compat GL has the ridiculous
 * feature that if you pass a bad name, it just gens a buffer object for you,
 * so we escape without having to know if things are valid or not.
 */
static void
track_vbo_binding(struct gl_context *ctx, GLenum target, GLuint buffer)
{
   struct glthread_state *glthread = ctx->GLThread;

   switch (target) {
   case GL_ARRAY_BUFFER:
      glthread->vertex_array_is_vbo = (buffer != 0);
      break;
   case GL_ELEMENT_ARRAY_BUFFER:
      /* The current element array buffer binding is actually tracked in the
       * vertex array object instead of the context, so this would need to
       * change on vertex array object updates.
       */
      glthread->element_array_is_vbo = (buffer != 0);
      break;
   }
}


struct marshal_cmd_BindBuffer
{
   struct marshal_cmd_base cmd_base;
   GLenum target;
   GLuint buffer;
};

/**
 * This is just like the code-generated glBindBuffer() support, except that we
 * call track_vbo_binding().
 */
void
_mesa_unmarshal_BindBuffer(struct gl_context *ctx,
                           const struct marshal_cmd_BindBuffer *cmd)
{
   const GLenum target = cmd->target;
   const GLuint buffer = cmd->buffer;
   CALL_BindBuffer(ctx->CurrentServerDispatch, (target, buffer));
}
void GLAPIENTRY
_mesa_marshal_BindBuffer(GLenum target, GLuint buffer)
{
   GET_CURRENT_CONTEXT(ctx);
   size_t cmd_size = sizeof(struct marshal_cmd_BindBuffer);
   struct marshal_cmd_BindBuffer *cmd;
   debug_print_marshal("BindBuffer");

   track_vbo_binding(ctx, target, buffer);

   if (cmd_size <= MARSHAL_MAX_CMD_SIZE) {
      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_BindBuffer,
                                            cmd_size);
      cmd->target = target;
      cmd->buffer = buffer;
      _mesa_post_marshal_hook(ctx);
   } else {
      _mesa_glthread_finish(ctx);
      CALL_BindBuffer(ctx->CurrentServerDispatch, (target, buffer));
   }
}

/* BufferData: marshalled asynchronously */
struct marshal_cmd_BufferData
{
   struct marshal_cmd_base cmd_base;
   GLenum target;
   GLsizeiptr size;
   GLenum usage;
   bool data_null; /* If set, no data follows for "data" */
   /* Next size bytes are GLubyte data[size] */
};

void
_mesa_unmarshal_BufferData(struct gl_context *ctx,
                           const struct marshal_cmd_BufferData *cmd)
{
   const GLenum target = cmd->target;
   const GLsizeiptr size = cmd->size;
   const GLenum usage = cmd->usage;
   const void *data;

   if (cmd->data_null)
      data = NULL;
   else
      data = (const void *) (cmd + 1);

   CALL_BufferData(ctx->CurrentServerDispatch, (target, size, data, usage));
}

void GLAPIENTRY
_mesa_marshal_BufferData(GLenum target, GLsizeiptr size, const GLvoid * data,
                         GLenum usage)
{
   GET_CURRENT_CONTEXT(ctx);
   size_t cmd_size =
      sizeof(struct marshal_cmd_BufferData) + (data ? size : 0);
   debug_print_marshal("BufferData");

   if (unlikely(size < 0)) {
      _mesa_glthread_finish(ctx);
      _mesa_error(ctx, GL_INVALID_VALUE, "BufferData(size < 0)");
      return;
   }

   if (target != GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD &&
       cmd_size <= MARSHAL_MAX_CMD_SIZE) {
      struct marshal_cmd_BufferData *cmd =
         _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_BufferData,
                                         cmd_size);

      cmd->target = target;
      cmd->size = size;
      cmd->usage = usage;
      cmd->data_null = !data;
      if (data) {
         char *variable_data = (char *) (cmd + 1);
         memcpy(variable_data, data, size);
      }
      _mesa_post_marshal_hook(ctx);
   } else {
      _mesa_glthread_finish(ctx);
      CALL_BufferData(ctx->CurrentServerDispatch,
                      (target, size, data, usage));
   }
}

/* BufferSubData: marshalled asynchronously */
struct marshal_cmd_BufferSubData
{
   struct marshal_cmd_base cmd_base;
   GLenum target;
   GLintptr offset;
   GLsizeiptr size;
   /* Next size bytes are GLubyte data[size] */
};

void
_mesa_unmarshal_BufferSubData(struct gl_context *ctx,
                              const struct marshal_cmd_BufferSubData *cmd)
{
   const GLenum target = cmd->target;
   const GLintptr offset = cmd->offset;
   const GLsizeiptr size = cmd->size;
   const void *data = (const void *) (cmd + 1);

   CALL_BufferSubData(ctx->CurrentServerDispatch,
                      (target, offset, size, data));
}

void GLAPIENTRY
_mesa_marshal_BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size,
                            const GLvoid * data)
{
   GET_CURRENT_CONTEXT(ctx);
   size_t cmd_size = sizeof(struct marshal_cmd_BufferSubData) + size;

   debug_print_marshal("BufferSubData");
   if (unlikely(size < 0)) {
      _mesa_glthread_finish(ctx);
      _mesa_error(ctx, GL_INVALID_VALUE, "BufferSubData(size < 0)");
      return;
   }

   if (target != GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD &&
       cmd_size <= MARSHAL_MAX_CMD_SIZE) {
      struct marshal_cmd_BufferSubData *cmd =
         _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_BufferSubData,
                                         cmd_size);
      cmd->target = target;
      cmd->offset = offset;
      cmd->size = size;
      char *variable_data = (char *) (cmd + 1);
      memcpy(variable_data, data, size);
      _mesa_post_marshal_hook(ctx);
   } else {
      _mesa_glthread_finish(ctx);
      CALL_BufferSubData(ctx->CurrentServerDispatch,
                         (target, offset, size, data));
   }
}

/* ClearBufferfv: marshalled asynchronously */
struct marshal_cmd_ClearBufferfv
{
   struct marshal_cmd_base cmd_base;
   GLenum buffer;
   GLint drawbuffer;
};

void
_mesa_unmarshal_ClearBufferfv(struct gl_context *ctx,
                              const struct marshal_cmd_ClearBufferfv *cmd)
{
   const GLenum buffer = cmd->buffer;
   const GLint drawbuffer = cmd->drawbuffer;
   const char *variable_data = (const char *) (cmd + 1);
   const GLfloat *value = (const GLfloat *) variable_data;

   CALL_ClearBufferfv(ctx->CurrentServerDispatch,
                      (buffer, drawbuffer, value));
}

void GLAPIENTRY
_mesa_marshal_ClearBufferfv(GLenum buffer, GLint drawbuffer,
                            const GLfloat *value)
{
   GET_CURRENT_CONTEXT(ctx);
   debug_print_marshal("ClearBufferfv");

   size_t size;
   switch (buffer) {
   case GL_DEPTH:
      size = sizeof(GLfloat);
      break;
   case GL_COLOR:
      size = sizeof(GLfloat) * 4;
      break;
   default:
      _mesa_glthread_finish(ctx);

      /* Page 498 of the PDF, section '17.4.3.1 Clearing Individual Buffers'
       * of the OpenGL 4.5 spec states:
       *
       *    "An INVALID_ENUM error is generated by ClearBufferfv and
       *     ClearNamedFramebufferfv if buffer is not COLOR or DEPTH."
       */
      _mesa_error(ctx, GL_INVALID_ENUM, "glClearBufferfv(buffer=%s)",
                  _mesa_enum_to_string(buffer));
      return;
   }

   size_t cmd_size = sizeof(struct marshal_cmd_ClearBufferfv) + size;
   if (cmd_size <= MARSHAL_MAX_CMD_SIZE) {
      struct marshal_cmd_ClearBufferfv *cmd =
         _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_ClearBufferfv,
                                         cmd_size);
      cmd->buffer = buffer;
      cmd->drawbuffer = drawbuffer;
      GLfloat *variable_data = (GLfloat *) (cmd + 1);
      if (buffer == GL_COLOR)
         COPY_4V(variable_data, value);
      else
         *variable_data = *value;

      _mesa_post_marshal_hook(ctx);
   } else {
      debug_print_sync("ClearBufferfv");
      _mesa_glthread_finish(ctx);
      CALL_ClearBufferfv(ctx->CurrentServerDispatch,
                         (buffer, drawbuffer, value));
   }
}

#endif