EGE专栏:EGE专栏
上一篇:EGE绘图之一 绘图讲解
下一篇:EGE绘图之三 动画
Windows的窗口有一块帧缓冲,里面保存有窗口要显示的内容图像。当窗口需要显示刷新时,系统就会从这块帧缓冲读取数据,经过一系列操作后,复制到显存中,之后显卡会从显存中读取数据,转成视频信号,驱动显示器进行显示。
每当窗口进行一些比如移动窗口,窗口大小变化之类的操作时,都会有窗口刷新消息产生。因为当窗口在屏幕上占据的区域变化,屏幕显示的内容肯定需要相应发生变化。这时系统就要从各自窗口的帧缓冲区中读取数据,再次显示到屏幕上。
当窗口内容变化时,程序也会主要通知系统要求刷新窗口,显示内容。
所以Windows窗口的帧缓冲,决定了窗口的显示内容。只要画在了上面,Windows窗口一刷新,那么上面的内容就会显示出来。
而在EGE中,也开辟有自己的帧缓冲。EGE的绘制,就是先绘制在这块自己的帧缓存上的,要显示时,再把这块帧缓冲中的内容复制到窗口的帧缓冲中,等到Windows窗口刷新,就会把内容显示出来。
而我常说的EGE刷新窗口,是指先将EGE内部帧缓冲中的内容输出到窗口帧缓冲上,然后使Windows窗口刷新,这样就把刚才在EGE内部帧缓冲上的内容显示出来了。
EGE绘图时仅仅是将图形绘制在EGE内部的帧缓冲上,并没有直接输出到窗口帧缓冲中。要想将绘图的内容显示到窗口上,就需要进行刷新,而 EGE的渲染模式就决定了通过何种方式来控制刷新的时间。
除非是极简单的绘图,否则 不推荐 使用自动渲染模式。
在自动渲染模式下,程序每隔一段时间(50ms)就会自动刷新窗口,并且可以通过调用延时、阻塞类的函数来刷新窗口,属于 定时+手动 的控制刷新方式。
由于程序运行过程中,绘图周期和程序内部定时的周期并不一致且定时会有偏差,所以时不时就会有在绘图过程中定时时间到达的情况。此时就会触发窗口刷新,会将没绘制完成的帧显示出来,和前一帧的画面很可能有很大的反差,让人觉得画面闪烁。
在手动渲染模式下,程序关闭了定时刷新,只有在调用某些延时、阻塞类的函数时,才会刷新窗口,如 delay_ms(0), delay_fps(60) 等,刷新时间完全由用户手动进行控制。
当我们把这些延时类函数的调用控制在绘图完成后,那么刷新时显示的画面就是完整的一帧,减少闪烁感。
一般情况下都要开启手动渲染模式,自动渲染模式只在简单绘图时使用,而且使用它也仅仅因为它是默认模式。
一般是在 initgraph() 调用时设置,比较简洁,在 mode 参数上添加 INIT_RENDERMANUAL 即可
手动渲染模式在初始化时设置一次即可,不要多次设置,否则容易引起动画卡顿。
initgraph(640, 480, INIT_RENDERMANUAL);
也可以在窗口初始化后,调用 setrendermode() 函数设置。
setrendermode(RENDER_MANUAL);
为什么我们需要请求EGE刷新窗口显示内容?
因为我们的绘图并不是实时更新到屏幕上的,只有在EGE将绘图数据传给窗口后,我们才能看到绘图内容。在调试程序时,经常会在代码中间暂停,此时会因为没有刷新窗口看不到刚刚绘制的内容,而我们又需要知道经过之前的绘图操作后,画面会变得如何,此时就需要在前面插入一些代码,请求EGE刷新窗口显示内容了。同时,有时一些代码耗时过长,如加载资源等,导致窗口内容长时间不刷新,会留下空白期,我们可以主动要求EGE刷新窗口来显示内容,避免长时间的画面等待。
在调试程序时,简单地加入一个 getch() 即可暂停,此时会自动刷新窗口,按下任意键即可继续执行。
getch();
而当我们需要程序一直执行,只想在某些代码之间插入一个窗口刷新点时,只需调用 delay_ms(0) 即可。调用后,如果从上一次窗口刷新到执行 delay_ms(0) 这个期间 调用了EGE的绘图函数进行绘图,那么EGE将会刷新窗口,显示内容。(因为将帧缓冲复制到窗口需要时间,所以实际上延时并不是0ms)
delay_ms(0);
让我们看看 ege.h 头文件中的文字
- 调用 Sleep() 这个API时,或者调用 delay(),实际均会转化为调用 delay_ms(),如必需调用API请使用 api_sleep()
- delay_ms() 能自行判断有没有更新的必要,连续多次但不大量的调用并不会产生帧率的影响
- 调用 delay_ms() , delay_fps(), getch() , getkey() , getmouse() 时,窗口内容可能会更新,这些函数相当于内置了delay_ms(0),
- 如果你只需要更新窗口,而不想等待,可以用 delay_ms(0) 。注意 delay() 只延时而不更新窗口
- 合理地使用 delay_ms() /delay_fps() 函数,可以减少你的程序占用的CPU,否则一个都没有调用同时也没有 getch() / getmouse() 的话,程序将占满一个CPU的时间
即调用EGE中的 delay_fps(), delay_jfps(), delay_ms(), getch(), Sleep() 函数,都会刷新一次窗口。
因为 getch() 会阻塞程序进行,所以一般用的,是调用 delay_ms(0);
delay_ms(0);
所以有时你明明绘制了图形,但是窗口就是没有显示,可能是窗口没刷新,可以使用 delay_ms(0) 来 要求EGE刷新窗口。这样就能看到你绘制的图形了。
但是delay_ms(0) 和getch() 有时并不一定会刷新窗口,因为你调用EGE中的绘图函数时,它会用一个标志位标记你有往帧缓存中绘制了图形。当调用 delay_ms(0) 和 getch() 时,会检查标志位,标志位为 true 时才会刷新,刷新后,标志位置为 false 。
比如,我们直接获取EGE帧缓存的首地址,然后往里面写入,这时是没有通过EGE的绘制函数来进行绘制的,那么这个标志位就没有变为 true,此时调用delay_ms(0) 或 getch(), 将看不到绘制。
下面是EGE相关的延时函数。
强制刷新 | 绘制后刷新 |
---|---|
delay_fps() | delay_ms(0) |
delay_jfps() | getch() |
delay_ms() (延时时间 > 0) |
getkey() |
getmouse() |
不带刷新的延时函数 |
---|
delay() |
api_sleep() |
ege_sleep() |
如同下面的例子: 获取EGE帧缓存的首地址,通过直接对帧缓冲的元素赋值的方法来绘制一条直线。
长按按键,可以看到窗口内容没有变化,看不到绘制的直线。
绘图是已经完成了的,只不过图形是绘制到了EGE的帧缓冲中,而EGE并没有将内容上传到窗口。
#include
int main()
{
const int windowWidth = 300, windowHeight = 300;
initgraph(windowWidth , windowHeight , 0);
setbkcolor(WHITE);
delay_ms(0); //先显示背景
// 获取窗口帧缓冲首地址
color_t* buff = getbuffer((PIMAGE)NULL);
for (int x = 0, y = 100; x < windowWidth ; i++) {
//将坐标为(x, y)的像素点赋值为黑色
buff[x + y * windowWidth] = BLACK;
getch();
}
closegraph();
return 0;
}
那这种情况应该如何处理?
强制刷新
在不通过EGE绘图函数进行绘图的情况下,我们需要进行强制刷新。
那么我们可以getch()在前面添加一个delay_ms(1), 只要有延时,那必定是强制刷新。因为 getch() 本就是暂停,多延时一点没有区别。
delay_fps() 也是有强制刷新的功能,所以如果代码本身帧循环结构没有问题,那么通过帧缓冲首地址绘图并不会对界面的显示有影响。
修改后的程序
长按按键,查看画线操作。
可以看到,即使不通过EGE绘图函数进行绘图,也能通过强制刷新来显示绘图内容。
#include
int main()
{
const int windowWidth = 300, windowHeight = 300;
initgraph(windowWidth , windowHeight , 0);
setbkcolor(WHITE);
delay_ms(0); //先显示背景
// 获取窗口帧缓冲首地址
color_t* buff = getbuffer((PIMAGE)NULL);
for (int x = 0, y = 100; x < windowWidth ; x++) {
//将坐标为(x, y)的像素点赋值为黑色
buff[x + y * windowWidth] = BLACK;
delay_ms(1); //强制刷新
getch();
}
closegraph();
return 0;
}
delay_fps() 属于强制刷新。而 delay_ms(0) 则是根据标志位来决定是否刷新窗口,这个标志位会在调用EGE绘图函数时置为true。
在我们的帧循环中,一般是调用 delay_fps() 来延时,所以这会强制刷新窗口。
当使用多线程时,如使用多线程单独对鼠标消息和按键消息进行处理,数据处理等,但绘图最好还是只在主线程,因为绘图是需要顺序的,绘制有先后,当顺序错乱时,很可能会出现图像被覆盖,图像显示出错的结果。在多线程中,难以控制各个线程绘制图形的顺序。
在其它子线程中,如果调用了带有刷新功能的 delay_fps() 之类的函数,将会使窗口在主线程绘制帧的过程中刷新,显示出未完成的一帧,很容易引起画面闪烁,所以在子线程中,延时需要调用不会引起窗口刷新的延时函数。
在子线程中调用延时函数时,应该使用不带刷新窗口功能的函数,如 api_sleep(),这样不会对主线程的绘制造成影响。
EGE专栏:EGE专栏
上一篇:EGE绘图之一 绘图讲解
下一篇:EGE绘图之三 动画