图形设备
图形设备,也称作帧缓冲(FrameBuffer)设备,通过该设备可以直接操作显存本身。SylixOS 中图形设备名称为/dev/fb0,如果硬件支持多个图层,相应的会有 /dev/fb1、/dev/fb2 等设备存在。在使用图形设备之前,需要首先获取其与显示模式相关的信息,比如分辨率,每个像素占用的字节大小及其 RGB 的编码结构、显存的大小等信息,这样我们才能将需要显示的图像数据正确地写入显存。在 SylixOS 中,用以描述图形设备信息的结构定义位于 <SylixOS/system/device/graph/gmemDev.h> 如下程序所示。
typedef struct {
ULONG GMVI_ulXRes; /* 可视区域 */
ULONG GMVI_ulYRes;
ULONG GMVI_ulXResVirtual; /* 虚拟区域 */
ULONG GMVI_ulYResVirtual;
ULONG GMVI_ulXOffset; /* 显示区域偏移 */
ULONG GMVI_ulYOffset;
ULONG GMVI_ulBitsPerPixel; /* 每个像素的数据位数 */
ULONG GMVI_ulBytesPerPixel; /* 每个像素的存储字节数 */
/* 有些图形处理器 DMA 为了对齐 */
/* 使用了填补无效字节 */
ULONG GMVI_ulGrayscale; /* 灰度等级 */
ULONG GMVI_ulRedMask; /* 红色掩码 */
ULONG GMVI_ulGreenMask; /* 绿色掩码 */
ULONG GMVI_ulBlueMask; /* 蓝色掩码 */
ULONG GMVI_ulTransMask; /* 透明度掩码 */
LW_GM_BITFIELD GMVI_gmbfRed; /* true color bitfield */
LW_GM_BITFIELD GMVI_gmbfGreen;
LW_GM_BITFIELD GMVI_gmbfBlue;
LW_GM_BITFIELD GMVI_gmbfTrans;
BOOL GMVI_bHardwareAccelerate; /* 是否使用硬件加速 */
ULONG GMVI_ulMode; /* 显示模式 */
ULONG GMVI_ulStatus; /* 显示器状态 */
} LW_GM_VARINFO;
typedef LW_GM_VARINFO *PLW_GM_VARINFO;
typedef struct {
PCHAR GMSI_pcName; /* 显示器名称 */
ULONG GMSI_ulId; /* ID */
size_t GMSI_stMemSize; /* framebuffer内存大小 */
size_t GMSI_stMemSizePerLine; /* 每一行的内存大小 */
caddr_t GMSI_pcMem; /* 显示内存 (需要驱动程序映射) */
} LW_GM_SCRINFO;
typedef LW_GM_SCRINFO *PLW_GM_SCRINFO;
LW_GM_VARINFO 包含了与显示数据密切相关的信息。其中最重要的是像素的 RGB 掩码及其占用的数据位数。数据位数有以下几种:
- 8 位:最多能显示 256 种颜色。如果硬件仅支持黑白显示,则一个像素可支持 256 阶灰度值。如果硬件支持彩色显示,通常情况下这 256 个编码值对应 256 种生活中最常用的颜色,这便是调色板模式,即用有限的颜色来近似表达实际的显示需求。
- 16 位:最大能显示 65536 种颜色,也称作伪真彩色,支持 16 位色彩的硬件能够显示生活中绝大多数的颜色。16 位数据显示下,有 RGB555 和 RGB565 两种编码方式,这可以通过上面的 GMVI_ulRedMask、GMVI_ulGreenMask、GMVI_ulBlueMask 得到。
- 24 位:能够显示多达 1600 万种颜色,用肉眼几乎无法分辨出与实际颜色的差异,因此也叫做真彩色。在 24 位显示下,一个像素的红、绿、蓝三种颜色分别使用 8 位表示。
- 32 位:相对于 24 位,它多出的 8 位用来表示像素的 256 阶透明度(0 表示不透明,255 表示完全透明,此时该像素不被显示)。使用 GMVI_ulTransMask 可以知道像素的透明度值的位置。
在大多数嵌入式系统中,显示控制器通常支持 8 位或 16 位数据显示,某些高端的处理器才支持 24 位或 32 位真彩显示。通常情况下,8 位数据显示时一个像素所占用的内存为 1 个字节,相应地 16 位占用 2 个字节内存,24 位占用 3 个字节内存,32 位占用 4 个字节内存。但是,可能某些硬件对 DMA 内存的对齐限制,在各个像素所占用内存之间需要填充字节以满足对齐需要。因此,实际使用中,我们应该使用 GMVI_ulBitsPerPixel 并配合 RGB 掩码值设置像素内容,使用 GMVI_ulBytesPerPixel 处理像素之间的内存偏移。
LW_GM_SCRINFO 包含了显存的一些必要信息,GMSI_stMemSize 表示显存总的字节大小,GMSI_stMemSizePerLine 表示每一行所占用的字节大小,根据这些信息我们可以知道如何处理行与行之间的内存偏移,同时也可以知道总的列数。
通常情况下,我们根据以上所讲的信息就能正确地完成图像的显示,此外还有其他一些信息,如可视区域、虚拟区域、显示区域偏移、硬件加速等。下面的程序简单地展示图形设备的使用方法。
#include <SylixOS.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
static VOID __drawPixel (VOID *pvFrameBuffer,
LW_GM_SCRINFO *pscrinfo,
LW_GM_VARINFO *pvarinfo,
INT iX,
INT iY,
UINT32 uiColor)
{
VOID *pvPixelAddr;
if ((iX < 0) || (iX >= pvarinfo->GMVI_ulXResVirtual) ||
(iY < 0) || (iY >= pvarinfo->GMVI_ulYResVirtual)) {
return;
}
pvPixelAddr = (UINT8 *)pvFrameBuffer + iY *
pscrinfo->GMSI_stMemSizePerLine;
switch (pvarinfo->GMVI_ulBitsPerPixel) {
case 16:
if (pvarinfo->GMVI_ulGreenMask == (0x3f << 5)) { /* RGB565模式 */
uiColor = ((uiColor & 0xff0000) >> 16 >> 3 << 11) |
((uiColor & 0x00ff00) >> 8 >> 2 << 5) |
((uiColor & 0x0000ff) >> 0 >> 3 << 0);
} else { /* RGB555模式 */
uiColor = ((uiColor & 0xff0000) >> 16 >> 3 << 10) |
((uiColor & 0x00ff00) >> 8 >> 3 << 5) |
((uiColor & 0x0000ff) >> 0 >> 3 << 0);
}
*((UINT16 *)pvPixelAddr + iX) = (UINT16)uiColor;
break;
case 24:
case 32:
*((UINT32 *)pvPixelAddr + iX) = uiColor;
break;
}
}
static VOID __drawHorizLine (VOID *pvFrameBuffer,
LW_GM_SCRINFO *pscrinfo,
LW_GM_VARINFO *pvarinfo,
INT iXstart,
INT iYstart,
INT iXend,
UINT32 uiColor)
{
for (; iXstart <= iXend; iXstart++) {
__drawPixel(pvFrameBuffer, pscrinfo, pvarinfo,
iXstart, iYstart, uiColor);
}
}
int main (int argc, char *argv[])
{
INT iFbFd;
LW_GM_SCRINFO scrInfo;
LW_GM_VARINFO varInfo;
INT iError;
VOID *pvFrameBuffer;
iFbFd = open("/dev/fb0", O_RDWR);
if (iFbFd < 0) {
fprintf(stderr, "open /dev/fb0 failed.\n");
return (-1);
}
iError = ioctl(iFbFd, LW_GM_GET_SCRINFO, &scrInfo);
if (iError < 0) {
fprintf(stderr, "get /dev/fb0 screen info failed.\n");
goto __error;
}
iError = ioctl(iFbFd, LW_GM_GET_VARINFO, &varInfo);
if (iError < 0) {
fprintf(stderr, "get /dev/fb0 var info failed.\n");
goto __error;
}
pvFrameBuffer = mmap(LW_NULL, scrInfo.GMSI_stMemSize,
PROT_READ | PROT_WRITE, MAP_SHARED, iFbFd, 0);
if (pvFrameBuffer == MAP_FAILED) {
fprintf(stderr, "mmap /dev/fb0 failed.\n");
goto __error;
}
memset(pvFrameBuffer, 0xff, scrInfo.GMSI_stMemSize); /* 清屏 */
__drawHorizLine(pvFrameBuffer, &scrInfo, &varInfo, 0, 10,
varInfo.GMVI_ulXResVirtual - 1, 0xff0000);
__drawHorizLine(pvFrameBuffer, &scrInfo, &varInfo, 0, 20,
varInfo.GMVI_ulXResVirtual - 1, 0x00ff00);
__drawHorizLine(pvFrameBuffer, &scrInfo, &varInfo, 0, 30,
varInfo.GMVI_ulXResVirtual - 1, 0x0000ff);
munmap(pvFrameBuffer, scrInfo.GMSI_stMemSize);
close(iFbFd);
return (0);
__error:
close(iFbFd);
return (-1);
}
在 main 函数中,我们首先获取与显示相关的信息,随后使用 mmap 将显存映射到用户虚拟空间,这样我们操作该虚拟空间相当于直接操作显存,关于 mmap 的详细介绍见 12.4 虚拟内存管理部分。
在 __drawPixel 函数中,参数 uiColor 定义为一个 32 位的数据类型,从低地址到高地址开始,字节 0 表示蓝色,字节 1 表示绿色,字节 2 表示红色,这和真彩色(24 位或 32 位)的颜色格式一样。如果是 16 位数据模式,我们仅通过绿色掩码值,即可知道当前的 RGB 模式,并根据相应的模式进行对应的转换。注意,由于 16 位模式下不能完全表示所有的真彩色,因此作了相应的线性转换处理,比如 0~255,如果是 5 位数据,则对应 0~31,如果是 6 位数据,则对应 0~63。由于本例的重点是展示图形设备的使用方法,因此仅仅简单地画出三条水平直线,读者可通过其他方式了解画任意直线、圆形或者椭圆形的相关算法。
限于篇幅,同时为了使程序简单直观,很多地方没有作详细的处理,例如并没有考虑像素实际所占用的字节大小,而是假设它刚好与其数据位数一致;也没有考虑真实显示区域与虚拟显示区域之间的偏移,而是假设它们的偏移为 0;程序中也没有处理 8 位数据显示的情况;也忽略了 32 位模式下像素的透明度信息。读者须明白,这些信息在实际应用中都必须进行适当处理。
通常情况下,应用程序通过 GUI 组件间接操作显存设备,GUI 本身会处理上面所讲的所有信息。只有在某些特殊场合(比如需要更高显示效率,或者一些简单的图形化应用),才需要直接操作显存本身。