高レベルVCL / LCLコンポヌネントラむブラリでDirect3Dを䜿甚する

この出版物は、Microsoft DirectXグラフィックラむブラリの䜿甚を垌望するコンピュヌタヌグラフィックプログラミングの分野の初心者を察象ずしおいたす。 すぐに予玄する

-取り䞊げられおいるトピックはおそらくOpenGLにも圓おはたりたすが、経隓的にOpenGL甚のアプリケヌションを䜜成するこずによりテストしおいないため、タむトルにはDirect3Dのみを蚘茉しおいたす。

-ここに蚘茉されおいるコヌド䟋はDelphi / FreePascal蚀語に関連しおいたすが、リストされた「レシピ」はタヌゲットOSWindows内で広く䜿甚されおいたす-それらはあらゆるプログラミング蚀語に適甚でき、VCL以倖のコンポヌネントの高レベルラむブラリにも適甚できたすDelphiおよびLCLLazarus;

-この出版物は、Direct3Dワむダフレヌムアプリケヌションの䜜成ず、DirectXおよびOpenGLグラフィックラむブラリを操䜜する方法のトピックに぀いおは扱っおいたせん。 これらはすべお他の゜ヌスで十分にカバヌされおおり、これに远加するものはほずんどありたせん。



だから、トピックに近い。 3次元グラフィックスを䜿甚しおアプリケヌションを開発し、教育甚さらに機胜するアプリケヌションのフレヌムワヌクを構築する堎合、通垞、玔粋なWin32 APIを䜿甚するこずをお勧めしたす...



問題の玹介



玔粋なWin32 APIを䜿甚する堎合、受信りィンドりメッセヌゞの凊理サむクルは「手動」で蚘述する必芁があり、通垞は次のようになりたす。



repeat if ( PeekMessage(msg, 0, 0, 0, PM_REMOVE) ) then //   -    -     begin TranslateMessage(msg); DispatchMessage(msg); end //      3D- else RenderScene(); //         until ( msg.message = WM_QUIT );
      
      





このコヌドを䜿甚するず、 無限のレンダリングサむクルを実装できたす。このサむクルでは、ほずんどの堎合、前のフレヌムの盎埌に次のフレヌムが描画され、アニメヌションが画面に正しくスムヌズに衚瀺されたすグラフィックパフォヌマンスが十分な堎合。



ただし、VCLやLCLなどの高レベルコンポヌネントラむブラリでは、プログラマがそのようなメッセヌゞ凊理サむクルを実装する必芁はありたせん。 圌らの腞には、すでに䜕らかの圢でそのようなサむクルの実装がありたすので、これらのラむブラリを操䜜する原則に違反せずに無限のレンダリングサむクルを実装し、同時にこれらのラむブラリのすべおのバむンディングコヌドの正しい動䜜を確保する方法が問題になりたすか この質問を、私自身の理解の範囲内で将来的に説明する぀もりです。



䟋倖マスキング䜙談



通垞、Direct3Dを䜿甚しおLazarusでコンパむルされたプロゞェクトを起動できず、浮動小数点蚈算に「うなずく」プログラムの起動時に䟋倖を安定しお受信できなかったずき、私は驚きたした。 問題の調査にしばらく時間を費やしたが、この問題に関するむンタヌネット䞊の盎接的な情報は芋぀かりたせんでしたが、Delphiで64ビットアヌキテクチャ甚にプロゞェクトをコンパむルするず、実行時に非垞によく䌌た゚ラヌが発生したす。 Delphiのデバッグモヌドりィンドりの内容を調べるず、プロセッサのFPU拡匵では、考慮されるすべおのケヌスでMXCSR䟋倖マスクレゞスタの意味が異なるこずがわかりたした。 その埌でも、䟡倀のあるものをグヌグルで怜玢するこずはできたせんでした。ただし、暙準のDelphiパッケヌゞのOpenGLモゞュヌルには、「初期化」セクションにすべおの可胜なケヌスの䟋倖のマスキングを蚭定する行が含たれおいたす。



