using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.VisualBasic.ApplicationServices; namespace FDK { /// /// アプリケーションフォームの基礎クラス。 /// public class ApplicationBase : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase { /// /// 基本的に、このインスタンスを使用する任意のスレッドはこれを lock してからアクセスすること。 /// protected readonly object スレッド間同期 = new object(); public bool 全画面モードである { get; protected set; } = false; public bool ウィンドウモードである { get { return !this.全画面モードである; } protected set { this.全画面モードである = !value; } } public ApplicationBase() : base() { this.EnableVisualStyles = true; this.IsSingleInstance = true; // 二重起動を禁止する this.MainForm = new Form(); this.MainForm.Load += OnLoad; this.MainForm.FormClosing += OnClosing; this.MainForm.ClientSizeChanged += OnClientSizeChanged; } protected SharpDX.Size2F 設計画面サイズdpx = SharpDX.Size2F.Empty; // 初期化する() 内で設定すること。 protected FDK.メディア.デバイスリソース デバイスリソース = null; protected System.Threading.Thread 進行描画スレッド = null; protected AutoResetEvent 進行描画スレッドのキャンセル = null; protected System.Threading.AutoResetEvent 進行描画スレッドの起動を完了した = new System.Threading.AutoResetEvent( false ); protected virtual void 初期化する() { //---------------- // 以下は実装例。 //---------------- lock( this.スレッド間同期 ) { Debug.Assert( null == this.デバイスリソース, "デバイスリソースの作成前であること。" ); this.設計画面サイズdpx = new SharpDX.Size2F( 640, 480 ); } } protected virtual void 終了する() { //---------------- // 以下は実装例。 //---------------- lock( this.スレッド間同期 ) { Debug.Assert( null != this.デバイスリソース, "デバイスリソースが解放される前であること。" ); } } protected virtual void シーンを描画する() { //---------------- // 以下は実装例。 // このメソッドはGUIスレッドではなく進行描画スレッドから呼び出されるので注意する。 //---------------- lock( this.スレッド間同期 ) { // ここで、描画を行う。 // ... // ここで、入力を行う。 // ... } // 表示する。垂直帰線待ちなどで時間がかかるので、lock しないこと。 this.デバイスリソース.SwapChain1.Present( 0, SharpDX.DXGI.PresentFlags.None ); } protected virtual void デバイス依存リソースを解放する() { lock( this.スレッド間同期 ) { Debug.Assert( null != this.デバイスリソース ); // 解放前であること。 // ここで自分のデバイス依存リソースを解放する。 // ... } } protected virtual void デバイス依存リソースを再構築する() { lock( this.スレッド間同期 ) { Debug.Assert( null != this.デバイスリソース ); // 再生成済みであること。 // ここで自分のデバイス依存リソースを解放する。 // ... } } protected void 全画面モードとウィンドウモードを切り替える() { lock( this.スレッド間同期 ) { if( this.全画面モードである ) { this.デバイスリソース.SwapChain1.SetFullscreenState( false, null ); // ウィンドウモードへ。 this.ウィンドウモードである = true; } else { this.デバイスリソース.SwapChain1.SetFullscreenState( true, null ); // 全画面モードへ。 this.全画面モードである = true; } } } /// /// アプリが二重起動されたときに発生するイベント。 /// /// /// 後続のインスタンスは起動せず、既存のインスタンスに対してこのイベントが発生する。 /// eventArg.CommandLine で、後続のインスタンスのコマンドライン引数を確認することができる。 /// protected override void OnStartupNextInstance( StartupNextInstanceEventArgs eventArgs ) { // 必要がれば、派生クラスで実装すること。 } private bool Closing中 = false; private void OnLoad( object sender, EventArgs e ) { FDK.Log.現在のスレッドに名前をつける( "GUI" ); FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" ); #region " アプリケーションを初期化する。" //---------------- FDK.Log.BeginInfo( "派生クラスを初期化します。" ); this.初期化する(); Debug.Assert( SharpDX.Size2F.Empty != this.設計画面サイズdpx, "初期化メソッド内で設計画面サイズを設定してあること。" ); FDK.Log.Info( $"設計画面サイズ: {this.設計画面サイズdpx}" ); FDK.Log.Info( $"物理画面サイズ: {this.MainForm.ClientSize}" ); FDK.Log.EndInfo( "派生クラスを初期化しました。" ); //---------------- #endregion #region " デバイスリソースを作成する。" //---------------- FDK.Log.BeginInfo( "デバイスリソースを作成します。" ); lock( this.スレッド間同期 ) { this.デバイスリソース = new メディア.デバイスリソース(); this.デバイスリソース.設計画面サイズdpx = this.設計画面サイズdpx; this.デバイスリソース.すべてのリソースを作成する( this.MainForm.ClientSize, this.MainForm.Handle ); } FDK.Log.EndInfo( "デバイスリソースを作成しました。" ); //---------------- #endregion #region " 進行描画スレッドを開始する。" //---------------- FDK.Log.BeginInfo( "進行描画スレッドを開始します。" ); lock( this.スレッド間同期 ) { this.進行描画スレッドのキャンセル = new AutoResetEvent( false ); this.進行描画スレッド = new Thread( this.進行描画スレッドエントリ ); //this.進行描画スレッド.Priority = ThreadPriority.AboveNormal; this.進行描画スレッド.Start(); this.進行描画スレッドの起動を完了した.WaitOne(); // スレッドの起動完了通知を待つ。 } //---------------- #endregion FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" ); } private void OnClosing( object sender, FormClosingEventArgs e ) { FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" ); this.Closing中 = true; // 進行描画スレッドを終了する。 if( ( null != this.進行描画スレッドのキャンセル ) && ( null != this.進行描画スレッド ) ) { try { this.進行描画スレッドのキャンセル.Set(); // lock 内で呼び出したら絶対タイムアウトになるので注意。 FDK.Log.Info( "進行描画スレッドにキャンセルを発行しました。" ); bool done = this.進行描画スレッド.Join( millisecondsTimeout: 5000 ); // タイムアウトは保険。 if( done ) FDK.Log.Info( "進行描画スレッドの完了を確認しました。" ); else FDK.Log.ERROR( "進行描画スレッドの完了を時間内に確認できませんでした。" ); } catch( AggregateException ex ) { if( ex.InnerExceptions.Any( ( 内部例外 ) => ( 内部例外 is OperationCanceledException ) ) ) { // OK } else { throw; // NG } } catch( ObjectDisposedException ) { // タスクがすでに終わってた。 } } lock( this.スレッド間同期 ) { // 派生クラスの終了処理を呼び出す。 this.終了する(); // デバイスリソースを解放する。 this.デバイスリソース?.Dispose(); this.デバイスリソース = null; } FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" ); } private void OnClientSizeChanged( object sender, EventArgs e ) { FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" ); FDK.Log.Info( $"新しいクライアントサイズ = {this.MainForm.ClientSize}" ); lock( this.スレッド間同期 ) { #region " 実行条件チェック。" //---------------- if( null == this.デバイスリソース ) { FDK.Log.Info( " まだ初期化されてないので、何もしません。" ); FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" ); return; } if( this.MainForm.WindowState == FormWindowState.Minimized ) { FDK.Log.Info( "最小化されました。" ); FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" ); return; // 何もしない } if( this.Closing中 ) { FDK.Log.Info( " 終了処理中なので、何もしません。" ); FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" ); return; } //---------------- #endregion var dr = this.デバイスリソース; Debug.Assert( null != dr, "デバイスリソースが作成済みであること。" ); // 現在の画面モードを取得しておく。(Alt+TABなど、勝手に全画面を解除されることもあるので。) SharpDX.Mathematics.Interop.RawBool fullscreen; SharpDX.DXGI.Output outputTarget; dr.SwapChain1.GetFullscreenState( out fullscreen, out outputTarget ); this.全画面モードである = fullscreen; outputTarget?.Dispose(); FDK.Log.Info( $"現在、全画面モードである = {this.全画面モードである}" ); // (1) リソースを解放して、 this.デバイス依存リソースを解放する(); dr.サイズに依存するリソースを解放する(); // (2) 物理画面サイズを変更して、 dr.物理画面サイズpx = new SharpDX.Size2F( this.MainForm.ClientSize.Width, this.MainForm.ClientSize.Height ); // (3) リソースを再構築する。 dr.サイズに依存するリソースを作成する(); this.デバイス依存リソースを再構築する(); } FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" ); } private void 進行描画スレッドエントリ() { FDK.Log.現在のスレッドに名前をつける( "Main" ); FDK.Log.Info( "進行描画スレッドを起動しました。" ); this.進行描画スレッドの起動を完了した.Set(); // 生成元へ起動完了を通知する。 while( true ) { bool アプリを終了せよ = false; lock( this.スレッド間同期 ) { // 別スレッドからキャンセル要求があれば、終了フラグを立てる。 if( this.進行描画スレッドのキャンセル.WaitOne( 0 ) ) { アプリを終了せよ = true; } else { // D3Dデバイスが消失していれば再構築する。 bool 異常発生 = false; this.デバイスリソース.D3Dデバイスが消失していれば再構築する( out 異常発生 ); if( 異常発生 ) アプリを終了せよ = true; } } // 終了フラグがセットされていれば、ループを抜けてスレッドを終了する。 if( アプリを終了せよ ) break; // それ以外なら、シーンを進行・描画する。 this.シーンを描画する(); } FDK.Log.Info( "進行描画スレッドを終了します。" ); } }; }