C# DirectX API Face-off: SlimDX vs SharpDX – Which should you choose?

A question I often see asked by beginning game programmers on the internet is:

I want to code my game in C#. Which DirectX wrapper API should I use?

As with most such things, all choices have their pros and cons. Here I will look at the two frameworks most currently in use at the time of writing: SlimDX and SharpDX.

Why choose C# for games and graphical applications?

Actually, as a general rule, I strongly advise all users to write their games in C++ where possible. Almost all commercial game development is done in C++, using C# decreases portability between platforms and incurs a small (perhaps 1-2%) performance hit due to the extra abstraction layer you will be using over the standard C++ DirectX API.

There are, however, some compelling reasons to use C#, and if you are in one of the categories below, read on:

  • you are a hobbyist developer writing code for fun, you know C#, and feel that C++ is too complicated (which is perfectly reasonable)
  • you want to quickly prototype a game and are using C# to save time
  • your environment prevents you from using C++

Obviously, if you’re a hobbyist coder, there is no need to get embroiled in C++, and if you’re a commercial outfit, using C# to prototype will get you up and running with less boilerplate code and memory management woes than C++. In all other cases, C++ is the preferred choice.

Note that in this article, I shall not be assessing the relative performance of SlimDX and SharpDX against each other, or against the native C++ DirectX API.

English: The wordmark of Microsoft's DirectX-A...

(Photo credit: Wikipedia)

SlimDX and SharpDX on paper

So what are these APIs? Well, SlimDX is an OOP-ified version of the C++ DirectX API written in C#, with some tweaks. It is updated as and when Microsoft release updates to DirectX itself. SharpDX uses a different approach, instead generating C# wrapper calls directly from the C++ DirectX API header files, meaning that unlike SlimDX, every method, property etc. available in the C++ DirectX API is also available in SharpDX.

SlimDX was the first product to arrive, and as such SharpDX has tried to encourage users to switch by making it largely syntactically compatible with SlimDX, using the same object and field names in most places etc.

Both products are free for commercial use.

Feature comparison

The following table shows some important comparisons between the two APIs:

  SlimDX SharpDX
Paradigm Custom wrapper API Thin wrapper over DirectX API
Auto-generated from DirectX SDK headers
Latest Release January 2012
(4.0.13.43)
August 2013
(2.5.0)
License MIT MIT
Platform Compatibility Windows XP
Windows Vista
Windows Server 2003
Windows Server 2008
Windows 7
Windows XP
Windows Vista
Windows 7
Windows 8
Windows Phone 8
Native Libraries .NET 2.0 (x86/x64)
.NET 4.0 (x86/x64)
.NET 2.0-4.5
.NET 4.5 Core (Windows 8 Metro)
Mono 2.10 and later
Any CPU Support Yes but requires GAC install Yes
Direct2D 1.1 / DXGI 1.2 available in Windows 7 No No
Higher level API No Yes
Documentation state Getting Started Guide
Programming Guide
Deployment Guide
Class Library Reference
Class Library Reference (incomplete)
No tutorials
Use of MSDN DirectX docs recommended
NuGet Packages available Yes Yes
Notable users/games Spiderman: Web of Shadows
Zipper Interactive (SOCOM, MAG etc.)
LizardTech
Star Wars: The Force Unleashed
Operation Flashpoint: Dragon Rising
Unknown
Compatible game engines Unknown MonoGame
ANX Framework
Axiom3D
Delta Engine

If you are a professional user, the first thing to note is that SlimDX has not been updated since the start of 2012. In fairness, neither has DirectX itself to any major degree, but it would appear that development has stopped on SlimDX and the web site no longer seems to be maintained as it contains a number of out-of-date pieces of information.

On the other hand, the documentation for SharpDX – if you could call it that – is awful, and as you will see below I had quite a headache preparing some example code for this article. Production of code for SlimDX was quite smooth thanks to reasonable-to-good documentation on the web site.

SlimDX would appear to have the larger user base at present, so it may be easier to find help online from other users for SlimDX, however there is plenty of knowledge available from users of both frameworks. Despite the lack of tutorials on the official sites (SlimDX only provides 3 or 4 very basic tutorials), note that tutorials for both products can of course be found on various independent web sites.

In terms of features, only SharpDX supports Metro apps, Windows Phone 8, builds for Mono and .NET Any CPU support without requiring assemblies to be installed into the GAC, which is a potential minor inconvenience for the end-users of your software. SharpDX is also the only library of the two to offer a higher-level API – the SharpDX Toolkit – which abstracts away some of the complexity and boilerplate code.

