/*
 * Copyright © 2020 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (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.
 */

/* Draw function marshalling for glthread.
 *
 * The purpose of these glDraw wrappers is to upload non-VBO vertex and
 * index data, so that glthread doesn't have to execute synchronously.
 */

#include "c99_alloca.h"

#include "api_exec_decl.h"
#include "main/glthread_marshal.h"
#include "main/dispatch.h"
#include "main/varray.h"

static inline unsigned
get_index_size(GLenum type)
{
   return 1 << _mesa_get_index_size_shift(type);
}

static inline GLindextype
encode_index_type(GLenum type)
{
   /* Map invalid values less than GL_UNSIGNED_BYTE to GL_UNSIGNED_BYTE - 1,
    * and invalid values greater than GL_UNSIGNED_INT to GL_UNSIGNED_INT + 1,
    * Then subtract GL_UNSIGNED_BYTE - 1. Final encoding:
    *    0 = invalid value
    *    1 = GL_UNSIGNED_BYTE
    *    2 = invalid value
    *    3 = GL_UNSIGNED_SHORT
    *    4 = invalid value
    *    5 = GL_UNSIGNED_INT
    *    6 = invalid value
    */
   const unsigned min = GL_UNSIGNED_BYTE - 1;
   const unsigned max = GL_UNSIGNED_INT + 1;
   return (GLindextype){CLAMP(type, min, max) - min};
}

static ALWAYS_INLINE struct gl_buffer_object *
upload_indices(struct gl_context *ctx, unsigned count, unsigned index_size,
               const GLvoid **indices)
{
   struct gl_buffer_object *upload_buffer = NULL;
   unsigned upload_offset = 0;

   assert(count);

   _mesa_glthread_upload(ctx, *indices, index_size * count,
                         &upload_offset, &upload_buffer, NULL, 0);
   *indices = (const GLvoid*)(intptr_t)upload_offset;

   if (!upload_buffer)
      _mesa_marshal_InternalSetError(GL_OUT_OF_MEMORY);

   return upload_buffer;
}

static ALWAYS_INLINE struct gl_buffer_object *
upload_multi_indices(struct gl_context *ctx, unsigned total_count,
                     unsigned index_size, unsigned draw_count,
                     const GLsizei *count, const GLvoid *const *indices,
                     const GLvoid **out_indices)
{
   struct gl_buffer_object *upload_buffer = NULL;
   unsigned upload_offset = 0;
   uint8_t *upload_ptr = NULL;

   assert(total_count);

   _mesa_glthread_upload(ctx, NULL, index_size * total_count,
                         &upload_offset, &upload_buffer, &upload_ptr, 0);
   if (!upload_buffer) {
      _mesa_marshal_InternalSetError(GL_OUT_OF_MEMORY);
      return NULL;
   }

   for (unsigned i = 0, offset = 0; i < draw_count; i++) {
      if (!count[i]) {
         /* Set some valid value so as not to leave it uninitialized. */
         out_indices[i] = (const GLvoid*)(intptr_t)upload_offset;
         continue;
      }

      unsigned size = count[i] * index_size;

      memcpy(upload_ptr + offset, indices[i], size);
      out_indices[i] = (const GLvoid*)(intptr_t)(upload_offset + offset);
      offset += size;
   }

   return upload_buffer;
}

static ALWAYS_INLINE bool
upload_vertices(struct gl_context *ctx, unsigned user_buffer_mask,
                unsigned start_vertex, unsigned num_vertices,
                unsigned start_instance, unsigned num_instances,
                struct gl_buffer_object **buffers, int *offsets)
{
   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned attrib_mask_iter = vao->Enabled;
   unsigned num_buffers = 0;

   assert((num_vertices || !(user_buffer_mask & ~vao->NonZeroDivisorMask)) &&
          (num_instances || !(user_buffer_mask & vao->NonZeroDivisorMask)));

   if (unlikely(vao->BufferInterleaved & user_buffer_mask)) {
      /* Slower upload path where some buffers reference multiple attribs,
       * so we have to use 2 while loops instead of 1.
       */
      unsigned start_offset[VERT_ATTRIB_MAX];
      unsigned end_offset[VERT_ATTRIB_MAX];
      uint32_t buffer_mask = 0;

      while (attrib_mask_iter) {
         unsigned i = u_bit_scan(&attrib_mask_iter);
         unsigned binding_index = vao->Attrib[i].BufferIndex;

         if (!(user_buffer_mask & (1 << binding_index)))
            continue;

         unsigned stride = vao->Attrib[binding_index].Stride;
         unsigned instance_div = vao->Attrib[binding_index].Divisor;
         unsigned element_size = vao->Attrib[i].ElementSize;
         unsigned offset = vao->Attrib[i].RelativeOffset;
         unsigned size;

         if (instance_div) {
            /* Per-instance attrib. */

            /* Figure out how many instances we'll render given instance_div.  We
             * can't use the typical div_round_up() pattern because the CTS uses
             * instance_div = ~0 for a test, which overflows div_round_up()'s
             * addition.
             */
            unsigned count = num_instances / instance_div;
            if (count * instance_div != num_instances)
               count++;

            offset += stride * start_instance;
            size = stride * (count - 1) + element_size;
         } else {
            /* Per-vertex attrib. */
            offset += stride * start_vertex;
            size = stride * (num_vertices - 1) + element_size;
         }

         unsigned binding_index_bit = 1u << binding_index;

         /* Update upload offsets. */
         if (!(buffer_mask & binding_index_bit)) {
            start_offset[binding_index] = offset;
            end_offset[binding_index] = offset + size;
         } else {
            if (offset < start_offset[binding_index])
               start_offset[binding_index] = offset;
            if (offset + size > end_offset[binding_index])
               end_offset[binding_index] = offset + size;
         }

         buffer_mask |= binding_index_bit;
      }

      /* Upload buffers. */
      while (buffer_mask) {
         struct gl_buffer_object *upload_buffer = NULL;
         unsigned upload_offset = 0;
         unsigned start, end;

         unsigned binding_index = u_bit_scan(&buffer_mask);

         start = start_offset[binding_index];
         end = end_offset[binding_index];
         assert(start < end);

         /* If the draw start index is non-zero, glthread can upload to offset 0,
         * which means the attrib offset has to be -(first * stride).
         * So use signed vertex buffer offsets when possible to save memory.
         */
         const void *ptr = vao->Attrib[binding_index].Pointer;
         _mesa_glthread_upload(ctx, (uint8_t*)ptr + start,
                               end - start, &upload_offset,
                               &upload_buffer, NULL, ctx->Const.VertexBufferOffsetIsInt32 ? 0 : start);
         if (!upload_buffer) {
            for (unsigned i = 0; i < num_buffers; i++)
               _mesa_reference_buffer_object(ctx, &buffers[i], NULL);

            _mesa_marshal_InternalSetError(GL_OUT_OF_MEMORY);
            return false;
         }

         buffers[num_buffers] = upload_buffer;
         offsets[num_buffers] = upload_offset - start;
         num_buffers++;
      }

      return true;
   }

   /* Faster path where all attribs are separate. */
   while (attrib_mask_iter) {
      unsigned i = u_bit_scan(&attrib_mask_iter);
      unsigned binding_index = vao->Attrib[i].BufferIndex;

      if (!(user_buffer_mask & (1 << binding_index)))
         continue;

      struct gl_buffer_object *upload_buffer = NULL;
      unsigned upload_offset = 0;
      unsigned stride = vao->Attrib[binding_index].Stride;
      unsigned instance_div = vao->Attrib[binding_index].Divisor;
      unsigned element_size = vao->Attrib[i].ElementSize;
      unsigned offset = vao->Attrib[i].RelativeOffset;
      unsigned size;

      if (instance_div) {
         /* Per-instance attrib. */

         /* Figure out how many instances we'll render given instance_div.  We
          * can't use the typical div_round_up() pattern because the CTS uses
          * instance_div = ~0 for a test, which overflows div_round_up()'s
          * addition.
          */
         unsigned count = num_instances / instance_div;
         if (count * instance_div != num_instances)
            count++;

         offset += stride * start_instance;
         size = stride * (count - 1) + element_size;
      } else {
         /* Per-vertex attrib. */
         offset += stride * start_vertex;
         size = stride * (num_vertices - 1) + element_size;
      }

      /* If the draw start index is non-zero, glthread can upload to offset 0,
       * which means the attrib offset has to be -(first * stride).
       * So use signed vertex buffer offsets when possible to save memory.
       */
      const void *ptr = vao->Attrib[binding_index].Pointer;
      _mesa_glthread_upload(ctx, (uint8_t*)ptr + offset,
                            size, &upload_offset, &upload_buffer, NULL,
                            ctx->Const.VertexBufferOffsetIsInt32 ? 0 : offset);
      if (!upload_buffer) {
         for (unsigned i = 0; i < num_buffers; i++)
            _mesa_reference_buffer_object(ctx, &buffers[i], NULL);

         _mesa_marshal_InternalSetError(GL_OUT_OF_MEMORY);
         return false;
      }

      buffers[num_buffers] = upload_buffer;
      offsets[num_buffers] = upload_offset - offset;
      num_buffers++;
   }

   return true;
}

/* DrawArraysInstanced without user buffers. */
uint32_t
_mesa_unmarshal_DrawArraysInstanced(struct gl_context *ctx,
                                    const struct marshal_cmd_DrawArraysInstanced *restrict cmd)
{
   const GLenum mode = cmd->mode;
   const GLint first = cmd->first;
   const GLsizei count = cmd->count;
   const GLsizei instance_count = cmd->primcount;

   CALL_DrawArraysInstanced(ctx->Dispatch.Current, (mode, first, count, instance_count));
   return align(sizeof(*cmd), 8) / 8;
}

