トップページ > 記事閲覧
ScreenFlip は垂直同期信号を待っていない
名前:MKII 日時: 2019/11/12 16:08

以下のテストプログラムを組んでます: ーーーーーーーーーー using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using DxLibDLL; namespace DXLib_FlipScreen_Test { class Program { static void Main(string[] args) { DX.ChangeWindowMode(DX.TRUE); DX.SetAlwaysRunFlag(DX.TRUE); DX.DxLib_Init(); DX.SetDrawScreen(DX.DX_SCREEN_BACK); Console.WriteLine("Press [Enter] to start testing."); Console.ReadLine(); DX.WaitVSync(1); uint frame_counter = 0; long time_beginning = Stopwatch.GetTimestamp(); while (DX.ProcessMessage() == 0) { frame_counter++; long time_flip_begin = Stopwatch.GetTimestamp(); DX.ScreenFlip(); long time_flip_end = Stopwatch.GetTimestamp(); long time_flip_took = time_flip_end - time_flip_begin; Console.WriteLine($"Frame #{frame_counter} / Flip took: {1000.0 * time_flip_took / Stopwatch.Frequency} ms"); if (frame_counter == 6) { long time_total = time_flip_end - time_beginning; Console.WriteLine($"Time total: {1000.0 * time_total / Stopwatch.Frequency} ms"); break; } } DX.DxLib_End(); Console.ReadLine(); } } } ーーーーーーーーーー もし ScreenFlip は垂直同期信号を待ったら、毎回の時間は 16.66ms(60Hz 想定),6 フレームなら 100 ms のはず。 ですが実際動かしてみた結果: ーーーーーーーーーー Press [Enter] to start testing. Frame #1 / Flip took: 0.965918180693107 ms Frame #2 / Flip took: 10.2013033858451 ms Frame #3 / Flip took: 16.7127993214425 ms Frame #4 / Flip took: 15.4082060786439 ms Frame #5 / Flip took: 16.3312616400687 ms Frame #6 / Flip took: 16.4882233444313 ms Time total: 84.3005092199909 ms ーーーーーーーーーー 最初の1フレームは全然待ってない、そのまま2フレーム目の処理に入ってます。 Windows Vista 以降、DWM 有効の場合えは、DwmFlush を使って待たせることが出来る: ーーーーーーーーーー using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using DxLibDLL; namespace DXLib_FlipScreen_Test { class Program { #region DWM API [DllImport("dwmapi")] public static extern uint DwmFlush(); #endregion static void Main(string[] args) { DX.ChangeWindowMode(DX.TRUE); DX.SetAlwaysRunFlag(DX.TRUE); DX.DxLib_Init(); DX.SetDrawScreen(DX.DX_SCREEN_BACK); Console.WriteLine("Press [Enter] to start testing."); Console.ReadLine(); DX.WaitVSync(1); uint frame_counter = 0; long time_beginning = Stopwatch.GetTimestamp(); while (DX.ProcessMessage() == 0) { frame_counter++; long time_flip_begin = Stopwatch.GetTimestamp(); DX.ScreenFlip(); long time_flip_end = Stopwatch.GetTimestamp(); long time_flip_took = time_flip_end - time_flip_begin; long time_flush_begin = Stopwatch.GetTimestamp(); DwmFlush(); long time_flush_end = Stopwatch.GetTimestamp(); long time_flush_took = time_flush_end - time_flush_begin; Console.WriteLine($"Frame #{frame_counter} / Flip took: {1000.0 * time_flip_took / Stopwatch.Frequency:0.00} ms / DWM flush took: {1000.0 * time_flush_took / Stopwatch.Frequency:0.00} ms"); if (frame_counter == 6) { //long time_total = time_flip_end - time_beginning; long time_total = time_flush_end - time_beginning; Console.WriteLine($"Time total: {1000.0 * time_total / Stopwatch.Frequency} ms"); break; } } DX.DxLib_End(); Console.ReadLine(); } } } ーーーーーーーーーー 実際に動かしてみた: Press [Enter] to start testing. Frame #1 / Flip took: 0.99 ms / DWM flush took: 8.40 ms Frame #2 / Flip took: 0.18 ms / DWM flush took: 15.61 ms Frame #3 / Flip took: 0.48 ms / DWM flush took: 15.47 ms Frame #4 / Flip took: 0.26 ms / DWM flush took: 16.20 ms Frame #5 / Flip took: 0.24 ms / DWM flush took: 16.22 ms Frame #6 / Flip took: 0.22 ms / DWM flush took: 16.35 ms Time total: 100.815295314391 ms ーーーーーーーーーー つまり、ScreenFlip はすでに Flip したフレームがある場合のみ垂直同期信号を待ってます。 (DwmFlush 使うと、ScreenFlip 待つことが無い) ですが DwmFlush 使えない(フルスクリーンや、ユーザー設定で切った)場合の修正法が分からない。 単純に最初のフレームを捨てるのは何が違う気がする。
メンテ

Page: 1 |

Re: ScreenFlip は垂直同期信号を待っていない ( No.1 )
名前:管理人 日時:2019/11/12 23:45

はい、ScreenFlip は実際には Direct3D の API の Present を呼んでいるだけで、 垂直同期信号を待つかどうかは Present 任せとなっています ScreenFlip を呼んだ際に必ず垂直同期信号を待つようにされたい場合は DxLib_Init を呼ぶ前に SetWaitVSyncFlag( FALSE ); を実行したうえで ScreenFlip の後に WaitVSync( 1 ); を実行するようにしてください m(_ _)m > ですが DwmFlush 使えない(フルスクリーンや、ユーザー設定で切った)場合の修正法が分からない。 SetWaitVSyncFlag( FALSE ); を実行していない状態で ScreenFlip を実行した直後に WaitVSync( 1 ); を実行した場合も DwmFlush(); と同様の効果があるようです
メンテ
Re: ScreenFlip は垂直同期信号を待っていない ( No.2 )
名前:MKII 日時:2019/11/13 15:38

