Re: 前回VBLANKの時間を取得したい ( No.1 ) |
- 名前:管理人 日時:2019/11/10 22:59
|
Re: 前回VBLANKの時間を取得したい ( No.2 ) |
- 名前:MKII 日時:2019/11/11 09:23
自分の環境ではこのような感じです:
D3DKMTWaitForVerticalBlankEvent() と DXLib の関数同時に使う(他に比べると重い)
puu.sh/ED5nt/beb42e54a8.gif
D3DKMTWaitForVerticalBlankEvent() を外す( Thread.Sleep(16) に換えて)
puu.sh/ED5nu/4c8d613c69.gif
DXLib の関数を外す
puu.sh/ED5nv/d3c09fd7f1.gif
ーーーーーーーーーー
最新版でも同じ現象です。これは自分のパソコンの問題でしょうか?
プログラムも少々更新しました:
Win32API.cs
pastebin.com/aM4cRyce
VBlankTracker.cs
pastebin.com/y0uVSEgM
Main.cs
pastebin.com/k17JGCXK
|
Re: 前回VBLANKの時間を取得したい ( No.3 ) |
- 名前:MKII 日時:2019/11/11 22:47
D3DKMTGetScanLine() を使っていろいろ試した結果も重いので、一旦自力で時間を計ることが諦めます…
DWM API DwmGetCompositionTimingInfo() から qpcVBlank 取得すると、かなり安定します(D3DKMTGetScanLine()使うより安定)
当然 DWM が動いてないなら使えない、その場合妥協策として前回の ScreenFlip 後の時間を使いたいが、自力で C# 側で計ると誤差が大きい。
DxLibMake のソースコードにはこのような部分がある:
// 環境依存処理
Result = Graphics_ScreenFlipBase_PF() ;
// ScreenFlip が呼ばれた時間を保存
GSYS.PerformanceInfo.ScreenFlipTime[ 1 ] = GSYS.PerformanceInfo.ScreenFlipTime[ 0 ] ;
GSYS.PerformanceInfo.ScreenFlipTime[ 0 ] = NS_GetNowHiPerformanceCount() ;
そこの GSYS.PerformanceInfo.ScreenFlipTime[ 0 ] を取得したい、自力で上手く C# 版ビルドできないので時間があれば関数の追加をお願いします。
|
Re: 前回VBLANKの時間を取得したい ( No.4 ) |
- 名前:管理人 日時:2019/11/12 01:14
うーん駄目でしたか…謎ですね…
DXライブラリには WaitVSync という関数があるので、よろしければ DxLib_Init の前で
SetWaitVSyncFlag( FALSE );
を実行して ScreenFlip の際に VBLANK 待ちをしないようにして、別途 WaitVSync( 1 ); を
実行して VBLANK 待ちをするようにしてみてください
もしかしたら WaitVSync であれば重くないかもしれませんので…
あと、それとは別に ScreenFlipTime を取得する関数を追加しましたので、よろしければダウンロードしてください m(_ _)m
https://dxlib.xsrv.jp/temp/DxLibDotNet.zip // Windows版 .NET用
以下の関数を追加しました
// ScreenFlip の処理が完了した直後の GetNowHiPerformanceCount の値を取得する
LONGLONG GetScreenFlipTime( void ) ;
よろしければお試しください
|
Re: 前回VBLANKの時間を取得したい ( No.5 ) |
- 名前:MKII 日時:2019/11/13 12:27
関数追加ありがとうございます。試しに使いましたか、返した数字は何なのかちょっと分からないです...
自分は C# の Stopwatch.GetTimestamp() を使っている、DWM API DwmGetCompositionTimingInfo() で取得
した qpcVBlank と同じく QueryPerformanceCounter()からの値と思います。
GetScreenFlipTime()
249400231598
ScreenFlip() 直後で Stopwatch.GetTimestamp()
413120260653
計算したら約 27 時間以上離れていますね…調べてみたら:DxSystemWin.cpp
ーーーーーーーーーー
WinAPIData.Win32Func.QueryPerformanceCounterFunc( ( LARGE_INTEGER * )&NowTime ) ;
// // 精度設定を上げる
// FPUStatus = _control87( 0x00000000, 0x00030000 ) ;
MulNum = 1000000;
_MUL128_1( (DWORD *)&MulNum, (DWORD *)&NowTime, (DWORD *)Temp );
_DIV128_1( (DWORD *)Temp, (DWORD *)&WinData.PerformanceClock, (DWORD *)&Result );
ーーーーーーーーーー
なぜ DXLib の内部は直接にカンタから返した値を使わずに、毎回わざわざ秒数単位に変換したでしょう?
逆に秒数単位と計算したい時だけに、秒数を &WinData.PerformanceClock と乗算してカンタの単位に変換するべきと思う。
|
Re: 前回VBLANKの時間を取得したい ( No.6 ) |
- 名前:管理人 日時:2019/11/14 01:06
> なぜ DXLib の内部は直接にカンタから返した値を使わずに、毎回わざわざ秒数単位に変換したでしょう?
分かりやすくするためです
『GetNowHiPerformanceCount の戻り値は高精度タイマーの値で、秒やミリ秒などに変換する場合は高精度タイマーの周波数で除算してください』
と言われても分からない方も居るかもしれないと思い、最初からマイクロ秒単位の値で返すようにしています
あと、マイクロ秒に変換した値を float型ではなく整数型で得たい場合、普通に
『QueryPerformanceCounterで得られる値 * 1000000 / 周波数』をしてしまうと演算の途中で 64bit の範囲を超えてしまうので
DxSystemWin.cpp で使用している _MUL128_1, _DIV128_1 の関数の中で行っているような特殊な演算を行わなければなりません
大抵の場合最終的にミリ秒なりマイクロ秒なりの値に変換するので、それなら面倒な処理を省けるようにライブラリの中で
マイクロ秒に変換してしまおう、と考え現在のような仕様になっています
|
Re: 前回VBLANKの時間を取得したい ( No.7 ) |
- 名前:MKII 日時:2019/11/14 18:19
ご返信ありがとうございます。ですが、この変換についてご再考して欲しい:
1)QueryPerformanceCounter の値(以下、QPC)と周波数は容易に取得できる。
C# なら Stopwatch = QPC,当然直接 Win32API QueryPerformanceCounter() と QueryPerformanceFrequency() 使うもいい。
2)仰るとおり、QPC値をマイクロ秒単位に変換する途中 64bit の範囲を超えるため、特殊な演算を行わなければならない。
ですが、逆にマイクロ秒単位からQPC値に変換するにはただ『マイクロ秒数 * 周波数 / 1000000』だけでいい、特殊な演算など一切いらない。
3)QPCは『最高』精度のタイマーです、わざわざ精度下げる理由は?
MSDNから:... value of the performance counter ... is a high resolution (<1us) time stamp ...
すでに1マイクロ秒以下の精度なので、マイクロ秒単位に変換する=精度を下げる。
4)『大抵の場合最終的にミリ秒なりマイクロ秒なりの値に変換する』ーそうなのか?
唯一にマイクロ秒単位に返すDXライブラリ関数は GetNowHiPerformanceCount() のみ。(ミリ秒単位は別API)
検索したら基本的に内部で使ってますが、どこでもマイクロ秒単位に変換する必要は見つかれない。
高精度の計算は、直接QPC使う方が簡単だと思う:
−−−−−−−−−
例えば 29.97 fps (30000/1001) のムービー、現在のフレームを求むなら:
『30000 * (QPC_経過値 / 周波数) / 1001 』// 秒精度部分
+
『30000 * (QPC_経過値 % 周波数) / 1001 / 周波数』// 1マイクロ秒以下精度部分
すべで整数型で、精度も保つし、計算過程に 64bit の範囲を超える心配もない。
−−−−−−−−−
所々に見られるマイクロ秒単位で待つ処理も:
『EndTime = StartTime + WaitTime - 4000 ;』 // マイクロ秒単位
容易にQPC単位に変換てきる(※特殊な演算はいらない※):
『EndTime = StartTime + WaitTime - ( 4 * 周波数 / 1000 ) ;』
−−−−−−−−−
現在の仕様では毎回時間を取得するときに、強制的に変換され、特殊な演算でCPUを消耗する。
高精度の時間を計りたいなのに、while( NS_GetNowHiPerformanceCount()... のループ毎回毎回この演算で無駄に遅延時間が生じる。
−−−−−−−−−
以上の理由で、この変換を外す、DXLib 内部でQPCを直接に使うことを強くお勧めします。
当然 DXLib 現存の関数 GetNowHiPerformanceCount() 現在使った人のプログラムを壊さないため、
その特殊な演算はここだけに使って、元の仕様を保つ方がいい。
今回追加して貰った GetScreenFlipTime() 関数はQPC値にして欲しい…
|
Re: 前回VBLANKの時間を取得したい ( No.8 ) |
- 名前:管理人 日時:2019/11/16 01:37
ご意見ありがとうございます
概ねご指摘の通りですので、とりあえずDXライブラリにもQPCをそのまま返す関数と周波数を返す関数を追加して、
内部でも GetNowHiPerformanceCount を使用しないようにしようと思います( 少し時間が掛かるので直ぐには
対応が完了しないかもしれませんが… )
GetScreenFlipTime についてですが、こちらは改めてプログラムを確認してみたところ
ScreenFlip();
LONGLONG Time = GetNowHiPerformanceCount();
として取得できる値とほぼ同じ( GSYS.PerformanceInfo.ScreenFlipTime[ 0 ] = NS_GetNowHiPerformanceCount() ; の
後は数行単純演算を処理した後関数から出るだけ )なので、GetScreenFlipTime は削除しても良いかな…と思っているのですが、
削除してしまっても良いでしょうか?
|
Re: 前回VBLANKの時間を取得したい ( No.9 ) |
- 名前:管理人 日時:2019/11/18 00:20
QPCをそのまま返す関数 GetNowSysPerformanceCount などを追加したバージョンをアップしましたので、
よろしければダウンロードしてください m(_ _)m
https://dxlib.xsrv.jp/temp/DxLibDotNet.zip // Windows版 .NET用
以下の関数を追加しました
// OSが提供する高精度カウンタの現在の値を得る
ULONGLONG GetNowSysPerformanceCount( void ) ;
// OSが提供する高精度カウンタの周波数( 1秒辺りのカウント数 )を得る
ULONGLONG GetSysPerformanceFrequency( void ) ;
// OSが提供する高精度カウンタの値を秒の値に変換する
ULONGLONG ConvSysPerformanceCountToSeconds( ULONGLONG Count ) ;
// OSが提供する高精度カウンタの値をミリ秒の値に変換する
ULONGLONG ConvSysPerformanceCountToMilliSeconds( ULONGLONG Count ) ;
// OSが提供する高精度カウンタの値をマイクロ秒の値に変換する
ULONGLONG ConvSysPerformanceCountToMicroSeconds( ULONGLONG Count ) ;
// OSが提供する高精度カウンタの値をナノ秒の値に変換する
ULONGLONG ConvSysPerformanceCountToNanoSeconds( ULONGLONG Count ) ;
// 秒の値をOSが提供する高精度カウンタの値に変換する
ULONGLONG ConvSecondsToSysPerformanceCount( ULONGLONG Seconds ) ;
// ミリ秒の値をOSが提供する高精度カウンタの値に変換する
ULONGLONG ConvMilliSecondsToSysPerformanceCount( ULONGLONG MilliSeconds ) ;
// マイクロ秒の値をOSが提供する高精度カウンタの値に変換する
ULONGLONG ConvMicroSecondsToSysPerformanceCount( ULONGLONG MicroSeconds ) ;
// ナノ秒の値をOSが提供する高精度カウンタの値に変換する
ULONGLONG ConvNanoSecondsToSysPerformanceCount( ULONGLONG NanoSeconds ) ;
( MKIIさんは Stopwatch を使用されているので不要かもしれませんが一応ご紹介… )
内部で使用している GetNowHiPerformanceCount についてはある程度 QPC を直接使用する方式に変更しました
ただ、動画再生処理では時間管理をμ秒で扱いかなり色々処理していて QPC に変更するとバグを
生みそうだったのでそのままにしました…
GetScreenFlipTime についてですが、No.8 で申し上げました通りあまり意味の無い関数でしたので削除しました
あと、Direct3D 9 では ScreenFlip で VBLANK を待っていましたが、プログラムを確認したところ
こちらはDXライブラリ内部で VBLANK を待っていました
ただ、本来の意図は『SetAlwaysRunFlag( TRUE ); を設定した状態でウィンドウが最小化されて
Present が一瞬で終わってしまうとメインループが高速で実行されてしまうので、Present の
処理時間が極めて短かった場合は VBLANK 待ちをしていないと判断してDXライブラリ側で VBLANK を待つ』
という処理で、それが今回『初回の Present は一瞬で終わる』現象によって誤作動していたようです
( Direct3D 11 にはこの処理が無かったので、”SetAlwaysRunFlag( TRUE ); + ウィンドウ最小化”では
高速にメインループが実行されてしまう状態でした )
今回『ウィンドウが最小化されているかどうか』を Present の処理時間で判断するのを止め、フラグで
判断するようにしましたので、今回のバージョンからは Direct3D 9 の場合も Direct3D 11 と同様に
初回の ScreenFlip が VBLANK を待たずに関数から出てくるようになっています
|
Re: 前回VBLANKの時間を取得したい ( No.10 ) |
- 名前:MKII (解決) 日時:2019/11/18 20:20
いつも迅速な対応ありがとうございます。
この数日間、色々調べた結果… 「精確」に時間を取得することは極めて困難です。
docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
QPCの精度も限度があり(1MHz ±50 ppm の誤差なら一日で 4.3 秒の差が出る)、
それを修正するための処理はあまりにも複雑(外部サーバーに同期するしかない)なので素直に諦めます。
ーーーーーー
新しいバージョンを試しました、DX9 の ScreenFlip も VBLANK を待たない仕様になっているのは確認されました。
現在の ScreenFlip では、基本 DX9 / DX9EX / DX11 全て:( Windows 7 x64 )
Window (DWM ON): < 1 ms / Fullscreen: < 1 ms
の仕様に統一されている。
ーーーーーー
GetScreenFlipTime を破棄するのは同意です。
現在の ScreenFlip は基本 VBLANK 待たずに返すので、この時間を取得するにも特に意味が無い。
ーーーーーー
追伸:DWM OFF のウインドウモードだけ、まだ統一されてない:
DX9 / DX9EX
Frame #01 / Flip: 10.30 ms / Wait: 05.22 ms
Frame #02 / Flip: 11.07 ms / Wait: 05.31 ms
Frame #03 / Flip: 11.16 ms / Wait: 05.18 ms
Frame #04 / Flip: 11.27 ms / Wait: 05.22 ms
Frame #05 / Flip: 11.26 ms / Wait: 05.23 ms
...
Frame #155 / Flip: 00.35 ms / Wait: 13.89 ms
Frame #156 / Flip: 00.36 ms / Wait: 15.05 ms
Frame #157 / Flip: 00.30 ms / Wait: 14.21 ms
Frame #158 / Flip: 00.40 ms / Wait: 14.95 ms
Frame #159 / Flip: 00.39 ms / Wait: 14.58 ms
最初は 10 ms ですが(なぜ?)ウインドウを移動するなど一旦停止し、再開すると <1 ms になる。
何回試しても同じ現象(10 ms)、DWM ON の場合なら最初から <1 ms です。
とにかく ScreenFlip は VBLANK を待ってないのは確かです。
DX11
Frame #01 / Flip: 15.34 ms / Wait: 16.46 ms
Frame #02 / Flip: 16.55 ms / Wait: 16.49 ms
Frame #03 / Flip: 16.47 ms / Wait: 16.54 ms
Frame #04 / Flip: 16.68 ms / Wait: 16.48 ms
Frame #05 / Flip: 16.64 ms / Wait: 16.54 ms
Frame #06 / Flip: 16.67 ms / Wait: 16.52 ms
Frame #07 / Flip: 16.70 ms / Wait: 16.41 ms
Frame #08 / Flip: 16.71 ms / Wait: 16.47 ms
Frame #09 / Flip: 16.74 ms / Wait: 16.46 ms
Frame #10 / Flip: 16.63 ms / Wait: 16.67 ms
ScreenFlip,WaitVSync(1) の時間ともに 16 ms に安定しています(合計2フレーム分待ってます)
つまり ScreenFlip は VBLANK を待ってます。
テストプログラムは前回のスレッドと同じ:
dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4748
|
Re: 前回VBLANKの時間を取得したい ( No.11 ) |
- 名前:管理人(解決) 日時:2019/11/19 00:45
DWM OFF では異なる結果になるのですね
Windows は昔から VBLANK 関係の融通が全くきかなくてモヤモヤしますね…
( VBLANK 関係で悩まされる度に「MS-DOS の頃はハードウェアVBLANK割り込みが使えて良かったなぁ」と今でも思います… )
|