支持Ubuntu Touch的PinePhone上的Wayland和LVGL

2020-07-25 15:27:51

它们感觉很舒服,它们符合我们的轮廓。别管那些洞和旧污渍了🤢。

X11就像旧内衣。它已经存在了30年了……。然而,我们仍然在使用它,尽管它的功能差距和不可靠的怪癖。

它运行Linux,但没有任何遗留的X11代码。因为它针对Wayland出色的移动体验进行了优化。

但是新内衣让人感觉不舒服。因此,今天我们将学习Wayland,并了解应用程序是如何使用Wayland构建的。

希望有一天,随着我们抛弃旧内衣,我们将转向更新、更简单的应用程序框架(如LVGL和Ffltter):X11、SDL、GTK、Qt等。

X11服务控制Linux应用程序的呈现(以及键盘和鼠标输入),如下所示……。

在顶部,我们有在Linux机器上运行的Linux程序:终端、编辑器、Web浏览器。

每个程序呈现其图形显示并将原始图形传输到X11服务(通过本地TCP套接字)。

窗口管理器/合成器由桌面环境提供:Xfce、KDE、Gnome、...。

窗口管理器/合成器将渲染的图形包装到显示窗口中,并使用滚动条、标题栏和最小化/最大化/关闭按钮来装饰它们。

然后,窗口管理器/合成器根据它们的屏幕坐标将显示窗口绘制到屏幕缓冲区中。

屏幕缓冲区通过与Linux显示驱动程序对话的X11服务呈现到我们的屏幕上。

任何键盘和鼠标输入都由X11服务捕获,并转发给程序。

这就是我是如何使用(虐待?)。UIUC系统研究小组的X11R4早在1990年(30年前!)。

我们是否需要用标题栏和最小化/最大化/关闭按钮来装饰PinePhone窗口?

以下是Wayland Compositor如何使用Ubuntu Touch控制PinePhone上的应用程序和触摸屏输入…

在顶部,我们的手机上运行着以下应用程序:终端、编辑器、网络浏览器。

当应用程序启动时,它会向Wayland Compositor查询可用的图形显示界面。(他们通过LINUX套接字文件交谈:/RUN/USER/32011/WAYLAND-0)。

APP调用EGL接口将OpenGL图形直接渲染到Linux显示驱动程序。

Linux显示驱动将OpenGL渲染命令转发给GPU以更新屏幕。

任何触摸屏输入都会被Wayland Compositor捕获,并转发到活动的应用程序。

是!。我谎称韦兰是新内衣。韦兰德并不是真的那么新!

“韦兰德”于2008年首次上映(11年前)……。然而,它是围绕OpenGL和GPU设计的,同样的技术也为我们今天美丽的游戏提供了动力。(网站也是如此)。

请继续阅读,了解如何在PinePhone上使用Wayland和Ubuntu Touch渲染我们自己的OpenGL图形。

