static void vrend_resource_copy_fallback(struct vrend_resource *src_res,
                                         struct vrend_resource *dst_res,
                                         uint32_t dst_level,
                                         uint32_t dstx, uint32_t dsty,
                                         uint32_t dstz, uint32_t src_level,
                                         const struct pipe_box *src_box)
{
   char *tptr;
   uint32_t total_size, src_stride, dst_stride, src_layer_stride;
   GLenum glformat, gltype;
   int elsize = util_format_get_blocksize(dst_res->base.format);
   int compressed = util_format_is_compressed(dst_res->base.format);
   int cube_slice = 1;
   uint32_t slice_size, slice_offset;
   int i;
   struct pipe_box box;

   if (src_res->target == GL_TEXTURE_CUBE_MAP)
      cube_slice = 6;

   if (src_res->base.format != dst_res->base.format) {
      vrend_printf( "copy fallback failed due to mismatched formats %d %d\n", src_res->base.format, dst_res->base.format);
      return;
   }

   box = *src_box;
   box.depth = vrend_get_texture_depth(src_res, src_level);
   dst_stride = util_format_get_stride(dst_res->base.format, dst_res->base.width0);

   /* this is ugly need to do a full GetTexImage */
   slice_size = util_format_get_nblocks(src_res->base.format, u_minify(src_res->base.width0, src_level), u_minify(src_res->base.height0, src_level)) *
                util_format_get_blocksize(src_res->base.format);
   total_size = slice_size * vrend_get_texture_depth(src_res, src_level);

   tptr = malloc(total_size);
   if (!tptr)
      return;

   glformat = tex_conv_table[src_res->base.format].glformat;
   gltype = tex_conv_table[src_res->base.format].gltype;

   if (compressed)
      glformat = tex_conv_table[src_res->base.format].internalformat;

   /* If we are on gles we need to rely on the textures backing
    * iovec to have the data we need, otherwise we can use glGetTexture
    */
   if (vrend_state.use_gles) {
      uint64_t src_offset = 0;
      uint64_t dst_offset = 0;
      if (src_level < VR_MAX_TEXTURE_2D_LEVELS) {
         src_offset = src_res->mipmap_offsets[src_level];
         dst_offset = dst_res->mipmap_offsets[src_level];
      }

      src_stride = util_format_get_nblocksx(src_res->base.format,
                                            u_minify(src_res->base.width0, src_level)) * elsize;
      src_layer_stride = util_format_get_2d_size(src_res->base.format,
                                                 src_stride,
                                                 u_minify(src_res->base.height0, src_level));
      read_transfer_data(src_res->iov, src_res->num_iovs, tptr,
                         src_res->base.format, src_offset,
                         src_stride, src_layer_stride, &box, false);
      /* When on GLES sync the iov that backs the dst resource because
       * we might need it in a chain copy A->B, B->C */
      write_transfer_data(&dst_res->base, dst_res->iov, dst_res->num_iovs, tptr,
                          dst_stride, &box, src_level, dst_offset, false);
      /* 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 */
      if (dst_res->base.format == (enum pipe_format)VIRGL_FORMAT_Z24X8_UNORM) {
         float depth_scale = 256.0;
         vrend_scale_depth(tptr, total_size, depth_scale);
      }
   } else {
      uint32_t read_chunk_size;
      switch (elsize) {
      case 1:
      case 3:
         glPixelStorei(GL_PACK_ALIGNMENT, 1);
         break;
      case 2:
      case 6:
         glPixelStorei(GL_PACK_ALIGNMENT, 2);
         break;
      case 4:
      default:
         glPixelStorei(GL_PACK_ALIGNMENT, 4);
         break;
      case 8:
         glPixelStorei(GL_PACK_ALIGNMENT, 8);
         break;
      }
      glBindTexture(src_res->target, src_res->id);
      slice_offset = 0;
      read_chunk_size = (src_res->target == GL_TEXTURE_CUBE_MAP) ? slice_size : total_size;
      for (i = 0; i < cube_slice; i++) {
         GLenum ctarget = src_res->target == GL_TEXTURE_CUBE_MAP ?
                            (GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i) : src_res->target;
         if (compressed) {
            if (has_feature(feat_arb_robustness))
               glGetnCompressedTexImageARB(ctarget, src_level, read_chunk_size, tptr + slice_offset);
            else
               glGetCompressedTexImage(ctarget, src_level, tptr + slice_offset);
         } else {
            if (has_feature(feat_arb_robustness))
               glGetnTexImageARB(ctarget, src_level, glformat, gltype, read_chunk_size, tptr + slice_offset);
            else
               glGetTexImage(ctarget, src_level, glformat, gltype, tptr + slice_offset);
         }
         slice_offset += slice_size;
      }
   }

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

   glBindTexture(dst_res->target, dst_res->id);
   slice_offset = src_box->z * slice_size;
   cube_slice = (src_res->target == GL_TEXTURE_CUBE_MAP) ? src_box->z + src_box->depth : cube_slice;
   i = (src_res->target == GL_TEXTURE_CUBE_MAP) ? src_box->z : 0;
   for (; i < cube_slice; i++) {
      GLenum ctarget = dst_res->target == GL_TEXTURE_CUBE_MAP ?
                          (GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i) : dst_res->target;
      if (compressed) {
         if (ctarget == GL_TEXTURE_1D) {
            glCompressedTexSubImage1D(ctarget, dst_level, dstx,
                                      src_box->width,
                                      glformat, slice_size, tptr + slice_offset);
         } else {
            glCompressedTexSubImage2D(ctarget, dst_level, dstx, dsty,
                                      src_box->width, src_box->height,
                                      glformat, slice_size, tptr + slice_offset);
         }
      } else {
         if (ctarget == GL_TEXTURE_1D) {
            glTexSubImage1D(ctarget, dst_level, dstx, src_box->width, glformat, gltype, tptr + slice_offset);
         } else if (ctarget == GL_TEXTURE_3D ||
                    ctarget == GL_TEXTURE_2D_ARRAY ||
                    ctarget == GL_TEXTURE_CUBE_MAP_ARRAY) {
            glTexSubImage3D(ctarget, dst_level, dstx, dsty, dstz, src_box->width, src_box->height, src_box->depth, glformat, gltype, tptr + slice_offset);
         } else {
            glTexSubImage2D(ctarget, dst_level, dstx, dsty, src_box->width, src_box->height, glformat, gltype, tptr + slice_offset);
         }
      }
      slice_offset += slice_size;
   }

   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
   free(tptr);
   glBindTexture(GL_TEXTURE_2D, 0);
}