前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >图片的实时渲染和离屏渲染

图片的实时渲染和离屏渲染

作者头像
马上就说
发布2023-03-05 14:47:37
1.7K0
发布2023-03-05 14:47:37
举报
文章被收录于专栏:码上就说码上就说

之前我们比较多的介绍视频的渲染和处理,本文我们想谈一谈图片,和视频比起来,图片确实相对简单点,我们知道视频本质上是一帧帧的“图片”组成的,都了解了视频了,图片还需要去了解吗?图片的渲染和视频有相通之处,也有其独特的特点。

视频的渲染一般都是实时渲染,使用SurfaceView或者TextureView,图片的渲染一般都会采用ImageView,可以设置路径,也可以设置Bitmap,再加上Canvas和Paint,我无敌了,可以实现多张多样的效果。ImageView是不是真的无敌?

  • 问:我想在一张图片上加上贴纸
  • 答:简单,我使用Cavas,将贴纸放在特定的位置,然后生成一张新的Bitmap放入ImageView上。
  • 问:我要添加多张贴纸在不同的位置上
  • 答:简单,一样的道理啊,我还可以举一反三,Cavas解决一切啊。每添加一张贴纸就重新生成一张Bitmap放入ImageView上。
  • 问:我可以实时移动贴纸到不同的位置吗?
  • 答:当然可以啊,Canvas可以支持设置位置啊。
  • 问:每改变一次位置,就要生成新的Bitmap,这样不断生成回收,是不是过分频繁了?
  • 答:是有点频繁,但是为了每次都能生成Bitmap,这样不可避免,开发同学注意回收就行了。
  • 问:频繁销毁除了产生内存问题,导致稳定性问题,会不会导致性能比较差?
  • 答:内存抖动过分频繁,确实会导致性能问题。

从上面的聊天我们已经得知ImageView处理图片的的两个问题:

  • 内存抖动问题
  • 渲染效率低,性能差

ImageView渲染图片和离屏渲染怎么关联起来了?OpenGL有离屏渲染的概念,顾名思义为屏幕外的渲染,即在当前屏幕缓冲区以外,新开辟一个新缓冲区进行操作。离屏渲染发生在GPU层面上,会创建新的渲染缓冲区,会触发 OpenGL 的多通道渲染管线,图形上下文的切换会造成额外的开销,增加 GPU 工作量。其实从描述上来看,就知道离屏渲染是比较影响性能的。

  • 需要创建新的缓存区
  • 离屏渲染的整个过程,需要多次切换上下文环境,当前屏幕—>离屏,渲染结束后将离屏缓冲区内容显示到当前屏幕,上下文切换回当前环境。

与离屏渲染相反的就是实时渲染,或者称当前屏幕渲染,CPU计算好frame等属性,将计算好的内容提交给GPU去渲染,GPU渲染完成之后就会放入屏幕帧缓冲区,然后控制器每隔一段时间会去屏幕缓存区读取渲染好的内容,从而显示出来。图片渲染怎么样实现实时渲染?当然是SurfaceView啦,既然是实时渲染,必定有画布的概念,上一篇文章已经非常清楚地指出了SurfaceView的画布本质了。大家有不清楚的可以看一下上一篇文章:为播放器外接一套渲染框架

我们的印象中SurfaceView通常和视频或者摄像头采集关联比较多,用来渲染图片还是比较少见的。但是为了保证图片实时渲染,SurfaceView确实是一个非常的好的载体。

  • 创建EGL环境
  • Surface转NativeWindow,构建EGLSurface
  • 设置FBO
  • 渲染图片纹理

创建EGL环境

  1. 获取EGLDisplay,EGLDisplay实际上是底层显示设备的OpenGL层的抽象
  2. 初始化EGLDisplay
  3. 设置EGLDisplay,包括ARGB,渲染类型,版本等
  4. 设置OpenGL版本
  5. 获取EGLContext,EGLContext可以在多个EGL环境中共享,可以实现多线程交互。