struct marshal_cmd_DrawArraysInstancedBaseInstanceDrawID
{
   struct marshal_cmd_base cmd_base;
   GLenum8 mode;
   GLint first;
   GLsizei count;
   GLsizei instance_count;
   GLuint baseinstance;
   GLuint drawid;
};

uint32_t
_mesa_unmarshal_DrawArraysInstancedBaseInstanceDrawID(struct gl_context *ctx,
                                                const struct marshal_cmd_DrawArraysInstancedBaseInstanceDrawID *cmd)
{
   const GLenum mode = cmd->mode;
   const GLint first = cmd->first;
   const GLsizei count = cmd->count;
   const GLsizei instance_count = cmd->instance_count;
   const GLuint baseinstance = cmd->baseinstance;

   ctx->DrawID = cmd->drawid;
   CALL_DrawArraysInstancedBaseInstance(ctx->Dispatch.Current,
                                        (mode, first, count, instance_count,
                                         baseinstance));
   ctx->DrawID = 0;
   return align(sizeof(*cmd), 8) / 8;
}

/* DrawArraysInstancedBaseInstance with user buffers. */
struct marshal_cmd_DrawArraysUserBuf
{
   struct marshal_cmd_base cmd_base;
   GLenum8 mode;
   uint16_t num_slots;
   GLint first;
   GLsizei count;
   GLsizei instance_count;
   GLuint baseinstance;
   GLuint drawid;
   GLuint user_buffer_mask;
};

uint32_t
_mesa_unmarshal_DrawArraysUserBuf(struct gl_context *ctx,
                                  const struct marshal_cmd_DrawArraysUserBuf *restrict cmd)
{
   const GLuint user_buffer_mask = cmd->user_buffer_mask;

   /* Bind uploaded buffers if needed. */
   if (user_buffer_mask) {
      struct gl_buffer_object **buffers = (struct gl_buffer_object **)(cmd + 1);
      const int *offsets = (const int *)(buffers + util_bitcount(user_buffer_mask));

      _mesa_InternalBindVertexBuffers(ctx, buffers, offsets, user_buffer_mask);
   }

   const GLenum mode = cmd->mode;
   const GLint first = cmd->first;
   const GLsizei count = cmd->count;
   const GLsizei instance_count = cmd->instance_count;
   const GLuint baseinstance = cmd->baseinstance;

   ctx->DrawID = cmd->drawid;
   CALL_DrawArraysInstancedBaseInstance(ctx->Dispatch.Current,
                                        (mode, first, count, instance_count,
                                         baseinstance));
   ctx->DrawID = 0;
   return cmd->num_slots;
}

static inline unsigned
get_user_buffer_mask(struct gl_context *ctx)
{
   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;

   /* BufferEnabled means which attribs are enabled in terms of buffer
    * binding slots (not attrib slots).
    *
    * UserPointerMask means which buffer bindings don't have a buffer bound.
    *
    * NonNullPointerMask means which buffer bindings have a NULL pointer.
    * Those are not uploaded. This can happen when an attrib is enabled, but
    * the shader doesn't use it, so it's ignored by mesa/state_tracker.
    */
   return vao->BufferEnabled & vao->UserPointerMask & vao->NonNullPointerMask;
}

static ALWAYS_INLINE void
draw_arrays(GLuint drawid, GLenum mode, GLint first, GLsizei count,
            GLsizei instance_count, GLuint baseinstance,
            bool compiled_into_dlist, bool no_error)
{
   GET_CURRENT_CONTEXT(ctx);

   /* The main benefit of no_error is that we can discard no-op draws
    * immediately.
    */
   if (no_error && (count <= 0 || instance_count <= 0))
      return;

   if (unlikely(compiled_into_dlist && ctx->GLThread.ListMode)) {
      _mesa_glthread_finish_before(ctx, "DrawArrays");
      /* Use the function that's compiled into a display list. */
      CALL_DrawArrays(ctx->Dispatch.Current, (mode, first, count));
      return;
   }

   unsigned user_buffer_mask =
      _mesa_is_desktop_gl_core(ctx) ? 0 : get_user_buffer_mask(ctx);

   /* Fast path when nothing needs to be done.
    *
    * This is also an error path. Zero counts should still call the driver
    * for possible GL errors.
    */
   if (!user_buffer_mask ||
       (!no_error &&
        (count <= 0 || instance_count <= 0 ||   /* GL_INVALID_VALUE / no-op */
         ctx->GLThread.inside_begin_end ||      /* GL_INVALID_OPERATION */
         ctx->Dispatch.Current == ctx->Dispatch.ContextLost || /* GL_INVALID_OPERATION */
         ctx->GLThread.ListMode))) {            /* GL_INVALID_OPERATION */
      if (baseinstance == 0 && drawid == 0) {
         int cmd_size = sizeof(struct marshal_cmd_DrawArraysInstanced);
         struct marshal_cmd_DrawArraysInstanced *cmd =
            _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawArraysInstanced, cmd_size);

         cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
         cmd->first = first;
         cmd->count = count;
         cmd->primcount = instance_count;
      } else {
         int cmd_size = sizeof(struct marshal_cmd_DrawArraysInstancedBaseInstanceDrawID);
         struct marshal_cmd_DrawArraysInstancedBaseInstanceDrawID *cmd =
            _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawArraysInstancedBaseInstanceDrawID, cmd_size);

         cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
         cmd->first = first;
         cmd->count = count;
         cmd->instance_count = instance_count;
         cmd->baseinstance = baseinstance;
         cmd->drawid = drawid;
      }
      return;
   }

   /* Upload and draw. */
   struct gl_buffer_object *buffers[VERT_ATTRIB_MAX];
   int offsets[VERT_ATTRIB_MAX];

   if (!upload_vertices(ctx, user_buffer_mask, first, count, baseinstance,
                        instance_count, buffers, offsets))
      return; /* the error is set by upload_vertices */

   unsigned num_buffers = util_bitcount(user_buffer_mask);
   int buffers_size = num_buffers * sizeof(buffers[0]);
   int offsets_size = num_buffers * sizeof(int);
   int cmd_size = sizeof(struct marshal_cmd_DrawArraysUserBuf) +
                  buffers_size + offsets_size;
   struct marshal_cmd_DrawArraysUserBuf *cmd;

   cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawArraysUserBuf,
                                         cmd_size);
   cmd->num_slots = align(cmd_size, 8) / 8;
   cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
   cmd->first = first;
   cmd->count = count;
   cmd->instance_count = instance_count;
   cmd->baseinstance = baseinstance;
   cmd->drawid = drawid;
   cmd->user_buffer_mask = user_buffer_mask;

   if (user_buffer_mask) {
      char *variable_data = (char*)(cmd + 1);
      memcpy(variable_data, buffers, buffers_size);
      variable_data += buffers_size;
      memcpy(variable_data, offsets, offsets_size);
   }
}

/* MultiDrawArrays with user buffers. */
struct marshal_cmd_MultiDrawArraysUserBuf
{
   struct marshal_cmd_base cmd_base;
   GLenum8 mode;
   uint16_t num_slots;
   GLsizei draw_count;
   GLuint user_buffer_mask;
};

uint32_t
_mesa_unmarshal_MultiDrawArraysUserBuf(struct gl_context *ctx,
                                       const struct marshal_cmd_MultiDrawArraysUserBuf *restrict cmd)
{
   const GLenum mode = cmd->mode;
   const GLsizei draw_count = cmd->draw_count;
   const GLsizei real_draw_count = MAX2(draw_count, 0);
   const GLuint user_buffer_mask = cmd->user_buffer_mask;

   const char *variable_data = (const char *)(cmd + 1);
   const GLint *first = (GLint *)variable_data;
   variable_data += sizeof(GLint) * real_draw_count;
   const GLsizei *count = (GLsizei *)variable_data;

   /* Bind uploaded buffers if needed. */
   if (user_buffer_mask) {
      variable_data += sizeof(GLsizei) * real_draw_count;
      const int *offsets = (const int *)variable_data;
      variable_data += sizeof(int) * util_bitcount(user_buffer_mask);

      /* Align for pointers. */
      if ((uintptr_t)variable_data % sizeof(uintptr_t))
         variable_data += 4;

      struct gl_buffer_object **buffers = (struct gl_buffer_object **)variable_data;

      _mesa_InternalBindVertexBuffers(ctx, buffers, offsets, user_buffer_mask);
   }

   CALL_MultiDrawArrays(ctx->Dispatch.Current,
                        (mode, first, count, draw_count));
   return cmd->num_slots;
}

