Edit Mesh: multi-thread auto-smooth & custom normal calculations
Supported multi-threading for bm_mesh_loops_calc_normals. This is done by operating on vertex-loops instead of face-loops. Single threaded operation still loops over faces since iterating over vertices adds some overhead in the case of custom-normals as the order used for accessing loops must be the same as iterating of a faces loops. From isolated timing tests of bm_mesh_loops_calc_normals on high poly models, this gives between 3.5x to 10x speedup, with larger gains for meshes with custom-normals. NOTE: this is part one of two patches for multi-threaded auto-smooth, tagging edges as sharp is still single threaded. Reviewed By: mont29 Ref D11928
This commit is contained in:
@@ -399,6 +399,12 @@ void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr,
|
||||
const char data_type);
|
||||
void BKE_lnor_spacearr_clear(MLoopNorSpaceArray *lnors_spacearr);
|
||||
void BKE_lnor_spacearr_free(MLoopNorSpaceArray *lnors_spacearr);
|
||||
|
||||
void BKE_lnor_spacearr_tls_init(MLoopNorSpaceArray *lnors_spacearr,
|
||||
MLoopNorSpaceArray *lnors_spacearr_tls);
|
||||
void BKE_lnor_spacearr_tls_join(MLoopNorSpaceArray *lnors_spacearr,
|
||||
MLoopNorSpaceArray *lnors_spacearr_tls);
|
||||
|
||||
MLoopNorSpace *BKE_lnor_space_create(MLoopNorSpaceArray *lnors_spacearr);
|
||||
void BKE_lnor_space_define(MLoopNorSpace *lnor_space,
|
||||
const float lnor[3],
|
||||
|
@@ -530,6 +530,36 @@ void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr,
|
||||
lnors_spacearr->data_type = data_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for multi-threaded calculation that ensures
|
||||
* `lnors_spacearr_tls` doesn't share memory with `lnors_spacearr`
|
||||
* that would cause it not to be thread safe.
|
||||
*
|
||||
* \note This works as long as threads never operate on the same loops at once.
|
||||
*/
|
||||
void BKE_lnor_spacearr_tls_init(MLoopNorSpaceArray *lnors_spacearr,
|
||||
MLoopNorSpaceArray *lnors_spacearr_tls)
|
||||
{
|
||||
*lnors_spacearr_tls = *lnors_spacearr;
|
||||
lnors_spacearr_tls->mem = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for multi-threaded calculation
|
||||
* that merges `lnors_spacearr_tls` into `lnors_spacearr`.
|
||||
*/
|
||||
void BKE_lnor_spacearr_tls_join(MLoopNorSpaceArray *lnors_spacearr,
|
||||
MLoopNorSpaceArray *lnors_spacearr_tls)
|
||||
{
|
||||
BLI_assert(lnors_spacearr->data_type == lnors_spacearr_tls->data_type);
|
||||
BLI_assert(lnors_spacearr->mem != lnors_spacearr_tls->mem);
|
||||
lnors_spacearr->num_spaces += lnors_spacearr_tls->num_spaces;
|
||||
BLI_memarena_merge(lnors_spacearr->mem, lnors_spacearr_tls->mem);
|
||||
BLI_memarena_free(lnors_spacearr_tls->mem);
|
||||
lnors_spacearr_tls->mem = nullptr;
|
||||
BKE_lnor_spacearr_clear(lnors_spacearr_tls);
|
||||
}
|
||||
|
||||
void BKE_lnor_spacearr_clear(MLoopNorSpaceArray *lnors_spacearr)
|
||||
{
|
||||
lnors_spacearr->num_spaces = 0;
|
||||
|
@@ -526,81 +526,43 @@ bool BM_loop_check_cyclic_smooth_fan(BMLoop *l_curr)
|
||||
}
|
||||
|
||||
/**
|
||||
* BMesh version of BKE_mesh_normals_loop_split() in `mesh_evaluate.cc`
|
||||
* Will use first clnors_data array, and fallback to cd_loop_clnors_offset
|
||||
* (use NULL and -1 to not use clnors).
|
||||
* Called for all faces loops.
|
||||
*
|
||||
* \note This sets #BM_ELEM_TAG which is used in tool code (e.g. T84426).
|
||||
* we could add a low-level API flag for this, see #BM_ELEM_API_FLAG_ENABLE and friends.
|
||||
* - All loops must have #BM_ELEM_TAG cleared.
|
||||
* - Loop indices must be valid.
|
||||
*
|
||||
* \note When custom normals are present, the order of loops can be important.
|
||||
* Loops with lower indices must be passed before loops with higher indices (for each vertex).
|
||||
* This is needed since the first loop sets the reference point for the custom normal offsets.
|
||||
*
|
||||
* \return The number of loops that were handled (for early exit when all have been handled).
|
||||
*/
|
||||
static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
static int bm_mesh_loops_calc_normals_for_loop(BMesh *bm,
|
||||
const float (*vcos)[3],
|
||||
const float (*fnos)[3],
|
||||
float (*r_lnos)[3],
|
||||
MLoopNorSpaceArray *r_lnors_spacearr,
|
||||
const short (*clnors_data)[2],
|
||||
const int cd_loop_clnors_offset,
|
||||
const bool do_rebuild)
|
||||
const bool has_clnors,
|
||||
/* Cache. */
|
||||
BLI_Stack *edge_vectors,
|
||||
/* Iterate. */
|
||||
BMLoop *l_curr,
|
||||
/* Result. */
|
||||
float (*r_lnos)[3],
|
||||
MLoopNorSpaceArray *r_lnors_spacearr)
|
||||
{
|
||||
BMIter fiter;
|
||||
BMFace *f_curr;
|
||||
const bool has_clnors = clnors_data || (cd_loop_clnors_offset != -1);
|
||||
BLI_assert((bm->elem_index_dirty & (BM_FACE | BM_LOOP)) == 0);
|
||||
BLI_assert((vcos == NULL) || ((bm->elem_index_dirty & BM_VERT) == 0));
|
||||
UNUSED_VARS_NDEBUG(bm);
|
||||
|
||||
MLoopNorSpaceArray _lnors_spacearr = {NULL};
|
||||
int handled = 0;
|
||||
|
||||
/* Temp normal stack. */
|
||||
BLI_SMALLSTACK_DECLARE(normal, float *);
|
||||
/* Temp clnors stack. */
|
||||
BLI_SMALLSTACK_DECLARE(clnors, short *);
|
||||
/* Temp edge vectors stack, only used when computing lnor spacearr. */
|
||||
BLI_Stack *edge_vectors = NULL;
|
||||
|
||||
{
|
||||
char htype = 0;
|
||||
if (vcos) {
|
||||
htype |= BM_VERT;
|
||||
}
|
||||
/* Face/Loop indices are set inline below. */
|
||||
BM_mesh_elem_index_ensure(bm, htype);
|
||||
}
|
||||
|
||||
if (!r_lnors_spacearr && has_clnors) {
|
||||
/* We need to compute lnor spacearr if some custom lnor data are given to us! */
|
||||
r_lnors_spacearr = &_lnors_spacearr;
|
||||
}
|
||||
if (r_lnors_spacearr) {
|
||||
BKE_lnor_spacearr_init(r_lnors_spacearr, bm->totloop, MLNOR_SPACEARR_BMLOOP_PTR);
|
||||
edge_vectors = BLI_stack_new(sizeof(float[3]), __func__);
|
||||
}
|
||||
|
||||
/* Clear all loops' tags (means none are to be skipped for now). */
|
||||
int index_face, index_loop = 0;
|
||||
BM_ITER_MESH_INDEX (f_curr, &fiter, bm, BM_FACES_OF_MESH, index_face) {
|
||||
BMLoop *l_curr, *l_first;
|
||||
|
||||
BM_elem_index_set(f_curr, index_face); /* set_inline */
|
||||
|
||||
l_curr = l_first = BM_FACE_FIRST_LOOP(f_curr);
|
||||
do {
|
||||
BM_elem_index_set(l_curr, index_loop++); /* set_inline */
|
||||
BM_elem_flag_disable(l_curr, BM_ELEM_TAG);
|
||||
} while ((l_curr = l_curr->next) != l_first);
|
||||
}
|
||||
bm->elem_index_dirty &= ~(BM_FACE | BM_LOOP);
|
||||
|
||||
/* We now know edges that can be smoothed (they are tagged),
|
||||
* and edges that will be hard (they aren't).
|
||||
* Now, time to generate the normals.
|
||||
*/
|
||||
BM_ITER_MESH (f_curr, &fiter, bm, BM_FACES_OF_MESH) {
|
||||
BMLoop *l_curr, *l_first;
|
||||
|
||||
l_curr = l_first = BM_FACE_FIRST_LOOP(f_curr);
|
||||
do {
|
||||
if (do_rebuild && !BM_ELEM_API_FLAG_TEST(l_curr, BM_LNORSPACE_UPDATE) &&
|
||||
!(bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL)) {
|
||||
continue;
|
||||
}
|
||||
/* A smooth edge, we have to check for cyclic smooth fan case.
|
||||
* If we find a new, never-processed cyclic smooth fan, we can do it now using that loop/edge
|
||||
* as 'entry point', otherwise we can skip it. */
|
||||
@@ -620,7 +582,7 @@ static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
* this vertex just takes its poly normal.
|
||||
*/
|
||||
const int l_curr_index = BM_elem_index_get(l_curr);
|
||||
const float *no = fnos ? fnos[BM_elem_index_get(f_curr)] : f_curr->no;
|
||||
const float *no = fnos ? fnos[BM_elem_index_get(l_curr->f)] : l_curr->f->no;
|
||||
copy_v3_v3(r_lnos[l_curr_index], no);
|
||||
|
||||
/* If needed, generate this (simple!) lnor space. */
|
||||
@@ -657,6 +619,7 @@ static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
BKE_lnor_space_custom_data_to_normal(lnor_space, *clnor, r_lnos[l_curr_index]);
|
||||
}
|
||||
}
|
||||
handled = 1;
|
||||
}
|
||||
/* We *do not need* to check/tag loops as already computed!
|
||||
* Due to the fact a loop only links to one of its two edges,
|
||||
@@ -692,8 +655,7 @@ static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
|
||||
const float *co_pivot = vcos ? vcos[BM_elem_index_get(v_pivot)] : v_pivot->co;
|
||||
|
||||
MLoopNorSpace *lnor_space = r_lnors_spacearr ? BKE_lnor_space_create(r_lnors_spacearr) :
|
||||
NULL;
|
||||
MLoopNorSpace *lnor_space = r_lnors_spacearr ? BKE_lnor_space_create(r_lnors_spacearr) : NULL;
|
||||
|
||||
BLI_assert((edge_vectors == NULL) || BLI_stack_is_empty(edge_vectors));
|
||||
|
||||
@@ -758,8 +720,7 @@ static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
(const void *)BM_ELEM_CD_GET_VOID_P(
|
||||
lfan_pivot, cd_loop_clnors_offset);
|
||||
if (clnors_nbr) {
|
||||
clnors_invalid |= ((*clnor_ref)[0] != (*clnor)[0] ||
|
||||
(*clnor_ref)[1] != (*clnor)[1]);
|
||||
clnors_invalid |= ((*clnor_ref)[0] != (*clnor)[0] || (*clnor_ref)[1] != (*clnor)[1]);
|
||||
}
|
||||
else {
|
||||
clnor_ref = clnor;
|
||||
@@ -777,14 +738,15 @@ static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
|
||||
if (r_lnors_spacearr) {
|
||||
/* Assign current lnor space to current 'vertex' loop. */
|
||||
BKE_lnor_space_add_loop(
|
||||
r_lnors_spacearr, lnor_space, lfan_pivot_index, lfan_pivot, false);
|
||||
BKE_lnor_space_add_loop(r_lnors_spacearr, lnor_space, lfan_pivot_index, lfan_pivot, false);
|
||||
if (e_next != e_org) {
|
||||
/* We store here all edges-normalized vectors processed. */
|
||||
BLI_stack_push(edge_vectors, vec_next);
|
||||
}
|
||||
}
|
||||
|
||||
handled += 1;
|
||||
|
||||
if (!BM_elem_flag_test(e_next, BM_ELEM_TAG) || (e_next == e_org)) {
|
||||
/* Next edge is sharp, we have finished with this fan of faces around this vert! */
|
||||
break;
|
||||
@@ -861,6 +823,277 @@ static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
BM_elem_flag_enable(l_curr->v, BM_ELEM_TAG);
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
static int bm_loop_index_cmp(const void *a, const void *b)
|
||||
{
|
||||
BLI_assert(BM_elem_index_get((BMLoop *)a) != BM_elem_index_get((BMLoop *)b));
|
||||
if (BM_elem_index_get((BMLoop *)a) < BM_elem_index_get((BMLoop *)b)) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Operate on all vertices loops.
|
||||
* operating on vertices this is needed for multi-threading
|
||||
* so there is a guarantee that each thread has isolated loops.
|
||||
*/
|
||||
static void bm_mesh_loops_calc_normals_for_vert_with_clnors(BMesh *bm,
|
||||
const float (*vcos)[3],
|
||||
const float (*fnos)[3],
|
||||
float (*r_lnos)[3],
|
||||
const short (*clnors_data)[2],
|
||||
const int cd_loop_clnors_offset,
|
||||
const bool do_rebuild,
|
||||
/* TLS */
|
||||
MLoopNorSpaceArray *r_lnors_spacearr,
|
||||
BLI_Stack *edge_vectors,
|
||||
/* Iterate over. */
|
||||
BMVert *v)
|
||||
{
|
||||
/* Respecting face order is necessary so the initial starting loop is consistent
|
||||
* with looping over loops of all faces.
|
||||
*
|
||||
* Logically we could sort the loops by their index & loop over them
|
||||
* however it's faster to use the lowest index of an un-ordered list
|
||||
* since it's common that smooth vertices only ever need to pick one loop
|
||||
* which then handles all the others.
|
||||
*
|
||||
* Sorting is only performed when multiple fans are found. */
|
||||
const bool has_clnors = true;
|
||||
LinkNode *loops_of_vert = NULL;
|
||||
int loops_of_vert_count = 0;
|
||||
|
||||
/* The loop with the lowest index. */
|
||||
{
|
||||
LinkNode *link_best;
|
||||
uint index_best = UINT_MAX;
|
||||
BMEdge *e_curr_iter = v->e;
|
||||
do { /* Edges of vertex. */
|
||||
BMLoop *l_curr = e_curr_iter->l;
|
||||
if (l_curr == NULL) {
|
||||
continue;
|
||||
}
|
||||
do { /* Radial loops. */
|
||||
if (l_curr->v != v) {
|
||||
continue;
|
||||
}
|
||||
if (do_rebuild && !BM_ELEM_API_FLAG_TEST(l_curr, BM_LNORSPACE_UPDATE) &&
|
||||
!(bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL)) {
|
||||
continue;
|
||||
}
|
||||
BM_elem_flag_disable(l_curr, BM_ELEM_TAG);
|
||||
BLI_linklist_prepend_alloca(&loops_of_vert, l_curr);
|
||||
loops_of_vert_count += 1;
|
||||
|
||||
const uint index_test = (uint)BM_elem_index_get(l_curr);
|
||||
if (index_best > index_test) {
|
||||
index_best = index_test;
|
||||
link_best = loops_of_vert;
|
||||
}
|
||||
} while ((l_curr = l_curr->radial_next) != e_curr_iter->l);
|
||||
} while ((e_curr_iter = BM_DISK_EDGE_NEXT(e_curr_iter, v)) != v->e);
|
||||
|
||||
if (UNLIKELY(loops_of_vert == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Immediately pop the best element.
|
||||
* The order doesn't matter, so swap the links as it's simpler than tracking
|
||||
* reference to `link_best`. */
|
||||
if (link_best != loops_of_vert) {
|
||||
SWAP(void *, link_best->link, loops_of_vert->link);
|
||||
}
|
||||
}
|
||||
|
||||
bool loops_of_vert_is_sorted = false;
|
||||
|
||||
/* Keep track of the number of loops that have been assigned. */
|
||||
int loops_of_vert_handled = 0;
|
||||
|
||||
while (loops_of_vert != NULL) {
|
||||
BMLoop *l_best = loops_of_vert->link;
|
||||
loops_of_vert = loops_of_vert->next;
|
||||
|
||||
BLI_assert(l_best->v == v);
|
||||
loops_of_vert_handled += bm_mesh_loops_calc_normals_for_loop(bm,
|
||||
vcos,
|
||||
fnos,
|
||||
clnors_data,
|
||||
cd_loop_clnors_offset,
|
||||
has_clnors,
|
||||
edge_vectors,
|
||||
l_best,
|
||||
r_lnos,
|
||||
r_lnors_spacearr);
|
||||
|
||||
/* Check if an early exit is possible without an exhaustive inspection of every loop
|
||||
* where 1 loop's fan extends out to all remaining loops.
|
||||
* This is a common case for smooth vertices. */
|
||||
BLI_assert(loops_of_vert_handled <= loops_of_vert_count);
|
||||
if (loops_of_vert_handled == loops_of_vert_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Note on sorting, in some cases it will be faster to scan for the lowest index each time.
|
||||
* However in the worst case this is `O(N^2)`, so use a single sort call instead. */
|
||||
if (!loops_of_vert_is_sorted) {
|
||||
if (loops_of_vert && loops_of_vert->next) {
|
||||
loops_of_vert = BLI_linklist_sort(loops_of_vert, bm_loop_index_cmp);
|
||||
loops_of_vert_is_sorted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified version of #bm_mesh_loops_calc_normals_for_vert_with_clnors
|
||||
* that can operate on loops in any order.
|
||||
*/
|
||||
static void bm_mesh_loops_calc_normals_for_vert_without_clnors(
|
||||
BMesh *bm,
|
||||
const float (*vcos)[3],
|
||||
const float (*fnos)[3],
|
||||
float (*r_lnos)[3],
|
||||
const bool do_rebuild,
|
||||
/* TLS */
|
||||
MLoopNorSpaceArray *r_lnors_spacearr,
|
||||
BLI_Stack *edge_vectors,
|
||||
/* Iterate over. */
|
||||
BMVert *v)
|
||||
{
|
||||
const bool has_clnors = false;
|
||||
const short(*clnors_data)[2] = NULL;
|
||||
const int cd_loop_clnors_offset = -1;
|
||||
|
||||
BMEdge *e_curr_iter;
|
||||
|
||||
/* Unfortunately a loop is needed just to clear loop-tags. */
|
||||
e_curr_iter = v->e;
|
||||
do { /* Edges of vertex. */
|
||||
BMLoop *l_curr = e_curr_iter->l;
|
||||
if (l_curr == NULL) {
|
||||
continue;
|
||||
}
|
||||
do { /* Radial loops. */
|
||||
if (l_curr->v != v) {
|
||||
continue;
|
||||
}
|
||||
BM_elem_flag_disable(l_curr, BM_ELEM_TAG);
|
||||
} while ((l_curr = l_curr->radial_next) != e_curr_iter->l);
|
||||
} while ((e_curr_iter = BM_DISK_EDGE_NEXT(e_curr_iter, v)) != v->e);
|
||||
|
||||
e_curr_iter = v->e;
|
||||
do { /* Edges of vertex. */
|
||||
BMLoop *l_curr = e_curr_iter->l;
|
||||
if (l_curr == NULL) {
|
||||
continue;
|
||||
}
|
||||
do { /* Radial loops. */
|
||||
if (l_curr->v != v) {
|
||||
continue;
|
||||
}
|
||||
if (do_rebuild && !BM_ELEM_API_FLAG_TEST(l_curr, BM_LNORSPACE_UPDATE) &&
|
||||
!(bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL)) {
|
||||
continue;
|
||||
}
|
||||
bm_mesh_loops_calc_normals_for_loop(bm,
|
||||
vcos,
|
||||
fnos,
|
||||
clnors_data,
|
||||
cd_loop_clnors_offset,
|
||||
has_clnors,
|
||||
edge_vectors,
|
||||
l_curr,
|
||||
r_lnos,
|
||||
r_lnors_spacearr);
|
||||
} while ((l_curr = l_curr->radial_next) != e_curr_iter->l);
|
||||
} while ((e_curr_iter = BM_DISK_EDGE_NEXT(e_curr_iter, v)) != v->e);
|
||||
}
|
||||
|
||||
/**
|
||||
* BMesh version of BKE_mesh_normals_loop_split() in `mesh_evaluate.cc`
|
||||
* Will use first clnors_data array, and fallback to cd_loop_clnors_offset
|
||||
* (use NULL and -1 to not use clnors).
|
||||
*
|
||||
* \note This sets #BM_ELEM_TAG which is used in tool code (e.g. T84426).
|
||||
* we could add a low-level API flag for this, see #BM_ELEM_API_FLAG_ENABLE and friends.
|
||||
*/
|
||||
static void bm_mesh_loops_calc_normals__single_threaded(BMesh *bm,
|
||||
const float (*vcos)[3],
|
||||
const float (*fnos)[3],
|
||||
float (*r_lnos)[3],
|
||||
MLoopNorSpaceArray *r_lnors_spacearr,
|
||||
const short (*clnors_data)[2],
|
||||
const int cd_loop_clnors_offset,
|
||||
const bool do_rebuild)
|
||||
{
|
||||
BMIter fiter;
|
||||
BMFace *f_curr;
|
||||
const bool has_clnors = clnors_data || (cd_loop_clnors_offset != -1);
|
||||
|
||||
MLoopNorSpaceArray _lnors_spacearr = {NULL};
|
||||
|
||||
BLI_Stack *edge_vectors = NULL;
|
||||
|
||||
{
|
||||
char htype = 0;
|
||||
if (vcos) {
|
||||
htype |= BM_VERT;
|
||||
}
|
||||
/* Face/Loop indices are set inline below. */
|
||||
BM_mesh_elem_index_ensure(bm, htype);
|
||||
}
|
||||
|
||||
if (!r_lnors_spacearr && has_clnors) {
|
||||
/* We need to compute lnor spacearr if some custom lnor data are given to us! */
|
||||
r_lnors_spacearr = &_lnors_spacearr;
|
||||
}
|
||||
if (r_lnors_spacearr) {
|
||||
BKE_lnor_spacearr_init(r_lnors_spacearr, bm->totloop, MLNOR_SPACEARR_BMLOOP_PTR);
|
||||
edge_vectors = BLI_stack_new(sizeof(float[3]), __func__);
|
||||
}
|
||||
|
||||
/* Clear all loops' tags (means none are to be skipped for now). */
|
||||
int index_face, index_loop = 0;
|
||||
BM_ITER_MESH_INDEX (f_curr, &fiter, bm, BM_FACES_OF_MESH, index_face) {
|
||||
BMLoop *l_curr, *l_first;
|
||||
|
||||
BM_elem_index_set(f_curr, index_face); /* set_inline */
|
||||
|
||||
l_curr = l_first = BM_FACE_FIRST_LOOP(f_curr);
|
||||
do {
|
||||
BM_elem_index_set(l_curr, index_loop++); /* set_inline */
|
||||
BM_elem_flag_disable(l_curr, BM_ELEM_TAG);
|
||||
} while ((l_curr = l_curr->next) != l_first);
|
||||
}
|
||||
bm->elem_index_dirty &= ~(BM_FACE | BM_LOOP);
|
||||
|
||||
/* We now know edges that can be smoothed (they are tagged),
|
||||
* and edges that will be hard (they aren't).
|
||||
* Now, time to generate the normals.
|
||||
*/
|
||||
BM_ITER_MESH (f_curr, &fiter, bm, BM_FACES_OF_MESH) {
|
||||
BMLoop *l_curr, *l_first;
|
||||
|
||||
l_curr = l_first = BM_FACE_FIRST_LOOP(f_curr);
|
||||
do {
|
||||
if (do_rebuild && !BM_ELEM_API_FLAG_TEST(l_curr, BM_LNORSPACE_UPDATE) &&
|
||||
!(bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL)) {
|
||||
continue;
|
||||
}
|
||||
bm_mesh_loops_calc_normals_for_loop(bm,
|
||||
vcos,
|
||||
fnos,
|
||||
clnors_data,
|
||||
cd_loop_clnors_offset,
|
||||
has_clnors,
|
||||
edge_vectors,
|
||||
l_curr,
|
||||
r_lnos,
|
||||
r_lnors_spacearr);
|
||||
} while ((l_curr = l_curr->next) != l_first);
|
||||
}
|
||||
|
||||
@@ -872,6 +1105,206 @@ static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct BMLoopsCalcNormalsWithCoordsData {
|
||||
/* Read-only data. */
|
||||
const float (*fnos)[3];
|
||||
const float (*vcos)[3];
|
||||
BMesh *bm;
|
||||
const short (*clnors_data)[2];
|
||||
const int cd_loop_clnors_offset;
|
||||
const bool do_rebuild;
|
||||
|
||||
/* Output. */
|
||||
float (*r_lnos)[3];
|
||||
MLoopNorSpaceArray *r_lnors_spacearr;
|
||||
} BMLoopsCalcNormalsWithCoordsData;
|
||||
|
||||
typedef struct BMLoopsCalcNormalsWithCoords_TLS {
|
||||
BLI_Stack *edge_vectors;
|
||||
|
||||
/** Copied from #BMLoopsCalcNormalsWithCoordsData.r_lnors_spacearr when it's not NULL. */
|
||||
MLoopNorSpaceArray *lnors_spacearr;
|
||||
MLoopNorSpaceArray lnors_spacearr_buf;
|
||||
} BMLoopsCalcNormalsWithCoords_TLS;
|
||||
|
||||
static void bm_mesh_loops_calc_normals_for_vert_init_fn(const void *__restrict userdata,
|
||||
void *__restrict chunk)
|
||||
{
|
||||
const BMLoopsCalcNormalsWithCoordsData *data = userdata;
|
||||
BMLoopsCalcNormalsWithCoords_TLS *tls_data = chunk;
|
||||
if (data->r_lnors_spacearr) {
|
||||
tls_data->edge_vectors = BLI_stack_new(sizeof(float[3]), __func__);
|
||||
BKE_lnor_spacearr_tls_init(data->r_lnors_spacearr, &tls_data->lnors_spacearr_buf);
|
||||
tls_data->lnors_spacearr = &tls_data->lnors_spacearr_buf;
|
||||
}
|
||||
else {
|
||||
tls_data->lnors_spacearr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void bm_mesh_loops_calc_normals_for_vert_reduce_fn(const void *__restrict userdata,
|
||||
void *__restrict UNUSED(chunk_join),
|
||||
void *__restrict chunk)
|
||||
{
|
||||
const BMLoopsCalcNormalsWithCoordsData *data = userdata;
|
||||
BMLoopsCalcNormalsWithCoords_TLS *tls_data = chunk;
|
||||
|
||||
if (data->r_lnors_spacearr) {
|
||||
BKE_lnor_spacearr_tls_join(data->r_lnors_spacearr, tls_data->lnors_spacearr);
|
||||
}
|
||||
}
|
||||
|
||||
static void bm_mesh_loops_calc_normals_for_vert_free_fn(const void *__restrict userdata,
|
||||
void *__restrict chunk)
|
||||
{
|
||||
const BMLoopsCalcNormalsWithCoordsData *data = userdata;
|
||||
BMLoopsCalcNormalsWithCoords_TLS *tls_data = chunk;
|
||||
|
||||
if (data->r_lnors_spacearr) {
|
||||
BLI_stack_free(tls_data->edge_vectors);
|
||||
}
|
||||
}
|
||||
|
||||
static void bm_mesh_loops_calc_normals_for_vert_with_clnors_fn(
|
||||
void *userdata, MempoolIterData *mp_v, const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
BMVert *v = (BMVert *)mp_v;
|
||||
if (v->e == NULL) {
|
||||
return;
|
||||
}
|
||||
BMLoopsCalcNormalsWithCoordsData *data = userdata;
|
||||
BMLoopsCalcNormalsWithCoords_TLS *tls_data = tls->userdata_chunk;
|
||||
bm_mesh_loops_calc_normals_for_vert_with_clnors(data->bm,
|
||||
data->vcos,
|
||||
data->fnos,
|
||||
data->r_lnos,
|
||||
|
||||
data->clnors_data,
|
||||
data->cd_loop_clnors_offset,
|
||||
data->do_rebuild,
|
||||
/* Thread local. */
|
||||
tls_data->lnors_spacearr,
|
||||
tls_data->edge_vectors,
|
||||
/* Iterate over. */
|
||||
v);
|
||||
}
|
||||
|
||||
static void bm_mesh_loops_calc_normals_for_vert_without_clnors_fn(
|
||||
void *userdata, MempoolIterData *mp_v, const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
BMVert *v = (BMVert *)mp_v;
|
||||
if (v->e == NULL) {
|
||||
return;
|
||||
}
|
||||
BMLoopsCalcNormalsWithCoordsData *data = userdata;
|
||||
BMLoopsCalcNormalsWithCoords_TLS *tls_data = tls->userdata_chunk;
|
||||
bm_mesh_loops_calc_normals_for_vert_without_clnors(data->bm,
|
||||
data->vcos,
|
||||
data->fnos,
|
||||
data->r_lnos,
|
||||
|
||||
data->do_rebuild,
|
||||
/* Thread local. */
|
||||
tls_data->lnors_spacearr,
|
||||
tls_data->edge_vectors,
|
||||
/* Iterate over. */
|
||||
v);
|
||||
}
|
||||
|
||||
static void bm_mesh_loops_calc_normals__multi_threaded(BMesh *bm,
|
||||
const float (*vcos)[3],
|
||||
const float (*fnos)[3],
|
||||
float (*r_lnos)[3],
|
||||
MLoopNorSpaceArray *r_lnors_spacearr,
|
||||
const short (*clnors_data)[2],
|
||||
const int cd_loop_clnors_offset,
|
||||
const bool do_rebuild)
|
||||
{
|
||||
const bool has_clnors = clnors_data || (cd_loop_clnors_offset != -1);
|
||||
|
||||
MLoopNorSpaceArray _lnors_spacearr = {NULL};
|
||||
|
||||
{
|
||||
char htype = BM_LOOP;
|
||||
if (vcos) {
|
||||
htype |= BM_VERT;
|
||||
}
|
||||
if (fnos) {
|
||||
htype |= BM_FACE;
|
||||
}
|
||||
/* Face/Loop indices are set inline below. */
|
||||
BM_mesh_elem_index_ensure(bm, htype);
|
||||
}
|
||||
|
||||
if (!r_lnors_spacearr && has_clnors) {
|
||||
/* We need to compute lnor spacearr if some custom lnor data are given to us! */
|
||||
r_lnors_spacearr = &_lnors_spacearr;
|
||||
}
|
||||
if (r_lnors_spacearr) {
|
||||
BKE_lnor_spacearr_init(r_lnors_spacearr, bm->totloop, MLNOR_SPACEARR_BMLOOP_PTR);
|
||||
}
|
||||
|
||||
/* We now know edges that can be smoothed (they are tagged),
|
||||
* and edges that will be hard (they aren't).
|
||||
* Now, time to generate the normals.
|
||||
*/
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BLI_parallel_mempool_settings_defaults(&settings);
|
||||
|
||||
BMLoopsCalcNormalsWithCoords_TLS tls = {NULL};
|
||||
|
||||
settings.userdata_chunk = &tls;
|
||||
settings.userdata_chunk_size = sizeof(tls);
|
||||
|
||||
settings.func_init = bm_mesh_loops_calc_normals_for_vert_init_fn;
|
||||
settings.func_reduce = bm_mesh_loops_calc_normals_for_vert_reduce_fn;
|
||||
settings.func_free = bm_mesh_loops_calc_normals_for_vert_free_fn;
|
||||
|
||||
BMLoopsCalcNormalsWithCoordsData data = {
|
||||
.bm = bm,
|
||||
.vcos = vcos,
|
||||
.fnos = fnos,
|
||||
.r_lnos = r_lnos,
|
||||
.r_lnors_spacearr = r_lnors_spacearr,
|
||||
.clnors_data = clnors_data,
|
||||
.cd_loop_clnors_offset = cd_loop_clnors_offset,
|
||||
.do_rebuild = do_rebuild,
|
||||
};
|
||||
|
||||
BM_iter_parallel(bm,
|
||||
BM_VERTS_OF_MESH,
|
||||
has_clnors ? bm_mesh_loops_calc_normals_for_vert_with_clnors_fn :
|
||||
bm_mesh_loops_calc_normals_for_vert_without_clnors_fn,
|
||||
&data,
|
||||
&settings);
|
||||
|
||||
if (r_lnors_spacearr) {
|
||||
if (r_lnors_spacearr == &_lnors_spacearr) {
|
||||
BKE_lnor_spacearr_free(r_lnors_spacearr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bm_mesh_loops_calc_normals(BMesh *bm,
|
||||
const float (*vcos)[3],
|
||||
const float (*fnos)[3],
|
||||
float (*r_lnos)[3],
|
||||
MLoopNorSpaceArray *r_lnors_spacearr,
|
||||
const short (*clnors_data)[2],
|
||||
const int cd_loop_clnors_offset,
|
||||
const bool do_rebuild)
|
||||
{
|
||||
if (bm->totloop < BM_OMP_LIMIT) {
|
||||
bm_mesh_loops_calc_normals__single_threaded(
|
||||
bm, vcos, fnos, r_lnos, r_lnors_spacearr, clnors_data, cd_loop_clnors_offset, do_rebuild);
|
||||
}
|
||||
else {
|
||||
bm_mesh_loops_calc_normals__multi_threaded(
|
||||
bm, vcos, fnos, r_lnos, r_lnors_spacearr, clnors_data, cd_loop_clnors_offset, do_rebuild);
|
||||
}
|
||||
}
|
||||
|
||||
/* This threshold is a bit touchy (usual float precision issue), this value seems OK. */
|
||||
#define LNOR_SPACE_TRIGO_THRESHOLD (1.0f - 1e-4f)
|
||||
|
||||
|
Reference in New Issue
Block a user