// 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;
}
}
}
}