void GLAPIENTRY
_mesa_marshal_MultiDrawArrays(GLenum mode, const GLint *first,
                              const GLsizei *count, GLsizei draw_count)
{
   GET_CURRENT_CONTEXT(ctx);

   if (unlikely(ctx->GLThread.ListMode)) {
      _mesa_glthread_finish_before(ctx, "MultiDrawArrays");
      CALL_MultiDrawArrays(ctx->Dispatch.Current,
                           (mode, first, count, draw_count));
      return;
   }

   struct gl_buffer_object *buffers[VERT_ATTRIB_MAX];
   int offsets[VERT_ATTRIB_MAX];
   unsigned user_buffer_mask =
      _mesa_is_desktop_gl_core(ctx) || draw_count <= 0 ||
      ctx->Dispatch.Current == ctx->Dispatch.ContextLost ||
      ctx->GLThread.inside_begin_end ? 0 : get_user_buffer_mask(ctx);

   if (user_buffer_mask) {
      unsigned min_index = ~0;
      unsigned max_index_exclusive = 0;

      for (int i = 0; i < draw_count; i++) {
         GLsizei vertex_count = count[i];

         if (vertex_count < 0) {
            /* This will just call the driver to set the GL error. */
            min_index = ~0;
            break;
         }
         if (vertex_count == 0)
            continue;

         min_index = MIN2(min_index, first[i]);
         max_index_exclusive = MAX2(max_index_exclusive, first[i] + vertex_count);
      }

      if (min_index >= max_index_exclusive) {
         /* Nothing to do, but call the driver to set possible GL errors. */
         user_buffer_mask = 0;
      } else {
         /* Upload. */
         unsigned num_vertices = max_index_exclusive - min_index;

         if (!upload_vertices(ctx, user_buffer_mask, min_index, num_vertices,
                              0, 1, buffers, offsets))
            return; /* the error is set by upload_vertices */
      }
   }

   /* Add the call into the batch buffer. */
   int real_draw_count = MAX2(draw_count, 0);
   int first_size = sizeof(GLint) * real_draw_count;
   int count_size = sizeof(GLsizei) * real_draw_count;
   unsigned num_buffers = util_bitcount(user_buffer_mask);
   int buffers_size = num_buffers * sizeof(buffers[0]);
   int offsets_size = num_buffers * sizeof(int);
   int cmd_size = sizeof(struct marshal_cmd_MultiDrawArraysUserBuf) +
                  first_size + count_size + buffers_size + offsets_size;
   struct marshal_cmd_MultiDrawArraysUserBuf *cmd;

   /* Make sure cmd can fit in the batch buffer */
   if (cmd_size <= MARSHAL_MAX_CMD_SIZE) {
      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_MultiDrawArraysUserBuf,
                                            cmd_size);
      cmd->num_slots = align(cmd_size, 8) / 8;
      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->draw_count = draw_count;
      cmd->user_buffer_mask = user_buffer_mask;

      char *variable_data = (char*)(cmd + 1);
      memcpy(variable_data, first, first_size);
      variable_data += first_size;
      memcpy(variable_data, count, count_size);

      if (user_buffer_mask) {
         variable_data += count_size;
         memcpy(variable_data, offsets, offsets_size);
         variable_data += offsets_size;

         /* Align for pointers. */
         if ((uintptr_t)variable_data % sizeof(uintptr_t))
            variable_data += 4;

         memcpy(variable_data, buffers, buffers_size);
      }
   } else {
      /* The call is too large, so sync and execute the unmarshal code here. */
      _mesa_glthread_finish_before(ctx, "MultiDrawArrays");

      if (user_buffer_mask) {
         _mesa_InternalBindVertexBuffers(ctx, buffers, offsets,
                                         user_buffer_mask);
      }

      CALL_MultiDrawArrays(ctx->Dispatch.Current,
                           (mode, first, count, draw_count));
   }
}

uint32_t
_mesa_unmarshal_DrawElements(struct gl_context *ctx,
                             const struct marshal_cmd_DrawElements *restrict cmd)
{
   const GLenum mode = cmd->mode;
   const GLsizei count = cmd->count;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   const GLvoid *indices = cmd->indices;

   CALL_DrawElements(ctx->Dispatch.Current, (mode, count, type, indices));
   return align(sizeof(*cmd), 8) / 8;
}

uint32_t
_mesa_unmarshal_DrawElementsPacked(struct gl_context *ctx,
                                   const struct marshal_cmd_DrawElementsPacked *restrict cmd)
{
   const GLenum mode = cmd->mode;
   const GLsizei count = cmd->count;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   const GLvoid *indices = (void*)(uintptr_t)cmd->indices;

   CALL_DrawElements(ctx->Dispatch.Current, (mode, count, type, indices));
   return align(sizeof(*cmd), 8) / 8;
}

uint32_t
_mesa_unmarshal_DrawElementsInstancedBaseVertex(struct gl_context *ctx,
                                                const struct marshal_cmd_DrawElementsInstancedBaseVertex *restrict cmd)
{
   const GLenum mode = cmd->mode;
   const GLsizei count = cmd->count;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   const GLvoid *indices = cmd->indices;
   const GLsizei instance_count = cmd->primcount;
   const GLint basevertex = cmd->basevertex;

   CALL_DrawElementsInstancedBaseVertex(ctx->Dispatch.Current,
                                        (mode, count, type, indices,
                                         instance_count, basevertex));
   return align(sizeof(*cmd), 8) / 8;
}

uint32_t
_mesa_unmarshal_DrawElementsInstancedBaseInstance(struct gl_context *ctx,
                                                  const struct marshal_cmd_DrawElementsInstancedBaseInstance *restrict cmd)
{
   const GLenum mode = cmd->mode;
   const GLsizei count = cmd->count;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   const GLvoid *indices = cmd->indices;
   const GLsizei instance_count = cmd->primcount;
   const GLint baseinstance = cmd->baseinstance;

   CALL_DrawElementsInstancedBaseInstance(ctx->Dispatch.Current,
                                          (mode, count, type, indices,
                                           instance_count, baseinstance));
   return align(sizeof(*cmd), 8) / 8;
}

uint32_t
_mesa_unmarshal_DrawElementsInstancedBaseVertexBaseInstanceDrawID(struct gl_context *ctx,
                                                                  const struct marshal_cmd_DrawElementsInstancedBaseVertexBaseInstanceDrawID *restrict cmd)
{
   const GLenum mode = cmd->mode;
   const GLsizei count = cmd->count;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   const GLvoid *indices = cmd->indices;
   const GLsizei instance_count = cmd->instance_count;
   const GLint basevertex = cmd->basevertex;
   const GLuint baseinstance = cmd->baseinstance;

   ctx->DrawID = cmd->drawid;
   CALL_DrawElementsInstancedBaseVertexBaseInstance(ctx->Dispatch.Current,
                                                    (mode, count, type, indices,
                                                     instance_count, basevertex,
                                                     baseinstance));
   ctx->DrawID = 0;

   return align(sizeof(*cmd), 8) / 8;
}

uint32_t
_mesa_unmarshal_DrawElementsUserBuf(struct gl_context *ctx,
                                    const struct marshal_cmd_DrawElementsUserBuf *restrict cmd)
{
   const GLuint user_buffer_mask = cmd->user_buffer_mask;

   /* Bind uploaded buffers if needed. */
   if (user_buffer_mask) {
      struct gl_buffer_object **buffers = (struct gl_buffer_object **)(cmd + 1);
      const int *offsets = (const int *)(buffers + util_bitcount(user_buffer_mask));

      _mesa_InternalBindVertexBuffers(ctx, buffers, offsets, user_buffer_mask);
   }

   /* Draw. */
   CALL_DrawElementsUserBuf(ctx->Dispatch.Current, (cmd));

   struct gl_buffer_object *index_buffer = cmd->index_buffer;
   _mesa_reference_buffer_object(ctx, &index_buffer, NULL);
   return cmd->num_slots;
}

uint32_t
_mesa_unmarshal_DrawElementsUserBufPacked(struct gl_context *ctx,
                                    const struct marshal_cmd_DrawElementsUserBufPacked *restrict cmd)
{
   const GLuint user_buffer_mask = cmd->user_buffer_mask;

   /* Bind uploaded buffers if needed. */
   if (user_buffer_mask) {
      struct gl_buffer_object **buffers = (struct gl_buffer_object **)(cmd + 1);
      const int *offsets = (const int *)(buffers + util_bitcount(user_buffer_mask));

      _mesa_InternalBindVertexBuffers(ctx, buffers, offsets, user_buffer_mask);
   }

   /* Draw. */
   CALL_DrawElementsUserBufPacked(ctx->Dispatch.Current, (cmd));

   struct gl_buffer_object *index_buffer = cmd->index_buffer;
   _mesa_reference_buffer_object(ctx, &index_buffer, NULL);
   return cmd->num_slots;
}

static inline bool
should_convert_to_begin_end(struct gl_context *ctx, unsigned count,
                            unsigned num_upload_vertices,
                            unsigned instance_count, struct glthread_vao *vao)
{
   /* Some of these are limitations of _mesa_glthread_UnrollDrawElements.
    * Others prevent syncing, such as disallowing buffer objects because we
    * can't map them without syncing.
    */
   return ctx->API == API_OPENGL_COMPAT &&
          util_is_vbo_upload_ratio_too_large(count, num_upload_vertices) &&
          instance_count == 1 &&                /* no instancing */
          vao->CurrentElementBufferName == 0 && /* only user indices */
          !ctx->GLThread._PrimitiveRestart &&   /* no primitive restart */
          vao->UserPointerMask == vao->BufferEnabled && /* no VBOs */
          !(vao->NonZeroDivisorMask & vao->BufferEnabled); /* no instanced attribs */
}

static ALWAYS_INLINE void
draw_elements(GLuint drawid, GLenum mode, GLsizei count, GLenum type,
              const GLvoid *indices, GLsizei instance_count, GLint basevertex,
              GLuint baseinstance, bool index_bounds_valid, GLuint min_index,
              GLuint max_index, bool compiled_into_dlist, bool no_error)
{
   GET_CURRENT_CONTEXT(ctx);

   /* The main benefit of no_error is that we can discard no-op draws
    * immediately. These are plentiful in Viewperf2020/Catia1.
    */
   if (no_error && (count <= 0 || instance_count <= 0))
      return;

   if (unlikely(compiled_into_dlist && ctx->GLThread.ListMode)) {
      _mesa_glthread_finish_before(ctx, "DrawElements");

      /* Only use the ones that are compiled into display lists. */
      if (basevertex) {
         CALL_DrawElementsBaseVertex(ctx->Dispatch.Current,
                                     (mode, count, type, indices, basevertex));
      } else if (index_bounds_valid) {
         CALL_DrawRangeElements(ctx->Dispatch.Current,
                                (mode, min_index, max_index, count, type, indices));
      } else {
         CALL_DrawElements(ctx->Dispatch.Current, (mode, count, type, indices));
      }
      return;
   }

   if (unlikely(!no_error && index_bounds_valid && max_index < min_index)) {
      _mesa_marshal_InternalSetError(GL_INVALID_VALUE);
      return;
   }

   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned user_buffer_mask =
      _mesa_is_desktop_gl_core(ctx) ? 0 : get_user_buffer_mask(ctx);
   bool has_user_indices = vao->CurrentElementBufferName == 0 && indices;

   /* Fast path when nothing needs to be done.
    *
    * This is also an error path. Zero counts should still call the driver
    * for possible GL errors.
    */
   if ((!user_buffer_mask && !has_user_indices) ||
       (!no_error &&
        /* zeros are discarded for no_error at the beginning */
        (count <= 0 || instance_count <= 0 ||   /* GL_INVALID_VALUE / no-op */
         !_mesa_is_index_type_valid(type) ||    /* GL_INVALID_VALUE */
         ctx->Dispatch.Current == ctx->Dispatch.ContextLost || /* GL_INVALID_OPERATION */
         ctx->GLThread.inside_begin_end ||      /* GL_INVALID_OPERATION */
         ctx->GLThread.ListMode ||              /* GL_INVALID_OPERATION */
         mode >= 32 || !((1u << mode) & ctx->SupportedPrimMask) /* GL_INVALID_ENUM */
         ))) {
      if (drawid == 0 && baseinstance == 0) {
         if (instance_count == 1 && basevertex == 0) {
            if ((count & 0xffff) == count && (uintptr_t)indices <= UINT16_MAX) {
               /* Packed version of DrawElements: 16-bit count and 16-bit index offset,
                * reducing the call size by 8 bytes.
                * This is the most common case in Viewperf2020/Catia1.
                */
               int cmd_size = sizeof(struct marshal_cmd_DrawElementsPacked);
               struct marshal_cmd_DrawElementsPacked *cmd =
                     _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawElementsPacked, cmd_size);

               cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
               cmd->type = encode_index_type(type);
               cmd->count = count;
               cmd->indices = (uintptr_t)indices;
            } else {
               int cmd_size = sizeof(struct marshal_cmd_DrawElements);
               struct marshal_cmd_DrawElements *cmd =
                     _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawElements, cmd_size);

               cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
               cmd->type = encode_index_type(type);
               cmd->count = count;
               cmd->indices = indices;
            }
         } else {
            int cmd_size = sizeof(struct marshal_cmd_DrawElementsInstancedBaseVertex);
            struct marshal_cmd_DrawElementsInstancedBaseVertex *cmd =
                  _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawElementsInstancedBaseVertex, cmd_size);

            cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
            cmd->type = encode_index_type(type);
            cmd->count = count;
            cmd->primcount = instance_count;
            cmd->basevertex = basevertex;
            cmd->indices = indices;
         }
      } else if (drawid == 0 && basevertex == 0) {
         int cmd_size = sizeof(struct marshal_cmd_DrawElementsInstancedBaseInstance);
         struct marshal_cmd_DrawElementsInstancedBaseInstance *cmd =
               _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawElementsInstancedBaseInstance, cmd_size);

         cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
         cmd->type = encode_index_type(type);
         cmd->count = count;
         cmd->primcount = instance_count;
         cmd->baseinstance = baseinstance;
         cmd->indices = indices;
      } else {
         int cmd_size = sizeof(struct marshal_cmd_DrawElementsInstancedBaseVertexBaseInstanceDrawID);
         struct marshal_cmd_DrawElementsInstancedBaseVertexBaseInstanceDrawID *cmd =
            _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawElementsInstancedBaseVertexBaseInstanceDrawID, cmd_size);

         cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
         cmd->type = encode_index_type(type);
         cmd->count = count;
         cmd->instance_count = instance_count;
         cmd->basevertex = basevertex;
         cmd->baseinstance = baseinstance;
         cmd->drawid = drawid;
         cmd->indices = indices;
      }
      return;
   }

   bool need_index_bounds = user_buffer_mask & ~vao->NonZeroDivisorMask;
   unsigned index_size = get_index_size(type);

   if (need_index_bounds && !index_bounds_valid) {
      /* Compute the index bounds. */
      if (has_user_indices) {
         min_index = ~0;
         max_index = 0;
         vbo_get_minmax_index_mapped(count, index_size,
                                     ctx->GLThread._RestartIndex[index_size - 1],
                                     ctx->GLThread._PrimitiveRestart, indices,
                                     &min_index, &max_index);
      } else {
         /* Indices in a buffer. */
         _mesa_glthread_finish_before(ctx, "DrawElements - need index bounds");
         vbo_get_minmax_index(ctx, ctx->Array.VAO->IndexBufferObj,
                              NULL, (intptr_t)indices, count, index_size,
                              ctx->GLThread._PrimitiveRestart,
                              ctx->GLThread._RestartIndex[index_size - 1],
                              &min_index, &max_index);
      }
      index_bounds_valid = true;
   }

   unsigned start_vertex = min_index + basevertex;
   unsigned num_vertices = max_index + 1 - min_index;

   /* If the vertex range to upload is much greater than the vertex count (e.g.
    * only 3 vertices with indices 0, 1, 999999), uploading the whole range
    * would take too much time. If all buffers are user buffers, have glthread
    * fetch all indices and vertices and convert the draw into glBegin/glEnd.
    * For such pathological cases, it's the fastest way.
    *
    * The game Cogs benefits from this - its FPS increases from 0 to 197.
    */
   if (should_convert_to_begin_end(ctx, count, num_vertices, instance_count,
                                   vao)) {
      _mesa_glthread_UnrollDrawElements(ctx, mode, count, type, indices,
                                        basevertex);
      return;
   }

   struct gl_buffer_object *buffers[VERT_ATTRIB_MAX];
   int offsets[VERT_ATTRIB_MAX];

   if (user_buffer_mask) {
      if (!upload_vertices(ctx, user_buffer_mask, start_vertex, num_vertices,
                           baseinstance, instance_count, buffers, offsets))
         return; /* the error is set by upload_vertices */
   }

   /* Upload indices. */
   struct gl_buffer_object *index_buffer = NULL;
   if (has_user_indices) {
      index_buffer = upload_indices(ctx, count, index_size, &indices);
      if (!index_buffer)
         return; /* the error is set by upload_indices */
   }

   /* Draw asynchronously. */
   unsigned num_buffers = util_bitcount(user_buffer_mask);
   int buffers_size = num_buffers * sizeof(buffers[0]);
   int offsets_size = num_buffers * sizeof(int);
   char *variable_data;

   if (instance_count == 1 && basevertex == 0 && baseinstance == 0 &&
       drawid == 0 && (count & 0xffff) == count &&
       (uintptr_t)indices <= UINT32_MAX) {
      int cmd_size = sizeof(struct marshal_cmd_DrawElementsUserBufPacked) +
                     buffers_size + offsets_size;
      struct marshal_cmd_DrawElementsUserBufPacked *cmd;

      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawElementsUserBufPacked, cmd_size);
      cmd->num_slots = align(cmd_size, 8) / 8;
      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->type = encode_index_type(type);
      cmd->count = count; /* truncated */
      cmd->indices = (uintptr_t)indices; /* truncated */
      cmd->user_buffer_mask = user_buffer_mask;
      cmd->index_buffer = index_buffer;
      variable_data = (char*)(cmd + 1);
   } else {
      int cmd_size = sizeof(struct marshal_cmd_DrawElementsUserBuf) +
                     buffers_size + offsets_size;
      struct marshal_cmd_DrawElementsUserBuf *cmd;

      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawElementsUserBuf, cmd_size);
      cmd->num_slots = align(cmd_size, 8) / 8;
      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->type = encode_index_type(type);
      cmd->count = count;
      cmd->indices = indices;
      cmd->instance_count = instance_count;
      cmd->basevertex = basevertex;
      cmd->baseinstance = baseinstance;
      cmd->user_buffer_mask = user_buffer_mask;
      cmd->index_buffer = index_buffer;
      cmd->drawid = drawid;
      variable_data = (char*)(cmd + 1);
   }

   if (user_buffer_mask) {
      memcpy(variable_data, buffers, buffers_size);
      variable_data += buffers_size;
      memcpy(variable_data, offsets, offsets_size);
   }
}

struct marshal_cmd_MultiDrawElementsUserBuf
{
   struct marshal_cmd_base cmd_base;
   bool has_base_vertex;
   GLenum8 mode;
   GLindextype type;
   uint16_t num_slots;
   GLsizei draw_count;
   GLuint user_buffer_mask;
   struct gl_buffer_object *index_buffer;
};

