  
Welcome to Ventspace! Most posts here are delayed copies of posts from the real Ventspace.
 Direct3D Is Not 'More Complicated' Than OpenGL |
Posted - 4/9/2008 6:06:16 PM | I don't like surprises. I especially dislike it when those surprises are in the form of obscure bugs in code I own due to a wacky decision some other library made. In this case, I discovered that SlimDX wasn't writing char data correctly (that's Unicode characters, not bytes), because Marshal.SizeOf(typeof(char)) is 1, even though sizeof(char) is 2.
God damnit.
Direct3D Is Not 'More Complicated' Than OpenGL
Unfortunately, there's this general feeling out there that Direct3D is more complicated than OpenGL, at least to start out with. I don't want to call it a myth, because I don't think it's spread by anyone, misinformed or otherwise. As far as I can tell, it's something that people believe because a whole lot of extremely poor writers have taken it upon themselves to publish shoddy tutorials, guaranteed to lose most beginners. (Say what you will about NeHe's code -- Jeff was incredibly effective at teaching and that's why his work entirely dominates). The reason for this is mainly that Direct3D tends to encourage good practices from the beginning, whereas OpenGL actively makes it more difficult than it should be to exercise good practice.
As a result of this encouragement, D3D tutorials typically go through a whole bunch of complexity, including setting up the window in some complicated way, setting up the device just so (and even enumerating in some cases!) and then going through the trouble of creating a vertex buffer to render from. The result is a pretty decent skeleton application. It's also misguided and makes for a terrible tutorial. The typical defense I see is that you should take the opportunity to show a beginner how to do things The Right Way (tm) right from the beginning, rather than retroactively killing off bad habits.
That's wrong. Very, very wrong.
As someone writing for beginners, your goal is not to teach them Right. Your goal is to introduce concepts to them -- one at a time -- and show them how to use each new concept without introducing arbitrary complexity. Actually, that's secondary. Your real, primary goal is to give them results as soon as possible. They should see successful results of their code, immediately, no matter how bad or wrong the code is. The best way to do that is to give them a minimum of code, so that there's very little chance of messing up the initial writing, and very little turnover time in getting it written. In short, the quality of an introductory tutorial can be directly judged by the amount of code it involves, and nothing more. All other criteria are totally irrelevant.
Given that claim, I now present what I think would be the correct accompanying code for a Direct3D Tutorial #2. (The first would be setting up the window, so just cut the D3D code out if you want to know what that looks like.)
#include <windows.h>
#include <d3d9.h>
HWND hWindow;
IDirect3D9* D3D;
IDirect3DDevice9* Device;
struct Vertex
{
float x, y, z, rhw;
D3DCOLOR Color;
};
#define FVF_VERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_CLOSE:
PostQuitMessage( 0 );
return 0;
case WM_PAINT:
ValidateRect( hWnd, NULL );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
int main()
{
HINSTANCE hInstance = GetModuleHandle( NULL );
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0, 0, hInstance, NULL, NULL, NULL, NULL, "MiniD3D", NULL };
RegisterClassEx( &wc );
hWindow = CreateWindowEx( WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, "MiniD3D", "MiniD3D", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
300, 300, NULL, NULL, hInstance, NULL );
ShowWindow( hWindow, SW_SHOW );
UpdateWindow( hWindow );
D3D = Direct3DCreate9( D3D_SDK_VERSION );
D3DPRESENT_PARAMETERS d3dpp = { 0 };
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
D3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWindow, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &Device );
Vertex Triangle[3] =
{
{ 150.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB( 255, 0, 0 ) },
{ 250.0f, 250.0f, 0.5f, 1.0f, D3DCOLOR_XRGB( 0, 255, 0 ) },
{ 50.0f, 250.0f, 0.5f, 1.0f, D3DCOLOR_XRGB( 0, 0, 255 ) }
};
MSG msg;
bool RunApp = true;
while( RunApp )
{
if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
RunApp = false;
TranslateMessage( &msg );
DispatchMessage( &msg );
}
Device->Clear( 0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 0, 0 ), 1.0f, 0 );
Device->BeginScene();
Device->SetFVF( FVF_VERTEX );
Device->DrawPrimitiveUP( D3DPT_TRIANGLELIST, 1, Triangle, sizeof(Vertex) );
Device->EndScene();
Device->Present( 0, 0, 0, 0 );
}
Device->Release();
D3D->Release();
DestroyWindow( hWindow );
return 0;
}
87 lines, including whitespace and comments, and even giving most braces their own lines.
The obvious question is of course, why don't I write a series of tutorials, if I'm so confident about what the correct way to do it is? The obvious answer is that I don't have time. I might do it for SlimDX though, since that's probably useful.
| |
 ObjectTable Hacks in SlimDX |
