这次主要分析在MiniGUI中,显示引擎的初始化流程,让大家理解MiniGUI.cfg配置文件怎么影响到使用哪一个引擎的,如果需要开发一个新引擎的话,本文章也有帮助
因为使用MiniGUI的人数也少,写这些文章没有什么价值,以后可能就不再写minigui的文章了,之后会多写一点QT的文章
引擎名称 | 引擎含义 | 默认编译 |
---|---|---|
videodummy | 是否包括虚拟NEWGAL引擎,所有操作系统 | yes |
videofbcon | 是否包括FrameBuffer控制台NEWGAL引擎,Linux/uClinux | yes |
videoqvfb | 是否包括Qt Virtual FrameBuffer NEWGAL引擎,Linux | yes |
rtosxvfb | 是否包括RTOS Virtual FrameBuffer NEWGAL引擎 注意:如果要启用rtosxvfb,必须禁用pcxvfb | no |
pcxvfb | 是否包括PC Virtual FrameBuffer NEWGAL引擎,如qvfb,mvfb,gvfb或wvfb | yes |
videowvfb | 是否包括Windows Virtual FrameBuffer NEWGAL引擎,Win32 | no |
videocommlcd | 是否包括普通LCD的NEWGAL引擎,所有操作系统 | no |
videomlshadow | 是否包括MLShadow NEWGAL引擎,所有操作系统,MiniGui-Threads 、MiniGui-Standalone | no |
videoshadow | 是否包括Shadow NEWGAL引擎 | no |
videoem86gfx | 是否包括EM86xx GFG的NEWGAL引擎,EM86开发板的GFX图形引擎 | no |
videoem85xxyuv | 是否包括EM85xx YUV的NEWGAL引擎,EM85xx开发板的YUV图形引擎,uClinux | no |
videoem85xxosd | 是否包括EM85xx OSD的NEWGAL引擎,EM85xx开发板的OSD图形引擎,uClinux | no |
videosvpxxosd | 是否包括SVPXXOSD NEWGAL引擎,uClinux | no |
videobf533 | 是否包括通过SPI的BF533 OSD的NEWGAL引擎,uClinux | no |
videomb93493 | 是否包括mb93493的NEWGAL引擎YUV FrameBuffer驱动程序,uClinux | no |
videoutpmc | 是否包括UTPMC的NEWGAL引擎,uClinux | no |
videodfb | 是否包括DirectFB的NEWGAL引擎,将MiniGui运 行在DirectFB之上,Linux | no |
videost7167 | 是否包括ST7167的NEWGAL引擎 | no |
videostgfb | 是否包括STGFB的NEWGAL引擎 | no |
videohi35xx | 是否包括Hi35xx视频NEWGAL引擎,hi35xx开发板的图形引擎,Linux | no |
videohi3560a | 是否包括Hi3560A视频NEWGAL引擎 | no |
videogdl | 是否包括GDL Video NEWGAL引擎 | no |
videosigma8654 | 是否包括sigma8654 NEWGAL引擎 | no |
videomstar | 是否包括mstar NEWGAL引擎 | no |
videocustom | 是否包括自定义NEWGAL引擎 | no |
videonexus | 是否包括nexus NEWGAL引擎 | no |
videos3c6410 | 是否包括s3c6410 NEWGAL引擎 | no |
本文以videofbcon引擎为例,先看一下时序图
在文件libminigui-gpl-3.2/src/kernel/init.c中,有入口函数InitGUI,主要关注mg_InitGAL和mg_InitScreenDC函数。mg_InitGAL函数里面会解析MiniGUI.cfg文件里面的配置,然后选择一种显示引擎初始化,之后需要显示的界面都是绘制到__gal_screen这个GAL_Surface上面。mg_InitScreenDC是初始化DC槽,一共会初始化16个,由DCSLOTNUMBER宏定义,也就是说,在应用中最多只能申请到16个DC句柄,所以开发应用的时候要注意
/* libminigui-gpl-3.2/src/kernel/init.c */
int GUIAPI InitGUI (int args, const char *agr[])
{
/*......*/
step++;
/* 显示引擎初始化 */
switch (mg_InitGAL ()) {
case ERR_CONFIG_FILE:
fprintf (stderr,
"KERNEL>InitGUI: Reading configuration failure!\n");
return step;
case ERR_NO_ENGINE:
fprintf (stderr,
"KERNEL>InitGUI: No graphics engine defined!\n");
return step;
case ERR_NO_MATCH:
fprintf (stderr,
"KERNEL>InitGUI: Can not get graphics engine information!\n");
return step;
case ERR_GFX_ENGINE:
fprintf (stderr,
"KERNEL>InitGUI: Can not initialize graphics engine!\n");
return step;
}
/* 初始化主屏幕DC */
step++;
if (!mg_InitScreenDC (__gal_screen)) {
fprintf (stderr, "KERNEL>InitGUI: Can not initialize screen DC!\n");
goto failure1;
}
/*......*/
}
在文件libminigui-gpl-3.2/src/newgal/newgal.c,看看mg_InitGAL函数里面具体是怎么实现的,其实MiniGUI不仅可以从配置文件读取配置,也可以使用环境变量,比如下面的MG_GAL_ENGINE,如果设置了该环境变量,那么则忽略gal_engine的配置。主要关注GAL_VideoInit和GAL_SetVideoMode函数
/* libminigui-gpl-3.2/src/newgal/newgal.c */
int mg_InitGAL (void)
{
/*......*/
#ifndef __NOUNIX__
/* 获取环境变量MG_GAL_ENGINE,如果有值则不再获取gal_engine的值 */
if ((env_value = getenv ("MG_GAL_ENGINE"))) {
strncpy (engine, env_value, LEN_ENGINE_NAME);
engine [LEN_ENGINE_NAME] = '\0';
}
else
#endif
#ifndef _MG_MINIMALGDI
/* 从MiniGUI.cfg获取gal_engine的值 */
if (GetMgEtcValue ("system", "gal_engine", engine, LEN_ENGINE_NAME) < 0) {
return ERR_CONFIG_FILE;
}
#else /* _MG_MINIMALGDI */
# ifdef _MGGAL_PCXVFB
strcpy(engine, "pc_xvfb");
# else
strcpy(engine, "dummy");
# endif
#endif /* _MG_MINIMALGDI */
/* 显示引擎初始化 */
if (GAL_VideoInit (engine, 0)) {
GAL_VideoQuit ();
fprintf (stderr, "NEWGAL: Does not find matched engine: %s.\n", engine);
return ERR_NO_MATCH;
}
#if defined (WIN32) || !defined(__NOUNIX__)
if ((env_value = getenv ("MG_DEFAULTMODE"))) {
strncpy (mode, env_value, LEN_MODE);
mode [LEN_MODE] = '\0';
}
else
#endif
/* 获取配置的分辨率和位深defaultmode */
if (GetMgEtcValue (engine, "defaultmode", mode, LEN_MODE) < 0)
if (GetMgEtcValue ("system", "defaultmode", mode, LEN_MODE) < 0)
return ERR_CONFIG_FILE;
/* 解析mode,赋值给w,h,depth */
if (!GAL_ParseVideoMode (mode, &w, &h, &depth)) {
GAL_VideoQuit ();
fprintf (stderr, "NEWGAL: bad video mode parameter: %s.\n", mode);
return ERR_CONFIG_FILE;
}
/* 设置引擎的分辨率和位深,一般要求和硬件一致 */
if (!(__gal_screen = GAL_SetVideoMode (w, h, depth, GAL_HWPALETTE))) {
GAL_VideoQuit ();
fprintf (stderr, "NEWGAL: Set video mode failure.\n");
return ERR_GFX_ENGINE;
}
/*......*/
return 0;
}
在文件libminigui-gpl-3.2/src/newgal/video.c,GAL_VideoInit初始化显示引擎,确定本机像素格式,其中GAL_GetVideo函数获取需要初始化的引擎,比如fbcon,GAL_GetVideo函数里面是一个循环,匹配bootstrap结构体里面定义的引擎对象,再调用create创建具体的显示引擎,而video->VideoInit则是调用到需要使用的引擎对象的init函数中,fbcon的init函数是FB_VideoInit
/* libminigui-gpl-3.2/src/newgal/video.c */
int GAL_VideoInit (const char *driver_name, Uint32 flags)
{
GAL_VideoDevice *video;
GAL_PixelFormat vformat;
Uint32 video_flags;
/*......*/
/* 该函数通过前面传入的driver_name,确定到底要初始化哪一个显示引擎,在bootstrap数组里面存着所有引擎 */
video = GAL_GetVideo(driver_name);
if ( video == NULL ) {
return (-1);
}
video->screen = NULL;
/* current_video是一个全局对象,方便调用对应引擎里面的函数 */
current_video = video;
/* 真正的初始化显示引擎 */
memset(&vformat, 0, sizeof(vformat));
if ( video->VideoInit(video, &vformat) < 0 ) {
GAL_VideoQuit();
return(-1);
}
/*......*/
/* 创建适当格式大小为零的Surface,GAL_VideoSurface也就是current_video->screen,和__gal_screen是一个值 */
video_flags = GAL_SWSURFACE;
GAL_VideoSurface = GAL_CreateRGBSurface(video_flags, 0, 0,
vformat.BitsPerPixel,
vformat.Rmask, vformat.Gmask, vformat.Bmask, vformat.Amask);
if ( GAL_VideoSurface == NULL ) {
GAL_VideoQuit();
return(-1);
}
GAL_VideoSurface->video = current_video;
video->info.vfmt = GAL_VideoSurface->format;
return(0);
}
bootstrap具体结构如下,在配置MiniGUi的时候使能不同的引擎,则会定义不同的宏。VideoBootStrap有两个函数指针,一个是available,一个是create,create可以指向具体某一个引擎的的create函数中,fbcon引擎是FB_CreateDevice函数,而FB_CreateDevice函数里也是一些函数指针的赋值
/* libminigui-gpl-3.2/src/newgal/video.c */
/* Available video drivers */
static VideoBootStrap *bootstrap[] = {
#ifdef _MGGAL_DUMMY
&DUMMY_bootstrap,
#endif
#ifdef _MGGAL_SUNXIFBION
&SUNXIFBION_bootstrap,
#endif
#ifdef _MGGAL_SUNXIFB
&SUNXIFB_bootstrap,
#endif
#ifdef _MGGAL_SUNXI
&SUNXI_bootstrap,
#endif
#ifdef _MGGAL_FBCON
&FBCON_bootstrap,
#endif
#ifdef _MGGAL_QVFB
&QVFB_bootstrap,
#endif
/*......*/
NULL
};
下面具体看看video->VideoInit函数,这里举例fbcon,也就是FB_VideoInit函数,主要是打开fb设备,映射内存
/* libminigui-gpl-3.2/src/newgal/fbcon/fbvideo.c */
static int FB_VideoInit(_THIS, GAL_PixelFormat *vformat)
{
struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;
int i;
const char *GAL_fbdev;
/* 可以通过环境变量FRAMEBUFFER来设置打开哪个fb设备 */
GAL_fbdev = getenv("FRAMEBUFFER");
if ( GAL_fbdev == NULL ) {
GAL_fbdev = "/dev/fb0";
}
console_fd = open(GAL_fbdev, O_RDWR, 0);
if ( console_fd < 0 ) {
GAL_SetError("NEWGAL>FBCON: Unable to open %s\n", GAL_fbdev);
return(-1);
}
/* 获取一些硬件信息 */
if ( ioctl(console_fd, FBIOGET_FSCREENINFO, &finfo) < 0 ) {
GAL_SetError("NEWGAL>FBCON: Couldn't get console hardware info\n");
FB_VideoQuit(this);
return(-1);
}
/*......*/
/* 内存映射,之后minigui绘制的窗口数据就在这个mapped_mem地址,然后会显示在LCD上 */
mapped_offset = (((long)finfo.smem_start) -
(((long)finfo.smem_start)&~(getpagesize () - 1)));
mapped_memlen = finfo.smem_len+mapped_offset;
mapped_mem = mmap(NULL, mapped_memlen,
PROT_READ|PROT_WRITE, MAP_SHARED, console_fd, 0);
/*......*/
/* We're done! */
return(0);
}
找到并且打开了显示引擎,那么回到video.c,在调用GAL_VideoInit之后,会调用GAL_SetVideoMode
/* libminigui-gpl-3.2/src/newgal/video.c */
GAL_Surface * GAL_SetVideoMode (int width, int height, int bpp, Uint32 flags)
{
GAL_VideoDevice *video, *this;
GAL_Surface *prev_mode, *mode;
/*......*/
/* 获取与请求最接近的模式 */
if (!GAL_GetVideoMode(&video_w, &video_h, &video_bpp, flags)) {
GAL_SetError ("NEWGAL: GAL_GetVideoMode error, "
"not supported video mode.\n");
return(NULL);
}
/*......*/
/* 调用SetVideoMode去设置模式,video是前面实例化的具体引擎 */
prev_mode = GAL_VideoSurface;
GAL_VideoSurface = NULL; /* In case it's freed by driver */
mode = video->SetVideoMode(this, prev_mode,video_w,video_h,video_bpp,flags);
GAL_VideoSurface = (mode != NULL) ? mode : prev_mode;
/*......*/
video->info.vfmt = GAL_VideoSurface->format;
GAL_VideoSurface->video = current_video;
return(GAL_PublicSurface);
}
/* 这里注意下GAL_PublicSurface的定义 */
/* libminigui-gpl-3.2/src/newgal/sysvideo.h */
extern GAL_VideoDevice *current_video;
#define GAL_VideoSurface (current_video->screen)
#define GAL_PublicSurface (current_video->screen)
调用SetVideoMode去设置模式,video是前面实例化的具体引擎,这里用fbcon举例,那么最终调用的是FB_SetVideoMode,这个函数很重要,一些硬件特性在这里面指定
GAL_DOUBLEBUF这个flag目前是没有用到的,这个的作用是什么呢,我们知道framebuffer里面一般会有两个buffer,一个buffer用来显示,一个buffer用来绘制,绘制完成后切换这两个buffer,这样可以解决整屏绘制时肉眼可见的切线问题,这个是硬件上的双缓冲,minigui中也有软件上的双缓冲,请往下看
/* libminigui-gpl-3.2/src/newgal/fbcon/fbvideo.c */
static GAL_Surface *FB_SetVideoMode(_THIS, GAL_Surface *current,
int width, int height, int bpp, Uint32 flags)
{
/*......*/
/* 获取屏幕信息 */
if (ioctl(console_fd, FBIOGET_VSCREENINFO, &vinfo) < 0 ) {
GAL_SetError("NEWGAL>FBCON: Couldn't get console screen info");
return(NULL);
}
/* GAL_DOUBLEBUF这里是没有实现的,但是可以根据需要实现它 */
if ( ((vinfo.xres != width) || (vinfo.yres != height) ||
(vinfo.bits_per_pixel != bpp) /* || (flags & GAL_DOUBLEBUF) */) ) {
/*......*/
if ( ioctl(console_fd, FBIOPUT_VSCREENINFO, &vinfo) < 0 ) {
vinfo.yres_virtual = height;
if ( ioctl(console_fd, FBIOPUT_VSCREENINFO, &vinfo) < 0 ) {
GAL_SetError("NEWGAL>FBCON: Couldn't set console screen info");
return(NULL);
}
}
} else {
int maxheight;
maxheight = height;
if ( vinfo.yres_virtual > maxheight ) {
vinfo.yres_virtual = maxheight;
}
}
cache_vinfo = vinfo;
if ( ioctl(console_fd, FBIOGET_FSCREENINFO, &finfo) < 0 ) {
GAL_SetError("NEWGAL>FBCON: Couldn't get console hardware info");
return(NULL);
}
/* 这里最重要,设置了minigui绘制时需要的属性,current->pixels是绘制时的首地址 */
/* 如果需要实现GAL_DOUBLEBUF,那么current->pixels需要根据时机动态切换地址 */
current->flags = (GAL_FULLSCREEN|GAL_HWSURFACE);
current->w = vinfo.xres;
current->h = vinfo.yres;
current->pitch = finfo.line_length;
current->pixels = mapped_mem+mapped_offset;
/* Set up the information for hardware surfaces */
surfaces_mem = (char *)current->pixels +
vinfo.yres_virtual*current->pitch;
surfaces_len = (mapped_memlen-(surfaces_mem-mapped_mem));
/* 返回current,也就是current_video和__gal_screen */
return(current);
}
终于初始化完毕硬件,那么到软件了,也就是mg_InitScreenDC,在dc_InitClipRgnInfo中初始化16个DC,在创建创建双缓冲风格WS_EX_AUTOSECONDARYDC的窗口会使用DC,在调用类似CreateCompatibleDCEx的函数时也会使用DC,所以需要注意不要超过使用数量,__mg_screen_dc是屏幕的DC,在应用的MSG_PAINT消息中调用BeginPaint函数就可以获取到这个对象。创建双缓冲风格的窗口可以在一定程度上改善切线和闪烁现象,但是还是不能和硬件上的相比
/* libminigui-gpl-3.2/src/newgdi/gdi.c */
BOOL mg_InitScreenDC (void* surface)
{
/* 初始化用于分配剪切矩形的专用块数据堆 */
InitFreeClipRectList (&__mg_FreeClipRectList, SIZE_CLIPRECTHEAP);
INIT_LOCK (&__mg_gdilock, NULL);
INIT_LOCK (&dcslot, NULL);
/* 此函数初始化所有DC插槽中的剪辑区域 */
dc_InitClipRgnInfo ();
/* __mg_screen_dc和__mg_screen_sys_dc是屏幕的DC */
dc_InitScreenDC (&__mg_screen_dc, (GAL_Surface *)surface);
dc_InitScreenDC (&__mg_screen_sys_dc, (GAL_Surface *)surface);
return TRUE;
}
/* DCSLOTNUMBER定义在libminigui-gpl-3.2/src/include/dc.h */
static void dc_InitClipRgnInfo(void)
{
int i;
for (i=0; i<DCSLOTNUMBER; i++) {
/* Local clip region */
InitClipRgn (&DCSlot[i].lcrgn, &__mg_FreeClipRectList);
MAKE_REGION_INFINITE(&DCSlot[i].lcrgn);
/* Global clip region info */
DCSlot[i].pGCRInfo = NULL;
DCSlot[i].oldage = 0;
/* Effective clip region */
InitClipRgn (&DCSlot[i].ecrgn, &__mg_FreeClipRectList);
}
}