uint32_t
_mesa_unmarshal_MultiDrawElementsUserBuf(struct gl_context *ctx,
                                         const struct marshal_cmd_MultiDrawElementsUserBuf *restrict cmd)
{
   const GLsizei draw_count = cmd->draw_count;
   const GLsizei real_draw_count = MAX2(draw_count, 0);
   const GLuint user_buffer_mask = cmd->user_buffer_mask;
   const bool has_base_vertex = cmd->has_base_vertex;

   const char *variable_data = (const char *)(cmd + 1);
   const GLsizei *count = (GLsizei *)variable_data;
   variable_data += sizeof(GLsizei) * real_draw_count;
   const GLsizei *basevertex = NULL;
   if (has_base_vertex) {
      basevertex = (GLsizei *)variable_data;
      variable_data += sizeof(GLsizei) * real_draw_count;
   }
   const int *offsets = NULL;
   if (user_buffer_mask) {
      offsets = (const int *)variable_data;
      variable_data += sizeof(int) * util_bitcount(user_buffer_mask);
   }

   /* Align for pointers. */
   if ((uintptr_t)variable_data % sizeof(uintptr_t))
      variable_data += 4;

   const GLvoid *const *indices = (const GLvoid *const *)variable_data;
   variable_data += sizeof(const GLvoid *const *) * real_draw_count;

   /* Bind uploaded buffers if needed. */
   if (user_buffer_mask) {
      struct gl_buffer_object **buffers = (struct gl_buffer_object **)variable_data;

      _mesa_InternalBindVertexBuffers(ctx, buffers, offsets, user_buffer_mask);
   }

   /* Draw. */
   const GLenum mode = cmd->mode;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   struct gl_buffer_object *index_buffer = cmd->index_buffer;

   CALL_MultiDrawElementsUserBuf(ctx->Dispatch.Current,
                                 ((GLintptr)index_buffer, mode, count, type,
                                  indices, draw_count, basevertex));
   _mesa_reference_buffer_object(ctx, &index_buffer, NULL);
   return cmd->num_slots;
}

static void
multi_draw_elements_async(struct gl_context *ctx, GLenum mode,
                          const GLsizei *count, GLenum type,
                          const GLvoid *const *indices, GLsizei draw_count,
                          const GLsizei *basevertex,
                          struct gl_buffer_object *index_buffer,
                          unsigned user_buffer_mask,
                          struct gl_buffer_object **buffers,
                          const int *offsets)
{
   int real_draw_count = MAX2(draw_count, 0);
   int count_size = sizeof(GLsizei) * real_draw_count;
   int indices_size = sizeof(indices[0]) * real_draw_count;
   int basevertex_size = basevertex ? sizeof(GLsizei) * real_draw_count : 0;
   unsigned num_buffers = util_bitcount(user_buffer_mask);
   int buffers_size = num_buffers * sizeof(buffers[0]);
   int offsets_size = num_buffers * sizeof(int);
   int cmd_size = sizeof(struct marshal_cmd_MultiDrawElementsUserBuf) +
                  count_size + indices_size + basevertex_size + buffers_size +
                  offsets_size;
   struct marshal_cmd_MultiDrawElementsUserBuf *cmd;

   /* Make sure cmd can fit the queue buffer */
   if (cmd_size <= MARSHAL_MAX_CMD_SIZE) {
      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_MultiDrawElementsUserBuf, cmd_size);
      cmd->num_slots = align(cmd_size, 8) / 8;
      cmd->mode = MIN2(mode, 0xff); /* primitive types go from 0 to 14 */
      cmd->type = encode_index_type(type);
      cmd->draw_count = draw_count;
      cmd->user_buffer_mask = user_buffer_mask;
      cmd->index_buffer = index_buffer;
      cmd->has_base_vertex = basevertex != NULL;

      char *variable_data = (char*)(cmd + 1);
      memcpy(variable_data, count, count_size);
      variable_data += count_size;
      if (basevertex) {
         memcpy(variable_data, basevertex, basevertex_size);
         variable_data += basevertex_size;
      }
      if (user_buffer_mask) {
         memcpy(variable_data, offsets, offsets_size);
         variable_data += offsets_size;
      }

      /* Align for pointers. */
      if ((uintptr_t)variable_data % sizeof(uintptr_t))
         variable_data += 4;

      memcpy(variable_data, indices, indices_size);
      variable_data += indices_size;

      if (user_buffer_mask)
         memcpy(variable_data, buffers, buffers_size);
   } else {
      /* The call is too large, so sync and execute the unmarshal code here. */
      _mesa_glthread_finish_before(ctx, "DrawElements");

      /* Bind uploaded buffers if needed. */
      if (user_buffer_mask) {
         _mesa_InternalBindVertexBuffers(ctx, buffers, offsets,
                                         user_buffer_mask);
      }

      /* Draw. */
      CALL_MultiDrawElementsUserBuf(ctx->Dispatch.Current,
                                    ((GLintptr)index_buffer, mode, count,
                                     type, indices, draw_count, basevertex));
      _mesa_reference_buffer_object(ctx, &index_buffer, NULL);
   }
}

void GLAPIENTRY
_mesa_marshal_MultiDrawElementsBaseVertex(GLenum mode, const GLsizei *count,
                                          GLenum type,
                                          const GLvoid *const *indices,
                                          GLsizei draw_count,
                                          const GLsizei *basevertex)
{
   GET_CURRENT_CONTEXT(ctx);

   if (unlikely(ctx->GLThread.ListMode)) {
      _mesa_glthread_finish_before(ctx, "MultiDrawElements");

      if (basevertex) {
         CALL_MultiDrawElementsBaseVertex(ctx->Dispatch.Current,
                                          (mode, count, type, indices, draw_count,
                                           basevertex));
      } else {
         CALL_MultiDrawElements(ctx->Dispatch.Current,
                                (mode, count, type, indices, draw_count));
      }
      return;
   }

   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned user_buffer_mask = 0;
   bool has_user_indices = false;

   /* Non-VBO vertex arrays are used only if this is true.
    * When nothing needs to be uploaded or the draw is no-op or generates
    * a GL error, we don't upload anything.
    */
   if (draw_count > 0 && _mesa_is_index_type_valid(type) &&
       ctx->Dispatch.Current != ctx->Dispatch.ContextLost &&
       !ctx->GLThread.inside_begin_end &&
       !(mode >= 32 || !((1u << mode) & ctx->SupportedPrimMask))) {
      user_buffer_mask = _mesa_is_desktop_gl_core(ctx) ? 0 : get_user_buffer_mask(ctx);
      has_user_indices = vao->CurrentElementBufferName == 0;
   }

   /* Fast path when we don't need to upload anything. */
   if (!user_buffer_mask && !has_user_indices) {
      multi_draw_elements_async(ctx, mode, count, type, indices,
                                draw_count, basevertex, NULL, 0, NULL, NULL);
      return;
   }

   bool need_index_bounds = user_buffer_mask & ~vao->NonZeroDivisorMask;
   unsigned index_size = get_index_size(type);
   unsigned min_index = ~0;
   unsigned max_index = 0;
   unsigned total_count = 0;
   unsigned num_vertices = 0;

   /* This is always true if there is per-vertex data that needs to be
    * uploaded.
    */
   if (need_index_bounds) {
      bool synced = false;

      /* Compute the index bounds. */
      for (unsigned i = 0; i < draw_count; i++) {
         GLsizei vertex_count = count[i];

         if (vertex_count < 0) {
            /* Just call the driver to set the error. */
            multi_draw_elements_async(ctx, mode, count, type, indices, draw_count,
                                      basevertex, NULL, 0, NULL, NULL);
            return;
         }
         if (vertex_count == 0)
            continue;

         unsigned min = ~0, max = 0;
         if (has_user_indices) {
            vbo_get_minmax_index_mapped(vertex_count, index_size,
                                        ctx->GLThread._RestartIndex[index_size - 1],
                                        ctx->GLThread._PrimitiveRestart, indices[i],
                                        &min, &max);
         } else {
            if (!synced) {
               _mesa_glthread_finish_before(ctx, "MultiDrawElements - need index bounds");
               synced = true;
            }
            vbo_get_minmax_index(ctx, ctx->Array.VAO->IndexBufferObj,
                                 NULL, (intptr_t)indices[i], vertex_count,
                                 index_size, ctx->GLThread._PrimitiveRestart,
                                 ctx->GLThread._RestartIndex[index_size - 1],
                                 &min, &max);
         }

         if (basevertex) {
            min += basevertex[i];
            max += basevertex[i];
         }
         min_index = MIN2(min_index, min);
         max_index = MAX2(max_index, max);
         total_count += vertex_count;
      }

      num_vertices = max_index + 1 - min_index;

      if (total_count == 0 || num_vertices == 0) {
         /* Nothing to do, but call the driver to set possible GL errors. */
         multi_draw_elements_async(ctx, mode, count, type, indices, draw_count,
                                   basevertex, NULL, 0, NULL, NULL);
         return;
      }
   } else if (has_user_indices) {
      /* Only compute total_count for the upload of indices. */
      for (unsigned i = 0; i < draw_count; i++) {
         GLsizei vertex_count = count[i];

         if (vertex_count < 0) {
            /* Just call the driver to set the error. */
            multi_draw_elements_async(ctx, mode, count, type, indices, draw_count,
                                      basevertex, NULL, 0, NULL, NULL);
            return;
         }
         if (vertex_count == 0)
            continue;

         total_count += vertex_count;
      }

      if (total_count == 0) {
         /* Nothing to do, but call the driver to set possible GL errors. */
         multi_draw_elements_async(ctx, mode, count, type, indices, draw_count,
                                   basevertex, NULL, 0, NULL, NULL);
         return;
      }
   }

   /* Upload vertices. */
   struct gl_buffer_object *buffers[VERT_ATTRIB_MAX];
   int offsets[VERT_ATTRIB_MAX];

   if (user_buffer_mask) {
      if (!upload_vertices(ctx, user_buffer_mask, min_index, num_vertices,
                           0, 1, buffers, offsets))
         return; /* the error is set by upload_vertices */
   }

   /* Upload indices. */
   struct gl_buffer_object *index_buffer = NULL;
   if (has_user_indices) {
      const GLvoid **out_indices = alloca(sizeof(indices[0]) * draw_count);

      index_buffer = upload_multi_indices(ctx, total_count, index_size,
                                          draw_count, count, indices,
                                          out_indices);
      if (!index_buffer)
         return; /* the error is set by upload_multi_indices */

      indices = out_indices;
   }

   /* Draw asynchronously. */
   multi_draw_elements_async(ctx, mode, count, type, indices, draw_count,
                             basevertex, index_buffer, user_buffer_mask,
                             buffers, offsets);
}

