A D3D11-aware WinForms control for SlimDX
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

506 lines
15 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. using SlimDX.Direct3D11;
  2. using SlimDX.DXGI;
  3. using System;
  4. using System.ComponentModel;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Drawing;
  7. using System.Windows.Forms;
  8. using Device = SlimDX.Direct3D11.Device;
  9. using Resource = SlimDX.Direct3D11.Resource;
  10. namespace SlimDX.Windows {
  11. /// <summary>
  12. /// D3D11-aware WinForms control.
  13. /// The WinForms designer will always call the default constructor.
  14. /// Inherit from this class and call one of its specialized constructors
  15. /// to create a Direct3D11 device or swapchain with custom properties.
  16. /// </summary>
  17. public partial class D3D11Control : UserControl {
  18. readonly bool designMode;
  19. Device device;
  20. SwapChain swapChain;
  21. SwapChainDescription? swapChainDescription;
  22. RenderTargetView renderTargetView;
  23. Texture2D depthStencilBuffer;
  24. DepthStencilView depthStencilView;
  25. RasterizerStateDescription rsDescription = new RasterizerStateDescription() {
  26. CullMode = CullMode.Back,
  27. FillMode = FillMode.Solid,
  28. DepthBias = 0,
  29. DepthBiasClamp = 0,
  30. SlopeScaledDepthBias = 0,
  31. IsFrontCounterclockwise = false,
  32. IsDepthClipEnabled = true,
  33. IsAntialiasedLineEnabled = false,
  34. IsScissorEnabled = false,
  35. IsMultisampleEnabled = false
  36. };
  37. /// <summary>
  38. /// Occurs when the aspect ratio of the viewport has changed.
  39. /// </summary>
  40. [Description("Occurs when the aspect ratio of the viewport has changed.")]
  41. public event EventHandler AspectRatioChanged;
  42. /// <summary>
  43. /// Gets the virtual adapter used to perform rendering and create resources.
  44. /// </summary>
  45. /// <exception cref="ObjectDisposedException">
  46. /// The object has been disposed.
  47. /// </exception>
  48. [Browsable(false)]
  49. public Device Device {
  50. get {
  51. CheckDisposed();
  52. return device;
  53. }
  54. }
  55. /// <summary>
  56. /// Gets the <c>DeviceContext</c> instance which generates rendering commands.
  57. /// </summary>
  58. /// <exception cref="ObjectDisposedException">
  59. /// The object has been disposed.
  60. /// </exception>
  61. [Browsable(false)]
  62. public DeviceContext Context {
  63. get {
  64. CheckDisposed();
  65. return Device.ImmediateContext;
  66. }
  67. }
  68. /// <summary>
  69. /// Gets the <c>SwapChain</c> instance associated with the device.
  70. /// </summary>
  71. /// <exception cref="ObjectDisposedException">
  72. /// The object has been disposed.
  73. /// </exception>
  74. [Browsable(false)]
  75. public SwapChain SwapChain {
  76. get {
  77. CheckDisposed();
  78. return swapChain;
  79. }
  80. }
  81. /// <summary>
  82. /// Gets the <c>RenderTargetView</c> instance associated with the output merger.
  83. /// </summary>
  84. /// <exception cref="ObjectDisposedException">
  85. /// The object has been disposed.
  86. /// </exception>
  87. [Browsable(false)]
  88. public RenderTargetView RenderTargetView {
  89. get {
  90. CheckDisposed();
  91. return renderTargetView;
  92. }
  93. }
  94. /// <summary>
  95. /// Gets the <c>DepthStencilView</c> instance associated with the output merger.
  96. /// </summary>
  97. /// <exception cref="ObjectDisposedException">
  98. /// The object has been disposed.
  99. /// </exception>
  100. [Browsable(false)]
  101. public DepthStencilView DepthStencilView {
  102. get {
  103. CheckDisposed();
  104. return depthStencilView;
  105. }
  106. }
  107. /// <summary>
  108. /// Indicates whether the viewport will automatically size itself when the control is resized.
  109. /// </summary>
  110. [Description("Indicates whether the viewport will automatically size itself when the control is resized.")]
  111. [DefaultValue(true)]
  112. public bool AutoAdjustViewPort {
  113. get;
  114. set;
  115. }
  116. /// <summary>
  117. /// Gets the aspect ratio of this <c>D3D11Control</c> instance.
  118. /// </summary>
  119. [Description("The aspect ratio of the client area of this D3D11Control instance.")]
  120. [Browsable(false)]
  121. public float AspectRatio {
  122. get {
  123. CheckDisposed();
  124. return ClientSize.Width / (float)ClientSize.Height;
  125. }
  126. }
  127. /// <summary>
  128. /// Gets or sets which triangles are to be culled.
  129. /// </summary>
  130. [Description("Indicates which triangles are to be culled.")]
  131. [DefaultValue(CullMode.Back)]
  132. public CullMode CullMode {
  133. get {
  134. return rsDescription.CullMode;
  135. }
  136. set {
  137. rsDescription.CullMode = value;
  138. if (!designMode)
  139. UpdateRasterizerState();
  140. }
  141. }
  142. /// <summary>
  143. /// Gets or sets the fill mode to use when rendering.
  144. /// </summary>
  145. [Description("Determines the fill mode to use when rendering.")]
  146. [DefaultValue(FillMode.Solid)]
  147. public FillMode FillMode {
  148. get {
  149. return rsDescription.FillMode;
  150. }
  151. set {
  152. rsDescription.FillMode = value;
  153. if (!designMode)
  154. UpdateRasterizerState();
  155. }
  156. }
  157. /// <summary>
  158. /// Determines if a triangle is front- or back-facing.
  159. /// </summary>
  160. /// <remarks>
  161. /// If this parameter is true, then a triangle will be considered front-facing if its
  162. /// vertices are counter-clockwise on the render target and considered back-facing if they
  163. /// are clockwise. If this parameter is false then the opposite is true.
  164. /// </remarks>
  165. [Description("Determines if a triangle is front- or back-facing.")]
  166. [DefaultValue(false)]
  167. public bool FrontCounterClockwise {
  168. get {
  169. return rsDescription.IsFrontCounterclockwise;
  170. }
  171. set {
  172. rsDescription.IsFrontCounterclockwise = value;
  173. if (!designMode)
  174. UpdateRasterizerState();
  175. }
  176. }
  177. /// <summary>
  178. /// Gets or sets a value indicating whether vertical synchronization (vsync) is active.
  179. /// </summary>
  180. [Description("Indicates whether vertical synchronization should be used.")]
  181. [DefaultValue(false)]
  182. public bool VSync {
  183. get;
  184. set;
  185. }
  186. /// <summary>
  187. /// Gets the <c>CreateParams</c> instance for this <c>D3D11Control</c>.
  188. /// </summary>
  189. protected override CreateParams CreateParams {
  190. get {
  191. const int CS_VREDRAW = 0x1;
  192. const int CS_HREDRAW = 0x2;
  193. const int CS_OWNDC = 0x20;
  194. var cp = base.CreateParams;
  195. // Setup necessary class style on windows.
  196. cp.ClassStyle |= CS_VREDRAW | CS_HREDRAW | CS_OWNDC;
  197. return cp;
  198. }
  199. }
  200. /// <summary>
  201. /// Creates a new instance of the D3D11Control class.
  202. /// </summary>
  203. /// <exception cref="Direct3D11Exception">
  204. /// The Direct3D device could not be created. Use the ResultCode property to obtain the
  205. /// specific error condition.
  206. /// </exception>
  207. public D3D11Control()
  208. : this(DriverType.Hardware, DeviceCreationFlags.None, null, null) {
  209. }
  210. /// <summary>
  211. /// Creates a new instance of the D3D11Controll class with the specified device and swapchain
  212. /// properties.
  213. /// </summary>
  214. /// <param name="type">
  215. /// The type of device to create.
  216. /// </param>
  217. /// <param name="flags">
  218. /// A list of runtime layers to enable.
  219. /// </param>
  220. /// <param name="featureLevels">
  221. /// A list of feature levels which determine the order of feature levels to attempt to create.
  222. /// </param>
  223. /// <param name="swapChainDescription">
  224. /// The properties to use for creating the swapchain.
  225. /// </param>
  226. /// <exception cref="Direct3D11Exception">
  227. /// The requested device could not be created. Use the ResultCode property to obtain the
  228. /// specific error condition.
  229. /// </exception>
  230. public D3D11Control(DriverType type, DeviceCreationFlags flags, FeatureLevel[] featureLevels,
  231. SwapChainDescription? swapChainDescription) : base() {
  232. // Setup control styles for owner draw.
  233. SetStyle(ControlStyles.Opaque | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint,
  234. true);
  235. DoubleBuffered = false;
  236. AutoAdjustViewPort = true;
  237. designMode = DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime;
  238. if (!designMode) {
  239. // Create the D3D11 device.
  240. device = new Device(type, flags, featureLevels);
  241. // Defer swapchain creation until the control's handle has been created.
  242. this.swapChainDescription = swapChainDescription;
  243. }
  244. InitializeComponent();
  245. }
  246. /// <summary>
  247. /// Resizes the viewport to the specified dimensions.
  248. /// </summary>
  249. /// <param name="s">
  250. /// The width and height to resize the viewport to, in pixels.
  251. /// </param>
  252. public void ResizeViewport(Size s) {
  253. var current = Context.Rasterizer.GetViewports();
  254. var viewport = new Viewport(0, 0, s.Width, s.Height);
  255. Context.Rasterizer.SetViewports(viewport);
  256. // See if we need to raise an AspectRatioChanged event.
  257. if (current.Length > 0) {
  258. var oldAspectRatio = current[0].Width / current[0].Height;
  259. if (!NearlyEqual(AspectRatio, oldAspectRatio)) {
  260. OnAspectRatioChanged(EventArgs.Empty);
  261. }
  262. } else {
  263. OnAspectRatioChanged(EventArgs.Empty);
  264. }
  265. }
  266. /// <summary>
  267. /// Swaps the front and back buffers, presenting the rendered scene to the screen.
  268. /// </summary>
  269. public void Present() {
  270. swapChain.Present(VSync ? 1 : 0, PresentFlags.None);
  271. }
  272. /// <summary>
  273. /// Clears the back buffer and optionally the depth- and stencil buffers.
  274. /// </summary>
  275. /// <param name="color">
  276. /// The color to fill the back buffer with.
  277. /// </param>
  278. /// <param name="clearDepthStencil">
  279. /// true to clear the depth- and stencil buffers; otherwise false.
  280. /// </param>
  281. public void Clear(Color color, bool clearDepthStencil = true) {
  282. Context.ClearRenderTargetView(renderTargetView, color);
  283. if (clearDepthStencil) {
  284. Context.ClearDepthStencilView(depthStencilView,
  285. DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0);
  286. }
  287. }
  288. /// <summary>
  289. /// Raises the System.Windows.Forms.Control.HandleCreated event.
  290. /// </summary>
  291. /// <param name="e">
  292. /// An System.EventArgs that contains the event data.
  293. /// </param>
  294. protected override void OnHandleCreated(EventArgs e) {
  295. base.OnHandleCreated(e);
  296. if (designMode)
  297. return;
  298. // If no swapchain description was provided, use default values.
  299. var description = swapChainDescription.HasValue ? swapChainDescription.Value :
  300. new SwapChainDescription() {
  301. BufferCount = 1,
  302. Usage = Usage.RenderTargetOutput,
  303. IsWindowed = true,
  304. ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format.R8G8B8A8_UNorm),
  305. SampleDescription = new SampleDescription(1, 0),
  306. Flags = SwapChainFlags.AllowModeSwitch,
  307. SwapEffect = SwapEffect.Discard
  308. };
  309. description.OutputHandle = Handle;
  310. // The swapchain must be created with the Factory instance that was used to create the
  311. // device.
  312. swapChain = new SwapChain(device.Factory, device, description);
  313. // Simply call OnResize to setup renderTargetView and Depth + Stencil Buffers.
  314. OnResize(EventArgs.Empty);
  315. }
  316. /// <summary>
  317. /// Raises the System.Windows.Forms.Control.HandleDestroyed event.
  318. /// </summary>
  319. /// <param name="e">
  320. /// An System.EventArgs that contains the event data.
  321. /// </param>
  322. protected override void OnHandleDestroyed(EventArgs e) {
  323. base.OnHandleDestroyed(e);
  324. ReleaseCOMObjects(true);
  325. }
  326. /// <summary>
  327. /// Raises the System.Windows.Forms.Control.Resize event.
  328. /// </summary>
  329. /// <param name="e">
  330. /// An System.EventArgs that contains the event data.
  331. /// </param>
  332. protected override void OnResize(EventArgs e) {
  333. base.OnResize(e);
  334. // If the form is minimized, OnResize is triggered with a client-size of (0,0).
  335. if (ClientSize.IsEmpty)
  336. return;
  337. if (swapChain == null)
  338. return;
  339. ReleaseCOMObjects(false);
  340. // Resize the back buffer.
  341. swapChain.ResizeBuffers(1, 0, 0, Format.R8G8B8A8_UNorm, SwapChainFlags.AllowModeSwitch);
  342. // Bind the back buffer to the output merger stage of the pipeline so that Direct3D can
  343. // render onto it. FromSwapChain increases the reference count of the swapChain object, so
  344. // wrap it in a using statement to keep the reference count at 1.
  345. using (var resource = Resource.FromSwapChain<Texture2D>(swapChain, 0))
  346. renderTargetView = new RenderTargetView(device, resource);
  347. // Create the depth and stencil buffers.
  348. var depthStencilDesc = new Texture2DDescription {
  349. Width = ClientSize.Width,
  350. Height = ClientSize.Height,
  351. MipLevels = 1,
  352. ArraySize = 1,
  353. Format = Format.D24_UNorm_S8_UInt,
  354. SampleDescription = new SampleDescription(1, 0),
  355. Usage = ResourceUsage.Default,
  356. BindFlags = BindFlags.DepthStencil,
  357. CpuAccessFlags = CpuAccessFlags.None,
  358. OptionFlags = ResourceOptionFlags.None
  359. };
  360. depthStencilBuffer = new Texture2D(device, depthStencilDesc);
  361. depthStencilView = new DepthStencilView(device, depthStencilBuffer);
  362. Context.OutputMerger.SetTargets(depthStencilView, renderTargetView);
  363. // Adjust the viewport
  364. if (AutoAdjustViewPort)
  365. ResizeViewport(ClientSize);
  366. }
  367. /// <summary>
  368. /// Raises the System.Windows.Forms.Control.Paint event.
  369. /// </summary>
  370. /// <param name="e">
  371. /// A System.Windows.Forms.PaintEventArgs that contains the event data.
  372. /// </param>
  373. protected override void OnPaint(PaintEventArgs e) {
  374. if (designMode) {
  375. e.Graphics.Clear(BackColor);
  376. } else {
  377. base.OnPaint(e);
  378. swapChain.Present(0, PresentFlags.None);
  379. }
  380. }
  381. /// <summary>
  382. /// Raises the AspectRatioChanged event.
  383. /// </summary>
  384. /// <param name="e">
  385. /// An System.EventArgs that contains the event data.
  386. /// </param>
  387. protected virtual void OnAspectRatioChanged(EventArgs e) {
  388. // Best practice.
  389. var handler = AspectRatioChanged;
  390. if(handler != null)
  391. handler(this, e);
  392. }
  393. /// <summary>
  394. /// Throws an ObjectDisposedException if the instance is in the disposed state.
  395. /// </summary>
  396. protected void CheckDisposed() {
  397. if (IsDisposed) {
  398. throw new ObjectDisposedException(GetType().FullName);
  399. }
  400. }
  401. /// <summary>
  402. /// Returns a value indicating whether this instance and a specified System.Single object
  403. /// represent the same value.
  404. /// </summary>
  405. /// <param name="a">
  406. /// The first value to compare.
  407. /// </param>
  408. /// <param name="b">
  409. /// The second value to compare.
  410. /// </param>
  411. /// <param name="epsilon">
  412. /// The epsilon value to use.
  413. /// </param>
  414. /// <returns>
  415. /// true if the values are considered equal; otherwise, false.
  416. /// </returns>
  417. [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
  418. static bool NearlyEqual(float a, float b, float epsilon = .0000001f) {
  419. var absA = Math.Abs(a);
  420. var absB = Math.Abs(b);
  421. var diff = Math.Abs(a - b);
  422. if (a == b) // shortcut, handles infinities
  423. return true;
  424. if (a == 0 || b == 0 || diff < float.Epsilon) {
  425. // a or b is zero or both are extremely close to it
  426. // relative error is less meaningful here
  427. return diff < epsilon;
  428. }
  429. // use relative error
  430. return diff / (absA + absB) < epsilon;
  431. }
  432. /// <summary>
  433. /// Releases various D3D COM resources.
  434. /// </summary>
  435. /// <param name="deviceAndSwapChain">
  436. /// true to release the device and swapchain objects; otherwise false.
  437. /// </param>
  438. void ReleaseCOMObjects(bool deviceAndSwapChain) {
  439. if (renderTargetView != null) {
  440. renderTargetView.Dispose();
  441. renderTargetView = null;
  442. }
  443. if (depthStencilView != null) {
  444. depthStencilView.Dispose();
  445. depthStencilView = null;
  446. }
  447. if (depthStencilBuffer != null) {
  448. depthStencilBuffer.Dispose();
  449. depthStencilBuffer = null;
  450. }
  451. if (deviceAndSwapChain) {
  452. if (swapChain != null) {
  453. swapChain.Dispose();
  454. swapChain = null;
  455. }
  456. if (device != null) {
  457. if (Context.Rasterizer.State != null) {
  458. Context.Rasterizer.State.Dispose();
  459. }
  460. device.Dispose();
  461. device = null;
  462. }
  463. }
  464. }
  465. /// <summary>
  466. /// Updates the rasterizer state for the rasterizer stage of the pipeline.
  467. /// </summary>
  468. void UpdateRasterizerState() {
  469. var rs = RasterizerState.FromDescription(device, rsDescription);
  470. var oldRs = Context.Rasterizer.State;
  471. Context.Rasterizer.State = rs;
  472. if (oldRs != null) {
  473. oldRs.Dispose();
  474. }
  475. }
  476. }
  477. }