From be876b8db684685605d18ea70b935ba5e40a03bc Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 22 Nov 2021 15:29:12 +0100 Subject: 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. --- .../sculpt_paint/paint_image_2d_curve_mask.cc | 71 +++++++++++++++++++--- source/blender/editors/sculpt_paint/paint_intern.h | 12 ++++ 2 files changed, 75 insertions(+), 8 deletions(-) (limited to 'source/blender/editors/sculpt_paint') 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(x) + aa_offset; float total_weight = 0; + for (int i = 0; i < aa_samples; i++) { + pixel_xy[1] = static_cast(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( + 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. * -- cgit v1.2.3