Sculpt: Compress position undo step data

Stored undo step data for position changes in sculpt mode are now
automatically compressed. Compression is run in background threads,
reducing memory consumption during sculpting sessions while adding
little performance overhead.

For testing and benchmarks, memory usage is now available through
`bpy.app.undo_memory_info()`. Undo memory usage is now tracked by the
existing automated benchmark tests. Some changes to the web benchmark
visualization present the data a bit better.

ZSTD compression is run asynchronously in a backround task pool.
Compression is only blocking if the data is requested immediately for
undo/redo.

Co-authored-by: Hans Goudey <hans@blender.org>

Pull Request: https://projects.blender.org/blender/blender/pulls/141310
This commit is contained in:
Namit Bhutani
2025-09-03 19:15:05 +02:00
committed by Hans Goudey
parent f3c5119d7d
commit 8536fd1223
8 changed files with 294 additions and 47 deletions

View File

@@ -39,6 +39,33 @@
return ndt;
}
function drawChart(chartsQueue, index) {
index = index || 0;
if (index === chartsQueue.length)
return;
var chartData = chartsQueue[index];
var chart;
if (chartData.chart_type == 'line') {
chart = new google.charts.Line(document.getElementById(chartData.id));
} else {
chart = new google.charts.Bar(document.getElementById(chartData.id));
}
google.visualization.events.addOneTimeListener(chart, 'ready', function() {
/* Auto scale chart elements to display full SVG. */
var allSvg = document.getElementsByTagName("svg");
for (var svgIndex = 0; svgIndex < allSvg.length; svgIndex++) {
allSvg[svgIndex].setAttribute('height', allSvg[svgIndex].getBBox().height);
}
drawChart(chartsQueue, index + 1);
});
chart.draw(chartData.data, chartData.options);
}
function draw_charts()
{
/* Load JSON data. */
@@ -54,7 +81,9 @@
charts_content_elem.removeChild(charts_content_elem.firstChild);
}
/* Draw charts for each device. */
var chartsQueue = [];
/* Prepare UI and charts queue for each device. */
for (var i = 0; i < json_data.length; i++)
{
benchmark = json_data[i];
@@ -104,6 +133,7 @@
/* Create chart div. */
chart_div = document.createElement('div');
chart_div.id = "chart-" + i;
tab_div.appendChild(chart_div)
/* Chart drawing options. */
@@ -113,25 +143,20 @@
height: 500,
};
/* Create chart. */
/* Prepare chart data for queue. */
var data = new google.visualization.DataTable(benchmark["data"]);
if (benchmark['chart_type'] == 'line') {
var chart = new google.charts.Line(chart_div);
chart.draw(data, options);
}
else {
var chart = new google.charts.Bar(chart_div);
chart.draw(transposeDataTable(data), options);
}
/* Auto scale chart elements to display full SVG. */
google.visualization.events.addListener(chart, 'ready', function () {
var allSvg = document.getElementsByTagName("svg");
for (var index = 0; index < allSvg.length; index++) {
allSvg[index].setAttribute('height', allSvg[index].getBBox().height);
}
});
var chartData = {
id: chart_div.id,
data: benchmark['chart_type'] == 'line' ? data : transposeDataTable(data),
options: options,
chart_type: benchmark['chart_type']
};
chartsQueue.push(chartData);
}
/* Start drawing charts sequentially. */
drawChart(chartsQueue, 0);
}
</script>
</head>

View File

@@ -169,7 +169,6 @@ def _run_brush_test(args: dict):
min_measurements = 5
max_measurements = 100
measurements = []
while True:
prepare_sculpt_scene(context, args['mode'])
@@ -178,16 +177,18 @@ def _run_brush_test(args: dict):
with context.temp_override(**context_override):
if args.get('spatial_reorder', False):
bpy.ops.mesh.reorder_vertices_spatial()
bpy.ops.ed.undo_push()
start = time.time()
bpy.ops.sculpt.brush_stroke(stroke=generate_stroke(context_override), override_location=True)
bpy.ops.ed.undo_push()
measurements.append(time.time() - start)
memory_info = bpy.app.undo_memory_info()
if len(measurements) >= min_measurements and (time.time() - total_time_start) > timeout:
break
if len(measurements) >= max_measurements:
break
return sum(measurements) / len(measurements)
return {'time': sum(measurements) / len(measurements), 'memory': memory_info}
def _run_bvh_test(args: dict):
@@ -277,7 +278,7 @@ class SculptBrushTest(api.Test):
result, _ = env.run_in_blender(_run_brush_test, args, [self.filepath])
return {'time': result}
return result
class SculptBrushAfterSpatialReorderingTest(api.Test):
@@ -301,7 +302,7 @@ class SculptBrushAfterSpatialReorderingTest(api.Test):
result, _ = env.run_in_blender(_run_brush_test, args, [self.filepath])
return {'time': result}
return result
class SculptRebuildBVHTest(api.Test):