Make bvhutil safe for multi-threaded usage

There were couple of reasons why it wasn't safe for usage from
multiple threads.

First of all, it was trying to cache BVH in derived mesh, which
wasn't safe because multiple threads might have requested BVH
tree and simultaneous reading and writing to the cache became a
big headache.

Solved this with RW lock so now access to BVH cache is safe.

Another issue is causes by the fact that it's not guaranteed
DM to have vert/edge/face arrays pre-allocated and when one
was calling functions like getVertDataArray() array could
have been allocated and marked as temporary. This is REALLY
bad, because NO ONE is ever allowed to modify data which
doesn't belong to him. This lead to situations when multiple
threads were using BVH tree and they run into race condition
with this temporary allocated arrays.

Now bvhtree owns allocated arrays and keeps track of them, so
no race condition happens with temporary data stored in the
derived mesh. This solved threading issues and likely wouldn't
introduce noticeable slowdown. Even when DM was keeping track
of this arrays, they were re-allocated on every BVH creation
anyway, because those arrays were temporary and were freed
with dm->release() call.

We might re-consider this a bit and make it so BVH trees are
allocated when DM itself is being allocated based on the DAG
layout, but that i'd consider an optimization and not something
we need to do 1st priority.

Fixes crash happening with 05_4g_track.blend from Mango after
the threaded object update landed to master.
This commit is contained in:
2014-01-10 17:21:39 +06:00
parent bc989497de
commit 881fb43878
2 changed files with 261 additions and 130 deletions

View File

