Smart pointers in a DirectX game

Started by
6 comments, last by Dan Caranfil 18 years, 1 month ago
I'm making a game that will use a lot of LPD3DXMESH meshes. After doing some research I found claims that the best method to avoid COM-specific memory leaks is using smart pointers. So I have this questions: 1. Is is easier to use smart pointers instead of AddRef - Release method? 2. Since I can't write a smart pointer myself yet, I plan using ATL CComPtr class. Do this instructions have to be followed in each ATL + DirectX project? 3. I'm staking my meshes in a STL list and want to be able to modify textures for each mesh in the stack. I managed putting up a copy constructor that gives me this behavior I want. My question is how will the smart pointer affect the copy constructor and do I need a copy constructor at all if I plan using a smart pointer?
Advertisement
Quote:Original post by Dan Caranfil
1. Is is easier to use smart pointers instead of AddRef - Release method?


Yes because calls to addref/release are automated.

Quote:Original post by Dan Caranfil
2. Since I can't write a smart pointer myself yet, I plan using ATL CComPtr class. Do this instructions have to be followed in each ATL + DirectX project?


I don't know. You can use boost::intrusive_ptr with COM/COM like objects. It's very simple to use.

Quote:Original post by Dan Caranfil
3. I'm staking my meshes in a STL list and want to be able to modify textures for each mesh in the stack. I managed putting up a copy constructor that gives me this behavior I want. My question is how will the smart pointer affect the copy constructor and do I need a copy constructor at all if I plan using a smart pointer?


I don't understand what you're trying to say so i may have understood you wrong but typical smart pointer strategies do nothing with copy constructors on there own. The only strategy i can think of where copying is involved is copy-on-write (COW) but this isn't applicable in this context.

Also unless you declare a copy constructor private/protected you always have a copy constructor defined either implicitly compiler generated that does a memberwise (or bitwise for POD-struct types) copy or explicitly you define the copy constructor yourself.

No you don't need to user-define a copy constructor to use most smart pointers.
Quote:Original post by Dan Caranfil
2. Since I can't write a smart pointer myself yet, I plan using ATL CComPtr class. Do this instructions have to be followed in each ATL + DirectX project?

No, CComPtr is a very simple class. Just include <atlbase.h> and go ahead.

Quote:3. I'm staking my meshes in a STL list and want to be able to modify textures for each mesh in the stack. I managed putting up a copy constructor that gives me this behavior I want. My question is how will the smart pointer affect the copy constructor and do I need a copy constructor at all if I plan using a smart pointer?

You have a list of meshes. Where do you store the textures for each mesh? What does your copy constructor do?
I don't really follow what you're saying or what the problem is, but if you're asking about what copying a smart COM pointer does: It will typically increment the reference count of the underlying interface.

Thanks for your replies

Quote:
You have a list of meshes. Where do you store the textures for each mesh? What does your copy constructor do?
I don't really follow what you're saying or what the problem is, but if you're asking about what copying a smart COM pointer does: It will typically increment the reference count of the underlying interface.


I'm loading a mesh with textures from HDD and then do 'push_back' to place it in the stack. I do this many times for same mesh. This way I get many instances of the mesh in the container. Then what I want to do is change a texture on a particular mesh instance. If I don't use a copy constructor the texture is being applied on all instances of same type.
If I use a copy constructor I can change the textures for each mesh individually.

