My program works, but too slow

Started by
9 comments, last by Wire-Z 15 years, 5 months ago
Hello, I begin with Directx and I have a little trouble. I've learned 3D programming with this website : http://www.directxtutorial.com I've found a 3D mesh on the net, I've converted it to .x with PandaExporter and I'm now using this mesh in my program. Now, my program just rotates a car. Everything works with little 3D meshes, but nor for the others (>50 000 vertices). You can find my program here. Inside, there is the source code, and the game is in the "release" folder. Here, I use a car with at the very most 20 000 vertices, and the program is already quite slow. For example, with 3D studio max, I an use 3D meshes with at least 500 000 vertices : when I rotate or translate the object, I can see it in "real time" in the perspective view. But when I use such meshes (> 200 000 - 300 000 vertices) in my program, it works too slow and each frame needs 1 or 2 seconds to be displayed.
Advertisement
A few points:

1) You don't have any error checking. For example, you don't check if the D3D device was created successfully, if the mesh was loaded successfully, etc.

2) You have a lot of duplicate code. For example, the code to render the mesh is duplicated 5 times. You can move into a separate function and just call it 5 times.

3) There's no need to call Clear() twice to clear both the back-buffer and Z-buffer. You can do it in one call:

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

4) There's no need to load the mesh and it's textures/materials more than once, as it wastes memory (which can degrade performance). Just load everything once and store several pointers to the mesh.

5) Using D3DCREATE_HARDWARE_VERTEXPROCESSING can improve performance.

6) Using different flags when loading the mesh can significantly improve performance. See the D3DXMESH enumeration in the SDK for the options available. You might try D3DXMESH_WRITEONLY (and remove D3DXMESH_SYSTEMMEM). I think this can give the biggest performance boost.
Thanks for your answer, Gage 64.

I removed remove D3DXMESH_SYSTEMMEM and I have put D3DXMESH_DYNAMIC (I got an error with D3DXMESH_WRITEONLY). And the program seemed to be a bit faster.

Using D3DCREATE_HARDWARE_VERTEXPROCESSING really increased the program's speed. Now, it works properly with little meshes.

But I still have the same problem with big meshes. Here, I tried with a car made of 200 000 vertices (I took a video while the game was running):
http://www.dailymotion.com/WireLink/video/12470236

Here is my program :

/////////////////////////////////////////////////////////////////////////

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL; // the pointer to the vertex buffer
LPDIRECT3DSURFACE9 z_buffer = NULL; // the pointer to the z-buffer

// mesh declarations
LPD3DXMESH meshCar; // define the mesh pointer
D3DMATERIAL9* material; // define the material object
DWORD numMaterials; // stores the number of materials in the mesh

// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
void init_light(void); // sets up the light and the material

LPD3DXMESH LoadMesh(LPD3DXMESH MeshName); // 3D Declarations/Loads the Meshes
void TransformMesh(LPD3DXMESH MeshName, float RotateAngle, float TranslateX, float TranslateY, float TranslateZ); // 3D Declarations/Loads the Meshes

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;

ZeroMemory(&wc, sizeof(WNDCLASSEX));

wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = "WindowClass1";

RegisterClassEx(&wc);

hWnd = CreateWindowEx(NULL,
"WindowClass1",
"Our Direct3D Program",
WS_EX_TOPMOST | WS_POPUP,
0, 0,
SCREEN_WIDTH, SCREEN_HEIGHT,
NULL,
NULL,
hInstance,
NULL);

ShowWindow(hWnd, nCmdShow);

// set up and initialize Direct3D
initD3D(hWnd);

// enter the main loop:

MSG msg;

while(TRUE)
{
DWORD starting_point = GetTickCount();

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;

TranslateMessage(&msg);
DispatchMessage(&msg);
}

render_frame();

// check the 'escape' key
if(KEY_DOWN(VK_ESCAPE))
PostMessage(hWnd, WM_DESTROY, 0, 0);

while ((GetTickCount() - starting_point) < 25);
}

// clean up DirectX and COM
cleanD3D();

return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}

return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION);

