[Bf-blender-cvs] [be876b8db68] master: Painting: Performance curve masks.

Jeroen Bakker noreply at git.blender.org
Mon Nov 22 15:36:06 CET 2021


Commit: be876b8db684685605d18ea70b935ba5e40a03bc
Author: Jeroen Bakker
Date:   Mon Nov 22 15:29:12 2021 +0100
Branches: master
https://developer.blender.org/rBbe876b8db684685605d18ea70b935ba5e40a03bc

Painting: Performance curve masks.

This patch separates the static-part from the dynamic-part when
generate brush masks. This makes the generation of brush masks 2-5 times
faster depending on the size of the brush.

More improvements can be done, this was just low hanging fruit.

===================================================================

M	source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc
M	source/blender/editors/sculpt_paint/paint_intern.h

===================================================================

diff --git a/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc b/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc
index a41e102b540..8d57a3d9152 100644
--- a/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc
+++ b/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc
@@ -33,11 +33,26 @@
 
 namespace blender::ed::sculpt_paint {
 
+constexpr int AntiAliasingSamplesPerTexelAxisMin = 3;
+constexpr int AntiAliasingSamplesPerTexelAxisMax = 16;
+/**
+ * \brief Number of samples to use between 0..1.
+ */
+constexpr int CurveSamplesBaseLen = 1024;
+/**
+ * \brief Number of samples to store in the cache.
+ *
+ * M_SQRT2 is used as brushes are circles and the curve_mask is square.
+ * + 1 to fix floating rounding issues.
+ */
+constexpr int CurveSamplesLen = M_SQRT2 * CurveSamplesBaseLen + 1;
+
 static int aa_samples_per_texel_axis(const Brush *brush, const float radius)
 {
   int aa_samples = 1.0f / (radius * 0.20f);
   if (brush->sampling_flag & BRUSH_PAINT_ANTIALIASING) {
-    aa_samples = clamp_i(aa_samples, 3, 16);
+    aa_samples = clamp_i(
+        aa_samples, AntiAliasingSamplesPerTexelAxisMin, AntiAliasingSamplesPerTexelAxisMax);
   }
   else {
     aa_samples = 1;
@@ -62,29 +77,65 @@ static void update_curve_mask(CurveMaskCache *curve_mask_cache,
   const float aa_step = 1.0f / (float)aa_samples;
 
   float bpos[2];
-  bpos[0] = cursor_position[0] - floorf(cursor_position[0]) + offset - aa_offset;
-  bpos[1] = cursor_position[1] - floorf(cursor_position[1]) + offset - aa_offset;
+  bpos[0] = cursor_position[0] - floorf(cursor_position[0]) + offset;
+  bpos[1] = cursor_position[1] - floorf(cursor_position[1]) + offset;
 
   float weight_factor = 65535.0f / (float)(aa_samples * aa_samples);
 
   for (int y = 0; y < diameter; y++) {
     for (int x = 0; x < diameter; x++, m++) {
+      float pixel_xy[2];
+      pixel_xy[0] = static_cast<float>(x) + aa_offset;
       float total_weight = 0;
+
       for (int i = 0; i < aa_samples; i++) {
+        pixel_xy[1] = static_cast<float>(y) + aa_offset;
         for (int j = 0; j < aa_samples; j++) {
-          float pixel_xy[2] = {x + (aa_step * i), y + (aa_step * j)};
-          sub_v2_v2(pixel_xy, bpos);
+          const float len = len_v2v2(pixel_xy, bpos);
+          const int sample_index = min_ii((len / radius) * CurveSamplesBaseLen,
+                                          CurveSamplesLen - 1);
+          const float sample_weight = curve_mask_cache->sampled_curve[sample_index];
 
-          const float len = len_v2(pixel_xy);
-          const float sample_weight = BKE_brush_curve_strength_clamped(brush, len, radius);
           total_weight += sample_weight;
+
+          pixel_xy[1] += aa_step;
         }
+        pixel_xy[0] += aa_step;
       }
       *m = (unsigned short)(total_weight * weight_factor);
     }
   }
 }
 
+static bool is_sampled_curve_valid(const CurveMaskCache *curve_mask_cache, const Brush *brush)
+{
+  if (curve_mask_cache->sampled_curve == nullptr) {
+    return false;
+  }
+  return curve_mask_cache->last_curve_timestamp == brush->curve->changed_timestamp;
+}
+
+static void sampled_curve_free(CurveMaskCache *curve_mask_cache)
+{
+  MEM_SAFE_FREE(curve_mask_cache->sampled_curve);
+  curve_mask_cache->last_curve_timestamp = 0;
+}
+
+static void update_sampled_curve(CurveMaskCache *curve_mask_cache, const Brush *brush)
+{
+  if (curve_mask_cache->sampled_curve == nullptr) {
+    curve_mask_cache->sampled_curve = static_cast<float *>(
+        MEM_mallocN(CurveSamplesLen * sizeof(float), __func__));
+  }
+
+  for (int i = 0; i < CurveSamplesLen; i++) {
+    const float len = i / float(CurveSamplesBaseLen);
+    const float sample_weight = BKE_brush_curve_strength_clamped(brush, len, 1.0f);
+    curve_mask_cache->sampled_curve[i] = sample_weight;
+  }
+  curve_mask_cache->last_curve_timestamp = brush->curve->changed_timestamp;
+}
+
 static size_t diameter_to_curve_mask_size(const int diameter)
 {
   return diameter * diameter * sizeof(ushort);
@@ -115,6 +166,7 @@ using namespace blender::ed::sculpt_paint;
 
 void paint_curve_mask_cache_free_data(CurveMaskCache *curve_mask_cache)
 {
+  sampled_curve_free(curve_mask_cache);
   curve_mask_free(curve_mask_cache);
 }
 
@@ -124,10 +176,13 @@ void paint_curve_mask_cache_update(CurveMaskCache *curve_mask_cache,
                                    const float radius,
                                    const float cursor_position[2])
 {
+  if (!is_sampled_curve_valid(curve_mask_cache, brush)) {
+    update_sampled_curve(curve_mask_cache, brush);
+  }
+
   if (!is_curve_mask_size_valid(curve_mask_cache, diameter)) {
     curve_mask_free(curve_mask_cache);
     curve_mask_allocate(curve_mask_cache, diameter);
   }
   update_curve_mask(curve_mask_cache, brush, diameter, radius, cursor_position);
 }
-
diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h
index 13ae2b34860..62320defbb3 100644
--- a/source/blender/editors/sculpt_paint/paint_intern.h
+++ b/source/blender/editors/sculpt_paint/paint_intern.h
@@ -257,6 +257,18 @@ void PAINT_OT_add_simple_uvs(struct wmOperatorType *ot);
  * When 2d painting images the curve mask is used as an input.
  */
 typedef struct CurveMaskCache {
+  /**
+   * \brief Last #CurveMapping.changed_timestamp being read.
+   *
+   * When different the input cache needs to be recalculated.
+   */
+  int last_curve_timestamp;
+
+  /**
+   * \brief sampled version of the brush curvemapping.
+   */
+  float *sampled_curve;
+
   /**
    * \brief Size in bytes of the curve_mask field.
    *



More information about the Bf-blender-cvs mailing list