Posted - 4/1/2008 5:13:40 PM | I simultaneously love and hate April Fool's day. On the one hand, a lot of the stuff that happens is absolutely hilarious. On the other hand, some people do really stupid or annoying things. It's also apparently become fashionable to start posting things a day early, which is bull. Go to hell, TechARP. Go. To. Hell.
ObjectTable Hacks in SlimDX
For the most part, SlimDX is an amalgation of disconnected parts. There are groups of related classes that work together, but outside those groups, the various pieces are completely unrelated. However, most of the library is built up on top of COM objects, which all require very similar behavior and treatment in order to work correctly. Unfortunately, COM objects don't really mesh well with the .NET model for object lifetimes, and DirectX objects in particular can be quite touchy about threading issues. I'm not going to discuss this problem in detail, because jpetrie already has. I'm assuming you've already read that post before continuing on this one.
What I want to look at is the actual implementation of the hashtable that jpetrie mentions, and some of the consequences of its existence. The design goal of the hashtable, called ObjectTable, was that we would be able to take any native pointer to a COM object and retrieve an already constructed managed object for it whenever available. That is very useful, because it means that we don't have to spawn distinct managed instances any more than strictly necessary. In order to accomplish that goal, it's necessary to keep a table of every SlimDX object that has been created, and to remove entries from that table whenever a SlimDX object is disposed. Insertion into and removal from the table also correlates 1:1 to ref count changes by SlimDX. And in the very few instances that SlimDX internally causes an add ref on an object that won't be given to the user, it always releases it before returning control back to the client.
As a result, we now have a definitive and reliable table of every live instance of a ComObject derived class. (Technically, SlimDX could already do that, but it was optional and not useful thanks to the massive duplication of objects.) Every native COM instance is guaranteed to have exactly one corresponding managed ComObject instance. In other words, you can determine, at any arbitrary point in time, exactly how many ComObjects are outstanding and what types they are. In addition, SlimDX gives you the option to tag objects with their call stack when they're created, and even give them a string name of your choice. Want to fix your leaks? Just go through the list (or use the handy utility function to dump it for you) and see where your lost objects are coming from.
Or -- and I'm dead serious here -- just iterate over the list, and dispose everything. In some cases this is a fantastic way of masking serious bugs in your resource management. But not everybody is doing full management/streaming of resources. A lot of times, you're just writing a demo that loads a whole bunch of things are startup and nukes them at shutdown. You can get even more clever than that. Want to unload one level completely before moving onto the next? Do the exact same thing, but skip disposing the Device (and possibly Surfaces if you're using render targets) as you go through. It's ridiculous and absurdly lazy, but it will also work perfectly.
I realized that this hack was possible during development and made sure to expose it. Then, we decided to take things a little farther. We were already recording some extra information in every object; why not extend that a bit more? So we added two more bits of metadata that are D3D9 specific. First, every ComObject now has an IsDefaultPool flag that SlimDX sets where appropriate. Second, a number of classes derive from a new interface called IResettable that provides OnLostDevice and OnResetDevice functions. Both of these are useful for device lost and reset situations, obviously.
That's where the madness ends, for now. I'm kicking around a few ideas for more interfaces to add in the next version, but I don't have any ideas that have shown themselves to be particularly compelling yet. The only one that might actually go in is IDeviceObject, which simply provides GetDevice. There's no use for that which I've found yet, but I'm still hoping. I'm also open to suggestions, of course.
[EDIT] There's one thing I forgot to mention. In earlier incarnations of SlimDX, ObjectTracker (which became ObjectTable) only recorded objects while Configuration.EnableObjectTracking was true. Now, things always go into the table. That flag only controls whether or not the call stack is recorded at the point of creation. Disabling tracking is a performance optimization. The default is enabled. It's a good idea to turn it off in release mode, because it can actually get quite expensive.
| |
|
| S | M | T | W | T | F | S | | | | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | | | |
OPTIONS
Track this Journal
ARCHIVES
October, 2009
September, 2009
August, 2009
July, 2009
June, 2009
October, 2008
June, 2008
May, 2008
April, 2008
March, 2008
February, 2008
January, 2008
December, 2007
November, 2007
October, 2007
September, 2007
August, 2007
July, 2007
June, 2007
May, 2007
February, 2007
|