I googled around a bit and I saw multiple articles for D3D9 that talks exactly about using D3DUSAGE_WRITEONLY together with locking it with read only.
All of them say that the behaviour then is undocumented and not recommended, as the memory there might be invalid.
However, some users even noted that they have never seen it become invalid, but that it should still be avoided since on some hardware it might happen.
So this is an issue that needs to be solved by users in that case.
Lets say that you want to pick against all objects in your scene, does that mean that setVertexBufferPolicy/setIndexBufferPolicy must be set to use shadow buffers for all meshes before they are loaded in?
When I do this, the meshes that gets loaded are sometimes actually getting edited with their mUsage variable (for Direct3D9).
This is because when reading a mesh for Direct3D9, the function MeshSerializerImpl::readGeometryVertexBuffer calls D3D9HardwareBufferManager::createVertexBuffer, which disables shadow buffers and overwrites the usage to 1 (HBU_STATIC/HBU_GPU_TO_CPU).
In Direct3D11 for createVertexBuffer, it instead keeps its usage of 5 (HBU_STATIC_WRITE_ONLY/HBU_GPU_ONLY) and gets a real shadow buffer.
I also verified this by going into the lock function and placing a breakpoint when trying to read from the shadow buffer, which is only ever does for Direct3D11.
I guess this is intended though, but it also means that in Direct3D9, meshes are not as optimized for rendering as they were before with 5 (HBU_STATIC_WRITE_ONLY/HBU_GPU_ONLY)?
Is there some way of cloning a mesh and temporarily just reading the vertex/index data instead of always having the mesh using a shadow buffer and also avoiding a usage of 1 (HBU_STATIC/HBU_GPU_TO_CPU) for Direct3D9?
That way, you could load in the data into memory just once and then delete the temporary mesh, without having all meshes possibly get lower performance because of the setVertexBufferPolicy/setIndexBufferPolicy switch.
Trying to do that with MeshPtr::clone seems to be impossible at least.
Setting setVertexBufferPolicy and setIndexBufferPolicy and then using MeshPtr::reload makes the mesh just lose all its data, so that option is not viable.
Anyway, my way to solve all these things are below.
However, I do not know if there might be a better way.
The only way I could clone a mesh with the right policies was by loading the mesh in again from scratch into a temporary mesh, and then deleting that mesh when the information from it was read, using this function:
Code: Select all
MeshPtr CMeshToMesh::ConvertToMesh(CString filePath, CString nameAddition)
{
// Setup the mesh to return
MeshPtr tmpMeshPtr;
tmpMeshPtr.reset();
// Setup the file as a mesh
FILE* pFile = NULL;
fopen_s(&pFile, filePath.ToCharArray(), "rb");
if(pFile)
{
struct stat tagStat;
stat(filePath.ToCharArray(), &tagStat);
MemoryDataStream* tmpMemoryDataStream = new MemoryDataStream(filePath, tagStat.st_size, true);
fread((void*)tmpMemoryDataStream->getPtr(), tagStat.st_size, 1, pFile);
fclose( pFile );
CString tmpFileName = CGeneric::GetFileName(filePath);
tmpFileName.Replace(".mesh", "_ConvertToMesh_" + nameAddition + CGeneric::GenerateUniqueName() + ".mesh");
MeshPtr tmpNewMesh = MeshManager::getSingleton().createManual(tmpFileName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
CGeneric::HandleMeshLoad(tmpNewMesh);
try
{
DataStreamPtr tmpDataStream(tmpMemoryDataStream);
MeshSerializer tmpMeshSerializer;
tmpMeshSerializer.importMesh(tmpDataStream, tmpNewMesh.get());
}
catch(...)
{
CGeneric::Destroy(tmpNewMesh);
}
tmpMeshPtr = tmpNewMesh;
}
// Return the mesh
return tmpMeshPtr;
}
If you need to get the file path of only a file name (for example, meshPtr->getName()), then this function can be used to find the actual file path:
Code: Select all
// We need this in order to find the file path of a specific file name
CString CGeneric::GetResourceFilePathFromFileName(const CString& fileName)
{
// Loop through all resource groups
StringVector tmpStringVector = ResourceGroupManager::getSingleton().getResourceGroups();
for (int i = 0; i < (int)tmpStringVector.size(); i++)
{
// Check if the texture exists in our current resource group
const String& tmpResourceGroupName = tmpStringVector[i];
ResourceGroupManager::LocationList tmpLocationList = Ogre::ResourceGroupManager::getSingleton().getResourceLocationList(tmpResourceGroupName);
ResourceGroupManager::LocationList::iterator tmpItr = tmpLocationList.begin();
ResourceGroupManager::LocationList::iterator tmpEnd = tmpLocationList.end();
for (; tmpItr != tmpEnd; ++tmpItr)
{
CString tmpDirectory = tmpItr->archive->getName();
StringVectorPtr tmpList = tmpItr->archive->list();
for (int n = 0; n < tmpList->size(); n++)
{
CString tmpFileName = (*tmpList)[n];
if (tmpFileName == fileName)
return tmpDirectory + "/" + tmpFileName;
}
}
}
// Return that it was not found
return "";
}
Also, there are sometimes meshes that always want to use their information on runtime, and by using a listener on the resource group being loaded that can be done before loading them (ResourceGroupManager::addResourceGroupListener):
Code: Select all
void CResourceGroupListener::resourceCreated(const ResourcePtr& resource)
{
// Get the resource type that is about to be loaded
CString tmpResourceType = resource->getCreator()->getResourceType();
// Check if the resource about to be loaded is a mesh
if (tmpResourceType == "Mesh")
// Handle mesh load
CGeneric::HandleMeshLoad(resource);
}
CGeneric::HandleMeshLoad is here:
Code: Select all
// Handles a mesh being loaded
// This is needed because: https://forums.ogre3d.org/viewtopic.php?p=554664#p554664
void CGeneric::HandleMeshLoad(const Ogre::ResourcePtr& resource)
{
// Check if the mesh needs to be read on runtime by the CPU (also check CGeneric::Lock to validate that it works)
// https://forums.ogre3d.org/viewtopic.php?p=554664#p554664
CString tmpName = resource->getName();
if (tmpName.Contains("_col_") || // For example: barrel0_col_convex.mesh
tmpName.Contains("ReadMesh")) // For all meshes created with the single purpose of being able to read a mesh
{
// Alter the mesh buffer policy to allow us to read it on runtime by the CPU
MeshPtr tmpMeshPtr = Ogre::static_pointer_cast<Mesh>(resource);
int tmpVertexBufferUsage = tmpMeshPtr->getVertexBufferUsage();
int tmpIndexBufferUsage = tmpMeshPtr->getIndexBufferUsage();
tmpMeshPtr->setVertexBufferPolicy(tmpVertexBufferUsage, true);
tmpMeshPtr->setIndexBufferPolicy(tmpIndexBufferUsage, true);
}
}
Then, to validate if everything works correctly, these functions must be used instead of when locking:
Code: Select all
void LockHandleError(HardwareBuffer::LockOptions options, int usage, bool hasShadowBuffer)
{
// Check for possible errors (reading from a HBU_STATIC_WRITE_ONLY/HBU_GPU_ONLY is apparently bad without using a shadow buffer: https://forums.ogre3d.org/viewtopic.php?p=554664#p554664)
if (options == HardwareBuffer::LockOptions::HBL_READ_ONLY ||
options == HardwareBuffer::LockOptions::HBL_NORMAL)
{
// For Direct3D9, the mesh attempting to get a shadow buffer instead does not get a shadow buffer and just gets assigned a usage of HBU_STATIC/HBU_GPU_TO_CPU instead.
// For Direct3D11, the shadow buffer actually gets created and the usage stays the same.
// For Direct3D9, if the usage is not HBU_STATIC/HBU_GPU_TO_CPU, it means that the mesh has not been fixed in CResourceGroupListener::resourceLoadStarted.
if (usage != HardwareBuffer::HBU_STATIC /* HBU_GPU_TO_CPU */)
{
// In Direct3D9, the shadow buffer will always be empty, but in Direct3D11 it will have a shadow buffer if it was changed with CGeneric::HandleMeshLoad.
if (!hasShadowBuffer)
{
// Write the message into the log
CString tmpMessage = "Warning: LockHandleError, no shadow buffer found, add it with CGeneric::HandleMeshLoad";
LogManager::getSingleton().getDefaultLog()->logMessage(tmpMessage);
}
}
}
}
// Locks a buffer and checks for errors
void* CGeneric::Lock(HardwareVertexBufferSharedPtr ptr, HardwareBuffer::LockOptions options)
{
LockHandleError(options, ptr->getUsage(), ptr->hasShadowBuffer());
return ptr->lock(options);
}
// Locks a buffer and checks for errors
void* CGeneric::Lock(HardwareIndexBufferSharedPtr ptr, HardwareBuffer::LockOptions options)
{
LockHandleError(options, ptr->getUsage(), ptr->hasShadowBuffer());
return ptr->lock(options);
}
So for example, if the code before was:
Code: Select all
unsigned char* vertex = static_cast<unsigned char*>(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
It should now instead be:
Code: Select all
unsigned char* vertex = static_cast<unsigned char*>(CGeneric::Lock(vbuf, Ogre::HardwareBuffer::HBL_READ_ONLY));
That way, if there is somewhere in your application that still has an invalid lock, the function will tell you with a warning in the log.
You can then put a breakpoint there instead and see exactly which mesh caused the issue, which you can then easily fix either by adding the mesh name to CGeneric::HandleMeshLoad or by cloning the mesh with this before reading/locking it:
Code: Select all
MeshPtr tmpClonedMesh = CMeshToMesh::ConvertToMesh(CGeneric::GetResourceFilePathFromFileName(meshPtr->getName()), "ReadMesh");