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

#include "util/u_memory.h"
#include "pipe/p_defines.h"
#include "pipe/p_state.h"
#include "pipe/p_shader_tokens.h"
#include "virgl_context.h"
#include "virgl_resource.h"
#include "vrend_renderer.h"
#include "vrend_object.h"
#include "tgsi/tgsi_text.h"
#include "vrend_debug.h"
#include "vrend_tweaks.h"
#include "virgl_util.h"

#ifdef ENABLE_VIDEO
#include "vrend_video.h"
#endif

/* decode side */
#define DECODE_MAX_TOKENS 8000

struct vrend_decode_ctx {
   struct virgl_context base;
   struct vrend_context *grctx;
};

static inline uint32_t get_buf_entry(const uint32_t *buf, uint32_t offset)
{
   return buf[offset];
}

static inline const void *get_buf_ptr(const uint32_t *buf, uint32_t offset)
{
   return &buf[offset];
}

static int vrend_decode_create_shader(struct vrend_context *ctx,
                                      const uint32_t *buf,
                                      uint32_t handle,
                                      uint16_t length)
{
   struct pipe_stream_output_info so_info;
   uint i;
   int ret;
   uint32_t shader_offset, req_local_mem = 0;
   unsigned num_tokens, num_so_outputs, offlen;
   const uint8_t *shd_text;
   uint32_t type;

   if (length < VIRGL_OBJ_SHADER_HDR_SIZE(0))
      return EINVAL;

   type = get_buf_entry(buf, VIRGL_OBJ_SHADER_TYPE);
   num_tokens = get_buf_entry(buf, VIRGL_OBJ_SHADER_NUM_TOKENS);
   offlen = get_buf_entry(buf, VIRGL_OBJ_SHADER_OFFSET);

   if (type == PIPE_SHADER_COMPUTE) {
      req_local_mem = get_buf_entry(buf, VIRGL_OBJ_SHADER_SO_NUM_OUTPUTS);
      num_so_outputs = 0;
   } else {
      num_so_outputs = get_buf_entry(buf, VIRGL_OBJ_SHADER_SO_NUM_OUTPUTS);
      if (length < VIRGL_OBJ_SHADER_HDR_SIZE(num_so_outputs))
         return EINVAL;

      if (num_so_outputs > PIPE_MAX_SO_OUTPUTS)
         return EINVAL;
   }

   shader_offset = 6;
   if (num_so_outputs) {
      so_info.num_outputs = num_so_outputs;
      if (so_info.num_outputs) {
         for (i = 0; i < 4; i++)
            so_info.stride[i] = get_buf_entry(buf, VIRGL_OBJ_SHADER_SO_STRIDE(i));
         for (i = 0; i < so_info.num_outputs; i++) {
            uint32_t tmp = get_buf_entry(buf, VIRGL_OBJ_SHADER_SO_OUTPUT0(i));

            so_info.output[i].register_index = tmp & 0xff;
            so_info.output[i].start_component = (tmp >> 8) & 0x3;
            so_info.output[i].num_components = (tmp >> 10) & 0x7;
            so_info.output[i].output_buffer = (tmp >> 13) & 0x7;
            so_info.output[i].dst_offset = (tmp >> 16) & 0xffff;
            tmp = get_buf_entry(buf, VIRGL_OBJ_SHADER_SO_OUTPUT0_SO(i));
            so_info.output[i].stream = (tmp & 0x3);
            so_info.output[i].need_temp = so_info.output[i].num_components < 4;
         }

         for (i = 0; i < so_info.num_outputs - 1; i++) {
            for (unsigned j = i + 1; j < so_info.num_outputs; j++) {
               so_info.output[j].need_temp |=
                     (so_info.output[i].register_index == so_info.output[j].register_index);
            }
         }
      }
      shader_offset += 4 + (2 * num_so_outputs);
   } else
     memset(&so_info, 0, sizeof(so_info));

   shd_text = get_buf_ptr(buf, shader_offset);
   ret = vrend_create_shader(ctx, handle, &so_info, req_local_mem, (const char *)shd_text, offlen, num_tokens, type, length - shader_offset + 1);

   return ret;
}

static int vrend_decode_create_stream_output_target(struct vrend_context *ctx, const uint32_t *buf,
                                                    uint32_t handle, uint16_t length)
{
   uint32_t res_handle, buffer_size, buffer_offset;

   if (length != VIRGL_OBJ_STREAMOUT_SIZE)
      return EINVAL;

   res_handle = get_buf_entry(buf, VIRGL_OBJ_STREAMOUT_RES_HANDLE);
   buffer_offset = get_buf_entry(buf, VIRGL_OBJ_STREAMOUT_BUFFER_OFFSET);
   buffer_size = get_buf_entry(buf, VIRGL_OBJ_STREAMOUT_BUFFER_SIZE);

   return vrend_create_so_target(ctx, handle, res_handle, buffer_offset,
                                 buffer_size);
}

static int vrend_decode_set_framebuffer_state(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length < 2)
      return EINVAL;

   uint32_t nr_cbufs = get_buf_entry(buf, VIRGL_SET_FRAMEBUFFER_STATE_NR_CBUFS);
   uint32_t zsurf_handle = get_buf_entry(buf, VIRGL_SET_FRAMEBUFFER_STATE_NR_ZSURF_HANDLE);
   uint32_t surf_handle[8];
   uint32_t i;

   if (length != (2u + nr_cbufs))
      return EINVAL;

   if (nr_cbufs > 8)
      return EINVAL;

   for (i = 0; i < nr_cbufs; i++)
      surf_handle[i] = get_buf_entry(buf, VIRGL_SET_FRAMEBUFFER_STATE_CBUF_HANDLE(i));
   vrend_set_framebuffer_state(ctx, nr_cbufs, surf_handle, zsurf_handle);
   return 0;
}

static int vrend_decode_set_framebuffer_state_no_attach(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t width, height;
   uint32_t layers, samples;
   uint32_t tmp;

   if (length != VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_SIZE)
      return EINVAL;

   tmp = get_buf_entry(buf, VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_WIDTH_HEIGHT);
   width = VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_WIDTH(tmp);
   height = VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_HEIGHT(tmp);

   tmp = get_buf_entry(buf, VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_LAYERS_SAMPLES);
   layers = VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_LAYERS(tmp);
   samples = VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_SAMPLES(tmp);

   vrend_set_framebuffer_state_no_attach(ctx, width, height, layers, samples);
   return 0;
}

static int vrend_decode_clear(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   union pipe_color_union color;
   double depth;
   unsigned stencil, buffers;
   int i;

   if (length != VIRGL_OBJ_CLEAR_SIZE)
      return EINVAL;
   buffers = get_buf_entry(buf, VIRGL_OBJ_CLEAR_BUFFERS);
   for (i = 0; i < 4; i++)
      color.ui[i] = get_buf_entry(buf, VIRGL_OBJ_CLEAR_COLOR_0 + i);
   const void *depth_ptr = get_buf_ptr(buf, VIRGL_OBJ_CLEAR_DEPTH_0);
   memcpy(&depth, depth_ptr, sizeof(double));
   stencil = get_buf_entry(buf, VIRGL_OBJ_CLEAR_STENCIL);

   vrend_clear(ctx, buffers, &color, depth, stencil);
   return 0;
}

static int vrend_decode_clear_texture(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_box box;
   uint32_t handle;
   uint32_t level;
   uint32_t arr[4] = {0};

   if (length != VIRGL_CLEAR_TEXTURE_SIZE)
      return EINVAL;

   handle = get_buf_entry(buf, VIRGL_TEXTURE_HANDLE);
   level = get_buf_entry(buf, VIRGL_TEXTURE_LEVEL);
   box.x = get_buf_entry(buf, VIRGL_TEXTURE_SRC_X);
   box.y = get_buf_entry(buf, VIRGL_TEXTURE_SRC_Y);
   box.z = get_buf_entry(buf, VIRGL_TEXTURE_SRC_Z);
   box.width = get_buf_entry(buf, VIRGL_TEXTURE_SRC_W);
   box.height = get_buf_entry(buf, VIRGL_TEXTURE_SRC_H);
   box.depth = get_buf_entry(buf, VIRGL_TEXTURE_SRC_D);
   arr[0] = get_buf_entry(buf, VIRGL_TEXTURE_ARRAY_A);
   arr[1] = get_buf_entry(buf, VIRGL_TEXTURE_ARRAY_B);
   arr[2] = get_buf_entry(buf, VIRGL_TEXTURE_ARRAY_C);
   arr[3] = get_buf_entry(buf, VIRGL_TEXTURE_ARRAY_D);

   return vrend_clear_texture(ctx, handle, level, &box, (void *) &arr);
}

static int vrend_decode_set_viewport_state(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_viewport_state vps[PIPE_MAX_VIEWPORTS];
   uint i, v;
   uint32_t num_viewports, start_slot;
   if (length < 1)
      return EINVAL;

   if ((length - 1) % 6)
      return EINVAL;

   num_viewports = (length - 1) / 6;
   start_slot = get_buf_entry(buf, VIRGL_SET_VIEWPORT_START_SLOT);

   if (num_viewports > PIPE_MAX_VIEWPORTS ||
       start_slot > (PIPE_MAX_VIEWPORTS - num_viewports))
      return EINVAL;

   for (v = 0; v < num_viewports; v++) {
      for (i = 0; i < 3; i++)
         vps[v].scale[i] = uif(get_buf_entry(buf, VIRGL_SET_VIEWPORT_STATE_SCALE_0(v) + i));
      for (i = 0; i < 3; i++)
         vps[v].translate[i] = uif(get_buf_entry(buf, VIRGL_SET_VIEWPORT_STATE_TRANSLATE_0(v) + i));
   }

   vrend_set_viewport_states(ctx, start_slot, num_viewports, vps);
   return 0;
}

static int vrend_decode_set_index_buffer(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 1 && length != 3)
      return EINVAL;
   vrend_set_index_buffer(ctx,
                          get_buf_entry(buf, VIRGL_SET_INDEX_BUFFER_HANDLE),
                          (length == 3) ? get_buf_entry(buf, VIRGL_SET_INDEX_BUFFER_INDEX_SIZE) : 0,
                          (length == 3) ? get_buf_entry(buf, VIRGL_SET_INDEX_BUFFER_OFFSET) : 0);
   return 0;
}

static int vrend_decode_set_constant_buffer(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t shader;
   int nc = (length - 2);

   if (length < 2)
      return EINVAL;

   shader = get_buf_entry(buf, VIRGL_SET_CONSTANT_BUFFER_SHADER_TYPE);
   /* VIRGL_SET_CONSTANT_BUFFER_INDEX is not used */

   if (shader >= PIPE_SHADER_TYPES)
      return EINVAL;

   vrend_set_constants(ctx, shader, nc, get_buf_ptr(buf, VIRGL_SET_CONSTANT_BUFFER_DATA_START));
   return 0;
}