D3DPRESENT_PARAMETERS d3dpp;

ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = FALSE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;
d3dpp.EnableAutoDepthStencil = TRUE; // automatically run the z-buffer for us
d3dpp.AutoDepthStencilFormat = D3DFMT_D16; // 16-bit pixel format for the z-buffer

// create a device class using this information and the info from the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);

meshCar = LoadMesh(meshCar); // call the function to load the 3D Meshes

init_light(); // call the function to initialize the light and material

d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE); // turn on the 3D lighting
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE); // turn on the z-buffer
d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50)); // ambient light

return;
}


// this is the function used to render a single frame
void render_frame(void)
{

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

d3ddev->BeginScene();

// SET UP THE TRANSFORMS

D3DXMATRIX matView; // the view transform matrix
D3DXMatrixLookAtLH(&matView,
&D3DXVECTOR3 (0.0f, 4.0f, 14.0f), // the camera position
&D3DXVECTOR3 (0.0f, 0.0f, 0.0f), // the look-at position
&D3DXVECTOR3 (0.0f, 5.0f, 0.0f)); // the up direction
d3ddev->SetTransform(D3DTS_VIEW, &matView); // set the view transform to matView

D3DXMATRIX matProjection; // the projection transform matrix
D3DXMatrixPerspectiveFovLH(&matProjection,
D3DXToRadian(45), // the horizontal field of view
1.3f, // the aspect ratio
1.0f, // the near view-plane
100.0f); // the far view-plane
d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection

// A different car is displayed, but the same textures and the same Mesh are used every time.

static float index = 1.0f; index+=0.03f; // an ever-increasing float value
TransformMesh(meshCar, index, 0.0f, 0.0f, 0.0f); // 3D Declarations/Loads the Meshes
TransformMesh(meshCar, index, 0.0f, -3.0f, 0.0f); // 3D Declarations/Loads the Meshes
TransformMesh(meshCar, index, 0.0f, 3.0f, 0.0f); // 3D Declarations/Loads the Meshes
TransformMesh(meshCar, index, -3.5f, 0.0f, 0.0f); // 3D Declarations/Loads the Meshes
TransformMesh(meshCar, index, 3.5f, 0.0f, 0.0f); // 3D Declarations/Loads the Meshes

d3ddev->EndScene();

d3ddev->Present(NULL, NULL, NULL, NULL);

return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
meshCar->Release(); // close and release the spaceship mesh
d3ddev->Release(); // close and release the 3D device
d3d->Release(); // close and release Direct3D

return;
}

LPD3DXMESH LoadMesh(LPD3DXMESH MeshName)
{
LPD3DXBUFFER bufShipMaterial;

D3DXLoadMeshFromX("Car.x", // load this file
D3DXMESH_DYNAMIC, // load the mesh into system memory
d3ddev, // the Direct3D Device
NULL, // we aren't using adjacency
&bufShipMaterial, // put the materials here
NULL, // we aren't using effect instances
&numMaterials, // the number of materials in this model
&MeshName); // put the mesh here

// retrieve the pointer to the buffer containing the material information
D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

// create a new material buffer for each material in the mesh
material = new D3DMATERIAL9[numMaterials];

for(DWORD i = 0; i < numMaterials; i++) // for each material...
{
material = tempMaterials.MatD3D; // get the material info
material.Ambient = material.Diffuse; // make ambient the same as diffuse
}

return MeshName;
}

// this is the function that sets up the lights and materials
void init_light(void)
{
D3DLIGHT9 light; // create the light struct

ZeroMemory(&light, sizeof(light)); // clear out the struct for use
light.Type = D3DLIGHT_DIRECTIONAL; // make the light type 'directional light'
light.Diffuse.r = 0.5f; // .5 red
light.Diffuse.g = 0.5f; // .5 green
light.Diffuse.b = 0.5f; // .5 blue
light.Diffuse.a = 1.0f; // full alpha (we'll get to that soon)

D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f}; // the direction of the light
light.Direction = vecDirection; // set the direction

d3ddev->SetLight(0, &light); // send the light struct properties to light #0
d3ddev->LightEnable(0, TRUE); // turn on light #0

return;
}