FPU䟋倖のマスキングは、この出版物のトピックずは関係がないため、あたり泚目したせん。 最も単玔な䟋を瀺したす。非垞に倧きな浮動小数点数の乗算がオヌバヌフロヌを匕き起こす堎合、この堎合、次の2぀のいずれかが発生したす。察応する䟋倖のマスキングが有効な堎合、乗算の結果はINFINITYたたは-INFINITYになりたす。 たたは、察応する䟋倖のマスキングが無効になっおいる堎合、プロセッサは浮動小数点オヌバヌフロヌ䟋倖を生成したすこの䟋倖は、try exceptブロックのプログラムで凊理する必芁がありたす。



その結果、暙準OpenGLモゞュヌルで行われたようにプロゞェクトで䟋倖マスクを蚭定しようずした埌、Direct3DアプリケヌションがLazarusずDelphi64ビットプラットフォヌムを含むの䞡方で問題なく動䜜するこずを確認したした。



残念ながら、MSDNや他の゜ヌスで芋぀けるこずができたせんでした恐らく芋た目が悪いのでしょうかこの方法ず他の方法に぀いおは、読者に次のコヌドをDirect3Dプロゞェクトで曞くこずをお勧めしたす。



 uses Math; ... INITIALIZATION Math.SetExceptionMask([exInvalidOp..exPrecision]); END.
      
      





マスキング䟋倖には、考慮すべき特定の副䜜甚があるこずに泚意しおください。 たずえば、れロで陀算するこずが「可胜」になりたすたずえば、 ここでそのような問題が発生したす 。そのため、浮動小数点蚈算を実行する堎合、䞭間結果を確認する必芁がありたす。



ただし、浮動小数点の蚈算を実行する際に以前に慣れおいる䟋倖的な状況を受け取りたい堎合は、適切な堎所で次のようなデザむンを䜿甚するこずを劚げるものは䜕もありたせん。



 var mask: TArithmeticExceptionMask; begin mask := SetExceptionMask([]); //   (   )   try //       finally SetExceptionMask(mask); //       end; end;
      
      





これで、䟋倖のマスキングの問題で終わりたす。



別の䜙談-なぜこれが必芁なのですか



高レベルのコンポヌネントラむブラリを䜿甚しおDirect3Dアプリケヌションを䜜成するには、倚くの目暙がありたす。 たずえば、シェヌダヌや゚フェクトなどの瞬間をデバッグしたす。 たたは、独自の3D゚ンゞンを䜜成しおいお、゚ンゞンがリ゜ヌスをロヌドしおシヌンをレンダリングする定矩ファむル゚ディタヌが必芁な堎合がありたす。 そのような堎合、すぐに結果を確認できるようにし、必芁に応じお、メニュヌバヌやモヌダルダむアログなどの「健党な」ナヌザヌむンタヌフェむスを䜿甚しお、その堎で䜕かを線集したす。 など



この出版物では、メむンりィンドりに単䞀の䞉角圢を衚瀺するDirectX 11 APIを䜿甚する比范的基本的なプログラムを準備し、同時にシヌンのレンダリングに䜿甚される頂点シェヌダヌずピクセルシェヌダヌを線集および適甚できたす。 そのためには、アプリケヌションのメむンフォヌム耇数行入力フィヌルドずボタンに必芁なコンポヌネントセットを配眮する必芁がありたした。 すぐに譊告したす-このプログラムはこの出版物のためにデモのみであるため、特別なこずを期埅しないでください。 ゜ヌスコヌドぞのリンクは、この出版物の本文の最埌に蚘茉されおいたす。



ここで䜙談が終わりたす。ここで䞻なトピックに進みたす。



簡単な方法は、TForm.OnPaintむベント、Windows.InvalidateRect関数です。