static int vrend_decode_set_uniform_buffer(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != VIRGL_SET_UNIFORM_BUFFER_SIZE)
      return EINVAL;

   uint32_t shader = get_buf_entry(buf, VIRGL_SET_UNIFORM_BUFFER_SHADER_TYPE);
   uint32_t index = get_buf_entry(buf, VIRGL_SET_UNIFORM_BUFFER_INDEX);
   uint32_t offset = get_buf_entry(buf, VIRGL_SET_UNIFORM_BUFFER_OFFSET);
   uint32_t blength = get_buf_entry(buf, VIRGL_SET_UNIFORM_BUFFER_LENGTH);
   uint32_t handle = get_buf_entry(buf, VIRGL_SET_UNIFORM_BUFFER_RES_HANDLE);

   if (shader >= PIPE_SHADER_TYPES)
      return EINVAL;

   if (index >= PIPE_MAX_CONSTANT_BUFFERS)
      return EINVAL;

   vrend_set_uniform_buffer(ctx, shader, index, offset, blength, handle);
   return 0;
}

static int vrend_decode_set_vertex_buffers(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   int num_vbo;
   int i;

   /* must be a multiple of 3 */
   if (length && (length % 3))
      return EINVAL;

   num_vbo = (length / 3);
   if (num_vbo > PIPE_MAX_ATTRIBS)
      return EINVAL;

   for (i = 0; i < num_vbo; i++) {
      vrend_set_single_vbo(ctx, i,
                           get_buf_entry(buf, VIRGL_SET_VERTEX_BUFFER_STRIDE(i)),
                           get_buf_entry(buf, VIRGL_SET_VERTEX_BUFFER_OFFSET(i)),
                           get_buf_entry(buf, VIRGL_SET_VERTEX_BUFFER_HANDLE(i)));
   }
   vrend_set_num_vbo(ctx, num_vbo);
   return 0;
}

static int vrend_decode_set_sampler_views(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t num_samps;
   uint32_t i;
   uint32_t shader_type;
   uint32_t start_slot;

   if (length < 2)
      return EINVAL;
   num_samps = length - 2;
   shader_type = get_buf_entry(buf, VIRGL_SET_SAMPLER_VIEWS_SHADER_TYPE);
   start_slot = get_buf_entry(buf, VIRGL_SET_SAMPLER_VIEWS_START_SLOT);

   if (shader_type >= PIPE_SHADER_TYPES)
      return EINVAL;

   if (num_samps > PIPE_MAX_SHADER_SAMPLER_VIEWS ||
       start_slot > (PIPE_MAX_SHADER_SAMPLER_VIEWS - num_samps))
      return EINVAL;

   for (i = 0; i < num_samps; i++) {
      uint32_t handle = get_buf_entry(buf, VIRGL_SET_SAMPLER_VIEWS_V0_HANDLE + i);
      vrend_set_single_sampler_view(ctx, shader_type, i + start_slot, handle);
   }
   vrend_set_num_sampler_views(ctx, shader_type, start_slot, num_samps);
   return 0;
}

static void vrend_decode_transfer_common(const uint32_t *buf,
                                         uint32_t *dst_handle,
                                         struct vrend_transfer_info *info)
{
   *dst_handle = get_buf_entry(buf, VIRGL_RESOURCE_IW_RES_HANDLE);

   info->level = get_buf_entry(buf, VIRGL_RESOURCE_IW_LEVEL);
   info->stride = get_buf_entry(buf, VIRGL_RESOURCE_IW_STRIDE);
   info->layer_stride = get_buf_entry(buf, VIRGL_RESOURCE_IW_LAYER_STRIDE);
   info->box->x = get_buf_entry(buf, VIRGL_RESOURCE_IW_X);
   info->box->y = get_buf_entry(buf, VIRGL_RESOURCE_IW_Y);
   info->box->z = get_buf_entry(buf, VIRGL_RESOURCE_IW_Z);
   info->box->width = get_buf_entry(buf, VIRGL_RESOURCE_IW_W);
   info->box->height = get_buf_entry(buf, VIRGL_RESOURCE_IW_H);
   info->box->depth = get_buf_entry(buf, VIRGL_RESOURCE_IW_D);
}

static int vrend_decode_resource_inline_write(struct vrend_context *ctx, const uint32_t *buf,
                                              uint32_t length)
{
   struct pipe_box box;
   uint32_t dst_handle;
   struct vrend_transfer_info info;
   uint32_t data_len;
   struct iovec dataiovec;
   const void *data;

   if (length < 12)
      return EINVAL;

   memset(&info, 0, sizeof(info));
   info.box = &box;
   vrend_decode_transfer_common(buf, &dst_handle, &info);
   data_len = (length - 11) * 4;
   data = get_buf_ptr(buf, VIRGL_RESOURCE_IW_DATA_START);

   info.offset = 0;

   dataiovec.iov_base = (void*)data;
   dataiovec.iov_len = data_len;

   info.iovec = &dataiovec;
   info.iovec_cnt = 1;
   return vrend_transfer_inline_write(ctx, dst_handle, &info);
}

static int vrend_decode_draw_vbo(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_draw_info info;
   uint32_t cso;
   uint32_t handle = 0, indirect_draw_count_handle = 0;
   if (length != VIRGL_DRAW_VBO_SIZE && length != VIRGL_DRAW_VBO_SIZE_TESS &&
       length != VIRGL_DRAW_VBO_SIZE_INDIRECT)
      return EINVAL;
   memset(&info, 0, sizeof(struct pipe_draw_info));

   info.start = get_buf_entry(buf, VIRGL_DRAW_VBO_START);
   info.count = get_buf_entry(buf, VIRGL_DRAW_VBO_COUNT);
   info.mode = get_buf_entry(buf, VIRGL_DRAW_VBO_MODE);
   info.indexed = !!get_buf_entry(buf, VIRGL_DRAW_VBO_INDEXED);
   info.instance_count = get_buf_entry(buf, VIRGL_DRAW_VBO_INSTANCE_COUNT);
   info.index_bias = get_buf_entry(buf, VIRGL_DRAW_VBO_INDEX_BIAS);
   info.start_instance = get_buf_entry(buf, VIRGL_DRAW_VBO_START_INSTANCE);
   info.primitive_restart = !!get_buf_entry(buf, VIRGL_DRAW_VBO_PRIMITIVE_RESTART);
   info.restart_index = get_buf_entry(buf, VIRGL_DRAW_VBO_RESTART_INDEX);
   info.min_index = get_buf_entry(buf, VIRGL_DRAW_VBO_MIN_INDEX);
   info.max_index = get_buf_entry(buf, VIRGL_DRAW_VBO_MAX_INDEX);

   if (length >= VIRGL_DRAW_VBO_SIZE_TESS) {
      info.vertices_per_patch = get_buf_entry(buf, VIRGL_DRAW_VBO_VERTICES_PER_PATCH);
      info.drawid = get_buf_entry(buf, VIRGL_DRAW_VBO_DRAWID);
   }

   if (length == VIRGL_DRAW_VBO_SIZE_INDIRECT) {
      handle = get_buf_entry(buf, VIRGL_DRAW_VBO_INDIRECT_HANDLE);
      info.indirect.offset = get_buf_entry(buf, VIRGL_DRAW_VBO_INDIRECT_OFFSET);
      info.indirect.stride = get_buf_entry(buf, VIRGL_DRAW_VBO_INDIRECT_STRIDE);
      info.indirect.draw_count = get_buf_entry(buf, VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT);
      info.indirect.indirect_draw_count_offset = get_buf_entry(buf, VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT_OFFSET);
      indirect_draw_count_handle = get_buf_entry(buf, VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT_HANDLE);
   }

   cso = get_buf_entry(buf, VIRGL_DRAW_VBO_COUNT_FROM_SO);

   return vrend_draw_vbo(ctx, &info, cso, handle, indirect_draw_count_handle);
}

static int vrend_decode_create_blend(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   struct pipe_blend_state *blend_state;
   uint32_t tmp;
   int i;

   if (length != VIRGL_OBJ_BLEND_SIZE) {
      return EINVAL;
   }

   blend_state = CALLOC_STRUCT(pipe_blend_state);
   if (!blend_state)
      return ENOMEM;

   tmp = get_buf_entry(buf, VIRGL_OBJ_BLEND_S0);
   blend_state->independent_blend_enable = (tmp & 1);
   blend_state->logicop_enable = (tmp >> 1) & 0x1;
   blend_state->dither = (tmp >> 2) & 0x1;
   blend_state->alpha_to_coverage = (tmp >> 3) & 0x1;
   blend_state->alpha_to_one = (tmp >> 4) & 0x1;

   tmp = get_buf_entry(buf, VIRGL_OBJ_BLEND_S1);
   blend_state->logicop_func = tmp & 0xf;

   for (i = 0; i < PIPE_MAX_COLOR_BUFS; i++) {
      tmp = get_buf_entry(buf, VIRGL_OBJ_BLEND_S2(i));
      blend_state->rt[i].blend_enable = tmp & 0x1;
      blend_state->rt[i].rgb_func = (tmp >> 1) & 0x7;
      blend_state->rt[i].rgb_src_factor = (tmp >> 4) & 0x1f;
      blend_state->rt[i].rgb_dst_factor = (tmp >> 9) & 0x1f;
      blend_state->rt[i].alpha_func = (tmp >> 14) & 0x7;
      blend_state->rt[i].alpha_src_factor = (tmp >> 17) & 0x1f;
      blend_state->rt[i].alpha_dst_factor = (tmp >> 22) & 0x1f;
      blend_state->rt[i].colormask = (tmp >> 27) & 0xf;
   }

   tmp = vrend_renderer_object_insert(ctx, blend_state, handle,
                                      VIRGL_OBJECT_BLEND);
   if (tmp == 0) {
      FREE(blend_state);
      return ENOMEM;
   }
   return 0;
}

