图形模式刷新与防闪烁
- 来源: https://www.freebasic.net/wiki/wikka.php?wakka=ProPgAntiFlickering
- 最后更新: 2024-02-18
图形模式刷新(清屏或视口后)的防闪烁编码方法。
前言:
在图形模式下刷新(不加预防措施地重绘)窗口可能会导致烦人的闪烁(这是由于在用户刷新阶段显示了不需要的中间图像所致)。
如果在每次完全更新之前先清除整个窗口,并且反复执行,这种现象显然会加剧。
本页介绍对抗这种现象的基本 FreeBASIC 编码技术。
防闪烁的主要原则
如果只考虑图形用户任务(不考虑操作系统所需的 CPU 资源),有两种主要方法可以避免闪烁:
- 首选方法,使用 '[Screenlock...Screenunlock]' 块来封装用于刷新的图形指令。
但文档中对其使用有如下警告:
强烈建议页面上的锁定保持时间尽可能短。在屏幕锁定期间只应进行屏幕绘图,必须避免输入/输出和等待操作。在 Win32 和 Linux 中,锁定屏幕会通过停止同时处理操作系统事件的线程来实现。如果屏幕长时间保持锁定,事件队列可能溢出,使系统不稳定。
- 其次,如果锁定时间过长,使用双视频分页原则。
'Screensync' 指令是旧 QuickBASIC 的遗留物,在那里只有这种类型的指令('wait &h3DA, 8')来改善闪烁。
它是经验性的,因为它只允许同步与两帧之间固定死时间相关的绘图。
适用于绘图时间非常短的场合偶尔使用。
通常不建议将这 2(或 3)种方法混合使用。
然后,在显示循环中(如果存在),用户必须为操作系统提供足够的 CPU 资源(通过在循环末尾使用 'Sleep' 指令平滑控制)。否则,它将失去用户控制,导致显示卡顿。
注意:使用 'Screensync' 为操作系统提供 CPU 资源(图形绘制结束和帧跟踪结束之间的死时间),但以不可控制的方式(因为与帧周期相关联)。
通过小示例学习防闪烁方法
一个小程序,用于说明(并比较不同效率)以下 4 种不同方法(方法 2 到 5,在"无方法"的方法 1 之后),通过带清屏循环对图形屏幕进行绘图/打印动画:
方法 1:使用原始编码(无方法)进行绘图/打印循环到屏幕
算法:
' SCREEN 19, , 2 'to enable double-paging
' SCREENSET 0, 0 'to cancel double-paging
'
' ┌─► CLS 'to clear the page
' │ Drawing 'to draw on the page
' │ Printing 'to print on the page
' └── Temporizing 'to avoid hogging the CPU方法 2:使用同步进行绘图/打印循环到屏幕
算法:
' SCREEN 19, , 2 'to enable double-paging
' SCREENSET 0, 0 'to cancel double-paging
'
' ┌─► SCREENSYNC 'to synchronize between two frames
' │ CLS 'to clear the page
' │ Drawing 'to draw on the page
' │ Printing 'to print on the page
' └── Temporizing 'to avoid hogging the CPU方法 3:使用锁定进行绘图/打印循环到屏幕
算法:
' SCREEN 19, , 2 'to enable double-paging
' SCREENSET 0, 0 'to cancel double-paging
'
' ┌─► SCREENLOCK 'to lock the page's frame buffer
' │ CLS 'to clear the page
' │ Drawing 'to draw on the page
' │ Printing 'to print on the page
' │ SCREENUNLOCK 'to unlock the page's frame buffer
' └── Temporizing 'to avoid hogging the CPU方法 4:使用双缓冲进行绘图/打印循环到屏幕
算法:
' SCREEN 19, , 2 'to enable double-paging
' SCREENSET 1, 0 'to activate double-paging
'
' ┌─► CLS 'to clear the work page
' │ Drawing 'to draw on the work page
' │ Printing 'to print on the work page
' │ SCREENCOPY 'to copy the work page into the visible page
' └── Temporizing 'to avoid hogging the CPU注意:
如果工作页面在每次迭代时都完全刷新(如此处),双缓冲和翻页(见下文)在功能上是等效的(但内部实现不同)。
方法 5:使用翻页进行绘图/打印循环到屏幕
算法:
' SCREEN 19, , 2 'to enable double-paging
' SCREENSET 1, 0 'to activate double-paging
' p0=0 : p1=1 'to initialize flipping
'
' ┌─► CLS 'to clear the work page
' │ Drawing 'to draw on the work page
' │ Printing 'to print on the work page
' │ SCREENSET p0, p1 'to set the work page to the p0 value, and the visible page to the p1 value
' │ SWAP p0, p1 'to exchange the values of p0 and p1
' └── Temporizing 'to avoid hogging the CPU注意:
如果工作页面在每次迭代时都完全刷新(如此处),翻页和双缓冲(见上文)在功能上是等效的(但内部实现不同)。
完整程序清单
代码:
start GeSHi
Declare Sub Draw_circle_recursion (ByVal x As Integer, ByVal y As Integer, ByVal r As Integer, ByVal rmin As Integer)
Dim I As Integer = 0
Dim Inc As Integer
Dim Key As String
Dim Code As Integer = 1
Dim Tempo As Integer = 3
Dim T As Single = Tempo
Dim p0 As Integer = 0
Dim p1 As Integer = 1
Screen 19, , 2
Do
If Code = 4 Or Code = 5 Then
ScreenSet 1, 0
Else
ScreenSet 0, 0
End If
Do
Select Case Code
Case 2
ScreenSync
Case 3
ScreenLock
End Select
Cls
Draw_circle_recursion(10 + I, 300, 9 + I * I / 29 / 29, 10)
Locate 1, 1
Select Case Code
Case 1
Print "1. Draw/Print loop to screen with raw coding:"
Print
Print " SCREEN 19, , 2 'to enable double-paging"
Print " SCREENSET 0, 0 'to cancel double-paging"
Print
Print " " & Chr(218) & Chr(196) & Chr(16) & " CLS"
Print " " & Chr(179) & " " & " " & " Drawing"
Print " " & Chr(179) & " " & " " & " Printing"
Print " " & Chr(192) & Chr(196) & Chr(196) & " Temporizing"; T; " ms";
Case 2
Print "2. Draw/Print loop to screen with synchronizing:"
Print
Print " SCREEN 19, , 2 'to enable double-paging"
Print " SCREENSET 0, 0 'to cancel double-paging"
Print
Print " " & Chr(218) & Chr(196) & Chr(16) & " SCREENSYNC"
Print " " & Chr(179) & " " & " " & " CLS"
Print " " & Chr(179) & " " & " " & " Drawing"
Print " " & Chr(179) & " " & " " & " Printing"
Print " " & Chr(192) & Chr(196) & Chr(196) & " Temporizing"; T; " ms";
Case 3
Print "3. Draw/Print loop to screen with locking:"
Print
Print " SCREEN 19, , 2 'to enable double-paging"
Print " SCREENSET 0, 0 'to cancel double-paging"
Print
Print " " & Chr(218) & Chr(196) & Chr(16) & " SCREENLOCK"
Print " " & Chr(179) & " " & " " & " CLS"
Print " " & Chr(179) & " " & " " & " Drawing"
Print " " & Chr(179) & " " & " " & " Printing"
Print " " & Chr(179) & " " & " " & " SCREENUNLOCK"
Print " " & Chr(192) & Chr(196) & Chr(196) & " Temporizing"; T; " ms";
Case 4
Print "4. Draw/Print loop to screen with double buffering:"
Print
Print " SCREEN 19, , 2 'to enable double-paging"
Print " SCREENSET 1, 0 'to activate double-paging"
Print
Print " " & Chr(218) & Chr(196) & Chr(16) & " CLS"
Print " " & Chr(179) & " " & " " & " Drawing"
Print " " & Chr(179) & " " & " " & " Printing"
Print " " & Chr(179) & " " & " " & " SCREENCOPY"
Print " " & Chr(192) & Chr(196) & Chr(196) & " Temporizing"; T; " ms";
Case 5
Print "5. Draw/Print loop to screen with page flipping:"
Print
Print " SCREEN 19, , 2 'to enable double-paging"
Print " SCREENSET 1, 0 'to activate double-paging"
Print " p0=0 : p1=1 'to initialize flipping"
Print
Print " " & Chr(218) & Chr(196) & Chr(16) & " CLS"
Print " " & Chr(179) & " " & " " & " Drawing"
Print " " & Chr(179) & " " & " " & " Printing"
Print " " & Chr(179) & " " & " " & " SCREENSET p0, p1"
Print " " & Chr(179) & " " & " " & " SWAP p0, p1"
Print " " & Chr(192) & Chr(196) & Chr(196) & " Temporizing"; T; " ms";
End Select
Locate 30, 1
Print "`<1>`: Draw/Print with raw coding"
Print "`<2>`: Draw/Print with synchronizing"
Print "`<3>`: Draw/Print with locking"
Print "`<4>`: Draw/Print with double buffering"
Print "`<5>`: Draw/Print with page flipping"
Print "`<+/->`: Tempo setting (+/-)"
Print
Print "`<Escape>` or click [X]: Quit";
Select Case Code
Case 3
ScreenUnlock
Case 4
ScreenCopy
Case 5
ScreenSet p0, p1
Swap p0, p1
End Select
If I = 0 Then
Inc = +1
ElseIf I = 480 Then
Inc = -1
End If
I = I + Inc
Key = Inkey
If Key = "+" And Tempo < 10 Then
Tempo = Tempo + 1
ElseIf Key = "-" And Tempo > 0 Then
Tempo = Tempo - 1
End If
If Tempo > 0 Then
T = Tempo
Else
T = 0.5
End If
Static As Integer K
K += 1
If K >= 25 / T Then
Sleep 25
K = 0
End If
Loop While Key <> "1" And Key <> "2" And Key <> "3" And Key <> "4" And Key <> "5" And Key <> Chr(27) And Key <> Chr(255) & "k"
Code = Val(Key)
Loop Until Key = Chr(27) Or Key = Chr(255) & "k"
Sub Draw_circle_recursion ( ByVal x As Integer, ByVal y As Integer, ByVal r As Integer, ByVal rmin As Integer )
Circle (x, y), r, r Shr 1
If r > rmin Then
Draw_circle_recursion(x + r Shr 1, y, r Shr 1, rmin)
Draw_circle_recursion(x - r Shr 1, y, r Shr 1, rmin)
Draw_circle_recursion(x, y + r Shr 1, r Shr 1, rmin)
Draw_circle_recursion(x, y - r Shr 1, r Shr 1, rmin)
Draw_circle_recursion(x + r Shr 1, y + r Shr 1, r Shr 2, rmin)
Draw_circle_recursion(x - r Shr 1, y + r Shr 1, r Shr 2, rmin)
Draw_circle_recursion(x + r Shr 1, y - r Shr 1, r Shr 2, rmin)
Draw_circle_recursion(x - r Shr 1, y - r Shr 1, r Shr 2, rmin)
End If
End Subend GeSHi
关于循环中定时的说明(为了与任何 PC 兼容):
实际应用的定时值始终为 25 ms('Sleep 25'),但仅在 N 次循环中执行一次(其中 'N = 25 / tempo','tempo' 是以 ms 为单位的所需值,范围在 0.5 到 10 之间)。
参见
ScreensyncScreenlock,ScreenunlockScreenset,Screencopy- 集成到用户循环中用于 FPS 控制的简单调节函数
- 用于等待和循环内精细调节 FPS 的细粒度过程
返回 目录