void GLAPIENTRY
_mesa_marshal_MultiModeDrawArraysIBM(const GLenum *mode, const GLint *first,
                                     const GLsizei *count, GLsizei primcount,
                                     GLint modestride)
{
   for (int i = 0 ; i < primcount; i++) {
      if (count[i] > 0) {
         GLenum m = *((GLenum *)((GLubyte *)mode + i * modestride));
         _mesa_marshal_DrawArrays(m, first[i], count[i]);
      }
   }
}

void GLAPIENTRY
_mesa_marshal_MultiModeDrawElementsIBM(const GLenum *mode,
                                       const GLsizei *count, GLenum type,
                                       const GLvoid * const *indices,
                                       GLsizei primcount, GLint modestride)
{
   for (int i = 0 ; i < primcount; i++) {
      if (count[i] > 0) {
         GLenum m = *((GLenum *)((GLubyte *)mode + i * modestride));
         _mesa_marshal_DrawElements(m, count[i], type, indices[i]);
      }
   }
}

static const void *
map_draw_indirect_params(struct gl_context *ctx, GLintptr offset,
                         unsigned count, unsigned stride)
{
   struct gl_buffer_object *obj = ctx->DrawIndirectBuffer;

   if (!obj)
      return (void*)offset;

   return _mesa_bufferobj_map_range(ctx, offset,
                                    MIN2((size_t)count * stride, obj->Size),
                                    GL_MAP_READ_BIT, obj, MAP_INTERNAL);
}

static void
unmap_draw_indirect_params(struct gl_context *ctx)
{
   if (ctx->DrawIndirectBuffer)
      _mesa_bufferobj_unmap(ctx, ctx->DrawIndirectBuffer, MAP_INTERNAL);
}

static unsigned
read_draw_indirect_count(struct gl_context *ctx, GLintptr offset)
{
   unsigned result = 0;

   if (ctx->ParameterBuffer) {
      _mesa_bufferobj_get_subdata(ctx, offset, sizeof(result), &result,
                                  ctx->ParameterBuffer);
   }
   return result;
}

static void
lower_draw_arrays_indirect(struct gl_context *ctx, GLenum mode,
                           GLintptr indirect, GLsizei stride,
                           unsigned draw_count)
{
   /* If <stride> is zero, the elements are tightly packed. */
   if (stride == 0)
      stride = 4 * sizeof(GLuint);      /* sizeof(DrawArraysIndirectCommand) */

   const uint32_t *params =
      map_draw_indirect_params(ctx, indirect, draw_count, stride);

   for (unsigned i = 0; i < draw_count; i++) {
      draw_arrays(i, mode,
                  params[i * stride / 4 + 2],
                  params[i * stride / 4 + 0],
                  params[i * stride / 4 + 1],
                  params[i * stride / 4 + 3], false, false);
   }

   unmap_draw_indirect_params(ctx);
}

static void
lower_draw_elements_indirect(struct gl_context *ctx, GLenum mode, GLenum type,
                             GLintptr indirect, GLsizei stride,
                             unsigned draw_count)
{
   /* If <stride> is zero, the elements are tightly packed. */
   if (stride == 0)
      stride = 5 * sizeof(GLuint);      /* sizeof(DrawArraysIndirectCommand) */

   const uint32_t *params =
      map_draw_indirect_params(ctx, indirect, draw_count, stride);

   for (unsigned i = 0; i < draw_count; i++) {
      draw_elements(i, mode,
                    params[i * stride / 4 + 0],
                    type,
                    (GLvoid*)((uintptr_t)params[i * stride / 4 + 2] *
                              get_index_size(type)),
                    params[i * stride / 4 + 1],
                    params[i * stride / 4 + 3],
                    params[i * stride / 4 + 4],
                    false, 0, 0, false, false);
   }
   unmap_draw_indirect_params(ctx);
}

static inline bool
draw_indirect_async_allowed(struct gl_context *ctx, unsigned user_buffer_mask)
{
   return ctx->API != API_OPENGL_COMPAT ||
          /* This will just generate GL_INVALID_OPERATION, as it should. */
          ctx->GLThread.inside_begin_end ||
          ctx->GLThread.ListMode ||
          ctx->Dispatch.Current == ctx->Dispatch.ContextLost ||
          /* If the DrawIndirect buffer is bound, it behaves like profile != compat
           * if there are no user VBOs. */
          (ctx->GLThread.CurrentDrawIndirectBufferName && !user_buffer_mask);
}

uint32_t
_mesa_unmarshal_DrawArraysIndirect(struct gl_context *ctx,
                                   const struct marshal_cmd_DrawArraysIndirect *cmd)
{
   GLenum mode = cmd->mode;
   const GLvoid * indirect = cmd->indirect;

   CALL_DrawArraysIndirect(ctx->Dispatch.Current, (mode, indirect));

   return align(sizeof(struct marshal_cmd_DrawArraysIndirect), 8) / 8;
}

void GLAPIENTRY
_mesa_marshal_DrawArraysIndirect(GLenum mode, const GLvoid *indirect)
{
   GET_CURRENT_CONTEXT(ctx);
   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned user_buffer_mask =
      _mesa_is_gles31(ctx) ? 0 : vao->UserPointerMask & vao->BufferEnabled;

   if (draw_indirect_async_allowed(ctx, user_buffer_mask)) {
      int cmd_size = sizeof(struct marshal_cmd_DrawArraysIndirect);
      struct marshal_cmd_DrawArraysIndirect *cmd;

      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawArraysIndirect, cmd_size);
      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->indirect = indirect;
      return;
   }

   _mesa_glthread_finish_before(ctx, "DrawArraysIndirect");
   lower_draw_arrays_indirect(ctx, mode, (GLintptr)indirect, 0, 1);
}

uint32_t
_mesa_unmarshal_DrawElementsIndirect(struct gl_context *ctx,
                                     const struct marshal_cmd_DrawElementsIndirect *cmd)
{
   GLenum mode = cmd->mode;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   const GLvoid * indirect = cmd->indirect;

   CALL_DrawElementsIndirect(ctx->Dispatch.Current, (mode, type, indirect));
   return align(sizeof(struct marshal_cmd_DrawElementsIndirect), 8) / 8;
}

void GLAPIENTRY
_mesa_marshal_DrawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect)
{
   GET_CURRENT_CONTEXT(ctx);
   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned user_buffer_mask =
      _mesa_is_gles31(ctx) ? 0 : vao->UserPointerMask & vao->BufferEnabled;

   if (draw_indirect_async_allowed(ctx, user_buffer_mask) ||
       !_mesa_is_index_type_valid(type)) {
      int cmd_size = sizeof(struct marshal_cmd_DrawElementsIndirect);
      struct marshal_cmd_DrawElementsIndirect *cmd;

      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_DrawElementsIndirect, cmd_size);
      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->type = encode_index_type(type);
      cmd->indirect = indirect;
      return;
   }

   _mesa_glthread_finish_before(ctx, "DrawElementsIndirect");
   lower_draw_elements_indirect(ctx, mode, type, (GLintptr)indirect, 0, 1);
}

uint32_t
_mesa_unmarshal_MultiDrawArraysIndirect(struct gl_context *ctx,
                                        const struct marshal_cmd_MultiDrawArraysIndirect *cmd)
{
   GLenum mode = cmd->mode;
   const GLvoid * indirect = cmd->indirect;
   GLsizei primcount = cmd->primcount;
   GLsizei stride = cmd->stride;

   CALL_MultiDrawArraysIndirect(ctx->Dispatch.Current,
                                (mode, indirect, primcount, stride));
   return align(sizeof(struct marshal_cmd_MultiDrawArraysIndirect), 8) / 8;
}

void GLAPIENTRY
_mesa_marshal_MultiDrawArraysIndirect(GLenum mode, const GLvoid *indirect,
                                      GLsizei primcount, GLsizei stride)
{
   GET_CURRENT_CONTEXT(ctx);
   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned user_buffer_mask =
      _mesa_is_gles31(ctx) ? 0 : vao->UserPointerMask & vao->BufferEnabled;

   if (draw_indirect_async_allowed(ctx, user_buffer_mask) ||
       primcount <= 0) {
      int cmd_size = sizeof(struct marshal_cmd_MultiDrawArraysIndirect);
      struct marshal_cmd_MultiDrawArraysIndirect *cmd;

      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_MultiDrawArraysIndirect,
                                            cmd_size);
      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->indirect = indirect;
      cmd->primcount = primcount;
      cmd->stride = stride;
      return;
   }

   /* Lower the draw to direct due to non-VBO vertex arrays. */
   _mesa_glthread_finish_before(ctx, "MultiDrawArraysIndirect");
   lower_draw_arrays_indirect(ctx, mode, (GLintptr)indirect, stride, primcount);
}