static int vrend_decode_create_dsa(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   int i;
   struct pipe_depth_stencil_alpha_state *dsa_state;
   uint32_t tmp;

   if (length != VIRGL_OBJ_DSA_SIZE)
      return EINVAL;

   dsa_state = CALLOC_STRUCT(pipe_depth_stencil_alpha_state);
   if (!dsa_state)
      return ENOMEM;

   tmp = get_buf_entry(buf, VIRGL_OBJ_DSA_S0);
   dsa_state->depth.enabled = tmp & 0x1;
   dsa_state->depth.writemask = (tmp >> 1) & 0x1;
   dsa_state->depth.func = (tmp >> 2) & 0x7;

   dsa_state->alpha.enabled = (tmp >> 8) & 0x1;
   dsa_state->alpha.func = (tmp >> 9) & 0x7;

   for (i = 0; i < 2; i++) {
      tmp = get_buf_entry(buf, VIRGL_OBJ_DSA_S1 + i);
      dsa_state->stencil[i].enabled = tmp & 0x1;
      dsa_state->stencil[i].func = (tmp >> 1) & 0x7;
      dsa_state->stencil[i].fail_op = (tmp >> 4) & 0x7;
      dsa_state->stencil[i].zpass_op = (tmp >> 7) & 0x7;
      dsa_state->stencil[i].zfail_op = (tmp >> 10) & 0x7;
      dsa_state->stencil[i].valuemask = (tmp >> 13) & 0xff;
      dsa_state->stencil[i].writemask = (tmp >> 21) & 0xff;
   }

   tmp = get_buf_entry(buf, VIRGL_OBJ_DSA_ALPHA_REF);
   dsa_state->alpha.ref_value = uif(tmp);

   tmp = vrend_renderer_object_insert(ctx, dsa_state, handle,
                                      VIRGL_OBJECT_DSA);
   if (tmp == 0) {
      FREE(dsa_state);
      return ENOMEM;
   }
   return 0;
}

static int vrend_decode_create_rasterizer(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   struct pipe_rasterizer_state *rs_state;
   uint32_t tmp;

   if (length != VIRGL_OBJ_RS_SIZE)
      return EINVAL;

   rs_state = CALLOC_STRUCT(pipe_rasterizer_state);
   if (!rs_state)
      return ENOMEM;

   tmp = get_buf_entry(buf, VIRGL_OBJ_RS_S0);
#define ebit(name, bit) rs_state->name = (tmp >> bit) & 0x1
#define emask(name, bit, mask) rs_state->name = (tmp >> bit) & mask

   ebit(flatshade, 0);
   ebit(depth_clip, 1);
   ebit(clip_halfz, 2);
   ebit(rasterizer_discard, 3);
   ebit(flatshade_first, 4);
   ebit(light_twoside, 5);
   ebit(sprite_coord_mode, 6);
   ebit(point_quad_rasterization, 7);
   emask(cull_face, 8, 0x3);
   emask(fill_front, 10, 0x3);
   emask(fill_back, 12, 0x3);
   ebit(scissor, 14);
   ebit(front_ccw, 15);
   ebit(clamp_vertex_color, 16);
   ebit(clamp_fragment_color, 17);
   ebit(offset_line, 18);
   ebit(offset_point, 19);
   ebit(offset_tri, 20);
   ebit(poly_smooth, 21);
   ebit(poly_stipple_enable, 22);
   ebit(point_smooth, 23);
   ebit(point_size_per_vertex, 24);
   ebit(multisample, 25);
   ebit(line_smooth, 26);
   ebit(line_stipple_enable, 27);
   ebit(line_last_pixel, 28);
   ebit(half_pixel_center, 29);
   ebit(bottom_edge_rule, 30);
   ebit(force_persample_interp, 31);
   rs_state->point_size = uif(get_buf_entry(buf, VIRGL_OBJ_RS_POINT_SIZE));
   rs_state->sprite_coord_enable = get_buf_entry(buf, VIRGL_OBJ_RS_SPRITE_COORD_ENABLE);
   tmp = get_buf_entry(buf, VIRGL_OBJ_RS_S3);
   emask(line_stipple_pattern, 0, 0xffff);
   emask(line_stipple_factor, 16, 0xff);
   emask(clip_plane_enable, 24, 0xff);

   rs_state->line_width = uif(get_buf_entry(buf, VIRGL_OBJ_RS_LINE_WIDTH));
   rs_state->offset_units = uif(get_buf_entry(buf, VIRGL_OBJ_RS_OFFSET_UNITS));
   rs_state->offset_scale = uif(get_buf_entry(buf, VIRGL_OBJ_RS_OFFSET_SCALE));
   rs_state->offset_clamp = uif(get_buf_entry(buf, VIRGL_OBJ_RS_OFFSET_CLAMP));

   tmp = vrend_renderer_object_insert(ctx, rs_state, handle,
                                      VIRGL_OBJECT_RASTERIZER);
   if (tmp == 0) {
      FREE(rs_state);
      return ENOMEM;
   }
   return 0;
}

static int vrend_decode_create_surface_common(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint32_t sample_count)
{
   uint32_t res_handle, format, val0, val1;

   res_handle = get_buf_entry(buf, VIRGL_OBJ_SURFACE_RES_HANDLE);
   format = get_buf_entry(buf, VIRGL_OBJ_SURFACE_FORMAT);
   /* decide later if these are texture or buffer */
   val0 = get_buf_entry(buf, VIRGL_OBJ_SURFACE_BUFFER_FIRST_ELEMENT);
   val1 = get_buf_entry(buf, VIRGL_OBJ_SURFACE_BUFFER_LAST_ELEMENT);

   return vrend_create_surface(ctx, handle, res_handle, format, val0, val1, sample_count);
}

static int vrend_decode_create_surface(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   if (length != VIRGL_OBJ_SURFACE_SIZE)
      return EINVAL;

   return vrend_decode_create_surface_common(ctx, buf, handle, 0);
}

static int vrend_decode_create_msaa_surface(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   if (length != VIRGL_OBJ_MSAA_SURFACE_SIZE)
      return EINVAL;

   uint32_t sample_count = get_buf_entry(buf, VIRGL_OBJ_SURFACE_SAMPLE_COUNT);
   return vrend_decode_create_surface_common(ctx, buf, handle, sample_count);
}

static int vrend_decode_create_sampler_view(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   uint32_t res_handle, format, val0, val1, swizzle_packed;

   if (length != VIRGL_OBJ_SAMPLER_VIEW_SIZE)
      return EINVAL;

   res_handle = get_buf_entry(buf, VIRGL_OBJ_SAMPLER_VIEW_RES_HANDLE);
   format = get_buf_entry(buf, VIRGL_OBJ_SAMPLER_VIEW_FORMAT);
   val0 = get_buf_entry(buf, VIRGL_OBJ_SAMPLER_VIEW_BUFFER_FIRST_ELEMENT);
   val1 = get_buf_entry(buf, VIRGL_OBJ_SAMPLER_VIEW_BUFFER_LAST_ELEMENT);
   swizzle_packed = get_buf_entry(buf, VIRGL_OBJ_SAMPLER_VIEW_SWIZZLE);
   return vrend_create_sampler_view(ctx, handle, res_handle, format, val0, val1,swizzle_packed);
}

static int vrend_decode_create_sampler_state(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   struct pipe_sampler_state state;
   int i;
   uint32_t tmp;

   if (length != VIRGL_OBJ_SAMPLER_STATE_SIZE)
      return EINVAL;
   tmp = get_buf_entry(buf, VIRGL_OBJ_SAMPLER_STATE_S0);
   state.wrap_s = tmp & 0x7;
   state.wrap_t = (tmp >> 3) & 0x7;
   state.wrap_r = (tmp >> 6) & 0x7;
   state.min_img_filter = (tmp >> 9) & 0x3;
   state.min_mip_filter = (tmp >> 11) & 0x3;
   state.mag_img_filter = (tmp >> 13) & 0x3;
   state.compare_mode = (tmp >> 15) & 0x1;
   state.compare_func = (tmp >> 16) & 0x7;
   state.seamless_cube_map = (tmp >> 19) & 0x1;
   state.max_anisotropy = (float)((tmp >> 20) & 0x3f);
   state.normalized_coords = 0;

   state.lod_bias = uif(get_buf_entry(buf, VIRGL_OBJ_SAMPLER_STATE_LOD_BIAS));
   state.min_lod = uif(get_buf_entry(buf, VIRGL_OBJ_SAMPLER_STATE_MIN_LOD));
   state.max_lod = uif(get_buf_entry(buf, VIRGL_OBJ_SAMPLER_STATE_MAX_LOD));

   for (i = 0; i < 4; i++)
      state.border_color.ui[i] = get_buf_entry(buf, VIRGL_OBJ_SAMPLER_STATE_BORDER_COLOR(i));

   if (state.min_mip_filter != PIPE_TEX_MIPFILTER_NONE &&
       state.min_mip_filter != PIPE_TEX_MIPFILTER_LINEAR &&
       state.min_mip_filter != PIPE_TEX_MIPFILTER_NEAREST)
     return EINVAL;

   return vrend_create_sampler_state(ctx, handle, &state);
}

static int vrend_decode_create_ve(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   struct pipe_vertex_element *ve = NULL;
   int num_elements;
   int i;
   int ret;

   if (length < 1)
      return EINVAL;

   if ((length - 1) % 4)
      return EINVAL;

   num_elements = (length - 1) / 4;

   if (num_elements) {
      ve = calloc(num_elements, sizeof(struct pipe_vertex_element));

      if (!ve)
         return ENOMEM;

      for (i = 0; i < num_elements; i++) {
         ve[i].src_offset = get_buf_entry(buf, VIRGL_OBJ_VERTEX_ELEMENTS_V0_SRC_OFFSET(i));
         ve[i].instance_divisor = get_buf_entry(buf, VIRGL_OBJ_VERTEX_ELEMENTS_V0_INSTANCE_DIVISOR(i));
         ve[i].vertex_buffer_index = get_buf_entry(buf, VIRGL_OBJ_VERTEX_ELEMENTS_V0_VERTEX_BUFFER_INDEX(i));

         if (ve[i].vertex_buffer_index >= PIPE_MAX_ATTRIBS) {
            FREE(ve);
            return EINVAL;
         }

         ve[i].src_format = get_buf_entry(buf, VIRGL_OBJ_VERTEX_ELEMENTS_V0_SRC_FORMAT(i));
      }
   }

   ret = vrend_create_vertex_elements_state(ctx, handle, num_elements, ve);

   FREE(ve);
   return ret;
}

static int vrend_decode_create_query(struct vrend_context *ctx, const uint32_t *buf, uint32_t handle, uint16_t length)
{
   uint32_t query_type;
   uint32_t query_index;
   uint32_t res_handle;
   uint32_t offset;
   uint32_t tmp;

   if (length != VIRGL_OBJ_QUERY_SIZE)
      return EINVAL;

   tmp = get_buf_entry(buf, VIRGL_OBJ_QUERY_TYPE_INDEX);
   query_type = VIRGL_OBJ_QUERY_TYPE(tmp);
   query_index = (tmp >> 16) & 0xffff;

   offset = get_buf_entry(buf, VIRGL_OBJ_QUERY_OFFSET);
   res_handle = get_buf_entry(buf, VIRGL_OBJ_QUERY_RES_HANDLE);

   return vrend_create_query(ctx, handle, query_type, query_index, res_handle, offset);
}

