From de821416c75bffcfc175870e68f6580827289f6f Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sun, 9 Mar 2025 03:59:40 +0100 Subject: [PATCH] Cycles: Adaptive subdivision dicing and splitting improvements * Share vertices between patches instead of using stitch map * Switch to OpenSubdiv compatible counter-clockwise indexing * Simplify patch edge reverse direction logic * Add more comments to splitting and dicing Pull Request: https://projects.blender.org/blender/blender/pulls/135681 --- intern/cycles/scene/mesh.cpp | 3 - intern/cycles/scene/mesh.h | 5 - intern/cycles/scene/mesh_displace.cpp | 70 -- intern/cycles/scene/mesh_subdivision.cpp | 26 +- intern/cycles/subd/dice.cpp | 56 +- intern/cycles/subd/dice.h | 6 +- intern/cycles/subd/osd.h | 3 +- intern/cycles/subd/split.cpp | 922 +++++++++-------------- intern/cycles/subd/split.h | 54 +- intern/cycles/subd/subpatch.h | 176 +++-- tests/data | 2 +- tests/python/cycles_render_tests.py | 5 +- 12 files changed, 506 insertions(+), 822 deletions(-) diff --git a/intern/cycles/scene/mesh.cpp b/intern/cycles/scene/mesh.cpp index f223a4afe3d..613d80c83e0 100644 --- a/intern/cycles/scene/mesh.cpp +++ b/intern/cycles/scene/mesh.cpp @@ -260,9 +260,6 @@ void Mesh::clear_non_sockets() num_subd_added_verts = 0; num_subd_faces = 0; - - vert_to_stitching_key_map.clear(); - vert_stitching_map.clear(); } void Mesh::clear(bool preserve_shaders, bool preserve_voxel_data) diff --git a/intern/cycles/scene/mesh.h b/intern/cycles/scene/mesh.h index 9090c2e80ea..42c83ca657f 100644 --- a/intern/cycles/scene/mesh.h +++ b/intern/cycles/scene/mesh.h @@ -14,7 +14,6 @@ #include "util/array.h" #include "util/boundbox.h" -#include "util/map.h" #include "util/param.h" #include "util/set.h" #include "util/types.h" @@ -182,10 +181,6 @@ class Mesh : public Geometry { size_t num_subd_added_verts; size_t num_subd_faces; - unordered_map vert_to_stitching_key_map; /* real vert index -> stitching index */ - unordered_multimap - vert_stitching_map; /* stitching index -> multiple real vert indices */ - friend class BVH2; friend class BVHBuild; friend class BVHSpatialSplit; diff --git a/intern/cycles/scene/mesh_displace.cpp b/intern/cycles/scene/mesh_displace.cpp index fe3835f6d9b..c928a5eb7cc 100644 --- a/intern/cycles/scene/mesh_displace.cpp +++ b/intern/cycles/scene/mesh_displace.cpp @@ -11,9 +11,7 @@ #include "scene/scene.h" #include "scene/shader.h" -#include "util/map.h" #include "util/progress.h" -#include "util/set.h" CCL_NAMESPACE_BEGIN @@ -180,38 +178,6 @@ bool GeometryManager::displace(Device *device, Scene *scene, Mesh *mesh, Progres return false; } - /* stitch */ - unordered_set stitch_keys; - for (const pair i : mesh->vert_to_stitching_key_map) { - stitch_keys.insert(i.second); /* stitching index */ - } - - using map_it_t = unordered_multimap::iterator; - - for (const int key : stitch_keys) { - const pair verts = mesh->vert_stitching_map.equal_range(key); - - float3 pos = zero_float3(); - int num = 0; - - for (map_it_t v = verts.first; v != verts.second; ++v) { - const int vert = v->second; - - pos += mesh->verts[vert]; - num++; - } - - if (num <= 1) { - continue; - } - - pos *= 1.0f / num; - - for (map_it_t v = verts.first; v != verts.second; ++v) { - mesh->verts[v->second] = pos; - } - } - /* For displacement method both, we don't need to recompute the vertex normals * as bump mapping in the shader will already alter the vertex normal, so we start * from the non-displaced vertex normals to avoid applying the perturbation twice. */ @@ -267,24 +233,6 @@ bool GeometryManager::displace(Device *device, Scene *scene, Mesh *mesh, Progres for (size_t j = 0; j < 3; j++) { const int vert = triangle.v[j]; vN[vert] += fN; - - /* add face normals to stitched vertices */ - if (!stitch_keys.empty()) { - const map_it_t key = mesh->vert_to_stitching_key_map.find(vert); - - if (key != mesh->vert_to_stitching_key_map.end()) { - const pair verts = mesh->vert_stitching_map.equal_range( - key->second); - - for (map_it_t v = verts.first; v != verts.second; ++v) { - if (v->second == vert) { - continue; - } - - vN[v->second] += fN; - } - } - } } } } @@ -342,24 +290,6 @@ bool GeometryManager::displace(Device *device, Scene *scene, Mesh *mesh, Progres for (size_t j = 0; j < 3; j++) { const int vert = triangle.v[j]; mN[vert] += fN; - - /* add face normals to stitched vertices */ - if (!stitch_keys.empty()) { - const map_it_t key = mesh->vert_to_stitching_key_map.find(vert); - - if (key != mesh->vert_to_stitching_key_map.end()) { - const pair verts = mesh->vert_stitching_map.equal_range( - key->second); - - for (map_it_t v = verts.first; v != verts.second; ++v) { - if (v->second == vert) { - continue; - } - - mN[v->second] += fN; - } - } - } } } } diff --git a/intern/cycles/scene/mesh_subdivision.cpp b/intern/cycles/scene/mesh_subdivision.cpp index f36a561edd8..d20dc50d734 100644 --- a/intern/cycles/scene/mesh_subdivision.cpp +++ b/intern/cycles/scene/mesh_subdivision.cpp @@ -97,11 +97,10 @@ void Mesh::tessellate(DiagSplit *split) patch->patch_index = face.ptex_offset; patch->from_ngon = false; - for (int i = 0; i < 4; i++) { - hull[i] = verts[subd_face_corners[face.start_corner + i]]; - } - - swap(hull[2], hull[3]); + hull[0] = verts[subd_face_corners[face.start_corner + 0]]; + hull[1] = verts[subd_face_corners[face.start_corner + 1]]; + hull[2] = verts[subd_face_corners[face.start_corner + 3]]; + hull[3] = verts[subd_face_corners[face.start_corner + 2]]; patch->shader = face.shader; patch++; @@ -123,16 +122,15 @@ void Mesh::tessellate(DiagSplit *split) patch->shader = face.shader; - hull[0] = - verts[subd_face_corners[face.start_corner + mod(corner + 0, face.num_corners)]]; - hull[1] = - verts[subd_face_corners[face.start_corner + mod(corner + 1, face.num_corners)]]; - hull[2] = - verts[subd_face_corners[face.start_corner + mod(corner - 1, face.num_corners)]]; - hull[3] = center_vert; + const int v0 = subd_face_corners[face.start_corner + mod(corner + 0, face.num_corners)]; + const int v1 = subd_face_corners[face.start_corner + mod(corner + 1, face.num_corners)]; + const int v3 = subd_face_corners[face.start_corner + + mod(corner + face.num_corners - 1, face.num_corners)]; - hull[1] = (hull[1] + hull[0]) * 0.5; - hull[2] = (hull[2] + hull[0]) * 0.5; + hull[0] = verts[v0]; + hull[1] = 0.5f * (verts[v0] + verts[v1]); + hull[2] = 0.5f * (verts[v3] + verts[v0]); + hull[3] = center_vert; patch++; } diff --git a/intern/cycles/subd/dice.cpp b/intern/cycles/subd/dice.cpp index e8a76fb6f8e..898bbb25930 100644 --- a/intern/cycles/subd/dice.cpp +++ b/intern/cycles/subd/dice.cpp @@ -26,10 +26,10 @@ void EdgeDice::reserve(const int num_verts, const int num_triangles) { Mesh *mesh = params.mesh; - mesh->num_subd_added_verts = num_verts; + mesh->num_subd_added_verts = num_verts - mesh->get_verts().size(); - mesh->resize_mesh(mesh->get_verts().size() + num_verts, mesh->num_triangles()); - mesh->reserve_mesh(mesh->get_verts().size() + num_verts, mesh->num_triangles() + num_triangles); + mesh->resize_mesh(num_verts, 0); + mesh->reserve_mesh(num_verts, num_triangles); Attribute *attr_vN = mesh->attributes.add(ATTR_STD_VERTEX_NORMAL); @@ -91,17 +91,11 @@ void EdgeDice::add_triangle(const Patch *patch, void EdgeDice::stitch_triangles(SubPatch &sub, const int edge) { - int Mu = max(sub.edge_u0.T, sub.edge_u1.T); - int Mv = max(sub.edge_v0.T, sub.edge_v1.T); - Mu = max(Mu, 2); - Mv = max(Mv, 2); + const int Mu = max(max(sub.edge_u0.edge->T, sub.edge_u1.edge->T), 2); + const int Mv = max(max(sub.edge_v0.edge->T, sub.edge_v1.edge->T), 2); - const int outer_T = sub.edges[edge].T; - const int inner_T = ((edge % 2) == 0) ? Mv - 2 : Mu - 2; - - if (inner_T < 0 || outer_T < 0) { - return; // XXX avoid crashes for Mu or Mv == 1, missing polygons - } + const int outer_T = sub.edges[edge].edge->T; + const int inner_T = ((edge % 2) == 0) ? Mu - 2 : Mv - 2; const float du = 1.0f / (float)Mu; const float dv = 1.0f / (float)Mv; @@ -134,9 +128,14 @@ void EdgeDice::stitch_triangles(SubPatch &sub, const int edge) break; } - /* Stitch together two arrays of verts with triangles. at each step, we compare using the next - * verts on both sides, to find the split direction with the smallest diagonal, and use that - * in order to keep the triangle shape reasonable. */ + if (inner_T < 0 || outer_T < 0) { + return; // XXX avoid crashes for Mu or Mv == 1, missing polygons + } + + /* stitch together two arrays of verts with triangles. at each step, + * we compare using the next verts on both sides, to find the split + * direction with the smallest diagonal, and use that in order to keep + * the triangle shape reasonable. */ for (size_t i = 0, j = 0; i < inner_T || j < outer_T;) { const int v0 = sub.get_vert_along_grid_edge(edge, i); const int v1 = sub.get_vert_along_edge(edge, j); @@ -157,13 +156,13 @@ void EdgeDice::stitch_triangles(SubPatch &sub, const int edge) uv2 = sub.map_uv(outer_uv); } else { - /* Length of diagonals. */ + /* length of diagonals */ const float len1 = len_squared(mesh_P[sub.get_vert_along_grid_edge(edge, i)] - mesh_P[sub.get_vert_along_edge(edge, j + 1)]); const float len2 = len_squared(mesh_P[sub.get_vert_along_edge(edge, j)] - mesh_P[sub.get_vert_along_grid_edge(edge, i + 1)]); - /* Use smallest diagonal. */ + /* use smallest diagonal */ if (len1 < len2) { v2 = sub.get_vert_along_edge(edge, ++j); outer_uv += outer_uv_step; @@ -176,7 +175,7 @@ void EdgeDice::stitch_triangles(SubPatch &sub, const int edge) } } - add_triangle(sub.patch, v1, v0, v2, uv1, uv0, uv2); + add_triangle(sub.patch, v0, v1, v2, uv0, uv1, uv2); } } @@ -203,7 +202,7 @@ void QuadDice::set_vert(SubPatch &sub, const int index, const float2 uv) void QuadDice::set_side(SubPatch &sub, const int edge) { - const int t = sub.edges[edge].T; + const int t = sub.edges[edge].edge->T; /* set verts on the edge of the patch */ for (int i = 0; i < t; i++) { @@ -212,17 +211,17 @@ void QuadDice::set_side(SubPatch &sub, const int edge) float2 uv; switch (edge) { case 0: - uv = make_float2(0.0f, f); + uv = make_float2(f, 0.0f); break; case 1: - uv = make_float2(f, 1.0f); + uv = make_float2(1.0f, f); break; case 2: - uv = make_float2(1.0f, 1.0f - f); + uv = make_float2(1.0f - f, 1.0f); break; case 3: default: - uv = make_float2(1.0f - f, 0.0f); + uv = make_float2(0.0f, 1.0f - f); break; } @@ -259,8 +258,9 @@ float QuadDice::scale_factor(SubPatch &sub, const int Mu, const int Mv) // XXX does the -sqrt solution matter // XXX max(D, 0.0) is highly suspicious, need to test cases // where D goes negative - const float N = 0.5f * (Ntris - (sub.edge_u0.T + sub.edge_u1.T + sub.edge_v0.T + sub.edge_v1.T)); - const float D = 4.0f * N * Mu * Mv + (Mu + Mv) * (Mu + Mv); + const float N = 0.5f * (Ntris - (sub.edge_u0.edge->T + sub.edge_u1.edge->T + + sub.edge_v0.edge->T + sub.edge_v1.edge->T)); + const float D = (4.0f * N * Mu * Mv) + ((Mu + Mv) * (Mu + Mv)); const float S = (Mu + Mv + sqrtf(max(D, 0.0f))) / (2 * Mu * Mv); return S; @@ -301,8 +301,8 @@ void QuadDice::add_grid(SubPatch &sub, const int Mu, const int Mv, const int off void QuadDice::dice(SubPatch &sub) { /* compute inner grid size with scale factor */ - int Mu = max(sub.edge_u0.T, sub.edge_u1.T); - int Mv = max(sub.edge_v0.T, sub.edge_v1.T); + int Mu = max(sub.edge_u0.edge->T, sub.edge_u1.edge->T); + int Mv = max(sub.edge_v0.edge->T, sub.edge_v1.edge->T); #if 0 /* Doesn't work very well, especially at grazing angles. */ const float S = scale_factor(sub, ef, Mu, Mv); diff --git a/intern/cycles/subd/dice.h b/intern/cycles/subd/dice.h index 4ceac649136..a4d1a8228b1 100644 --- a/intern/cycles/subd/dice.h +++ b/intern/cycles/subd/dice.h @@ -48,7 +48,6 @@ class EdgeDice { void reserve(const int num_verts, const int num_triangles); - protected: void set_vert(const Patch *patch, const int index, const float2 uv); void add_triangle(const Patch *patch, const int v0, @@ -67,9 +66,6 @@ class QuadDice : public EdgeDice { public: explicit QuadDice(const SubdParams ¶ms); - void dice(SubPatch &sub); - - protected: float3 eval_projected(SubPatch &sub, const float2 uv); void set_vert(SubPatch &sub, const int index, const float2 uv); @@ -80,6 +76,8 @@ class QuadDice : public EdgeDice { float quad_area(const float3 &a, const float3 &b, const float3 &c, const float3 &d); float scale_factor(SubPatch &sub, const int Mu, const int Mv); + + void dice(SubPatch &sub); }; CCL_NAMESPACE_END diff --git a/intern/cycles/subd/osd.h b/intern/cycles/subd/osd.h index 5250f761f36..4cde9bcbb92 100644 --- a/intern/cycles/subd/osd.h +++ b/intern/cycles/subd/osd.h @@ -1,4 +1,4 @@ -/* SPDX-FileCopyrightText: 2011-2025 Blender Foundation +/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation * * SPDX-License-Identifier: Apache-2.0 */ @@ -76,7 +76,6 @@ struct OsdData { vector> refined_verts; void build(OsdMesh &osd_mesh); - void subdivide_attribute(Attribute &attr); }; /* Patch with OpenSubdiv evaluation. */ diff --git a/intern/cycles/subd/split.cpp b/intern/cycles/subd/split.cpp index 4ed75575474..fa751725d7f 100644 --- a/intern/cycles/subd/split.cpp +++ b/intern/cycles/subd/split.cpp @@ -9,9 +9,9 @@ #include "subd/patch.h" #include "subd/split.h" +#include "subd/subpatch.h" #include "util/algorithm.h" -#include "util/hash.h" #include "util/math.h" #include "util/types.h" @@ -21,12 +21,45 @@ CCL_NAMESPACE_BEGIN enum { DSPLIT_NON_UNIFORM = -1, - STITCH_NGON_CENTER_VERT_INDEX_OFFSET = 0x60000000, - STITCH_NGON_SPLIT_EDGE_CENTER_VERT_TAG = (0x60000000 - 1) + DSPLIT_MAX_DEPTH = 32, + DSPLIT_MAX_SEGMENTS = 8, }; DiagSplit::DiagSplit(const SubdParams ¶ms_) : params(params_) {} +int DiagSplit::alloc_verts(int num) +{ + const int index = num_verts; + num_verts += num; + return index; +} + +SubEdge *DiagSplit::alloc_edge(const int v0, const int v1) +{ + const SubEdge edge(v0, v1); + const auto it = edges.find(edge); + return const_cast((it == edges.end()) ? &*(edges.emplace(edge).first) : &*it); +} + +void DiagSplit::alloc_edge(SubPatch::Edge *sub_edge, int v0, int v1) +{ + sub_edge->edge = (v0 < v1) ? alloc_edge(v0, v1) : alloc_edge(v1, v0); + sub_edge->reversed = sub_edge->edge->start_vert_index != v0; +} + +void DiagSplit::alloc_subpatch(SubPatch &&sub) +{ + assert(sub.edge_u0.edge->T >= 1); + assert(sub.edge_v1.edge->T >= 1); + assert(sub.edge_u1.edge->T >= 1); + assert(sub.edge_v0.edge->T >= 1); + + sub.inner_grid_vert_offset = alloc_verts(sub.calc_num_inner_verts()); + num_triangles += sub.calc_num_triangles(); + + subpatches.push_back(std::move(sub)); +} + float3 DiagSplit::to_world(const Patch *patch, const float2 uv) { float3 P; @@ -39,16 +72,13 @@ float3 DiagSplit::to_world(const Patch *patch, const float2 uv) return P; } -static void order_float2(float2 &a, float2 &b) +int DiagSplit::T( + const Patch *patch, float2 Pstart, float2 Pend, const int depth, const bool recursive_resolve) { - if (b.x < a.x || b.y < a.y) { - swap(a, b); + /* May not be necessary, but better to be safe. */ + if (Pend.x < Pstart.x || Pend.y < Pstart.y) { + swap(Pstart, Pend); } -} - -int DiagSplit::T(const Patch *patch, float2 Pstart, float2 Pend, const bool recursive_resolve) -{ - order_float2(Pstart, Pend); /* May not be necessary, but better to be safe. */ float Lsum = 0.0f; float Lmax = 0.0f; @@ -90,41 +120,26 @@ int DiagSplit::T(const Patch *patch, float2 Pstart, float2 Pend, const bool recu } else { const float2 P = (Pstart + Pend) * 0.5f; - res = T(patch, Pstart, P, true) + T(patch, P, Pend, true); + res = T(patch, Pstart, P, depth, true) + T(patch, P, Pend, depth, true); + } + } + + res = limit_edge_factor(patch, Pstart, Pend, res); + + /* Limit edge factor so we don't go beyond max depth. */ + if (depth >= DSPLIT_MAX_DEPTH - 2) { + if (res == DSPLIT_NON_UNIFORM || res > DSPLIT_MAX_SEGMENTS) { + res = DSPLIT_MAX_SEGMENTS; } } - limit_edge_factor(res, patch, Pstart, Pend); return res; } -void DiagSplit::partition_edge(const Patch *patch, - float2 *P, - int *t0, - int *t1, - const float2 Pstart, - const float2 Pend, - const int t) -{ - if (t == DSPLIT_NON_UNIFORM) { - *P = (Pstart + Pend) * 0.5f; - *t0 = T(patch, Pstart, *P); - *t1 = T(patch, *P, Pend); - } - else { - assert(t >= 2); /* Need at least two segments to partition into. */ - - const int I = (int)floorf((float)t * 0.5f); - *P = interp(Pstart, Pend, I / (float)t); - *t0 = I; - *t1 = t - I; - } -} - -void DiagSplit::limit_edge_factor(int &T, - const Patch *patch, - const float2 Pstart, - const float2 Pend) +int DiagSplit::limit_edge_factor(const Patch *patch, + const float2 Pstart, + const float2 Pend, + const int T) { const int max_t = 1 << params.max_level; int max_t_for_edge = int(max_t * len(Pstart - Pend)); @@ -133,63 +148,122 @@ void DiagSplit::limit_edge_factor(int &T, max_t_for_edge >>= 1; /* Initial split of ngon causes edges to extend half the distance. */ } - T = (max_t_for_edge <= 1) ? 1 : min(T, max_t_for_edge); + const int limit_T = (max_t_for_edge <= 1) ? 1 : min(T, max_t_for_edge); - assert(T >= 1 || T == DSPLIT_NON_UNIFORM); + assert(limit_T >= 1 || limit_T == DSPLIT_NON_UNIFORM); + return limit_T; } -void DiagSplit::resolve_edge_factors(SubPatch &sub) +void DiagSplit::assign_edge_factor(SubEdge *edge, const int T) { - /* Resolve DSPLIT_NON_UNIFORM to actual T value if splitting is no longer possible. */ - if (sub.edge_u0.T == 1 && sub.edge_u1.T == DSPLIT_NON_UNIFORM) { - sub.edge_u1.T = T(sub.patch, sub.uv01, sub.uv11, true); - } - if (sub.edge_u1.T == 1 && sub.edge_u0.T == DSPLIT_NON_UNIFORM) { - sub.edge_u0.T = T(sub.patch, sub.uv00, sub.uv10, true); - } - if (sub.edge_v0.T == 1 && sub.edge_v1.T == DSPLIT_NON_UNIFORM) { - sub.edge_v1.T = T(sub.patch, sub.uv11, sub.uv10, true); - } - if (sub.edge_v1.T == 1 && sub.edge_v0.T == DSPLIT_NON_UNIFORM) { - sub.edge_v0.T = T(sub.patch, sub.uv01, sub.uv00, true); + assert(edge->T == 0 || edge->T == DSPLIT_NON_UNIFORM); + edge->T = T; + + if (edge->T != DSPLIT_NON_UNIFORM) { + edge->second_vert_index = alloc_verts(edge->T - 1); } } -void DiagSplit::split(SubPatch &sub, const int depth) +void DiagSplit::resolve_edge_factors(const SubPatch &sub, const int depth) { - if (depth > 32) { - /* We should never get here, but just in case end recursion safely. */ - assert(!"diagsplit recursion limit reached"); - - sub.edge_u0.T = 1; - sub.edge_u1.T = 1; - sub.edge_v0.T = 1; - sub.edge_v1.T = 1; - - subpatches.push_back(sub); - return; - } - - bool split_u = (sub.edge_u0.T == DSPLIT_NON_UNIFORM || sub.edge_u1.T == DSPLIT_NON_UNIFORM); - bool split_v = (sub.edge_v0.T == DSPLIT_NON_UNIFORM || sub.edge_v1.T == DSPLIT_NON_UNIFORM); - - /* Split subpatches such that the ratio of T for opposite edges doesn't - * exceed 1.5, this reduces over tessellation for some patches - */ - /* clang-format off */ - if (min(sub.edge_u0.T, sub.edge_u1.T) > 8 && /* Must be uniform and preferably greater than 8 to split. */ - min(sub.edge_v0.T, sub.edge_v1.T) >= 2 && /* Must be uniform and at least 2 to split. */ - max(sub.edge_u0.T, sub.edge_u1.T) / min(sub.edge_u0.T, sub.edge_u1.T) > 1.5f) + /* Compute edge factor if not already set. Or if DSPLIT_NON_UNIFORM and splitting is + * no longer possible because the opposite side can't be split. */ + if (sub.edge_u0.edge->T == 0 || + (sub.edge_u0.edge->T == DSPLIT_NON_UNIFORM && sub.edge_u1.edge->T == 1)) { - split_v = true; + assign_edge_factor(sub.edge_u0.edge, T(sub.patch, sub.uv00, sub.uv10, depth, true)); } - if (min(sub.edge_v0.T, sub.edge_v1.T) > 8 && - min(sub.edge_u0.T, sub.edge_u1.T) >= 2 && - max(sub.edge_v0.T, sub.edge_v1.T) / min(sub.edge_v0.T, sub.edge_v1.T) > 1.5f) + if (sub.edge_v1.edge->T == 0 || + (sub.edge_v1.edge->T == DSPLIT_NON_UNIFORM && sub.edge_v0.edge->T == 1)) { - split_u = true; + assign_edge_factor(sub.edge_v1.edge, T(sub.patch, sub.uv10, sub.uv11, depth, true)); } - /* clang-format on */ + if (sub.edge_u1.edge->T == 0 || + (sub.edge_u1.edge->T == DSPLIT_NON_UNIFORM && sub.edge_u0.edge->T == 1)) + { + assign_edge_factor(sub.edge_u1.edge, T(sub.patch, sub.uv11, sub.uv01, depth, true)); + } + if (sub.edge_v0.edge->T == 0 || + (sub.edge_v0.edge->T == DSPLIT_NON_UNIFORM && sub.edge_v1.edge->T == 1)) + { + assign_edge_factor(sub.edge_v0.edge, T(sub.patch, sub.uv01, sub.uv00, depth, true)); + } +} + +float2 DiagSplit::split_edge( + const Patch *patch, SubPatch::Edge *subedge, float2 Pstart, float2 Pend, const int depth) +{ + /* This splits following the direction of the edge itself, not subpatch edge direction. */ + if (subedge->reversed) { + swap(Pstart, Pend); + } + + SubEdge *edge = subedge->edge; + + if (edge->T == DSPLIT_NON_UNIFORM) { + /* Split down the middle. */ + const float2 P = 0.5f * (Pstart + Pend); + if (edge->mid_vert_index == -1) { + /* Allocate mid vertex and edges. */ + edge->mid_vert_index = alloc_verts(1); + + SubEdge *edge_a = alloc_edge(edge->start_vert_index, edge->mid_vert_index); + SubEdge *edge_b = alloc_edge(edge->mid_vert_index, edge->end_vert_index); + assign_edge_factor(edge_a, T(patch, Pstart, P, depth)); + assign_edge_factor(edge_b, T(patch, P, Pend, depth)); + } + assert(P.x >= 0 && P.x <= 1.0f && P.y >= 0.0f && P.y <= 1.0f); + return P; + } + + assert(edge->T >= 2); + const int mid = edge->T / 2; + + /* T is final and edge vertices are already allocated. An adjacent subpatch may not + * split this edge. So we ensure T and vertex indices match up with the non-split edge. */ + if (edge->mid_vert_index == -1) { + /* Allocate mid vertex and edges. */ + edge->mid_vert_index = edge->second_vert_index - 1 + mid; + + SubEdge *edge_a = alloc_edge(edge->start_vert_index, edge->mid_vert_index); + SubEdge *edge_b = alloc_edge(edge->mid_vert_index, edge->end_vert_index); + edge_a->T = mid; + edge_b->T = edge->T - mid; + edge_a->second_vert_index = edge->second_vert_index; + edge_b->second_vert_index = edge->second_vert_index + edge_a->T; + } + + const float2 P = interp(Pstart, Pend, mid / (float)edge->T); + assert(P.x >= 0 && P.x <= 1.0f && P.y >= 0.0f && P.y <= 1.0f); + + return P; +} + +void DiagSplit::split(SubPatch &&sub, const int depth) +{ + /* Edge factors are limited so that this should never happen. */ + assert(depth <= DSPLIT_MAX_DEPTH); + + /* Set edge factors if we haven't already. */ + resolve_edge_factors(sub, depth); + + /* Split subpatch if edges are marked as DSPLIT_NON_UNIFORM, + * or if the following conditions are met: + * - Both edges have at least 2 segments. + * - Either edge has more than DSPLIT_MAX_SEGMENTS segments. + * - The ratio of segments for opposite edges doesn't exceed 1.5. + * This reduces over tessellation for some patches. */ + const int min_T_u = min(sub.edge_u0.edge->T, sub.edge_u1.edge->T); + const int max_T_u = max(sub.edge_u0.edge->T, sub.edge_u1.edge->T); + const int min_T_v = min(sub.edge_v0.edge->T, sub.edge_v1.edge->T); + const int max_T_v = max(sub.edge_v0.edge->T, sub.edge_v1.edge->T); + + bool split_u = sub.edge_u0.edge->T == DSPLIT_NON_UNIFORM || + sub.edge_u1.edge->T == DSPLIT_NON_UNIFORM || + (min_T_u >= 2 && min_T_v > DSPLIT_MAX_SEGMENTS && max_T_v / min_T_v > 1.5f); + bool split_v = sub.edge_v0.edge->T == DSPLIT_NON_UNIFORM || + sub.edge_v1.edge->T == DSPLIT_NON_UNIFORM || + (min_T_v >= 2 && min_T_u > DSPLIT_MAX_SEGMENTS && max_T_u / min_T_u > 1.5f); /* Alternate axis. */ if (split_u && split_v) { @@ -198,558 +272,240 @@ void DiagSplit::split(SubPatch &sub, const int depth) if (!split_u && !split_v) { /* Add the unsplit subpatch. */ - subpatches.push_back(sub); - SubPatch &subpatch = subpatches[subpatches.size() - 1]; + alloc_subpatch(std::move(sub)); + return; + } - /* Update T values and offsets. */ - for (int i = 0; i < 4; i++) { - SubPatch::Edge &edge = subpatch.edges[i]; + /* Copy into new subpatches. */ + SubPatch sub_a = sub; + SubPatch sub_b = sub; - edge.offset = edge.edge->T; - edge.edge->T += edge.T; - } + /* Pointers to various subpatch elements. */ + SubPatch::Edge *sub_across_0; + SubPatch::Edge *sub_across_1; + SubPatch::Edge *sub_a_across_0; + SubPatch::Edge *sub_a_across_1; + SubPatch::Edge *sub_b_across_0; + SubPatch::Edge *sub_b_across_1; + + SubPatch::Edge *sub_a_split; + SubPatch::Edge *sub_b_split; + + float2 *Pa; + float2 *Pb; + float2 *Pc; + float2 *Pd; + + /* Set pointers based on split axis. */ + if (split_u) { + /* + * sub_across_1 + * -------Pa Pc------- + * | | | | + * | A | | B | + * | | | | + * -------Pb Pd------- + * sub_across_0 + */ + sub_across_0 = &sub.edge_u0; + sub_across_1 = &sub.edge_u1; + sub_a_across_0 = &sub_a.edge_u0; + sub_a_across_1 = &sub_a.edge_u1; + sub_b_across_0 = &sub_b.edge_u0; + sub_b_across_1 = &sub_b.edge_u1; + + sub_a_split = &sub_a.edge_v1; + sub_b_split = &sub_b.edge_v0; + + Pa = &sub_a.uv11; + Pb = &sub_a.uv10; + Pc = &sub_b.uv01; + Pd = &sub_b.uv00; } else { - /* Copy into new subpatches. */ - SubPatch sub_a = sub; - SubPatch sub_b = sub; + /* + * -------------------- + * | A | + * Pb----------------Pa + * sub_across_0 sub_across_1 + * Pd----------------Pc + * | B | + * -------------------- + */ + sub_across_0 = &sub.edge_v0; + sub_across_1 = &sub.edge_v1; + sub_a_across_0 = &sub_a.edge_v0; + sub_a_across_1 = &sub_a.edge_v1; + sub_b_across_0 = &sub_b.edge_v0; + sub_b_across_1 = &sub_b.edge_v1; - /* Pointers to various subpatch elements. */ - SubPatch::Edge *sub_across_0; - SubPatch::Edge *sub_across_1; - SubPatch::Edge *sub_a_across_0; - SubPatch::Edge *sub_a_across_1; - SubPatch::Edge *sub_b_across_0; - SubPatch::Edge *sub_b_across_1; + sub_a_split = &sub_a.edge_u0; + sub_b_split = &sub_b.edge_u1; - SubPatch::Edge *sub_a_split; - SubPatch::Edge *sub_b_split; - - float2 *Pa; - float2 *Pb; - float2 *Pc; - float2 *Pd; - - /* Set pointers based on split axis. */ - if (split_u) { - sub_across_0 = &sub.edge_u0; - sub_across_1 = &sub.edge_u1; - sub_a_across_0 = &sub_a.edge_u0; - sub_a_across_1 = &sub_a.edge_u1; - sub_b_across_0 = &sub_b.edge_u0; - sub_b_across_1 = &sub_b.edge_u1; - - sub_a_split = &sub_a.edge_v1; - sub_b_split = &sub_b.edge_v0; - - Pa = &sub_a.uv11; - Pb = &sub_a.uv10; - Pc = &sub_b.uv01; - Pd = &sub_b.uv00; - } - else { - sub_across_0 = &sub.edge_v0; - sub_across_1 = &sub.edge_v1; - sub_a_across_0 = &sub_a.edge_v0; - sub_a_across_1 = &sub_a.edge_v1; - sub_b_across_0 = &sub_b.edge_v0; - sub_b_across_1 = &sub_b.edge_v1; - - sub_a_split = &sub_a.edge_u0; - sub_b_split = &sub_b.edge_u1; - - Pa = &sub_a.uv10; - Pb = &sub_a.uv00; - Pc = &sub_b.uv11; - Pd = &sub_b.uv01; - } - - /* Partition edges */ - float2 P0; - float2 P1; - - partition_edge( - sub.patch, &P0, &sub_a_across_0->T, &sub_b_across_0->T, *Pd, *Pb, sub_across_0->T); - partition_edge( - sub.patch, &P1, &sub_a_across_1->T, &sub_b_across_1->T, *Pc, *Pa, sub_across_1->T); - - /* Split */ - *Pa = P1; - *Pb = P0; - - *Pc = P1; - *Pd = P0; - - int tsplit = T(sub.patch, P0, P1); - - if (depth == -2 && tsplit == 1) { - tsplit = 2; /* Ensure we can always split at depth -1. */ - } - - sub_a_split->T = tsplit; - sub_b_split->T = tsplit; - - resolve_edge_factors(sub_a); - resolve_edge_factors(sub_b); - - /* Create new edge */ - SubEdge &edge = *alloc_edge(); - - sub_a_split->edge = &edge; - sub_b_split->edge = &edge; - - sub_a_split->offset = 0; - sub_b_split->offset = 0; - - sub_a_split->indices_decrease_along_edge = false; - sub_b_split->indices_decrease_along_edge = true; - - sub_a_split->sub_edges_created_in_reverse_order = !split_u; - sub_b_split->sub_edges_created_in_reverse_order = !split_u; - - edge.top_indices_decrease = sub_across_1->sub_edges_created_in_reverse_order; - edge.bottom_indices_decrease = sub_across_0->sub_edges_created_in_reverse_order; - - /* Recurse */ - edge.T = 0; - split(sub_a, depth + 1); - - const int edge_t = edge.T; - (void)edge_t; - - edge.top_offset = sub_across_1->edge->T; - edge.bottom_offset = sub_across_0->edge->T; - - edge.T = 0; /* We calculate T twice along each edge. :/ */ - split(sub_b, depth + 1); - - assert(edge.T == edge_t); /* If this fails we will crash at some later point! */ - - edge.top = sub_across_1->edge; - edge.bottom = sub_across_0->edge; - } -} - -int DiagSplit::alloc_verts(const int n) -{ - const int a = num_alloced_verts; - num_alloced_verts += n; - return a; -} - -SubEdge *DiagSplit::alloc_edge() -{ - edges.emplace_back(); - return &edges.back(); -} - -void DiagSplit::split_patches(const Patch *patches, const size_t patches_byte_stride) -{ - int patch_index = 0; - - for (int f = 0; f < params.mesh->get_num_subd_faces(); f++) { - Mesh::SubdFace face = params.mesh->get_subd_face(f); - - const Patch *patch = (const Patch *)(((char *)patches) + patch_index * patches_byte_stride); - - if (face.is_quad()) { - patch_index++; - - split_quad(face, patch); - } - else { - patch_index += face.num_corners; - - split_ngon(face, patch, patches_byte_stride); - } + Pa = &sub_a.uv10; + Pb = &sub_a.uv00; + Pc = &sub_b.uv11; + Pd = &sub_b.uv01; } - params.mesh->vert_to_stitching_key_map.clear(); - params.mesh->vert_stitching_map.clear(); + /* Allocate new edges and vertices. */ + const float2 P0 = split_edge(sub.patch, sub_across_0, *Pd, *Pb, depth); + const float2 P1 = split_edge(sub.patch, sub_across_1, *Pa, *Pc, depth); - post_split(); -} + alloc_edge(sub_a_across_0, sub_across_0->start_vert_index(), sub_across_0->mid_vert_index()); + alloc_edge(sub_b_across_0, sub_across_0->mid_vert_index(), sub_across_0->end_vert_index()); + alloc_edge(sub_b_across_1, sub_across_1->start_vert_index(), sub_across_1->mid_vert_index()); + alloc_edge(sub_a_across_1, sub_across_1->mid_vert_index(), sub_across_1->end_vert_index()); -static SubEdge *create_edge_from_corner(DiagSplit *split, - const Mesh *mesh, - const Mesh::SubdFace &face, - const int corner, - bool &reversed, - int v0, - int v1) -{ - int a = mesh->get_subd_face_corners()[face.start_corner + mod(corner + 0, face.num_corners)]; - int b = mesh->get_subd_face_corners()[face.start_corner + mod(corner + 1, face.num_corners)]; + assert(sub_a_across_0->edge->T != 0); + assert(sub_b_across_0->edge->T != 0); + assert(sub_a_across_1->edge->T != 0); + assert(sub_b_across_1->edge->T != 0); - reversed = !(b < a); + /* Split */ + *Pa = P1; + *Pb = P0; - if (b < a) { - swap(a, b); - swap(v0, v1); + *Pc = P1; + *Pd = P0; + + /* Create new edge */ + alloc_edge(sub_a_split, sub_across_0->mid_vert_index(), sub_across_1->mid_vert_index()); + alloc_edge(sub_b_split, sub_across_1->mid_vert_index(), sub_across_0->mid_vert_index()); + + /* Set T for split edge. */ + int tsplit = T(sub.patch, P0, P1, depth); + if (depth == -2 && tsplit == 1) { + tsplit = 2; /* Ensure we can always split at depth -1. */ } + assign_edge_factor(sub_a_split->edge, tsplit); - SubEdge *edge = split->alloc_edge(); - - edge->is_stitch_edge = true; - edge->stitch_start_vert_index = a; - edge->stitch_end_vert_index = b; - - edge->start_vert_index = v0; - edge->end_vert_index = v1; - - edge->stitch_edge_key = {a, b}; - - return edge; + /* Recurse */ + split(std::move(sub_a), depth + 1); + split(std::move(sub_b), depth + 1); } void DiagSplit::split_quad(const Mesh::SubdFace &face, const Patch *patch) { + /* + * edge_u1 + * uv01 ←-------- uv11 + * | ↑ + * edge_v0 | | edge_v1 + * ↓ | + * uv00 --------→ uv10 + * edge_u0 + */ + + const int *subd_face_corners = params.mesh->get_subd_face_corners().data(); + const int v00 = subd_face_corners[face.start_corner + 0]; + const int v10 = subd_face_corners[face.start_corner + 1]; + const int v11 = subd_face_corners[face.start_corner + 2]; + const int v01 = subd_face_corners[face.start_corner + 3]; + SubPatch subpatch(patch); - - const int v = alloc_verts(4); - - bool v0_reversed; - bool u1_reversed; - bool v1_reversed; - bool u0_reversed; - subpatch.edge_v0.edge = create_edge_from_corner( - this, params.mesh, face, 3, v0_reversed, v + 3, v + 0); - subpatch.edge_u1.edge = create_edge_from_corner( - this, params.mesh, face, 2, u1_reversed, v + 2, v + 3); - subpatch.edge_v1.edge = create_edge_from_corner( - this, params.mesh, face, 1, v1_reversed, v + 1, v + 2); - subpatch.edge_u0.edge = create_edge_from_corner( - this, params.mesh, face, 0, u0_reversed, v + 0, v + 1); - - subpatch.edge_v0.sub_edges_created_in_reverse_order = !v0_reversed; - subpatch.edge_u1.sub_edges_created_in_reverse_order = u1_reversed; - subpatch.edge_v1.sub_edges_created_in_reverse_order = v1_reversed; - subpatch.edge_u0.sub_edges_created_in_reverse_order = !u0_reversed; - - subpatch.edge_v0.indices_decrease_along_edge = v0_reversed; - subpatch.edge_u1.indices_decrease_along_edge = u1_reversed; - subpatch.edge_v1.indices_decrease_along_edge = v1_reversed; - subpatch.edge_u0.indices_decrease_along_edge = u0_reversed; + alloc_edge(&subpatch.edge_u0, v00, v10); + alloc_edge(&subpatch.edge_v1, v10, v11); + alloc_edge(&subpatch.edge_u1, v11, v01); + alloc_edge(&subpatch.edge_v0, v01, v00); /* Forces a split in both axis for quads, needed to match split of ngons into quads. */ - subpatch.edge_u0.T = DSPLIT_NON_UNIFORM; - subpatch.edge_u1.T = DSPLIT_NON_UNIFORM; - subpatch.edge_v0.T = DSPLIT_NON_UNIFORM; - subpatch.edge_v1.T = DSPLIT_NON_UNIFORM; + subpatch.edge_u0.edge->T = DSPLIT_NON_UNIFORM; + subpatch.edge_v0.edge->T = DSPLIT_NON_UNIFORM; + subpatch.edge_u1.edge->T = DSPLIT_NON_UNIFORM; + subpatch.edge_v1.edge->T = DSPLIT_NON_UNIFORM; - split(subpatch, -2); -} - -static SubEdge *create_split_edge_from_corner(DiagSplit *split, - const Mesh *mesh, - const Mesh::SubdFace &face, - const int corner, - const int side, - bool &reversed, - int v0, - int v1, - const int vc) -{ - SubEdge *edge = split->alloc_edge(); - - int a = mesh->get_subd_face_corners()[face.start_corner + mod(corner + 0, face.num_corners)]; - int b = mesh->get_subd_face_corners()[face.start_corner + mod(corner + 1, face.num_corners)]; - - if (b < a) { - edge->stitch_edge_key = {b, a}; - } - else { - edge->stitch_edge_key = {a, b}; - } - - reversed = !(b < a); - - if (side == 0) { - a = vc; - } - else { - b = vc; - } - - if (!reversed) { - swap(a, b); - swap(v0, v1); - } - - edge->is_stitch_edge = true; - edge->stitch_start_vert_index = a; - edge->stitch_end_vert_index = b; - - edge->start_vert_index = v0; - edge->end_vert_index = v1; - - return edge; + split(std::move(subpatch), -2); } void DiagSplit::split_ngon(const Mesh::SubdFace &face, const Patch *patches, const size_t patches_byte_stride) { - SubEdge *prev_edge_u0 = nullptr; - SubEdge *first_edge_v0 = nullptr; + const int *subd_face_corners = params.mesh->get_subd_face_corners().data(); + const int v11 = alloc_verts(1); for (int corner = 0; corner < face.num_corners; corner++) { - const Patch *patch = (const Patch *)(((char *)patches) + corner * patches_byte_stride); + const Patch *patch = (const Patch *)(((char *)patches) + (corner * patches_byte_stride)); - SubPatch subpatch(patch); - - const int v = alloc_verts(4); + /* vprev . + * . . + * . edge_u1 . + * v01 ←------ v11 . . . + * | ↑ + * edge_v0 | | edge_v1 + * ↓ | + * v00 ------→ v10 . . vnext + * edge_u0 + */ /* Setup edges. */ - SubEdge *edge_u1 = alloc_edge(); - SubEdge *edge_v1 = alloc_edge(); + const int v00 = subd_face_corners[face.start_corner + corner]; + const int vprev = subd_face_corners[face.start_corner + + mod(corner + face.num_corners - 1, face.num_corners)]; + const int vnext = subd_face_corners[face.start_corner + mod(corner + 1, face.num_corners)]; - edge_v1->is_stitch_edge = true; - edge_u1->is_stitch_edge = true; + SubPatch::Edge edge_u0; + SubPatch::Edge edge_v0; - edge_u1->stitch_start_vert_index = -(face.start_corner + mod(corner + 0, face.num_corners)) - - 1; - edge_u1->stitch_end_vert_index = STITCH_NGON_CENTER_VERT_INDEX_OFFSET + face.ptex_offset; + alloc_edge(&edge_u0, v00, vnext); + alloc_edge(&edge_v0, vprev, v00); - edge_u1->start_vert_index = v + 3; - edge_u1->end_vert_index = v + 2; + if (edge_u0.edge->mid_vert_index == -1) { + edge_u0.edge->mid_vert_index = alloc_verts(1); + } + if (edge_v0.edge->mid_vert_index == -1) { + edge_v0.edge->mid_vert_index = alloc_verts(1); + } - edge_u1->stitch_edge_key = {edge_u1->stitch_start_vert_index, edge_u1->stitch_end_vert_index}; + const int v10 = edge_u0.edge->mid_vert_index; + const int v01 = edge_v0.edge->mid_vert_index; - edge_v1->stitch_start_vert_index = -(face.start_corner + mod(corner + 1, face.num_corners)) - - 1; - edge_v1->stitch_end_vert_index = STITCH_NGON_CENTER_VERT_INDEX_OFFSET + face.ptex_offset; - - edge_v1->start_vert_index = v + 1; - edge_v1->end_vert_index = v + 2; - - edge_v1->stitch_edge_key = {edge_v1->stitch_start_vert_index, edge_v1->stitch_end_vert_index}; - - bool v0_reversed; - bool u0_reversed; - - subpatch.edge_v0.edge = create_split_edge_from_corner(this, - params.mesh, - face, - corner - 1, - 0, - v0_reversed, - v + 3, - v + 0, - STITCH_NGON_SPLIT_EDGE_CENTER_VERT_TAG); - - subpatch.edge_u1.edge = edge_u1; - subpatch.edge_v1.edge = edge_v1; - - subpatch.edge_u0.edge = create_split_edge_from_corner(this, - params.mesh, - face, - corner + 0, - 1, - u0_reversed, - v + 0, - v + 1, - STITCH_NGON_SPLIT_EDGE_CENTER_VERT_TAG); - - subpatch.edge_v0.sub_edges_created_in_reverse_order = !v0_reversed; - subpatch.edge_u1.sub_edges_created_in_reverse_order = false; - subpatch.edge_v1.sub_edges_created_in_reverse_order = true; - subpatch.edge_u0.sub_edges_created_in_reverse_order = !u0_reversed; - - subpatch.edge_v0.indices_decrease_along_edge = v0_reversed; - subpatch.edge_u1.indices_decrease_along_edge = false; - subpatch.edge_v1.indices_decrease_along_edge = true; - subpatch.edge_u0.indices_decrease_along_edge = u0_reversed; + SubPatch subpatch(patch); + alloc_edge(&subpatch.edge_u0, v00, v10); + alloc_edge(&subpatch.edge_v1, v10, v11); + alloc_edge(&subpatch.edge_u1, v11, v01); + alloc_edge(&subpatch.edge_v0, v01, v00); /* Perform split. */ - { - subpatch.edge_u0.T = T(subpatch.patch, subpatch.uv00, subpatch.uv10); - subpatch.edge_u1.T = T(subpatch.patch, subpatch.uv01, subpatch.uv11); - subpatch.edge_v0.T = T(subpatch.patch, subpatch.uv00, subpatch.uv01); - subpatch.edge_v1.T = T(subpatch.patch, subpatch.uv10, subpatch.uv11); - - resolve_edge_factors(subpatch); - - split(subpatch, 0); - } - - /* Update offsets after T is known from split. */ - edge_u1->top = subpatch.edge_v0.edge; - edge_u1->stitch_top_offset = edge_u1->top->T * (v0_reversed ? -1 : 1); - edge_v1->top = subpatch.edge_u0.edge; - edge_v1->stitch_top_offset = edge_v1->top->T * (!u0_reversed ? -1 : 1); - - if (corner == 0) { - first_edge_v0 = subpatch.edge_v0.edge; - } - - if (prev_edge_u0) { - if (v0_reversed) { - subpatch.edge_v0.edge->stitch_offset = prev_edge_u0->T; - } - else { - prev_edge_u0->stitch_offset = subpatch.edge_v0.edge->T; - } - - const int T = subpatch.edge_v0.edge->T + prev_edge_u0->T; - subpatch.edge_v0.edge->stitch_edge_T = T; - prev_edge_u0->stitch_edge_T = T; - } - - if (corner == face.num_corners - 1) { - if (v0_reversed) { - subpatch.edge_u0.edge->stitch_offset = first_edge_v0->T; - } - else { - first_edge_v0->stitch_offset = subpatch.edge_u0.edge->T; - } - - const int T = first_edge_v0->T + subpatch.edge_u0.edge->T; - first_edge_v0->stitch_edge_T = T; - subpatch.edge_u0.edge->stitch_edge_T = T; - } - - prev_edge_u0 = subpatch.edge_u0.edge; + split(std::move(subpatch), 0); } } -void DiagSplit::post_split() +void DiagSplit::split_patches(const Patch *patches, const size_t patches_byte_stride) { - int num_stitch_verts = 0; + /* Keep base mesh vertices, create new triangels. */ + num_verts = params.mesh->get_num_subd_base_verts(); + num_triangles = 0; - /* All patches are now split, and all T values known. */ + /* Split all faces in the mesh. */ + int patch_index = 0; + for (int f = 0; f < params.mesh->get_num_subd_faces(); f++) { + Mesh::SubdFace face = params.mesh->get_subd_face(f); + const Patch *patch = (const Patch *)(((char *)patches) + (patch_index * patches_byte_stride)); - for (SubEdge &edge : edges) { - if (edge.second_vert_index < 0) { - edge.second_vert_index = alloc_verts(edge.T - 1); + if (face.is_quad()) { + patch_index++; + split_quad(face, patch); } - - if (edge.is_stitch_edge) { - num_stitch_verts = max(num_stitch_verts, - max(edge.stitch_start_vert_index, edge.stitch_end_vert_index)); + else { + patch_index += face.num_corners; + split_ngon(face, patch, patches_byte_stride); } } - num_stitch_verts += 1; + // TODO: avoid multiple write for shared vertices + // TODO: avoid multiple write for linear vert attributes + // TODO: avoid multiple write for smooth vert attributes + // TODO: lift restriction of max(Mu, 2) and max(Mv, 2) + // TODO: support not splitting n-gons if not needed + // TODO: multithreading - /* Map of edge key to edge stitching vert offset. */ - struct pair_hasher { - size_t operator()(const pair &k) const - { - return hash_uint2(k.first, k.second); - } - }; - using edge_stitch_verts_map_t = unordered_map, int, pair_hasher>; - edge_stitch_verts_map_t edge_stitch_verts_map; - - for (SubEdge &edge : edges) { - if (edge.is_stitch_edge) { - if (edge.stitch_edge_T == 0) { - edge.stitch_edge_T = edge.T; - } - - if (edge_stitch_verts_map.find(edge.stitch_edge_key) == edge_stitch_verts_map.end()) { - edge_stitch_verts_map[edge.stitch_edge_key] = num_stitch_verts; - num_stitch_verts += edge.stitch_edge_T - 1; - } - } - } - - /* Set start and end indices for edges generated from a split. */ - for (SubEdge &edge : edges) { - if (edge.start_vert_index < 0) { - /* Fix up offsets. */ - if (edge.top_indices_decrease) { - edge.top_offset = edge.top->T - edge.top_offset; - } - - edge.start_vert_index = edge.top->get_vert_along_edge(edge.top_offset); - } - - if (edge.end_vert_index < 0) { - if (edge.bottom_indices_decrease) { - edge.bottom_offset = edge.bottom->T - edge.bottom_offset; - } - - edge.end_vert_index = edge.bottom->get_vert_along_edge(edge.bottom_offset); - } - } - - const int vert_offset = params.mesh->verts.size(); - - /* Add verts to stitching map. */ - for (const SubEdge &edge : edges) { - if (edge.is_stitch_edge) { - const int second_stitch_vert_index = edge_stitch_verts_map[edge.stitch_edge_key]; - - for (int i = 0; i <= edge.T; i++) { - /* Get proper stitching key. */ - int key; - - if (i == 0) { - key = edge.stitch_start_vert_index; - } - else if (i == edge.T) { - key = edge.stitch_end_vert_index; - } - else { - key = second_stitch_vert_index + i - 1 + edge.stitch_offset; - } - - if (key == STITCH_NGON_SPLIT_EDGE_CENTER_VERT_TAG) { - if (i == 0) { - key = second_stitch_vert_index - 1 + edge.stitch_offset; - } - else if (i == edge.T) { - key = second_stitch_vert_index - 1 + edge.T; - } - } - else if (key < 0 && edge.top) { /* ngon spoke edge */ - const int s = edge_stitch_verts_map[edge.top->stitch_edge_key]; - if (edge.stitch_top_offset >= 0) { - key = s - 1 + edge.stitch_top_offset; - } - else { - key = s - 1 + edge.top->stitch_edge_T + edge.stitch_top_offset; - } - } - - /* Get real vert index. */ - const int vert = edge.get_vert_along_edge(i) + vert_offset; - - /* Add to map */ - if (params.mesh->vert_to_stitching_key_map.find(vert) == - params.mesh->vert_to_stitching_key_map.end()) - { - params.mesh->vert_to_stitching_key_map[vert] = key; - params.mesh->vert_stitching_map.insert({key, vert}); - } - } - } - } - - /* Dice; TODO(mai): Move this out of split. */ + /* Dice all patches. */ QuadDice dice(params); - - int num_verts = num_alloced_verts; - int num_triangles = 0; - - for (size_t i = 0; i < subpatches.size(); i++) { - subpatches[i].inner_grid_vert_offset = num_verts; - num_verts += subpatches[i].calc_num_inner_verts(); - num_triangles += subpatches[i].calc_num_triangles(); - } - dice.reserve(num_verts, num_triangles); - for (size_t i = 0; i < subpatches.size(); i++) { - SubPatch &sub = subpatches[i]; - - sub.edge_u0.T = max(sub.edge_u0.T, 1); - sub.edge_u1.T = max(sub.edge_u1.T, 1); - sub.edge_v0.T = max(sub.edge_v0.T, 1); - sub.edge_v1.T = max(sub.edge_v1.T, 1); - + for (SubPatch &sub : subpatches) { dice.dice(sub); } diff --git a/intern/cycles/subd/split.h b/intern/cycles/subd/split.h index 61e987c76ff..46b9fc63c90 100644 --- a/intern/cycles/subd/split.h +++ b/intern/cycles/subd/split.h @@ -14,7 +14,7 @@ #include "subd/dice.h" #include "subd/subpatch.h" -#include "util/deque.h" +#include "util/set.h" #include "util/types.h" #include "util/vector.h" @@ -24,47 +24,43 @@ class Mesh; class Patch; class DiagSplit { + private: SubdParams params; - vector subpatches; - /* `deque` is used so that element pointers remain valid when size is changed. */ - deque edges; + unordered_set edges; + int num_verts = 0; + int num_triangles = 0; + /* Allocate vertices, edges and subpatches. */ + int alloc_verts(const int num); + SubEdge *alloc_edge(const int v0, const int v1); + void alloc_edge(SubPatch::Edge *sub_edge, const int v0, const int v1); + void alloc_subpatch(SubPatch &&sub); + + /* Compute edge factors. */ float3 to_world(const Patch *patch, const float2 uv); int T(const Patch *patch, const float2 Pstart, const float2 Pend, - bool recursive_resolve = false); - - void limit_edge_factor(int &T, const Patch *patch, const float2 Pstart, const float2 Pend); - void resolve_edge_factors(SubPatch &sub); - - void partition_edge(const Patch *patch, - float2 *P, - int *t0, - int *t1, - const float2 Pstart, - const float2 Pend, - const int t); - - void split(SubPatch &sub, const int depth = 0); - - int num_alloced_verts = 0; - int alloc_verts(const int n); /* Returns start index of new verts. */ - - public: - SubEdge *alloc_edge(); - - explicit DiagSplit(const SubdParams ¶ms); - - void split_patches(const Patch *patches, const size_t patches_byte_stride); + const int depth, + const bool recursive_resolve = false); + int limit_edge_factor(const Patch *patch, const float2 Pstart, const float2 Pend, const int T); + void assign_edge_factor(SubEdge *edge, const int T); + void resolve_edge_factors(const SubPatch &sub, const int depth); + /* Split edge, subpatch, quad and n-gon. */ + float2 split_edge( + const Patch *patch, SubPatch::Edge *edge, float2 Pstart, float2 Pend, const int depth); + void split(SubPatch &&sub, const int depth = 0); void split_quad(const Mesh::SubdFace &face, const Patch *patch); void split_ngon(const Mesh::SubdFace &face, const Patch *patches, const size_t patches_byte_stride); - void post_split(); + public: + explicit DiagSplit(const SubdParams ¶ms); + + void split_patches(const Patch *patches, const size_t patches_byte_stride); }; CCL_NAMESPACE_END diff --git a/intern/cycles/subd/subpatch.h b/intern/cycles/subd/subpatch.h index 3a5627094b1..2cf0a035ecf 100644 --- a/intern/cycles/subd/subpatch.h +++ b/intern/cycles/subd/subpatch.h @@ -4,7 +4,7 @@ #pragma once -#include "util/map.h" +#include "util/hash.h" #include "util/math_float2.h" CCL_NAMESPACE_BEGIN @@ -14,35 +14,24 @@ class Patch; /* SubEdge */ struct SubEdge { + SubEdge(const int start_vert_index, const int end_vert_index) + : start_vert_index(start_vert_index), end_vert_index(end_vert_index) + { + } + + /* Vertex indices. */ + int start_vert_index; + int end_vert_index; + + /* If edge was split, vertex index in the middle. */ + int mid_vert_index = -1; + /* Number of segments the edge will be diced into, see DiagSplit paper. */ int T = 0; - /* top is edge adjacent to start, bottom is adjacent to end. */ - SubEdge *top = nullptr, *bottom = nullptr; - - int top_offset = -1, bottom_offset = -1; - bool top_indices_decrease = false, bottom_indices_decrease = false; - - int start_vert_index = -1; - int end_vert_index = -1; - /* Index of the second vert from this edges corner along the edge towards the next corner. */ int second_vert_index = -1; - /* Vertices on edge are to be stitched. */ - bool is_stitch_edge = false; - - /* Key to match this edge with others to be stitched with. - * The ints in the pair are ordered stitching indices */ - pair stitch_edge_key; - - /* Full T along edge (may be larger than T for edges split from ngon edges) */ - int stitch_edge_T = 0; - int stitch_offset = 0; - int stitch_top_offset; - int stitch_start_vert_index; - int stitch_end_vert_index; - SubEdge() = default; int get_vert_along_edge(const int n) const @@ -58,55 +47,77 @@ struct SubEdge { return second_vert_index + n - 1; } + + struct Hash { + size_t operator()(const SubEdge &edge) const + { + int a = edge.start_vert_index; + int b = edge.end_vert_index; + if (b > a) { + std::swap(a, b); + } + return hash_uint2(a, b); + } + }; + + struct Equal { + size_t operator()(const SubEdge &a, const SubEdge &b) const + { + return (a.start_vert_index == b.start_vert_index && a.end_vert_index == b.end_vert_index) || + (a.start_vert_index == b.end_vert_index && a.end_vert_index == b.start_vert_index); + } + }; }; /* SubPatch */ class SubPatch { public: - const Patch *patch; /* Patch this is a subpatch of. */ - int inner_grid_vert_offset; + /* Patch this is a subpatch of. */ + const Patch *patch = nullptr; + /* Vertex indices for inner grid start at this index. */ + int inner_grid_vert_offset = 0; + /* Edge of patch. */ struct Edge { - int T; - int offset; /* Offset along main edge, interpretation depends on the two flags below. */ + SubEdge *edge; - bool indices_decrease_along_edge; - bool sub_edges_created_in_reverse_order; + /* Is the direction of this edge reverse compared to SubEdge? */ + bool reversed; - struct SubEdge *edge; + /* Get vertex indices in the direction of this patch edge, will take into + * account the reversed flag to flip the indices. */ + int start_vert_index() const + { + return (reversed) ? edge->end_vert_index : edge->start_vert_index; + } + int mid_vert_index() const + { + return edge->mid_vert_index; + } + int end_vert_index() const + { + return (reversed) ? edge->start_vert_index : edge->end_vert_index; + } int get_vert_along_edge(const int n_relative) const { - assert(n_relative >= 0 && n_relative <= T); + assert(n_relative >= 0 && n_relative <= edge->T); - int n = n_relative; - - if (!indices_decrease_along_edge && !sub_edges_created_in_reverse_order) { - n = offset + n; - } - else if (!indices_decrease_along_edge && sub_edges_created_in_reverse_order) { - n = edge->T - offset - T + n; - } - else if (indices_decrease_along_edge && !sub_edges_created_in_reverse_order) { - n = offset + T - n; - } - else if (indices_decrease_along_edge && sub_edges_created_in_reverse_order) { - n = edge->T - offset - n; - } + const int n = (reversed) ? edge->T - n_relative : n_relative; return edge->get_vert_along_edge(n); } }; /* - * eu1 - * uv01 --------- uv11 - * | | - * ev0 | | ev1 - * | | - * uv00 --------- uv10 - * eu0 + * edge_u1 + * uv01 ←-------- uv11 + * | ↑ + * edge_v0 | | edge_v1 + * ↓ | + * uv00 --------→ uv10 + * edge_u0 */ /* UV within patch, counter-clockwise starting from uv (0, 0) towards (1, 0) etc. */ @@ -115,10 +126,11 @@ class SubPatch { float2 uv11 = one_float2(); float2 uv01 = make_float2(0.0f, 1.0f); + /* Edges of this subpatch. */ union { - Edge edges[4]; /* Edges of this subpatch, each edge starts at the corner of the same index. */ + Edge edges[4] = {}; struct { - Edge edge_v0, edge_u1, edge_v1, edge_u0; + Edge edge_u0, edge_v1, edge_u1, edge_v0; }; }; @@ -126,43 +138,48 @@ class SubPatch { int calc_num_inner_verts() const { - int Mu = fmax(edge_u0.T, edge_u1.T); - int Mv = fmax(edge_v0.T, edge_v1.T); - Mu = fmax(Mu, 2); - Mv = fmax(Mv, 2); + const int Mu = max(max(edge_u0.edge->T, edge_u1.edge->T), 2); + const int Mv = max(max(edge_v0.edge->T, edge_v1.edge->T), 2); return (Mu - 1) * (Mv - 1); } int calc_num_triangles() const { - int Mu = fmax(edge_u0.T, edge_u1.T); - int Mv = fmax(edge_v0.T, edge_v1.T); - Mu = fmax(Mu, 2); - Mv = fmax(Mv, 2); + const int Mu = max(max(edge_u0.edge->T, edge_u1.edge->T), 2); + const int Mv = max(max(edge_v0.edge->T, edge_v1.edge->T), 2); const int inner_triangles = (Mu - 2) * (Mv - 2) * 2; - const int edge_triangles = edge_u0.T + edge_u1.T + edge_v0.T + edge_v1.T + (Mu - 2) * 2 + - (Mv - 2) * 2; + const int edge_triangles = edge_u0.edge->T + edge_u1.edge->T + edge_v0.edge->T + + edge_v1.edge->T + ((Mu - 2) * 2) + ((Mv - 2) * 2); return inner_triangles + edge_triangles; } + int get_vert_along_edge(const int edge, const int n) const + { + return edges[edge].get_vert_along_edge(n); + } + int get_vert_along_grid_edge(const int edge, const int n) const { - int Mu = fmax(edge_u0.T, edge_u1.T); - int Mv = fmax(edge_v0.T, edge_v1.T); - Mu = fmax(Mu, 2); - Mv = fmax(Mv, 2); + const int Mu = max(max(edge_u0.edge->T, edge_u1.edge->T), 2); + const int Mv = max(max(edge_v0.edge->T, edge_v1.edge->T), 2); switch (edge) { - case 0: - return inner_grid_vert_offset + n * (Mu - 1); - case 1: - return inner_grid_vert_offset + (Mu - 1) * (Mv - 2) + n; - case 2: - return inner_grid_vert_offset + ((Mu - 1) * (Mv - 1) - 1) - n * (Mu - 1); - case 3: - return inner_grid_vert_offset + (Mu - 2) - n; + case 0: { + return inner_grid_vert_offset + n; + } + case 1: { + return inner_grid_vert_offset + (Mu - 2) + n * (Mu - 1); + } + case 2: { + const int reverse_n = (Mu - 2) - n; + return inner_grid_vert_offset + (Mu - 1) * (Mv - 2) + reverse_n; + } + case 3: { + const int reverse_n = (Mv - 2) - n; + return inner_grid_vert_offset + reverse_n * (Mu - 1); + } default: assert(0); break; @@ -171,11 +188,6 @@ class SubPatch { return -1; } - int get_vert_along_edge(const int edge, const int n) const - { - return edges[edge].get_vert_along_edge(n); - } - float2 map_uv(float2 uv) { /* Map UV from subpatch to patch parametric coordinates. */ diff --git a/tests/data b/tests/data index 2de4677cbd2..363b794cf8a 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 2de4677cbd20fe011de33ae76c3e97d119b9748c +Subproject commit 363b794cf8ac9e0cb45fc0a9123c98e551959c3e diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index 49a13513582..7cd69eec86e 100644 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -254,9 +254,12 @@ def main(): # OSL tests: # Blackbody is slightly different between SVM and OSL. # Microfacet hair renders slightly differently, and fails on Windows and Linux with OSL + # + # both_displacement.blend has slight differences between Linux and other platforms. test_dir_name = Path(args.testdir).name - if (test_dir_name in {'motion_blur', 'integrator'}) or ((args.osl) and (test_dir_name in {'shader', 'hair'})): + if (test_dir_name in {'motion_blur', 'integrator', "displacement"}) or \ + ((args.osl) and (test_dir_name in {'shader', 'hair'})): report.set_fail_threshold(0.032) # Layer mixing is different between SVM and OSL, so a few tests have