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

using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Windows.Forms;
using Device = SlimDX.Direct3D11.Device;
using Resource = SlimDX.Direct3D11.Resource;
namespace SlimDX.Windows {
/// <summary>
/// D3D11-aware WinForms control.
/// The WinForms designer will always call the default constructor.
/// Inherit from this class and call one of its specialized constructors
/// to create a Direct3D11 device or swapchain with custom properties.
/// </summary>
public partial class D3D11Control : UserControl {
readonly bool designMode;
Device device;
SwapChain swapChain;
SwapChainDescription? swapChainDescription;
RenderTargetView renderTargetView;
Texture2D depthStencilBuffer;
DepthStencilView depthStencilView;
RasterizerStateDescription rsDescription = new RasterizerStateDescription() {
CullMode = CullMode.Back,
FillMode = FillMode.Solid,
DepthBias = 0,
DepthBiasClamp = 0,
SlopeScaledDepthBias = 0,
IsFrontCounterclockwise = false,
IsDepthClipEnabled = true,
IsAntialiasedLineEnabled = false,
IsScissorEnabled = false,
IsMultisampleEnabled = false
};
/// <summary>
/// Occurs when the aspect ratio of the viewport has changed.
/// </summary>
[Description("Occurs when the aspect ratio of the viewport has changed.")]
public event EventHandler AspectRatioChanged;
/// <summary>
/// Gets the virtual adapter used to perform rendering and create resources.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// The object has been disposed.
/// </exception>
[Browsable(false)]
public Device Device {
get {
CheckDisposed();
return device;
}
}
/// <summary>
/// Gets the <c>DeviceContext</c> instance which generates rendering commands.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// The object has been disposed.
/// </exception>
[Browsable(false)]
public DeviceContext Context {
get {
CheckDisposed();
return Device.ImmediateContext;
}
}
/// <summary>
/// Gets the <c>SwapChain</c> instance associated with the device.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// The object has been disposed.
/// </exception>
[Browsable(false)]
public SwapChain SwapChain {
get {
CheckDisposed();
return swapChain;
}
}
/// <summary>
/// Gets the <c>RenderTargetView</c> instance associated with the output merger.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// The object has been disposed.
/// </exception>
[Browsable(false)]
public RenderTargetView RenderTargetView {
get {
CheckDisposed();
return renderTargetView;
}
}
/// <summary>
/// Gets the <c>DepthStencilView</c> instance associated with the output merger.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// The object has been disposed.
/// </exception>
[Browsable(false)]
public DepthStencilView DepthStencilView {
get {
CheckDisposed();
return depthStencilView;
}
}
/// <summary>
/// Indicates whether the viewport will automatically size itself when the control is resized.
/// </summary>
[Description("Indicates whether the viewport will automatically size itself when the control is resized.")]
[DefaultValue(true)]
public bool AutoAdjustViewPort {
get;
set;
}
/// <summary>
/// Gets the aspect ratio of this <c>D3D11Control</c> instance.
/// </summary>
[Description("The aspect ratio of the client area of this D3D11Control instance.")]
[Browsable(false)]
public float AspectRatio {
get {
CheckDisposed();
return ClientSize.Width / (float)ClientSize.Height;
}
}
/// <summary>
/// Gets or sets which triangles are to be culled.
/// </summary>
[Description("Indicates which triangles are to be culled.")]
[DefaultValue(CullMode.Back)]
public CullMode CullMode {
get {
return rsDescription.CullMode;
}
set {
rsDescription.CullMode = value;
if (!designMode)
UpdateRasterizerState();
}
}
/// <summary>
/// Gets or sets the fill mode to use when rendering.
/// </summary>
[Description("Determines the fill mode to use when rendering.")]
[DefaultValue(FillMode.Solid)]
public FillMode FillMode {
get {
return rsDescription.FillMode;
}
set {
rsDescription.FillMode = value;
if (!designMode)
UpdateRasterizerState();
}
}
/// <summary>
/// Determines if a triangle is front- or back-facing.
/// </summary>
/// <remarks>
/// If this parameter is true, then a triangle will be considered front-facing if its
/// vertices are counter-clockwise on the render target and considered back-facing if they
/// are clockwise. If this parameter is false then the opposite is true.
/// </remarks>
[Description("Determines if a triangle is front- or back-facing.")]
[DefaultValue(false)]
public bool FrontCounterClockwise {
get {
return rsDescription.IsFrontCounterclockwise;
}
set {
rsDescription.IsFrontCounterclockwise = value;
if (!designMode)
UpdateRasterizerState();
}
}
/// <summary>
/// Gets or sets a value indicating whether vertical synchronization (vsync) is active.
/// </summary>
[Description("Indicates whether vertical synchronization should be used.")]
[DefaultValue(false)]
public bool VSync {
get;
set;
}
/// <summary>
/// Gets the <c>CreateParams</c> instance for this <c>D3D11Control</c>.
/// </summary>
protected override CreateParams CreateParams {
get {
const int CS_VREDRAW = 0x1;
const int CS_HREDRAW = 0x2;
const int CS_OWNDC = 0x20;
var cp = base.CreateParams;
// Setup necessary class style on windows.
cp.ClassStyle |= CS_VREDRAW | CS_HREDRAW | CS_OWNDC;
return cp;
}
}
/// <summary>
/// Creates a new instance of the D3D11Control class.
/// </summary>
/// <exception cref="Direct3D11Exception">
/// The Direct3D device could not be created. Use the ResultCode property to obtain the
/// specific error condition.
/// </exception>
public D3D11Control()
: this(DriverType.Hardware, DeviceCreationFlags.None, null, null) {
}
/// <summary>
/// Creates a new instance of the D3D11Controll class with the specified device and swapchain
/// properties.
/// </summary>
/// <param name="type">
/// The type of device to create.
/// </param>
/// <param name="flags">
/// A list of runtime layers to enable.
/// </param>
/// <param name="featureLevels">
/// A list of feature levels which determine the order of feature levels to attempt to create.
/// </param>
/// <param name="swapChainDescription">
/// The properties to use for creating the swapchain.
/// </param>
/// <exception cref="Direct3D11Exception">
/// The requested device could not be created. Use the ResultCode property to obtain the
/// specific error condition.
/// </exception>
public D3D11Control(DriverType type, DeviceCreationFlags flags, FeatureLevel[] featureLevels,
SwapChainDescription? swapChainDescription) : base() {
// Setup control styles for owner draw.
SetStyle(ControlStyles.Opaque | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint,
true);
DoubleBuffered = false;
AutoAdjustViewPort = true;
designMode = DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime;
if (!designMode) {
// Create the D3D11 device.
device = new Device(type, flags, featureLevels);
// Defer swapchain creation until the control's handle has been created.
this.swapChainDescription = swapChainDescription;
}
InitializeComponent();
}
/// <summary>
/// Resizes the viewport to the specified dimensions.
/// </summary>
/// <param name="s">
/// The width and height to resize the viewport to, in pixels.
/// </param>
public void ResizeViewport(Size s) {
var current = Context.Rasterizer.GetViewports();
var viewport = new Viewport(0, 0, s.Width, s.Height);
Context.Rasterizer.SetViewports(viewport);
// See if we need to raise an AspectRatioChanged event.
if (current.Length > 0) {
var oldAspectRatio = current[0].Width / current[0].Height;
if (!NearlyEqual(AspectRatio, oldAspectRatio)) {
OnAspectRatioChanged(EventArgs.Empty);
}
} else {
OnAspectRatioChanged(EventArgs.Empty);
}
}
/// <summary>
/// Swaps the front and back buffers, presenting the rendered scene to the screen.
/// </summary>
public void Present() {
swapChain.Present(VSync ? 1 : 0, PresentFlags.None);
}
/// <summary>
/// Clears the back buffer and optionally the depth- and stencil buffers.
/// </summary>
/// <param name="color">
/// The color to fill the back buffer with.
/// </param>
/// <param name="clearDepthStencil">
/// true to clear the depth- and stencil buffers; otherwise false.
/// </param>
public void Clear(Color color, bool clearDepthStencil = true) {
Context.ClearRenderTargetView(renderTargetView, color);
if (clearDepthStencil) {
Context.ClearDepthStencilView(depthStencilView,
DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0);
}
}
/// <summary>
/// Raises the System.Windows.Forms.Control.HandleCreated event.
/// </summary>
/// <param name="e">
/// An System.EventArgs that contains the event data.
/// </param>
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
if (designMode)
return;
// If no swapchain description was provided, use default values.
var description = swapChainDescription.HasValue ? swapChainDescription.Value :
new SwapChainDescription() {
BufferCount = 1,
Usage = Usage.RenderTargetOutput,
IsWindowed = true,
ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format.R8G8B8A8_UNorm),
SampleDescription = new SampleDescription(1, 0),
Flags = SwapChainFlags.AllowModeSwitch,
SwapEffect = SwapEffect.Discard
};
description.OutputHandle = Handle;
// The swapchain must be created with the Factory instance that was used to create the
// device.
swapChain = new SwapChain(device.Factory, device, description);
// Simply call OnResize to setup renderTargetView and Depth + Stencil Buffers.
OnResize(EventArgs.Empty);
}
/// <summary>
/// Raises the System.Windows.Forms.Control.HandleDestroyed event.
/// </summary>
/// <param name="e">
/// An System.EventArgs that contains the event data.
/// </param>
protected override void OnHandleDestroyed(EventArgs e) {
base.OnHandleDestroyed(e);
ReleaseCOMObjects(true);
}
/// <summary>
/// Raises the System.Windows.Forms.Control.Resize event.
/// </summary>
/// <param name="e">
/// An System.EventArgs that contains the event data.
/// </param>
protected override void OnResize(EventArgs e) {
base.OnResize(e);
// If the form is minimized, OnResize is triggered with a client-size of (0,0).
if (ClientSize.IsEmpty)
return;
if (swapChain == null)
return;
ReleaseCOMObjects(false);
// Resize the back buffer.
swapChain.ResizeBuffers(1, 0, 0, Format.R8G8B8A8_UNorm, SwapChainFlags.AllowModeSwitch);
// Bind the back buffer to the output merger stage of the pipeline so that Direct3D can
// render onto it. FromSwapChain increases the reference count of the swapChain object, so
// wrap it in a using statement to keep the reference count at 1.
using (var resource = Resource.FromSwapChain<Texture2D>(swapChain, 0))
renderTargetView = new RenderTargetView(device, resource);
// Create the depth and stencil buffers.
var depthStencilDesc = new Texture2DDescription {
Width = ClientSize.Width,
Height = ClientSize.Height,
MipLevels = 1,
ArraySize = 1,
Format = Format.D24_UNorm_S8_UInt,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Default,
BindFlags = BindFlags.DepthStencil,
CpuAccessFlags = CpuAccessFlags.None,
OptionFlags = ResourceOptionFlags.None
};
depthStencilBuffer = new Texture2D(device, depthStencilDesc);
depthStencilView = new DepthStencilView(device, depthStencilBuffer);
Context.OutputMerger.SetTargets(depthStencilView, renderTargetView);
// Adjust the viewport
if (AutoAdjustViewPort)
ResizeViewport(ClientSize);
}
/// <summary>
/// Raises the System.Windows.Forms.Control.Paint event.
/// </summary>
/// <param name="e">
/// A System.Windows.Forms.PaintEventArgs that contains the event data.
/// </param>
protected override void OnPaint(PaintEventArgs e) {
if (designMode) {
e.Graphics.Clear(BackColor);
} else {
base.OnPaint(e);
swapChain.Present(0, PresentFlags.None);
}
}
/// <summary>
/// Raises the AspectRatioChanged event.
/// </summary>
/// <param name="e">
/// An System.EventArgs that contains the event data.
/// </param>
protected virtual void OnAspectRatioChanged(EventArgs e) {
// Best practice.
var handler = AspectRatioChanged;
if(handler != null)
handler(this, e);
}
/// <summary>
/// Throws an ObjectDisposedException if the instance is in the disposed state.
/// </summary>
protected void CheckDisposed() {
if (IsDisposed) {
throw new ObjectDisposedException(GetType().FullName);
}
}
/// <summary>
/// Returns a value indicating whether this instance and a specified System.Single object
/// represent the same value.
/// </summary>
/// <param name="a">
/// The first value to compare.
/// </param>
/// <param name="b">
/// The second value to compare.
/// </param>
/// <param name="epsilon">
/// The epsilon value to use.
/// </param>
/// <returns>
/// true if the values are considered equal; otherwise, false.
/// </returns>
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
static bool NearlyEqual(float a, float b, float epsilon = .0000001f) {
var absA = Math.Abs(a);
var absB = Math.Abs(b);
var diff = Math.Abs(a - b);
if (a == b) // shortcut, handles infinities
return true;
if (a == 0 || b == 0 || diff < float.Epsilon) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < epsilon;
}
// use relative error
return diff / (absA + absB) < epsilon;
}
/// <summary>
/// Releases various D3D COM resources.
/// </summary>
/// <param name="deviceAndSwapChain">
/// true to release the device and swapchain objects; otherwise false.
/// </param>
void ReleaseCOMObjects(bool deviceAndSwapChain) {
if (renderTargetView != null) {
renderTargetView.Dispose();
renderTargetView = null;
}
if (depthStencilView != null) {
depthStencilView.Dispose();
depthStencilView = null;
}
if (depthStencilBuffer != null) {
depthStencilBuffer.Dispose();
depthStencilBuffer = null;
}
if (deviceAndSwapChain) {
if (swapChain != null) {
swapChain.Dispose();
swapChain = null;
}
if (device != null) {
if (Context.Rasterizer.State != null) {
Context.Rasterizer.State.Dispose();
}
device.Dispose();
device = null;
}
}
}
/// <summary>
/// Updates the rasterizer state for the rasterizer stage of the pipeline.
/// </summary>
void UpdateRasterizerState() {
var rs = RasterizerState.FromDescription(device, rsDescription);
var oldRs = Context.Rasterizer.State;
Context.Rasterizer.State = rs;
if (oldRs != null) {
oldRs.Dispose();
}
}
}
}