int vrend_create_vertex_elements_state(struct vrend_context *ctx,
                                       uint32_t handle,
                                       unsigned num_elements,
                                       const struct pipe_vertex_element *elements)
{
   struct vrend_vertex_element_array *v;
   const struct util_format_description *desc;
   GLenum type;
   uint i;
   uint32_t ret_handle;

   if (num_elements > PIPE_MAX_ATTRIBS)
      return EINVAL;

   v = CALLOC_STRUCT(vrend_vertex_element_array);
   if (!v)
      return ENOMEM;

   v->count = num_elements;
   for (i = 0; i < num_elements; i++) {
      memcpy(&v->elements[i].base, &elements[i], sizeof(struct pipe_vertex_element));

      desc = util_format_description(elements[i].src_format);
      if (!desc) {
         FREE(v);
         return EINVAL;
      }

      type = GL_FALSE;
      switch (desc->channel[0].type) {
      case UTIL_FORMAT_TYPE_FLOAT:
         switch (desc->channel[0].size) {
         case 16: type = GL_HALF_FLOAT; break;
         case 32: type = GL_FLOAT; break;
         case 64: type = GL_DOUBLE; break;
         }
         break;
      case UTIL_FORMAT_TYPE_UNSIGNED:
         switch (desc->channel[0].size) {
         case 8: type = GL_UNSIGNED_BYTE; break;
         case 16: type = GL_UNSIGNED_SHORT; break;
         case 32: type = GL_UNSIGNED_INT; break;
         }
         break;
      case UTIL_FORMAT_TYPE_SIGNED:
         switch (desc->channel[0].size) {
         case 8: type = GL_BYTE; break;
         case 16: type = GL_SHORT; break;
         case 32: type = GL_INT; break;
         }
         break;
      }
      if (type == GL_FALSE) {
         switch (elements[i].src_format) {
         case PIPE_FORMAT_R10G10B10A2_SSCALED:
         case PIPE_FORMAT_R10G10B10A2_SNORM:
         case PIPE_FORMAT_B10G10R10A2_SNORM:
            type = GL_INT_2_10_10_10_REV;
            break;
         case PIPE_FORMAT_R10G10B10A2_USCALED:
         case PIPE_FORMAT_R10G10B10A2_UNORM:
         case PIPE_FORMAT_B10G10R10A2_UNORM:
            type = GL_UNSIGNED_INT_2_10_10_10_REV;
            break;
         case PIPE_FORMAT_R11G11B10_FLOAT:
            type = GL_UNSIGNED_INT_10F_11F_11F_REV;
            break;
         default:
            ;
         }
      }

      if (type == GL_FALSE) {
         vrend_report_context_error(ctx, VIRGL_ERROR_CTX_ILLEGAL_VERTEX_FORMAT, elements[i].src_format);
         FREE(v);
         return EINVAL;
      }

      v->elements[i].type = type;
      if (desc->channel[0].normalized)
         v->elements[i].norm = GL_TRUE;
      if (desc->nr_channels == 4 && desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_Z)
         v->elements[i].nr_chan = GL_BGRA;
      else if (elements[i].src_format == PIPE_FORMAT_R11G11B10_FLOAT)
         v->elements[i].nr_chan = 3;
      else
         v->elements[i].nr_chan = desc->nr_channels;
   }

   if (has_feature(feat_gles31_vertex_attrib_binding)) {
      glGenVertexArrays(1, &v->id);
      glBindVertexArray(v->id);
      for (i = 0; i < num_elements; i++) {
         struct vrend_vertex_element *ve = &v->elements[i];

         if (util_format_is_pure_integer(ve->base.src_format)) {
            UPDATE_INT_SIGN_MASK(ve->base.src_format, i,
                                 v->signed_int_bitmask,
                                 v->unsigned_int_bitmask);
            glVertexAttribIFormat(i, ve->nr_chan, ve->type, ve->base.src_offset);
         }
         else
            glVertexAttribFormat(i, ve->nr_chan, ve->type, ve->norm, ve->base.src_offset);
         glVertexAttribBinding(i, ve->base.vertex_buffer_index);
         glVertexBindingDivisor(i, ve->base.instance_divisor);
         glEnableVertexAttribArray(i);
      }
   }
   ret_handle = vrend_renderer_object_insert(ctx, v, handle,
                                             VIRGL_OBJECT_VERTEX_ELEMENTS);
   if (!ret_handle) {
      FREE(v);
      return ENOMEM;
   }
   v->owning_sub = ctx->sub;
   return 0;
}