2 * Copyright (c) 2007-2009 SlimDX Group
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 using System.ComponentModel;
25 using System.Runtime.InteropServices;
27 using System.Threading;
28 using System.Windows.Forms;
30 using SharpDX.Direct3D9;
32 using System.Diagnostics;
34 using Rectangle = System.Drawing.Rectangle;
36 namespace SampleFramework
39 /// Handles the configuration and management of the graphics device.
41 public class GraphicsDeviceManager : IDisposable
44 bool ignoreSizeChanges;
46 // bool doNotStoreBufferSize;
47 // bool renderingOccluded;
49 int fullscreenWindowWidth;
50 int fullscreenWindowHeight;
51 int windowedWindowWidth;
52 int windowedWindowHeight;
53 WINDOWPLACEMENT windowedPlacement;
58 internal static Direct3DEx Direct3D9Object // yyagi
60 internal static Direct3D Direct3D9Object
67 public DeviceSettings CurrentSettings
72 public bool IsWindowed
74 get { return CurrentSettings.Windowed; }
76 public int ScreenWidth
78 get { return CurrentSettings.BackBufferWidth; }
80 public int ScreenHeight
82 get { return CurrentSettings.BackBufferHeight; }
84 public Size ScreenSize
86 get { return new Size(CurrentSettings.BackBufferWidth, CurrentSettings.BackBufferHeight); }
88 public Direct3D9Manager Direct3D9
93 public string DeviceStatistics
98 public string DeviceInformation
104 public GraphicsDeviceManager( Game game )
107 throw new ArgumentNullException( "game" );
111 game.Window.ScreenChanged += Window_ScreenChanged;
112 game.Window.UserResized += Window_UserResized;
114 game.FrameStart += game_FrameStart;
115 game.FrameEnd += game_FrameEnd;
117 Direct3D9 = new Direct3D9Manager( this );
120 public void Dispose()
123 GC.SuppressFinalize(this);
126 public void ChangeDevice( DeviceSettings settings, DeviceSettings minimumSettings )
128 if( settings == null )
129 throw new ArgumentNullException( "settings" );
131 Enumeration9.MinimumSettings = minimumSettings;
133 DeviceSettings validSettings = DeviceSettings.FindValidSettings( settings );
135 var pp = validSettings.Direct3D9.PresentParameters;
136 pp.DeviceWindowHandle = game.Window.Handle;
137 validSettings.Direct3D9.PresentParameters = pp;
139 CreateDevice( validSettings );
141 public void ChangeDevice(bool windowed, int desiredWidth, int desiredHeight)
143 DeviceSettings desiredSettings = new DeviceSettings();
144 desiredSettings.Windowed = windowed;
145 desiredSettings.BackBufferWidth = desiredWidth;
146 desiredSettings.BackBufferHeight = desiredHeight;
148 ChangeDevice(desiredSettings, null);
150 public void ChangeDevice(DeviceSettings settings)
152 ChangeDevice(settings, null);
155 public void ToggleFullScreen()
158 throw new InvalidOperationException("No valid device.");
160 DeviceSettings newSettings = CurrentSettings.Clone();
162 newSettings.Windowed = !newSettings.Windowed;
164 int width = newSettings.Windowed ? windowedWindowWidth : fullscreenWindowWidth;
165 int height = newSettings.Windowed ? windowedWindowHeight : fullscreenWindowHeight;
167 newSettings.BackBufferWidth = width;
168 newSettings.BackBufferHeight = height;
170 ChangeDevice(newSettings);
172 public bool EnsureDevice()
174 if (Direct3D9.Device != null && !deviceLost)
180 protected virtual void Dispose( bool disposing )
184 this.bDisposed = true;
190 private bool bDisposed = false;
192 void CreateDevice(DeviceSettings settings)
194 DeviceSettings oldSettings = CurrentSettings;
195 CurrentSettings = settings;
197 ignoreSizeChanges = true;
199 bool keepCurrentWindowSize = false;
200 if (settings.BackBufferWidth == 0 && settings.BackBufferHeight == 0)
201 keepCurrentWindowSize = true;
203 // handle the window state in Direct3D9 (it will be handled for us in DXGI)
204 // check if we are going to windowed or fullscreen mode
205 if( settings.Windowed )
207 if( oldSettings != null && !oldSettings.Windowed )
208 NativeMethods.SetWindowLong( game.Window.Handle, WindowConstants.GWL_STYLE, (uint) windowedStyle );
212 if( oldSettings == null || oldSettings.Windowed )
214 savedTopmost = game.Window.TopMost;
215 long style = NativeMethods.GetWindowLong( game.Window.Handle, WindowConstants.GWL_STYLE );
216 style &= ~WindowConstants.WS_MAXIMIZE & ~WindowConstants.WS_MINIMIZE;
217 windowedStyle = style;
219 windowedPlacement = new WINDOWPLACEMENT();
220 windowedPlacement.length = WINDOWPLACEMENT.Length;
221 NativeMethods.GetWindowPlacement( game.Window.Handle, ref windowedPlacement );
224 // hide the window until we are done messing with it
226 NativeMethods.SetWindowLong( game.Window.Handle, WindowConstants.GWL_STYLE, (uint) ( WindowConstants.WS_POPUP | WindowConstants.WS_SYSMENU ) );
228 WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
229 placement.length = WINDOWPLACEMENT.Length;
230 NativeMethods.GetWindowPlacement( game.Window.Handle, ref placement );
232 // check if we are in the middle of a restore
233 if( ( placement.flags & WindowConstants.WPF_RESTORETOMAXIMIZED ) != 0 )
235 // update the flags to avoid sizing issues
236 placement.flags &= ~WindowConstants.WPF_RESTORETOMAXIMIZED;
237 placement.showCmd = WindowConstants.SW_RESTORE;
238 NativeMethods.SetWindowPlacement( game.Window.Handle, ref placement );
242 if (settings.Windowed)
244 if (oldSettings != null && !oldSettings.Windowed)
246 fullscreenWindowWidth = oldSettings.BackBufferWidth;
247 fullscreenWindowHeight = oldSettings.BackBufferHeight;
252 if (oldSettings != null && oldSettings.Windowed)
254 windowedWindowWidth = oldSettings.BackBufferWidth;
255 windowedWindowHeight = oldSettings.BackBufferHeight;
259 // check if the device can be reset, or if we need to completely recreate it
260 Result result = SharpDX.Direct3D9.ResultCode.Success;
261 bool canReset = CanDeviceBeReset(oldSettings, settings);
263 result = ResetDevice();
265 if (result == SharpDX.Direct3D9.ResultCode.DeviceLost)
267 else if (!canReset || result.Failure)
269 if (oldSettings != null)
275 UpdateDeviceInformation();
277 // check if we changed from fullscreen to windowed mode
278 if (oldSettings != null && !oldSettings.Windowed && settings.Windowed)
280 NativeMethods.SetWindowPlacement(game.Window.Handle, ref windowedPlacement);
281 game.Window.TopMost = savedTopmost;
284 // check if we need to resize
285 if (settings.Windowed && !keepCurrentWindowSize)
289 if (NativeMethods.IsIconic(game.Window.Handle))
291 WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
292 placement.length = WINDOWPLACEMENT.Length;
293 NativeMethods.GetWindowPlacement(game.Window.Handle, ref placement);
295 // check if we are being restored
296 if ((placement.flags & WindowConstants.WPF_RESTORETOMAXIMIZED) != 0 && placement.showCmd == WindowConstants.SW_SHOWMINIMIZED)
298 NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_RESTORE);
300 Rectangle rect = NativeMethods.GetClientRectangle(game.Window.Handle);
303 height = rect.Height;
304 NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_MINIMIZE);
308 NativeRectangle frame = new NativeRectangle();
309 NativeMethods.AdjustWindowRect(ref frame, (uint)windowedStyle, false);
310 int frameWidth = frame.right - frame.left;
311 int frameHeight = frame.bottom - frame.top;
313 width = placement.rcNormalPosition.right - placement.rcNormalPosition.left - frameWidth;
314 height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top - frameHeight;
319 Rectangle rect = NativeMethods.GetClientRectangle(game.Window.Handle);
321 height = rect.Height;
324 // check if we have a different desired size
325 if (width != settings.BackBufferWidth ||
326 height != settings.BackBufferHeight)
328 if (NativeMethods.IsIconic(game.Window.Handle))
329 NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_RESTORE);
330 if (NativeMethods.IsZoomed(game.Window.Handle))
331 NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_RESTORE);
333 NativeRectangle rect = new NativeRectangle();
334 rect.right = settings.BackBufferWidth;
335 rect.bottom = settings.BackBufferHeight;
336 NativeMethods.AdjustWindowRect(ref rect,
337 NativeMethods.GetWindowLong(game.Window.Handle, WindowConstants.GWL_STYLE), false);
339 NativeMethods.SetWindowPos(game.Window.Handle, IntPtr.Zero, 0, 0, rect.right - rect.left,
340 rect.bottom - rect.top, WindowConstants.SWP_NOZORDER | WindowConstants.SWP_NOMOVE);
342 Rectangle r = NativeMethods.GetClientRectangle(game.Window.Handle);
343 int clientWidth = r.Width;
344 int clientHeight = r.Height;
346 // check if the size was modified by Windows
347 if (clientWidth != settings.BackBufferWidth ||
348 clientHeight != settings.BackBufferHeight)
350 DeviceSettings newSettings = CurrentSettings.Clone();
351 newSettings.BackBufferWidth = 0;
352 newSettings.BackBufferHeight = 0;
353 if (newSettings.Direct3D9 != null)
355 var pp = newSettings.Direct3D9.PresentParameters;
356 pp.BackBufferWidth = GameWindowSize.Width; // #23510 2010.10.31 add yyagi: to avoid setting BackBufferSize=ClientSize
357 pp.BackBufferHeight = GameWindowSize.Height; // #23510 2010.10.31 add yyagi: to avoid setting BackBufferSize=ClientSize
358 newSettings.Direct3D9.PresentParameters = pp;
361 CreateDevice(newSettings);
366 // if the window is still hidden, make sure it is shown
367 if (!game.Window.Visible)
368 NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_SHOW);
370 // set the execution state of the thread
372 NativeMethods.SetThreadExecutionState(WindowConstants.ES_DISPLAY_REQUIRED | WindowConstants.ES_CONTINUOUS);
374 NativeMethods.SetThreadExecutionState(WindowConstants.ES_CONTINUOUS);
376 ignoreSizeChanges = false;
379 void Window_UserResized( object sender, EventArgs e )
381 if( ignoreSizeChanges || !EnsureDevice() || ( !IsWindowed ) )
384 DeviceSettings newSettings = CurrentSettings.Clone();
386 Rectangle rect = NativeMethods.GetClientRectangle( game.Window.Handle );
387 if( rect.Width != newSettings.BackBufferWidth || rect.Height != newSettings.BackBufferHeight )
389 newSettings.BackBufferWidth = 0;
390 newSettings.BackBufferHeight = 0;
391 var pp = newSettings.Direct3D9.PresentParameters;
392 pp.BackBufferWidth = GameWindowSize.Width; // #23510 2010.10.31 add yyagi: to avoid setting BackBufferSize=ClientSize
393 pp.BackBufferHeight = GameWindowSize.Height; //
394 newSettings.Direct3D9.PresentParameters = pp;
395 CreateDevice( newSettings );
398 void Window_ScreenChanged( object sender, EventArgs e )
400 if( !EnsureDevice() || !CurrentSettings.Windowed || ignoreSizeChanges )
403 IntPtr windowMonitor = NativeMethods.MonitorFromWindow( game.Window.Handle, WindowConstants.MONITOR_DEFAULTTOPRIMARY );
405 DeviceSettings newSettings = CurrentSettings.Clone();
406 int adapterOrdinal = GetAdapterOrdinal( windowMonitor );
407 if( adapterOrdinal == -1 )
409 newSettings.Direct3D9.AdapterOrdinal = adapterOrdinal;
411 newSettings.BackBufferWidth = 0; // #23510 2010.11.1 add yyagi to avoid to reset to 640x480 for the first time in XP.
412 newSettings.BackBufferHeight = 0; //
413 var pp = newSettings.Direct3D9.PresentParameters;
414 pp.BackBufferWidth = GameWindowSize.Width; //
415 pp.BackBufferHeight = GameWindowSize.Height; //
416 newSettings.Direct3D9.PresentParameters = pp;
418 CreateDevice( newSettings);
421 void game_FrameEnd( object sender, EventArgs e )
423 Result result = SharpDX.Direct3D9.ResultCode.Success;
427 //result = Direct3D9.Device.TestCooperativeLevel();
428 Direct3D9.Device.Present();
430 catch // #23842 2011.1.6 yyagi: catch D3D9Exception to avoid unexpected termination by changing VSyncWait in fullscreen.
435 if( result == SharpDX.Direct3D9.ResultCode.DeviceLost )
438 void game_FrameStart(object sender, CancelEventArgs e)
440 if (Direct3D9.Device == null )
446 //if (!game.IsActive || deviceLost) // #23568 2010.11.3 yyagi: separate conditions to support valiable sleep value when !IsActive.
449 else if( !game.IsActive && !this.CurrentSettings.EnableVSync ) // #23568 2010.11.4 yyagi: Don't add sleep() while VSync is enabled.
450 Thread.Sleep( this.game.InactiveSleepTime.Milliseconds );
454 Result result = Direct3D9.Device.TestCooperativeLevel();
455 if (result == SharpDX.Direct3D9.ResultCode.DeviceLost)
461 // if we are windowed, check the adapter format to see if the user
462 // changed the desktop format, causing a lost device
465 DisplayMode displayMode = GraphicsDeviceManager.Direct3D9Object.GetAdapterDisplayMode(CurrentSettings.Direct3D9.AdapterOrdinal);
466 if (CurrentSettings.Direct3D9.AdapterFormat != displayMode.Format)
468 DeviceSettings newSettings = CurrentSettings.Clone();
469 ChangeDevice(newSettings);
475 result = ResetDevice();
486 bool CanDeviceBeReset( DeviceSettings oldSettings, DeviceSettings newSettings )
488 if( oldSettings == null )
491 return Direct3D9.Device != null &&
492 oldSettings.Direct3D9.AdapterOrdinal == newSettings.Direct3D9.AdapterOrdinal &&
493 oldSettings.Direct3D9.DeviceType == newSettings.Direct3D9.DeviceType &&
494 oldSettings.Direct3D9.CreationFlags == newSettings.Direct3D9.CreationFlags;
497 void InitializeDevice()
505 // Direct3D9.DeviceExを呼ぶ際(IDirect3D9Ex::CreateDeviceExを呼ぶ際)、
506 // フルスクリーンモードで初期化する場合はDisplayModeEx(D3DDISPLAYMODEEX *pFullscreenDisplayMode)に
508 // 一方、ウインドウモードで初期化する場合は、D3DDISPLAYMODEEXをNULLにする必要があるが、
509 // DisplayModeExがNULL不可と定義されているため、DeviceExのoverloadの中でDisplayModeExを引数に取らないものを
510 // 使う。(DeviceEx側でD3DDISPLAYMODEEXをNULLにしてくれる)
511 // 結局、DeviceExの呼び出しの際に、フルスクリーンかどうかで場合分けが必要となる。
512 if ( CurrentSettings.Direct3D9.PresentParameters.Windowed == false )
514 DisplayModeEx fullScreenDisplayMode = new DisplayModeEx();
515 fullScreenDisplayMode.Width = CurrentSettings.Direct3D9.PresentParameters.BackBufferWidth;
516 fullScreenDisplayMode.Height = CurrentSettings.Direct3D9.PresentParameters.BackBufferHeight;
517 fullScreenDisplayMode.RefreshRate = CurrentSettings.Direct3D9.PresentParameters.FullScreenRefreshRateInHertz;
518 fullScreenDisplayMode.Format = CurrentSettings.Direct3D9.PresentParameters.BackBufferFormat;
520 Direct3D9.Device = new SlimDX.Direct3D9.DeviceEx( Direct3D9Object, CurrentSettings.Direct3D9.AdapterOrdinal,
521 CurrentSettings.Direct3D9.DeviceType, game.Window.Handle,
522 CurrentSettings.Direct3D9.CreationFlags, CurrentSettings.Direct3D9.PresentParameters, fullScreenDisplayMode );
526 Direct3D9.Device = new SlimDX.Direct3D9.DeviceEx( Direct3D9Object, CurrentSettings.Direct3D9.AdapterOrdinal,
527 CurrentSettings.Direct3D9.DeviceType, game.Window.Handle,
528 CurrentSettings.Direct3D9.CreationFlags, CurrentSettings.Direct3D9.PresentParameters );
530 Direct3D9.Device.MaximumFrameLatency = 1;
532 Direct3D9.Device = new SharpDX.Direct3D9.Device(
534 CurrentSettings.Direct3D9.AdapterOrdinal,
535 CurrentSettings.Direct3D9.DeviceType,
537 CurrentSettings.Direct3D9.CreationFlags,
538 CurrentSettings.Direct3D9.PresentParameters );
540 if ( Result.GetResultFromWin32Error( Marshal.GetLastWin32Error() ) == SharpDX.Direct3D9.ResultCode.DeviceLost )
546 Direct3D9.Device.MaximumFrameLatency = 1; // yyagi
551 throw new DeviceCreationException( "Could not create graphics device.", e );
564 game.UnloadContent();
566 Direct3D9.Device.Reset( CurrentSettings.Direct3D9.PresentParameters );
568 var result = Result.GetResultFromWin32Error( Marshal.GetLastWin32Error() );
570 if( result == SharpDX.Direct3D9.ResultCode.DeviceLost )
585 void ReleaseDevice9()
587 if (Direct3D9.Device == null)
592 game.UnloadContent();
598 Direct3D9.Device.Dispose();
600 catch( ObjectDisposedException )
604 Direct3D9Object.Dispose();
606 Direct3D9Object = null;
607 Direct3D9.Device = null;
609 void PropogateSettings()
611 CurrentSettings.BackBufferCount = CurrentSettings.Direct3D9.PresentParameters.BackBufferCount;
612 CurrentSettings.BackBufferWidth = CurrentSettings.Direct3D9.PresentParameters.BackBufferWidth;
613 CurrentSettings.BackBufferHeight = CurrentSettings.Direct3D9.PresentParameters.BackBufferHeight;
614 CurrentSettings.BackBufferFormat = CurrentSettings.Direct3D9.PresentParameters.BackBufferFormat;
615 CurrentSettings.DepthStencilFormat = CurrentSettings.Direct3D9.PresentParameters.AutoDepthStencilFormat;
616 CurrentSettings.DeviceType = CurrentSettings.Direct3D9.DeviceType;
617 CurrentSettings.MultisampleQuality = CurrentSettings.Direct3D9.PresentParameters.MultiSampleQuality;
618 CurrentSettings.MultisampleType = CurrentSettings.Direct3D9.PresentParameters.MultiSampleType;
619 CurrentSettings.RefreshRate = CurrentSettings.Direct3D9.PresentParameters.FullScreenRefreshRateInHz;
620 CurrentSettings.Windowed = CurrentSettings.Direct3D9.PresentParameters.Windowed;
623 void UpdateDeviceInformation()
625 StringBuilder builder = new StringBuilder();
627 if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Hardware )
628 builder.Append( "HAL" );
629 else if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Reference )
630 builder.Append( "REF" );
631 else if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Software )
632 builder.Append( "SW" );
634 if( ( CurrentSettings.Direct3D9.CreationFlags & CreateFlags.HardwareVertexProcessing ) != 0 )
635 if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Hardware )
636 builder.Append( " (hw vp)" );
638 builder.Append( " (simulated hw vp)" );
639 else if( ( CurrentSettings.Direct3D9.CreationFlags & CreateFlags.MixedVertexProcessing ) != 0 )
640 if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Hardware )
641 builder.Append( " (mixed vp)" );
643 builder.Append( " (simulated mixed vp)" );
645 builder.Append( " (sw vp)" );
647 if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Hardware )
649 // loop through each adapter until we find the right one
650 foreach( AdapterInfo9 adapterInfo in Enumeration9.Adapters )
652 if( adapterInfo.AdapterOrdinal == CurrentSettings.Direct3D9.AdapterOrdinal )
654 builder.AppendFormat( ": {0}", adapterInfo.Description );
660 DeviceInformation = builder.ToString();
663 void UpdateDeviceStats()
665 StringBuilder builder = new StringBuilder();
667 builder.Append( "D3D9 Vsync " );
669 if( CurrentSettings.Direct3D9.PresentParameters.PresentationInterval == PresentInterval.Immediate )
670 builder.Append( "off" );
672 builder.Append( "on" );
674 builder.AppendFormat( " ({0}x{1}), ", CurrentSettings.Direct3D9.PresentParameters.BackBufferWidth, CurrentSettings.Direct3D9.PresentParameters.BackBufferHeight );
676 if( CurrentSettings.Direct3D9.AdapterFormat == CurrentSettings.Direct3D9.PresentParameters.BackBufferFormat )
677 builder.Append( Enum.GetName( typeof( SharpDX.Direct3D9.Format ), CurrentSettings.Direct3D9.AdapterFormat ) );
679 builder.AppendFormat( "backbuf {0}, adapter {1}",
680 Enum.GetName( typeof( SharpDX.Direct3D9.Format ), CurrentSettings.Direct3D9.AdapterFormat ),
681 Enum.GetName( typeof( SharpDX.Direct3D9.Format ), CurrentSettings.Direct3D9.PresentParameters.BackBufferFormat ) );
683 builder.AppendFormat( " ({0})", Enum.GetName( typeof( SharpDX.Direct3D9.Format ), CurrentSettings.Direct3D9.PresentParameters.AutoDepthStencilFormat ) );
685 if( CurrentSettings.Direct3D9.PresentParameters.MultiSampleType == MultisampleType.NonMaskable )
686 builder.Append( " (Nonmaskable Multisample)" );
687 else if( CurrentSettings.Direct3D9.PresentParameters.MultiSampleType != MultisampleType.None )
688 builder.AppendFormat( " ({0}x Multisample)", (int) CurrentSettings.Direct3D9.PresentParameters.MultiSampleQuality );
690 DeviceStatistics = builder.ToString();
693 int GetAdapterOrdinal( IntPtr screen )
695 AdapterInfo9 adapter = null;
696 foreach( AdapterInfo9 a in Enumeration9.Adapters )
698 if( Direct3D9Object.GetAdapterMonitor( a.AdapterOrdinal ) == screen )
705 if( adapter != null )
706 return adapter.AdapterOrdinal;
711 internal static void EnsureD3D9()
713 if ( Direct3D9Object == null )
715 Direct3D9Object = new Direct3DEx(); // yyagi
717 Direct3D9Object = new Direct3D();