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.
This commit is contained in:
Jeroen Bakker 2021-11-22 15:29:12 +01:00
parent 178947dec3
commit be876b8db6
Notes: blender-bot 2023-02-14 08:08:56 +01:00
Referenced by issue #95992, Crash: painting 2D textures with anchored strokes in 3.1 Beta
2 changed files with 77 additions and 10 deletions

View File

@ -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++) {
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_v2(pixel_xy);
const float sample_weight = BKE_brush_curve_strength_clamped(brush, len, radius);
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++) {
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];
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);
}

View File

@ -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.
*