static int vrend_decode_create_object(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length < 1)
      return EINVAL;

   uint32_t header = get_buf_entry(buf, VIRGL_OBJ_CREATE_HEADER);
   uint32_t handle = get_buf_entry(buf, VIRGL_OBJ_CREATE_HANDLE);
   uint8_t obj_type = (header >> 8) & 0xff;
   int ret = 0;

   if (handle == 0)
      return EINVAL;

   VREND_DEBUG(dbg_object, ctx,"  CREATE %-18s handle:0x%x len:%d\n",
               vrend_get_object_type_name(obj_type), handle, length);

   TRACE_SCOPE(vrend_get_object_type_name(obj_type));

   switch (obj_type){
   case VIRGL_OBJECT_BLEND:
      ret = vrend_decode_create_blend(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_DSA:
      ret = vrend_decode_create_dsa(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_RASTERIZER:
      ret = vrend_decode_create_rasterizer(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_SHADER:
      ret = vrend_decode_create_shader(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_VERTEX_ELEMENTS:
      ret = vrend_decode_create_ve(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_SURFACE:
      ret = vrend_decode_create_surface(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_SAMPLER_VIEW:
      ret = vrend_decode_create_sampler_view(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_SAMPLER_STATE:
      ret = vrend_decode_create_sampler_state(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_QUERY:
      ret = vrend_decode_create_query(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_STREAMOUT_TARGET:
      ret = vrend_decode_create_stream_output_target(ctx, buf, handle, length);
      break;
   case VIRGL_OBJECT_MSAA_SURFACE:
      ret = vrend_decode_create_msaa_surface(ctx, buf, handle, length);
      break;
   default:
      return EINVAL;
   }

   return ret;
}

static int vrend_decode_bind_object(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 1)
      return EINVAL;

   uint32_t header = get_buf_entry(buf, VIRGL_OBJ_BIND_HEADER);
   uint32_t handle = get_buf_entry(buf, VIRGL_OBJ_BIND_HANDLE);
   uint8_t obj_type = (header >> 8) & 0xff;

   VREND_DEBUG(dbg_object, ctx,
               "  BIND %-20s handle:0x%x len:%d\n",
               vrend_get_object_type_name(obj_type), handle, length);

   switch (obj_type) {
   case VIRGL_OBJECT_BLEND:
      vrend_object_bind_blend(ctx, handle);
      break;
   case VIRGL_OBJECT_DSA:
      vrend_object_bind_dsa(ctx, handle);
      break;
   case VIRGL_OBJECT_RASTERIZER:
      vrend_object_bind_rasterizer(ctx, handle);
      break;
   case VIRGL_OBJECT_VERTEX_ELEMENTS:
      vrend_bind_vertex_elements_state(ctx, handle);
      break;
   default:
      return EINVAL;
   }

   return 0;
}

static int vrend_decode_destroy_object(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 1)
      return EINVAL;

   uint32_t handle = get_buf_entry(buf, VIRGL_OBJ_DESTROY_HANDLE);

   VREND_DEBUG_EXT(dbg_object, ctx,
               uint32_t obj = (get_buf_entry(buf, 0) >> 8) & 0xFF;
               vrend_printf("  DESTROY %-17s handle:0x%x\n",
                       vrend_get_object_type_name(obj), handle));

   vrend_renderer_object_destroy(ctx, handle);
   return 0;
}

static int vrend_decode_set_stencil_ref(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != VIRGL_SET_STENCIL_REF_SIZE)
      return EINVAL;

   struct pipe_stencil_ref ref;
   uint32_t val = get_buf_entry(buf, VIRGL_SET_STENCIL_REF);

   ref.ref_value[0] = val & 0xff;
   ref.ref_value[1] = (val >> 8) & 0xff;
   vrend_set_stencil_ref(ctx, &ref);
   return 0;
}

static int vrend_decode_set_blend_color(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_blend_color color;
   int i;

   if (length != VIRGL_SET_BLEND_COLOR_SIZE)
      return EINVAL;

   for (i = 0; i < 4; i++)
      color.color[i] = uif(get_buf_entry(buf, VIRGL_SET_BLEND_COLOR(i)));

   vrend_set_blend_color(ctx, &color);
   return 0;
}

static int vrend_decode_set_scissor_state(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_scissor_state ss[PIPE_MAX_VIEWPORTS];
   uint32_t temp;
   int32_t num_scissor;
   uint32_t start_slot;
   int s;
   if (length < 1)
      return EINVAL;

   if ((length - 1) % 2)
      return EINVAL;

   num_scissor = (length - 1) / 2;
   if (num_scissor > PIPE_MAX_VIEWPORTS)
      return EINVAL;

   start_slot = get_buf_entry(buf, VIRGL_SET_SCISSOR_START_SLOT);

   for (s = 0; s < num_scissor; s++) {
      temp = get_buf_entry(buf, VIRGL_SET_SCISSOR_MINX_MINY(s));
      ss[s].minx = temp & 0xffff;
      ss[s].miny = (temp >> 16) & 0xffff;

      temp = get_buf_entry(buf, VIRGL_SET_SCISSOR_MAXX_MAXY(s));
      ss[s].maxx = temp & 0xffff;
      ss[s].maxy = (temp >> 16) & 0xffff;
   }

   vrend_set_scissor_state(ctx, start_slot, num_scissor, ss);
   return 0;
}

static int vrend_decode_set_polygon_stipple(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_poly_stipple ps;
   int i;

   if (length != VIRGL_POLYGON_STIPPLE_SIZE)
      return EINVAL;

   for (i = 0; i < 32; i++)
      ps.stipple[i] = get_buf_entry(buf, VIRGL_POLYGON_STIPPLE_P0 + i);

   vrend_set_polygon_stipple(ctx, &ps);
   return 0;
}

static int vrend_decode_set_clip_state(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_clip_state clip;
   int i, j;

   if (length != VIRGL_SET_CLIP_STATE_SIZE)
      return EINVAL;

   for (i = 0; i < 8; i++)
      for (j = 0; j < 4; j++)
         clip.ucp[i][j] = uif(get_buf_entry(buf, VIRGL_SET_CLIP_STATE_C0 + (i * 4) + j));
   vrend_set_clip_state(ctx, &clip);
   return 0;
}

static int vrend_decode_set_sample_mask(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   unsigned mask;

   if (length != VIRGL_SET_SAMPLE_MASK_SIZE)
      return EINVAL;
   mask = get_buf_entry(buf, VIRGL_SET_SAMPLE_MASK_MASK);
   vrend_set_sample_mask(ctx, mask);
   return 0;
}

static int vrend_decode_set_min_samples(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   unsigned min_samples;

   if (length != VIRGL_SET_MIN_SAMPLES_SIZE)
      return EINVAL;
   min_samples = get_buf_entry(buf, VIRGL_SET_MIN_SAMPLES_MASK);
   vrend_set_min_samples(ctx, min_samples);
   return 0;
}

static int vrend_decode_resource_copy_region(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_box box;
   uint32_t dst_handle, src_handle;
   uint32_t dst_level, dstx, dsty, dstz;
   uint32_t src_level;

   if (length != VIRGL_CMD_RESOURCE_COPY_REGION_SIZE)
      return EINVAL;

   dst_handle = get_buf_entry(buf, VIRGL_CMD_RCR_DST_RES_HANDLE);
   dst_level = get_buf_entry(buf, VIRGL_CMD_RCR_DST_LEVEL);
   dstx = get_buf_entry(buf, VIRGL_CMD_RCR_DST_X);
   dsty = get_buf_entry(buf, VIRGL_CMD_RCR_DST_Y);
   dstz = get_buf_entry(buf, VIRGL_CMD_RCR_DST_Z);
   src_handle = get_buf_entry(buf, VIRGL_CMD_RCR_SRC_RES_HANDLE);
   src_level = get_buf_entry(buf, VIRGL_CMD_RCR_SRC_LEVEL);
   box.x = get_buf_entry(buf, VIRGL_CMD_RCR_SRC_X);
   box.y = get_buf_entry(buf, VIRGL_CMD_RCR_SRC_Y);
   box.z = get_buf_entry(buf, VIRGL_CMD_RCR_SRC_Z);
   box.width = get_buf_entry(buf, VIRGL_CMD_RCR_SRC_W);
   box.height = get_buf_entry(buf, VIRGL_CMD_RCR_SRC_H);
   box.depth = get_buf_entry(buf, VIRGL_CMD_RCR_SRC_D);

   vrend_renderer_resource_copy_region(ctx, dst_handle,
                                       dst_level, dstx, dsty, dstz,
                                       src_handle, src_level,
                                       &box);
   return 0;
}


static int vrend_decode_blit(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_blit_info info;
   uint32_t dst_handle, src_handle, temp;

   if (length != VIRGL_CMD_BLIT_SIZE)
      return EINVAL;
   temp = get_buf_entry(buf, VIRGL_CMD_BLIT_S0);
   info.mask = temp & 0xff;
   info.filter = (temp >> 8) & 0x3;
   info.scissor_enable = (temp >> 10) & 0x1;
   info.render_condition_enable = (temp >> 11) & 0x1;
   info.alpha_blend = (temp >> 12) & 0x1;
   temp = get_buf_entry(buf, VIRGL_CMD_BLIT_SCISSOR_MINX_MINY);
   info.scissor.minx = temp & 0xffff;
   info.scissor.miny = (temp >> 16) & 0xffff;
   temp = get_buf_entry(buf, VIRGL_CMD_BLIT_SCISSOR_MAXX_MAXY);
   info.scissor.maxx = temp & 0xffff;
   info.scissor.maxy = (temp >> 16) & 0xffff;
   dst_handle = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_RES_HANDLE);
   info.dst.level = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_LEVEL);
   info.dst.format = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_FORMAT);
   info.dst.box.x = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_X);
   info.dst.box.y = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_Y);
   info.dst.box.z = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_Z);
   info.dst.box.width = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_W);
   info.dst.box.height = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_H);
   info.dst.box.depth = get_buf_entry(buf, VIRGL_CMD_BLIT_DST_D);

   src_handle = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_RES_HANDLE);
   info.src.level = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_LEVEL);
   info.src.format = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_FORMAT);
   info.src.box.x = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_X);
   info.src.box.y = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_Y);
   info.src.box.z = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_Z);
   info.src.box.width = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_W);
   info.src.box.height = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_H);
   info.src.box.depth = get_buf_entry(buf, VIRGL_CMD_BLIT_SRC_D);

   vrend_renderer_blit(ctx, dst_handle, src_handle, &info);
   return 0;
}

