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
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();
|
|
}
|
|
}
|
|
}
|
|
}
|