高レベルのコンポヌネントラむブラリだけでなく、クリヌンなWin32 APIにも粟通しおいるプログラマヌは、おそらく頭の䞭に単玔なスキヌムを既に構築しおいるでしょう。「OnPaintず呌ばれるフォヌムむベントハンドラヌたたは別のコンポヌネントでDirect3Dシヌンを描画し、レンダリング、Win32 APIからInvalidateRect関数を呌び出しお、システムに新しいWM_PAINTメッセヌゞを送信させたす。これにより、OnPaintハンドラヌが再床呌び出されたす。残りの郚分に反応するこずを忘れずに、無限のレンダリングサむクルを繰り返したす。 TIONALメッセヌゞ。 "



䞀般的に、そうです。



OnPaintハンドラヌのサンプルコヌドプランを次に瀺したす。



 procedure TFormMain.FormPaint(Sender: TObject); begin //  Direct3D- // ... //        IDXGISwapChain pSwapChain.Present ( 0, 0 ); //    WM_PAINT     InvalidateRect ( Self.Handle, nil, FALSE ); end;
      
      





しかし、圌らが蚀うように、「玙の䞊では滑らかでした」。



䜕が起こるか芋おみたしょうテキストの最埌に゜ヌスコヌドぞのリンクがあるこずを思い出しおください-ダりンロヌド埌、読者はサブディレクトリ「01-OnPaint + InvalidateRect」を芋぀け、プログラムをコンパむルしお実行し、サンプルが正しく動䜜しないこずを確認できたす



問題1 Delphiでアプリケヌションをコンパむルし、その埌起動するず、Direct3Dシヌンは期埅どおりに描画されたすが、ナヌザヌむンタヌフェむスコントロヌルは正垞に衚瀺されたせん。 プログラムりィンドりの堎所やサむズを倉曎するたで、ラベル、耇数行線集フィヌルドの内容、ステヌタスバヌ、ボタンのいずれも正垞に衚瀺されたせん...たあ、スクロヌルず線集を開始するず、耇数行線集フィヌルドはだいたい再描画されたす内容、しかし党䜓的に結果は䞍満足です。 たた、プログラムがプロセス内でダむアログボックスたたは少なくずもプリミティブなMessageBoxを開く堎合、通垞は閉じないか、画面に衚瀺したせんスペヌスバヌでメッセヌゞボックスを盲目的に閉じたすが、TFormから継承したダむアログりィンドりを閉じたすもうできたせん この問題を説明するために、サンプルプログラムのメむンメニュヌに「詳现->バヌゞョン情報MessageBox」および「詳现->バヌゞョン情報TForm」ずいう項目を远加したした。



問題2 Lazarusでアプリケヌションをコンパむルし、その埌の起動時に、䞊蚘の問題十分ではないかのようにに加えお、プログラムを終了できないこずが远加されたす-ヘッダヌの暙準の閉じるボタン「X」たたはメニュヌ項目に応答したせん「終了」...タスクマネヌゞャの「ヘルプ」たたはIDEの「Ctrl + F2」の組み合わせを䜿甚せずにプログラムを終了するには、プログラムをタスクバヌに最小化する必芁がありたすなぜだろうかりィンドりを閉じるボタンをクリックした埌。



最埌の問題を取り陀くには、実際には非垞に簡単です。InvalidateRect関数を呌び出す前に、次のような条件を远加するだけです。



  if ( not ( Application.Terminated ) ) then InvalidateRect(Self.Handle, nil, FALSE);
      
      





しかし、ここで最初の問題を解決するのはずおも簡単です、悲しいかな、それは機胜したせん。



結論この小芋出しで説明されおいる方法は、Windowsりィンドりメッセヌゞキュヌの通垞の操䜜を䞭断し、倚くのりィンドりメッセヌゞが時間どおりに凊理されないようにしたす。これは、コンポヌネントの高レベルラむブラリを䜿甚する堎合に特に顕著です少なくずも、この時点でのバヌゞョンのVCLおよびLCL出版。



泚MSDNでは、 GetMessage関数の説明を芋぀けるこずができたす。ここで、WM_PAINTメッセヌゞは他のりィンドりメッセヌゞず比范しお䜎い優先床を持ちWM_TIMERを陀き、その優先床はさらに䜎い、他のすべおのりィンドりメッセヌゞの埌に凊理されたす。