static int vrend_decode_bind_sampler_states(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length < 2)
      return EINVAL;

   uint32_t shader_type = get_buf_entry(buf, VIRGL_BIND_SAMPLER_STATES_SHADER_TYPE);
   uint32_t start_slot = get_buf_entry(buf, VIRGL_BIND_SAMPLER_STATES_START_SLOT);
   uint32_t num_states = length - 2;

   if (shader_type >= PIPE_SHADER_TYPES)
      return EINVAL;

   vrend_bind_sampler_states(ctx, shader_type, start_slot, num_states,
                             get_buf_ptr(buf, VIRGL_BIND_SAMPLER_STATES_S0_HANDLE));
   return 0;
}

static int vrend_decode_begin_query(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 1)
      return EINVAL;

   uint32_t handle = get_buf_entry(buf, VIRGL_QUERY_BEGIN_HANDLE);

   return vrend_begin_query(ctx, handle);
}

static int vrend_decode_end_query(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 1)
      return EINVAL;

   uint32_t handle = get_buf_entry(buf, VIRGL_QUERY_END_HANDLE);

   return vrend_end_query(ctx, handle);
}

static int vrend_decode_get_query_result(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 2)
      return EINVAL;

   uint32_t handle = get_buf_entry(buf, VIRGL_QUERY_RESULT_HANDLE);
   uint32_t wait = get_buf_entry(buf, VIRGL_QUERY_RESULT_WAIT);

   vrend_get_query_result(ctx, handle, wait);
   return 0;
}

static int vrend_decode_get_query_result_qbo(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != VIRGL_QUERY_RESULT_QBO_SIZE)
      return EINVAL;

   uint32_t handle = get_buf_entry(buf, VIRGL_QUERY_RESULT_QBO_HANDLE);
   uint32_t qbo_handle = get_buf_entry(buf, VIRGL_QUERY_RESULT_QBO_QBO_HANDLE);
   uint32_t wait = get_buf_entry(buf, VIRGL_QUERY_RESULT_QBO_WAIT);
   uint32_t result_type = get_buf_entry(buf, VIRGL_QUERY_RESULT_QBO_RESULT_TYPE);
   uint32_t offset = get_buf_entry(buf, VIRGL_QUERY_RESULT_QBO_OFFSET);
   int32_t index = get_buf_entry(buf, VIRGL_QUERY_RESULT_QBO_INDEX);

   vrend_get_query_result_qbo(ctx, handle, qbo_handle, wait, result_type, offset, index);
   return 0;
}

static int vrend_decode_set_render_condition(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != VIRGL_RENDER_CONDITION_SIZE)
      return EINVAL;

   uint32_t handle = get_buf_entry(buf, VIRGL_RENDER_CONDITION_HANDLE);
   bool condition = get_buf_entry(buf, VIRGL_RENDER_CONDITION_CONDITION) & 1;
   uint mode = get_buf_entry(buf, VIRGL_RENDER_CONDITION_MODE);

   vrend_render_condition(ctx, handle, condition, mode);
   return 0;
}

static int vrend_decode_set_sub_ctx(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 1)
      return EINVAL;

   uint32_t ctx_sub_id = get_buf_entry(buf, 1);

   vrend_renderer_set_sub_ctx(ctx, ctx_sub_id);
   return 0;
}

static int vrend_decode_create_sub_ctx(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 1)
      return EINVAL;

   uint32_t ctx_sub_id = get_buf_entry(buf, 1);

   vrend_renderer_create_sub_ctx(ctx, ctx_sub_id);
   return 0;
}

static int vrend_decode_destroy_sub_ctx(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != 1)
      return EINVAL;

   uint32_t ctx_sub_id = get_buf_entry(buf, 1);

   vrend_renderer_destroy_sub_ctx(ctx, ctx_sub_id);
   return 0;
}

static int vrend_decode_link_shader(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != VIRGL_LINK_SHADER_SIZE)
      return EINVAL;

   uint32_t handles[PIPE_SHADER_TYPES];
   handles[PIPE_SHADER_VERTEX] = get_buf_entry(buf, VIRGL_LINK_SHADER_VERTEX_HANDLE);
   handles[PIPE_SHADER_FRAGMENT] = get_buf_entry(buf, VIRGL_LINK_SHADER_FRAGMENT_HANDLE);
   handles[PIPE_SHADER_GEOMETRY] = get_buf_entry(buf, VIRGL_LINK_SHADER_GEOMETRY_HANDLE);
   handles[PIPE_SHADER_TESS_CTRL] = get_buf_entry(buf, VIRGL_LINK_SHADER_TESS_CTRL_HANDLE);
   handles[PIPE_SHADER_TESS_EVAL] = get_buf_entry(buf, VIRGL_LINK_SHADER_TESS_EVAL_HANDLE);
   handles[PIPE_SHADER_COMPUTE] = get_buf_entry(buf, VIRGL_LINK_SHADER_COMPUTE_HANDLE);

   vrend_link_program_hook(ctx, handles);
   return 0;
}

static int vrend_decode_bind_shader(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t handle, type;
   if (length != VIRGL_BIND_SHADER_SIZE)
      return EINVAL;

   handle = get_buf_entry(buf, VIRGL_BIND_SHADER_HANDLE);
   type = get_buf_entry(buf, VIRGL_BIND_SHADER_TYPE);

   vrend_bind_shader(ctx, handle, type);
   return 0;
}

static int vrend_decode_set_tess_state(struct vrend_context *ctx,
				       const uint32_t *buf, uint32_t length)
{
   float tess_factors[6];
   int i;

   if (length != VIRGL_TESS_STATE_SIZE)
      return EINVAL;

   for (i = 0; i < 6; i++) {
      tess_factors[i] = uif(get_buf_entry(buf, i + 1));
   }
   vrend_set_tess_state(ctx, tess_factors);
   return 0;
}

static int vrend_decode_set_shader_buffers(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t num_ssbo;
   uint32_t shader_type, start_slot;

   if (length < 2)
      return EINVAL;

   num_ssbo = (length - 2) / VIRGL_SET_SHADER_BUFFER_ELEMENT_SIZE;
   shader_type = get_buf_entry(buf, VIRGL_SET_SHADER_BUFFER_SHADER_TYPE);
   start_slot = get_buf_entry(buf, VIRGL_SET_SHADER_BUFFER_START_SLOT);
   if (shader_type >= PIPE_SHADER_TYPES)
      return EINVAL;

   if (num_ssbo < 1)
      return 0;

   if (start_slot > PIPE_MAX_SHADER_BUFFERS ||
       num_ssbo > PIPE_MAX_SHADER_BUFFERS - start_slot)
      return EINVAL;

   for (uint32_t i = 0; i < num_ssbo; i++) {
      uint32_t offset = get_buf_entry(buf, VIRGL_SET_SHADER_BUFFER_OFFSET(i));
      uint32_t buf_len = get_buf_entry(buf, VIRGL_SET_SHADER_BUFFER_LENGTH(i));
      uint32_t handle = get_buf_entry(buf, VIRGL_SET_SHADER_BUFFER_RES_HANDLE(i));
      vrend_set_single_ssbo(ctx, shader_type, start_slot + i, offset, buf_len,
                            handle);
   }
   return 0;
}

static int vrend_decode_set_atomic_buffers(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t num_abo;
   uint32_t start_slot;

   if (length < 2)
      return EINVAL;

   num_abo = (length - 1) / VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE;
   start_slot = get_buf_entry(buf, VIRGL_SET_ATOMIC_BUFFER_START_SLOT);
   if (num_abo < 1)
      return 0;

   if (num_abo > PIPE_MAX_HW_ATOMIC_BUFFERS ||
       start_slot > PIPE_MAX_HW_ATOMIC_BUFFERS ||
       start_slot > PIPE_MAX_HW_ATOMIC_BUFFERS - num_abo)
      return EINVAL;

   for (uint32_t i = 0; i < num_abo; i++) {
      uint32_t offset = get_buf_entry(buf, i * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 2);
      uint32_t buf_len = get_buf_entry(buf, i * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 3);
      uint32_t handle = get_buf_entry(buf, i * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 4);
      vrend_set_single_abo(ctx, start_slot + i, offset, buf_len, handle);
   }

   return 0;
}

static int vrend_decode_set_shader_images(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t num_images;
   uint32_t shader_type, start_slot;
   if (length < 2)
      return EINVAL;

   num_images = (length - 2) / VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE;
   shader_type = get_buf_entry(buf, VIRGL_SET_SHADER_IMAGE_SHADER_TYPE);
   start_slot = get_buf_entry(buf, VIRGL_SET_SHADER_IMAGE_START_SLOT);
   if (shader_type >= PIPE_SHADER_TYPES)
      return EINVAL;

   if (num_images < 1) {
      return 0;
   }

   if (start_slot > PIPE_MAX_SHADER_IMAGES ||
       start_slot + num_images > PIPE_MAX_SHADER_IMAGES)
      return EINVAL;

   for (uint32_t i = 0; i < num_images; i++) {
      uint32_t format = get_buf_entry(buf, VIRGL_SET_SHADER_IMAGE_FORMAT(i));
      uint32_t access = get_buf_entry(buf, VIRGL_SET_SHADER_IMAGE_ACCESS(i));
      uint32_t layer_offset = get_buf_entry(buf, VIRGL_SET_SHADER_IMAGE_LAYER_OFFSET(i));
      uint32_t level_size = get_buf_entry(buf, VIRGL_SET_SHADER_IMAGE_LEVEL_SIZE(i));
      uint32_t handle = get_buf_entry(buf, VIRGL_SET_SHADER_IMAGE_RES_HANDLE(i));
      vrend_set_single_image_view(ctx, shader_type, start_slot + i, format, access,
                                  layer_offset, level_size, handle);
   }
   return 0;
}

static int vrend_decode_memory_barrier(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != VIRGL_MEMORY_BARRIER_SIZE)
      return EINVAL;

   unsigned flags = get_buf_entry(buf, VIRGL_MEMORY_BARRIER_FLAGS);
   vrend_memory_barrier(ctx, flags);
   return 0;
}