uint32_t
_mesa_unmarshal_MultiDrawElementsIndirect(struct gl_context *ctx,
                                          const struct marshal_cmd_MultiDrawElementsIndirect *cmd)
{
   GLenum mode = cmd->mode;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   const GLvoid * indirect = cmd->indirect;
   GLsizei primcount = cmd->primcount;
   GLsizei stride = cmd->stride;

   CALL_MultiDrawElementsIndirect(ctx->Dispatch.Current,
                                  (mode, type, indirect, primcount, stride));
   return align(sizeof(struct marshal_cmd_MultiDrawElementsIndirect), 8) / 8;
}

void GLAPIENTRY
_mesa_marshal_MultiDrawElementsIndirect(GLenum mode, GLenum type,
                                        const GLvoid *indirect,
                                        GLsizei primcount, GLsizei stride)
{
   GET_CURRENT_CONTEXT(ctx);
   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned user_buffer_mask =
      _mesa_is_gles31(ctx) ? 0 : vao->UserPointerMask & vao->BufferEnabled;

   if (draw_indirect_async_allowed(ctx, user_buffer_mask) ||
       primcount <= 0 ||
       !_mesa_is_index_type_valid(type)) {
      int cmd_size = sizeof(struct marshal_cmd_MultiDrawElementsIndirect);
      struct marshal_cmd_MultiDrawElementsIndirect *cmd;

      cmd = _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_MultiDrawElementsIndirect,
                                            cmd_size);
      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->type = encode_index_type(type);
      cmd->indirect = indirect;
      cmd->primcount = primcount;
      cmd->stride = stride;
      return;
   }

   /* Lower the draw to direct due to non-VBO vertex arrays. */
   _mesa_glthread_finish_before(ctx, "MultiDrawElementsIndirect");
   lower_draw_elements_indirect(ctx, mode, type, (GLintptr)indirect, stride,
                                primcount);
}

uint32_t
_mesa_unmarshal_MultiDrawArraysIndirectCountARB(struct gl_context *ctx,
                                                const struct marshal_cmd_MultiDrawArraysIndirectCountARB *cmd)
{
   GLenum mode = cmd->mode;
   GLintptr indirect = cmd->indirect;
   GLintptr drawcount = cmd->drawcount;
   GLsizei maxdrawcount = cmd->maxdrawcount;
   GLsizei stride = cmd->stride;

   CALL_MultiDrawArraysIndirectCountARB(ctx->Dispatch.Current,
                                        (mode, indirect, drawcount,
                                         maxdrawcount, stride));
   return align(sizeof(struct marshal_cmd_MultiDrawArraysIndirectCountARB), 8) / 8;
}

void GLAPIENTRY
_mesa_marshal_MultiDrawArraysIndirectCountARB(GLenum mode, GLintptr indirect,
                                              GLintptr drawcount,
                                              GLsizei maxdrawcount,
                                              GLsizei stride)
{
   GET_CURRENT_CONTEXT(ctx);
   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned user_buffer_mask =
      _mesa_is_gles31(ctx) ? 0 : vao->UserPointerMask & vao->BufferEnabled;

   if (draw_indirect_async_allowed(ctx, user_buffer_mask) ||
       /* This will just generate GL_INVALID_OPERATION because Draw*IndirectCount
        * functions forbid a user indirect buffer in the Compat profile. */
       !ctx->GLThread.CurrentDrawIndirectBufferName) {
      int cmd_size =
         sizeof(struct marshal_cmd_MultiDrawArraysIndirectCountARB);
      struct marshal_cmd_MultiDrawArraysIndirectCountARB *cmd =
         _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_MultiDrawArraysIndirectCountARB,
                                         cmd_size);

      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->indirect = indirect;
      cmd->drawcount = drawcount;
      cmd->maxdrawcount = maxdrawcount;
      cmd->stride = stride;
      return;
   }

   /* Lower the draw to direct due to non-VBO vertex arrays. */
   _mesa_glthread_finish_before(ctx, "MultiDrawArraysIndirectCountARB");
   lower_draw_arrays_indirect(ctx, mode, indirect, stride,
                              read_draw_indirect_count(ctx, drawcount));
}

uint32_t
_mesa_unmarshal_MultiDrawElementsIndirectCountARB(struct gl_context *ctx,
                                                  const struct marshal_cmd_MultiDrawElementsIndirectCountARB *cmd)
{
   GLenum mode = cmd->mode;
   const GLenum type = _mesa_decode_index_type(cmd->type);
   GLintptr indirect = cmd->indirect;
   GLintptr drawcount = cmd->drawcount;
   GLsizei maxdrawcount = cmd->maxdrawcount;
   GLsizei stride = cmd->stride;

   CALL_MultiDrawElementsIndirectCountARB(ctx->Dispatch.Current, (mode, type, indirect, drawcount, maxdrawcount, stride));

   return align(sizeof(struct marshal_cmd_MultiDrawElementsIndirectCountARB), 8) / 8;
}

void GLAPIENTRY
_mesa_marshal_MultiDrawElementsIndirectCountARB(GLenum mode, GLenum type,
                                                GLintptr indirect,
                                                GLintptr drawcount,
                                                GLsizei maxdrawcount,
                                                GLsizei stride)
{
   GET_CURRENT_CONTEXT(ctx);
   struct glthread_vao *vao = ctx->GLThread.CurrentVAO;
   unsigned user_buffer_mask =
      _mesa_is_gles31(ctx) ? 0 : vao->UserPointerMask & vao->BufferEnabled;

   if (draw_indirect_async_allowed(ctx, user_buffer_mask) ||
       /* This will just generate GL_INVALID_OPERATION because Draw*IndirectCount
        * functions forbid a user indirect buffer in the Compat profile. */
       !ctx->GLThread.CurrentDrawIndirectBufferName ||
       !_mesa_is_index_type_valid(type)) {
      int cmd_size = sizeof(struct marshal_cmd_MultiDrawElementsIndirectCountARB);
      struct marshal_cmd_MultiDrawElementsIndirectCountARB *cmd =
         _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_MultiDrawElementsIndirectCountARB, cmd_size);

      cmd->mode = MIN2(mode, 0xff); /* clamped to 0xff (invalid enum) */
      cmd->type = encode_index_type(type);
      cmd->indirect = indirect;
      cmd->drawcount = drawcount;
      cmd->maxdrawcount = maxdrawcount;
      cmd->stride = stride;
      return;
   }

   /* Lower the draw to direct due to non-VBO vertex arrays. */
   _mesa_glthread_finish_before(ctx, "MultiDrawElementsIndirectCountARB");
   lower_draw_elements_indirect(ctx, mode, type, indirect, stride,
                                read_draw_indirect_count(ctx, drawcount));
}

void GLAPIENTRY
_mesa_marshal_DrawArrays(GLenum mode, GLint first, GLsizei count)
{
   draw_arrays(0, mode, first, count, 1, 0, true, false);
}

void GLAPIENTRY
_mesa_marshal_DrawArrays_no_error(GLenum mode, GLint first, GLsizei count)
{
   draw_arrays(0, mode, first, count, 1, 0, true, true);
}

void GLAPIENTRY
_mesa_marshal_DrawArraysInstanced(GLenum mode, GLint first, GLsizei count,
                                  GLsizei instance_count)
{
   draw_arrays(0, mode, first, count, instance_count, 0, false, false);
}

void GLAPIENTRY
_mesa_marshal_DrawArraysInstanced_no_error(GLenum mode, GLint first, GLsizei count,
                                           GLsizei instance_count)
{
   draw_arrays(0, mode, first, count, instance_count, 0, false, true);
}

void GLAPIENTRY
_mesa_marshal_DrawArraysInstancedBaseInstance(GLenum mode, GLint first,
                                              GLsizei count, GLsizei instance_count,
                                              GLuint baseinstance)
{
   draw_arrays(0, mode, first, count, instance_count, baseinstance, false, false);
}

void GLAPIENTRY
_mesa_marshal_DrawArraysInstancedBaseInstance_no_error(GLenum mode, GLint first,
                                                       GLsizei count, GLsizei instance_count,
                                                       GLuint baseinstance)
{
   draw_arrays(0, mode, first, count, instance_count, baseinstance, false, true);
}

void GLAPIENTRY
_mesa_marshal_DrawElements(GLenum mode, GLsizei count, GLenum type,
                           const GLvoid *indices)
{
   draw_elements(0, mode, count, type, indices, 1, 0,
                 0, false, 0, 0, true, false);
}

void GLAPIENTRY
_mesa_marshal_DrawElements_no_error(GLenum mode, GLsizei count, GLenum type,
                                    const GLvoid *indices)
{
   draw_elements(0, mode, count, type, indices, 1, 0,
                 0, false, 0, 0, true, true);
}

void GLAPIENTRY
_mesa_marshal_DrawRangeElements(GLenum mode, GLuint start, GLuint end,
                                GLsizei count, GLenum type,
                                const GLvoid *indices)
{
   draw_elements(0, mode, count, type, indices, 1, 0,
                 0, true, start, end, true, false);
}