/渲染OpenGL ES2 Displaystatic void Render_Display(){//用黄色glClearColor(1.0,//Red 1.0,//Green 0.0,//Blue 1.0//Alpha);glClear(GL_COLOR_BUFFER_BIT);//立即渲染glFlush();}填充矩形区域。

RENDER_DISPLAY()看起来和普通的OpenGL完全一样,并且它可以在带有Wayland的PinePhone上工作!(感谢Ubuntu Touch)。

PinePhone支持OpenGL的一个流行子集,称为OpenGL for Embedded Systems Version 2.0。

OpenGL ES针对嵌入式设备进行了优化。如今,许多手机和游戏机游戏都在使用它。

要渲染OpenGL ES图形,需要从Wayland获取OpenGL ES上下文和窗口表面。

/要渲染的OpenGL区域的尺寸static int width=480;static int Height=360;static struct wl_EGL_Window*EGL_Window;//Wayland EGL Windowstatic EGL Surface EGL_Surface;//EGL Surface//创建EGL窗口并渲染OpenGL图形static void create_window(Void){//从Wayland Surface EGL_Window=wl_EGL_Window_Create(Surface)创建EGL窗口。//创建OpenGL窗口失败//创建用于渲染的OpenGL窗口表面EGL_Surface=eglCreateWindowSurface(EGL_DISPLAY,EGL_CONF,EGL_WINDOW,NULL);Assert(EGL_Surface!=NULL);//创建OpenGL窗口表面失败//设置当前渲染表面EGLBoolean madeCurrent=eglMakeCurrent(EGL_DISPLAY,EGL_SERFACE,EGL_FACE,EGL_CONTEXT);Assert(mam。//调换显示缓冲区使显示可见EGLBoolean swappdBuffers=eglSwapBuffers(EGL_DISPLAY,EGL_Surface);Assert(SwappdBuffers);//调换显示缓冲区失败}。

名为wl_egl_的函数...。由Wayland EGL接口提供。名为EGL的函数...。来自跨平台的台面3D图形库。

韦兰德只了解EGL,它会很乐意把EGL物品交给我们。但将EGL转换为OpenGL进行渲染取决于我们。

因此,在上面的代码中,我们使用Wayland Surface曲面并将其转换为EGL窗口EGL_WINDOW...。

//设置当前渲染面eglMakeCurrent(EGL_DISPLAY,EGL_FACE,EGL_FACE,EGL_CONTEXT);//渲染displayender_display();//调换显示缓冲区,使显示可见eglSwapBuffers(EGL_DISPLAY,EGL_SEATURE);

Static struct wl_region*region;//Wayland Region//创建渲染OpenGL图形的Wayland Region静态void create_opque_region(Void){//创建Wayland Region Region=wl_Compositor_Create_Region(合成器);Assert(Region!=null);//创建EGL区域失败//设置Wayland Region wl_Region_add(Region,0,0,width,Height)的尺寸;//将区域添加到。

/用于OpenGL渲染的Wayland EGL接口静态EGL显示EGL_DISPLAY;//EGL Displaystatic EGL配置EGL_conf;//EGL配置静态EGL上下文EGL_CONTEXT;//EGL上下文//创建用于呈现OpenGL图形的EGL上下文static void init_egl(Void){//EGL显示EGL的属性EGLint config_attribs[]={EGL_Surface_type,EGL。Static const EGLint CONTEXT_ATTRIBS[]={EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE};//获取EGL显示EGL_DISPLAY=eglGetDisplay((EGLNativeDisplayType)display);Assert(EGL_DISPLAY!=EGL_NO_DISPLAY);//获取EGL显示失败//初始化EGL显示EGLint主,次;EGL布尔值EGL_init=eglInitialize(。EglGetConfigs(EGL_DISPLAY,NULL,0,&;count);EGLConfig*configs=calloc(count,sizeof*configs);eglChooseConfig(EGL_DISPLAY,config_attribs,configs,count,&;n);//选择(int i=0;i<;n;i++){EGLint size;eglGetConfigAttrib(EGL_。EGL_CONF=configs[i];Break;}Assert(EGL_CONF!=NULL);//获取EGL配置失败//根据EGL显示和配置EGL_CONTEXT=eglCreateContext(EGL_DISPLAY,EGL_CONF,EGL_NO_CONTEXT,CONTEXT_ATTRBS);ASSERT(EGL_CONTEXT!=NULL);//创建EGL上下文失败}。

/Wayland Interfacesstatic struct wl_Surface*Surface;//Wayland Surfacestatic struct wl_shell_Surface*shell_Surface;//Wayland Shell Surface/连接到Wayland Compositor并渲染OpenGL raphicsint main(int argc,char**argv){//获取Wayland Compositor和Wayland Shell的接口get_server_reference();assert(display!=null);//获取Wayland Compositor和Wayland Shell的接口GET_SERVER_REFERENCES();Assert(display!=null);//获取Wayland Compositor和Wayland Shell的接口。//无法获取Wayland Shell//创建用于渲染我们的应用程序的Wayland Surface=wl_Compositor_Create_Surface(合成器);Assert(Surface!=null);//无法创建Wayland Surface//获取用于渲染我们的应用程序的Wayland Shell Surface//获取Wayland Shell Surface=wl_shell_get_shell_Surface(shell,Surface);Assert(shell_Surface!=null);//将Shell Surface设置为顶级wl_shell_Surface_set_toplet。//创建用于渲染OpenGL图形的EGL上下文init_egl();//创建EGL窗口并渲染OpenGL图形create_window();//处理Event Loop中的所有Wayland事件,同时(wl_display_dispatch(Display)!=-1){}//断开与Wayland显示的连接wl_display_disconnect(Display);返回0;}。

前面我们已经看到了create_opque_region()、init_egl()和create_window()。我们调用它们来创建Wayland区域、EGL上下文和EGL窗口,并渲染OpenGL图形。

//创建渲染OpenGL图形的Wayland区域create_opque_region();//创建渲染OpenGL图形的EGL上下文sinit_egl();//创建EGL窗口,渲染OpenGL图形create_window();

现在,让我们在我们的Linux开发机器上构建并测试这个应用程序。(我们稍后将在PinePhone上运行它)

现在我们已经创建了一个简单的Wayland应用程序来渲染OpenGL图形...。让我们建造它吧!

构建Wayland应用程序令人耳目一新(如果你习惯GDK、Qt和SDL的话)。

下面是我们如何在Linux机器(安装了Wayland、Mesa EGL和OpenGL ES2库)上用egl.c构建Wayland应用程序……。

#构建Wayland EGL appgcc\-g\-o EGL\egl.c\-WL,-Map=egl.map\-L/usr/lib/aarch64-linux-gnu/mesa-egl\-lwayland-client\-lwayland-server\-lwayland-egl\-lEGL\-lGLESv2。

这将生成可执行的应用程序EGL。在我们的Linux机器上运行EGL应用程序,如下所示...。

#安装Weston Wayland Compositor...#适用于Arch Linux和Manjaro:Sudo Pacman-S Weston#适用于其他发行版:#Check https://github.com/wayland-project/weston#在我们的电脑上使用PinePhone屏幕尺寸启动Weston Wayland Compositor--width=720--width=1398&;#运行Wayland EGL应用程序。/egl。

它使用Weston合成器,这是在X11上运行的Wayland合成器的参考实现。

/Wayland接口Static struct wl_display*display;//Wayland Displaystatic struct wl_Compositor*Compositor;//Wayland Compositorstatic struct wl_shell*shell;//Wayland Shell/连接到Wayland Service并获取Wayland Compositor和Wayland Shellstatic void get_server_reference(Void)的接口{//连接到Wayland Service Display=wl_display_connect(null。}//获取Wayland注册表结构wl_register*register=wl_display_get_register(Display);assert(register!=null);//获取Wayland注册表失败//添加注册表回调以处理Wayland服务返回的接口wl_register_add_listener(register,&;register_listener,null);//等待注册表回调获取Wayland接口wl_display_dispatch(Display);wl_display_round Trip(display。//获取Wayland Compositor Assert失败(shell!=null);//获取Wayland Shell}失败

Wayland Compositor作为Linux服务运行,它监听Linux套接字File:/Run/User/32011/Wayland-0 for PinePhone on Ubuntu Touch。

为了使用Wayland服务,我们获取Wayland Compositor和Wayland Shell的接口。

要从Wayland注册表获取Compositor和Shell,我们添加一个注册表侦听器(稍后将详细介绍)...。

现在,我们将注册表侦听器请求分派给Wayland服务。(请记住,Wayland服务在Linux套接字消息上运行)。

/Wayland Servicestatic Const struct wl_register_listener REGISTRY_LISTENER={GLOBAL_REGISTRY_HANDLER,GLOBAL_REGISTRY_REMOVER}返回的接口回调;/Wayland Servicestatic void GLOBAL_REGISTRY_HANDLER(void*data,struct wl_register*register,uint32_t id,const char*interface,uint32_t version){printf(";已获取接口%s ID%d)返回的接口回调。)==0){//绑定到Wayland Compositor接口组合器=wl_register_bind(注册表,id,&;wl_positor_interface,//接口类型1);//接口版本}Else if(strcmp(interface,";wl_shell";)==0){//绑定到Wayland Shell接口shell=wl_register_bind(注册表,id,&;wl_shell_interface,//接口类型1);

GLOBAL_REGISTRY_HANDLER()是将为Wayland Registry中的每个接口触发的回调函数。

Ubuntu Touch Unity-System-Compositor的Wayland服务返回一大堆有趣的Wayland接口(如qt_WindowManager)。

但是今天我们将绑定到名为wl_positor的合成器接口和名为wl_shell的Shell接口。

我们上面看到的四个框是从放大的2像素乘以2像素位图呈现的:pinephone-mir/texture.c。

//2x2图像,3字节/像素(R,G,B)GLubyte像素[4*3]={255,0,0,//红色0,255,0,//绿色0,0,255,//蓝色255,255,0//黄色};

//创建具有四种不同颜色的简单2x2纹理图像GLuint CreateSimpleTexture2D(){//Texture Object Handle GLuint textureId;//2x2 Image,3字节/像素(R,G,B)GLubyte像素[4*3]={255,0,0,//Red 0,255,0,//Green 0,0,255,//Blue 255,255,0//黄色};//使用紧凑的数据像素glxel.。//绑定纹理对象glBindTexture(GL_Texture_2D,textureId);//加载纹理glTexImage2D(GL_Texture_2D,0,GL_RGB,2,2,0,GL_RGB,GL_UNSIGNED_BYTE,Pixels);//设置过滤模式glTexParameteri(GL_Texture_2D,GL_Texture_min_Filter,GL_NEAREST);glTexParameteri(GL_Texture_2D,GL_Texture_MAG_。

(渲染位图不是最有效的方式...。不过,让我们试试这个,试驾PinePhone的GPU吧!)。

这是我们创建OpenGL纹理的常用方法,如OpenGL®ES 3.0编程指南中所述。

接下来是棘手的部分..。在渲染OpenGL纹理之前,我们需要使用类似C的语言对PinePhone上的GPU着色器进行编程:pinephone-mir/texture.c。

//初始化着色器和程序objectint Init(ESContext*esContext){esContext->;userdata=malloc(sizeof(Userdata));userdata*userdata=esContext->;userdata;GLbyte vShaderStr[]=";属性Vector 4 a_position;\n";";属性Vector 2 a_texCoord;\n";";变化Vector 2。";gl_position=a_position;\n";";v_texCoord=a_texCoord;\n";";}\n";;GLbyte fShaderStr[]=";精度中值浮点;\n";";变化矢量2 v_texCoord;\n";";均匀采样器2D s_Texture;\n";&#。{\n";";gl_FragColor=texture2D(s_Texture,v_texCoord);\n";";}\n";;//加载着色器并获取链接的程序对象userdata->;ProgramObject=esLoadProgram(vShaderStr,fShaderStr);...。

我们现在正在与PinePhone的GPU对话,它的级别太低了,只能识别三角形,而不能识别矩形。

因此,要渲染OpenGL纹理,我们将矩形纹理映射到两个三角形并渲染它们:pinephone-mir/texture.c。

//使用在Init()void Draw(ESContext*esContext){GLloat vVerties[]={-0.5f,0.5f,0.0f,//Position 0 0.0f,0.0f,//TexCoord 0-0.5f,-0.5f,0.0f,//Position 1 0.0f,-0.5f,0.0f,//Position 2 1.0f,//TexCoord 2 0.5f,//TexCoord 1 0.5f,-0.5f,0.0f,//Position 2 1.0f,0.0f,//Position 0-0.5f,-0.5f,0.0f,//Position 0 0.0f,0.0f,//TexCoord 0-0.5f,-0.5f,0.0f中创建的着色器对绘制三角形。0.5f,0.0f,//位置3 1.0f,0.0f//Texcoord 3};GLushort index[]={0,1,2,//第一个三角形0,2,3//第二个三角形};...//将6个顶点绘制为2个三角形glDrawElements(GL_Triangles,6,GL_UNSIGNED_SHORT,INDEX);}。

最后,多亏了Wayland和OpenGL,我们连接了上面的代码以在PinePhone上呈现四个颜色框:pinephone-mir/egl2.c。

/渲染OpenGL ES2 Displaystatic void Render_Display(){//创建纹理上下文static ESContext esContext;esInitContext(&;esContext);esContext.width=width;esContext.Height=Height;//绘制纹理Init(&;esContext);Draw(&;esContext);//立即渲染glFlush();}。

本文中的OpenGL纹理代码改编自OpenGL®ES 2.0编程指南";

我们如何在PinePhone上呈现一个简单的图形用户界面(GUI),就像上面的按钮一样?

#include";../lvgl.h";/渲染Button Widget和标签Widgetstatic void Render_Widgets(Void){LV_obj_t*btn=LV_BTN_CREATE(LV_SCR_ACT(),NULL);//在当前屏幕LV_obj_set_pos(btn,10,10);//设置其位置LV_obj_set_size(。//给按钮LV_LABEL_SET_TEXT(LABEL,";Button";);//设置标签文本}。

LVGL是为嵌入式设备设计的一个简单的C工具包,因此它只需要很少的内存和处理能力。LVGL是自给自足的..。字体和图标捆绑到LVGL库中。

还记得我们如何通过使用CreateSimpleTexture2D()创建OpenGL纹理来渲染简单的2像素x 2像素位图吗?

让我们将位图扩展到覆盖整个PinePhone屏幕:720像素乘以1398像素。

/PinePhone屏幕分辨率,在LV_conf.h#定义LV_HOR_RES_MAX(720)#定义LV_VER_RES_MAX(1398)#定义LV_SCALE_RES 1/屏幕缓冲区#定义Bytes_Per_Pixel 3GLubyte像素[LV_HOR_RES_MAX*LV_VER_RES_MAX*Bytes_Per_Pixel];/为屏幕缓冲区GLuint CreateTexerGLuint创建OpenGL纹理。GlTexImage2D(GL_Texture_2D,0,//级别GL_RGB,LV_HOR_RES_Max,//宽度LV_VER_RES_Max,//高度0,//格式GL_RGB,GL_UNSIGNED_BYTE,像素);glTexParameteri(GL_Texture_2D,GL_Texture_Min_Filter,GL_LINEAR);glTexParameteri(GL_Texture_2D,GL_Texture_MAG_Filter,GL_LINEAR);glTexParameteri(GL_Texture_2D,GL_Texture_MAG_Filter,GL_LINEAR)。GlTexParameteri(GL_Texture_2D,GL_Texture_WRAP_T,GL_CLAMP_TO_EDGE);return texId;}。

像素是屏幕缓冲区,它将包含呈现的UI控件(如按钮)的像素。

/设置屏幕缓冲区中像素的颜色void put_px(uint16_t x,uint16_t y,uint8_tr,uint8_tg,uint8_t b,uint8_t a){assert(x>;=0);assert(x<;lv_HOR_RES_Max);assert(y>;=0);assert(y<;lv_ver_res。像素[i++]=r;//红色像素[i++]=g;//绿色像素[i++]=b;//蓝色}。

/渲染OpenGL ES2 Displaystatic void Render_Display(){//此部分是新的...。//初始化LVGL display lv_init();lv_port_disp_init();//创建LVGL控件:Button和Label Render_widgets();//渲染LVGL控件LV_TASK_HANDLER();//此部分与之前相同...。//创建纹理上下文Static ESContext esContext;esInitContext(&;esContext);esContext.width=width;esContext.Height=Height;//绘制纹理Init(&;esContext);Draw(&;esContext);//立即渲染glFlush();}。

Render_widgets():调用LVGL库以创建两个UI控件:按钮和标签。

现在,让我们调整LVGL库以将UI控件呈现到我们的屏幕缓冲区像素中

根据LVGL移植文档,我们需要编写一个刷新回调函数disp_flush(),LVGL将调用该函数将UI控件渲染到屏幕缓冲区。

//将内部缓冲区的内容刷新到显示器上的特定区域//您可以在后台使用DMA或任何硬件加速来完成此操作,但//';lv_disp_flush_ady()';必须在完成时调用。static void disp_flush(lv_disp_drv_t*disp_drv,const lv_area_。

.