static int vrend_decode_launch_grid(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t block[3], grid[3];
   uint32_t indirect_handle, indirect_offset;
   if (length != VIRGL_LAUNCH_GRID_SIZE)
      return EINVAL;

   block[0] = get_buf_entry(buf, VIRGL_LAUNCH_BLOCK_X);
   block[1] = get_buf_entry(buf, VIRGL_LAUNCH_BLOCK_Y);
   block[2] = get_buf_entry(buf, VIRGL_LAUNCH_BLOCK_Z);
   grid[0] = get_buf_entry(buf, VIRGL_LAUNCH_GRID_X);
   grid[1] = get_buf_entry(buf, VIRGL_LAUNCH_GRID_Y);
   grid[2] = get_buf_entry(buf, VIRGL_LAUNCH_GRID_Z);
   indirect_handle = get_buf_entry(buf, VIRGL_LAUNCH_INDIRECT_HANDLE);
   indirect_offset = get_buf_entry(buf, VIRGL_LAUNCH_INDIRECT_OFFSET);
   vrend_launch_grid(ctx, block, grid, indirect_handle, indirect_offset);
   return 0;
}

static int vrend_decode_set_streamout_targets(struct vrend_context *ctx,
                                              const uint32_t *buf, uint32_t length)
{
   uint32_t handles[16];
   uint32_t num_handles = length - 1;
   uint32_t append_bitmask;
   uint i;

   if (length < 1)
      return EINVAL;
   if (num_handles > ARRAY_SIZE(handles))
      return EINVAL;

   append_bitmask = get_buf_entry(buf, VIRGL_SET_STREAMOUT_TARGETS_APPEND_BITMASK);
   for (i = 0; i < num_handles; i++)
      handles[i] = get_buf_entry(buf, VIRGL_SET_STREAMOUT_TARGETS_H0 + i);
   vrend_set_streamout_targets(ctx, append_bitmask, num_handles, handles);
   return 0;
}

static int vrend_decode_texture_barrier(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   if (length != VIRGL_TEXTURE_BARRIER_SIZE)
      return EINVAL;

   unsigned flags = get_buf_entry(buf, VIRGL_TEXTURE_BARRIER_FLAGS);
   vrend_texture_barrier(ctx, flags);
   return 0;
}

static int vrend_decode_set_debug_mask(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   char *flagstring;
   int slen = sizeof(uint32_t) * length;

   if (length < VIRGL_SET_DEBUG_FLAGS_MIN_SIZE)
      return EINVAL;

   const uint32_t *flag_buf = get_buf_ptr(buf, VIRGL_SET_DEBUG_FLAGSTRING_OFFSET);
   flagstring = malloc(slen+1);

   if (!flagstring) {
      return ENOMEM;
   }

   memcpy(flagstring, flag_buf, slen);
   flagstring[slen] = 0;
   vrend_context_set_debug_flags(ctx, flagstring);

   free(flagstring);

   return 0;
}

static int vrend_decode_set_tweaks(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   VREND_DEBUG(dbg_tweak, NULL, "Received TWEAK set command\n");

   if (length < VIRGL_SET_TWEAKS_SIZE)
      return EINVAL;

   uint32_t tweak_id = get_buf_entry(buf, VIRGL_SET_TWEAKS_ID);
   uint32_t tweak_value = get_buf_entry(buf, VIRGL_SET_TWEAKS_VALUE);

   vrend_set_active_tweaks(vrend_get_context_tweaks(ctx), tweak_id, tweak_value);
   return 0;
}


static int vrend_decode_transfer3d(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_box box;
   uint32_t dst_handle;
   struct vrend_transfer_info info;

   if (length < VIRGL_TRANSFER3D_SIZE)
      return EINVAL;

   memset(&info, 0, sizeof(info));
   info.box = &box;
   vrend_decode_transfer_common(buf, &dst_handle, &info);
   info.offset = get_buf_entry(buf, VIRGL_TRANSFER3D_DATA_OFFSET);
   int transfer_mode = get_buf_entry(buf, VIRGL_TRANSFER3D_DIRECTION);

   if (transfer_mode != VIRGL_TRANSFER_TO_HOST &&
       transfer_mode != VIRGL_TRANSFER_FROM_HOST)
      return EINVAL;

   return vrend_renderer_transfer_iov(ctx, dst_handle, &info,
                                      transfer_mode);
}

static int vrend_decode_copy_transfer3d(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct pipe_box box;
   struct vrend_transfer_info info;
   uint32_t dst_handle;
   uint32_t src_handle;

   if (length != VIRGL_COPY_TRANSFER3D_SIZE)
      return EINVAL;

   memset(&info, 0, sizeof(info));
   info.box = &box;

   // synchronized is set either to 1 or 0. This means that we can use other bits
   // to identify the direction of copy transfer
   uint32_t flags = get_buf_entry(buf, VIRGL_COPY_TRANSFER3D_FLAGS);
   bool read_from_host = (flags & VIRGL_COPY_TRANSFER3D_FLAGS_READ_FROM_HOST) != 0;
   info.synchronized = (flags & VIRGL_COPY_TRANSFER3D_FLAGS_SYNCHRONIZED) != 0;

   if (!read_from_host) {
      // this means that guest would like to make transfer to host
      // it can also mean that guest is using legacy copy transfer path
      vrend_decode_transfer_common(buf, &dst_handle, &info);
      info.offset = get_buf_entry(buf, VIRGL_COPY_TRANSFER3D_SRC_RES_OFFSET);
      src_handle = get_buf_entry(buf, VIRGL_COPY_TRANSFER3D_SRC_RES_HANDLE);

      return vrend_renderer_copy_transfer3d(ctx, dst_handle, src_handle,
                                             &info);
   } else {
      vrend_decode_transfer_common(buf, &src_handle, &info);
      info.offset = get_buf_entry(buf, VIRGL_COPY_TRANSFER3D_SRC_RES_OFFSET);
      dst_handle = get_buf_entry(buf, VIRGL_COPY_TRANSFER3D_SRC_RES_HANDLE);

      return vrend_renderer_copy_transfer3d_from_host(ctx, dst_handle, src_handle,
                                                      &info);
   }
}

static int vrend_decode_pipe_resource_create(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct vrend_renderer_resource_create_args args = { 0 };
   uint32_t blob_id;

   if (length != VIRGL_PIPE_RES_CREATE_SIZE)
      return EINVAL;

   args.target = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_TARGET);
   args.format = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_FORMAT);
   args.bind = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_BIND);
   args.width = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_WIDTH);
   args.height = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_HEIGHT);
   args.depth = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_DEPTH);
   args.array_size = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_ARRAY_SIZE);
   args.last_level = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_LAST_LEVEL);
   args.nr_samples = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_NR_SAMPLES);
   args.flags = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_FLAGS);
   blob_id = get_buf_entry(buf, VIRGL_PIPE_RES_CREATE_BLOB_ID);

   return vrend_renderer_pipe_resource_create(ctx, blob_id, &args);
}

static int vrend_decode_pipe_resource_set_type(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   struct vrend_renderer_resource_set_type_args args = { 0 };
   uint32_t res_id;

   if (length >= VIRGL_PIPE_RES_SET_TYPE_SIZE(0))
      args.plane_count = (length - VIRGL_PIPE_RES_SET_TYPE_SIZE(0)) / 2;

   if (length != VIRGL_PIPE_RES_SET_TYPE_SIZE(args.plane_count) ||
       !args.plane_count || args.plane_count > VIRGL_GBM_MAX_PLANES)
      return EINVAL;

   res_id = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_RES_HANDLE);
   args.format = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_FORMAT);
   args.bind = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_BIND);
   args.width = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_WIDTH);
   args.height = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_HEIGHT);
   args.usage = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_USAGE);
   args.modifier = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_MODIFIER_LO);
   args.modifier |= (uint64_t)get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_MODIFIER_HI) << 32;
   for (uint32_t i = 0; i < args.plane_count; i++) {
      args.plane_strides[i] = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_PLANE_STRIDE(i));
      args.plane_offsets[i] = get_buf_entry(buf, VIRGL_PIPE_RES_SET_TYPE_PLANE_OFFSET(i));
   }

   return vrend_renderer_pipe_resource_set_type(ctx, res_id, &args);
}

static void vrend_decode_ctx_init_base(struct vrend_decode_ctx *dctx,
                                       uint32_t ctx_id);

static void vrend_decode_ctx_fence_retire(uint64_t fence_id,
                                          void *retire_data)
{
   struct vrend_decode_ctx *dctx = retire_data;
   dctx->base.fence_retire(&dctx->base, 0, fence_id);
}

struct virgl_context *vrend_renderer_context_create(uint32_t handle,
                                                    uint32_t nlen,
                                                    const char *debug_name)
{
   struct vrend_decode_ctx *dctx;

   dctx = malloc(sizeof(struct vrend_decode_ctx));
   if (!dctx)
      return NULL;

   vrend_decode_ctx_init_base(dctx, handle);

   dctx->grctx = vrend_create_context(handle, nlen, debug_name);
   if (!dctx->grctx) {
      free(dctx);
      return NULL;
   }

   vrend_renderer_set_fence_retire(dctx->grctx,
                                   vrend_decode_ctx_fence_retire,
                                   dctx);

   return &dctx->base;
}

static void vrend_decode_ctx_destroy(struct virgl_context *ctx)
{
   TRACE_FUNC();
   struct vrend_decode_ctx *dctx = (struct vrend_decode_ctx *)ctx;

   vrend_destroy_context(dctx->grctx);
   free(dctx);
}

static void vrend_decode_ctx_attach_resource(struct virgl_context *ctx,
                                             struct virgl_resource *res)
{
   TRACE_FUNC();
   struct vrend_decode_ctx *dctx = (struct vrend_decode_ctx *)ctx;
   vrend_renderer_attach_res_ctx(dctx->grctx, res);
}

static void vrend_decode_ctx_detach_resource(struct virgl_context *ctx,
                                             struct virgl_resource *res)
{
   TRACE_FUNC();
   struct vrend_decode_ctx *dctx = (struct vrend_decode_ctx *)ctx;
   vrend_renderer_detach_res_ctx(dctx->grctx, res);
}

static int vrend_decode_ctx_transfer_3d(struct virgl_context *ctx,
                                        struct virgl_resource *res,
                                        const struct vrend_transfer_info *info,
                                        int transfer_mode)
{
   TRACE_FUNC();
   struct vrend_decode_ctx *dctx = (struct vrend_decode_ctx *)ctx;
   int ret = vrend_renderer_transfer_iov(dctx->grctx, res->res_id, info,
                                         transfer_mode);
   return vrend_check_no_error(dctx->grctx) || ret ? ret : EINVAL;
}