void TransformMesh(LPD3DXMESH MeshName, float RotateAngle, float TranslateX, float TranslateY, float TranslateZ) // 3D Declarations/Loads the Meshes
{

D3DXMATRIX matRotateY; // a matrix to store the rotation for each triangle
D3DXMATRIX matTranslate; // a matrix to store the translation for each triangle

D3DXMatrixTranslation(&matTranslate, TranslateX, TranslateY, TranslateZ); // the rotation matrix
D3DXMatrixRotationY(&matRotateY, RotateAngle); // the rotation matrix

d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY * matTranslate)); // set the world transform

// draw the mesh car
for(DWORD i = 0; i < numMaterials; i++) // loop through each subset
{
d3ddev->SetMaterial(&material); // set the material for the subset
MeshName->DrawSubset(i); // draw the subset
}

return;

}

/////////////////////////////////////////////////////////////////////////

Games like GTA3 work properly on my computer. So why can't I display big mesh files with my program? How do game creators use meshes?
One thing i noticed is you are calculating the projection matrix every frame, it will most likely never change so you should calculate it only once and store somewhere, should give a wee boost :p

Edit:
Same can sort of be said for the view tranformation matrix but that might be changing more in the future depending on your goal with the code.

[Edited by - Guthur on November 16, 2008 11:02:00 AM]
Innovation not reiterationIf at any point I look as if I know what I'm doing don't worry it was probably an accident.
Thanks for your help.

I would like to begin a little 3D game. So, later, this program will have to calculate the projection matrix and display the meshes every frame.

And so, that's why I need my program to load big mesh files (the game will have to display cars, characters and buildings at the same time).

In fact, I was just asking if there was a way to improve performance, or a different way to display 3D models made with 3D Studio Max.
One important thing I forgot to mention is that you can optimize the mesh using Optimize() and OptimizeInPlace() (these are member functions of ID3DXMesh, see the SDK for details). I would try OptimizeInPlace() with D3DXMESHOPT_ATTRSORT, combined with either D3DXMESHOPT_STRIPREORDER or D3DXMESHOPT_VERTEXCACHE (these two are mutually exclusive). Try both combinations to see what gives you the best performance.

Also note that the SDK contains a sample called OptimizedMesh which allows you to switch between different optimization methods in real-time.

I also think that 200,000 vertices is way too much for just one car model. I think it should be like a tenth of that. Consider that you will probably have a lot of models to render, as well as world geometry like a terrain.
200K vertices is ludicrous. Try to get it less than 5K.
Quote:Original post by Wire-Z
I would like to begin a little 3D game. So, later, this program will have to calculate the projection matrix and display the meshes every frame.


Still wont change my friend, even in the most interactive 3D simulation ever :)

Only time you will probably want to will be if the screen resolution changes or if you want maybe an ortho projection for some sort of sprite overlay, i think this would be a usage, but even then its only probably two you would want :p.

You will have to compute a model transformation matrix every turn which will include your models location and orientation in the world, assuming its moving, and then a view transformation to move the world to the orientation you wish to render. But the projection will stay the same the vast majority of runtime.

I am not vastly experienced in 3D programming so if i have made an error please someone point it out, but i'm pretty confident thats a fair assessment;

But like Nypyren said thats going to have to be a killer render pipe to get that amount of verts pumped out, especially if you want any sort of eye candy :).
Innovation not reiterationIf at any point I look as if I know what I'm doing don't worry it was probably an accident.
Yes, sorry Guthur. I begin with DirectX, and I thought that the projection matrix was the camera position. So, I don't need to calculate it every frame, I will only do it in the beginning of my program.

Then, I won't use such a mesh in my game, 200k vertices is too much for a simple car. But the game will have to load 5-6 cars, characters, buildings and the terrain at the same time, and so finally more than 100-200k vertices. That's why I used this model.

Gage64, I used the OptimizeInPlace() function, I also used the "Optimize Mesh" option in Panda Exporter and the program is now really faster. That's about what I was waiting for.

So, thank you for your help.

(I'm still interested if there are other ways to improve performance)
Just out of curiosity, what sort of hardware is this running on?

This topic is closed to new replies.

Advertisement