From 4a328b5a635eb82ad4fc52ecc8fc01fbdea82e24 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Thu, 29 May 2025 15:40:31 +0200 Subject: [PATCH] Nodes: Voronoi node CPU optimizations Several small speedups for Voronoi node (no behavior change). This affects Cycles and CPU execution of Voronoi node e.g. in Compositor. - F1 mode: when evaluating distance for Voronoi cells, use a faster distance estimation, and only do final distance calculation on the resulting closest cell. This is only really relevant for the default Euclidian distance, where this saves a square root per evaluated cell (in 3D Voronoi case saves 26 square roots; in 4D case saves 80 square roots). - N-Sphere Radius mode: speedup by doing squared distance calculations. We only need to find the closest one, so again doing the square root per cell is not needed here. Something like 5%-10% speedup for F1 3D Voronoi; more performance details in the PR. Pull Request: https://projects.blender.org/blender/blender/pulls/139490 --- intern/cycles/kernel/svm/voronoi.h | 83 ++++++++++++++++---------- source/blender/blenlib/intern/noise.cc | 71 ++++++++++++---------- 2 files changed, 94 insertions(+), 60 deletions(-) diff --git a/intern/cycles/kernel/svm/voronoi.h b/intern/cycles/kernel/svm/voronoi.h index 861a82f27b8..370baf57424 100644 --- a/intern/cycles/kernel/svm/voronoi.h +++ b/intern/cycles/kernel/svm/voronoi.h @@ -71,6 +71,28 @@ ccl_device float voronoi_distance(const T a, const T b, const ccl_private Vorono return 0.0f; } +/* Possibly cheaper/faster version of Voronoi distance, in a way that does not change + * logic of "which distance is the closest?". */ +template +ccl_device float voronoi_distance_bound(const T a, + const T b, + const ccl_private VoronoiParams ¶ms) +{ + if (params.metric == NODE_VORONOI_EUCLIDEAN) { + return len_squared(a - b); + } + if (params.metric == NODE_VORONOI_MANHATTAN) { + return reduce_add(fabs(a - b)); + } + if (params.metric == NODE_VORONOI_CHEBYCHEV) { + return reduce_max(fabs(a - b)); + } + if (params.metric == NODE_VORONOI_MINKOWSKI) { + return reduce_add(power(fabs(a - b), params.exponent)); + } + return 0.0f; +} + /* **** 1D Voronoi **** */ ccl_device float4 voronoi_position(const float coord) @@ -256,7 +278,7 @@ ccl_device VoronoiOutput voronoi_f1(const ccl_private VoronoiParams ¶ms, con const float2 cellOffset = make_float2(i, j); const float2 pointPosition = cellOffset + hash_float2_to_float2(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + const float distanceToPoint = voronoi_distance_bound(pointPosition, localPosition, params); if (distanceToPoint < minDistance) { targetOffset = cellOffset; minDistance = distanceToPoint; @@ -266,7 +288,7 @@ ccl_device VoronoiOutput voronoi_f1(const ccl_private VoronoiParams ¶ms, con } VoronoiOutput octave; - octave.distance = minDistance; + octave.distance = voronoi_distance(targetPosition, localPosition, params); octave.color = hash_float2_to_float3(cellPosition + targetOffset); octave.position = voronoi_position(targetPosition + cellPosition); return octave; @@ -400,22 +422,22 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms float2 closestPoint = make_float2(0.0f, 0.0f); float2 closestPointOffset = make_float2(0.0f, 0.0f); - float minDistance = FLT_MAX; + float minDistanceSq = FLT_MAX; for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { const float2 cellOffset = make_float2(i, j); const float2 pointPosition = cellOffset + hash_float2_to_float2(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = distance(pointPosition, localPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + const float distanceToPointSq = len_squared(pointPosition - localPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPoint = pointPosition; closestPointOffset = cellOffset; } } } - minDistance = FLT_MAX; + minDistanceSq = FLT_MAX; float2 closestPointToClosestPoint = make_float2(0.0f, 0.0f); for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { @@ -425,9 +447,9 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms const float2 cellOffset = make_float2(i, j) + closestPointOffset; const float2 pointPosition = cellOffset + hash_float2_to_float2(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = distance(closestPoint, pointPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + const float distanceToPointSq = len_squared(closestPoint - pointPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPointToClosestPoint = pointPosition; } } @@ -458,7 +480,7 @@ ccl_device VoronoiOutput voronoi_f1(const ccl_private VoronoiParams ¶ms, con const float3 pointPosition = cellOffset + hash_float3_to_float3(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + const float distanceToPoint = voronoi_distance_bound(pointPosition, localPosition, params); if (distanceToPoint < minDistance) { targetOffset = cellOffset; minDistance = distanceToPoint; @@ -469,7 +491,7 @@ ccl_device VoronoiOutput voronoi_f1(const ccl_private VoronoiParams ¶ms, con } VoronoiOutput octave; - octave.distance = minDistance; + octave.distance = voronoi_distance(targetPosition, localPosition, params); octave.color = hash_float3_to_float3(cellPosition + targetOffset); octave.position = voronoi_position(targetPosition + cellPosition); return octave; @@ -613,7 +635,7 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms float3 closestPoint = make_float3(0.0f, 0.0f, 0.0f); float3 closestPointOffset = make_float3(0.0f, 0.0f, 0.0f); - float minDistance = FLT_MAX; + float minDistanceSq = FLT_MAX; for (int k = -1; k <= 1; k++) { for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { @@ -621,9 +643,9 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms const float3 pointPosition = cellOffset + hash_float3_to_float3(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = distance(pointPosition, localPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + const float distanceToPointSq = len_squared(pointPosition - localPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPoint = pointPosition; closestPointOffset = cellOffset; } @@ -631,7 +653,7 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms } } - minDistance = FLT_MAX; + minDistanceSq = FLT_MAX; float3 closestPointToClosestPoint = make_float3(0.0f, 0.0f, 0.0f); for (int k = -1; k <= 1; k++) { for (int j = -1; j <= 1; j++) { @@ -643,9 +665,9 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms const float3 pointPosition = cellOffset + hash_float3_to_float3(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = distance(closestPoint, pointPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + const float distanceToPointSq = len_squared(closestPoint - pointPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPointToClosestPoint = pointPosition; } } @@ -678,7 +700,8 @@ ccl_device VoronoiOutput voronoi_f1(const ccl_private VoronoiParams ¶ms, con const float4 pointPosition = cellOffset + hash_float4_to_float4(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + const float distanceToPoint = voronoi_distance_bound( + pointPosition, localPosition, params); if (distanceToPoint < minDistance) { targetOffset = cellOffset; minDistance = distanceToPoint; @@ -690,7 +713,7 @@ ccl_device VoronoiOutput voronoi_f1(const ccl_private VoronoiParams ¶ms, con } VoronoiOutput octave; - octave.distance = minDistance; + octave.distance = voronoi_distance(targetPosition, localPosition, params); octave.color = hash_float4_to_float3(cellPosition + targetOffset); octave.position = voronoi_position(targetPosition + cellPosition); return octave; @@ -842,7 +865,7 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms float4 closestPoint = zero_float4(); float4 closestPointOffset = zero_float4(); - float minDistance = FLT_MAX; + float minDistanceSq = FLT_MAX; for (int u = -1; u <= 1; u++) { for (int k = -1; k <= 1; k++) { for (int j = -1; j <= 1; j++) { @@ -851,9 +874,9 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms const float4 pointPosition = cellOffset + hash_float4_to_float4(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = distance(pointPosition, localPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + const float distanceToPointSq = len_squared(pointPosition - localPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPoint = pointPosition; closestPointOffset = cellOffset; } @@ -862,7 +885,7 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms } } - minDistance = FLT_MAX; + minDistanceSq = FLT_MAX; float4 closestPointToClosestPoint = zero_float4(); for (int u = -1; u <= 1; u++) { for (int k = -1; k <= 1; k++) { @@ -875,9 +898,9 @@ ccl_device float voronoi_n_sphere_radius(const ccl_private VoronoiParams ¶ms const float4 pointPosition = cellOffset + hash_float4_to_float4(cellPosition + cellOffset) * params.randomness; - const float distanceToPoint = distance(closestPoint, pointPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + const float distanceToPointSq = len_squared(closestPoint - pointPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPointToClosestPoint = pointPosition; } } diff --git a/source/blender/blenlib/intern/noise.cc b/source/blender/blenlib/intern/noise.cc index e88bfd11744..0d94bbb1c21 100644 --- a/source/blender/blenlib/intern/noise.cc +++ b/source/blender/blenlib/intern/noise.cc @@ -1134,6 +1134,17 @@ float voronoi_distance(const float4 a, const float4 b, const VoronoiParams ¶ return 0.0f; } +/* Possibly cheaper/faster version of Voronoi distance, in a way that does not change + * logic of "which distance is the closest?". */ +template +static float voronoi_distance_bound(const T a, const T b, const VoronoiParams ¶ms) +{ + if (params.metric == NOISE_SHD_VORONOI_EUCLIDEAN) { + return math::length_squared(a - b); + } + return voronoi_distance(a, b, params); +} + /* **** 1D Voronoi **** */ float4 voronoi_position(const float coord) @@ -1317,7 +1328,7 @@ VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, const float2 coord) float2 cellOffset(i, j); float2 pointPosition = cellOffset + hash_float_to_float2(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + float distanceToPoint = voronoi_distance_bound(pointPosition, localPosition, params); if (distanceToPoint < minDistance) { targetOffset = cellOffset; minDistance = distanceToPoint; @@ -1327,7 +1338,7 @@ VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, const float2 coord) } VoronoiOutput octave; - octave.distance = minDistance; + octave.distance = voronoi_distance(targetPosition, localPosition, params); octave.color = hash_float_to_float3(cellPosition + targetOffset); octave.position = voronoi_position(targetPosition + cellPosition); return octave; @@ -1461,22 +1472,22 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float2 coord) float2 closestPoint = {0.0f, 0.0f}; float2 closestPointOffset = {0.0f, 0.0f}; - float minDistance = FLT_MAX; + float minDistanceSq = FLT_MAX; for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { float2 cellOffset(i, j); float2 pointPosition = cellOffset + hash_float_to_float2(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = math::distance(pointPosition, localPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + float distanceToPointSq = math::length_squared(pointPosition - localPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPoint = pointPosition; closestPointOffset = cellOffset; } } } - minDistance = FLT_MAX; + minDistanceSq = FLT_MAX; float2 closestPointToClosestPoint = {0.0f, 0.0f}; for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { @@ -1486,9 +1497,9 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float2 coord) float2 cellOffset = float2(i, j) + closestPointOffset; float2 pointPosition = cellOffset + hash_float_to_float2(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = math::distance(closestPoint, pointPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + float distanceToPointSq = math::length_squared(closestPoint - pointPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPointToClosestPoint = pointPosition; } } @@ -1518,7 +1529,7 @@ VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, const float3 coord) float3 cellOffset(i, j, k); float3 pointPosition = cellOffset + hash_float_to_float3(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + float distanceToPoint = voronoi_distance_bound(pointPosition, localPosition, params); if (distanceToPoint < minDistance) { targetOffset = cellOffset; minDistance = distanceToPoint; @@ -1529,7 +1540,7 @@ VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, const float3 coord) } VoronoiOutput octave; - octave.distance = minDistance; + octave.distance = voronoi_distance(targetPosition, localPosition, params); octave.color = hash_float_to_float3(cellPosition + targetOffset); octave.position = voronoi_position(targetPosition + cellPosition); return octave; @@ -1673,16 +1684,16 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float3 coord) float3 closestPoint = {0.0f, 0.0f, 0.0f}; float3 closestPointOffset = {0.0f, 0.0f, 0.0f}; - float minDistance = FLT_MAX; + float minDistanceSq = FLT_MAX; for (int k = -1; k <= 1; k++) { for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { float3 cellOffset(i, j, k); float3 pointPosition = cellOffset + hash_float_to_float3(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = math::distance(pointPosition, localPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + float distanceToPointSq = math::length_squared(pointPosition - localPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPoint = pointPosition; closestPointOffset = cellOffset; } @@ -1690,7 +1701,7 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float3 coord) } } - minDistance = FLT_MAX; + minDistanceSq = FLT_MAX; float3 closestPointToClosestPoint = {0.0f, 0.0f, 0.0f}; for (int k = -1; k <= 1; k++) { for (int j = -1; j <= 1; j++) { @@ -1701,9 +1712,9 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float3 coord) float3 cellOffset = float3(i, j, k) + closestPointOffset; float3 pointPosition = cellOffset + hash_float_to_float3(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = math::distance(closestPoint, pointPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + float distanceToPointSq = math::length_squared(closestPoint - pointPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPointToClosestPoint = pointPosition; } } @@ -1735,7 +1746,7 @@ VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, const float4 coord) float4 cellOffset(i, j, k, u); float4 pointPosition = cellOffset + hash_float_to_float4(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + float distanceToPoint = voronoi_distance_bound(pointPosition, localPosition, params); if (distanceToPoint < minDistance) { targetOffset = cellOffset; minDistance = distanceToPoint; @@ -1747,7 +1758,7 @@ VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, const float4 coord) } VoronoiOutput octave; - octave.distance = minDistance; + octave.distance = voronoi_distance(targetPosition, localPosition, params); octave.color = hash_float_to_float3(cellPosition + targetOffset); octave.position = voronoi_position(targetPosition + cellPosition); return octave; @@ -1899,7 +1910,7 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float4 coord) float4 closestPoint = {0.0f, 0.0f, 0.0f, 0.0f}; float4 closestPointOffset = {0.0f, 0.0f, 0.0f, 0.0f}; - float minDistance = FLT_MAX; + float minDistanceSq = FLT_MAX; for (int u = -1; u <= 1; u++) { for (int k = -1; k <= 1; k++) { for (int j = -1; j <= 1; j++) { @@ -1907,9 +1918,9 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float4 coord) float4 cellOffset(i, j, k, u); float4 pointPosition = cellOffset + hash_float_to_float4(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = math::distance(pointPosition, localPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + float distanceToPointSq = math::length_squared(pointPosition - localPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPoint = pointPosition; closestPointOffset = cellOffset; } @@ -1918,7 +1929,7 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float4 coord) } } - minDistance = FLT_MAX; + minDistanceSq = FLT_MAX; float4 closestPointToClosestPoint = {0.0f, 0.0f, 0.0f, 0.0f}; for (int u = -1; u <= 1; u++) { for (int k = -1; k <= 1; k++) { @@ -1930,9 +1941,9 @@ float voronoi_n_sphere_radius(const VoronoiParams ¶ms, const float4 coord) float4 cellOffset = float4(i, j, k, u) + closestPointOffset; float4 pointPosition = cellOffset + hash_float_to_float4(cellPosition + cellOffset) * params.randomness; - float distanceToPoint = math::distance(closestPoint, pointPosition); - if (distanceToPoint < minDistance) { - minDistance = distanceToPoint; + float distanceToPointSq = math::length_squared(closestPoint - pointPosition); + if (distanceToPointSq < minDistanceSq) { + minDistanceSq = distanceToPointSq; closestPointToClosestPoint = pointPosition; } }