Here's my mesh class:
function definition:
#include "MeshObject.h"#include "FileUtil.h"#include "../common/dhUtility.h"#include "criptare.h"#include "utility.h"MeshObjMold::MeshObjMold(){	m_dwNumMaterials = 0;	m_pMaterialsBuffer = NULL;	m_pMaterials = NULL;	m_pTextures = NULL;	m_pMesh = NULL;	Clone = false;}MeshObjMold::~MeshObjMold(){	string Message;	string war = "WARNING: Mesh Object was not released. Mesh: ";	Message = war + Name;if(Full){	Log(Message.c_str());	Log("\n");}}HRESULT MeshObjMold::Initialise(string Id, char *szMeshFile, LPDIRECT3DDEVICE9 &pDevice){    Name = Id;    Full = true;	D3Ddev = pDevice;	HRESULT rslt = D3D_OK;		// First, load the .X file into a mesh object, and fill out the materials buffer	rslt = D3DXLoadMeshFromX(szMeshFile, D3DXMESH_MANAGED, pDevice, NULL,							 &m_pMaterialsBuffer, NULL, &m_dwNumMaterials, &m_pMesh);	if(FAILED(rslt)) {		LogDX("Failed to load mesh from file",rslt);	}	// Next, get a pointer to the first element of data in the materials buffer, ready	// to read out.	D3DXMATERIAL* pMaterials = (D3DXMATERIAL*)m_pMaterialsBuffer->GetBufferPointer();	// Create 2 arrays, 1 for textures and 1 for materials. D3DXLoadMeshFromX puts the	// number of materials/textures (always the same) into m_dwNumMaterials above.	m_pMaterials = new D3DMATERIAL9[m_dwNumMaterials];	m_pTextures = new LPDIRECT3DTEXTURE9[m_dwNumMaterials];	// Next, iterate through the pMaterials buffer.	{	for(int iCount = 0; iCount < (int)m_dwNumMaterials; iCount++)	{		// For each material buffer element, copy the D3DMATERIAL into this class' array.		m_pMaterials[iCount] = pMaterials[iCount].MatD3D;				m_pMaterials[iCount].Ambient = m_pMaterials[iCount].Diffuse;		if (IsFileExist2(pMaterials[iCount].pTextureFilename)) // Check if there is a file under that name on disk and if yes make a texture out of it		{			if (DEBUG_V){Log("Loading texture from file: ");Log(pMaterials[iCount].pTextureFilename);Log("\n");}							//Decrypt the texture file					decrypt(pMaterials[iCount].pTextureFilename,50);  								// Finally, create a texture from the file specified in the mateirals buffer and put		    // it in the m_pTextures array.					rslt=D3DXCreateTextureFromFile(pDevice, pMaterials[iCount].pTextureFilename, &m_pTextures[iCount]);						if (DEBUG_V){if(FAILED(rslt)) {Log("\n");LogDX("Failed to load mesh texture from file",rslt); }}	            			 //Encrypt the texture file				   encrypt(pMaterials[iCount].pTextureFilename,50);			if (DEBUG_V)			  {Log("Texture successifully loaded");Log("\n");}		}			else 			{   if (DEBUG_V)  //DEBUG_V code VV				{				Log("-------------------------------!ERROR!--------------------------------- \n");								if (pMaterials[iCount].pTextureFilename != NULL)				{				Log("Could not load file: ");				Log(pMaterials[iCount].pTextureFilename);				Log("\n");				Log("-------------------------------------------------------------------------\n");				}				if (pMaterials[iCount].pTextureFilename == NULL)				{				Log("Bad X file format ");				Log("\n");				Log("-------------------------------------------------------------------------\n");								}				}				//DEBUG_V code ^^											}		 					}	}	// m_pMaterialsBuffer is a COM object created by D3DXLoadMeshFromX. We only need it when	// reading the data from the X file - now that we've populated our mesh, materials and	// textures we can get rid of it.	m_pMaterialsBuffer->Release();	m_pMaterialsBuffer = NULL;return D3D_OK;}MeshObjMold::MeshObjMold(const MeshObjMold &OrigM){	Clone =true; // Mark object as clone (this is required for the ShutDown -> the m_pMesh is not released for clones;	D3Ddev = OrigM.D3Ddev;	Name = OrigM.Name;	m_dwNumMaterials = OrigM.m_dwNumMaterials;				m_pMesh = OrigM.m_pMesh;			m_pMaterials = new D3DMATERIAL9[m_dwNumMaterials];	m_pTextures = new LPDIRECT3DTEXTURE9[m_dwNumMaterials];    	for(int iCount = 0; iCount < (int)m_dwNumMaterials; iCount++)	{		m_pMaterials[iCount] = OrigM.m_pMaterials[iCount];		m_pTextures[iCount]  = OrigM.m_pTextures[iCount];	}	Identity = OrigM.Identity;}HRESULT MeshObjMold::ShutDown(){	Full = false;	Log("Shuting down ");	Log(Name.c_str());	Log("\n"); 		// Free the materials array	if(m_pMaterials)		delete [] m_pMaterials;	// If there are textures...	if(m_pTextures)	{		// ...index through the texture array & release the texture object		for(int iCount = 0; iCount < (int)m_dwNumMaterials; iCount++)		{			if(m_pTextures[iCount] != NULL)			{			m_pTextures[iCount]->Release();			m_pTextures[iCount] = NULL;			}		}		// Don't forget to delete the array as well		delete [] m_pTextures;	}	// Release the mesh object		if(m_pMesh)	{	m_pMesh->Release();	m_pMesh = NULL;	}		m_dwNumMaterials = 0;	m_pMaterialsBuffer = NULL;	m_pMaterials = NULL;		return D3D_OK;}HRESULT MeshObjMold::Render(LPDIRECT3DDEVICE9& pDevice){	HRESULT rslt = S_OK;	// Rendering a mesh is very easy. Simply iterate through each material...	{	for(int iCount = 0; iCount < (int)m_dwNumMaterials; iCount++)	{		//... set the material and texture in the normal way		rslt = D3Ddev->SetMaterial(&m_pMaterials[iCount]);		if(FAILED(rslt))  { LogDX("Set Material Failed",rslt);}        		rslt = D3Ddev->SetTexture(0, m_pTextures[iCount]);		if(FAILED(rslt)) {LogDX("Set Texture Failed",rslt);}				//... then call ID3DXMesh::DrawSubset to draw the vertices in the mesh		// that have this material and texture applied to them.		rslt = m_pMesh->DrawSubset(iCount);		if(FAILED(rslt)) { LogDX("Draw Subset Failed",rslt);}	  }		}	return S_OK;}


