diff --git a/source/blender/io/usd/hydra/mesh.cc b/source/blender/io/usd/hydra/mesh.cc index 7dac3b15bc9..1239c12b258 100644 --- a/source/blender/io/usd/hydra/mesh.cc +++ b/source/blender/io/usd/hydra/mesh.cc @@ -6,7 +6,9 @@ #include #include +#include "BLI_array_utils.hh" #include "BLI_string.h" +#include "BLI_vector_set.hh" #include "BKE_attribute.h" #include "BKE_material.h" @@ -217,6 +219,148 @@ const MeshData::SubMesh &MeshData::submesh(pxr::SdfPath const &id) const return submeshes_[index]; } +/** + * #VtArray::resize() does value initialization of every new value, which ends up being `memset` + * for the trivial attribute types we deal with here. This is unnecessary since every item is + * initialized via copy from a Blender mesh here anyway. This specializes the resize call to skip + * initialization. + */ +template static void resize_uninitialized(pxr::VtArray &array, const int new_size) +{ + static_assert(std::is_trivial_v); + array.resize(new_size, [](auto /*begin*/, auto /*end*/) {}); +} + +static std::pair> get_mesh_normals(const Mesh &mesh) +{ + switch (mesh.normals_domain()) { + case bke::MeshNormalDomain::Face: + return {bke::MeshNormalDomain::Face, mesh.face_normals()}; + case bke::MeshNormalDomain::Point: + return {bke::MeshNormalDomain::Point, mesh.vert_normals()}; + case bke::MeshNormalDomain::Corner: + return {bke::MeshNormalDomain::Corner, mesh.corner_normals()}; + } + BLI_assert_unreachable(); + return {}; +} + +template +void gather_vert_data(const Span verts, + const bool copy_all_verts, + const Span src_data, + MutableSpan dst_data) +{ + if (copy_all_verts) { + array_utils::copy(src_data, dst_data); + } + else { + array_utils::gather(src_data, verts, dst_data); + } +} + +template +void gather_face_data(const Span looptri_faces, + const IndexMask &triangles, + const Span src_data, + MutableSpan dst_data) +{ + triangles.foreach_index_optimized(GrainSize(1024), [&](const int src, const int dst) { + dst_data[dst] = src_data[looptri_faces[src]]; + }); +} + +template +void gather_corner_data(const Span looptris, + const IndexMask &triangles, + const Span src_data, + MutableSpan dst_data) +{ + triangles.foreach_index_optimized(GrainSize(1024), [&](const int src, const int dst) { + const MLoopTri &tri = looptris[src]; + dst_data[dst * 3 + 0] = src_data[tri.tri[0]]; + dst_data[dst * 3 + 1] = src_data[tri.tri[1]]; + dst_data[dst * 3 + 2] = src_data[tri.tri[2]]; + }); +} + +static void copy_submesh(const Mesh &mesh, + const Span vert_positions, + const Span corner_verts, + const Span looptris, + const Span looptri_faces, + const std::pair> normals, + const Span uv_map, + const IndexMask &triangles, + MeshData::SubMesh &sm) +{ + resize_uninitialized(sm.face_vertex_indices, triangles.size() * 3); + + /* If all triangles are part of this submesh and there are no loose vertices that shouldn't be + * copied (Hydra will warn about this), vertex index compression can be completely skipped. */ + const bool copy_all_verts = triangles.size() == looptris.size() && + mesh.verts_no_face().count == 0; + + int dst_verts_num; + VectorSet verts; + if (copy_all_verts) { + /* Copy the vertex indices from the corner indices stored in every triangle. */ + array_utils::gather(corner_verts, + looptris.cast(), + MutableSpan(sm.face_vertex_indices.data(), sm.face_vertex_indices.size())); + dst_verts_num = vert_positions.size(); + } + else { + /* Compress vertex indices to be contiguous so it's only necessary to copy values + * for vertices actually used by the subset of triangles. */ + verts.reserve(triangles.size()); + triangles.foreach_index([&](const int src, const int dst) { + const MLoopTri &tri = looptris[src]; + sm.face_vertex_indices[dst * 3 + 0] = verts.index_of_or_add(corner_verts[tri.tri[0]]); + sm.face_vertex_indices[dst * 3 + 1] = verts.index_of_or_add(corner_verts[tri.tri[1]]); + sm.face_vertex_indices[dst * 3 + 2] = verts.index_of_or_add(corner_verts[tri.tri[2]]); + }); + dst_verts_num = verts.size(); + } + + resize_uninitialized(sm.vertices, dst_verts_num); + gather_vert_data(verts, + copy_all_verts, + vert_positions, + MutableSpan(sm.vertices.data(), sm.vertices.size()).cast()); + + resize_uninitialized(sm.face_vertex_counts, triangles.size()); + std::fill(sm.face_vertex_counts.begin(), sm.face_vertex_counts.end(), 3); + + const Span src_normals = normals.second; + resize_uninitialized(sm.normals, triangles.size() * 3); + MutableSpan dst_normals = MutableSpan(sm.normals.data(), sm.normals.size()).cast(); + switch (normals.first) { + case bke::MeshNormalDomain::Face: + triangles.foreach_index(GrainSize(1024), [&](const int src, const int dst) { + std::fill_n(&dst_normals[dst * 3], 3, src_normals[looptri_faces[src]]); + }); + break; + case bke::MeshNormalDomain::Point: + triangles.foreach_index(GrainSize(1024), [&](const int src, const int dst) { + const MLoopTri &tri = looptris[src]; + dst_normals[dst * 3 + 0] = src_normals[corner_verts[tri.tri[0]]]; + dst_normals[dst * 3 + 1] = src_normals[corner_verts[tri.tri[1]]]; + dst_normals[dst * 3 + 2] = src_normals[corner_verts[tri.tri[2]]]; + }); + break; + case bke::MeshNormalDomain::Corner: + gather_corner_data(looptris, triangles, src_normals, dst_normals); + break; + } + + if (!uv_map.is_empty()) { + resize_uninitialized(sm.uvs, triangles.size() * 3); + gather_corner_data( + looptris, triangles, uv_map, MutableSpan(sm.uvs.data(), sm.uvs.size()).cast()); + } +} + void MeshData::write_submeshes(const Mesh *mesh) { const int mat_count = BKE_object_material_count_eval(reinterpret_cast(id)); @@ -225,71 +369,55 @@ void MeshData::write_submeshes(const Mesh *mesh) submeshes_[i].mat_index = i; } - /* Fill submeshes data */ - const int *material_indices = BKE_mesh_material_indices(mesh); - - const Span looptri_faces = mesh->looptri_faces(); + const Span vert_positions = mesh->vert_positions(); const Span corner_verts = mesh->corner_verts(); const Span looptris = mesh->looptris(); + const Span looptri_faces = mesh->looptri_faces(); - Span corner_normals; - if (mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner) { - corner_normals = mesh->corner_normals(); - } + const std::pair> normals = get_mesh_normals(*mesh); const float2 *uv_map = static_cast( CustomData_get_layer(&mesh->loop_data, CD_PROP_FLOAT2)); - for (const int i : looptris.index_range()) { - int mat_ind = material_indices ? material_indices[looptri_faces[i]] : 0; - const MLoopTri < = looptris[i]; - SubMesh &sm = submeshes_[mat_ind]; - - sm.face_vertex_counts.push_back(3); - sm.face_vertex_indices.push_back(corner_verts[lt.tri[0]]); - sm.face_vertex_indices.push_back(corner_verts[lt.tri[1]]); - sm.face_vertex_indices.push_back(corner_verts[lt.tri[2]]); - - if (!corner_normals.is_empty()) { - sm.normals.push_back(pxr::GfVec3f(&corner_normals[lt.tri[0]].x)); - sm.normals.push_back(pxr::GfVec3f(&corner_normals[lt.tri[1]].x)); - sm.normals.push_back(pxr::GfVec3f(&corner_normals[lt.tri[2]].x)); - } - - if (uv_map) { - sm.uvs.push_back(pxr::GfVec2f(&uv_map[lt.tri[0]].x)); - sm.uvs.push_back(pxr::GfVec2f(&uv_map[lt.tri[1]].x)); - sm.uvs.push_back(pxr::GfVec2f(&uv_map[lt.tri[2]].x)); - } - } - - /* Remove submeshes without faces */ - submeshes_.remove_if([](const SubMesh &submesh) { return submesh.face_vertex_counts.empty(); }); - if (submeshes_.is_empty()) { + const int *material_indices = BKE_mesh_material_indices(mesh); + if (!material_indices) { + copy_submesh(*mesh, + vert_positions, + corner_verts, + looptris, + looptri_faces, + normals, + uv_map ? Span(uv_map, mesh->totloop) : Span(), + looptris.index_range(), + submeshes_.first()); return; } - pxr::VtVec3fArray vertices(mesh->totvert); - const Span positions = mesh->vert_positions(); - MutableSpan(vertices.data(), vertices.size()).copy_from(positions.cast()); + IndexMaskMemory memory; + Array triangles_by_material(submeshes_.size()); + const int max_index = std::max(mat_count - 1, 0); + IndexMask::from_groups( + looptris.index_range(), + memory, + [&](const int i) { return std::min(material_indices[looptri_faces[i]], max_index); }, + triangles_by_material); - if (submeshes_.size() == 1) { - submeshes_[0].vertices = std::move(vertices); - } - else { - /* Optimizing submeshes: getting only used vertices, rearranged indices */ - for (SubMesh &sm : submeshes_) { - Vector index_map(vertices.size(), 0); - for (int &face_vertex_index : sm.face_vertex_indices) { - const int v = face_vertex_index; - if (index_map[v] == 0) { - sm.vertices.push_back(vertices[v]); - index_map[v] = sm.vertices.size(); - } - face_vertex_index = index_map[v] - 1; - } + threading::parallel_for(submeshes_.index_range(), 1, [&](const IndexRange range) { + for (const int i : range) { + copy_submesh(*mesh, + vert_positions, + corner_verts, + looptris, + looptri_faces, + normals, + uv_map ? Span(uv_map, mesh->totloop) : Span(), + triangles_by_material[i], + submeshes_[i]); } - } + }); + + /* Remove submeshes without faces */ + submeshes_.remove_if([](const SubMesh &submesh) { return submesh.face_vertex_counts.empty(); }); } void MeshData::update_prims() diff --git a/source/blender/io/usd/hydra/mesh.h b/source/blender/io/usd/hydra/mesh.h index 7302b439983..29dbd74a102 100644 --- a/source/blender/io/usd/hydra/mesh.h +++ b/source/blender/io/usd/hydra/mesh.h @@ -17,6 +17,7 @@ namespace blender::io::hydra { class MeshData : public ObjectData { + public: struct SubMesh { pxr::VtVec3fArray vertices; pxr::VtIntArray face_vertex_counts; @@ -27,6 +28,7 @@ class MeshData : public ObjectData { MaterialData *mat_data = nullptr; }; + private: Vector submeshes_; int submeshes_count_ = 0;