@@ -55,6 +55,9 @@ typedef struct BVHTreeFromMesh {
struct MVert *vert;
struct MEdge *edge; /* only used for BVHTreeFromMeshEdges */
struct MFace *face;
bool vert_allocated;
bool face_allocated;
bool edge_allocated;
/* radius for raycast */
float sphere_radius;

View File

@@ -39,12 +39,15 @@
#include "BLI_utildefines.h"
#include "BLI_linklist.h"
#include "BLI_math.h"
#include "BLI_threads.h"
#include "BKE_DerivedMesh.h"
#include "BKE_editmesh.h"
#include "MEM_guardedalloc.h"
static ThreadRWMutex cache_rwlock = BLI_RWLOCK_INITIALIZER;
/* Math stuff for ray casting on mesh faces and for nearest surface */
float bvhtree_ray_tri_intersection(const BVHTreeRay *ray, const float UNUSED(m_dist), const float v0[3], const float v1[3], const float v2[3])
@@ -514,19 +517,78 @@ static void mesh_edges_nearest_point(void *userdata, int index, const float co[3
}
}
static MVert *get_dm_vert_data_array(DerivedMesh *dm, bool *allocated)
{
CustomData *vert_data = dm->getVertDataLayout(dm);
MVert *mvert = CustomData_get_layer(vert_data, CD_MVERT);
*allocated = false;
if (mvert == NULL) {
mvert = MEM_mallocN(sizeof(MVert) * dm->getNumVerts(dm), "bvh vert data array");
dm->copyVertArray(dm, mvert);
*allocated = true;
}
return mvert;
}
static MEdge *get_dm_edge_data_array(DerivedMesh *dm, bool *allocated)
{
CustomData *edge_data = dm->getEdgeDataLayout(dm);
MEdge *medge = CustomData_get_layer(edge_data, CD_MEDGE);
*allocated = false;
if (medge == NULL) {
medge = MEM_mallocN(sizeof(MEdge) * dm->getNumEdges(dm), "bvh medge data array");
dm->copyEdgeArray(dm, medge);
*allocated = true;
}
return medge;
}
static MFace *get_dm_tessface_data_array(DerivedMesh *dm, bool *allocated)
{
CustomData *tessface_data = dm->getTessFaceDataLayout(dm);
MFace *mface = CustomData_get_layer(tessface_data, CD_MFACE);
*allocated = false;
if (mface == NULL) {
int numTessFaces = dm->getNumTessFaces(dm);
if (numTessFaces > 0) {
mface = MEM_mallocN(sizeof(MFace) * numTessFaces, "bvh mface data array");
dm->copyTessFaceArray(dm, mface);
*allocated = true;
}
}
return mface;
}
/*
* BVH builders
*/
/* Builds a bvh tree.. where nodes are the vertexs of the given mesh */
BVHTree *bvhtree_from_mesh_verts(BVHTreeFromMesh *data, DerivedMesh *dm, float epsilon, int tree_type, int axis)
{
BVHTree *tree = bvhcache_find(&dm->bvhCache, BVHTREE_FROM_VERTICES);
BVHTree *tree;
MVert *vert;
bool vert_allocated;
BLI_rw_mutex_lock(&cache_rwlock, THREAD_LOCK_READ);
tree = bvhcache_find(&dm->bvhCache, BVHTREE_FROM_VERTICES);
BLI_rw_mutex_unlock(&cache_rwlock);
vert = get_dm_vert_data_array(dm, &vert_allocated);
/* Not in cache */
if (tree == NULL) {
BLI_rw_mutex_lock(&cache_rwlock, THREAD_LOCK_WRITE);
tree = bvhcache_find(&dm->bvhCache, BVHTREE_FROM_VERTICES);
if (tree == NULL) {
int i;
int numVerts = dm->getNumVerts(dm);
MVert *vert = dm->getVertDataArray(dm, CD_MVERT);
if (vert != NULL) {
tree = BLI_bvhtree_new(numVerts, epsilon, tree_type, axis);
@@ -544,6 +606,8 @@ BVHTree *bvhtree_from_mesh_verts(BVHTreeFromMesh *data, DerivedMesh *dm, float e
}
}
}
BLI_rw_mutex_unlock(&cache_rwlock);
}
else {
// printf("BVHTree is already build, using cached tree\n");
}
@@ -561,11 +625,17 @@ BVHTree *bvhtree_from_mesh_verts(BVHTreeFromMesh *data, DerivedMesh *dm, float e
data->nearest_callback = NULL;
data->raycast_callback = NULL;
data->vert = dm->getVertDataArray(dm, CD_MVERT);
data->face = dm->getTessFaceDataArray(dm, CD_MFACE);
data->vert = vert;
data->vert_allocated = vert_allocated;
data->face = get_dm_tessface_data_array(dm, &data->face_allocated);
data->sphere_radius = epsilon;
}
else {
if (vert_allocated) {
MEM_freeN(vert);
}
}
return data->tree;
}
@@ -575,9 +645,24 @@ BVHTree *bvhtree_from_mesh_faces(BVHTreeFromMesh *data, DerivedMesh *dm, float e
{
BMEditMesh *em = data->em_evil;
const int bvhcache_type = em ? BVHTREE_FROM_FACES_EDITMESH : BVHTREE_FROM_FACES;
BVHTree *tree = bvhcache_find(&dm->bvhCache, bvhcache_type);
BVHTree *tree;
MVert *vert;
MFace *face;
bool vert_allocated, face_allocated;
BLI_rw_mutex_lock(&cache_rwlock, THREAD_LOCK_READ);
tree = bvhcache_find(&dm->bvhCache, bvhcache_type);
BLI_rw_mutex_unlock(&cache_rwlock);
if (em == NULL) {
vert = get_dm_vert_data_array(dm, &vert_allocated);
face = get_dm_tessface_data_array(dm, &face_allocated);
}
/* Not in cache */
if (tree == NULL) {
BLI_rw_mutex_lock(&cache_rwlock, THREAD_LOCK_WRITE);
tree = bvhcache_find(&dm->bvhCache, bvhcache_type);
if (tree == NULL) {
int i;
int numFaces;
@@ -663,9 +748,6 @@ BVHTree *bvhtree_from_mesh_faces(BVHTreeFromMesh *data, DerivedMesh *dm, float e
}
}
else {
MVert *vert = dm->getVertDataArray(dm, CD_MVERT);
MFace *face = dm->getTessFaceDataArray(dm, CD_MFACE);
if (vert != NULL && face != NULL) {
for (i = 0; i < numFaces; i++) {
float co[4][3];
@@ -687,6 +769,8 @@ BVHTree *bvhtree_from_mesh_faces(BVHTreeFromMesh *data, DerivedMesh *dm, float e
}
}
}
BLI_rw_mutex_unlock(&cache_rwlock);
}
else {
// printf("BVHTree is already build, using cached tree\n");
}
@@ -708,12 +792,23 @@ BVHTree *bvhtree_from_mesh_faces(BVHTreeFromMesh *data, DerivedMesh *dm, float e
data->nearest_callback = mesh_faces_nearest_point;
data->raycast_callback = mesh_faces_spherecast;
data->vert = dm->getVertDataArray(dm, CD_MVERT);
data->face = dm->getTessFaceDataArray(dm, CD_MFACE);
data->vert = vert;
data->vert_allocated = vert_allocated;
data->face = face;
data->face_allocated = face_allocated;
}
data->sphere_radius = epsilon;
}
else {
if (vert_allocated) {
MEM_freeN(vert);
}
if (face_allocated) {
MEM_freeN(face);
}
}
return data->tree;
}
@@ -721,14 +816,25 @@ BVHTree *bvhtree_from_mesh_faces(BVHTreeFromMesh *data, DerivedMesh *dm, float e
/* Builds a bvh tree.. where nodes are the faces of the given dm. */
BVHTree *bvhtree_from_mesh_edges(BVHTreeFromMesh *data, DerivedMesh *dm, float epsilon, int tree_type, int axis)
{
BVHTree *tree = bvhcache_find(&dm->bvhCache, BVHTREE_FROM_EDGES);
BVHTree *tree;
MVert *vert;
MEdge *edge;
bool vert_allocated, edge_allocated;
BLI_rw_mutex_lock(&cache_rwlock, THREAD_LOCK_READ);
tree = bvhcache_find(&dm->bvhCache, BVHTREE_FROM_EDGES);
BLI_rw_mutex_unlock(&cache_rwlock);
vert = get_dm_vert_data_array(dm, &vert_allocated);
edge = get_dm_edge_data_array(dm, &edge_allocated);
/* Not in cache */
if (tree == NULL) {
BLI_rw_mutex_lock(&cache_rwlock, THREAD_LOCK_WRITE);
tree = bvhcache_find(&dm->bvhCache, BVHTREE_FROM_EDGES);
if (tree == NULL) {
int i;
int numEdges = dm->getNumEdges(dm);
MVert *vert = dm->getVertDataArray(dm, CD_MVERT);
MEdge *edge = dm->getEdgeDataArray(dm, CD_MEDGE);
if (vert != NULL && edge != NULL) {
/* Create a bvh-tree of the given target */
@@ -749,6 +855,8 @@ BVHTree *bvhtree_from_mesh_edges(BVHTreeFromMesh *data, DerivedMesh *dm, float e
}
}
}
BLI_rw_mutex_unlock(&cache_rwlock);
}
else {
// printf("BVHTree is already build, using cached tree\n");
}
@@ -764,11 +872,21 @@ BVHTree *bvhtree_from_mesh_edges(BVHTreeFromMesh *data, DerivedMesh *dm, float e
data->nearest_callback = mesh_edges_nearest_point;
data->raycast_callback = NULL;
data->vert = dm->getVertDataArray(dm, CD_MVERT);
data->edge = dm->getEdgeDataArray(dm, CD_MEDGE);
data->vert = vert;
data->vert_allocated = vert_allocated;
data->edge = edge;
data->edge_allocated = edge_allocated;
data->sphere_radius = epsilon;
}
else {
if (vert_allocated) {
MEM_freeN(vert);
}
if (edge_allocated) {
MEM_freeN(edge);
}
}
return data->tree;
}
@@ -780,6 +898,16 @@ void free_bvhtree_from_mesh(struct BVHTreeFromMesh *data)
if (!data->cached)
BLI_bvhtree_free(data->tree);
if (data->vert_allocated) {
MEM_freeN(data->vert);
}
if (data->edge_allocated) {
MEM_freeN(data->edge);
}
if (data->face_allocated) {
MEM_freeN(data->face);
}
memset(data, 0, sizeof(*data));
}
}