In this second lesson we will be going over the second “simple” sample application from the DirectX SDK, called “SimpleProject”. And let me tell you this: it is not that simple. Not at all! It pretty much throws everything in. It even loads a shader, includes the shader source code that does simple vertex lighting using a single directional light source, but in the end it does not use it to render anything. When I’ll introduce vertex lighting shaders I’ll dedicate at least a full lesson to it. Still, it is the simplest from the sample projects (except for “EmptyProject”) and provides the basis for all the other samples that each tackle a specific technique. The sample is in no way didactic and there is probably no way I can explain it in a good way in any reasonable space, so I’ll just explain it in order and split the lesson up in multiple parts.
By this point I expect you to have your entire environment set up correctly. So if you installed everything correctly in the default locations, you will find the entire source tree for the project under:
"c:\Program Files\Microsoft DirectX SDK (June 2010)\Samples\C++\Direct3D\SimpleSample"
First I will tour the application and show you what it can do. Here is a screenshot of the application:
Like the first example, this one is also an empty blue window that does not render anything, but this time we have support for text drawing with full font support and basic GUI with a display configuration and rendering dialog. In the top left corner, we have a first line of text that displays the frame information. Here you will find the framerate, but the way the program is written, the framerate is not displayed if vertical sync is on and the application is capable of maintaining a framerate equal to your refresh rate. We get information about the display size and color format. The next line gives information about the device. It tells us that we have a HAL device with software vertex processing (sw vp).
There are two types of DirectX devices. HAL (probably Hardware Abstraction Layer) devices are devices capable of using hardware acceleration. HAL devices are not required to implement every single feature, but obviously you won’t be able to use a feature unless it is available. The other kind of devices is called a REF (reference) device. These devices are software and are meant to be very precise and support every single DirectX feature. They are completely CPU based and incredibly slow. So slow that you probably won’t be able to render a single triangle at “interactive” framerates. These devices are used purely for debugging purposes and are only available if you have the DirectX SDK installed. Because DXUT samples have support for REF devices and vertex debugging, for a while at least I’ll keep the support in, but I have no intention to debug devices or teach you how to do it.
So back to the second line of text. It also tells us the name of the adapter. If you know a thing or two about GPU and the software vertex processing did not worry you, seeing an Intel onboard GPU should. These GPUs are very weak and very much ill suited for any kind of hardware accelerated 3D rendering, yet alone engine development or game playing. I have access to a lot of computers and most of them are extraordinarily strong. Real beasts. But they are meant for development and thus do not have powerful GPUs. When real power is needed, I’ll switch over to a computer with a dedicated GPU.
In the top right corner, we have 3 buttons. The first one will toggle between windowed mode and fullscreen mode. By default, when switching to fullscreen it will try to use the highest resolution available, but you can fine tune this behavior. The second button toggles between HAL and REF devices. I’ll only focus on HAL devices. The third button is little more interesting, opening up the default device setting dialog:
The dialog is a little underpowered under the Intel GPU, having some options disabled and in general fewer choices. I’ll show this dialog in the future on several GPUs when I decide describe it in further detail. Currently I do not know if you can easily extend this dialog with further options, but it is pretty good for something you get out of the box. It is also fully functional, not just an empty shell, so you can change properties and play with it around and all changes will instantly be reflected in your application.
So that’s it for the functionality of our simple application. It is smarter than the “empty” application and even loads that shader and sets its properties behind the scenes. If it loaded a mesh and used the shader to render it, we would have a full “hello world” sample.
So on to the code. It starts by including pretty much every single DXUT header in existence. See the code for a full list. Then we get something new: shader debugging support!
Uncomment the lines and theoretically with a REF device you should be able to do more debugging. I will not touch upon this subject. Then we have a bunch of global variables, and here is where things start to become interesting:
Let’s go over each variable:
g_Camera is a DXUT camera. If you are familiar with 3D you probably have used a camera before. It is a nice abstraction mechanism that allows us to control the way we perceive the 3D scene in a way that mirrors the way a real life camera is used to film stuff. Generally it has a positions and a direction. Or it has a position and the target’s position. Using these two points, a field of view value and two cut off planes, a view frustum can be defined. Everything inside that view frustum can be seen. Let me show you an unnecessarily math infused view frustum:
Unlike 3D modeling programs and higher level 3D toolkits, DirectX is happy with not providing you with such an abstraction in a very straight forward manner. A camera is actually just a set of matrix transformations, using the world-view-projections matrices (they also appear under other names in some literature). I’ll explain these matrices at the right moment. Suffice to say, using these matrices you can convert from object local 3D coordinates to 2D screen coordinates.
DXUT provides a few camera classes that behave as expected (I hope) and some utility functions and widgets.
g_DialogResourceManager is our dialog resource manager. In DXUT you need to have one such dialog resource manager than handles shared resources between dialogs and for event passing. You just need to set it up and in general you’ll focus on dialogs and widgets, not on the manager itself.
g_SettingsDlg is the dialog you saw above, the default device setting dialog.
g_HUD is a standard dialog. It is less specialized than g_SettingsDlg and is pretty much just a container into which you can insert widgets. It contains the 3 buttons from the top right corner I described before.
g_SampleUI is another such empty container. While the 3 buttons will be the same in all samples from the DirectX SDK, the g_SampleUI is used for sample specific controls. Since this application is “simple”, this dialog will be left empty.
g_pTxtHelper is a text helper object, the one we use to render text. Text rendering can be quite the obstacle. Especially when you are starting out with a toolkit. Let’s say you embark on one of the popular OpenGL tutorials. You’ll see that there is no out of the box font support and you will eventually find out that you need bitmap fonts. A readymade bitmap font utility can save you a lot of time and effort, but straight forwards static bitmap fonts have one huge problem: they are primitive. Like, 1980 came by and called a bitmap font a retarded dinosaur. You may not want real font support in your game, with Unicode and all the internationalization shabang, but still, support should be available. A bitmap font with a few hundred (at most) characters just won’t cut it. The text helper will allow you to use a font object and a sprite object to create a high performance (traditional flexible text rendering with flexible fonts can be quite the bottleneck) text display. In the end it probably creates bitmap fonts, but on the fly with any character selection. So you can use any font and render any character that it supports, allowing for Asian, Arabic and pretty much any script to be used. Even this poor integrated GPU can render a mix of scripts with hundreds of frames per second. g_pFont9 is the font object. Each font will have an object that needs to be properly initialized. And g_pSprite9 is the sprite object that is used by the text helper to render text using you font object. Hopefully things will become clearer when we see the code.
And finally we have the effect support. In this context, a shader is often called and effect. Loading a shader file and loading an effect file are synonymous. g_pEffect9 is the variable that will hold the information about the loaded shader (I will continue to use the shader term in this series, not the effect term). Shaders are generally created by loading a shader source code that is compiled after loading into GPU machine code. Shaders generally have multiple variables that must be set each frame before that shader is use to render a model. There are multiple ways to do that. Under DXUT you will load a shader and then use the string name of the variable you want to obtain a handle for that variable. Later you’ll select the handle and the value you wish to set the shader variable to. g_hmWorldViewProjection, g_hmWorld and g_hfTime are the 3 handles/variables you will set in this sample.
And finally we have the 3 IDs for the 3 buttons we added to the g_HUD dialog:
I don’t do as many crusades like I did back in the day, but my next one is probably going to be against GUI systems where you have manually and arbitrarily assigned numerical IDs for widgets (again!). I had a rant some posts back about this and is pretty much the sole reason I did my own GUI under Irrlicht. But DXUT is somewhat more complex in its GUI approach than the simple yet much hated ID assign scheme. I’m pretty sure I can add a pleasant callbacks system to it, but I haven’t tried it yet. So DXUT’s widgeting system should be enough. But defines? C’mon! At least use const, you wankers.
After having declared these variables and “constants”, the SimpleSample project goes into the familiar direction of implementing the DXUT callbacks and except for a few helper functions, the entire application is implemented just in those callback functions and the pretty much standard and interchangeable main function:
This function is identical to the one from the first sample project, except that it has some extra initialization in the InitApp function:
Here finally we have something unique to our sample: no more generic glue code! As said, if you have GUI you need a single dialog resource manager. On the first tree lines of the function you initialize all you dialogs with this resource manager. Every single such dialogue must be initialized before properly used. Additionally, each dialogue must have its callback set. This function will be called when a GUI related event occurs and the callback will allow you to identify the widget in question, in this sample using the numerical IDs we define before. The device settings dialog has its own callback that we don’t need to set, but we must do this for both our custom dialogs. We set the same callback for both, but the second dialog is empty and has no functionality.
And finally we add the 3 buttons to the first dialog. We use the AddButton function. This function takes the following parameters, in this order:
- the ID of the button. This will be passed to the dialog callback to identify the widgets.
- the label of the button. This text will be displayed on the button.
- the X position in parent relative coordinates.
- the Y position in parent relative coordinates.
- the width of the button.
- the height of the button.
- the hotkey used to trigger the button with keyboard input. Optional.
- if the button is a default button. Optional and false unless specified otherwise.
- a pointer to CDXUTButton pointer ("out" parameter). This can be used to obtain the instance of the button created by this method. Optional, default NULL.
And finally we have the GUI callback for these dialogs:
Here we get a taste for some of the utility functions from DXUT. DXUTToggleFullScreen toggles between windowed mode and fullscreen mode. The device is recreated when the switch occurs, going though the callback called when the device is lost and then the callback for when the device is reset. And before that the callback that verifies the device settings gets a chance to refuse the new settings. DXUTToggleREF toggles between a HAL and a REF device. Again, the entire flow of loose/reset callbacks is executed. And finally, we activate the device settings dialog. The SetActive method both activates and shows the dialog. By passing it the opposite of the current activation state, we create a toggle between the dialog shown and hidden.
And this concluded the first part of this lesson. In part two we’ll go over the resource lifetime management callbacks.
Seth, where ya been? I dropped you an email to see where you dissapeared to :P
ReplyDeleteStill waiting on DwarvisH updates. Tell me it ain't dead...
ReplyDelete