EGE绘图之二 窗口刷新

EGE专栏:EGE专栏
上一篇:EGE绘图之一 绘图讲解
下一篇:EGE绘图之三 动画

EGE绘图之二 窗口刷新

目录

  • 一、EGE的窗口刷新
    • 1. EGE窗口刷新流程
    • 2. 渲染模式
      • 2.1 自动渲染模式(不推荐)
      • 2.2 手动渲染模式
      • 2.3 手动渲染的开启
    • 3. 如何主动请求EGE刷新窗口显示内容
      • 3.1 调用delay_ms(0)
      • 3.2 EGE刷新相关 delay族函数
        • 3.2.1 具有刷新窗口功能的延时函数
        • 3.2.2 不具有刷新窗口功能的延时函数
        • 3.2.3 强制刷新的使用
    • 4. 多线程中的延时
      • 4.1 子线程中的延时:使用不刷新窗口的 api_sleep()

一、EGE的窗口刷新

1. EGE窗口刷新流程

EGE绘图之二 窗口刷新_第1张图片
  Windows的窗口有一块帧缓冲,里面保存有窗口要显示的内容图像。当窗口需要显示刷新时,系统就会从这块帧缓冲读取数据,经过一系列操作后,复制到显存中,之后显卡会从显存中读取数据,转成视频信号,驱动显示器进行显示。

  每当窗口进行一些比如移动窗口,窗口大小变化之类的操作时,都会有窗口刷新消息产生。因为当窗口在屏幕上占据的区域变化,屏幕显示的内容肯定需要相应发生变化。这时系统就要从各自窗口的帧缓冲区中读取数据,再次显示到屏幕上。
  当窗口内容变化时,程序也会主要通知系统要求刷新窗口,显示内容。

  所以Windows窗口的帧缓冲,决定了窗口的显示内容。只要画在了上面,Windows窗口一刷新,那么上面的内容就会显示出来。

  而在EGE中,也开辟有自己的帧缓冲。EGE的绘制,就是先绘制在这块自己的帧缓存上的,要显示时,再把这块帧缓冲中的内容复制到窗口的帧缓冲中,等到Windows窗口刷新,就会把内容显示出来。

  而我常说的EGE刷新窗口,是指先将EGE内部帧缓冲中的内容输出到窗口帧缓冲上,然后使Windows窗口刷新,这样就把刚才在EGE内部帧缓冲上的内容显示出来了。


2. 渲染模式

  EGE绘图时仅仅是将图形绘制在EGE内部的帧缓冲上,并没有直接输出到窗口帧缓冲中。要想将绘图的内容显示到窗口上,就需要进行刷新,而 EGE的渲染模式就决定了通过何种方式来控制刷新的时间。

2.1 自动渲染模式(不推荐)

  除非是极简单的绘图,否则 不推荐 使用自动渲染模式。

  在自动渲染模式下,程序每隔一段时间(50ms)就会自动刷新窗口,并且可以通过调用延时、阻塞类的函数来刷新窗口,属于 定时+手动 的控制刷新方式。
  由于程序运行过程中,绘图周期和程序内部定时的周期并不一致且定时会有偏差,所以时不时就会有在绘图过程中定时时间到达的情况。此时就会触发窗口刷新,会将没绘制完成的帧显示出来,和前一帧的画面很可能有很大的反差,让人觉得画面闪烁。

2.2 手动渲染模式

  在手动渲染模式下,程序关闭了定时刷新,只有在调用某些延时、阻塞类的函数时,才会刷新窗口,如 delay_ms(0), delay_fps(60) 等,刷新时间完全由用户手动进行控制。
  当我们把这些延时类函数的调用控制在绘图完成后,那么刷新时显示的画面就是完整的一帧,减少闪烁感。

  一般情况下都要开启手动渲染模式,自动渲染模式只在简单绘图时使用,而且使用它也仅仅因为它是默认模式。

2.3 手动渲染的开启

  一般是在 initgraph() 调用时设置,比较简洁,在 mode 参数上添加 INIT_RENDERMANUAL 即可

  手动渲染模式在初始化时设置一次即可,不要多次设置,否则容易引起动画卡顿。

initgraph(640, 480, INIT_RENDERMANUAL);

  也可以在窗口初始化后,调用 setrendermode() 函数设置。

setrendermode(RENDER_MANUAL);

3. 如何主动请求EGE刷新窗口显示内容

  为什么我们需要请求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() 函数,都会刷新一次窗口。

3.1 调用delay_ms(0)

  因为 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相关的延时函数。

3.2 EGE刷新相关 delay族函数

3.2.1 具有刷新窗口功能的延时函数

强制刷新 绘制后刷新
delay_fps() delay_ms(0)
delay_jfps() getch()
delay_ms() (延时时间 > 0) getkey()
getmouse()

3.2.2 不具有刷新窗口功能的延时函数

不带刷新的延时函数
delay()
api_sleep()
ege_sleep()

3.2.3 强制刷新的使用

  如同下面的例子: 获取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;
}

EGE绘图之二 窗口刷新_第2张图片

  delay_fps() 属于强制刷新。而 delay_ms(0) 则是根据标志位来决定是否刷新窗口,这个标志位会在调用EGE绘图函数时置为true
  在我们的帧循环中,一般是调用 delay_fps() 来延时,所以这会强制刷新窗口。

4. 多线程中的延时

  当使用多线程时,如使用多线程单独对鼠标消息和按键消息进行处理,数据处理等,但绘图最好还是只在主线程,因为绘图是需要顺序的,绘制有先后,当顺序错乱时,很可能会出现图像被覆盖,图像显示出错的结果。在多线程中,难以控制各个线程绘制图形的顺序。

  在其它子线程中,如果调用了带有刷新功能的 delay_fps() 之类的函数,将会使窗口在主线程绘制帧的过程中刷新,显示出未完成的一帧,很容易引起画面闪烁,所以在子线程中,延时需要调用不会引起窗口刷新的延时函数。

4.1 子线程中的延时:使用不刷新窗口的 api_sleep()

  在子线程中调用延时函数时,应该使用不带刷新窗口功能的函数,如 api_sleep(),这样不会对主线程的绘制造成影响。


EGE专栏:EGE专栏
上一篇:EGE绘图之一 绘图讲解
下一篇:EGE绘图之三 动画

你可能感兴趣的:(EGE,EGE)