static int vrend_transfer_send_readpixels(struct vrend_context *ctx,
                                          struct vrend_resource *res,
                                          const 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;
   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;

   if (ctx)
      vrend_use_program(ctx->sub, 0);
   else
      glUseProgram(0);

   enum virgl_formats fmt = 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 UTIL_ARCH_BIG_ENDIAN
   glPixelStorei(GL_PACK_SWAP_BYTES, 1);
#endif

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

   if (vrend_state.use_gles && vrend_format_is_bgra(res->base.format) &&
       !vrend_resource_is_emulated_bgra(res))
       need_temp = true;

   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 (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 (!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 == 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);
      }
   }

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

   /* on GLES, texture-backed BGR* resources are always stored with RGB* internal format, but
    * the guest will expect to readback the data in BGRA format.
    * Since the GLES API doesn't allow format conversions like GL, we CPU-swizzle the data
    * on upload and need to do the same on readback.
    * The notable exception is externally-stored (GBM/EGL) BGR* resources, for which BGR*
    * byte-ordering is used instead to match external access patterns. */
   if (vrend_state.use_gles && vrend_format_is_bgra(res->base.format) &&
       !vrend_resource_is_emulated_bgra(res)) {
      VREND_DEBUG(dbg_bgra, ctx, "manually swizzling rgba->bgra on readback since gles+bgra\n");
      vrend_swizzle_data_bgra(send_size, data);
   }

   if (res->base.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 UTIL_ARCH_BIG_ENDIAN
   glPixelStorei(GL_PACK_SWAP_BYTES, 0);
#endif

   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;
}