static int vrend_decode_ctx_get_blob(struct virgl_context *ctx,
                                     UNUSED uint32_t res_id,
                                     uint64_t blob_id,
                                     UNUSED uint64_t blob_size,
                                     UNUSED uint32_t blob_flags,
                                     struct virgl_context_blob *blob)
{
   TRACE_FUNC();
   struct vrend_decode_ctx *dctx = (struct vrend_decode_ctx *)ctx;

   blob->type = VIRGL_RESOURCE_FD_INVALID;
   /* this transfers ownership and blob_id is no longer valid */
   blob->u.pipe_resource = vrend_get_blob_pipe(dctx->grctx, blob_id);
   if (!blob->u.pipe_resource)
      return -EINVAL;

   blob->map_info = vrend_renderer_resource_get_map_info(blob->u.pipe_resource);
   return 0;
}

static int vrend_decode_get_memory_info(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   TRACE_FUNC();
   if (length != 1)
      return EINVAL;

   uint32_t res_handle = get_buf_entry(buf, 1);

   vrend_renderer_get_meminfo(ctx, res_handle);

   return 0;
}

static int vrend_decode_send_string_marker(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   uint32_t buf_len = sizeof(uint32_t) * (length - 1);

   if (length < VIRGL_SEND_STRING_MARKER_MIN_SIZE) {
      fprintf(stderr, "minimal command length not okay\n");
      return EINVAL;
   }

   uint32_t str_len = get_buf_entry(buf, VIRGL_SEND_STRING_MARKER_STRING_SIZE);
   if (str_len > buf_len) {
       fprintf(stderr, "String len %u > buf_len %u\n", str_len, buf_len);
       return EINVAL;
   }

   vrend_context_emit_string_marker(ctx, str_len, get_buf_ptr(buf, VIRGL_SEND_STRING_MARKER_OFFSET));

   return 0;
}

#ifdef ENABLE_VIDEO
/* video codec related functions */

static int vrend_decode_create_video_codec(struct vrend_context *ctx,
                                           const uint32_t *buf,
                                           uint32_t length)
{
   struct vrend_video_context *vctx = vrend_context_get_video_ctx(ctx);

   if (length < VIRGL_CREATE_VIDEO_CODEC_MIN_SIZE)
      return EINVAL;

   uint32_t handle     = get_buf_entry(buf, VIRGL_CREATE_VIDEO_CODEC_HANDLE);
   uint32_t profile    = get_buf_entry(buf, VIRGL_CREATE_VIDEO_CODEC_PROFILE);
   uint32_t entrypoint = get_buf_entry(buf, VIRGL_CREATE_VIDEO_CODEC_ENTRYPOINT);
   uint32_t chroma_fmt = get_buf_entry(buf, VIRGL_CREATE_VIDEO_CODEC_CHROMA_FMT);
   uint32_t level      = get_buf_entry(buf, VIRGL_CREATE_VIDEO_CODEC_LEVEL);
   uint32_t width      = get_buf_entry(buf, VIRGL_CREATE_VIDEO_CODEC_WIDTH);
   uint32_t height     = get_buf_entry(buf, VIRGL_CREATE_VIDEO_CODEC_HEIGHT);
   uint32_t max_ref    = 2; /* The max number of ref frames is 2 by default */

   if (length >= VIRGL_CREATE_VIDEO_CODEC_MAX_REF)
      max_ref = get_buf_entry(buf, VIRGL_CREATE_VIDEO_CODEC_MAX_REF);

   vrend_video_create_codec(vctx, handle, profile, entrypoint,
                            chroma_fmt, level, width, height, max_ref, 0);

   return 0;
}

static int vrend_decode_destroy_video_codec(struct vrend_context *ctx,
                                            const uint32_t *buf,
                                            uint32_t length)
{
   struct vrend_video_context *vctx = vrend_context_get_video_ctx(ctx);

    if (length < VIRGL_DESTROY_VIDEO_CODEC_MIN_SIZE)
       return EINVAL;

   uint32_t handle = get_buf_entry(buf, VIRGL_DESTROY_VIDEO_CODEC_HANDLE);
   vrend_video_destroy_codec(vctx, handle);

   return 0;
}

static int vrend_decode_create_video_buffer(struct vrend_context *ctx,
                                            const uint32_t *buf,
                                            uint32_t length)
{
   uint32_t i, num_res;
   uint32_t res_handles[VREND_VIDEO_BUFFER_PLANE_NUM];
   struct vrend_video_context *vctx = vrend_context_get_video_ctx(ctx);

   if (length < VIRGL_CREATE_VIDEO_BUFFER_MIN_SIZE)
      return EINVAL;

   num_res = length - VIRGL_CREATE_VIDEO_BUFFER_RES_BASE + 1;
   if (num_res > VREND_VIDEO_BUFFER_PLANE_NUM)
       num_res = VREND_VIDEO_BUFFER_PLANE_NUM;

   uint32_t handle = get_buf_entry(buf, VIRGL_CREATE_VIDEO_BUFFER_HANDLE);
   uint32_t format = get_buf_entry(buf, VIRGL_CREATE_VIDEO_BUFFER_FORMAT);
   uint32_t width  = get_buf_entry(buf, VIRGL_CREATE_VIDEO_BUFFER_WIDTH);
   uint32_t height = get_buf_entry(buf, VIRGL_CREATE_VIDEO_BUFFER_HEIGHT);

   memset(res_handles, 0, sizeof(res_handles));
   for (i = 0; i < num_res; i++)
       res_handles[i] = get_buf_entry(buf,
                                      VIRGL_CREATE_VIDEO_BUFFER_RES_BASE + i);

   vrend_video_create_buffer(vctx, handle, format, width, height,
                                      res_handles, num_res);

   return 0;
}

static int vrend_decode_destroy_video_buffer(struct vrend_context *ctx,
                                             const uint32_t *buf,
                                             uint32_t length)
{
   struct vrend_video_context *vctx = vrend_context_get_video_ctx(ctx);

   if (length < VIRGL_DESTROY_VIDEO_BUFFER_MIN_SIZE)
      return EINVAL;

   uint32_t handle = get_buf_entry(buf, VIRGL_DESTROY_VIDEO_BUFFER_HANDLE);
   vrend_video_destroy_buffer(vctx, handle);

   return 0;
}

static int vrend_decode_begin_frame(struct vrend_context *ctx,
                                    const uint32_t *buf,
                                    uint32_t length)
{
   struct vrend_video_context *vctx = vrend_context_get_video_ctx(ctx);

   if (length < VIRGL_BEGIN_FRAME_MIN_SIZE)
      return EINVAL;

   uint32_t cdc_handle = get_buf_entry(buf, VIRGL_BEGIN_FRAME_CDC_HANDLE);
   uint32_t tgt_handle = get_buf_entry(buf, VIRGL_BEGIN_FRAME_TGT_HANDLE);
   vrend_video_begin_frame(vctx, cdc_handle, tgt_handle);

   return 0;
}

static int vrend_decode_decode_bitstream(struct vrend_context *ctx,
                                         const uint32_t *buf,
                                         uint32_t length)
{
   struct vrend_video_context *vctx = vrend_context_get_video_ctx(ctx);

   if (length < VIRGL_DECODE_BS_MIN_SIZE)
      return EINVAL;

   uint32_t cdc_handle = get_buf_entry(buf, VIRGL_DECODE_BS_CDC_HANDLE);
   uint32_t tgt_handle = get_buf_entry(buf, VIRGL_DECODE_BS_TGT_HANDLE);
   uint32_t dsc_handle = get_buf_entry(buf, VIRGL_DECODE_BS_DSC_HANDLE);
   uint32_t buf_handle = get_buf_entry(buf, VIRGL_DECODE_BS_BUF_HANDLE);
   uint32_t buf_size   = get_buf_entry(buf, VIRGL_DECODE_BS_BUF_SIZE);

   vrend_video_decode_bitstream(vctx, cdc_handle, tgt_handle,
                                dsc_handle, 1, &buf_handle, &buf_size);

   return 0;
}

static int vrend_decode_encode_bitstream(struct vrend_context *ctx,
                                         const uint32_t *buf,
                                         uint32_t length)
{
   struct vrend_video_context *vctx = vrend_context_get_video_ctx(ctx);

   if (length < VIRGL_ENCODE_BS_MIN_SIZE)
      return EINVAL;

   uint32_t cdc_handle  = get_buf_entry(buf, VIRGL_ENCODE_BS_CDC_HANDLE);
   uint32_t src_handle  = get_buf_entry(buf, VIRGL_ENCODE_BS_SRC_HANDLE);
   uint32_t dest_handle = get_buf_entry(buf, VIRGL_ENCODE_BS_DEST_HANDLE);
   uint32_t desc_handle = get_buf_entry(buf, VIRGL_ENCODE_BS_DESC_HANDLE);
   uint32_t feed_handle = get_buf_entry(buf, VIRGL_ENCODE_BS_FEED_HANDLE);

   vrend_video_encode_bitstream(vctx, cdc_handle, src_handle, dest_handle,
                                desc_handle, feed_handle);

   return 0;
}

static int vrend_decode_end_frame(struct vrend_context *ctx,
                                  const uint32_t *buf,
                                  uint32_t length)
{
   struct vrend_video_context *vctx = vrend_context_get_video_ctx(ctx);

   if (length < VIRGL_END_FRAME_MIN_SIZE)
      return EINVAL;

   uint32_t cdc_handle = get_buf_entry(buf, VIRGL_END_FRAME_CDC_HANDLE);
   uint32_t tgt_handle = get_buf_entry(buf, VIRGL_END_FRAME_TGT_HANDLE);

   vrend_video_end_frame(vctx, cdc_handle, tgt_handle);

   return 0;
}

#else

static int vrend_unsupported(struct vrend_context *ctx,
                                    const uint32_t *buf,
                                    uint32_t length)
{
   (void)ctx;
   (void)buf;
   (void)length;
   return EINVAL;
}

#endif /* ENABLE_VIDEO */


typedef int (*vrend_decode_callback)(struct vrend_context *ctx, const uint32_t *buf, uint32_t length);

static int vrend_decode_dummy(struct vrend_context *ctx, const uint32_t *buf, uint32_t length)
{
   (void)ctx;
   (void)buf;
   (void)length;
   return 0;
}

