static int vrend_renderer_resource_allocate_texture(struct vrend_resource *gr,
                                                    void *image_oes)
{
   uint level;
   GLenum internalformat, glformat, gltype;
   enum virgl_formats format = gr->base.format;
   struct vrend_texture *gt = (struct vrend_texture *)gr;
   struct pipe_resource *pr = &gr->base;

   if (pr->width0 == 0)
      return EINVAL;

   if (!image_oes && vrend_allocate_using_gbm(gr)) {
      if ((gr->base.bind & (VIRGL_BIND_RENDER_TARGET | VIRGL_BIND_SAMPLER_VIEW)) == 0) {
         gr->storage = VREND_RESOURCE_STORAGE_GBM_ONLY;
         return 0;
      }
      image_oes = virgl_egl_image_from_dmabuf(egl, gr->gbm_bo);
      if (!image_oes) {
         gbm_bo_destroy(gr->gbm_bo);
         gr->gbm_bo = NULL;
      } else {
         gr->egl_image = image_oes;
      }
   }

   bool format_can_texture_storage = has_feature(feat_texture_storage) &&
         (tex_conv_table[format].flags & VIRGL_TEXTURE_CAN_TEXTURE_STORAGE);

   if (image_oes)
      gr->base.bind &= ~VIRGL_BIND_PREFER_EMULATED_BGRA;
   else {
      /* On GLES there is no support for glTexImage*DMultisample and
       * BGRA surfaces are also unlikely to support glTexStorage2DMultisample
       * so we try to emulate here */
      if (vrend_state.use_gles && pr->nr_samples > 0 && !format_can_texture_storage) {
         VREND_DEBUG(dbg_tex, NULL, "Apply VIRGL_BIND_PREFER_EMULATED_BGRA because GLES+MS+noTS\n");
         gr->base.bind |= VIRGL_BIND_PREFER_EMULATED_BGRA;
      }
      format = vrend_format_replace_emulated(gr->base.bind, gr->base.format);
      format_can_texture_storage = has_feature(feat_texture_storage) &&
            (tex_conv_table[format].flags & VIRGL_TEXTURE_CAN_TEXTURE_STORAGE);
   }

   gr->target = tgsitargettogltarget(pr->target, pr->nr_samples);
   gr->storage = VREND_RESOURCE_STORAGE_TEXTURE;

   /* ugly workaround for texture rectangle missing on GLES */
   if (vrend_state.use_gles && gr->target == GL_TEXTURE_RECTANGLE_NV) {
      /* for some guests this is the only usage of rect */
      if (pr->width0 != 1 || pr->height0 != 1) {
         report_gles_warn(NULL, GLES_WARN_TEXTURE_RECT);
      }
      gr->target = GL_TEXTURE_2D;
   }

   /* fallback for 1D textures */
   if (vrend_state.use_gles && gr->target == GL_TEXTURE_1D) {
      gr->target = GL_TEXTURE_2D;
   }

   /* fallback for 1D array textures */
   if (vrend_state.use_gles && gr->target == GL_TEXTURE_1D_ARRAY) {
      gr->target = GL_TEXTURE_2D_ARRAY;
   }

   debug_texture(__func__, gr);

   glGenTextures(1, &gr->id);
   glBindTexture(gr->target, gr->id);

   if (image_oes) {
      if (epoxy_has_gl_extension("GL_OES_EGL_image_external")) {
         glEGLImageTargetTexture2DOES(gr->target, (GLeglImageOES) image_oes);
      } else {
         vrend_printf( "missing GL_OES_EGL_image_external extension\n");
         glBindTexture(gr->target, 0);
         FREE(gr);
         return EINVAL;
      }
   } else {
      internalformat = tex_conv_table[format].internalformat;
      glformat = tex_conv_table[format].glformat;
      gltype = tex_conv_table[format].gltype;

      if (internalformat == 0) {
         vrend_printf("unknown format is %d\n", pr->format);
         glBindTexture(gr->target, 0);
         FREE(gt);
         return EINVAL;
      }

      if (pr->nr_samples > 0) {
         if (format_can_texture_storage) {
            if (gr->target == GL_TEXTURE_2D_MULTISAMPLE) {
               glTexStorage2DMultisample(gr->target, pr->nr_samples,
                                         internalformat, pr->width0, pr->height0,
                                         GL_TRUE);
            } else {
               glTexStorage3DMultisample(gr->target, pr->nr_samples,
                                         internalformat, pr->width0, pr->height0, pr->array_size,
                                         GL_TRUE);
            }
         } else {
            if (gr->target == GL_TEXTURE_2D_MULTISAMPLE) {
               glTexImage2DMultisample(gr->target, pr->nr_samples,
                                       internalformat, pr->width0, pr->height0,
                                       GL_TRUE);
            } else {
               glTexImage3DMultisample(gr->target, pr->nr_samples,
                                       internalformat, pr->width0, pr->height0, pr->array_size,
                                       GL_TRUE);
            }
         }
      } else if (gr->target == GL_TEXTURE_CUBE_MAP) {
            int i;
            if (format_can_texture_storage)
               glTexStorage2D(GL_TEXTURE_CUBE_MAP, pr->last_level + 1, internalformat, pr->width0, pr->height0);
            else {
               for (i = 0; i < 6; i++) {
                  GLenum ctarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
                  for (level = 0; level <= pr->last_level; level++) {
                     unsigned mwidth = u_minify(pr->width0, level);
                     unsigned mheight = u_minify(pr->height0, level);

                     glTexImage2D(ctarget, level, internalformat, mwidth, mheight, 0, glformat,
                                  gltype, NULL);
                  }
               }
            }
      } else if (gr->target == GL_TEXTURE_3D ||
                 gr->target == GL_TEXTURE_2D_ARRAY ||
                 gr->target == GL_TEXTURE_CUBE_MAP_ARRAY) {
         if (format_can_texture_storage) {
            unsigned depth_param = (gr->target == GL_TEXTURE_2D_ARRAY || gr->target == GL_TEXTURE_CUBE_MAP_ARRAY) ?
                                      pr->array_size : pr->depth0;
            glTexStorage3D(gr->target, pr->last_level + 1, internalformat, pr->width0, pr->height0, depth_param);
         } else {
            for (level = 0; level <= pr->last_level; level++) {
               unsigned depth_param = (gr->target == GL_TEXTURE_2D_ARRAY || gr->target == GL_TEXTURE_CUBE_MAP_ARRAY) ?
                                         pr->array_size : u_minify(pr->depth0, level);
               unsigned mwidth = u_minify(pr->width0, level);
               unsigned mheight = u_minify(pr->height0, level);
               glTexImage3D(gr->target, level, internalformat, mwidth, mheight,
                            depth_param, 0, glformat, gltype, NULL);
            }
         }
      } else if (gr->target == GL_TEXTURE_1D && vrend_state.use_gles) {
         report_gles_missing_func(NULL, "glTexImage1D");
      } else if (gr->target == GL_TEXTURE_1D) {
         if (format_can_texture_storage) {
            glTexStorage1D(gr->target, pr->last_level + 1, internalformat, pr->width0);
         } else {
            for (level = 0; level <= pr->last_level; level++) {
               unsigned mwidth = u_minify(pr->width0, level);
               glTexImage1D(gr->target, level, internalformat, mwidth, 0,
                            glformat, gltype, NULL);
            }
         }
      } else {
         if (format_can_texture_storage)
            glTexStorage2D(gr->target, pr->last_level + 1, internalformat, pr->width0,
                           gr->target == GL_TEXTURE_1D_ARRAY ? pr->array_size : pr->height0);
         else {
            for (level = 0; level <= pr->last_level; level++) {
               unsigned mwidth = u_minify(pr->width0, level);
               unsigned mheight = u_minify(pr->height0, level);
               glTexImage2D(gr->target, level, internalformat, mwidth,
                            gr->target == GL_TEXTURE_1D_ARRAY ? pr->array_size : mheight,
                            0, glformat, gltype, NULL);
            }
         }
      }
   }

   if (!format_can_texture_storage) {
      glTexParameteri(gr->target, GL_TEXTURE_BASE_LEVEL, 0);
      glTexParameteri(gr->target, GL_TEXTURE_MAX_LEVEL, pr->last_level);
   }

   glBindTexture(gr->target, 0);

   gt->state.max_lod = -1;
   gt->cur_swizzle_r = gt->cur_swizzle_g = gt->cur_swizzle_b = gt->cur_swizzle_a = -1;
   gt->cur_base = -1;
   gt->cur_max = 10000;
   return 0;
}