SetWaitVSyncFlag(FALSE); にすると、DWM が無い環境で画面がティアリングする。 WaitVSync(1); を直前・直後に実行して、どちでも微妙にズレる。 でも DwmFlush(); の代わりに WaitVSync(1) を使うのは盲点だった。 自分のコードにも使っているにも関わらず、思考が Win32 API から放ってない…反省。 いろいろ試した結果:(VSyncFlag = TRUE の場合 ScreenFlip() 掛かる時間 ) DX_DIRECT3D_9 Window: ~16 ms / Window (DWM OFF): ~16 ms / Fullscreen: ~16 ms DX_DIRECT3D_9EX Window: ~16 ms / Window (DWM OFF): ~16 ms / Fullscreen: ~16 ms DX_DIRECT3D_11 Window: < 1 ms / Window (DWM OFF): ~16 ms / Fullscreen: < 1 ms DX_DIRECT3D_NONE を設定しても効果が無い( GetUseDirect3DVersion() は 1 を返す) つまり DX_DIRECT3D_11 使う場合、DWM OFF のウインドウモード以外自力で待つ必要がある( WaitVSync(1); で十分 ) これは自分の PC (Windows 7 x64, Intel HD4000)だけの現象なのか分からない…テストプログラムをここで貼ります: ーーーーーーーーーー using System; using System.Diagnostics; using DxLibDLL; namespace DXLib_FlipScreen_Test { class NOWIN32API { private volatile static bool FullScreen = false; static void Main(string[] args) { DX.SetUseDirect3DVersion(DX.DX_DIRECT3D_11); DX.GetDefaultState( out int desktop_width, out int desktop_height, out int desktop_color, out int desktop_refresh, out int desktop_top_x, out int desktop_top_y, out int desktop_pixel_w, out int desktop_pixel_h); int game_width; int game_height; if (FullScreen) { game_width = desktop_width; game_height = desktop_height; } else { game_width = 320; game_height = 240; DX.ChangeWindowMode(DX.TRUE); } DX.SetGraphMode(game_width, game_height, desktop_color); DX.SetAlwaysRunFlag(DX.TRUE); DX.DxLib_Init(); Console.WriteLine($"DX Version: {(DXVER)DX.GetUseDirect3DVersion()}"); DX.DrawString(10, 10, "Press [Enter] to start testing.", DX.GetColor(255, 255, 255)); DX.DrawString(10, 30, "Press [Esc] during test to stop.", DX.GetColor(255, 255, 255)); DX.WaitKey(); DX.SetDrawScreen(DX.DX_SCREEN_BACK); uint frame_counter = 0; DX.WaitVSync(1); long time_beginning = Stopwatch.GetTimestamp(); while (DX.ProcessMessage() == 0) { frame_counter++; // DRAW SOMETHING DX.ClearDrawScreen(); int line_width = 32; int speed = 3; int x = (int)(frame_counter) % ((game_width + line_width) / speed); DX.DrawBox(x * speed - line_width, 0, x * speed, game_height, DX.GetColor(255, 255, 255), DX.TRUE); // END DRAW SOMETHING long time_flip_begin = Stopwatch.GetTimestamp(); DX.ScreenFlip(); long time_flip_end = Stopwatch.GetTimestamp(); long time_flip_took = time_flip_end - time_flip_begin; long time_vsync_begin = Stopwatch.GetTimestamp(); DX.WaitVSync(1); long time_vsync_end = Stopwatch.GetTimestamp(); long time_vsync_took = time_vsync_end - time_vsync_begin; Console.WriteLine($"Frame #{frame_counter:00} / Flip: {1000.0 * time_flip_took / Stopwatch.Frequency:00.00} ms / Wait: {1000.0 * time_vsync_took / Stopwatch.Frequency:00.00} ms"); if (DX.CheckHitKey(DX.KEY_INPUT_ESCAPE) == DX.TRUE || frame_counter == 600) { long time_total = time_vsync_end - time_beginning; Console.WriteLine($"Time total: {1000.0 * time_total / Stopwatch.Frequency} ms"); break; } } DX.DxLib_End(); Console.ReadLine(); } private enum DXVER : int { DX_DIRECT3D_9 = DX.DX_DIRECT3D_9, DX_DIRECT3D_9EX = DX.DX_DIRECT3D_9EX, DX_DIRECT3D_11 = DX.DX_DIRECT3D_11, } } } ーーーーーーーーーー
メンテ
Re: ScreenFlip は垂直同期信号を待っていない ( No.3 )
名前:管理人 日時:2019/11/14 01:04

手元の環境は Windows10 なのですが、同様の結果になりました DX_DIRECT3D_9 Window: ~16 ms / Fullscreen: ~16 ms DX_DIRECT3D_9EX Window: ~16 ms / Fullscreen: ~16 ms DX_DIRECT3D_11 Window: < 1 ms / Fullscreen: < 1 ms ( Windows10 は DWM を OFF にできないので DWM OFF の結果はありません ) 今後の WindowsUpdate で仕様が変更される可能性はありますが、現状では 『Direct3D 11 の場合は ScreenFlip( Direct3D の Present ) 1回目は VBLANK を待たない』となっているようです
メンテ
Re: ScreenFlip は垂直同期信号を待っていない ( No.4 )
名前:MKII (解決) 日時:2019/11/18 19:25

こちらのスレッドで仕様統一されたので、一応解決。  dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4747
メンテ

Page: 1 |

題名
名前
コメント
パスワード (記事メンテ時に使用)

   クッキー保存