After the window and device are created, the application needs to use a main loop (also called a render loop or message loop) that responds to window messages, updates and renders the scene, and handles device events. The application can either implement the main loop itself, or it can use the DXUT implementation. Registration of callback functions allows DXUT to handle device, frame, and message events.
To use the framework's main loop, simply call DXUTMainLoop with the single parameter set to NULL.
Although it is easiest for the framework to handle the main loop, for some advanced applications a custom main loop may be a better design. It is possible to use the DXUTMainLoop function with a custom main loop, but more code is required in the application, as shown in the following code example:
INT WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, INT ) { DXUTSetCallbackDeviceCreated( OnCreateDevice ); DXUTSetCallbackDeviceReset( OnResetDevice ); DXUTSetCallbackDeviceLost( OnLostDevice ); DXUTSetCallbackDeviceDestroyed( OnDestroyDevice ); DXUTSetCallbackFrameRender( OnFrameRender ); DXUTSetCallbackFrameMove( OnFrameMove ); DXUTInit( TRUE, TRUE, TRUE ); DXUTCreateWindow( L"BasicHLSL" ); DXUTCreateDevice( D3DADAPTER_DEFAULT, TRUE, 640, 480 ); // Custom main loop HWND hWnd = DXUTGetHWND(); BOOL bGotMsg; MSG msg; msg.message = WM_NULL; PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE ); while( WM_QUIT != msg.message ) { // Use PeekMessage() so we can use idle time to render the scene bGotMsg = ( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) != 0 ); if( bGotMsg ) { // Translate and dispatch the message if( 0 == TranslateAccelerator( hWnd, NULL, &msg ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } } else { // Render a frame during idle time (no messages are waiting) DXUTRender3DEnvironment(); } } return DXUTGetExitCode(); }
In this code example, the application calls the function DXUTRender3DEnvironment to have the framework update and render the scene and handle device events. While it is possible for the application to completely replicate this functionality, it is not recommended.
The framework uses a callback function mechanism to allow the application to respond to events. The application simply needs to register, or set, a function pointer with the framework, and the framework will call this function when the event occurs. The framework does not require that any callback function is registered, so the application is free to register only as many callback functions as needed.
For the DirectX April 2005 SDK Update, the callback functions pass a void* pUserContext from the DXUTSetCallback* function to the callback. This allows the callback functions to recieve context from the application such as a class pointer.
Three types of events occur in the framework:
While an application is rendering to a Direct3D device, it is possible for the device to become lost. This can happen for a number of reasons, such as when a user presses ALT+TAB to leave a full-screen application, when a user presses CTRL+ALT+DEL, or when the user runs another full-screen 3D application. The Direct3D API notifies the application when this happens by returning D3DERR_DEVICELOST when calling certain functions such as IDirect3DDevice9::Present. (See Lost Devices.)
When a device becomes lost, it is the application's responsibility to release all Direct3D objects that do not survive when the device was lost, such as objects created in D3DPOOL_DEFAULT memory. If such objects are not released, the device cannot be reset when it returns from its lost state.
The application must wait while the device is lost. When the device returns, the application must call IDirect3DDevice9::Reset and recreate all device objects that did not survive the reset procedure.
With the framework, this process is simplified by using callback functions in the application to handle the various device events: device changing, created, reset, lost, or destroyed. The framework will note when the device becomes lost and will properly reset the device when it returns from the lost state. The framework uses the application's callback functions to release and recreate the device objects at the appropriate times. All the application needs to do is to register and implement callback functions as follows.
Callback Registration Function | Application Callback Function | When Called By the Framework | Resource Creation | Resource Release |
---|---|---|---|---|
DXUTSetCallbackDeviceChanging | LPDXUTCALLBACKMODIFYDEVICESETTINGS | Called before the Direct3D device is created. This gives the application a chance to reject the device change by returning false. | - | - |
DXUTSetCallbackDeviceCreated | LPDXUTCALLBACKDEVICECREATED | Called immediately after the Direct3D device has been created, which will happen during application initialization and device recreation. | Create D3DPOOL_MANAGED resources because these resources need to be reloaded whenever the device is destroyed. | - |
DXUTSetCallbackDeviceReset | LPDXUTCALLBACKDEVICERESET | Called immediately after the Direct3D device has been reset, which will happen after the device is lost. | Create D3DPOOL_DEFAULT resources because these resources need to be reloaded whenever the device is lost. | - |
DXUTSetCallbackDeviceLost | LPDXUTCALLBACKDEVICELOST | Called immediately after the Direct3D device has entered a lost state and before IDirect3DDevice9::Reset is called. | - | Resources created in the device reset callback function (LPDXUTCALLBACKDEVICERESET), which generally includes all D3DPOOL_DEFAULT resources, should be released by this application callback function. |
DXUTSetCallbackDeviceDestroyed | LPDXUTCALLBACKDEVICEDESTROYED | Called immediately after the Direct3D device has been destroyed, which generally happens as a result of application termination or device recreation. | - | Resources created in the device created callback function (LPDXUTCALLBACKDEVICECREATED), which generally includes all D3DPOOL_MANAGED resources, should be released by this application callback function. |
When the device is toggled between windowed and full-screen modes, it is usually reset, but sometimes it will have to be recreated by Direct3D.
It is optional to call any of these callback registration functions. However, if the application does not register the device destroyed and device created callback functions by calling DXUTSetCallbackDeviceDestroyed and DXUTSetCallbackDeviceCreated, then the framework cannot recreate the device because it will have no way of notifying the application that the device is being destroyed or created. In this case, changing devices or toggling between hal or reference devices will not work.
Similarly, if the application does not register the device lost and device reset callback functions by calling DXUTSetCallbackDeviceLost and DXUTSetCallbackDeviceReset, then the framework has no way of notifying the application when the device is lost or reset. In this case, typically if all the device objects are not in D3DPOOL_MANAGED memory, then Direct3D will fail to reset the device.
Also note that if the application uses DXUTCreateDevice, it does not need to call DXUTSetCallbackDeviceChanging because the framework will remember the LPDXUTCALLBACKMODIFYDEVICESETTINGS function.
The framework also provides frame events that are called every frame during the rendering process. The application should register and implement callback functions as follows.
Application Callback Function | Callback Registration Function | When Called By the Framework | Scene Rendering |
---|---|---|---|
LPDXUTCALLBACKFRAMEMOVE | DXUTSetCallbackFrameMove | Called once at the beginning of every frame. | This callback function is the best location for your application to handle updates to the scene, but it should not contain actual rendering calls, which should instead be placed in the frame render callback function (LPDXUTCALLBACKFRAMERENDER). |
LPDXUTCALLBACKFRAMERENDER | DXUTSetCallbackFrameRender | Called at the end of every frame, or if the window needs to be repainted. | All the rendering calls for the scene should be performed in this callback function. After this callback function has returned, the framework will call IDirect3DDevice9::Present to display the contents of the next buffer in the swap chain. |
The framework passes window messages, keyboard events, and mouse events through the following callback functions and their corresponding registration functions. Program the application to provide appropriate responses to such events.
Application Callback Function | Callback Registration Function | Description |
---|---|---|
LPDXUTCALLBACKMSGPROC | DXUTSetCallbackMsgProc | Processes window messages from the DXUT message pump. |
LPDXUTCALLBACKKEYBOARD | DXUTSetCallbackKeyboard | Processes keyboard events from the DXUT message pump. |
LPDXUTCALLBACKMOUSE | DXUTSetCallbackMouse | Processes mouse events from the DXUT message pump. |