class header file
#ifndef MESHOBJECT_H#define MESHOBJECT_H#include "utility.h"#include <string>using namespace std;class MeshObjMold{private:    				// The actual mesh object		LPD3DXBUFFER			m_pMaterialsBuffer;			// Receives the materials when the X file is loaded	D3DMATERIAL9*			m_pMaterials;				// Array of materials extracted from m_pMaterialsBuffer		DWORD					m_dwNumMaterials;			// The number of materials in this mesh    bool Clone;	bool Full;   		public:	string Name,Identity;	LPDIRECT3DDEVICE9  D3Ddev;	LPDIRECT3DTEXTURE9*		m_pTextures;// Array of textures extracted from m_pMaterialsBuffer    	MeshObjMold( const MeshObjMold & m);	LPD3DXMESH	 m_pMesh;	MeshObjMold();	~MeshObjMold();	HRESULT Initialise(string Id,char* szMeshFile, LPDIRECT3DDEVICE9& pDevice);    HRESULT Render(LPDIRECT3DDEVICE9& pDevice);	HRESULT ShutDown();	};#endif


To change a texture I do this:
list <ScnItm> ObjectList;  // ScnItm inherits from MeshObjMoldScnItm ColorO[5];//some codefor (list <ScnItm>::iterator i =  ObjectList.begin(); i != ObjectList.end(); ++i) {if (someting)i->m_pTextures[0] = ColorO[0].m_pTextures[0];}


[Edited by - Dan Caranfil on March 13, 2006 6:30:39 AM]
Quote:Original post by Dan Caranfil
I'm loading a mesh with textures from HDD and then do 'push_back' to place it in the stack. I do this many times for same mesh. This way I get many instances of the mesh in the container. Then what I want to do is change a texture on a particular mesh instance. If I don't use a copy constructor the texture is being applied on all instances of same type.
If I use a copy constructor I can change the textures for each mesh individually.


Well of-course, like i mentioned already all user-defined types get a copy constructor, assignment operator and destructor either implicitly or explicitly unless otherwise explicitly declared private/protected.

When implicitly defined (compiler generated) the copy constructor & assignment operator do a memberwise (or in somecases bitwise) copy/assignment for (smart) pointer members this is a shallow copy, a copy of address.

The standard library containers expect the contained type to at the very least be an assignable type, they do a copy of new elements.

So either you disable value/copy-semantics of your mesh class and store (smart) pointers into std::list or use boost::ptr_list or define what it means to copy/assign by explicily defining a copy constructor and assignment operator and destructor.

On a side note don't use C-style dynamic arrays use std::vector, simply no excuse here. Use constructor initializer lists, you're not doing initialization in your constructors, you're doing an assignment operation there not equivalent. Pass std::string by (constant) reference to avoid redundant copies. Try to use the standard library generic algorithms over explicict hand-written loops with containers (even for C-style arrays). Lastly don't dump entire namespaces into global namespace.
Quote:Original post by Dan Caranfil2. Since I can't write a smart pointer myself yet, I plan using ATL CComPtr class. Do this instructions have to be followed in each ATL + DirectX project?
You could also try boost::shared_ptr

F-R-E-D F-R-E-D-B-U-R...G-E-R! - Yes!
Quote:Original post by DigiDude
Quote:Original post by Dan Caranfil2. Since I can't write a smart pointer myself yet, I plan using ATL CComPtr class. Do this instructions have to be followed in each ATL + DirectX project?
You could also try boost::shared_ptr


Not exactly the greatest idea, COM/COM like objects already have an embedded reference count so using boost::/std::tr1::shared_ptr just adds more, unnecessary overhead, in this case it's better to use boost::intrusive_ptr it's specifically designed to work with objects that already have an embedded reference count.
snk_kid: Thanks for advices.

This topic is closed to new replies.

Advertisement