(讨论)关于LCD仿真的速度的改进
不成熟的意见 希望和大家讨论在skyeye_lcd_win32.c里面,一共有三个存储块涉及到LCD framebuffer
1.LCD dma
2.fHDC
3.fscreenDC
LCD仿真程序每个200个tick从LCD dma同步到Win32程序的客户区
一共涉及到两次拷贝
第一次: SkyEyeLCD_UpdateFromSkyEye函数中从dma拷贝到fHDC(内存)
第二次: SkyEyeLCD_DoUpdate函数中从fHDC拷贝到屏幕DC
改进的地方有两个:
1. 可以省去第一步拷贝.提供一个新函数dma直接用新申请的fHDC内存,把这两边内存合二为一.可以减少一次拷贝.
2. 采用"中断:的形式查询LCD更新. GDI的bitblt速度很慢,所以没有必要尽量不要调用.每200tick调用一次还是很频繁的.skyeye内部可以提供一个标志位什么的用于指示LCD framebuffer已经被更新过了. 这样LCD前端在检查到标志位更新之后才做更新.
我现在只看了win32的实现,其他的还没看.我想应该有遗漏的细节,希望大家踊跃拍砖.
回复 1# lurker0 的帖子
交待一下情况。 :)1. 第一步中把虚拟的 LCD 缓冲区变换确实可以直接写到屏幕 DC 去,
但假如用 API32 写点的话,其效率比现双缓冲的速度还慢。
要实现你说的合二为一,可能要考虑采用 DX 了。
2. 有考虑过你说的情况(在 xxx_filter_xxx 函数可看出痕迹)... :)
可比较可惜的是当大区域更新时,那种方法比较耗时,
也许需要结合 LCD 各模块的模拟作调整。
LCD 虚拟应该还有很大的改善空间,欢迎加入到团队一起合作。:) 感谢Anthony的回复
你说的的确是是个问题. 我正在考虑怎么才能优化LCD的模拟.
如果考虑到跨平台,opengl可能是个更好的选择.不过这不是一个大问题.
想另外请教一下一处代码问题
在函数callback_expose_event(skyeye_lcd_gtk.c)中case 32:
gdk_draw_rgb_32_image(widget->window,
widget->style->fg_gc,
0, rect.y, lcd->width, rect.height,
GDK_RGB_DITHER_NORMAL,
(guchar*)((u32*)lcd->rgbbuf + rect.y * lcd->virtual_width),
lcd->virtual_width * 4);
红色的这一行是不是应该是 (guchar*)((u32*)lcd->rgbbuf + rect.y * lcd->virtual_width * 4),
回复 4# lurker0 的帖子
lcd->virtual_width 是虚拟屏(部分模拟采用缓冲)的每行像素点数,RGBA模式下每个点缓冲区描述是一个 32bits,
那么目标行起始缓冲数据是 (u32*)lcd->rgbbuf + rect.y * lcd->virtual_width 或 (guchar*)lcd->rgbbuf + rect.y * lcd->virtual_width * 4,
用(guchar*)(XXX)只是强制转换指针类型以便编译的时候不出现警告信息而已。 我现在在考虑是否可以把LCD模拟和LCD实现从源代码上分离.
当前考虑的解决方案是把LCD DMA的数据直接写到文件中.这样的话,skyeye这边可以只向文件中写像素,而LCD的显示部分就从skyeye中独立出来.以后如果要实现GUI 接口,类似VirtualBox或者vmware的集成界面,会容易很多.因为不再需要skyeye源代码的支持. 另外skyeye得到的好处是不再需要GTK开发环境的支持,更容易移植. 我相信posix文件接口在几乎所有OS上都有实现.
举个例子:
我们有一个新的后端叫做skyeye_lcd_file类似skyeye_lcd_gtk
在skyeye_lcd.c中直接使用open read write(无缓存文件操作函数), 或者直接mmap文件到
虚拟空间. 一个我能想到的好处是如果要截屏之类的功能,就用gimp或者photoshop之类的软件直接打开这个文件就可以了(raw格式).
另外在LCD实现端,可以用实现GTK/GDI/QUERTZ之类的API,实现也很简单
用mmap或者CreateFileMapping映射文件,直接拷贝到屏幕就可以(skyeye当前的实现就是这样子) ; 如果考虑到硬件加速实现,可以实现opengl, 使用glTexGen/glBindTexture 系列绑定为纹理,然后显示在屏幕.
另外需要一个文件也用mmap的方式映射到skyeye中,其中的内容是DMA格式信息.
typedef struct FBHDR{
ULONG header_size; //in unit of bytes
ULONG version;
ULONG width;
ULONG length;
ULONG depth;
ULONG format;
ULONG IsUpdate;
ULONG rectangle_x;
ULONG rectangle_y;
ULONG rectangle_width;
ULONG rectangle_length;
}Framebuffer_header;
这个东西有什么好处,在LCD显示部分,可以轮训IsUpdate成员检查FrameBuffer是否更新,而不用每隔多少时间强制跟新一次.因为我们模拟arm指令实际上很慢, 实际处理LCD IO的时间并不是很多.通过轮训的方法可以很大程度上减少CPU时间.
回复 6# lurker0 的帖子
因为目前 SkyEye 的指令模拟采用轮询而非类似 syscall 方式,所以对于单个 CPU 的机器来说,运行 SkyEye 的时候几乎占据了大片 CPU 资源,
外部程序响应速度将非常缓慢。
或许能够在 SkyEye 端设置一个标准,
比如 FrameBuffer 更新量达到一定程度或者最后一次更新间隔一定时间后
强制把 CPU 片交给外部程序。 CPU占有率确实是个很大的问题.我在server上运行skyeye,其他人基本上没法干活了.
我现在在代码中稍微改了一下:
在io_do_cycle中进行计数
每10000次IO释放就usleep(10);
CPU占有率大约为50%,勉强可以接受.
我以前试过每100次做一次释放.结果就是慢的要死.
模拟平台上的linux像是死掉了一样, 害得我以为哪儿改坏了,debug了很长时间.
最后发现是太慢了.:-(
sleep太多,skyeye会很慢.想要快,占有率就会很高.
模拟类的软件一般都有这个问题, 比如模拟Cisco路由器的Dynamips 最早的时候就有CPU 100%问题, 后来采用idlepc机制才稍稍好一点.qemu的策略好像是在模拟 hlt /idle 指令的时候sleep一段时间.后来他们发现window 95模拟的时候还是100%,因为95 没采用hlt 指令.
========
void
io_do_cycle (void * state)
{
struct device_desc *dev;
int i;
#if 0
#ifdef DBCT_TEST_SPEED
state->instr_count++;
#endif//DBCT_TEST_SPEED
#endif
prescale--;
static cnt = 0;
cnt ++;
if (cnt % 10000 == 0)
{
usleep(10);
}
if (prescale < 0) {
prescale = DIVISOR;
for (i = 0; i < skyeye_config.mach->dev_count; i++) {
dev = skyeye_config.mach->devices;
if (dev->update)
dev->update (dev);
}
skyeye_config.mach->mach_io_do_cycle (state);
}
}
我来通报一下进展吧
在原始版本上做了一下性能测试,在s3c2410的测试包上, 大约平均2.5ms会有一次更新LCD操作.
usec:1941 cnt:1 update:0x1
usec:2036 cnt:1 update:0x1
usec:3044 cnt:1 update:0x1
usec:2968 cnt:1 update:0x1
usec:2928 cnt:1 update:0x1
usec:1961 cnt:1 update:0x1
usec:2064 cnt:1 update:0x1
usec:1909 cnt:1 update:0x1
usec:2065 cnt:1 update:0x1
usec:5042 cnt:1 update:0x1
usec:2892 cnt:1 update:0x1
usec:1982 cnt:1 update:0x1
usec:1978 cnt:1 update:0x1
usec:2050 cnt:1 update:0x1
usec:1968 cnt:1 update:0x1
usec:1938 cnt:1 update:0x1
usec:1983 cnt:1 update:0x1
usec:2021 cnt:1 update:0x1
usec:1996 cnt:1 update:0x1
usec:1950 cnt:1 update:0x1
usec:1994 cnt:1 update:0x1
usec:2032 cnt:1 update:0x1
usec:1991 cnt:1 update:0x1
usec:3120 cnt:1 update:0x1
usec:5082 cnt:1 update:0x1
usec:2716 cnt:1 update:0x1
usec:1987 cnt:1 update:0x1
usec:2144 cnt:1 update:0x1
sum:250635 aver:2481 usec 1. 对于硬件上不支持idle或者OS没有用 idle指令的系统,SkyEye如何判断什么时候应该sleep,这个有待研究。如果我们用OS Aware的方式去降低skyeye的cpu使用率, 我们可以用判断在客户端上的linux OS的idle函数是否执行,如果执行,我们就可以通知SkyEye去sleep。
2. 对于LCD刷新的时机,我这样考虑,每一个硬件应该都有一个divisor的值,最小为1,来控制轮询每个硬件的次数。
假设LCD设为10,那就每执行 10条指令轮询一次lcd是否要更新的状态。
uart可以设为50,每执行50条指令来轮询一次lcd是否要更新的状态。
根据外设的速度我们来设置不同的divisor。 to KSH:
1.用OS Aware是一个能看得见的好方法.不过带来的副作用是模拟的OS和最后的上板的image不一致的.这取决于用户是怎么用skyeye,如果只是用于开发应用系统,则基本上没有影响. 如果是用于调试驱动,学习硬件的目的,可能会带来模拟行为上的不一致. 基于这样的考虑,所以我才在我的本地代码里面采用在io处理函数中sleep的土方法.
2. 这个想法很有创意.这样慢速的设备就不用在每个IO周期都要检查一下模拟硬件的状态.节省不少时间.而且本来各个硬件就是有时钟频率的差异.这样做更符合实际.
再通报一下进展吧
按照我原来的想法, 用mmap来映射一个文件到虚地址空间当LCD用. 通过这种方法来实现前后端分离.如果实现,以后LCD后端可以不用skyeye的源代码就可以编译.可以用GTK WIN32 SDL任何你想用的GUI技术. skyeye也可以专注于模拟,而不是GUI.现在的进展是已经把file接口完成了,因为不会写autoconfigure,所以我把Makefile中skyeye_lcd_gtk的地方都替换为skyeye_lcd_file来实现编译. 另外写了一个小的测试程序来测试我的新接口. 现在遇到的问题是通过测试程序,发现LCD更新的很频繁.原来的skyeye程序中设定200ms更新一次,现在是平均2.5ms. 原因就是ksh上面所说的io_cycle中每个io周期都有的硬件update, 而每次update,LCD模拟部分都会从模拟sdram中拷贝像素到目标像素区.
这样达不到我最初减少LCD模拟负荷的设想. 所以我现在正在犹豫要不要继续做下去.
修改后的接口除非是回到定时更新的老路,然后利用opengl绑定为纹理的方法来实现硬件加速.不然不会有速度的提升.原来我以为skyeye中有专门的LCD写像素模拟操作.后来经过分析,发现s3c2410是通过在sdram中指定显存的方法来实现绘图,也就是说绘图就写内存.除非是把绘图和其他写内存操作区分开,不然没法做效能上的优化. 1、恩,有关开发的一些具体小问题,你可以直接发邮件给我(blackfin.kang at gmail.com),这样的话,你的一些改动我将来帮你合并到svn的仓库中。
2、我们现在代码中已经有了一些divisor的设定,你可以尝试在lcd模型中加入divisor,让每个外设刷新的频率不一致。
3、目前绘图就是内存拷贝,把客户操作系统想显示的数据拷贝到模拟的显存的位置,这个优化我还没有什么更好的想法。如果能多做一些优化,我们的LCD模拟显示很多图形界面就会流畅起来。 to KSH,
你说的divisor的设定是不是在最新版才有的.
我用的是skyeye-1.2.7_rc1,好像没找到对应的地方. 哦 不好意思
找到地方了
页:
[1]
2