#region CPL License /* Nuclex Framework Copyright (C) 2002-2009 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.Windows.Forms; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using DeviceEventHandler = System.EventHandler; namespace Nuclex.Testing.Xna { /// Helper for unit tests requiring a mocked graphics device /// /// This doesn't actually mock the graphics device, but creates a real graphics /// device on an invisible window. Tests have shown this method to be fast /// enough for usage in a unit test. /// public class MockedGraphicsDeviceService : IGraphicsDeviceService { /// Will be triggered when the graphics device has been created public event DeviceEventHandler DeviceCreated; /// /// Will be triggered when the graphics device is about to be destroyed /// public event DeviceEventHandler DeviceDisposing; /// /// Will be triggered when the graphics device has completed a reset /// public event DeviceEventHandler DeviceReset; /// /// Will be triggered when the graphics device is about to reset itself /// public event DeviceEventHandler DeviceResetting; #region class GraphicsDeviceKeeper /// Keeps a graphics device alive for RAII-like usage /// /// RAII means "Resource Acquisition Is Initialization" and is a very widespread /// pattern in languages with deterministic finalization (read: not .NET). /// private class GraphicsDeviceKeeper : IDisposable { /// Initializes a new graphics device keeper /// /// Dummy graphics device service for whose graphics device the keeper /// will be responsible /// public GraphicsDeviceKeeper(MockedGraphicsDeviceService dummyService) { this.dummyService = dummyService; } /// Immediately releases all resources owned by the instancer public void Dispose() { if(this.dummyService != null) { this.dummyService.DestroyDevice(); this.dummyService = null; } } /// /// Dummy graphics device service in which the graphics device needs to be /// destroyed when the keeper is disposed /// private MockedGraphicsDeviceService dummyService; } #endregion // classs GraphicsDeviceKeeper /// Initializs a new mocked graphics device service public MockedGraphicsDeviceService() : this(DeviceType.NullReference) { } /// Initializs a new mocked graphics device service /// Type of graphics device that will be created public MockedGraphicsDeviceService(DeviceType deviceType) : this(deviceType, GraphicsProfile.Reach) { } /// Initializs a new mocked graphics device service /// Type of graphics device that will be created /// Profile the graphics device will be initialized for public MockedGraphicsDeviceService( DeviceType deviceType, GraphicsProfile graphicsProfile ) { this.deviceType = deviceType; this.graphicsProfile = graphicsProfile; this.serviceContainer = new GameServiceContainer(); this.serviceContainer.AddService(typeof(IGraphicsDeviceService), this); } /// Graphics device provided by the graphics device service public GraphicsDevice GraphicsDevice { get { return this.dummyGraphicsDevice; } } /// /// A service provider containing the mocked graphics device service /// public IServiceProvider ServiceProvider { get { return this.serviceContainer; } } /// Creates a new graphics device /// /// An object implementing IDisposable that will destroy the graphics device /// again as soon as its Dispose() method is called. /// /// /// /// Make sure to call DestroyGraphicsDevice() either manually, /// or by disposing the returned object. A typical usage of this method is /// shown in the following code. /// /// /// /// using(IDisposable keeper = CreateDevice()) { /// GraphicsDevice.DoSomethingThatCouldFail(); /// } /// /// /// public IDisposable CreateDevice() { this.emptyPresentationParameters = new PresentationParameters(); this.invisibleRenderWindow = new Form(); try { this.invisibleRenderWindow.Visible = false; this.invisibleRenderWindow.ShowInTaskbar = false; // Do not minimize, the GraphicsDevice doesn't like that! IntPtr renderWindowHandle = this.invisibleRenderWindow.Handle; this.emptyPresentationParameters.DeviceWindowHandle = renderWindowHandle; this.emptyPresentationParameters.IsFullScreen = false; GraphicsAdapter.UseReferenceDevice = (this.deviceType != DeviceType.Hardware); this.dummyGraphicsDevice = new GraphicsDevice( GraphicsAdapter.DefaultAdapter, this.graphicsProfile, this.emptyPresentationParameters ); OnDeviceCreated(); return new GraphicsDeviceKeeper(this); } catch(InvalidOperationException exception) { if(this.deviceType == DeviceType.Reference) { string message = "GraphicsDevice creation failed with InvalidOperationException when asking " + "for the reference rasterizer. DirectX Debug Runtime not installed?"; // Also write the message to stderr so it will be seen if the unit tests are // run and fail due to the missing DirectX debug runtime Console.Error.WriteLine(message); System.Diagnostics.Trace.WriteLine(message); disposeEverything(); throw new InvalidOperationException(message, exception); } disposeEverything(); throw; } catch(Exception) { disposeEverything(); throw; } } /// Destroys the created graphics device again public void DestroyDevice() { OnDeviceDisposing(); disposeEverything(); } /// Performs a graphics device reset public void ResetDevice() { OnDeviceResetting(); Viewport dummyViewport = new Viewport( 0, 0, this.invisibleRenderWindow.ClientSize.Width, this.invisibleRenderWindow.ClientSize.Height ); dummyViewport.MinDepth = 0.1f; dummyViewport.MaxDepth = 0.9f; this.dummyGraphicsDevice.Viewport = dummyViewport; this.dummyGraphicsDevice.Reset(); OnDeviceReset(); } /// /// Shuts down and disposes all resources used by the mocked graphics device service /// private void disposeEverything() { if(this.dummyGraphicsDevice != null) { this.dummyGraphicsDevice.Dispose(); this.dummyGraphicsDevice = null; } if(this.invisibleRenderWindow != null) { this.invisibleRenderWindow.Dispose(); this.invisibleRenderWindow = null; } } /// Fires the DeviceCreated event protected virtual void OnDeviceCreated() { if(this.DeviceCreated != null) { DeviceCreated(this, EventArgs.Empty); } } /// Fires the DeviceDisposing event protected virtual void OnDeviceDisposing() { if(this.DeviceDisposing != null) { DeviceDisposing(this, EventArgs.Empty); } } /// Fires the DeviceResetting event protected virtual void OnDeviceResetting() { if(this.DeviceResetting != null) { DeviceResetting(this, EventArgs.Empty); } } /// Fires the DeviceReset event protected virtual void OnDeviceReset() { if(this.DeviceReset != null) { DeviceReset(this, EventArgs.Empty); } } /// A dummy graphics device used to run the unit tests private GraphicsDevice dummyGraphicsDevice; /// /// Empty presentation parameters used to initialize the dummy graphics device /// private PresentationParameters emptyPresentationParameters; /// Invisible render window the dummy graphics device renders into private Form invisibleRenderWindow; /// A service container providing this service private GameServiceContainer serviceContainer; /// Type of device that will be created private DeviceType deviceType; /// Graphics profile the device will be created for private GraphicsProfile graphicsProfile; } } // namespace Nuclex.Testing.Xna