简介
上一篇博客,介绍了QNX Screen的software rendering# QNX SCREEN-Software Rendering。本文主要介绍,QNX Screen侧的硬件(GPU)渲染。这一部分渲染主要是使用GPU来执行渲染操作;通常,在QNX侧使用通过使用Khronos API来使用EGL(例如OpenGL ES或OpenVG)的缓冲区,或使用QNX的native Screen API实现Blitting, 来进行渲染操作。
Khronos rendering API
Screen支持以下Khronos渲染API:OpenGL ES、OpenVG。它们为图形硬件提供通用接口,来辅助生成和操作高质量的二维、三维图像。 在QNX侧,使用Screen的EGL library(libEGL)来访问Khronos EGL,主要的流程类似于前一篇介绍的software rendering流程:
- 创建render target
- 创建render 上下文(Context)
- render
- 上屏
本文已OpenGL为例,详解QNX 侧通过EGL渲染的方案
1、创建render target
该步骤同screen的software render类似,关键区别点在于,创建的windows属性SCREEN_PROPERTY_USAGE。software render时,SCREEN_PROPERTY_USAGE通常设置为SCREEN_USAGE_READ | SCREEN_USAGE_WRITE | SCREEN_USAGE_NATIVE,当使用EGL进行渲染时,需要设置为SCREEN_USAGE_OPENGL_ES2。本文依旧以Windows为Render Target
int usage = SCREEN_USAGE_OPENGL_ES2 | SCREEN_USAGE_OVERLAY;
int format = SCREEN_FORMAT_RGBA8888;
ret = screen_set_window_property_iv(pstCtx->screen_win, SCREEN_PROPERTY_USAGE, &usage);
ret = screen_set_window_property_iv(pstCtx->screen_win, SCREEN_PROPERTY_FORMAT, &format);
ret = screen_set_window_property_iv(pstCtx->screen_win, SCREEN_PROPERTY_SWAP_INTERVAL, &pstCtx->interval);
ret = screen_set_window_property_iv(pstCtx->screen_win, SCREEN_PROPERTY_SIZE, pstCtx->size);
ret = screen_set_window_property_iv(pstCtx->screen_win, SCREEN_PROPERTY_POSITION, pstCtx->pos);
ret = screen_set_window_property_iv(pstCtx->screen_win, SCREEN_PROPERTY_ZORDER, &pstCtx->zorder);
ret = screen_set_window_property_cv(pstCtx->screen_win, SCREEN_PROPERTY_ID_STRING, strlen(id_string), id_string);
ret = screen_create_window_buffers(pstCtx->screen_win, pstCtx->nbuffers);
2、创建render context
该Context可以理解为EGL的Context,创建流程也和创建openGL Context一致,如下:
- 同EGL的Display建立连接
- 初始化EGL的Display
- 配置EGL config
- 创建Context
- 将该Context绑定至步骤一创建的Render Target 具体代码如下:
初始化EGL display
ret = eglInitialize(pstCtx->egl_disp, NULL, NULL);
if (ret != EGL_TRUE) {
LOGE( "eglInitialize error, %s\n", egl_strerror());
goto fail1;
}
配置EGL config
ret = eglChooseConfig(pstCtx->egl_disp, attribute_list, &pstCtx->egl_conf, 1, &pstCtx->num_config);
if (ret != EGL_TRUE) {
LOGE( "eglChooseConfig error, %s\n", egl_strerror());
goto fail2;
}
创建Context
pstCtx->egl_ctx = eglCreateContext(pstCtx->egl_disp, pstCtx->egl_conf, EGL_NO_CONTEXT, context_attributes);
if (pstCtx->egl_ctx == EGL_NO_CONTEXT) {
LOGE( "eglCreateContext error, %s\n", egl_strerror());
goto fail3;
}
将该Context绑定至步骤一创建的Render Target
创建windowsurface,绑定步骤一创建的render target
pstCtx->egl_surf = eglCreateWindowSurface(pstCtx->egl_disp, pstCtx->egl_conf, (EGLNativeWindowType)screen_win, (EGLint *)&pstCtx->egl_surf_attr);
if (pstCtx->egl_surf == EGL_NO_SURFACE) {
LOGE( "eglCreateWindowSurface error, %s\n", egl_strerror());
goto fail2;
}
ret = eglMakeCurrent(pstCtx->egl_disp, pstCtx->egl_surf, pstCtx->egl_surf, pstCtx->egl_ctx);
if (ret != EGL_TRUE) {
LOGE( "eglMakeCurrent error, %s\n", egl_strerror());
goto fail4;
}
3、Render
使用openGL对EGL的Framebuffer进行绘制,该步骤便不详细展开
4、上屏
ret = eglSwapBuffers(pstCtx->egl_disp, pstCtx->egl_surf);
if (ret != EGL_TRUE) {
LOGE( "eglSwapBuffers error, %s\n", egl_strerror());
return -__LINE__;
}
Blitting
由于图形处理需要大量的内存管理和内存区域的移动,为了更好的支持图形处理,通常使用 bit blitter。不同于CPU的字节复制,blitter实现的是pixel的搬运。因此,QNX Screen提供了一套native API,使用blitter搬运buffer。
使用screen_blit进行渲染,渲染前的绘制流程和Software Rendering一致,创建Render Target、创建buffer、绘制buffer,但在设置buffer的usage时,需要设置为SCREEN_USAGE_NATIVE。本文通过EGLImage来刷新一个native buffer,从而讲解screen_blit的调用
绑定EGL Framebuffer
QNX侧,可以使用两种方式将EGL的framebuffer同screen的native buffer绑定。1、使用pixmap创建EGL OES纹理,绘制纹理;2、pmem方式,创建EGL OES纹理,将screen buffer绑定pmem申请的地址。
1、pixmap创建OES纹理
///////创建native screen buffer
ret = screen_create_buffer(&pstCtx->buffer);
///////设置buffer属性,此处不在赘述
ret = screen_set_buffer_property_iv(pstCtx->buffer, SCREEN_PROPERTY_FORMAT, &fmt);
.......
///////创建pixmap
ret = screen_create_pixmap(&pstCtx->pixmap, pstCtx->pixmap_screen_ctx);
////// 将native screen buffer 绑定到pixmap上
screen_attach_pixmap_buffer(pstCtx->pixmap, pstCtx->buffer);
///// 创建EGLImage
imageKHR = (EGLImageKHR)eglCreateImageKHR(egl_display, NULL, EGL_NATIVE_PIXMAP_KHR, (EGLClientBUffer)pstCtx->pixmap, NULL);
///// 生成并绑定OES纹理
GL_CHECK(glGenTextures(1, &pstCtx->textures));
GL_CHECK(glBindTexture(GL_TEXTURE_EXTERNAL_OES, pstCtx->textures));
GL_CHECK(glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES), imageKHR));
如此,外部可以同过对该纹理进行渲染,实现对buffer的更新
2、高通QCOM创建OES纹理
高通同时也提供了自己的EGLImage创建方式。该buffer通过绑定pmem申请的pmem handle,来创建EGLImage。根据高通的手册,调试并整理了代码如下:
///// 创建pmem handle
pstCtx->mem_fd = nullptr;
pstCtx->mem_ptr = pmem_malloc_ext_v2(buffer_size,
PMEM_CAMERA_ID,
PMEM_FLAGS_SHMEM | PMEM_FLAGS_PHYS_NON_CONTIG | PMEM_FLAGS_CACHE_NONE,
PMEM_ALIGNMENT_4K,
0,
&pstCtx->mem_fd,
NULL);
////// 设置elg attributes
EGLint attr[] = {
EGL_WIDTH,buffer_width,
EGL_HEIGHT,buffer_height,
EGL_IMAGE_FORMAT_QCOM,buffer_format,
EGL_IMAGE_EXT_BUFFER_DESCRIPTOR_LOW_QCOM,(((intptr_t)(pstCtx->mem_fd))&0xFFFFFFFF),
EGL_IMAGE_EXT_BUFFER_DESCRIPTOR_HIGH_QCOM,((intptr_t)(pstCtx->mem_fd)>>32),
EGL_IMAGE_EXT_BUFFER_SIZE_QCOM,buffer_size,
EGL_IMAGE_EXT_BUFFER_STRIDE_QCOM,buffer_stride,
EGL_IMAGE_EXT_BUFFER_MEMORY_TYPE_QCOM,EGL_IMAGE_EXT_BUFFER_MEMORY_TYPE_PMEM_QCOM,
EGL_IMAGE_EXT_BUFFER_PLANE0_OFFSET_QCOM, 0,
EGL_NONE};
///// 创建EGLImage
pstCtx->output_image = eglCreateImageKHR(pstCtx->egl_disp,pstCtx->egl_ctx,EGL_NEW_IMAGE_QCOM,(EGLClientBuffer)0,attr);
GL_CHECK(glGenTextures(1, &pstCtx->frameBufferTexture));
GL_CHECK(glBindTexture(GL_TEXTURE_2D, pstCtx->frameBufferTexture));
GL_CHECK(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CHECK(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CHECK(glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)pstCtx->output_image));
通过pmem来生成OES纹理,pmem生成的虚拟地址亦指向了该EGLImage,因此,可以将该虚拟地址绑定于screen的native buffer,如此便可以通过OpenGL渲染Framebuffer来操作该qnx screen buffer。
//////创建screen native buffer,并设置属性
ret = screen_create_buffer(&pstCtx->screen_buf);
///// 其余常见属性此处不在赘述
......
///// 绑定虚拟地址
ret = screen_set_buffer_property_pv(pstCtx->screen_buf, SCREEN_PROPERTY_POINTER, &pstCtx->mem_ptr);
if (ret != 0) {
LOGE( "screen_set_buffer_property_pv error, %s\n", strerror(errno));
return -__LINE__;
}
至此,通过oepngl绘制buffer的准备工作完成
screen_blit,上屏
上述EGLImage生成之后,便可通过OpenGL绘制。渲染完成后,我们假如需要对这个buffer做一个类似滑动窗口的效果,可以使用screen_blit,操作如下
screen_get_window_property_pv(screen_pix, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)&screen_rbuf)
int hg[] = {SCREEN_BLIT_SOURCE_WIDTH, 100,
SCREEN_BLIT_SOURCE_HEIGHT, 100,
SCREEN_BLIT_DESTINATION_X, 10,
SCREEN_BLIT_DESTINATION_Y, 10,
SCREEN_BLIT_DESTINATION_WIDTH, 100,
SCREEN_BLIT_DESTINATION_HEIGHT, 100,
SCREEN_BLIT_TRANSPARENCY, SCREEN_TRANSPARENCY_SOURCE_OVER,
SCREEN_BLIT_END };
screen_blit(screen_ctx, screen_rbuf, screen_buf, hg);
/////// 渲染
screen_post_window(screen_win, screen_rbuf, 1, rect, 0);
数组hg即为需要搬运的buffer大小。具体属性含义:
SCREEN_BLIT_SOURCE_WIDTH, 100, /// 需要搬运的buffer水平位置起始点
SCREEN_BLIT_SOURCE_HEIGHT, 100, /// 需要搬运buffer的垂直方向起始点
SCREEN_BLIT_DESTINATION_X, 10, /// 目标buffer的水平方向起始点
SCREEN_BLIT_DESTINATION_Y, 10, /// 目标buffer的垂直方向起始点
SCREEN_BLIT_DESTINATION_WIDTH, 100, /// 目标buffer的宽
SCREEN_BLIT_DESTINATION_HEIGHT, 100, /// 目标buffer的高
SCREEN_BLIT_TRANSPARENCY, SCREEN_TRANSPARENCY_SOURCE_OVER, // buffer的aplha参数
SCREEN_BLIT_END /// 结束标志位
因此,screen_blit的本质,即从source screen buffer中,指定一个窗口,将该窗口中的pixel全都搬运到另一个destination screen buffer中。还可以通过属性值的设置,对该buffer进行操作,如缩放、图像格式转换、透明度混合等。
总结
hardware render总体来说,最常使用的还是通过EGL形式进行渲染。不管是通过Khronos rendering API进行渲染,还是使用Screen Native API Blitting的形式进行渲染,本质上都是pixel的操作。本文主要在项目侧,参考QNX文档对hardware render方案的两种方式进行调试,总结,并做记录。后续将解释QNX侧混合渲染(Hybrid Rendering)。