From ebe8f8ce719729eef402119f4d0edd29e746abf3 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 3 Feb 2023 10:19:19 -0500 Subject: [PATCH] BMesh: Parallelize BMesh to evaluated Mesh conversion Currently this conversion (which happens when using modifiers in edit mode, for example) is completely single threaded. It's harder than some other areas to multithread because BMesh elements don't always know their indices (and vise versa), and because the dynamic AoS format used by BMesh makes some typical solutions not helpful. This patch proposes to split the operation into two steps. The first updates the indices of BMesh elements and builds tables for easy iteration later. It also checks if some optional mesh attributes should be added. The second uses parallel loops over all elements, copying attribute values and building the Mesh topology. Both steps process different domains in separate threads (though the first has to combine faces and loops). Though this isn't proper data parallelism, it's quite helpful because each domain doesn't affect the others. **Timings** I tested this on a Ryzen 7950x with a 1 million face grid, with no extra attributes and then with several color attributes and vertex groups. | File | Before | After | | Simple | 101.6 ms | 59.6 ms | | More Attributes | 149.2 ms | 65.6 ms | The optimization scales better with more attributes on the BMesh. The speedup isn't as linear as multithreading other operations, indicating added overhead. I think this is worth it though, because the user is usually actively interacting with a mesh in edit mode. See the differential revision for more timing information. Differential Revision: https://developer.blender.org/D16249 --- source/blender/bmesh/intern/bmesh_construct.c | 22 - source/blender/bmesh/intern/bmesh_construct.h | 5 - .../bmesh/intern/bmesh_mesh_convert.cc | 460 ++++++++++++------ 3 files changed, 312 insertions(+), 175 deletions(-) diff --git a/source/blender/bmesh/intern/bmesh_construct.c b/source/blender/bmesh/intern/bmesh_construct.c index 0c05e7bb83d..b549580f354 100644 --- a/source/blender/bmesh/intern/bmesh_construct.c +++ b/source/blender/bmesh/intern/bmesh_construct.c @@ -715,25 +715,3 @@ BMesh *BM_mesh_copy(BMesh *bm_old) return bm_new; } - -char BM_edge_flag_from_mflag(const short mflag) -{ - return (((mflag & ME_SEAM) ? BM_ELEM_SEAM : 0) | ((mflag & ME_EDGEDRAW) ? BM_ELEM_DRAW : 0)); -} -char BM_face_flag_from_mflag(const char mflag) -{ - return ((mflag & ME_SMOOTH) ? BM_ELEM_SMOOTH : 0); -} - -short BM_edge_flag_to_mflag(BMEdge *e) -{ - const char hflag = e->head.hflag; - - return (((hflag & BM_ELEM_SEAM) ? ME_SEAM : 0) | ((hflag & BM_ELEM_DRAW) ? ME_EDGEDRAW : 0)); -} -char BM_face_flag_to_mflag(BMFace *f) -{ - const char hflag = f->head.hflag; - - return ((hflag & BM_ELEM_SMOOTH) ? ME_SMOOTH : 0); -} diff --git a/source/blender/bmesh/intern/bmesh_construct.h b/source/blender/bmesh/intern/bmesh_construct.h index 635198b9346..0b85abdaa92 100644 --- a/source/blender/bmesh/intern/bmesh_construct.h +++ b/source/blender/bmesh/intern/bmesh_construct.h @@ -170,11 +170,6 @@ void BM_mesh_copy_init_customdata_all_layers(BMesh *bm_dst, const struct BMAllocTemplate *allocsize); BMesh *BM_mesh_copy(BMesh *bm_old); -char BM_face_flag_from_mflag(char mflag); -char BM_edge_flag_from_mflag(short mflag); -/* ME -> BM */ -char BM_face_flag_to_mflag(BMFace *f); -short BM_edge_flag_to_mflag(BMEdge *e); #ifdef __cplusplus } diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.cc b/source/blender/bmesh/intern/bmesh_mesh_convert.cc index c795fd1138a..7fd8dbe44bf 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_convert.cc +++ b/source/blender/bmesh/intern/bmesh_mesh_convert.cc @@ -111,6 +111,28 @@ using blender::MutableSpan; using blender::Span; using blender::StringRef; +static char bm_edge_flag_from_mflag(const short mflag) +{ + return ((mflag & ME_SEAM) ? BM_ELEM_SEAM : 0) | ((mflag & ME_EDGEDRAW) ? BM_ELEM_DRAW : 0); +} +static char bm_face_flag_from_mflag(const char mflag) +{ + return ((mflag & ME_SMOOTH) ? BM_ELEM_SMOOTH : 0); +} + +static short bm_edge_flag_to_mflag(const BMEdge *e) +{ + const char hflag = e->head.hflag; + + return ((hflag & BM_ELEM_SEAM) ? ME_SEAM : 0) | ((hflag & BM_ELEM_DRAW) ? ME_EDGEDRAW : 0); +} +static char bm_face_flag_to_mflag(const BMFace *f) +{ + const char hflag = f->head.hflag; + + return ((hflag & BM_ELEM_SMOOTH) ? ME_SMOOTH : 0); +} + /* Static function for alloc (duplicate in modifiers_bmesh.c) */ static BMFace *bm_face_create_from_mpoly(BMesh &bm, Span loops, @@ -385,7 +407,7 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *me, const struct BMeshFromMeshPar BM_elem_index_set(e, i); /* set_ok */ /* Transfer flags. */ - e->head.hflag = BM_edge_flag_from_mflag(medge[i].flag); + e->head.hflag = bm_edge_flag_from_mflag(medge[i].flag); if (hide_edge && hide_edge[i]) { BM_elem_flag_enable(e, BM_ELEM_HIDDEN); } @@ -435,7 +457,7 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *me, const struct BMeshFromMeshPar BM_elem_index_set(f, bm->totface - 1); /* set_ok */ /* Transfer flag. */ - f->head.hflag = BM_face_flag_from_mflag(mpoly[i].flag); + f->head.hflag = bm_face_flag_from_mflag(mpoly[i].flag); if (hide_poly && hide_poly[i]) { BM_elem_flag_enable(f, BM_ELEM_HIDDEN); } @@ -1097,7 +1119,7 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh medge[i].v1 = BM_elem_index_get(e->v1); medge[i].v2 = BM_elem_index_get(e->v2); - medge[i].flag = BM_edge_flag_to_mflag(e); + medge[i].flag = bm_edge_flag_to_mflag(e); if (BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { need_hide_edge = true; } @@ -1127,7 +1149,7 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh if (f->mat_nr != 0) { need_material_index = true; } - mpoly[i].flag = BM_face_flag_to_mflag(f); + mpoly[i].flag = bm_face_flag_to_mflag(f); if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { need_hide_poly = true; } @@ -1287,6 +1309,197 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh multires_topology_changed(me); } +namespace blender { + +static void bm_vert_table_build(BMesh &bm, + MutableSpan table, + bool &need_select_vert, + bool &need_hide_vert) +{ + char hflag = 0; + BMIter iter; + int i; + BMVert *vert; + BM_ITER_MESH_INDEX (vert, &iter, &bm, BM_VERTS_OF_MESH, i) { + BM_elem_index_set(vert, i); /* set_inline */ + table[i] = vert; + hflag |= vert->head.hflag; + } + need_select_vert |= (hflag & BM_ELEM_SELECT); + need_hide_vert |= (hflag & BM_ELEM_HIDDEN); +} + +static void bm_edge_table_build(BMesh &bm, + MutableSpan table, + bool &need_select_edge, + bool &need_hide_edge, + bool &need_sharp_edge) +{ + char hflag = 0; + BMIter iter; + int i; + BMEdge *edge; + BM_ITER_MESH_INDEX (edge, &iter, &bm, BM_EDGES_OF_MESH, i) { + BM_elem_index_set(edge, i); /* set_inline */ + table[i] = edge; + hflag |= edge->head.hflag; + } + need_select_edge |= (hflag & BM_ELEM_SELECT); + need_hide_edge |= (hflag & BM_ELEM_HIDDEN); + need_sharp_edge |= (hflag & BM_ELEM_SMOOTH); +} + +static void bm_face_loop_table_build(BMesh &bm, + MutableSpan face_table, + MutableSpan loop_table, + bool &need_select_poly, + bool &need_hide_poly, + bool &need_material_index) +{ + char hflag = 0; + BMIter iter; + int face_i = 0; + int loop_i = 0; + BMFace *face; + BM_ITER_MESH_INDEX (face, &iter, &bm, BM_FACES_OF_MESH, face_i) { + BM_elem_index_set(face, face_i); /* set_inline */ + face_table[face_i] = face; + hflag |= face->head.hflag; + need_material_index |= face->mat_nr != 0; + + BMLoop *loop = BM_FACE_FIRST_LOOP(face); + for ([[maybe_unused]] const int i : IndexRange(face->len)) { + BM_elem_index_set(loop, loop_i); /* set_inline */ + loop_table[loop_i] = loop; + loop = loop->next; + loop_i++; + } + } + need_select_poly |= (hflag & BM_ELEM_SELECT); + need_hide_poly |= (hflag & BM_ELEM_HIDDEN); +} + +static void bm_to_mesh_verts(const BMesh &bm, + const Span bm_verts, + Mesh &mesh, + MutableSpan select_vert, + MutableSpan hide_vert) +{ + MutableSpan dst_vert_positions = mesh.vert_positions_for_write(); + threading::parallel_for(dst_vert_positions.index_range(), 1024, [&](const IndexRange range) { + for (const int vert_i : range) { + const BMVert &src_vert = *bm_verts[vert_i]; + copy_v3_v3(dst_vert_positions[vert_i], src_vert.co); + CustomData_from_bmesh_block(&bm.vdata, &mesh.vdata, src_vert.head.data, vert_i); + } + if (!select_vert.is_empty()) { + for (const int vert_i : range) { + select_vert[vert_i] = BM_elem_flag_test(bm_verts[vert_i], BM_ELEM_SELECT); + } + } + if (!hide_vert.is_empty()) { + for (const int vert_i : range) { + hide_vert[vert_i] = BM_elem_flag_test(bm_verts[vert_i], BM_ELEM_HIDDEN); + } + } + }); +} + +static void bm_to_mesh_edges(const BMesh &bm, + const Span bm_edges, + Mesh &mesh, + MutableSpan select_edge, + MutableSpan hide_edge, + MutableSpan sharp_edge) +{ + MutableSpan dst_edges = mesh.edges_for_write(); + threading::parallel_for(dst_edges.index_range(), 512, [&](const IndexRange range) { + for (const int edge_i : range) { + const BMEdge &src_edge = *bm_edges[edge_i]; + MEdge &dst_edge = dst_edges[edge_i]; + dst_edge.v1 = BM_elem_index_get(src_edge.v1); + dst_edge.v2 = BM_elem_index_get(src_edge.v2); + dst_edge.flag = bm_edge_flag_to_mflag(&src_edge); + + /* Handle this differently to editmode switching; only enable draw for single user + * edges rather than calculating angle. */ + if ((dst_edge.flag & ME_EDGEDRAW) == 0) { + if (src_edge.l && src_edge.l == src_edge.l->radial_next) { + dst_edge.flag |= ME_EDGEDRAW; + } + } + + CustomData_from_bmesh_block(&bm.edata, &mesh.edata, src_edge.head.data, edge_i); + } + if (!select_edge.is_empty()) { + for (const int edge_i : range) { + select_edge[edge_i] = BM_elem_flag_test(bm_edges[edge_i], BM_ELEM_SELECT); + } + } + if (!hide_edge.is_empty()) { + for (const int edge_i : range) { + hide_edge[edge_i] = BM_elem_flag_test(bm_edges[edge_i], BM_ELEM_HIDDEN); + } + } + if (!sharp_edge.is_empty()) { + for (const int edge_i : range) { + sharp_edge[edge_i] = !BM_elem_flag_test(bm_edges[edge_i], BM_ELEM_SMOOTH); + } + } + }); +} + +static void bm_to_mesh_faces(const BMesh &bm, + const Span bm_faces, + Mesh &mesh, + MutableSpan select_poly, + MutableSpan hide_poly, + MutableSpan material_indices) +{ + MutableSpan dst_polys = mesh.polys_for_write(); + threading::parallel_for(dst_polys.index_range(), 1024, [&](const IndexRange range) { + for (const int face_i : range) { + const BMFace &src_face = *bm_faces[face_i]; + MPoly &dst_poly = dst_polys[face_i]; + dst_poly.totloop = src_face.len; + dst_poly.loopstart = BM_elem_index_get(BM_FACE_FIRST_LOOP(&src_face)); + dst_poly.flag = bm_face_flag_to_mflag(&src_face); + CustomData_from_bmesh_block(&bm.pdata, &mesh.pdata, src_face.head.data, face_i); + } + if (!select_poly.is_empty()) { + for (const int face_i : range) { + select_poly[face_i] = BM_elem_flag_test(bm_faces[face_i], BM_ELEM_SELECT); + } + } + if (!hide_poly.is_empty()) { + for (const int face_i : range) { + hide_poly[face_i] = BM_elem_flag_test(bm_faces[face_i], BM_ELEM_HIDDEN); + } + } + if (!material_indices.is_empty()) { + for (const int face_i : range) { + material_indices[face_i] = bm_faces[face_i]->mat_nr; + } + } + }); +} + +static void bm_to_mesh_loops(const BMesh &bm, const Span bm_loops, Mesh &mesh) +{ + MutableSpan dst_loops = mesh.loops_for_write(); + threading::parallel_for(dst_loops.index_range(), 1024, [&](const IndexRange range) { + for (const int loop_i : range) { + const BMLoop &src_loop = *bm_loops[loop_i]; + MLoop &dst_loop = dst_loops[loop_i]; + dst_loop.v = BM_elem_index_get(src_loop.v); + dst_loop.e = BM_elem_index_get(src_loop.e); + CustomData_from_bmesh_block(&bm.ldata, &mesh.ldata, src_loop.head.data, loop_i); + } + }); +} + +} // namespace blender + /* NOTE: The function is called from multiple threads with the same input BMesh and different * mesh objects. */ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks *cd_mask_extra) @@ -1309,9 +1522,9 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * CustomData_add_layer_named( &me->vdata, CD_PROP_FLOAT3, CD_CONSTRUCT, nullptr, bm->totvert, "position"); } - CustomData_add_layer(&me->edata, CD_MEDGE, CD_SET_DEFAULT, nullptr, bm->totedge); - CustomData_add_layer(&me->ldata, CD_MLOOP, CD_SET_DEFAULT, nullptr, bm->totloop); - CustomData_add_layer(&me->pdata, CD_MPOLY, CD_SET_DEFAULT, nullptr, bm->totface); + CustomData_add_layer(&me->edata, CD_MEDGE, CD_CONSTRUCT, nullptr, bm->totedge); + CustomData_add_layer(&me->ldata, CD_MLOOP, CD_CONSTRUCT, nullptr, bm->totloop); + CustomData_add_layer(&me->pdata, CD_MPOLY, CD_CONSTRUCT, nullptr, bm->totface); /* Don't process shape-keys, we only feed them through the modifier stack as needed, * e.g. for applying modifiers or the like. */ @@ -1320,151 +1533,102 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks * CustomData_MeshMasks_update(&mask, cd_mask_extra); } mask.vmask &= ~CD_MASK_SHAPEKEY; - CustomData_merge(&bm->vdata, &me->vdata, mask.vmask, CD_SET_DEFAULT, me->totvert); - CustomData_merge(&bm->edata, &me->edata, mask.emask, CD_SET_DEFAULT, me->totedge); - CustomData_merge(&bm->ldata, &me->ldata, mask.lmask, CD_SET_DEFAULT, me->totloop); - CustomData_merge(&bm->pdata, &me->pdata, mask.pmask, CD_SET_DEFAULT, me->totpoly); - - BMIter iter; - BMVert *eve; - BMEdge *eed; - BMFace *efa; - MutableSpan positions = me->vert_positions_for_write(); - MutableSpan medge = me->edges_for_write(); - MutableSpan mpoly = me->polys_for_write(); - MutableSpan loops = me->loops_for_write(); - MLoop *mloop = loops.data(); - uint i, j; + CustomData_merge(&bm->vdata, &me->vdata, mask.vmask, CD_CONSTRUCT, me->totvert); + CustomData_merge(&bm->edata, &me->edata, mask.emask, CD_CONSTRUCT, me->totedge); + CustomData_merge(&bm->ldata, &me->ldata, mask.lmask, CD_CONSTRUCT, me->totloop); + CustomData_merge(&bm->pdata, &me->pdata, mask.pmask, CD_CONSTRUCT, me->totpoly); me->runtime->deformed_only = true; - bke::MutableAttributeAccessor mesh_attributes = me->attributes_for_write(); - - bke::SpanAttributeWriter hide_vert_attribute; - bke::SpanAttributeWriter select_vert_attribute; - BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) { - copy_v3_v3(positions[i], eve->co); - - BM_elem_index_set(eve, i); /* set_inline */ - - if (BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { - if (!hide_vert_attribute) { - hide_vert_attribute = mesh_attributes.lookup_or_add_for_write_span( - ".hide_vert", ATTR_DOMAIN_POINT); - } - hide_vert_attribute.span[i] = true; - } - if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - if (!select_vert_attribute) { - select_vert_attribute = mesh_attributes.lookup_or_add_for_write_span( - ".select_vert", ATTR_DOMAIN_POINT); - } - select_vert_attribute.span[i] = true; - } - - CustomData_from_bmesh_block(&bm->vdata, &me->vdata, eve->head.data, i); - } - bm->elem_index_dirty &= ~BM_VERT; - - bke::SpanAttributeWriter hide_edge_attribute; - bke::SpanAttributeWriter select_edge_attribute; - bke::SpanAttributeWriter sharp_edge_attribute; - BM_ITER_MESH_INDEX (eed, &iter, bm, BM_EDGES_OF_MESH, i) { - MEdge *med = &medge[i]; - - BM_elem_index_set(eed, i); /* set_inline */ - - med->v1 = BM_elem_index_get(eed->v1); - med->v2 = BM_elem_index_get(eed->v2); - - med->flag = BM_edge_flag_to_mflag(eed); - if (BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { - if (!hide_edge_attribute) { - hide_edge_attribute = mesh_attributes.lookup_or_add_for_write_span(".hide_edge", - ATTR_DOMAIN_EDGE); - } - hide_edge_attribute.span[i] = true; - } - if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { - if (!select_edge_attribute) { - select_edge_attribute = mesh_attributes.lookup_or_add_for_write_span( - ".select_edge", ATTR_DOMAIN_EDGE); - } - select_edge_attribute.span[i] = true; - } - if (!BM_elem_flag_test(eed, BM_ELEM_SMOOTH)) { - if (!sharp_edge_attribute) { - sharp_edge_attribute = mesh_attributes.lookup_or_add_for_write_span( - "sharp_edge", ATTR_DOMAIN_EDGE); - } - sharp_edge_attribute.span[i] = true; - } - - CustomData_from_bmesh_block(&bm->edata, &me->edata, eed->head.data, i); - } - bm->elem_index_dirty &= ~BM_EDGE; - - j = 0; - bke::SpanAttributeWriter material_index_attribute; - bke::SpanAttributeWriter hide_poly_attribute; - bke::SpanAttributeWriter select_poly_attribute; - BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) { - BMLoop *l_iter; - BMLoop *l_first; - MPoly *mp = &mpoly[i]; - - BM_elem_index_set(efa, i); /* set_inline */ - - mp->totloop = efa->len; - mp->flag = BM_face_flag_to_mflag(efa); - if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { - if (!hide_poly_attribute) { - hide_poly_attribute = mesh_attributes.lookup_or_add_for_write_span(".hide_poly", - ATTR_DOMAIN_FACE); - } - hide_poly_attribute.span[i] = true; - } - if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - if (!select_poly_attribute) { - select_poly_attribute = mesh_attributes.lookup_or_add_for_write_span( - ".select_poly", ATTR_DOMAIN_FACE); - } - select_poly_attribute.span[i] = true; - } - - mp->loopstart = j; - if (efa->mat_nr != 0) { - if (!material_index_attribute) { - material_index_attribute = mesh_attributes.lookup_or_add_for_write_span( - "material_index", ATTR_DOMAIN_FACE); - } - material_index_attribute.span[i] = efa->mat_nr; - } - - l_iter = l_first = BM_FACE_FIRST_LOOP(efa); - do { - mloop->v = BM_elem_index_get(l_iter->v); - mloop->e = BM_elem_index_get(l_iter->e); - CustomData_from_bmesh_block(&bm->ldata, &me->ldata, l_iter->head.data, j); - - BM_elem_index_set(l_iter, j); /* set_inline */ - - j++; - mloop++; - } while ((l_iter = l_iter->next) != l_first); - - CustomData_from_bmesh_block(&bm->pdata, &me->pdata, efa->head.data, i); - } - bm->elem_index_dirty &= ~(BM_FACE | BM_LOOP); + /* In a first pass, update indices of BMesh elements and build tables for easy iteration later. + * Also check if some optional mesh attributes should be added in the next step. Since each + * domain has no effect on others, process the independent domains on separate threads. */ + bool need_select_vert = false; + bool need_select_edge = false; + bool need_select_poly = false; + bool need_hide_vert = false; + bool need_hide_edge = false; + bool need_hide_poly = false; + bool need_material_index = false; + bool need_sharp_edge = false; + Array vert_table; + Array edge_table; + Array face_table; + Array loop_table; + threading::parallel_invoke( + me->totface > 1024, + [&]() { + vert_table.reinitialize(bm->totvert); + bm_vert_table_build(*bm, vert_table, need_select_vert, need_hide_vert); + }, + [&]() { + edge_table.reinitialize(bm->totedge); + bm_edge_table_build(*bm, edge_table, need_select_edge, need_hide_edge, need_sharp_edge); + }, + [&]() { + face_table.reinitialize(bm->totface); + loop_table.reinitialize(bm->totloop); + bm_face_loop_table_build( + *bm, face_table, loop_table, need_select_poly, need_hide_poly, need_material_index); + }); + bm->elem_index_dirty &= ~(BM_VERT | BM_EDGE | BM_FACE | BM_LOOP); + /* Add optional mesh attributes before parallel iteration. */ assert_bmesh_has_no_mesh_only_attributes(*bm); + bke::MutableAttributeAccessor attrs = me->attributes_for_write(); + bke::SpanAttributeWriter select_vert; + bke::SpanAttributeWriter hide_vert; + bke::SpanAttributeWriter select_edge; + bke::SpanAttributeWriter hide_edge; + bke::SpanAttributeWriter sharp_edge; + bke::SpanAttributeWriter select_poly; + bke::SpanAttributeWriter hide_poly; + bke::SpanAttributeWriter material_index; + if (need_select_vert) { + select_vert = attrs.lookup_or_add_for_write_only_span(".select_vert", ATTR_DOMAIN_POINT); + } + if (need_hide_vert) { + hide_vert = attrs.lookup_or_add_for_write_only_span(".hide_vert", ATTR_DOMAIN_POINT); + } + if (need_select_edge) { + select_edge = attrs.lookup_or_add_for_write_only_span(".select_edge", ATTR_DOMAIN_EDGE); + } + if (need_sharp_edge) { + sharp_edge = attrs.lookup_or_add_for_write_only_span("sharp_edge", ATTR_DOMAIN_EDGE); + } + if (need_hide_edge) { + hide_edge = attrs.lookup_or_add_for_write_only_span(".hide_edge", ATTR_DOMAIN_EDGE); + } + if (need_select_poly) { + select_poly = attrs.lookup_or_add_for_write_only_span(".select_poly", ATTR_DOMAIN_FACE); + } + if (need_hide_poly) { + hide_poly = attrs.lookup_or_add_for_write_only_span(".hide_poly", ATTR_DOMAIN_FACE); + } + if (need_material_index) { + material_index = attrs.lookup_or_add_for_write_only_span("material_index", + ATTR_DOMAIN_FACE); + } - material_index_attribute.finish(); - hide_vert_attribute.finish(); - hide_edge_attribute.finish(); - hide_poly_attribute.finish(); - select_vert_attribute.finish(); - select_edge_attribute.finish(); - select_poly_attribute.finish(); - sharp_edge_attribute.finish(); + /* Loop over all elements in parallel, copying attributes and building the Mesh topology. */ + threading::parallel_invoke( + me->totvert > 1024, + [&]() { bm_to_mesh_verts(*bm, vert_table, *me, select_vert.span, hide_vert.span); }, + [&]() { + bm_to_mesh_edges(*bm, edge_table, *me, select_edge.span, hide_edge.span, sharp_edge.span); + }, + [&]() { + bm_to_mesh_faces( + *bm, face_table, *me, select_poly.span, hide_poly.span, material_index.span); + }, + [&]() { bm_to_mesh_loops(*bm, loop_table, *me); }); + + select_vert.finish(); + hide_vert.finish(); + select_edge.finish(); + hide_edge.finish(); + sharp_edge.finish(); + select_poly.finish(); + hide_poly.finish(); + material_index.finish(); }