Refactor: Cycles: Subdivision avoids writing vertices multiple times

For performance and determinism with upcoming multithreading.

Pull Request: https://projects.blender.org/blender/blender/pulls/136411
This commit is contained in:
Brecht Van Lommel
2025-03-09 23:01:28 +01:00
parent e054dc3eeb
commit 17fbfbc2d5
4 changed files with 141 additions and 55 deletions

View File

@@ -264,9 +264,11 @@ 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].edge->T;
const int i_start = (sub.edges[edge].own_vertex) ? 0 : 1;
const int i_end = (sub.edges[edge].own_edge) ? t : 1;
/* set verts on the edge of the patch */
for (int i = 0; i < t; i++) {
for (int i = i_start; i < i_end; i++) {
const float f = i / (float)t;
float2 uv;

View File

@@ -34,17 +34,39 @@ int DiagSplit::alloc_verts(int num)
return index;
}
SubEdge *DiagSplit::alloc_edge(const int v0, const int v1)
SubEdge *DiagSplit::alloc_edge(const int v0, const int v1, bool &was_missing)
{
const SubEdge edge(v0, v1);
const auto it = edges.find(edge);
return const_cast<SubEdge *>((it == edges.end()) ? &*(edges.emplace(edge).first) : &*it);
was_missing = (it == edges.end());
return const_cast<SubEdge *>(was_missing ? &*(edges.emplace(edge).first) : &*it);
}
void DiagSplit::alloc_edge(SubPatch::Edge *sub_edge, int v0, int v1)
void DiagSplit::alloc_edge(SubPatch::Edge *sub_edge,
int v0,
int v1,
const bool want_to_own_edge,
const bool want_to_own_vertex)
{
sub_edge->edge = (v0 < v1) ? alloc_edge(v0, v1) : alloc_edge(v1, v0);
bool was_missing;
sub_edge->edge = (v0 < v1) ? alloc_edge(v0, v1, was_missing) : alloc_edge(v1, v0, was_missing);
sub_edge->own_vertex = false;
sub_edge->own_edge = was_missing && want_to_own_edge;
sub_edge->reversed = sub_edge->edge->start_vert_index != v0;
if (want_to_own_vertex) {
if (v0 < owned_verts.size()) {
/* Vertex in original mesh. */
if (!owned_verts[v0]) {
owned_verts[v0] = true;
sub_edge->own_vertex = true;
}
}
else {
/* Mid edge vertex. */
sub_edge->own_vertex = true;
}
}
}
void DiagSplit::alloc_subpatch(SubPatch &&sub)
@@ -190,8 +212,13 @@ void DiagSplit::resolve_edge_factors(const SubPatch &sub, const int depth)
}
}
float2 DiagSplit::split_edge(
const Patch *patch, SubPatch::Edge *subedge, float2 Pstart, float2 Pend, const int depth)
float2 DiagSplit::split_edge(const Patch *patch,
SubPatch::Edge *subedge,
SubPatch::Edge *subedge_a,
SubPatch::Edge *subedge_b,
float2 Pstart,
float2 Pend,
const int depth)
{
/* This splits following the direction of the edge itself, not subpatch edge direction. */
if (subedge->reversed) {
@@ -207,11 +234,22 @@ float2 DiagSplit::split_edge(
/* 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);
bool unused;
SubEdge *edge_a = alloc_edge(edge->start_vert_index, edge->mid_vert_index, unused);
SubEdge *edge_b = alloc_edge(edge->mid_vert_index, edge->end_vert_index, unused);
assign_edge_factor(edge_a, T(patch, Pstart, P, depth));
assign_edge_factor(edge_b, T(patch, P, Pend, depth));
}
/* Allocate sub edges and set ownership. */
alloc_edge(subedge_a, subedge->start_vert_index(), subedge->mid_vert_index(), false, false);
alloc_edge(subedge_b, subedge->mid_vert_index(), subedge->end_vert_index(), false, false);
subedge_a->own_edge = subedge->own_edge;
subedge_b->own_edge = subedge->own_edge;
subedge_a->own_vertex = subedge->own_vertex;
subedge_b->own_vertex = subedge->own_edge;
assert(P.x >= 0 && P.x <= 1.0f && P.y >= 0.0f && P.y <= 1.0f);
return P;
}
@@ -225,14 +263,24 @@ float2 DiagSplit::split_edge(
/* 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);
bool unused;
SubEdge *edge_a = alloc_edge(edge->start_vert_index, edge->mid_vert_index, unused);
SubEdge *edge_b = alloc_edge(edge->mid_vert_index, edge->end_vert_index, unused);
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;
}
/* Allocate sub edges and set ownership. */
alloc_edge(subedge_a, subedge->start_vert_index(), subedge->mid_vert_index(), false, false);
alloc_edge(subedge_b, subedge->mid_vert_index(), subedge->end_vert_index(), false, false);
subedge_a->own_edge = subedge->own_edge;
subedge_b->own_edge = subedge->own_edge;
subedge_a->own_vertex = subedge->own_vertex;
subedge_b->own_vertex = subedge->own_edge;
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);
@@ -277,8 +325,15 @@ void DiagSplit::split(SubPatch &&sub, const int depth)
}
/* Copy into new subpatches. */
SubPatch sub_a = sub;
SubPatch sub_b = sub;
SubPatch sub_a(sub);
SubPatch sub_b(sub);
for (int i = 0; i < 4; i++) {
sub_a.edges[i].own_edge = false;
sub_a.edges[i].own_vertex = false;
sub_b.edges[i].own_edge = false;
sub_b.edges[i].own_vertex = false;
}
/* Pointers to various subpatch elements. */
SubPatch::Edge *sub_across_0;
@@ -314,6 +369,11 @@ void DiagSplit::split(SubPatch &&sub, const int depth)
sub_b_across_0 = &sub_b.edge_u0;
sub_b_across_1 = &sub_b.edge_u1;
sub_a.edge_v0.own_edge = sub.edge_v0.own_edge;
sub_a.edge_v0.own_vertex = sub.edge_v0.own_vertex;
sub_b.edge_v1.own_edge = sub.edge_v1.own_edge;
sub_b.edge_v1.own_vertex = sub.edge_v1.own_vertex;
sub_a_split = &sub_a.edge_v1;
sub_b_split = &sub_b.edge_v0;
@@ -339,6 +399,11 @@ void DiagSplit::split(SubPatch &&sub, const int depth)
sub_b_across_0 = &sub_b.edge_v0;
sub_b_across_1 = &sub_b.edge_v1;
sub_a.edge_u1.own_edge = sub.edge_u1.own_edge;
sub_a.edge_u1.own_vertex = sub.edge_u1.own_vertex;
sub_b.edge_u0.own_edge = sub.edge_u0.own_edge;
sub_b.edge_u0.own_vertex = sub.edge_u0.own_vertex;
sub_a_split = &sub_a.edge_u0;
sub_b_split = &sub_b.edge_u1;
@@ -349,13 +414,10 @@ void DiagSplit::split(SubPatch &&sub, const int depth)
}
/* 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);
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());
const float2 P0 = split_edge(
sub.patch, sub_across_0, sub_a_across_0, sub_b_across_0, *Pd, *Pb, depth);
const float2 P1 = split_edge(
sub.patch, sub_across_1, sub_b_across_1, sub_a_across_1, *Pa, *Pc, depth);
assert(sub_a_across_0->edge->T != 0);
assert(sub_b_across_0->edge->T != 0);
@@ -370,8 +432,10 @@ void DiagSplit::split(SubPatch &&sub, const int depth)
*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());
alloc_edge(
sub_a_split, sub_across_0->mid_vert_index(), sub_across_1->mid_vert_index(), true, false);
alloc_edge(
sub_b_split, sub_across_1->mid_vert_index(), sub_across_0->mid_vert_index(), true, false);
/* Set T for split edge. */
int tsplit = T(sub.patch, P0, P1, depth);
@@ -404,10 +468,10 @@ void DiagSplit::split_quad(const Mesh::SubdFace &face, const Patch *patch)
const int v01 = subd_face_corners[face.start_corner + 3];
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);
alloc_edge(&subpatch.edge_u0, v00, v10, true, true);
alloc_edge(&subpatch.edge_v1, v10, v11, true, true);
alloc_edge(&subpatch.edge_u1, v11, v01, true, true);
alloc_edge(&subpatch.edge_v0, v01, v00, true, true);
/* Forces a split in both axis for quads, needed to match split of ngons into quads. */
subpatch.edge_u0.edge->T = DSPLIT_NON_UNIFORM;
@@ -425,6 +489,19 @@ void DiagSplit::split_ngon(const Mesh::SubdFace &face,
const int *subd_face_corners = params.mesh->get_subd_face_corners().data();
const int v11 = alloc_verts(1);
/* Allocate edges of n-gon. */
array<SubPatch::Edge> edges(face.num_corners);
for (int corner = 0; corner < face.num_corners; corner++) {
const int v = subd_face_corners[face.start_corner + corner];
const int vnext = subd_face_corners[face.start_corner + mod(corner + 1, face.num_corners)];
alloc_edge(&edges[corner], v, vnext, true, true);
if (edges[corner].edge->mid_vert_index == -1) {
edges[corner].edge->mid_vert_index = alloc_verts(1);
}
}
/* Allocate patches. */
for (int corner = 0; corner < face.num_corners; corner++) {
const Patch *patch = (const Patch *)(((char *)patches) + (corner * patches_byte_stride));
@@ -438,34 +515,24 @@ void DiagSplit::split_ngon(const Mesh::SubdFace &face,
* v00 ------→ v10 . . vnext
* edge_u0
*/
SubPatch::Edge &edge_v0 = edges[mod(corner + face.num_corners - 1, face.num_corners)];
SubPatch::Edge &edge_u0 = edges[corner];
/* Setup edges. */
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)];
SubPatch::Edge edge_u0;
SubPatch::Edge edge_v0;
alloc_edge(&edge_u0, v00, vnext);
alloc_edge(&edge_v0, vprev, v00);
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);
}
const int v10 = edge_u0.edge->mid_vert_index;
const int v01 = edge_v0.edge->mid_vert_index;
const int v00 = edge_u0.start_vert_index();
const int v10 = edge_u0.mid_vert_index();
const int v01 = edge_v0.mid_vert_index();
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);
alloc_edge(&subpatch.edge_u0, v00, v10, false, false);
alloc_edge(&subpatch.edge_v1, v10, v11, true, false);
alloc_edge(&subpatch.edge_u1, v11, v01, true, corner == 0);
alloc_edge(&subpatch.edge_v0, v01, v00, false, false);
subpatch.edge_u0.own_edge = edge_u0.own_edge;
subpatch.edge_u0.own_vertex = edge_u0.own_vertex;
subpatch.edge_v0.own_edge = edge_v0.own_edge;
subpatch.edge_v0.own_vertex = edge_v0.own_edge;
/* Perform split. */
split(std::move(subpatch), 0);
@@ -478,6 +545,8 @@ void DiagSplit::split_patches(const Patch *patches, const size_t patches_byte_st
num_verts = params.mesh->get_num_subd_base_verts();
num_triangles = 0;
owned_verts.resize(num_verts, false);
/* Split all faces in the mesh. */
int patch_index = 0;
for (int f = 0; f < params.mesh->get_num_subd_faces(); f++) {
@@ -494,7 +563,7 @@ void DiagSplit::split_patches(const Patch *patches, const size_t patches_byte_st
}
}
// TODO: avoid multiple write for shared vertices
// TODO: reuse edge factor vertex position computations
// TODO: avoid multiple write for linear vert attributes
// TODO: avoid multiple write for smooth vert attributes
// TODO: support not splitting n-gons if not needed

View File

@@ -27,14 +27,19 @@ class DiagSplit {
private:
SubdParams params;
vector<SubPatch> subpatches;
vector<bool> owned_verts;
unordered_set<SubEdge, SubEdge::Hash, SubEdge::Equal> 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);
SubEdge *alloc_edge(const int v0, const int v1, bool &was_missing);
void alloc_edge(SubPatch::Edge *sub_edge,
const int v0,
const int v1,
const bool want_to_own_edge,
const bool want_to_own_vertex);
void alloc_subpatch(SubPatch &&sub);
/* Compute edge factors. */
@@ -49,8 +54,13 @@ class DiagSplit {
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);
float2 split_edge(const Patch *patch,
SubPatch::Edge *subedge,
SubPatch::Edge *subedge_a,
SubPatch::Edge *subedge_b,
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,

View File

@@ -85,6 +85,11 @@ class SubPatch {
/* Is the direction of this edge reverse compared to SubEdge? */
bool reversed;
/* Is this subpatch responsible for owning attributes for the start vertex? */
bool own_vertex;
/* Is this subpatch responsible for owning attributes for edge vertices? */
bool own_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