Note that neither library provides support for Xbox 360 development (use XNA for that, it is your only choice).

In terms of trust, SlimDX has a proven track record, having been used in a number of high-profile video game titles from recent years. SharpDX does not have this track record yet, however most of the upcoming breed of C# “game engines” are based on SharpDX, so we will likely see a rise in use soon.

One important difference between the two APIs is the way COM objects are managed. SlimDX uses an object table to track the lifetime of COM objects and map them to SlimDX objects. It deals with duplicate instances for you (reference counting) but you must dispose of any objects you create. SharpDX leaves it up to the programmer to manage object lifetime, providing no middle management layer. Both have their pros and cons, and really, both go against the grain of .NET programming where you shouldn’t normally need to care about object lifetimes; however it is worth noting that the SlimDX solution – while making life for the user more consistent – does impose a small performance penalty.

Now let’s have a look at what it’s like to write code using these APIs.

The example
Figure 1. A clone of the MSDN Direct2D QuickStart demo application using SharpDX

Figure 1. A clone of the MSDN Direct2D QuickStart demo application using SharpDX

Here I will show how to create an exact clone of the MSDN Direct2D QuickStart application (see MSDN: Creating A Simple Direct2D Application and MSDN: Direct2D QuickStart (Windows 7 versions linked, a Windows 8 tutorial is also available via the links but the code is incorrect at the time of writing). This is a hideously bloated mess which takes over 400 lines to implement in C++, producing the image shown in figure 1.

For this example I will assume you have a basic knowledge of the components of DirectX and some basic experience writing simple DirectX applications. I shall use the latest techniques from Direct2D 1.1 – that is to say, using DXGI to deal with creating the render target, rather than the out-dated HwndRenderTarget interface from Direct2D 1.0. More details can be found in my articleDirect2D 1.1 Migration Guide for Windows 7 Developers.

Please note that the example is purely for educational purposes and should not be used as a comparison between the coding complexity of the two APIs, especially since most of it is initialization boilerplate code.

Let’s get cracking.

Installation

Both frameworks have NuGet packages available, so just choose Manage NuGet packages… in Visual Studio to install them the easy way.

Namespaces

SlimDX and SharpDX use their own namespaces, unsurprisingly named SlimDX and SharpDXrespectively. The pile of using statements at the very top of the code looks like this:

SlimDX
using System;
using System.Drawing;
using System.Windows.Forms;
 
using SlimDX;
using SlimDX.DXGI;
using SlimDX.Direct3D11;
using SlimDX.Direct2D;
using SlimDX.Windows;
using Device = SlimDX.Direct3D11.Device;
using FactoryD2D = SlimDX.Direct2D.Factory;
using FactoryDXGI = SlimDX.DXGI.Factory;
SharpDX
using System;
using System.Windows.Forms;
 
using SharpDX;
using SharpDX.DXGI;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.Direct2D1;
using SharpDX.Windows;
using Device = SharpDX.Direct3D11.Device;
using FactoryD2D = SharpDX.Direct2D1.Factory;
using FactoryDXGI = SharpDX.DXGI.Factory1;

SlimDX requires System.Drawing because the System.Drawing.Color object is used to specify colours. SharpDX uses its own SharpDX.Color struct for this.

The last three definitions for both APIs resolve name conflicts with the imported namespaces for objects we want to use. Note that SharpDX uses DXGI.Factory1 rather than DXGI.Factory to indicate the use of DXGI 1.2 as released in conjunction with Direct2D 1.1.

Form, device, swap chain, back buffer and render target initialization

This code goes at the start of Main() and is the same for both APIs:

// Create render target window
var form = new RenderForm( "MSDN Direct2D Demo clone in C# - written by Katy Coe" );
 
// Create swap chain description
var swapChainDesc = new SwapChainDescription()
{
     BufferCount = 2,
     Usage = Usage.RenderTargetOutput,
     OutputHandle = form.Handle,
     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
};
 
// Create swap chain and Direct3D device
// The BgraSupport flag is needed for Direct2D compatibility otherwise RenderTarget.FromDXGI will fail!
Device device;
SwapChain swapChain;
Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.BgraSupport, swapChainDesc, out device, out swapChain);
 
// Get back buffer in a Direct2D-compatible format (DXGI surface)
Surface backBuffer = Surface.FromSwapChain(swapChain, 0);

Both APIs provide a RenderForm object which derives from System.Windows.Forms.Form and modifies the message pump to be more amenable to the needs of a DirectX application.

Some minor differences in syntax crop up when we create the render target:

SlimDX
RenderTarget renderTarget;
 
// Create Direct2D factory
using ( var factory = new FactoryD2D())
{
     // Get desktop DPI
     var dpi = factory.DesktopDpi;
 
     // Create bitmap render target from DXGI surface
     renderTarget = RenderTarget.FromDXGI(factory, backBuffer, new RenderTargetProperties()
     {
         HorizontalDpi = dpi.Width,
         VerticalDpi = dpi.Height,
         MinimumFeatureLevel = SlimDX.Direct2D.FeatureLevel.Default,
         PixelFormat = new PixelFormat(Format.Unknown, AlphaMode.Ignore),
         Type = RenderTargetType.Default,
         Usage = RenderTargetUsage.None
     });
}
SharpDX
RenderTarget renderTarget;
 
// Create Direct2D factory
using ( var factory = new FactoryD2D())
{
     // Get desktop DPI
     var dpi = factory.DesktopDpi;
 
     // Create bitmap render target from DXGI surface
     renderTarget = new RenderTarget(factory, backBuffer, new RenderTargetProperties()
     {
         DpiX = dpi.Width,
         DpiY = dpi.Height,
         MinLevel = SharpDX.Direct2D1.FeatureLevel.Level_DEFAULT,
         PixelFormat = new PixelFormat(Format.Unknown, AlphaMode.Ignore),
         Type = RenderTargetType.Default,
         Usage = RenderTargetUsage.None
     });
}

While SlimDX uses the static member function RenderTarget.FromDXGI to create a render target from a DXGI surface, SharpDX provides an overloaded constructor for the purpose. The field names for DPI and DirectX feature level in RenderTargetProperties differ slightly between the two APIs; otherwise, the code is identical.

Specialized window behaviour

.NET’s Windows Forms architecture doesn’t play nice with Alt+Enter to switch to full-screen in DirectX applications, so we override its behaviour in both versions and tell Windows Forms to ignore Alt+Enter key presses. Such key presses will be intercepted and processed automatically by DirectX, so we don’t want Windows Forms to interfere with this.

SlimDX
// Disable automatic ALT+Enter processing because it doesn't work properly with WinForms
using ( var factory = swapChain.GetParent<FactoryDXGI>())
     factory.SetWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAltEnter);
SharpDX
// Disable automatic ALT+Enter processing because it doesn't work properly with WinForms
using ( var factory = swapChain.GetParent<FactoryDXGI>())
     factory.MakeWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAltEnter);

Note the difference in method names here: SlimDX uses SetWindowAssociation while SharpDX uses MakeWindowAssociation.

Now we replace the default Alt+Enter handling function with our own, set the window’s starting size and prevent it from being re-sized (I have deliberately left out window re-sizing code here for simplicity):

// Add event handler for ALT+Enter
form.KeyDown += (o, e) =>
{
     if (e.Alt && e.KeyCode == Keys.Enter)
         swapChain.IsFullScreen = !swapChain.IsFullScreen;
};
 
// Set window size
form.Size = new System.Drawing.Size(640, 480);
 
// Prevent window from being re-sized
form.AutoSizeMode = AutoSizeMode.GrowAndShrink;

The code is the same for both APIs.

The main rendering loop

Both APIs provide a mechanism to allow you to inject custom rendering code into aRenderForm. A static method in each API simply takes the desired RenderForm and rendering function as arguments, then runs it indefinitely until the window is closed. In SlimDX this method is called MessagePump.Run; in SharpDX it is called RenderLoop.Run.

SlimDX
// Rendering function
MessagePump.Run(form, () =>
{
     renderTarget.BeginDraw();
     renderTarget.Transform = Matrix3x2.Identity;
     renderTarget.Clear(Color.White);
 
     // put drawing code here (see below)
 
     renderTarget.EndDraw();
 
     swapChain.Present(0, PresentFlags.None);
});
SharpDX
// Rendering function
RenderLoop.Run(form, () =>
{
     renderTarget.BeginDraw();
     renderTarget.Transform = Matrix3x2.Identity;
     renderTarget.Clear(Color.White);
 
     // put drawing code here (see below)
 
     renderTarget.EndDraw();
 
     swapChain.Present(0, PresentFlags.None);
});

Here we have just supplied a lambda function which clears the render target (window) to a white background and calls SwapChain.Present to flip the back buffers.

Drawing the example image

The following code draws the image in figure 1. It should replace the commented line in the section above.