static const vrend_decode_callback decode_table[VIRGL_MAX_COMMANDS] = {
   [VIRGL_CCMD_NOP] = vrend_decode_dummy,
   [VIRGL_CCMD_CREATE_OBJECT] = vrend_decode_create_object,
   [VIRGL_CCMD_BIND_OBJECT] = vrend_decode_bind_object,
   [VIRGL_CCMD_DESTROY_OBJECT] = vrend_decode_destroy_object,
   [VIRGL_CCMD_CLEAR] = vrend_decode_clear,
   [VIRGL_CCMD_CLEAR_TEXTURE] = vrend_decode_clear_texture,
   [VIRGL_CCMD_DRAW_VBO] = vrend_decode_draw_vbo,
   [VIRGL_CCMD_SET_FRAMEBUFFER_STATE] = vrend_decode_set_framebuffer_state,
   [VIRGL_CCMD_SET_VERTEX_BUFFERS] = vrend_decode_set_vertex_buffers,
   [VIRGL_CCMD_RESOURCE_INLINE_WRITE] = vrend_decode_resource_inline_write,
   [VIRGL_CCMD_SET_VIEWPORT_STATE] = vrend_decode_set_viewport_state,
   [VIRGL_CCMD_SET_SAMPLER_VIEWS] = vrend_decode_set_sampler_views,
   [VIRGL_CCMD_SET_INDEX_BUFFER] = vrend_decode_set_index_buffer,
   [VIRGL_CCMD_SET_CONSTANT_BUFFER] = vrend_decode_set_constant_buffer,
   [VIRGL_CCMD_SET_STENCIL_REF] = vrend_decode_set_stencil_ref,
   [VIRGL_CCMD_SET_BLEND_COLOR] = vrend_decode_set_blend_color,
   [VIRGL_CCMD_SET_SCISSOR_STATE] = vrend_decode_set_scissor_state,
   [VIRGL_CCMD_BLIT] = vrend_decode_blit,
   [VIRGL_CCMD_RESOURCE_COPY_REGION] = vrend_decode_resource_copy_region,
   [VIRGL_CCMD_BIND_SAMPLER_STATES] = vrend_decode_bind_sampler_states,
   [VIRGL_CCMD_BEGIN_QUERY] = vrend_decode_begin_query,
   [VIRGL_CCMD_END_QUERY] = vrend_decode_end_query,
   [VIRGL_CCMD_GET_QUERY_RESULT] = vrend_decode_get_query_result,
   [VIRGL_CCMD_SET_POLYGON_STIPPLE] = vrend_decode_set_polygon_stipple,
   [VIRGL_CCMD_SET_CLIP_STATE] = vrend_decode_set_clip_state,
   [VIRGL_CCMD_SET_SAMPLE_MASK] = vrend_decode_set_sample_mask,
   [VIRGL_CCMD_SET_MIN_SAMPLES] = vrend_decode_set_min_samples,
   [VIRGL_CCMD_SET_STREAMOUT_TARGETS] = vrend_decode_set_streamout_targets,
   [VIRGL_CCMD_SET_RENDER_CONDITION] = vrend_decode_set_render_condition,
   [VIRGL_CCMD_SET_UNIFORM_BUFFER] = vrend_decode_set_uniform_buffer,
   [VIRGL_CCMD_SET_SUB_CTX] = vrend_decode_set_sub_ctx,
   [VIRGL_CCMD_CREATE_SUB_CTX] = vrend_decode_create_sub_ctx,
   [VIRGL_CCMD_DESTROY_SUB_CTX] = vrend_decode_destroy_sub_ctx,
   [VIRGL_CCMD_BIND_SHADER] = vrend_decode_bind_shader,
   [VIRGL_CCMD_SET_TESS_STATE] = vrend_decode_set_tess_state,
   [VIRGL_CCMD_SET_SHADER_BUFFERS] = vrend_decode_set_shader_buffers,
   [VIRGL_CCMD_SET_SHADER_IMAGES] = vrend_decode_set_shader_images,
   [VIRGL_CCMD_SET_ATOMIC_BUFFERS] = vrend_decode_set_atomic_buffers,
   [VIRGL_CCMD_MEMORY_BARRIER] = vrend_decode_memory_barrier,
   [VIRGL_CCMD_LAUNCH_GRID] = vrend_decode_launch_grid,
   [VIRGL_CCMD_SET_FRAMEBUFFER_STATE_NO_ATTACH] = vrend_decode_set_framebuffer_state_no_attach,
   [VIRGL_CCMD_TEXTURE_BARRIER] = vrend_decode_texture_barrier,
   [VIRGL_CCMD_SET_DEBUG_FLAGS] = vrend_decode_set_debug_mask,
   [VIRGL_CCMD_GET_QUERY_RESULT_QBO] = vrend_decode_get_query_result_qbo,
   [VIRGL_CCMD_TRANSFER3D] = vrend_decode_transfer3d,
   [VIRGL_CCMD_COPY_TRANSFER3D] = vrend_decode_copy_transfer3d,
   [VIRGL_CCMD_END_TRANSFERS] = vrend_decode_dummy,
   [VIRGL_CCMD_SET_TWEAKS] = vrend_decode_set_tweaks,
   [VIRGL_CCMD_PIPE_RESOURCE_CREATE] = vrend_decode_pipe_resource_create,
   [VIRGL_CCMD_PIPE_RESOURCE_SET_TYPE] = vrend_decode_pipe_resource_set_type,
   [VIRGL_CCMD_GET_MEMORY_INFO] = vrend_decode_get_memory_info,
   [VIRGL_CCMD_SEND_STRING_MARKER] = vrend_decode_send_string_marker,
   [VIRGL_CCMD_LINK_SHADER] = vrend_decode_link_shader,
#ifdef ENABLE_VIDEO
   [VIRGL_CCMD_CREATE_VIDEO_CODEC] = vrend_decode_create_video_codec,
   [VIRGL_CCMD_DESTROY_VIDEO_CODEC] = vrend_decode_destroy_video_codec,
   [VIRGL_CCMD_CREATE_VIDEO_BUFFER] = vrend_decode_create_video_buffer,
   [VIRGL_CCMD_DESTROY_VIDEO_BUFFER] = vrend_decode_destroy_video_buffer,
   [VIRGL_CCMD_BEGIN_FRAME] = vrend_decode_begin_frame,
   [VIRGL_CCMD_DECODE_MACROBLOCK] = vrend_decode_dummy,
   [VIRGL_CCMD_DECODE_BITSTREAM] = vrend_decode_decode_bitstream,
   [VIRGL_CCMD_ENCODE_BITSTREAM] = vrend_decode_encode_bitstream,
   [VIRGL_CCMD_END_FRAME] = vrend_decode_end_frame,
#else
   [VIRGL_CCMD_CREATE_VIDEO_CODEC] = vrend_unsupported,
   [VIRGL_CCMD_DESTROY_VIDEO_CODEC] = vrend_unsupported,
   [VIRGL_CCMD_CREATE_VIDEO_BUFFER] = vrend_unsupported,
   [VIRGL_CCMD_DESTROY_VIDEO_BUFFER] = vrend_unsupported,
   [VIRGL_CCMD_BEGIN_FRAME] = vrend_unsupported,
   [VIRGL_CCMD_DECODE_MACROBLOCK] = vrend_unsupported,
   [VIRGL_CCMD_DECODE_BITSTREAM] = vrend_unsupported,
   [VIRGL_CCMD_ENCODE_BITSTREAM] = vrend_unsupported,
   [VIRGL_CCMD_END_FRAME] = vrend_unsupported,
#endif
};

static int vrend_decode_ctx_submit_cmd(struct virgl_context *ctx,
                                       const void *buffer,
                                       size_t size)
{
   TRACE_FUNC();
   struct vrend_decode_ctx *gdctx = (struct vrend_decode_ctx *)ctx;
   bool bret;
   int ret;

   bret = vrend_hw_switch_context(gdctx->grctx, true);
   if (bret == false)
      return EINVAL;

   const uint32_t *typed_buf = (const uint32_t *)buffer;
   const uint32_t buf_total = (uint32_t)(size / sizeof(uint32_t));
   uint32_t buf_offset = 0;

   while (buf_offset < buf_total) {
      const uint32_t cur_offset = buf_offset;
      const uint32_t *buf = &typed_buf[buf_offset];
      uint32_t len = *buf >> 16;
      uint32_t cmd = *buf & 0xff;

      if (cmd >= VIRGL_MAX_COMMANDS)
         return EINVAL;

      buf_offset += len + 1;

      ret = 0;
      /* check if the guest is doing something bad */
      if (buf_offset > buf_total) {
         vrend_report_buffer_error(gdctx->grctx, 0);
         break;
      }

      VREND_DEBUG(dbg_cmd, gdctx->grctx, "%-4d %-20s len:%d\n",
                  cur_offset, vrend_get_comand_name(cmd), len);

      TRACE_SCOPE_SLOW(vrend_get_comand_name(cmd));

      ret = decode_table[cmd](gdctx->grctx, buf, len);
      if (!vrend_check_no_error(gdctx->grctx) && !ret)
         ret = EINVAL;
      if (ret) {
         vrend_printf("context %d failed to dispatch %s: %d\n",
               gdctx->base.ctx_id, vrend_get_comand_name(cmd), ret);
         if (ret == EINVAL)
            vrend_report_buffer_error(gdctx->grctx, *buf);
         return ret;
      }
   }
   return 0;
}

static int vrend_decode_ctx_get_fencing_fd(UNUSED struct virgl_context *ctx)
{
   return vrend_renderer_get_poll_fd();
}

static void vrend_decode_ctx_retire_fences(UNUSED struct virgl_context *ctx)
{
   vrend_renderer_check_fences();
}

static int vrend_decode_ctx_submit_fence(struct virgl_context *ctx,
                                         uint32_t flags,
                                         uint32_t ring_idx,
                                         uint64_t fence_id)
{
   struct vrend_decode_ctx *dctx = (struct vrend_decode_ctx *)ctx;

   if (ring_idx)
      return -EINVAL;

   return vrend_renderer_create_fence(dctx->grctx, flags, fence_id);
}

static void vrend_decode_ctx_init_base(struct vrend_decode_ctx *dctx,
                                       uint32_t ctx_id)
{
   struct virgl_context *ctx = &dctx->base ;

   for (unsigned i = 0; i < VIRGL_MAX_COMMANDS; ++i)
      assert(decode_table[i]);

   ctx->ctx_id = ctx_id;
   ctx->destroy = vrend_decode_ctx_destroy;
   ctx->attach_resource = vrend_decode_ctx_attach_resource;
   ctx->detach_resource = vrend_decode_ctx_detach_resource;
   ctx->transfer_3d = vrend_decode_ctx_transfer_3d;
   ctx->get_blob = vrend_decode_ctx_get_blob;
   ctx->submit_cmd = vrend_decode_ctx_submit_cmd;

   ctx->get_fencing_fd = vrend_decode_ctx_get_fencing_fd;
   ctx->retire_fences = vrend_decode_ctx_retire_fences;
   ctx->submit_fence = vrend_decode_ctx_submit_fence;
}
