static int vrend_transfer_send_readpixels(struct vrend_resource *res,
                                          struct iovec *iov, int num_iovs,
                                          const struct vrend_transfer_info *info)
{
   char *myptr = (char*)iov[0].iov_base + info->offset;
   int need_temp = 0;
   GLuint fb_id;
   char *data;
   bool actually_invert, separate_invert = false;
   GLenum format, type;
   GLint y1;
   uint32_t send_size = 0;
   uint32_t h = u_minify(res->base.height0, info->level);
   int elsize = util_format_get_blocksize(res->base.format);
   float depth_scale;
   int row_stride = info->stride / elsize;
   GLint old_fbo;

   glUseProgram(0);

   enum virgl_formats fmt = vrend_format_replace_emulated(res->base.bind, res->base.format);
   format = tex_conv_table[fmt].glformat;
   type = tex_conv_table[fmt].gltype;
   /* if we are asked to invert and reading from a front then don't */

   actually_invert = res->y_0_top;

   if (actually_invert && !has_feature(feat_mesa_invert))
      separate_invert = true;

   if (num_iovs > 1 || separate_invert)
      need_temp = 1;

   if (need_temp) {
      send_size = util_format_get_nblocks(res->base.format, info->box->width, info->box->height) * info->box->depth * util_format_get_blocksize(res->base.format);
      data = malloc(send_size);
      if (!data) {
         vrend_printf("malloc failed %d\n", send_size);
         return ENOMEM;
      }
   } else {
      send_size = iov[0].iov_len - info->offset;
      data = myptr;
      if (!row_stride)
         row_stride = util_format_get_nblocksx(res->base.format, u_minify(res->base.width0, info->level));
   }

   glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_fbo);

   if (res->readback_fb_id == 0 || (int)res->readback_fb_level != info->level ||
       (int)res->readback_fb_z != info->box->z) {

      if (res->readback_fb_id)
         glDeleteFramebuffers(1, &res->readback_fb_id);

      glGenFramebuffers(1, &fb_id);
      glBindFramebuffer(GL_FRAMEBUFFER, fb_id);

      vrend_fb_bind_texture(res, 0, info->level, info->box->z);

      res->readback_fb_id = fb_id;
      res->readback_fb_level = info->level;
      res->readback_fb_z = info->box->z;
   } else
      glBindFramebuffer(GL_FRAMEBUFFER, res->readback_fb_id);
   if (actually_invert)
      y1 = h - info->box->y - info->box->height;
   else
      y1 = info->box->y;

   if (has_feature(feat_mesa_invert) && actually_invert)
      glPixelStorei(GL_PACK_INVERT_MESA, 1);
   if (!vrend_format_is_ds(res->base.format))
      glReadBuffer(GL_COLOR_ATTACHMENT0);
   if (!need_temp && row_stride)
      glPixelStorei(GL_PACK_ROW_LENGTH, row_stride);

   switch (elsize) {
   case 1:
      glPixelStorei(GL_PACK_ALIGNMENT, 1);
      break;
   case 2:
      glPixelStorei(GL_PACK_ALIGNMENT, 2);
      break;
   case 4:
   default:
      glPixelStorei(GL_PACK_ALIGNMENT, 4);
      break;
   case 8:
      glPixelStorei(GL_PACK_ALIGNMENT, 8);
      break;
   }

   if (res->base.format == (enum pipe_format)VIRGL_FORMAT_Z24X8_UNORM) {
      /* we get values from the guest as 24-bit scaled integers
         but we give them to the host GL and it interprets them
         as 32-bit scaled integers, so we need to scale them here */
      depth_scale = 1.0 / 256.0;
      if (!vrend_state.use_core_profile) {
         glPixelTransferf(GL_DEPTH_SCALE, depth_scale);
      }
   }

   /* Warn if the driver doesn't agree about the read format and type.
      On desktop GL we can use basically any format and type to glReadPixels,
      so we picked the format and type that matches the native format.

      But on GLES we are limited to a very few set, luckily most GLES
      implementations should return type and format that match the native
      formats, and can be used for glReadPixels acording to the GLES spec.

      But we have found that at least Mesa returned the wrong formats, again
      luckily we are able to change Mesa. But just in case there are more bad
      drivers out there, or we mess up the format somewhere, we warn here. */
   if (vrend_state.use_gles) {
      GLint imp;
      if (type != GL_UNSIGNED_BYTE && type != GL_UNSIGNED_INT &&
          type != GL_INT && type != GL_FLOAT) {
         glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &imp);
         if (imp != (GLint)type) {
            vrend_printf( "GL_IMPLEMENTATION_COLOR_READ_TYPE is not expected native type 0x%x != imp 0x%x\n", type, imp);
         }
      }
      if (format != GL_RGBA && format != GL_RGBA_INTEGER) {
         glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &imp);
         if (imp != (GLint)format) {
            vrend_printf( "GL_IMPLEMENTATION_COLOR_READ_FORMAT is not expected native format 0x%x != imp 0x%x\n", format, imp);
         }
      }
   }

   do_readpixels(info->box->x, y1, info->box->width, info->box->height, format, type, send_size, data);

   if (res->base.format == (enum pipe_format)VIRGL_FORMAT_Z24X8_UNORM) {
      if (!vrend_state.use_core_profile)
         glPixelTransferf(GL_DEPTH_SCALE, 1.0);
      else
         vrend_scale_depth(data, send_size, depth_scale);
   }
   if (has_feature(feat_mesa_invert) && actually_invert)
      glPixelStorei(GL_PACK_INVERT_MESA, 0);
   if (!need_temp && row_stride)
      glPixelStorei(GL_PACK_ROW_LENGTH, 0);
   glPixelStorei(GL_PACK_ALIGNMENT, 4);
   if (need_temp) {
      write_transfer_data(&res->base, iov, num_iovs, data,
                          info->stride, info->box, info->level, info->offset,
                          separate_invert);
      free(data);
   }

   glBindFramebuffer(GL_FRAMEBUFFER, old_fbo);

   return 0;
}