简介
QNX屏幕图形子系统基于客户机/服务器模型,其中应用程序是向服务器(屏幕)请求图形服务的客户机。它包括一个复合窗口系统作为这些服务之一,这意味着所有的应用程序呈现都在屏幕外缓冲区上执行,然后可以稍后用于更新显示。
Screen支持硬件渲染加速和合成,也支持软件渲染。兼容显示控制器,2D\3D加速器和输入设备。除了事件之外,所有Screen API组件都必须与上下文关联,通过Context,可以实现对Screen各个模块进行操作,具体模块如下:
本文主要针对QNX侧最为基本的SoftWare上屏的形式做详细介绍
SoftWare Rendering(软件渲染)
按照官方手册,software rendering主要可以分为四个步骤:
- 创建rendering的目标
- 创建需要rendering的buffer
- 在buffer上绘制内容
- post上屏
根据自身项目,本文在这里对上述四个流程做一个展开
1、创建Rendering的目标
创建Rendering的目标,即QNX提供的Window、Pixmap、Stream。Rendering可以是Rendering + Display(即on-screen),也可以仅仅Rendering(即 off-screen),可以是多buffer,也可以是单个buffer。Screen API支持的Rendering Target如下:
Rendering Target | Output destination | Buffers |
---|---|---|
windows | On-screen | Multiple |
pixmap | Off-screen | Multiple |
stream | Off-screen | Single |
本文以on-screen渲染为例,首先是创建Screen Context和rendering目标Screen Windows
js
ret = screen_create_context(&pstCtx->screen_ctx, SCREEN_APPLICATION_CONTEXT | SCREEN_BUFFER_PROVIDER_CONTEXT);
ret = screen_create_window(&pstCtx->screen_win, pstCtx->screen_ctx);
创建Context时,需要设置创建的Context类型,主要表示了Context支持的某些操作,Context类型如下:
js
enum {
SCREEN_APPLICATION_CONTEXT = 0, /// 允许进程创建自己的窗口并控制某些窗口属性
SCREEN_WINDOW_MANAGER_CONTEXT = (1 << 0), /// 允许进程创建的win拥有修改其他窗口的权力
SCREEN_INPUT_PROVIDER_CONTEXT = (1 << 1), /// 允许进程创建的win可以向其他窗口发送event
SCREEN_POWER_MANAGER_CONTEXT = (1 << 2), /// 允许进程创建的win可以访问diplay power
SCREEN_DISPLAY_MANAGER_CONTEXT = (1 << 3), /// 允许进程创建的win可以修改系统的display属性
SCREEN_INPUT_MANAGER_CONTEXT = (1 << 4), /// 允许进程创建的win可以收到所有的session
SCREEN_BUFFER_PROVIDER_CONTEXT = (1 << 5) /// 允许进程创建的win可以attach buffer
};
创建Context和Windows之后,对创建的windows窗口设置属性。属性较多,本文不在此详细解释每一个属性的含义,在此列出项目中用到的属性设置
js
SCREEN_PROPERTY_USAGE //// 指定该rendering target的预期用途
SCREEN_PROPERTY_FORMAT //// 指定该rendering target的颜色格式
SCREEN_PROPERTY_SWAP_INTERVAL //// 指定该rendering target post的周期
SCREEN_PROPERTY_SIZE //// 指定该rendering target的尺寸
SCREEN_PROPERTY_POSITION //// 指定该rendering target的位置
SCREEN_PROPERTY_ZORDER //// 指定该rendering target的zorder
SCREEN_PROPERTY_ID_STRING //// 指定该rendering target的窗口名
SCREEN_PROPERTY_PIPELINE //// 指定该rendering target的display管道
SCREEN_PROPERTY_DISPLAY_COUNT //// 用于获取系统支持的外部硬件display数量
SCREEN_PROPERTY_DISPLAYS //// 指定该rendering target的display
具体参考代码如下
js
ret = screen_set_window_property_iv(pstCtx->screen_win, SCREEN_PROPERTY_USAGE, &pstCtx->usage);
ret = screen_set_window_property_iv(pstCtx->screen_win, SCREEN_PROPERTY_FORMAT, &pstCtx->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);
ret = screen_set_context_property_iv(pstCtx->screen_ctx, SCREEN_PROPERTY_PIPELINE, &pstCtx->pipeline);
//////首先获取Context下支持的硬件display数量
ret = screen_get_context_property_iv(pstCtx->screen_ctx, SCREEN_PROPERTY_DISPLAY_COUNT, &pstCtx->ndisplays);
pstCtx->screen_dpy = (screen_display_t *)calloc(pstCtx->ndisplays, sizeof(screen_display_t));
///// 获取所有的display指针
ret = screen_get_context_property_pv(pstCtx->screen_ctx, SCREEN_PROPERTY_DISPLAYS, (void **)pstCtx->screen_dpy);
for (i = 0; i < pstCtx->ndisplays; ++i) {
int display_id = 0;
int size[2] = {0};
///////获取display的下标以及对应的size
ret = screen_get_display_property_iv(pstCtx->screen_dpy[i], SCREEN_PROPERTY_ID, &display_id);
ret = screen_get_display_property_iv(pstCtx->screen_dpy[i], SCREEN_PROPERTY_SIZE, size);
}
//////// 根据项目需要,设置对应的display指针
pstCtx->target_disp_idx = pstCtx->ndisplays - 1;
pstCtx->target_disp = pstCtx->screen_dpy[pstCtx->target_disp_idx];
ret = screen_set_window_property_pv(pstCtx->screen_win, SCREEN_PROPERTY_DISPLAY, (void **)&pstCtx->target_disp);
2、创建Rendering buffer
设置完Rendering Target之后,便是创建需要渲染的Rendering buffer,attach到对应Rendering Target。亦可直接索引screen windows 中的renderbuffer。 buffer的创建同上述context和screen win的创建类似,screen_create_buffer后,设置对应的buffer属性,并将该buffer attach到对应的rendering target上,即可实现自定义buffer。
js
/////////////////////// 直接获取索引的方式///////////////////////////
ret = screen_get_window_property_pv(pstCtx->screen_win, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)&pstCtx->screen_buf);
/////////////////////自己创建buffer的方式///////////////////////////
ret = screen_create_buffer(&pstCtx->screen_buf);
ret = screen_set_buffer_property_iv(pstCtx->screen_buf, SCREEN_PROPERTY_FORMAT, &fmt);
ret = screen_set_buffer_property_iv(pstCtx->screen_buf, SCREEN_PROPERTY_BUFFER_SIZE, size);
ret = screen_set_buffer_property_iv(pstCtx->screen_buf, SCREEN_PROPERTY_SIZE, &msz);
ret = screen_set_buffer_property_iv(pstCtx->screen_buf, SCREEN_PROPERTY_STRIDE, &stride);
ret = screen_set_buffer_property_iv(pstCtx->screen_buf, SCREEN_PROPERTY_PLANAR_OFFSETS, offset);
项目中为了实现OpenGL的OES纹理直接绘制到screen buffer上,还设置了buffer的虚拟地址
js
ret = screen_set_buffer_property_pv(pstCtx->screen_buf, SCREEN_PROPERTY_POINTER, &pstCtx->mem_ptr);
设置完buffer的属性后,attach到对应的rendering buffer上
js
ret = screen_attach_window_buffers(pstCtx->screen_win, 1, &pstCtx->screen_buf);
3、绘制buffer
绘制buffer的方式,可以是对索引到的renderbuffer地址进行赋值,也可以使用OpenGL的形式进行绘制。高通针对OES纹理,提供了一种pmem绑定的形式,可以通过pmem创建的mem_ptr,创建ImageKHR,实现buffer的绘制。代码如下:
js
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);
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};
pstCtx->output_image = eglCreateImageKHR(pstCtx->egl_disp,pstCtx->egl_ctx,EGL_NEW_IMAGE_QCOM,(EGLClientBuffer)0,attr);
也可以通过对索引到的render buffer进行辅助操作,如下:
js
ret = screen_get_buffer_property_pv(screen_buf, SCREEN_PROPERTY_POINTER, (void **)&&pstCtx->ptr);
ret = screen_get_buffer_property_iv(screen_buf, SCREEN_PROPERTY_STRIDE, &pstCtx->stride);
memset(ptr, 0x00, buffer[0]*buffer[1]*4);//字节
// line 0 to 1/4: 0xFFFF0000
// line 1/4 to 2/4: 0xFF00FF00
// line 2/4 to 3/4: 0x000000FF
// line 3/4 to 1: 0x660000FF
int one_quarter = buffer[1]/4;
for(int i = 0; i < buffer[1]; ++i, ptr+=(stride/4))
{
for(int j = 0; j < buffer[0]; j++ )
{
if (i < one_quarter)
ptr[j] = 0xFFFF0000;
else if (i < one_quarter*2)
ptr[j] = 0xFF00FF00;
else if (i < one_quarter*3)
ptr[j] = 0x000000FF;
else
ptr[j] = 0x660000FF;
}
}
4、post上屏
最终,将绘制后的render buffer上屏。
js
ret = screen_post_window(pstCtx->screen_win, pstCtx->screen_buf, 0, NULL, 0);
若使用OpenGL形式绘制的buffer,需要在post之前,bindframebuffer
js
/// 先绑定framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, pstCtx->frameBuffer);
/// 获取windows的renderbuffer
ret = screen_get_window_property_pv(pstCtx->screen_win, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)&pstCtx->screen_renderbuf);
if(ret != 0){
LOGE( "screen_get_window_property_pv SCREEN_PROPERTY_RENDER_BUFFERS error:%s\n", strerror(errno));
return 0;
}
/// post上屏
ret = screen_post_window(pstCtx->screen_win, pstCtx->screen_renderbuf, 0, nullptr, 0);
小结
software 渲染的形式是QNX Screen最为基础的渲染方式,但由于文档提供的代码提供的可参考性较差,因此特意撰写此文,为自己在qnx侧调试做记录。后续还会更新QNX侧EGL渲染、Hybrid渲染。