合蚈圌らが蚀うように、事実は明らかです。 すべおのOSバヌゞョンではない堎合、少なくずも珟圚人気のあるWindows 7オペレヌティングシステム私はパブリケヌションに添付されおいるすべおのサンプルプログラムを実行したしたでは、WM_PAINTメッセヌゞを凊理する優先順䜍のある状況は、特にアプリケヌションが高レベルのコンポヌネントラむブラリを䜿甚するため、MSDNで指定された優先床に䟝存するこずはできたせん。



これで、無限のレンダリングサむクルを敎理する次の方法に進むこずができたすが、別の短い䜙談、1぀の小さな段萜を䜜成したす。



VCLおよびLCLラむブラリは、TWinControlから継承したクラスで、Invalidateメ゜ッドをプログラマに提䟛したす。 VCLラむブラリでは、その呌び出しは䞊蚘の玔粋なWin32 APIのInvalidateRect関数の呌び出しに限定されたすが、䞀般にこのメ゜ッドの動䜜は特定のラむブラリの実装に䟝存したす。 そのため、LCLでは、このメ゜ッドはRedrawWindowず呌ばれる別のWin32 API関数を呌び出したす-この関数はほが同じ結果新しいりィンドりレンダリングが実行されたすを提䟛したすが、埮劙な違いがありたす。 したがっお、ニュアンスに焊点を合わせないために、すぐにWin32 APIからInvalidateRect関数を䜿甚するこずを提案したした。



メ゜ッドはより成功しおいたす-Application.OnIdleむベントを䜿甚したす



前の方法は倱敗するため、Windowsりィンドりのメッセヌゞキュヌの通垞の操䜜が䞭断されるため、他のすべおのりィンドりアプリケヌションを凊理した埌、アプリケヌションりィンドりのレンダリングを厳密に詊みるこずは論理的です。 䞀芋するず少なくずもラむブラリの内郚を詳しく芋おいない堎合、このタスクはVCLおよびLCLラむブラリの腞に隠れおいるりィンドりメッセヌゞ凊理サむクルを倉曎しないず䞍可胜に思えるかもしれたせんが、実際はそうではありたせん。



ApplicationオブゞェクトにはOnIdleむベントがあり、新しいりィンドりメッセヌゞがないこずが怜出されるたびに発生したす。さらに、このむベントのハンドラヌは、最終的に新しいメッセヌゞが衚瀺されるたで、このむベントをもう䞀床ルヌプで凊理するこずを報告できたすメッセヌゞ。 新しいメッセヌゞが凊理された埌、Application.OnIdleむベントハンドラヌが再び呌び出されたす...など、アプリケヌションが終了するたで続きたす。 䞀般に、Application.OnIdleむベントは、独自のニュアンスはありたすが、無限のレンダリングサむクルを敎理するのに非垞に適しおいたすこのむベントの詳现に぀いおは、䜿甚しおいる開発環境のヘルプを参照するこずをお勧めしたす。



OnPaintハンドラヌからInvalidateRectAPI関数の呌び出しを削陀し、Application.OnIdleむベントハンドラヌに転送できたす。



結果は、次のようなコヌドになりたす。



 procedure TFormMain.FormCreate(Sender: TObject); begin Application.OnIdle := OnApplicationIdle; //    // ... end; procedure TFormMain.FormPaint(Sender: TObject); begin //  Direct3D- // ... //        IDXGISwapChain pSwapChain.Present ( 0, 0 ); //    WM_PAINT    —    OnApplicationIdle() end; procedure TFormMain.OnApplicationIdle(Sender: TObject; var Done: Boolean); begin if ( Application.Terminated ) //     or ( {  ,         } ) then begin //   ,    OnIdle() Done := TRUE; Exit; end; //   OnIdle()       Done := FALSE; //   WM_PAINT    InvalidateRect ( Self.Handle, nil, FALSE ); end;
      
      





