MonoGameContentControl.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. using System;
  21. using System.ComponentModel;
  22. using System.Diagnostics;
  23. using System.Windows;
  24. using System.Windows.Controls;
  25. using System.Windows.Interop;
  26. using System.Windows.Media;
  27. using Microsoft.Xna.Framework;
  28. using Microsoft.Xna.Framework.Graphics;
  29. namespace MonoGame.WpfCore.MonoGameControls
  30. {
  31. public sealed class MonoGameContentControl : ContentControl, IDisposable
  32. {
  33. private static readonly MonoGameGraphicsDeviceService _graphicsDeviceService = new MonoGameGraphicsDeviceService();
  34. private int _instanceCount;
  35. private IMonoGameViewModel _viewModel;
  36. private readonly GameTime _gameTime = new GameTime();
  37. private readonly Stopwatch _stopwatch = new Stopwatch();
  38. private D3DImage _direct3DImage;
  39. private RenderTarget2D _renderTarget;
  40. private SharpDX.Direct3D9.Texture _renderTargetD3D9;
  41. private bool _isFirstLoad = true;
  42. private bool _isInitialized;
  43. public MonoGameContentControl()
  44. {
  45. if (DesignerProperties.GetIsInDesignMode(this))
  46. return;
  47. _instanceCount++;
  48. Loaded += OnLoaded;
  49. Unloaded += OnUnloaded;
  50. DataContextChanged += (sender, args) =>
  51. {
  52. _viewModel = args.NewValue as IMonoGameViewModel;
  53. if (_viewModel != null)
  54. _viewModel.GraphicsDeviceService = _graphicsDeviceService;
  55. };
  56. SizeChanged += (sender, args) => _viewModel?.SizeChanged(sender, args);
  57. }
  58. public static GraphicsDevice GraphicsDevice => _graphicsDeviceService?.GraphicsDevice;
  59. public bool IsDisposed { get; private set; }
  60. public void Dispose()
  61. {
  62. Dispose(true);
  63. GC.SuppressFinalize(this);
  64. }
  65. private void Dispose(bool disposing)
  66. {
  67. if (IsDisposed)
  68. return;
  69. _viewModel?.Dispose();
  70. _renderTarget?.Dispose();
  71. _renderTargetD3D9?.Dispose();
  72. _instanceCount--;
  73. if (_instanceCount <= 0)
  74. _graphicsDeviceService?.Dispose();
  75. IsDisposed = true;
  76. }
  77. ~MonoGameContentControl()
  78. {
  79. Dispose(false);
  80. }
  81. protected override void OnGotFocus(RoutedEventArgs e)
  82. {
  83. _viewModel?.OnActivated(this, EventArgs.Empty);
  84. base.OnGotFocus(e);
  85. }
  86. protected override void OnLostFocus(RoutedEventArgs e)
  87. {
  88. _viewModel?.OnDeactivated(this, EventArgs.Empty);
  89. base.OnLostFocus(e);
  90. }
  91. private void Start()
  92. {
  93. if(_isInitialized)
  94. return;
  95. if (Application.Current.MainWindow == null)
  96. throw new InvalidOperationException("The application must have a MainWindow");
  97. Application.Current.MainWindow.Closing += (sender, args) => _viewModel?.OnExiting(this, EventArgs.Empty);
  98. Application.Current.MainWindow.ContentRendered += (sender, args) =>
  99. {
  100. if (_isFirstLoad)
  101. {
  102. _graphicsDeviceService.StartDirect3D(Application.Current.MainWindow);
  103. _viewModel?.Initialize();
  104. _viewModel?.LoadContent();
  105. _isFirstLoad = false;
  106. }
  107. };
  108. _direct3DImage = new D3DImage();
  109. AddChild(new Image { Source = _direct3DImage, Stretch = Stretch.None });
  110. //_direct3DImage.IsFrontBufferAvailableChanged += OnDirect3DImageIsFrontBufferAvailableChanged;
  111. _renderTarget = CreateRenderTarget();
  112. CompositionTarget.Rendering += OnRender;
  113. _stopwatch.Start();
  114. _isInitialized = true;
  115. }
  116. protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
  117. {
  118. base.OnRenderSizeChanged(sizeInfo);
  119. // sometimes OnRenderSizeChanged happens before OnLoaded.
  120. Start();
  121. ResetBackBufferReference();
  122. }
  123. private void OnLoaded(object sender, RoutedEventArgs e)
  124. {
  125. Start();
  126. }
  127. private void OnUnloaded(object sender, RoutedEventArgs e)
  128. {
  129. _viewModel?.UnloadContent();
  130. if (_graphicsDeviceService != null)
  131. {
  132. CompositionTarget.Rendering -= OnRender;
  133. ResetBackBufferReference();
  134. _graphicsDeviceService.DeviceResetting -= OnGraphicsDeviceServiceDeviceResetting;
  135. }
  136. }
  137. private void OnGraphicsDeviceServiceDeviceResetting(object sender, EventArgs e)
  138. {
  139. ResetBackBufferReference();
  140. }
  141. private void ResetBackBufferReference()
  142. {
  143. if (DesignerProperties.GetIsInDesignMode(this))
  144. return;
  145. if (_renderTarget != null)
  146. {
  147. _renderTarget.Dispose();
  148. _renderTarget = null;
  149. }
  150. if (_renderTargetD3D9 != null)
  151. {
  152. _renderTargetD3D9.Dispose();
  153. _renderTargetD3D9 = null;
  154. }
  155. _direct3DImage.Lock();
  156. _direct3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero);
  157. _direct3DImage.Unlock();
  158. }
  159. private RenderTarget2D CreateRenderTarget()
  160. {
  161. var actualWidth = (int)ActualWidth;
  162. var actualHeight = (int)ActualHeight;
  163. if (actualWidth == 0 || actualHeight == 0)
  164. return null;
  165. if (GraphicsDevice == null)
  166. return null;
  167. var renderTarget = new RenderTarget2D(GraphicsDevice, actualWidth, actualHeight,
  168. false, SurfaceFormat.Bgra32, DepthFormat.Depth24Stencil8, 1,
  169. RenderTargetUsage.PlatformContents, true);
  170. var handle = renderTarget.GetSharedHandle();
  171. if (handle == IntPtr.Zero)
  172. throw new ArgumentException("Handle could not be retrieved");
  173. _renderTargetD3D9 = new SharpDX.Direct3D9.Texture(_graphicsDeviceService.Direct3DDevice, renderTarget.Width,
  174. renderTarget.Height,
  175. 1, SharpDX.Direct3D9.Usage.RenderTarget, SharpDX.Direct3D9.Format.A8R8G8B8,
  176. SharpDX.Direct3D9.Pool.Default, ref handle);
  177. using (var surface = _renderTargetD3D9.GetSurfaceLevel(0))
  178. {
  179. _direct3DImage.Lock();
  180. _direct3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface.NativePointer);
  181. _direct3DImage.Unlock();
  182. }
  183. return renderTarget;
  184. }
  185. private void OnRender(object sender, EventArgs e)
  186. {
  187. _gameTime.ElapsedGameTime = _stopwatch.Elapsed;
  188. _gameTime.TotalGameTime += _gameTime.ElapsedGameTime;
  189. _stopwatch.Restart();
  190. if (CanBeginDraw())
  191. {
  192. try
  193. {
  194. _direct3DImage.Lock();
  195. if (_renderTarget == null)
  196. _renderTarget = CreateRenderTarget();
  197. if (_renderTarget != null)
  198. {
  199. GraphicsDevice.SetRenderTarget(_renderTarget);
  200. SetViewport();
  201. _viewModel?.Update(_gameTime);
  202. _viewModel?.Draw(_gameTime);
  203. GraphicsDevice.Flush();
  204. _direct3DImage.AddDirtyRect(new Int32Rect(0, 0, (int)ActualWidth, (int)ActualHeight));
  205. }
  206. }
  207. finally
  208. {
  209. _direct3DImage.Unlock();
  210. GraphicsDevice.SetRenderTarget(null);
  211. }
  212. }
  213. }
  214. private bool CanBeginDraw()
  215. {
  216. // If we have no graphics device, we must be running in the designer.
  217. if (_graphicsDeviceService == null)
  218. return false;
  219. if (!_direct3DImage.IsFrontBufferAvailable)
  220. return false;
  221. // Make sure the graphics device is big enough, and is not lost.
  222. if (!HandleDeviceReset())
  223. return false;
  224. return true;
  225. }
  226. private void SetViewport()
  227. {
  228. // Many GraphicsDeviceControl instances can be sharing the same
  229. // GraphicsDevice. The device backbuffer will be resized to fit the
  230. // largest of these controls. But what if we are currently drawing
  231. // a smaller control? To avoid unwanted stretching, we set the
  232. // viewport to only use the top left portion of the full backbuffer.
  233. var width = Math.Max(1, (int)ActualWidth);
  234. var height = Math.Max(1, (int)ActualHeight);
  235. GraphicsDevice.Viewport = new Viewport(0, 0, width, height);
  236. }
  237. private bool HandleDeviceReset()
  238. {
  239. if (GraphicsDevice == null)
  240. return false;
  241. var deviceNeedsReset = false;
  242. switch (GraphicsDevice.GraphicsDeviceStatus)
  243. {
  244. case GraphicsDeviceStatus.Lost:
  245. // If the graphics device is lost, we cannot use it at all.
  246. return false;
  247. case GraphicsDeviceStatus.NotReset:
  248. // If device is in the not-reset state, we should try to reset it.
  249. deviceNeedsReset = true;
  250. break;
  251. }
  252. if (deviceNeedsReset)
  253. {
  254. _graphicsDeviceService.ResetDevice((int)ActualWidth, (int)ActualHeight);
  255. return false;
  256. }
  257. return true;
  258. }
  259. }
  260. }