/* * Copyright (c) 2007-2009 SlimDX Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ using System; using System.ComponentModel; using System.Drawing; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Windows.Forms; using SharpDX; using SharpDX.Direct3D9; using SharpDX.DXGI; using System.Diagnostics; using Rectangle = System.Drawing.Rectangle; namespace SampleFramework { /// /// Handles the configuration and management of the graphics device. /// public class GraphicsDeviceManager : IDisposable { Game game; bool ignoreSizeChanges; bool deviceLost; // bool doNotStoreBufferSize; // bool renderingOccluded; int fullscreenWindowWidth; int fullscreenWindowHeight; int windowedWindowWidth; int windowedWindowHeight; WINDOWPLACEMENT windowedPlacement; long windowedStyle; bool savedTopmost; #if TEST_Direct3D9Ex internal static Direct3DEx Direct3D9Object // yyagi #else internal static Direct3D Direct3D9Object #endif { get; private set; } public DeviceSettings CurrentSettings { get; private set; } public bool IsWindowed { get { return CurrentSettings.Windowed; } } public int ScreenWidth { get { return CurrentSettings.BackBufferWidth; } } public int ScreenHeight { get { return CurrentSettings.BackBufferHeight; } } public Size ScreenSize { get { return new Size(CurrentSettings.BackBufferWidth, CurrentSettings.BackBufferHeight); } } public Direct3D9Manager Direct3D9 { get; private set; } public string DeviceStatistics { get; private set; } public string DeviceInformation { get; private set; } public GraphicsDeviceManager( Game game ) { if( game == null ) throw new ArgumentNullException( "game" ); this.game = game; game.Window.ScreenChanged += Window_ScreenChanged; game.Window.UserResized += Window_UserResized; game.FrameStart += game_FrameStart; game.FrameEnd += game_FrameEnd; Direct3D9 = new Direct3D9Manager( this ); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void ChangeDevice( DeviceSettings settings, DeviceSettings minimumSettings ) { if( settings == null ) throw new ArgumentNullException( "settings" ); Enumeration9.MinimumSettings = minimumSettings; DeviceSettings validSettings = DeviceSettings.FindValidSettings( settings ); var pp = validSettings.Direct3D9.PresentParameters; pp.DeviceWindowHandle = game.Window.Handle; validSettings.Direct3D9.PresentParameters = pp; CreateDevice( validSettings ); } public void ChangeDevice(bool windowed, int desiredWidth, int desiredHeight) { DeviceSettings desiredSettings = new DeviceSettings(); desiredSettings.Windowed = windowed; desiredSettings.BackBufferWidth = desiredWidth; desiredSettings.BackBufferHeight = desiredHeight; ChangeDevice(desiredSettings, null); } public void ChangeDevice(DeviceSettings settings) { ChangeDevice(settings, null); } public void ToggleFullScreen() { if (!EnsureDevice()) throw new InvalidOperationException("No valid device."); DeviceSettings newSettings = CurrentSettings.Clone(); newSettings.Windowed = !newSettings.Windowed; int width = newSettings.Windowed ? windowedWindowWidth : fullscreenWindowWidth; int height = newSettings.Windowed ? windowedWindowHeight : fullscreenWindowHeight; newSettings.BackBufferWidth = width; newSettings.BackBufferHeight = height; ChangeDevice(newSettings); } public bool EnsureDevice() { if (Direct3D9.Device != null && !deviceLost) return true; return false; } protected virtual void Dispose( bool disposing ) { if( this.bDisposed ) return; this.bDisposed = true; if( disposing ) ReleaseDevice(); } private bool bDisposed = false; void CreateDevice(DeviceSettings settings) { DeviceSettings oldSettings = CurrentSettings; CurrentSettings = settings; ignoreSizeChanges = true; bool keepCurrentWindowSize = false; if (settings.BackBufferWidth == 0 && settings.BackBufferHeight == 0) keepCurrentWindowSize = true; // handle the window state in Direct3D9 (it will be handled for us in DXGI) // check if we are going to windowed or fullscreen mode if( settings.Windowed ) { if( oldSettings != null && !oldSettings.Windowed ) NativeMethods.SetWindowLong( game.Window.Handle, WindowConstants.GWL_STYLE, (uint) windowedStyle ); } else { if( oldSettings == null || oldSettings.Windowed ) { savedTopmost = game.Window.TopMost; long style = NativeMethods.GetWindowLong( game.Window.Handle, WindowConstants.GWL_STYLE ); style &= ~WindowConstants.WS_MAXIMIZE & ~WindowConstants.WS_MINIMIZE; windowedStyle = style; windowedPlacement = new WINDOWPLACEMENT(); windowedPlacement.length = WINDOWPLACEMENT.Length; NativeMethods.GetWindowPlacement( game.Window.Handle, ref windowedPlacement ); } // hide the window until we are done messing with it game.Window.Hide(); NativeMethods.SetWindowLong( game.Window.Handle, WindowConstants.GWL_STYLE, (uint) ( WindowConstants.WS_POPUP | WindowConstants.WS_SYSMENU ) ); WINDOWPLACEMENT placement = new WINDOWPLACEMENT(); placement.length = WINDOWPLACEMENT.Length; NativeMethods.GetWindowPlacement( game.Window.Handle, ref placement ); // check if we are in the middle of a restore if( ( placement.flags & WindowConstants.WPF_RESTORETOMAXIMIZED ) != 0 ) { // update the flags to avoid sizing issues placement.flags &= ~WindowConstants.WPF_RESTORETOMAXIMIZED; placement.showCmd = WindowConstants.SW_RESTORE; NativeMethods.SetWindowPlacement( game.Window.Handle, ref placement ); } } if (settings.Windowed) { if (oldSettings != null && !oldSettings.Windowed) { fullscreenWindowWidth = oldSettings.BackBufferWidth; fullscreenWindowHeight = oldSettings.BackBufferHeight; } } else { if (oldSettings != null && oldSettings.Windowed) { windowedWindowWidth = oldSettings.BackBufferWidth; windowedWindowHeight = oldSettings.BackBufferHeight; } } // check if the device can be reset, or if we need to completely recreate it Result result = SharpDX.Direct3D9.ResultCode.Success; bool canReset = CanDeviceBeReset(oldSettings, settings); if (canReset) result = ResetDevice(); if (result == SharpDX.Direct3D9.ResultCode.DeviceLost) deviceLost = true; else if (!canReset || result.Failure) { if (oldSettings != null) ReleaseDevice(); InitializeDevice(); } UpdateDeviceInformation(); // check if we changed from fullscreen to windowed mode if (oldSettings != null && !oldSettings.Windowed && settings.Windowed) { NativeMethods.SetWindowPlacement(game.Window.Handle, ref windowedPlacement); game.Window.TopMost = savedTopmost; } // check if we need to resize if (settings.Windowed && !keepCurrentWindowSize) { int width; int height; if (NativeMethods.IsIconic(game.Window.Handle)) { WINDOWPLACEMENT placement = new WINDOWPLACEMENT(); placement.length = WINDOWPLACEMENT.Length; NativeMethods.GetWindowPlacement(game.Window.Handle, ref placement); // check if we are being restored if ((placement.flags & WindowConstants.WPF_RESTORETOMAXIMIZED) != 0 && placement.showCmd == WindowConstants.SW_SHOWMINIMIZED) { NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_RESTORE); Rectangle rect = NativeMethods.GetClientRectangle(game.Window.Handle); width = rect.Width; height = rect.Height; NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_MINIMIZE); } else { NativeRectangle frame = new NativeRectangle(); NativeMethods.AdjustWindowRect(ref frame, (uint)windowedStyle, false); int frameWidth = frame.right - frame.left; int frameHeight = frame.bottom - frame.top; width = placement.rcNormalPosition.right - placement.rcNormalPosition.left - frameWidth; height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top - frameHeight; } } else { Rectangle rect = NativeMethods.GetClientRectangle(game.Window.Handle); width = rect.Width; height = rect.Height; } // check if we have a different desired size if (width != settings.BackBufferWidth || height != settings.BackBufferHeight) { if (NativeMethods.IsIconic(game.Window.Handle)) NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_RESTORE); if (NativeMethods.IsZoomed(game.Window.Handle)) NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_RESTORE); NativeRectangle rect = new NativeRectangle(); rect.right = settings.BackBufferWidth; rect.bottom = settings.BackBufferHeight; NativeMethods.AdjustWindowRect(ref rect, NativeMethods.GetWindowLong(game.Window.Handle, WindowConstants.GWL_STYLE), false); NativeMethods.SetWindowPos(game.Window.Handle, IntPtr.Zero, 0, 0, rect.right - rect.left, rect.bottom - rect.top, WindowConstants.SWP_NOZORDER | WindowConstants.SWP_NOMOVE); Rectangle r = NativeMethods.GetClientRectangle(game.Window.Handle); int clientWidth = r.Width; int clientHeight = r.Height; // check if the size was modified by Windows if (clientWidth != settings.BackBufferWidth || clientHeight != settings.BackBufferHeight) { DeviceSettings newSettings = CurrentSettings.Clone(); newSettings.BackBufferWidth = 0; newSettings.BackBufferHeight = 0; if (newSettings.Direct3D9 != null) { var pp = newSettings.Direct3D9.PresentParameters; pp.BackBufferWidth = GameWindowSize.Width; // #23510 2010.10.31 add yyagi: to avoid setting BackBufferSize=ClientSize pp.BackBufferHeight = GameWindowSize.Height; // #23510 2010.10.31 add yyagi: to avoid setting BackBufferSize=ClientSize newSettings.Direct3D9.PresentParameters = pp; } CreateDevice(newSettings); } } } // if the window is still hidden, make sure it is shown if (!game.Window.Visible) NativeMethods.ShowWindow(game.Window.Handle, WindowConstants.SW_SHOW); // set the execution state of the thread if (!IsWindowed) NativeMethods.SetThreadExecutionState(WindowConstants.ES_DISPLAY_REQUIRED | WindowConstants.ES_CONTINUOUS); else NativeMethods.SetThreadExecutionState(WindowConstants.ES_CONTINUOUS); ignoreSizeChanges = false; } void Window_UserResized( object sender, EventArgs e ) { if( ignoreSizeChanges || !EnsureDevice() || ( !IsWindowed ) ) return; DeviceSettings newSettings = CurrentSettings.Clone(); Rectangle rect = NativeMethods.GetClientRectangle( game.Window.Handle ); if( rect.Width != newSettings.BackBufferWidth || rect.Height != newSettings.BackBufferHeight ) { newSettings.BackBufferWidth = 0; newSettings.BackBufferHeight = 0; var pp = newSettings.Direct3D9.PresentParameters; pp.BackBufferWidth = GameWindowSize.Width; // #23510 2010.10.31 add yyagi: to avoid setting BackBufferSize=ClientSize pp.BackBufferHeight = GameWindowSize.Height; // newSettings.Direct3D9.PresentParameters = pp; CreateDevice( newSettings ); } } void Window_ScreenChanged( object sender, EventArgs e ) { if( !EnsureDevice() || !CurrentSettings.Windowed || ignoreSizeChanges ) return; IntPtr windowMonitor = NativeMethods.MonitorFromWindow( game.Window.Handle, WindowConstants.MONITOR_DEFAULTTOPRIMARY ); DeviceSettings newSettings = CurrentSettings.Clone(); int adapterOrdinal = GetAdapterOrdinal( windowMonitor ); if( adapterOrdinal == -1 ) return; newSettings.Direct3D9.AdapterOrdinal = adapterOrdinal; newSettings.BackBufferWidth = 0; // #23510 2010.11.1 add yyagi to avoid to reset to 640x480 for the first time in XP. newSettings.BackBufferHeight = 0; // var pp = newSettings.Direct3D9.PresentParameters; pp.BackBufferWidth = GameWindowSize.Width; // pp.BackBufferHeight = GameWindowSize.Height; // newSettings.Direct3D9.PresentParameters = pp; CreateDevice( newSettings); } void game_FrameEnd( object sender, EventArgs e ) { Result result = SharpDX.Direct3D9.ResultCode.Success; try { //result = Direct3D9.Device.TestCooperativeLevel(); Direct3D9.Device.Present(); } catch // #23842 2011.1.6 yyagi: catch D3D9Exception to avoid unexpected termination by changing VSyncWait in fullscreen. { deviceLost = true; } if( result == SharpDX.Direct3D9.ResultCode.DeviceLost ) deviceLost = true; } void game_FrameStart(object sender, CancelEventArgs e) { if (Direct3D9.Device == null ) { e.Cancel = true; return; } //if (!game.IsActive || deviceLost) // #23568 2010.11.3 yyagi: separate conditions to support valiable sleep value when !IsActive. if( deviceLost ) Thread.Sleep( 50 ); else if( !game.IsActive && !this.CurrentSettings.EnableVSync ) // #23568 2010.11.4 yyagi: Don't add sleep() while VSync is enabled. Thread.Sleep( this.game.InactiveSleepTime.Milliseconds ); if (deviceLost) { Result result = Direct3D9.Device.TestCooperativeLevel(); if (result == SharpDX.Direct3D9.ResultCode.DeviceLost) { e.Cancel = true; return; } // if we are windowed, check the adapter format to see if the user // changed the desktop format, causing a lost device if (IsWindowed) { DisplayMode displayMode = GraphicsDeviceManager.Direct3D9Object.GetAdapterDisplayMode(CurrentSettings.Direct3D9.AdapterOrdinal); if (CurrentSettings.Direct3D9.AdapterFormat != displayMode.Format) { DeviceSettings newSettings = CurrentSettings.Clone(); ChangeDevice(newSettings); e.Cancel = true; return; } } result = ResetDevice(); if (result.Failure) { e.Cancel = true; return; } } deviceLost = false; } bool CanDeviceBeReset( DeviceSettings oldSettings, DeviceSettings newSettings ) { if( oldSettings == null ) return false; return Direct3D9.Device != null && oldSettings.Direct3D9.AdapterOrdinal == newSettings.Direct3D9.AdapterOrdinal && oldSettings.Direct3D9.DeviceType == newSettings.Direct3D9.DeviceType && oldSettings.Direct3D9.CreationFlags == newSettings.Direct3D9.CreationFlags; } void InitializeDevice() { try { EnsureD3D9(); #if TEST_Direct3D9Ex // 2011.4.26 yyagi // Direct3D9.DeviceExを呼ぶ際(IDirect3D9Ex::CreateDeviceExを呼ぶ際)、 // フルスクリーンモードで初期化する場合はDisplayModeEx(D3DDISPLAYMODEEX *pFullscreenDisplayMode)に // 適切な値を設定する必要あり。 // 一方、ウインドウモードで初期化する場合は、D3DDISPLAYMODEEXをNULLにする必要があるが、 // DisplayModeExがNULL不可と定義されているため、DeviceExのoverloadの中でDisplayModeExを引数に取らないものを // 使う。(DeviceEx側でD3DDISPLAYMODEEXをNULLにしてくれる) // 結局、DeviceExの呼び出しの際に、フルスクリーンかどうかで場合分けが必要となる。 if ( CurrentSettings.Direct3D9.PresentParameters.Windowed == false ) { DisplayModeEx fullScreenDisplayMode = new DisplayModeEx(); fullScreenDisplayMode.Width = CurrentSettings.Direct3D9.PresentParameters.BackBufferWidth; fullScreenDisplayMode.Height = CurrentSettings.Direct3D9.PresentParameters.BackBufferHeight; fullScreenDisplayMode.RefreshRate = CurrentSettings.Direct3D9.PresentParameters.FullScreenRefreshRateInHertz; fullScreenDisplayMode.Format = CurrentSettings.Direct3D9.PresentParameters.BackBufferFormat; Direct3D9.Device = new SlimDX.Direct3D9.DeviceEx( Direct3D9Object, CurrentSettings.Direct3D9.AdapterOrdinal, CurrentSettings.Direct3D9.DeviceType, game.Window.Handle, CurrentSettings.Direct3D9.CreationFlags, CurrentSettings.Direct3D9.PresentParameters, fullScreenDisplayMode ); } else { Direct3D9.Device = new SlimDX.Direct3D9.DeviceEx( Direct3D9Object, CurrentSettings.Direct3D9.AdapterOrdinal, CurrentSettings.Direct3D9.DeviceType, game.Window.Handle, CurrentSettings.Direct3D9.CreationFlags, CurrentSettings.Direct3D9.PresentParameters ); } Direct3D9.Device.MaximumFrameLatency = 1; #else Direct3D9.Device = new SharpDX.Direct3D9.Device( Direct3D9Object, CurrentSettings.Direct3D9.AdapterOrdinal, CurrentSettings.Direct3D9.DeviceType, game.Window.Handle, CurrentSettings.Direct3D9.CreationFlags, CurrentSettings.Direct3D9.PresentParameters ); #endif if ( Result.GetResultFromWin32Error( Marshal.GetLastWin32Error() ) == SharpDX.Direct3D9.ResultCode.DeviceLost ) { deviceLost = true; return; } #if TEST_Direct3D9Ex Direct3D9.Device.MaximumFrameLatency = 1; // yyagi #endif } catch( Exception e ) { throw new DeviceCreationException( "Could not create graphics device.", e ); } PropogateSettings(); UpdateDeviceStats(); game.Initialize(); game.LoadContent(); } Result ResetDevice() { game.UnloadContent(); Direct3D9.Device.Reset( CurrentSettings.Direct3D9.PresentParameters ); var result = Result.GetResultFromWin32Error( Marshal.GetLastWin32Error() ); if( result == SharpDX.Direct3D9.ResultCode.DeviceLost ) return result; PropogateSettings(); UpdateDeviceStats(); game.LoadContent(); return result; } void ReleaseDevice() { ReleaseDevice9(); } void ReleaseDevice9() { if (Direct3D9.Device == null) return; if (game != null) { game.UnloadContent(); game.Dispose(true); } try { Direct3D9.Device.Dispose(); } catch( ObjectDisposedException ) { // 時々発生するのでキャッチしておく。 } Direct3D9Object.Dispose(); Direct3D9Object = null; Direct3D9.Device = null; } void PropogateSettings() { CurrentSettings.BackBufferCount = CurrentSettings.Direct3D9.PresentParameters.BackBufferCount; CurrentSettings.BackBufferWidth = CurrentSettings.Direct3D9.PresentParameters.BackBufferWidth; CurrentSettings.BackBufferHeight = CurrentSettings.Direct3D9.PresentParameters.BackBufferHeight; CurrentSettings.BackBufferFormat = CurrentSettings.Direct3D9.PresentParameters.BackBufferFormat; CurrentSettings.DepthStencilFormat = CurrentSettings.Direct3D9.PresentParameters.AutoDepthStencilFormat; CurrentSettings.DeviceType = CurrentSettings.Direct3D9.DeviceType; CurrentSettings.MultisampleQuality = CurrentSettings.Direct3D9.PresentParameters.MultiSampleQuality; CurrentSettings.MultisampleType = CurrentSettings.Direct3D9.PresentParameters.MultiSampleType; CurrentSettings.RefreshRate = CurrentSettings.Direct3D9.PresentParameters.FullScreenRefreshRateInHz; CurrentSettings.Windowed = CurrentSettings.Direct3D9.PresentParameters.Windowed; } void UpdateDeviceInformation() { StringBuilder builder = new StringBuilder(); if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Hardware ) builder.Append( "HAL" ); else if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Reference ) builder.Append( "REF" ); else if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Software ) builder.Append( "SW" ); if( ( CurrentSettings.Direct3D9.CreationFlags & CreateFlags.HardwareVertexProcessing ) != 0 ) if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Hardware ) builder.Append( " (hw vp)" ); else builder.Append( " (simulated hw vp)" ); else if( ( CurrentSettings.Direct3D9.CreationFlags & CreateFlags.MixedVertexProcessing ) != 0 ) if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Hardware ) builder.Append( " (mixed vp)" ); else builder.Append( " (simulated mixed vp)" ); else builder.Append( " (sw vp)" ); if( CurrentSettings.Direct3D9.DeviceType == DeviceType.Hardware ) { // loop through each adapter until we find the right one foreach( AdapterInfo9 adapterInfo in Enumeration9.Adapters ) { if( adapterInfo.AdapterOrdinal == CurrentSettings.Direct3D9.AdapterOrdinal ) { builder.AppendFormat( ": {0}", adapterInfo.Description ); break; } } } DeviceInformation = builder.ToString(); } void UpdateDeviceStats() { StringBuilder builder = new StringBuilder(); builder.Append( "D3D9 Vsync " ); if( CurrentSettings.Direct3D9.PresentParameters.PresentationInterval == PresentInterval.Immediate ) builder.Append( "off" ); else builder.Append( "on" ); builder.AppendFormat( " ({0}x{1}), ", CurrentSettings.Direct3D9.PresentParameters.BackBufferWidth, CurrentSettings.Direct3D9.PresentParameters.BackBufferHeight ); if( CurrentSettings.Direct3D9.AdapterFormat == CurrentSettings.Direct3D9.PresentParameters.BackBufferFormat ) builder.Append( Enum.GetName( typeof( SharpDX.Direct3D9.Format ), CurrentSettings.Direct3D9.AdapterFormat ) ); else builder.AppendFormat( "backbuf {0}, adapter {1}", Enum.GetName( typeof( SharpDX.Direct3D9.Format ), CurrentSettings.Direct3D9.AdapterFormat ), Enum.GetName( typeof( SharpDX.Direct3D9.Format ), CurrentSettings.Direct3D9.PresentParameters.BackBufferFormat ) ); builder.AppendFormat( " ({0})", Enum.GetName( typeof( SharpDX.Direct3D9.Format ), CurrentSettings.Direct3D9.PresentParameters.AutoDepthStencilFormat ) ); if( CurrentSettings.Direct3D9.PresentParameters.MultiSampleType == MultisampleType.NonMaskable ) builder.AppendFormat( " (Nonmaskable Multisample {0}x)", (int)CurrentSettings.Direct3D9.PresentParameters.MultiSampleQuality); else if( CurrentSettings.Direct3D9.PresentParameters.MultiSampleType != MultisampleType.None ) builder.AppendFormat( " (Multisample {0}x Type={1})", (int) CurrentSettings.Direct3D9.PresentParameters.MultiSampleQuality, CurrentSettings.Direct3D9.PresentParameters.MultiSampleType.ToString() ); DeviceStatistics = builder.ToString(); } int GetAdapterOrdinal( IntPtr screen ) { AdapterInfo9 adapter = null; foreach( AdapterInfo9 a in Enumeration9.Adapters ) { if( Direct3D9Object.GetAdapterMonitor( a.AdapterOrdinal ) == screen ) { adapter = a; break; } } if( adapter != null ) return adapter.AdapterOrdinal; return -1; } internal static void EnsureD3D9() { if ( Direct3D9Object == null ) #if TEST_Direct3D9Ex Direct3D9Object = new Direct3DEx(); // yyagi #else Direct3D9Object = new Direct3D(); #endif } } }