之前OpenGL做的白板在书写时有不跟手,图像延迟等的问题,在参考了其他公司的竞品的书写现象后发现,书写得快的竞品有几款都有覆盖系统UI的表现,这不是OpenGL能做到的事,更有点像以前上汇编课程(可惜啊,汇编几乎忘光了)直接修改系统显存显示内容的感觉。于是在使用OpenGL做了一个书写Demo后发现还是比不上他们指哪打哪的感觉之后(还是有点延迟感,即使FPS一直都是60帧),于是我也决定使用FrameBuffer做一个库来使用。
但是安卓对这个设备节点的保护程度比Linux要高很多,于是只能拜托系统定制的同事解放一下——具体可以参考https://www.cnblogs.com/yangykaifa/p/7398833.html。具体怎么解放其实我不知道的,但好歹现在能顺利open FrameBuffer结点了。我只需要打开FrameBuffer之后写好一套填充像素的工具库即可。
打开FrameBuffer并映射到一段内存的Demo代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "rgbablock.h"
#define FIXED_WIDTH (1920)
#define FIXED_HEIGHT (1080)
#include
#define PAGESIZE 4096
uint32_t *fb0;
static size_t round_up_to_page_size(size_t x){
return (x + (PAGESIZE-1)) & ~(PAGESIZE-1);
}
int main(int argc, char **argv)
{
char *path = "/dev/graphics/fb";
static int8_t *fb_addr;
uint32_t fb_size = FIXED_WIDTH * FIXED_HEIGHT * 4;
uint32_t x, y;
uint32_t tmp, r, b;
uint32_t offset = 0;
uint32_t length = FIXED_WIDTH * FIXED_HEIGHT;
int fd = fbdev_open("/dev/graphics/fb", O_RDWR);
struct fb_fix_screeninfo finfo;
if (fbdev_ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) {
return -1;
}
size_t fbSize = round_up_to_page_size(finfo.smem_len);
fb_addr = (int8_t*)fbdev_mmap(0, fbSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
fb0 = (uint32_t*)fb_addr; //第一个屏
printf("fb0 = %d\n", fb0);
for (y = 0; y < FIXED_HEIGHT; y++) {
offset = (y * FIXED_WIDTH);
for (x = 0; x < FIXED_WIDTH; x++) {
//DBG("offset=%d\n", toffset);
fb0[toffset]= 0xFF0000FF; //ABGR(三屏同步对应像素点同一种像素颜色)
offset++;
}
}
return 0;
}
makeFile如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
draw_test.cpp
LOCAL_SHARED_LIBRARIES += libfbdev
MY_ANDROID_SOURCE=${ANDROID_BUILD_TOP}
LOCAL_C_INCLUDES += $(MY_ANDROID_SOURCE)/hardware/mstar/libfbdev2 \
$(TARGET_TVAPI_LIBS_DIR)/include
LOCAL_SHARED_LIBRARIES += \
libutils \
libcutils \
libjnigraphics \
libgui \
libui\
libandroid
LOCAL_MODULE:= draw_test
LOCAL_MODULE_TAGS := options
include $(BUILD_EXECUTABLE)
那这段代码作用是什么呢?这就涉及一些计算机组成原理方面的知识了。
一、屏幕显像的简化抽象原理:
首先,我们在屏幕看到的所有二维画面,实际上都是不断刷新一个一维内存区实现的,原理请看以下示意图(以一个3x3像素的屏幕为例子):
假如屏幕每秒显示60个画面,也就是其频率f=60hz,则根据f=1/t,周期t=16.67ms,即每个像素的填充时间是16.67ms / 9 = 1.85ms,那么它的实际意义是指什么呢?其实就是指每固定1.85ms的时间,就从显存区FrameBuffer中读取一个当前序号的像素颜色值并输出,然后当前序号+1,当当前序号大于8时归0,同时屏幕硬件也以同样的速度和周期做到周期对齐,拿到值之一个个像素那样写进去,写满一行又换行,因此一维的显存数据就通过这种轮询的方式被二维化到屏幕上让人可以看见。
二、二维坐标对一维下标的转换
那么,如何把二维的像素数据,从二维坐标x,y转化为一维内存空间的对应下标保存起来呢?其实很简单:
设目标下标变量为offset,首先,我们要确定FrameBuffer所代表的屏幕的宽高是多少,例如在这个例子中宽width,高height均为3,那么左上角则定为(0,0),右下角则定位(2,2)。然后先定一个坐标轴作为例子(1,2),该坐标中二维展开至一维的图示如下:
从图上的逻辑关系易得,坐标(1,2)位于第3行,也就是要使一维坐标先跳过y * width个下标, 然后再加上x坐标进行当前位置的偏移,即二维坐标对应的一维下标公式为:offset = f(x,y) = y * width(屏幕宽度)+ x,例如f(1,2) = 2 * 3 + 1 = 7,即FrameBuffer第7下标的数据对应的就是屏幕坐标(x=1,y=2)的像素。
因此我的Demo中的这段代码(FIXED_HEIGHT为1080,FIXED_WIDTH为1920):
for (y = 0; y < FIXED_HEIGHT; y++) {
offset = (y * FIXED_WIDTH);
for (x = 0; x < FIXED_WIDTH; x++) {
//DBG("offset=%d\n", toffset);
fb0[toffset]= 0xFF0000FF; //ABGR(三屏同步对应像素点同一种像素颜色)
offset++;
}
}
意思是,外层循环用于遍历出FrameBuffer对应屏幕第y行(<1080行)的第一个像素的位置(每一行是0~1919,每一列是0~1079),并记录于offset,然后内层循环在1920范围内不断给offset加1作为偏移,意味着把整个屏幕对应的FrameBuffer位置都遍历一次,并都赋予数据0xFF0000FF,那么这个0xFF0000FF的意思是什么呢?
三、如何表达像素的颜色:
计算机的一切都是用0和1来进行表达的,因此本质上计算机不会有连续量,只会有离散量。那么计算机会如何表达颜色呢?
以红色为例,自然界的红色的深度细分为无限种,但即使分级粗糙一些,可能也足够人眼观察图像。因此在大部分系统上,红色的分级都是256级,也就是在计算机中,红色有256种。然后已知2的8次方=256,因此256种变化需要8个二进制才可以完全表达,从而容易得到,三原色的剩余两种——蓝色、绿色,另外还有透明度都可以用同样的方式用8个二进制表达,合起来就是4个字节,刚好就是一个int类型变量占的字节数。
但颜色的定义顺序不同的硬件不同的系统都有可能不同,例如我开发时的目标机器的FrameBuffer在4个字节的编排上便是透明度、蓝色、绿色、红色,然后0~255在16进制上表示为0x00~0xFF,因此0xFF0000FF代表的就是透明度为满(不透明),并且红色也为满,也就是整个屏幕都填满红色的意思。其他颜色可以分别通过修改这4个字节或对int进行与或非等运算进行
四、总结
使用FrameBuffer可以局部更新显存的图像使得可以用于需要高速图像修改的场合,但要记住计算机的存储器本质都是一维的,所以需要你把多维数据转化为一维,懂得这点就可以读写任意二维坐标对应的FrameBuffer像素点了。