A number of differences start to creep in now:

  • while SlimDX uses System.Drawing.Color as an argument to its Color4 object constructor to reference colours, SharpDX uses SharpDX.Color directly
  • in SlimDX, methods such as DrawLineDrawRectangle and FillRectangle have overloads which accept the brush as the first argument, then a series of co-ordinates, followed by any other parameters such as stroke thickness. In SharpDX, the co-ordinate arguments come first and the brush argument comes after the co-ordinates, followed by any other parameters
  • in SharpDX, point co-ordinates must be specified with a Vector2 object, whereas in SlimDX they can be plain integers or floats

Let’s have a look at the code:

SlimDX
using ( var brush = new SolidColorBrush(renderTarget, new Color4(Color.LightSlateGray)))
{
     for ( int x = 0; x < renderTarget.Size.Width; x += 10)
         renderTarget.DrawLine(brush, x, 0, x, renderTarget.Size.Height, 0.5f);
 
     for ( int y = 0; y < renderTarget.Size.Height; y += 10)
         renderTarget.DrawLine(brush, 0, y, renderTarget.Size.Width, y, 0.5f);
 
     renderTarget.FillRectangle(brush, new RectangleF(renderTarget.Size.Width / 2 - 50, renderTarget.Size.Height / 2 - 50, 100, 100));
}
 
renderTarget.DrawRectangle( new SolidColorBrush(renderTarget, new Color4(Color.CornflowerBlue)),
     new RectangleF(renderTarget.Size.Width / 2 - 100, renderTarget.Size.Height / 2 - 100, 200, 200));
SharpDX
using ( var brush = new SolidColorBrush(renderTarget, Color.LightSlateGray))
{
     for ( int x = 0; x < renderTarget.Size.Width; x += 10)
         renderTarget.DrawLine( new Vector2(x, 0), new Vector2(x, renderTarget.Size.Height), brush, 0.5f);
 
     for ( int y = 0; y < renderTarget.Size.Height; y += 10)
         renderTarget.DrawLine( new Vector2(0, y), new Vector2(renderTarget.Size.Width, y), brush, 0.5f);
 
     renderTarget.FillRectangle( new RectangleF(renderTarget.Size.Width / 2 - 50, renderTarget.Size.Height / 2 - 50, 100, 100), brush);
}
 
renderTarget.DrawRectangle(
     new RectangleF(renderTarget.Size.Width / 2 - 100, renderTarget.Size.Height / 2 - 100, 200, 200),
     new SolidColorBrush(renderTarget, Color.CornflowerBlue));

As you can see, the arguments are re-ordered as appropriate for what each API expects, and wrapped in Color4 and Vector2 objects where applicable.

Cleaning up

Both APIs expect you to dispose of objects wrapping COM interfaces properly, and the code for both is the same although the inner workings are substantially different under the hood between implementations (see the notes about SlimDX object tracking in the Feature Comparison section above):

renderTarget.Dispose();
swapChain.Dispose();
device.Dispose();
Source code

The complete source code for both versions follows.

SlimDX
SharpDX

I would like to thank Roberto for his SharpDX tutorial repository at GitHub which saved me a lot of hassle while writing the SharpDX version of the example. Check out his repository for tons of good tutorials!

Conclusions

As you can see, the code for both versions of the example is very similar, although as noted earlier, this should not be taken as representative of how all applications written using these APIs will look. The availability of documentation and examples, higher-level APIs, support, updates and which API best suits your target platform are essentially the most important deciding factors, since – as shown – both have a fairly clean class-based API which closely resembles that of the COM interfaces used in the C++ DirectX SDK.

If I was to speculate, I would predict that SharpDX is the better choice for new projects, with the caveat that the learning curve is steep due to a lack of proper documentation. Since it is just a thin wrapper over the C++ headers, the MSDN documentation can be used and C++ code from other sources can be ported without too much trauma to C#; however, using the un-documented SharpDX Toolkit and other tools which come with the API is another matter entirely. Of crucial importance to professional developers is that SharpDX is still maintained, while SlimDX is not.

I hope you found this overview helpful. Please add your own feedback and experiences below!

I’m a software developer with very limited work capacity due to having the debilitating illness M.E. – please read my article Dying with M.E. as a Software Developer and donate to the crowdfund to help me with my bucket list if you found this article useful. Thank you so much!

Further Reading

Some useful links I found while writing this article:

Tutorials on SharpDX by Roberto/RobyDX (direct GitHub link)

SharpDX and Game Engines – Back to Zero? An interesting discussion of C# game engines on GameDevSE.

Richards Software Ramblings – Hills Demo with SlimDX and C# (gamedev.net)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值