Thread: Creating a class to load and render .x files

  1. #1
    george7378
    Guest

    Creating a class to load and render .x files

    Hi everyone,

    I'm doing some Direct3D programming and I'm at the point where I can load .x files and display them in the environment using the techniques described on this page:

    Load X Simply

    I'm also cross-referencing with the example given in the DirectX code samples. In an attempt to make the process of loading and rendering a .x file easier, I've created a class to do it for me. I'm just going to go ahead and post my class here:

    Code:
    class MeshObject { //Class for holding meshes
    private:
    	LPD3DXBUFFER materialBuffer; //Holds the materials in the mesh
    	DWORD numMaterials;  //No. of materials in the mesh
    	LPD3DXMESH texturedMesh; //Our mesh object
    	D3DMATERIAL9 *meshMaterials; //Create an array of materials for the object
    	LPDIRECT3DTEXTURE9 *meshTextures; //Create an array of textures for the object
    public:
    	std::string filename; LPDIRECT3DDEVICE9 renderdev;
    	MeshObject(std::string name, LPDIRECT3DDEVICE9 dev){filename = name; renderdev = dev;};
    
    	void MeshObjectLoad(){ //Load the mesh
    	D3DXLoadMeshFromX(filename.c_str(), D3DXMESH_SYSTEMMEM, renderdev, NULL, &materialBuffer,NULL, &numMaterials, &texturedMesh);
    	D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)materialBuffer->GetBufferPointer(); //Get pointer to materialBuffer
    	meshMaterials = new D3DMATERIAL9[numMaterials]; //Create an array of materials for the mesh
    	meshTextures = new LPDIRECT3DTEXTURE9[numMaterials]; //Create an array of textures for the mesh
    	for (DWORD i=0; i<numMaterials; i++){ //For all the materials in the .x file
    		meshMaterials[i] = d3dxMaterials[i].MatD3D; // Copy the material
    		meshMaterials[i].Ambient = meshMaterials[i].Diffuse; //Set the ambient color for the material
    		meshTextures[i] = NULL; //Assume no texture exists
    		if (d3dxMaterials[i].pTextureFilename) 	// Create the texture if it exists
    			{D3DXCreateTextureFromFile(renderdev, d3dxMaterials[i].pTextureFilename, &meshTextures[i]);}}}
    	
    	void MeshObjectRender(){ //Render the mesh
    	for (DWORD i=0; i<numMaterials; i++){ //For all the differently textured sections of the .x file
    		renderdev->SetMaterial(&meshMaterials[i]);	// Set the material and texture for this subset
    		renderdev->SetTexture(0,meshTextures[i]);    
    		texturedMesh->DrawSubset(i);}} // Draw the mesh subset
    
    	void MeshObjectClean(){ //Cleanup for mesh
    		materialBuffer->Release();}
    };
    As you can see, there are 3 functions - I call MeshObject.MeshObjectLoad() when I initialise Direct3D, I call MeshObject.MeshObjectRender() when I render my frame, and I call MeshObject.MeshObjectClean() when I cleanup after closing my program. When I try and create an instance of this class (just underneath the class definition itself):

    Code:
    MeshObject meshTorus("Torus.x", d3ddev);
    ... it compiles fine but causes a program crash. I've narrowed this down to the following line in the MeshObjectLoad() function:

    Code:
    D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)materialBuffer->GetBufferPointer(); //Get pointer to materialBuffer
    If I remove this line and everything after it and use the class, the program runs fine (apart from the fact that I don't have my Torus.x object loaded into it). So my question is:

    What is wrong with this line to cause the program to crash? Can anyone tell me of a better way to define my class to load .x files? I'd like to know if I'm doing something horribly wrong, but as far as I can tell, this should work. It's the same basic structure as the DirectX mesh tutorial code in the samples directory, but rather I've put the mesh loading/rendering code into a class so I don't have to repeat it all when I want to load more than one object.

    Thanks a lot!

  2. #2
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    • Prefer placement of larger method bodies in the cpp rather than the header file. Only inline very small methods.
    • D3DXLoadMeshFromX() is a COM function and as such returns its value on the stack. If the method does not find the specified mesh file it will return null for the output mesh. You do not check this condition and assume that the mesh is valid and proceed to dereference it. This is a crash waiting to happen. The method will return invalid call (if I remember correctly) and the output mesh will be null.
    • You are creating textures inside of this mesh class. That is fine for a small tutorial but very very bad for a larger system. Textures are not cheap resources and textures should be in video memory if they are going to be used. Imagine we have 50 barrels that all use the same texture. Your code will store the texture 50 times in memory...yet the textures do not differ from one another in any way. This is a complete waste of memory.
    • Your cleanup code is inadequate and will cause both COM resource leaks and CRT memory leaks. texturedMesh and meshTextures must be Released() by COM. In addition to this meshTextures must also be deleted. Your code will allocate space on the heap for pointers to texture interfaces yet you never clean this array up. Note that calling Release() on the textures cleans up the underlying COM interface but does not clean up the texture array.
      meshMaterials is a CRT array and must be deleted.
    • Ambient materials and Diffuse materials are not at all alike in any way. Ambient light is the lowest amount of light available in a scene. Diffuse light is the light created by the scattering of light rays off of various surfaces. If the ambient material is set to the diffuse material then you essentially have eliminated ambient light altogether which means you will not be able to do DOT3 lighting b/c your mesh will not be affected by ambient light.
    • The constructor for the class does not check to see if the pointer to the device interface pointer is valid. Secondary methods also do not check this pointer and as such these are crashes waiting to happen. I cannot stress how important this is. ALWAYS check your raw pointers. Prefer to use const references since references require an instance of an object and therefore cannot be null.
    • There is not enough abstraction in this class. It simpy wraps Direct3D calls which differ from version to version. If the version of Direct3D changes this code will break and you will be forced to implement an entirely new class with much the same code ....a maintenance nightmare waiting to happen. The Direct3D API needs to be abstracted more in this code to be useful. I realize that this is a simple tutorial application but please understand that in order to move on from the tutorial code you must think about these things. I offer that you cannot and will not produce any game by only using tutorial type code.
    • In general, before you dereference any raw pointer in C++ you should check for null. In the line of code you post you are not checking to ensure that materialBuffer is valid and proceed to dereference it. If it is null this line will crash.
    • Remove the string literal and replace with an ID or a constant.


    I recommend you create an asset finder class that will find assets for you. Pass the asset file name or ID to this class and let it retrieve the path to the file. Pass this path into your methods that load the mesh. If you hardcode mesh files and paths into your code you will code yourself into a corner and into a huge mess where certain assets cannot be found and other can be.
    Last edited by VirtualAce; 01-22-2013 at 07:57 PM.

  3. #3
    george7378
    Guest
    Quote Originally Posted by VirtualAce View Post
    • Prefer placement of larger method bodies in the cpp rather than the header file. Only inline very small methods.
    • D3DXLoadMeshFromX() is a COM function and as such returns its value on the stack. If the method does not find the specified mesh file it will return null for the output mesh. You do not check this condition and assume that the mesh is valid and proceed to dereference it. This is a crash waiting to happen. The method will return invalid call (if I remember correctly) and the output mesh will be null.
    • You are creating textures inside of this mesh class. That is fine for a small tutorial but very very bad for a larger system. Textures are not cheap resources and textures should be in video memory if they are going to be used. Imagine we have 50 barrels that all use the same texture. Your code will store the texture 50 times in memory...yet the textures do not differ from one another in any way. This is a complete waste of memory.
    • Your cleanup code is inadequate and will cause both COM resource leaks and CRT memory leaks. texturedMesh and meshTextures must be Released() by COM. In addition to this meshTextures must also be deleted. Your code will allocate space on the heap for pointers to texture interfaces yet you never clean this array up. Note that calling Release() on the textures cleans up the underlying COM interface but does not clean up the texture array.
      meshMaterials is a CRT array and must be deleted.
    • Ambient materials and Diffuse materials are not at all alike in any way. Ambient light is the lowest amount of light available in a scene. Diffuse light is the light created by the scattering of light rays off of various surfaces. If the ambient material is set to the diffuse material then you essentially have eliminated ambient light altogether which means you will not be able to do DOT3 lighting b/c your mesh will not be affected by ambient light.
    • The constructor for the class does not check to see if the pointer to the device interface pointer is valid. Secondary methods also do not check this pointer and as such these are crashes waiting to happen. I cannot stress how important this is. ALWAYS check your raw pointers. Prefer to use const references since references require an instance of an object and therefore cannot be null.
    • There is not enough abstraction in this class. It simpy wraps Direct3D calls which differ from version to version. If the version of Direct3D changes this code will break and you will be forced to implement an entirely new class with much the same code ....a maintenance nightmare waiting to happen. The Direct3D API needs to be abstracted more in this code to be useful. I realize that this is a simple tutorial application but please understand that in order to move on from the tutorial code you must think about these things. I offer that you cannot and will not produce any game by only using tutorial type code.
    • In general, before you dereference any raw pointer in C++ you should check for null. In the line of code you post you are not checking to ensure that materialBuffer is valid and proceed to dereference it. If it is null this line will crash.
    • Remove the string literal and replace with an ID or a constant.
    Thanks a lot for this answer! I should probably just say that at this point, I haven't really bothered to optimise it for error checking/cleanup (as you can see!) and also:

    - I defined this class at the top of the .cpp file rather than in a header.
    - I'll make sure to check that it returns properly - I guess I would have to define MeshObjectLoad() as a bool and return false if it fails. I'll do that once I figure out why this crash is happening (it's not because the model file is missing, because it's there and I've used it before).
    - I already knew that I was creating textures inside the class which is why I created a separate function to actually do the rendering. Couldn't I just define the object once and then re-use the MeshObjectRender() function if I wanted to duplicate the object? (Or am I missing the problem?)
    - OK, I will admit that I am not exactly sure about what needs to be released() and what needs to be deleted. I have been cross referencing a lot of different source codes and tutorials while I've been trying to learn D3D and they all do it differently. So basically, you're saying that I need to release() every COM object I create and I need to delete those which are arrays? If there's any more guidance you could recommend on how to cleanup properly then it would be very useful
    - For the ambient light thing I was just following what it does in the sample code which comes with the SDK - it contains the following line:

    Code:
    g_pMeshMaterials[i].Ambient = g_pMeshMaterials[i].Diffuse;

    ...which seems to be taking the diffuse data from the model and setting it as the ambient data for the loaded materials. If I disable this then I don't seem to see any ambient light when I run the program.
    - So if I wanted to make the class more compatible with other versions of Direct3D, would I need to first check which version is running (does this have something to do with Direct3DCreate9(D3D_SDK_VERSION);?) and based on that I would need to determine which function to use? So something like:

    //Code here to find out the version of direct3d
    if (direct3dversion == oneVersion)
    {run this function to, for example, load the mesh}
    else if (direct3dversion == anotherVersion)
    {run a different function to load the mesh}

    I'd be interested in reading more about this, so again, if there are any resources you could point me to, it would be much appreciated

    Thanks again for your help, this has given me a lot to think about.
    Last edited by george7378; 01-23-2013 at 08:06 AM.

  4. #4
    george7378
    Guest
    Also, quick question - how would I go about checking the raw pointers to see if they're valid? To check things like D3DXLoadMeshFromX() I just check to see if they return D3D_OK, but I can't do this with my pointer. Thanks!

  5. #5
    george7378
    Guest
    I think I might have cracked it - the function MeshObjectLoad() works when I create my MeshObject AFTER the CreateDevice() has been called. I changed the MeshObjectLoad() to this:

    Code:
    bool MeshObjectLoad(LPDIRECT3DDEVICE9 dev){
    	renderdev = dev;
    //rest of function
    Last edited by george7378; 01-24-2013 at 01:55 PM.

  6. #6
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    You must have a valid Direct3D device before trying to use any of its interfaces or interfaces that use it.

  7. #7
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    - I defined this class at the top of the .cpp file rather than in a header.
    In almost all cases your class declaration should be in the header file and its implementation should be in the source or cpp file. There are exceptions to this rule but this is not one of them.

    - I'll make sure to check that it returns properly - I guess I would have to define MeshObjectLoad() as a bool and return false if it fails.
    I'll do that once I figure out why this crash is happening (it's not because the model file is missing, because it's there and I've used it before).
    When I am issued a defect that I am to investigate the first thing I do is attempt to find the most obvious issues with the code. Nine times out of ten the assumptions we make about what is happening is not what is happening. Sometimes we are looking at the resolution right in the face yet we engineers often want to go trotting through the weeds to find a solution. Sometimes the simplest answer is the correct one. The first thing you want to do when debugging your code is account for these simple easy to catch edge cases. Once you realize this is not the problem you can cross it off and move on to the next one. If you do not have a specified process for debugging your code then it makes the debugging portion of software engineering quite an undertaking.

    - I already knew that I was creating textures inside the class which is why I created a separate function to actually do the rendering. Couldn't I just define the object once and then re-use the MeshObjectRender() function if I wanted to duplicate the object? (Or am I missing the problem?)
    You can do anything you want. The main issue here is the creation of a resource inside of an object that can be duplicated which results in duplicated resources that do not need to be duplicated. It is not related to your problem but it is an issue you will want to address later. Perhaps it was bad taste to bring this issue up at this time since it is not related to your specific issue. My apologies.

    - OK, I will admit that I am not exactly sure about what needs to be released() and what needs to be deleted. I have been cross referencing a lot of different source codes and tutorials while I've been trying to learn D3D and they all do it differently. So basically, you're saying that I need to release() every COM object I create and I need to delete those which are arrays? If there's any more guidance you could recommend on how to cleanup properly then it would be very useful
    Regardless of tutorial code which is usually fraught with resource leaks and cleanup issues you should always clean up your resources and memory footprint. The SDK is very clear which resources need to be released. I will give an example of ones I know of:
    • Vertex and index buffers
    • Meshes - note that ID3DXMesh releases its own vertex and index buffers so you do not have to
    • Shaders / effects
    • Any surfaces created from IDirect3DTexture interface calls...and any surfaces created by other means
    • Textures - (which are really just surfaces)
    • ID3DXFont objects
    • 3D font resources
    • IDirect3D9 object
    • IDirect3DDevice9 object

    The general rule is if its an interface pointer and it was created by calling into a Direct3D interface and the pointer was returned to you....then you must release it. Keep in mind that if you release the IDirect3DDevice9 object prior to cleaning up correctly...all resources that have not been cleaned up will leak...you must clean up prior to releasing the device. To debug resource leaks go into the DirectX control panel (SDK->DirectX Utilities->DirectX control panel) and set it to use debug runtimes. Set the debug slider to output more debug information (usually the 3rd notch from the left works). Run the program. The output will tell you if there are leaks and how many. It will also give you an allocation ID. Go back to the control panel and check break on memory leaks. Type the allocation ID into the BreakOnAllocID textbox. Run the program. The program will break on the specified allocation ID which indicates the memory leak. Keep in mind you will have to do this many times since some of the allocation may not make sense or are deeply embedded within Direct3D source code. Eventually you will resolve all your leaks using this method. Keep in mind the first 20 to 50 leaks will simply be the device not cleaning up correctly and won't make much sense when you break on them. General rule - If the program breaks on code that is not yours.....try another allocation ID.


    - For the ambient light thing I was just following what it does in the sample code which comes with the SDK - it contains the following line
    They are just trying to get something up and running quickly. This is ok for now but I thought I would bring it to your attention. Ambient light is the lowest level of light in the scene and diffuse is the diffuse or scattered light within the scene. Setting ambient to diffuse eliminates the ambient component and makes the lowest level of light in the scene.....the diffuse light. If you attempt to do lighting with this setup you will have issues. For a tutorial set ambient to dark white light - D3DXCOLOR(0.3f,0.3f,0.3f,1.0f) and diffuse. to D3DXCOLOR(1.0f,1.0f,1.0f,1.0f). That should give you a decent difference between the two. If you are using the fixed function pipeline (no shaders) then make sure specular light is off. It is off by default so it should be off if you have not set it via IDirect3DDevice9::SetRenderState().

    Best of luck learning Direct3D. I highly recommend this book to get you started:
    http://www.amazon.com/Introduction-G...+with+directx9

    There are two more editions of this book related to DirectX 10 and DirectX 11. There is a version for DirectX9 prior to the one I have listed but it uses the fixed function pipeline. There really is no sense in learning this pipeline since it is deprecated in newer versions of DirectX. You would do well to being learning about shaders and the programmable pipeline right from the start. You will be better prepared for DirectX 10 and 11.
    Last edited by VirtualAce; 01-27-2013 at 02:57 PM.

  8. #8
    george7378
    Guest
    That was an awesome reply, thanks for all your help! I think I've got my code closer to something that would be acceptable in terms of error checking, cleanup, etc... and looks less like raw tutorial code. I've decided to put a comment (of my own) on each line too, so that I know what it's doing rather than just lifting it straight from the samples and putting it into my program. I've been experimenting with alpha transparency too, along with getting my camera to follow my terrain, and the results have been OK so far:

    Creating a class to load and render .x files-direct3d-first-person-camera-2013-01-29-23-30-20-68-jpg

    I suppose there's only so far I can get by looking at online tutorials - they all seem to follow the same path of (create a window)->(draw a triangle)->(use the z-buffer)->(import meshes)->(add textures)... etc. I'll think about getting a book if I get to a point where I can't learn any more from websites. At the moment I've got studies, etc... and I don't know if I'll be getting into Direct3D very seriously, but it's something I've wanted to do for a while. Thanks again!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. How to load .obj files?
    By h3ro in forum Game Programming
    Replies: 4
    Last Post: 05-14-2007, 05:27 AM
  2. How to Load .lib files with lcc-32 compiler
    By maththeorylvr in forum C Programming
    Replies: 3
    Last Post: 10-27-2005, 06:55 PM
  3. Anyone want load 3DS files in BCB?
    By GletchM in forum C++ Programming
    Replies: 1
    Last Post: 05-18-2005, 06:50 AM
  4. load a class problem
    By Ivan! in forum C++ Programming
    Replies: 7
    Last Post: 03-21-2003, 02:26 PM
  5. creating class, and linking files
    By JCK in forum C++ Programming
    Replies: 12
    Last Post: 12-08-2002, 02:45 PM