代码语言:javascript
复制
int EGLCore::Init(EGLContext shared_context, int egl_version) {
  if (egl_version == 0) {
    egl_version = 2;
  }
  EGLint num_configs;
  const EGLint attributes[] = {
          EGL_BUFFER_SIZE,      32,
          EGL_ALPHA_SIZE,       8,
          EGL_BLUE_SIZE,        8,
          EGL_GREEN_SIZE,       8,
          EGL_RED_SIZE,         8,
          EGL_RENDERABLE_TYPE,
          EGL_OPENGL_ES2_BIT,
          EGL_SURFACE_TYPE,
          EGL_WINDOW_BIT,
          EGL_NONE
  };

  if ((display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) {
    LOGE("eglGetDisplay() returned error %d", eglGetError());
    return Error::OPENGL_DISPLAY_ERROR;
  }
  if (!eglInitialize(display_, 0, 0)) {
    LOGE("eglInitialize() returned error %d", eglGetError());
    return Error::OPENGL_INITIALIZE_ERROR;
  }

  if (!eglChooseConfig(display_, attributes, &config_, 1, &num_configs)) {
    LOGE("eglChooseConfig() returned error %d", eglGetError());
    Release();
    return Error::OPENGL_CONFIG_ERROR;
  }
  EGLint eglContextAttributes[] = { EGL_CONTEXT_CLIENT_VERSION, static_cast<EGLint>(egl_version), EGL_NONE };
  context_ = eglCreateContext(display_, config_, shared_context, eglContextAttributes);
  if (context_ == nullptr) {
    LOGE("eglCreateContext() returned error %d", eglGetError());
    Release();
    return Error::OPENGL_CREATE_CONTEXT_ERROR;
  }
  return 0;
}

Surface转NativeWindow,构建EGLSurface

  1. 可以在主线程中将Surface转为NativeWindow
  2. 在EGL线程中通过NativeWindow构建EGLSurface,这个EGLSurface非离屏,可以实现渲染的。
代码语言:javascript
复制
JNIEnv *env = nullptr;
int ret = get_env(&env);
if (env != nullptr) {
  native_window_ = ANativeWindow_fromSurface(env, surface);
}
if (ret == JNI_EDETACHED) {
  detach_env();
}
代码语言:javascript
复制
EGLSurface EGLCore::CreateWindowSurface(ANativeWindow *window) {
  EGLSurface surface = EGL_NO_SURFACE;
  EGLint format;

  if (window == nullptr) {
    LOGE("%s window is null.", __func__);
    return surface;
  }

  if (!eglGetConfigAttrib(display_, config_, EGL_NATIVE_VISUAL_ID, &format)) {
    LOGE("%s eglGetConfigAttrib() returned error %d", __func__, eglGetError());
    Release();
    return surface;
  }
  ANativeWindow_setBuffersGeometry(window, 0, 0, format);
  if (!(surface = eglCreateWindowSurface(display_, config_, window, nullptr))) {
    LOGE("%s eglCreateWindowSurface() returned error %d", __func__, eglGetError());
  }
  return surface;
}

渲染图片纹理

代码语言:javascript
复制
if (render_screen_ == nullptr) {
  render_screen_ = new effect::OpenGL();
}
render_screen_->SetOutput(surface_width_, surface_height_);
render_screen_->ProcessImage(process_id);
if (!egl_core_->SwapBuffers(egl_surface_)) {
  LOGE("eglSwapBuffers error: %d surface: %d", eglGetError(), (egl_surface_ == EGL_NO_SURFACE));
}

上面最终渲染的process_id就是和FBO绑定的纹理。

通过实现渲染图片我们可以做到什么呢?我们可以实现各种各样的效果。

例如一些视频蒙版效果、滤镜效果、颜色调节效果,下面看一下简单的视频蒙版效果。

通过此文,你可以举一反三,做出一些更有趣的效果。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 音视频平凡之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档