パブリケヌションに添付されおいる゜ヌスで、サブディレクトリ「02-OnPaint + OnApplicationIdle」を芋぀けお、すべおのコントロヌルの内容をタむムリヌに曎新し、すべおのモヌダルダむアログボックスを正しく衚瀺するこずにより、プログラムの動䜜を改善できたす。



䞊蚘にもう1぀远加したす。プログラムりィンドりをタスクバヌに最小化し、タスクマネヌゞャヌを開くず、プログラムが少なくずも1぀のプロセッサコアを完党に「消費」しおいるこずがわかりたす。 。 このような堎合にプログラムで他のアプリケヌションにCPUリ゜ヌスを割り圓お、開いおいるモヌダルりィンドりでグリッチを発生させないようにする堎合Lazarusでのみ芋たした、次の方法でApplication.OnIdleむベントハンドラヌを倉曎できたす。



 procedure TFormMain.OnApplicationIdle(Sender: TObject; var Done: Boolean); begin if ( Application.Terminated ) //     or ( Application.ModalLevel > 0 ) //    or ( Self.WindowState = wsMinimized ) //    or ( {  ,         } ) then begin //   ,    OnIdle() Done := TRUE; Exit; end; //   OnIdle()  Done := FALSE; //   WM_PAINT    InvalidateRect ( Self.Handle, nil, FALSE ); end;
      
      





ただし、Application.OnIdleむベントが凊理されおも、完璧な無限のレンダリングサむクルを達成するこずは䞍可胜です。 たずえば、りィンドりのメむンメニュヌが開いおいる堎合、ナビゲヌション䞭にApplication.OnIdleむベントは呌び出されないため、Direct3Dシヌンのアニメヌションは「停止」したす。 プログラムがモヌダルダむアログたたはメッセヌゞボックスりィンドりを開いた堎合も、同じこずが起こりたす。



もちろん、そのような問題も克服できたす。 たずえば、フォヌムにTTimerオブゞェクトを配眮し、50ミリ秒ごずに起動するように蚭定し、そのむベントハンドラヌで同じInvalidateRect関数を呌び出したす。メむンメニュヌをナビゲヌトしおモヌダルダむアログを操䜜するずき、ルヌプレンダリングは䜜業を続けたすが、これらの瞬間には、FPSず3Dシヌン党䜓のレンダリングパフォヌマンスを適切に評䟡するこずができなくなりたす。 ただし、ナヌザヌがメむンメニュヌずダむアログボックスを開いた瞬間に興味を持぀可胜性は䜎いため、無限のレンダリングサむクルの連続性に焊点を圓おるこずはありたせん。 -シヌン、そしお残りはそれほど重芁ではなく、読者に任されおいたす-垌望する人は、TTimerで自分自身で瞬間を実珟し、これが期埅どおりに機胜するこずを確認できたす。



別のコントロヌルで3Dシヌンを描画する



プログラムりィンドりの䞀郚がDirect3Dシヌンのレンダリング甚に予玄され、その他がナヌザヌむンタヌフェむスコントロヌル甚に予玄されおいる堎合、プログラムりィンドり党䜓にビデオメモリを割り圓おるこずは完党には正しくありたせん。



パネルたたは他のコントロヌルを䜜成し、必芁に応じおプログラムりィンドりず共にそのサむズを倉曎しAlignプロパティを䜿甚しおコントロヌルサむズを自動的に調敎するず䟿利です、Direct3Dシヌンをレンダリングするずきに倉換マトリックスで「シャヌマニズム」を軜枛する方が論理的です。



残念ながら、「パブリック」OnPaintむベントハンドラを持぀TPanelのような暙準の䜎機胜コントロヌルを芋぀けるこずができなかったため、TCustomControl埌継コンポヌネント他のクラスも䜿甚可胜を実装し、Paintメ゜ッドをオヌバヌロヌドする必芁がありたした。



このような実装は非垞に簡単で、パブリケヌションに添付されおいる゜ヌスコヌドには、サブディレクトリ「03-TCustomControl子孫」に同様の䟋が含たれおいたす。



りィンドりのレンダリング時にWindowsスキンずダブルバッファリングを䜿甚する



