// MonoGame - Copyright (C) The MonoGame Team // This file is subject to the terms and conditions defined in // file 'LICENSE.txt', which is part of this source code package. using System; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input.Touch; namespace Microsoft.Xna.Framework { /// /// Used to initialize and control the presentation of the graphics device. /// public partial class GraphicsDeviceManager : IGraphicsDeviceService, IDisposable, IGraphicsDeviceManager { private readonly Game _game; private GraphicsDevice _graphicsDevice; private bool _initialized = false; private int _preferredBackBufferHeight; private int _preferredBackBufferWidth; private SurfaceFormat _preferredBackBufferFormat; private DepthFormat _preferredDepthStencilFormat; private bool _preferMultiSampling; private DisplayOrientation _supportedOrientations; private bool _synchronizedWithVerticalRetrace = true; private bool _drawBegun; private bool _disposed; private bool _hardwareModeSwitch = true; private bool _wantFullScreen; private GraphicsProfile _graphicsProfile; // dirty flag for ApplyChanges private bool _shouldApplyChanges; /// /// The default back buffer width. /// public static readonly int DefaultBackBufferWidth = 800; /// /// The default back buffer height. /// public static readonly int DefaultBackBufferHeight = 480; /// /// Optional override for platform specific defaults. /// partial void PlatformConstruct(); /// /// Associates this graphics device manager to a game instances. /// /// The game instance to attach. public GraphicsDeviceManager(Game game) { if (game == null) throw new ArgumentNullException("game", "Game cannot be null."); _game = game; _supportedOrientations = DisplayOrientation.Default; _preferredBackBufferFormat = SurfaceFormat.Color; _preferredDepthStencilFormat = DepthFormat.Depth24; _synchronizedWithVerticalRetrace = true; // Assume the window client size as the default back // buffer resolution in the landscape orientation. var clientBounds = _game.Window.ClientBounds; if (clientBounds.Width >= clientBounds.Height) { _preferredBackBufferWidth = clientBounds.Width; _preferredBackBufferHeight = clientBounds.Height; } else { _preferredBackBufferWidth = clientBounds.Height; _preferredBackBufferHeight = clientBounds.Width; } // Default to windowed mode... this is ignored on platforms that don't support it. _wantFullScreen = false; // XNA would read this from the manifest, but it would always default // to reach unless changed. So lets mimic that without the manifest bit. GraphicsProfile = GraphicsProfile.Reach; // Let the plaform optionally overload construction defaults. PlatformConstruct(); if (_game.Services.GetService(typeof(IGraphicsDeviceManager)) != null) throw new ArgumentException("A graphics device manager is already registered. The graphics device manager cannot be changed once it is set."); _game.graphicsDeviceManager = this; _game.Services.AddService(typeof(IGraphicsDeviceManager), this); _game.Services.AddService(typeof(IGraphicsDeviceService), this); } ~GraphicsDeviceManager() { Dispose(false); } private void CreateDevice() { if (_graphicsDevice != null) return; try { if (!_initialized) Initialize(); var gdi = DoPreparingDeviceSettings(); CreateDevice(gdi); } catch (NoSuitableGraphicsDeviceException) { throw; } catch (Exception ex) { throw new NoSuitableGraphicsDeviceException("Failed to create graphics device!", ex); } } private void CreateDevice(GraphicsDeviceInformation gdi) { if (_graphicsDevice != null) return; _graphicsDevice = new GraphicsDevice(gdi); _shouldApplyChanges = false; // hook up reset events GraphicsDevice.DeviceReset += (sender, args) => OnDeviceReset(args); GraphicsDevice.DeviceResetting += (sender, args) => OnDeviceResetting(args); // update the touchpanel display size when the graphicsdevice is reset _graphicsDevice.DeviceReset += UpdateTouchPanel; _graphicsDevice.PresentationChanged += OnPresentationChanged; OnDeviceCreated(EventArgs.Empty); } void IGraphicsDeviceManager.CreateDevice() { CreateDevice(); } public bool BeginDraw() { if (_graphicsDevice == null) return false; _drawBegun = true; return true; } public void EndDraw() { if (_graphicsDevice != null && _drawBegun) { _drawBegun = false; _graphicsDevice.Present(); } } #region IGraphicsDeviceService Members public event EventHandler DeviceCreated; public event EventHandler DeviceDisposing; public event EventHandler DeviceReset; public event EventHandler DeviceResetting; public event EventHandler PreparingDeviceSettings; public event EventHandler Disposed; protected void OnDeviceDisposing(EventArgs e) { EventHelpers.Raise(this, DeviceDisposing, e); } protected void OnDeviceResetting(EventArgs e) { EventHelpers.Raise(this, DeviceResetting, e); } internal void OnDeviceReset(EventArgs e) { EventHelpers.Raise(this, DeviceReset, e); } internal void OnDeviceCreated(EventArgs e) { EventHelpers.Raise(this, DeviceCreated, e); } /// /// This populates a GraphicsDeviceInformation instance and invokes PreparingDeviceSettings to /// allow users to change the settings. Then returns that GraphicsDeviceInformation. /// Throws NullReferenceException if users set GraphicsDeviceInformation.PresentationParameters to null. /// private GraphicsDeviceInformation DoPreparingDeviceSettings() { var gdi = new GraphicsDeviceInformation(); PrepareGraphicsDeviceInformation(gdi); var preparingDeviceSettingsHandler = PreparingDeviceSettings; if (preparingDeviceSettingsHandler != null) { // this allows users to overwrite settings through the argument var args = new PreparingDeviceSettingsEventArgs(gdi); preparingDeviceSettingsHandler(this, args); if (gdi.PresentationParameters == null || gdi.Adapter == null) throw new NullReferenceException("Members should not be set to null in PreparingDeviceSettingsEventArgs"); } return gdi; } #endregion #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { if (_graphicsDevice != null) { _graphicsDevice.Dispose(); _graphicsDevice = null; } } _disposed = true; EventHelpers.Raise(this, Disposed, EventArgs.Empty); } } #endregion partial void PlatformApplyChanges(); partial void PlatformPreparePresentationParameters(PresentationParameters presentationParameters); private void PreparePresentationParameters(PresentationParameters presentationParameters) { presentationParameters.BackBufferFormat = _preferredBackBufferFormat; presentationParameters.BackBufferWidth = _preferredBackBufferWidth; presentationParameters.BackBufferHeight = _preferredBackBufferHeight; presentationParameters.DepthStencilFormat = _preferredDepthStencilFormat; presentationParameters.IsFullScreen = _wantFullScreen; presentationParameters.HardwareModeSwitch = _hardwareModeSwitch; presentationParameters.PresentationInterval = _synchronizedWithVerticalRetrace ? PresentInterval.One : PresentInterval.Immediate; presentationParameters.DisplayOrientation = _game.Window.CurrentOrientation; #if WINDOWS && DIRECTX presentationParameters.DeviceWindowHandle = _game.Window.Hwnd; #else presentationParameters.DeviceWindowHandle = _game.Window.Handle; #endif if (_preferMultiSampling) { // always initialize MultiSampleCount to the maximum, if users want to overwrite // this they have to respond to the PreparingDeviceSettingsEvent and modify // args.GraphicsDeviceInformation.PresentationParameters.MultiSampleCount presentationParameters.MultiSampleCount = GraphicsDevice != null ? GraphicsDevice.GraphicsCapabilities.MaxMultiSampleCount : 32; } else { presentationParameters.MultiSampleCount = 0; } PlatformPreparePresentationParameters(presentationParameters); } private void PrepareGraphicsDeviceInformation(GraphicsDeviceInformation gdi) { gdi.Adapter = GraphicsAdapter.DefaultAdapter; gdi.GraphicsProfile = GraphicsProfile; var pp = new PresentationParameters(); PreparePresentationParameters(pp); gdi.PresentationParameters = pp; } /// /// Applies any pending property changes to the graphics device. /// public void ApplyChanges() { // If the device hasn't been created then create it now. if (_graphicsDevice == null) CreateDevice(); if (!_shouldApplyChanges) return; _shouldApplyChanges = false; _game.Window.SetSupportedOrientations(_supportedOrientations); // Allow for optional platform specific behavior. PlatformApplyChanges(); // populates a gdi with settings in this gdm and allows users to override them with // PrepareDeviceSettings event this information should be applied to the GraphicsDevice var gdi = DoPreparingDeviceSettings(); if (gdi.GraphicsProfile != GraphicsDevice.GraphicsProfile) { // if the GraphicsProfile changed we need to create a new GraphicsDevice DisposeGraphicsDevice(); CreateDevice(gdi); return; } GraphicsDevice.Reset(gdi.PresentationParameters); } private void DisposeGraphicsDevice() { _graphicsDevice.Dispose(); EventHelpers.Raise(this, DeviceDisposing, EventArgs.Empty); _graphicsDevice = null; } partial void PlatformInitialize(PresentationParameters presentationParameters); private void Initialize() { _game.Window.SetSupportedOrientations(_supportedOrientations); var presentationParameters = new PresentationParameters(); PreparePresentationParameters(presentationParameters); // Allow for any per-platform changes to the presentation. PlatformInitialize(presentationParameters); _initialized = true; } private void UpdateTouchPanel(object sender, EventArgs eventArgs) { TouchPanel.DisplayWidth = _graphicsDevice.PresentationParameters.BackBufferWidth; TouchPanel.DisplayHeight = _graphicsDevice.PresentationParameters.BackBufferHeight; TouchPanel.DisplayOrientation = _graphicsDevice.PresentationParameters.DisplayOrientation; } /// /// Toggles between windowed and fullscreen modes. /// /// /// Note that on platforms that do not support windowed modes this has no affect. /// public void ToggleFullScreen() { IsFullScreen = !IsFullScreen; ApplyChanges(); } private void OnPresentationChanged(object sender, PresentationEventArgs args) { _game.Platform.OnPresentationChanged(args.PresentationParameters); } /// /// The profile which determines the graphics feature level. /// public GraphicsProfile GraphicsProfile { get { return _graphicsProfile; } set { _shouldApplyChanges = true; _graphicsProfile = value; } } /// /// Returns the graphics device for this manager. /// public GraphicsDevice GraphicsDevice { get { return _graphicsDevice; } } /// /// Indicates the desire to switch into fullscreen mode. /// /// /// When called at startup this will automatically set fullscreen mode during initialization. If /// set after startup you must call ApplyChanges() for the fullscreen mode to be changed. /// Note that for some platforms that do not support windowed modes this property has no affect. /// public bool IsFullScreen { get { return _wantFullScreen; } set { _shouldApplyChanges = true; _wantFullScreen = value; } } /// /// Gets or sets the boolean which defines how window switches from windowed to fullscreen state. /// "Hard" mode(true) is slow to switch, but more effecient for performance, while "soft" mode(false) is vice versa. /// The default value is true. /// public bool HardwareModeSwitch { get { return _hardwareModeSwitch;} set { _shouldApplyChanges = true; _hardwareModeSwitch = value; } } /// /// Indicates the desire for a multisampled back buffer. /// /// /// When called at startup this will automatically set the MSAA mode during initialization. If /// set after startup you must call ApplyChanges() for the MSAA mode to be changed. /// public bool PreferMultiSampling { get { return _preferMultiSampling; } set { _shouldApplyChanges = true; _preferMultiSampling = value; } } /// /// Indicates the desired back buffer color format. /// /// /// When called at startup this will automatically set the format during initialization. If /// set after startup you must call ApplyChanges() for the format to be changed. /// public SurfaceFormat PreferredBackBufferFormat { get { return _preferredBackBufferFormat; } set { _shouldApplyChanges = true; _preferredBackBufferFormat = value; } } /// /// Indicates the desired back buffer height in pixels. /// /// /// When called at startup this will automatically set the height during initialization. If /// set after startup you must call ApplyChanges() for the height to be changed. /// public int PreferredBackBufferHeight { get { return _preferredBackBufferHeight; } set { _shouldApplyChanges = true; _preferredBackBufferHeight = value; } } /// /// Indicates the desired back buffer width in pixels. /// /// /// When called at startup this will automatically set the width during initialization. If /// set after startup you must call ApplyChanges() for the width to be changed. /// public int PreferredBackBufferWidth { get { return _preferredBackBufferWidth; } set { _shouldApplyChanges = true; _preferredBackBufferWidth = value; } } /// /// Indicates the desired depth-stencil buffer format. /// /// /// The depth-stencil buffer format defines the scene depth precision and stencil bits available for effects during rendering. /// When called at startup this will automatically set the format during initialization. If /// set after startup you must call ApplyChanges() for the format to be changed. /// public DepthFormat PreferredDepthStencilFormat { get { return _preferredDepthStencilFormat; } set { _shouldApplyChanges = true; _preferredDepthStencilFormat = value; } } /// /// Indicates the desire for vsync when presenting the back buffer. /// /// /// Vsync limits the frame rate of the game to the monitor referesh rate to prevent screen tearing. /// When called at startup this will automatically set the vsync mode during initialization. If /// set after startup you must call ApplyChanges() for the vsync mode to be changed. /// public bool SynchronizeWithVerticalRetrace { get { return _synchronizedWithVerticalRetrace; } set { _shouldApplyChanges = true; _synchronizedWithVerticalRetrace = value; } } /// /// Indicates the desired allowable display orientations when the device is rotated. /// /// /// This property only applies to mobile platforms with automatic display rotation. /// When called at startup this will automatically apply the supported orientations during initialization. If /// set after startup you must call ApplyChanges() for the supported orientations to be changed. /// public DisplayOrientation SupportedOrientations { get { return _supportedOrientations; } set { _shouldApplyChanges = true; _supportedOrientations = value; } } } }