Ancillary Objects in SlimDX

As previously discussed (by myself and more recently by Promit) SlimDX’s “object table” provides us a way to indirect construction of our objects so that we make sure we reuse an existing managed object if one exists for the native object being wrapped. This significantly simplifies the usage of the SlimDX API, as well as letting us correctly leverage the IDisposable interface to provide clean-up behavior.

But everything is not so idyllic just yet. Some DirectX interfaces or methods operate in a fashion that can cause trouble for SlimDX in terms of its ability to correctly implement the disposal idiom in this manner. Recall that IDisposable’s contract is somewhat akin to unmanaged pointer ownership: you only call Dispose() on instances you own, either because you directly created them via new or some factory method, or because the API that gave you the instance stipulated a transfer of ownership from the API to you.

So what happens with things like the default back buffer surface that is automatically created for you in Direct3D 9? You, as the SlimDX client, did not explicitly create this object. Nor were you given ownership of it — that remains with the device. Calling GetBackBuffer on the device will perforce create the managed wrapper around the surface… but how does that wrapper get cleaned up?

In the early stages of SlimDX, you were expected to dispose of the resulting object, even though it was in violation of the disposal idiom (in retrospect we also could have moved away from implementing IDisposable to something more akin to explicit ‘release’ calls, but then we’d lose the benefit of the C# using construct). These days, we handle the issue using so-called ancillary objects. An ancillary object is one whose owner is some other SlimDX object. In the case of the surface example above, that owning object is the device that the surface is associated with. These objects are tied to their owner — when their owner is disposed (and thus, removed from the object table), so are they.

There isn’t any way for this to happen automatically (within SlimDX’s code, at least, although it it is entirely transparent to users); we need to know that the object produced by some native API call is something we consider ancillary and mark it appropriately. For example, the implementation of the GetBackBuffer() method that produces the swap chain described above looks something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Surface^ Device::GetBackBuffer( int swapChain, int backBuffer ) {
  IDirect3DSurface9* buffer = 0;
  HRESULT hr = InternalPointer->GetBackBuffer(
    swapChain,
    backBuffer,
    D3DBACKBUFFER_TYPE_MONO,
    &buffer
  );
 
  if( RECORD_D3D9( hr ).IsFailure )
    return nullptr;
 
  // Associate the surface with the device.
  return Surface::FromPointer( buffer, this );
}

The last line is where the ancillary object association is made; most regular objects will specify nullptr for the second parameter, making them full-fledged, regular objects. After navigating the rather subtle and complex call graph that constitutes object creation via FromPointer, the ancillary object ends up in the object table as normal, and also in a secondary table that maps owning objects to all the objects they own. This allows us to free them when the owner is removed from the table:

1
2
3
4
5
6
7
8
9
10
// ...from ObjectTable::Remove, in ObjectTable.cpp
m_Table->Remove( object->ComPointer );
if( m_Ancillary->ContainsKey( object->ComPointer ) ) {
  for each( ComObject^ ancillary in m_Ancillary[object->ComPointer]) {
    ancillary->Owner = nullptr;
    delete ancillary;
  }
 
  m_Ancillary[object->ComPointer]->Clear();
}

Implementing this architecture also provided us with a good place to hook in other behavioral modifications to COM object management, which will be useful in the future for addressing more advanced edge cases involving interop between SlimDX and other managed or native APIs that are using a DirectX API underneath.

, ,

3 Comments

Rethinking the SlimDX Sample Framework

slimdx-samples-listThe SlimDX sample situation is currently pretty disorganized: some of our samples are checked in to the Subversion repository, but others are just referenced from issues in our issue tracker. Not all the samples use our internal sample framework, either, and the framework itself has a few issues. But as SlimDX becomes more and more stable, and begins to really approach complete parity with the native APIs, we have an opportunity to revisit problems like this one and clean things up.

In the future, our intention is to create a firm split between official and community samples. Only the former will be checked into source control. The community samples will be hosted on our website somewhere, and when warranted we will fold appropriate community samples into the official distribution, after appropriate modification. One of the things we want to ensure is that the official samples have a uniform style (to make them easy to follow and compare) and that they meet certain quality criteria.

The other thing we’re going to do is refactor the sample framework itself. The current framework feels like a “mini-XNA,” which has caused some users to adopt it as a more generalized “game” framework, which is problematic for us. We do not want to be in the business of writing — more importantly, of maintaining — anything resembling an engine. There are other projects out there that fit that bill; SlimDX is a lightweight wrapper, and we want to preserve its minimalist ideals even across aspects of the project that are not the core library itself.

The sample framework should be about making it easy for us to build samples. The native SDK uses a pretty robust sample framework, and we’ve seen that hamper it as an educational tool. When the sample is trying to demonstrate some concept or technique, it should demonstrate that in the language of the API itself. API calls should be used as often as is reasonable in order to demonstrate ‘the simplest thing that could work,’ which is usually what a user is looking at the sample code for.

Basically, we want a relative lack of abstraction. Users are just going to peel away the abstraction layers anyway, so we’d just be creating more work for them.

It’s still going to be a framework, though! We’re still going to wrap the creation of the primary window and the ‘game loop,’ and we’ll provide extensibility points allowing a sample to customize the main phases of application execution: pre-initialization configuration, initialization and resource load, logic update, render update, and shutdown/cleanup. Simplifying such boilerplate code, code that is not directly related to the primary tasks one would use SlimDX for, is perfectly acceptable.

Similarly, we’ll probably wrap mesh loading (especially since there is no built-in method for creating ID3DX10Mesh objects from a file, and we’d like to use the same source assets when possible for homogeneity) and similar ancillary operations.

UI is another interesting issue. The native sample framework supports a simple collection of UI controls for tweaking parameters for a sample. I’d rather not implement (and thus maintain) a similar system in SlimDX, but the alternative of using Windows Forms controls means that tweaking won’t be available in samples running in fullscreen mode, which is a deal-breaker. So we’ll support a small set of basic controls useful for tweaking parameter values in the new framework as well.

These framework changes probably won’t make it into the next release (if indeed the next release is this month, as we suspect), but you should see them in the release immediately following that at the latest… as well as some other interesting changes. And of course, you can always live on the bleeding edge and work from the head of our repository.

And now a word from the marketing department…

In other SlimDX news, Arcen Games, LLC recently released AI War: Fleet Command. I’ve posted about this on Twitter already, and SlimDX lead Promit Roy has beaten me to the punch on a blog post, but I’ll repeat it here for the benefit of other readers: AI War is the first game available for purchase that was built using SlimDX! This is very cool. Developer Chris Park has written about his experience evaluating rendering solutions for C# projects, explaining how he ultimately chose SlimDX… head over and give his blog a read, and when you’re done, be sure to check out the game itself!

,

No Comments

How To: Keep Your Console Window Visible

Another common beginner’s query on the GDNet forums concerns character mode (”console”) applications. Specifically, keeping them open after they have completed execution, usually so you can examine the actual output produced. A frequent response is the suggestion to insert some sort of artificial pause in execution right before the closing brace of main(), such as a call to system("pause"), cin.get(), or similar.

There is nothing altogether horrible with doing this. It is not the end of the world. It will not cause any harm — the only potential snag would be in the use of the system() call, because interpretation of the input string is implementation defined (it may do nothing on some platforms). But that’s not usually a scenario a beginner need concern themselves with. In programs that are really just quick and dirty hacks, it would hardly merit a second glance… However, in larger, “real” projects, it does constitute a design flaw: “real” console mode applications typically are designed for batch pipelined processing. Data compilers; translation, reformatting or filtering tools; asset processors… Utility applications that are part of some larger aggregate operation that would be basically useless if you had to “press any key to continue” after every program in the sequence was finished.

Put another way, pausing at the end of your console program is roughly equivalent to having a GUI application pop up a dialog box when you selected the “exit” option that said “Click okay to exit.”

Finally, even for quick and dirty hacks it is pointless and inefficient, since it requires you to type some code (even if it’s a small amount of code) to do something that you can do with no code at all. The reason the program “closes immediately” is because your IDE is creating a temporary console context within which to run your code, and closing that context as soon as the program completes. Any decent IDE should have an additional option to run your program in such a way that that created console remains open.

Below, I’ve provided screenshots of the location of the appropriate command for Visual Studio and Code::Blocks, two of the most popular IDEs for C++ development. By making use of these options, you’ll no longer have to clutter up your code with hacks that are specific to running your program within your IDE (which are detrimental to running the program “for real”) and you should be able to work just a little bit faster because you’ll have a better grasp of how to use your tools.

Visual Studio

When you run your program in Visual Studio, you are usually invoking the IDE’s “Start Debugging” command. There is an additional “Start Without Debugging” command, usually just under the first command, inside the Debug menu (unless you’ve rearranged your menus). Your key bindings may be different than those in my screenshot here, but the command itself will still function the same.

keep-window-open-vs

The “Start Without Debugging” option keeps the console window created for your program open until you hit a key, basically automatically inserting the equivalent of system("pause") after your own code, just for this execution.

Code::Blocks

Code::Blocks keeps it’s “keep the window open” command in the Build menu.

keep-window-open-cb

An interesting feature is that Code::Blocks will provide you the return value of main(), and the execution time, directly in the console window your program runs in.

, , ,

No Comments