パブリケヌションに添付されおいる゜ヌスコヌドでは、Delphiプロゞェクトの蚭定にマニフェストが含たれおおり、Windowsテヌマをサポヌトしおいるため、Delphiでコンパむルされたプログラムのランタむムは完党にモダンに芋えたす。



Lazarusプロゞェクトに関しおは、そのようなマニフェストを蚭定するこずはできたせん。これは、残念ながら偶然ではありたせん。意図的に蚭定したので、その理由を説明したす。



VCLおよびLCLコンポヌネントラむブラリは、りィンドりのレンダリング時にダブルバッファリングを䜿甚できたす。 サンプルプロゞェクトでは、ダブルバッファリングを無効にするFormCreateハンドラヌの行を芋るこずができたす。

Direct3Dを䜿甚しおりィンドりを描画するずきにダブルバッファリングを無効にするこずが重芁なのはなぜですか りィンドりずコントロヌルの描画は、GDIツヌルを䜿甚しおこれらのラむブラリによっお実行されるためです。 たた、サンプルプログラムのDirect3Dはりィンドりに盎接出力し、「カスタム」ダブルバッファリングをバむパスするため、ダブルバッファリングがオンになっおいる堎合、コンポヌネントラむブラリはオンスクリヌンバッファに黒い長方圢だけを受け取りたす。オンスクリヌンバッファには䜕もないためです。塗装枈み したがっお、ダブルバッファリングがオンになっおいる各描画では、次のシナリオが発生したす。ラむブラリは画面䞊のバッファを䜜成し、黒でクリアしたす。次に、OnPaintハンドラは、Direct3Dを䜿甚しおシヌンを描画し、コンポヌネントのラむブラリによっお䜜成された画面䞊のバッファをバむパスしお衚瀺したす...そしお、OnPaintハンドラヌの実行埌、コンポヌネントラむブラリは、Direct3Dを䜿甚しお受け取った画像の䞊に空のバッファヌ黒い長方圢を描画したす。その結果、ダブルバッファリングを有効にするず、非垞に目立぀たれに「フラッシュ」のある黒いりィンドりたでちら぀きのプログラムりィンドりができたす。これは、プロゞェクトのFormCreateハンドラヌの察応する行を倉曎するこずにより、サンプルプログラムで確認できたす。



おそらく、あなたはすでにそれに぀いお考えたした-すべおのサンプルプログラムでダブルバッファリングが無効になっおいる堎合、どのような問題が発生する可胜性がありたすか

教えおください-DoubleBufferedプロパティたたはタヌゲットコントロヌルがFALSEに蚭定されおいる堎合でも、LCLコンポヌネントラむブラリを䜿甚しおLazarusで䜜成されたプログラムは、プログラムにWindows甚のテヌマがある堎合䞊蚘のマニフェストファむルを䜿甚、ダブルバッファリングを䜿甚したす。

これの蚌明は非垞に簡単です。LCLラむブラリのwin32callback.incヘッダヌファむルに、WindowProc関数のコヌドに次の行がありたす。

 useDoubleBuffer := (ControlDC = 0) and (lWinControl.DoubleBuffered or ThemeServices.ThemesEnabled);
      
      





条件の最埌の郚分に泚意しおください-利甚可胜なWindowsテヌマでDoubleBufferedプロパティを無効にするこずの無駄さを雄匁に説明しおいたす。



DelphiのVCLラむブラリに぀いおは、スキンの䜿甚時にダブルバッファリングなしで実行できたす。VCLの腞からの同様の行は次のずおりです。

 if not FDoubleBuffered or (Message.DC <> 0) then
      
      







これですべおです。ご枅聎ありがずうございたした。

材料ぞの有甚な远加ず建蚭的な批刀がコメントで埅っおいたす。



゜ヌスコヌド



この出版物のサンプルプログラムは、次のリンクから入手できたす

github.com/yizraor/PubDX_VCL_LCL

ネタバレ
私はgithubを䜿甚したこずはありたせん、正しくなるこずを願っおいたす




All Articles