void GLAPIENTRY
_mesa_marshal_DrawRangeElements_no_error(GLenum mode, GLuint start, GLuint end,
                                         GLsizei count, GLenum type,
                                         const GLvoid *indices)
{
   draw_elements(0, mode, count, type, indices, 1, 0,
                 0, true, start, end, true, true);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type,
                                    const GLvoid *indices, GLsizei instance_count)
{
   draw_elements(0, mode, count, type, indices, instance_count, 0,
                 0, false, 0, 0, false, false);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstanced_no_error(GLenum mode, GLsizei count,
                                             GLenum type, const GLvoid *indices,
                                             GLsizei instance_count)
{
   draw_elements(0, mode, count, type, indices, instance_count, 0,
                 0, false, 0, 0, false, true);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type,
                                     const GLvoid *indices, GLint basevertex)
{
   draw_elements(0, mode, count, type, indices, 1, basevertex,
                 0, false, 0, 0, true, false);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsBaseVertex_no_error(GLenum mode, GLsizei count,
                                              GLenum type, const GLvoid *indices,
                                              GLint basevertex)
{
   draw_elements(0, mode, count, type, indices, 1, basevertex,
                 0, false, 0, 0, true, true);
}

void GLAPIENTRY
_mesa_marshal_DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end,
                                          GLsizei count, GLenum type,
                                          const GLvoid *indices, GLint basevertex)
{
   draw_elements(0, mode, count, type, indices, 1, basevertex,
                 0, true, start, end, true, false);
}

void GLAPIENTRY
_mesa_marshal_DrawRangeElementsBaseVertex_no_error(GLenum mode, GLuint start,
                                                   GLuint end, GLsizei count, GLenum type,
                                                   const GLvoid *indices, GLint basevertex)
{
   draw_elements(0, mode, count, type, indices, 1, basevertex,
                 0, true, start, end, true, true);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count,
                                              GLenum type, const GLvoid *indices,
                                              GLsizei instance_count, GLint basevertex)
{
   draw_elements(0, mode, count, type, indices, instance_count, basevertex,
                 0, false, 0, 0, false, false);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstancedBaseVertex_no_error(GLenum mode, GLsizei count,
                                                       GLenum type, const GLvoid *indices,
                                                       GLsizei instance_count, GLint basevertex)
{
   draw_elements(0, mode, count, type, indices, instance_count, basevertex,
                 0, false, 0, 0, false, true);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count,
                                                GLenum type, const GLvoid *indices,
                                                GLsizei instance_count, GLuint baseinstance)
{
   draw_elements(0, mode, count, type, indices, instance_count, 0,
                 baseinstance, false, 0, 0, false, false);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstancedBaseInstance_no_error(GLenum mode, GLsizei count,
                                                         GLenum type, const GLvoid *indices,
                                                         GLsizei instance_count, GLuint baseinstance)
{
   draw_elements(0, mode, count, type, indices, instance_count, 0,
                 baseinstance, false, 0, 0, false, true);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count,
                                                          GLenum type, const GLvoid *indices,
                                                          GLsizei instance_count, GLint basevertex,
                                                          GLuint baseinstance)
{
   draw_elements(0, mode, count, type, indices, instance_count, basevertex,
                 baseinstance, false, 0, 0, false, false);
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstancedBaseVertexBaseInstance_no_error(GLenum mode, GLsizei count,
                                                                   GLenum type, const GLvoid *indices,
                                                                   GLsizei instance_count,
                                                                   GLint basevertex, GLuint baseinstance)
{
   draw_elements(0, mode, count, type, indices, instance_count, basevertex,
                 baseinstance, false, 0, 0, false, true);
}

void GLAPIENTRY
_mesa_marshal_MultiDrawElements(GLenum mode, const GLsizei *count,
                                GLenum type, const GLvoid *const *indices,
                                GLsizei draw_count)
{
   _mesa_marshal_MultiDrawElementsBaseVertex(mode, count, type, indices,
                                             draw_count, NULL);
}

uint32_t
_mesa_unmarshal_DrawArrays(struct gl_context *ctx,
                           const struct marshal_cmd_DrawArrays *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_DrawArraysInstancedBaseInstance(struct gl_context *ctx,
                                                const struct marshal_cmd_DrawArraysInstancedBaseInstance *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_MultiDrawArrays(struct gl_context *ctx,
                                const struct marshal_cmd_MultiDrawArrays *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_DrawRangeElements(struct gl_context *ctx,
                                  const struct marshal_cmd_DrawRangeElements *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_DrawRangeElementsBaseVertex(struct gl_context *ctx,
                                            const struct marshal_cmd_DrawRangeElementsBaseVertex *cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_DrawElementsInstanced(struct gl_context *ctx,
                                      const struct marshal_cmd_DrawElementsInstanced *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_DrawElementsBaseVertex(struct gl_context *ctx,
                                       const struct marshal_cmd_DrawElementsBaseVertex *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_DrawElementsInstancedBaseVertexBaseInstance(struct gl_context *ctx,
                                                            const struct marshal_cmd_DrawElementsInstancedBaseVertexBaseInstance *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_MultiDrawElements(struct gl_context *ctx,
                                  const struct marshal_cmd_MultiDrawElements *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_MultiDrawElementsBaseVertex(struct gl_context *ctx,
                                            const struct marshal_cmd_MultiDrawElementsBaseVertex *restrict cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_MultiModeDrawArraysIBM(struct gl_context *ctx,
                                       const struct marshal_cmd_MultiModeDrawArraysIBM *cmd)
{
   unreachable("should never end up here");
   return 0;
}

uint32_t
_mesa_unmarshal_MultiModeDrawElementsIBM(struct gl_context *ctx,
                                         const struct marshal_cmd_MultiModeDrawElementsIBM *cmd)
{
   unreachable("should never end up here");
   return 0;
}

void GLAPIENTRY
_mesa_marshal_DrawArraysUserBuf(void)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_marshal_DrawElementsUserBuf(const GLvoid *cmd)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_marshal_DrawElementsUserBufPacked(const GLvoid *cmd)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_marshal_MultiDrawArraysUserBuf(void)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_marshal_MultiDrawElementsUserBuf(GLintptr indexBuf, GLenum mode,
                                       const GLsizei *count, GLenum type,
                                       const GLvoid * const *indices,
                                       GLsizei primcount,
                                       const GLint *basevertex)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_marshal_DrawArraysInstancedBaseInstanceDrawID(void)
{
   unreachable("should never end up here");
}

void GLAPIENTRY _mesa_marshal_DrawElementsPacked(GLenum mode, GLenum type,
                                                 GLushort count, GLushort indices)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_marshal_DrawElementsInstancedBaseVertexBaseInstanceDrawID(GLenum mode, GLsizei count,
                                                                GLenum type, const GLvoid *indices,
                                                                GLsizei instance_count, GLint basevertex,
                                                                GLuint baseinstance, GLuint drawid)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_DrawArraysUserBuf(void)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_MultiDrawArraysUserBuf(void)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_DrawArraysInstancedBaseInstanceDrawID(void)
{
   unreachable("should never end up here");
}

void GLAPIENTRY _mesa_DrawElementsPacked(GLenum mode, GLenum type,
                                         GLushort count, GLushort indices)
{
   unreachable("should never end up here");
}

void GLAPIENTRY
_mesa_DrawElementsInstancedBaseVertexBaseInstanceDrawID(GLenum mode, GLsizei count,
                                                        GLenum type, const GLvoid *indices,
                                                        GLsizei instance_count, GLint basevertex,
                                                        GLuint baseinstance, GLuint drawid)
{
   unreachable("should never end up here");
}

uint32_t
_mesa_unmarshal_PushMatrix(struct gl_context *ctx,
                           const struct marshal_cmd_PushMatrix *restrict cmd)
{
   const unsigned push_matrix_size = 1;
   const unsigned mult_matrixf_size = 9;
   const unsigned draw_elements_size =
      (align(sizeof(struct marshal_cmd_DrawElements), 8) / 8);
   const unsigned draw_elements_packed_size =
      (align(sizeof(struct marshal_cmd_DrawElementsPacked), 8) / 8);
   const unsigned pop_matrix_size = 1;
   uint64_t *next1 = _mesa_glthread_next_cmd((uint64_t *)cmd, push_matrix_size);
   uint64_t *next2;

   /* Viewperf has these call patterns. */
   switch (_mesa_glthread_get_cmd(next1)->cmd_id) {
   case DISPATCH_CMD_DrawElements:
      /* Execute this sequence:
       *    glPushMatrix
       *    (glMultMatrixf with identity is eliminated by the marshal function)
       *    glDrawElements
       *    glPopMatrix
       * as:
       *    glDrawElements
       */
      next2 = _mesa_glthread_next_cmd(next1, draw_elements_size);

      if (_mesa_glthread_get_cmd(next2)->cmd_id == DISPATCH_CMD_PopMatrix) {
         /* The beauty of this is that this is inlined. */
         _mesa_unmarshal_DrawElements(ctx, (void*)next1);
         return push_matrix_size + draw_elements_size + pop_matrix_size;
      }
      break;

   case DISPATCH_CMD_DrawElementsPacked:
      next2 = _mesa_glthread_next_cmd(next1, draw_elements_packed_size);

      if (_mesa_glthread_get_cmd(next2)->cmd_id == DISPATCH_CMD_PopMatrix) {
         /* The beauty of this is that this is inlined. */
         _mesa_unmarshal_DrawElementsPacked(ctx, (void*)next1);
         return push_matrix_size + draw_elements_packed_size + pop_matrix_size;
      }
      break;

   case DISPATCH_CMD_MultMatrixf:
      /* Skip this sequence:
       *    glPushMatrix
       *    glMultMatrixf
       *    glPopMatrix
       */
      next2 = _mesa_glthread_next_cmd(next1, mult_matrixf_size);

      if (_mesa_glthread_get_cmd(next2)->cmd_id == DISPATCH_CMD_PopMatrix)
         return push_matrix_size + mult_matrixf_size + pop_matrix_size;
      break;
   }

   CALL_PushMatrix(ctx->Dispatch.Current, ());
   return push_matrix_size;
}

void GLAPIENTRY
_mesa_marshal_PushMatrix(void)
{
   GET_CURRENT_CONTEXT(ctx);

   _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_PushMatrix,
                                   sizeof(struct marshal_cmd_PushMatrix));
   _mesa_glthread_PushMatrix(ctx);
}
