It’s good to review the fundamentals sometimes. Written in 1995 and often forgotten: A Pixel Is Not A Little Square.
Just to be clear, I’m restricting my comments to the DirectX section of the codepack. I’ll probably be integrating some of the other bits (jump list support etc) into SlimTune, so we’ll see how that goes later. But around a week ago, they finally released 1.0. Same day as Windows 7 was out on MSDN actually — I’m pretty sure that’s not a coincidence. SlimDX’s release with Direct3D 11, Direct2D, and DirectWrite should come later this month, if all goes well. Now, the code pack does cover a few things we don’t, like the Windows Imaging Component.
So it’s not beta anymore. Now, I’m fairly sure that they haven’t looked at our code for legal reasons, but I did make some harsh comments about their work. They’ve made some changes that seem to follow directly from those comments. I’m about to make some more. I’ve been perusing the release and frankly, it’s just not any good. They seem to have spent most of their time implementing equally shoddy support for D3D 10 than fixing the actual problems. I’m going to run through all the reasons I see that this thing is not well done.
Okay, so they added a math library. I very pointedly slammed them for not having one in the 0.90 release, so let’s start with the bread and butter of graphics — Vector3F, as it’s called in the Code Pack. It offers X, Y, Z, Normalize, NormalizeInPlace, static Dot, static Cross, operator +, operator -, and (in)equality comparisons. Yes, that’s the entire class. No ref overloads, which are important for performance. No other helper methods of any kind. No PropertyGrid or System.ComponentModel compatibility. Matrix is even sadder — it has operator * (by-value only) and Identity. That’s it. Compare to ours, or XNA.
Functions that return object references still create brand new instances, which then not only have to be garbage collected, but if you don’t remember to Dispose them, they’ll be queued for finalization too. (These USED to be properties…it’s an improvement, I guess.) This is a similar effect to the original MDX’s event problems, just smeared out over time and difficult to track. There’s certainly no leak tracking functionality like SlimDX has. (OTOH they will be released eventually, which SlimDX does not promise.) These are lots of small allocations, which the .NET GC is good at handling, but if you don’t remember to Dispose them, or have a lot of them in general, this could really sour your day. It’s a problem that just doesn’t exist in SlimDX.
As for 64 bit support, it’s simply not configured at all in the solution (remember, the code pack is source code only, no binaries). I set up and ran an x64 build that went off without a hitch, and there’s no inherent reason for x64 to not work. I haven’t tested it though, and neither has anyone else apparently.
Lastly, even though they’ve added D3D 10 support, there’s no D3DX support in here at all. They basically invite you to go ahead and write what you need yourself, but none of it is done for you. For something that’s intended to make your job easier by letting you use managed code, this is another odd omission.
The 1.0 of the code pack IS dramatically improved in several respects — 0.90 had no math code at all, the memory situation was far worse, etc. Even so, this really doesn’t inspire a lot of confidence. Although the core APIs are wrapped, the support code is basically non-existent. There’s no binary distribution or redistributable, so you’re on your own there. I know this is probably a small team at Microsoft with nowhere near the level of resources it needs, and I’m sorry that I’m continually trashing your work. But if this is what constitutes the successor to Managed DirectX, I don’t think SlimDX is in any danger and I can’t say I mind.
I’ve decided that “Windows API Code Pack” is far too long to say/write in casual conversation. WACP it is.
These are terms I came up with a few weeks ago, and I wanted to document them properly. I feel that they’re good descriptions of what a wrapping API like SlimDX allows, and it’s useful to be able to settle on common jargon. The basic idea of interoperability is for libraries to be able to cooperate, by sharing objects with each other.
Export interoperability is the ability to “export” objects to other libraries. In the case of SlimDX and similar wrappers, it essentially involves exposing the internal IUnknown pointers, so that another system that supports importing objects can do so. It’s not difficult to implement, but it is critical to remember that when your objects are exported, their state can be changed at any time, outside of your control. You can’t cache anything that isn’t invariant. This is why XNA is unable to do export interop; they chose to cache just about everything, and allowing people to use the underlying interfaces directly will break it. People do it anyway via reflection of course, but it’s risky. SlimDX and WACP don’t cache anything about the objects, and support export interop cleanly. This is why we can work with libraries like DirectShow.NET, CUDA.NET, and more.
Import interop is of course the ability to consume objects from another library. Of the various DirectX wrappers, SlimDX seems to be the only one that handles import interop. There’s no particular reason WACP can’t do it, as far as I can tell; they simply haven’t added it to the public interface. With XNA, I believe that the cached values are again the problem, I suspect. They could be looked up on construction, but somebody else still has direct access to the interface. Import interop is at its core not that clever, because it’s a basic part of building the wrapper in the first place. You have to be able to convert pointers from the unmanaged API to your own objects, so it’s not a big step to do it from arbitrary pointers. The trick is doing it safely; you have to trust that the pointer you’re given is what the caller says it is. SlimDX assumes it is an IUnknown and then uses QueryInterface to get the desired type. This is our only line of defense, but it’s a fairly effective one.
Interoperability has been a major focus for the SlimDX design, for both import and export. There’s a fair amount of complexity involved in object construction, but it’s been carefully laid out to be able to handle external pointers. There was some internal caching of values early on, which we thought were invariant, but we were seeing some difficult to track problems and so we backed out the code to a safe implementation. Our commitment to making sure we can both export and import objects goes far beyond the other major wrappers, although WACP should be able to provide equivalent functionality if they stick to the current design or similar.
SlimDX has had Direct3D 10 support for a long time, and it’s almost certainly the single best way to get 10 support from C# or any other .NET language. While it’s true that the very original prototype didn’t plan on it, that was over two years ago and it’s very much a first class citizen. Although we have Direct3D 11 support now (see this post), 10 is still a priority and we’re not leaving it behind.
As part of that commitment, SlimDX now has full Direct3D 10.1 support. It will be part of our next release, which looks like it will be August 09 at this point. Although nobody specifically asked for 10.1, it’s a very useful API in its own right. Sure it adds some features, but just like DirectX 11, it has feature levels. In other words, even though DirectX 10 requires DX10 class hardware, 10.1 works on DX9 or 10 hardware! The caveat is that Vista SP1 or later is required, but if that’s an acceptable limitation, 10.1 is great to have for that reason alone.
And as always, if you find missing features or bugs or whatever, let us know! D3D 10 is in use by several of our customers in production environments, and we’re determined to continue being the single best option for using it from C#, VB.NET, or anything else in the .NET ecosystem.
Long story short: I’m not so sure about them.
The reason SlimDX does not, by and large, include finalizers is because of the threading issues involved. You can enable multithreaded devices in D3D 9 and 10, but it’s not really a good idea in most cases and we don’t see any reason to handle it differently. Direct3D 11, however, is making a big push for multithreaded rendering, and that means we could credibly enable finalizers just for it. There is a single threaded flag, but we can detect that and avoid finalizing those objects. But there’s more to it than that.
Let’s recap. Finalizers generally only make sense as part of the IDisposable pattern. The idea is that you expose Dispose to allow deterministic destruction of unmanaged objects, but in the case that you don’t call Dispose — deliberately or accidentally — the finalizer comes around and does it for you. This is mostly designed for Windows handles and the like, which I think is why it fails to really take threading into account. Because most DirectX objects are not free threaded, that goes to hell in SlimDX. We chose to eschew the finalization part of the pattern completely, and nobody really seems to mind.
From a strictly technical standpoint, enabling finalizers for 11 would be easy. It would take less time than writing this, actually. Josh pointed out that it’s a break in consistency, but I believe we can at least make an argument for it being part of the multithreaded support. The Windows API Code Pack provides finalizers, which tends to support the idea we should do it. However, there are a number of issues I’m concerned about, and for June we almost certainly won’t have them. I’m not sure we ever will.
At some level, finalizers exist to patch up programmer error. While this is not necessarily a bad thing, I have some misgivings about it. SlimDX doesn’t clean up after you, but it does provide some very powerful facilities for finding out what didn’t get cleaned up, and we even have some options for expanding that functionality. Finalizers will tend to interfere with that tracking in unpredictable (but innocuous) ways. If you have repeating leaks, maybe some will get finalized and maybe some won’t. The ObjectTable won’t be able to provide you with reliable results. We could provide a configuration option for it, like we do for exceptions — but that’s not a good idea. Basically if you’re exposing a choice to the user, it means you didn’t have the balls to actually make the choice and you’re passing the buck. I think Raymond Chen said something similar at one point, in not quite the same language.
There’s also a question of interop objects. We can track the single threaded flag in SlimDX code, but not in externally created objects. Sure it might be possible to get the device for an object and check its caps (I haven’t checked), but even if that works sometimes, it’s extra overhead and I’m guessing that the number of special cases involved is prohibitive. We could assume all external objects are multithreaded, and then someone will come complaining about a decision that we can no longer reverse, and which is impacting their performance negatively. Supporting interop smoothly is a critical use case for us, not least because it’s a major differentiating point against XNA. It has worked very well so far and I’m not thrilled about potentially breaking it now.
I guess I was lying at the beginning, actually. I am fairly sure about them. In fact, I’m fairly sure we won’t have them. They are looking to cause more problems than they solve, and nobody seems to care about the problem they’re supposedly solving in the first place.