Smart pointers in a DirectX game
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?
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
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:
class header file
To change a texture I do this:
[Edited by - Dan Caranfil on March 13, 2006 6:30:39 AM]
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
Quote:Original post by DigiDudeQuote: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.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement