Tests: Add sculpt rebuild bvh performance tests

Using the same sized grids as the brush tests, this commit uses the
`sculpt.optimize` operator to approximate the cost of rebuilding the BVH
for a given base mesh grid, multires grid, and dyntopo grid.

Pull Request: https://projects.blender.org/blender/blender/pulls/137690
This commit is contained in:
Sean Kim
2025-04-18 06:08:41 +02:00
committed by Sean Kim
parent 719c1b9611
commit 24f6719d00

View File

@@ -40,7 +40,7 @@ def set_view3d_context_override(context_override):
context_override["region"] = region
def prepare_sculpt_scene(context: any, mode: SculptMode, brush_type: BrushType):
def prepare_sculpt_scene(context: any, mode: SculptMode):
"""
Prepare a clean state of the scene suitable for benchmarking
@@ -96,20 +96,24 @@ def prepare_sculpt_scene(context: any, mode: SculptMode, brush_type: BrushType):
# Move the plane to the sculpt mode.
bpy.ops.object.mode_set(mode='SCULPT')
bpy.ops.brush.asset_activate(
asset_library_type='ESSENTIALS',
relative_asset_identifier='brushes/essentials_brushes-mesh_sculpt.blend/Brush/' +
brush_type.value)
# Reduce the brush strength to avoid deforming the mesh too much and influencing multiple strokes
context.tool_settings.sculpt.brush.strength = 0.1
if mode == SculptMode.MULTIRES:
bpy.ops.object.subdivision_set(level=3)
elif mode == SculptMode.DYNTOPO:
bpy.ops.sculpt.dynamic_topology_toggle()
def prepare_brush(context: any, brush_type: BrushType):
"""Activates and sets common brush settings"""
import bpy
bpy.ops.brush.asset_activate(
asset_library_type='ESSENTIALS',
relative_asset_identifier='brushes/essentials_brushes-mesh_sculpt.blend/Brush/' +
brush_type.value)
# Reduce the brush strength to avoid deforming the mesh too much and influencing multiple strokes
context.tool_settings.sculpt.brush.strength = 0.1
def generate_stroke(context):
"""
Generate stroke for the bpy.ops.sculpt.brush_stroke operator
@@ -150,7 +154,7 @@ def generate_stroke(context):
return stroke
def _run(args: dict):
def _run_brush_test(args: dict):
import bpy
import time
context = bpy.context
@@ -161,7 +165,8 @@ def _run(args: dict):
# Create an undo stack explicitly. This isn't created by default in background mode.
bpy.ops.ed.undo_push()
prepare_sculpt_scene(context, args['mode'], args['brush_type'])
prepare_sculpt_scene(context, args['mode'])
prepare_brush(context, args['brush_type'])
context_override = context.copy()
set_view3d_context_override(context_override)
@@ -184,6 +189,40 @@ def _run(args: dict):
return sum(measurements) / len(measurements)
def _run_bvh_test(args: dict):
import bpy
import time
context = bpy.context
timeout = 5
total_time_start = time.time()
# Create an undo stack explicitly. This isn't created by default in background mode.
bpy.ops.ed.undo_push()
prepare_sculpt_scene(context, args['mode'])
context_override = context.copy()
set_view3d_context_override(context_override)
min_measurements = 5
max_measurements = 100
measurements = []
while True:
with context.temp_override(**context_override):
start = time.time()
bpy.ops.sculpt.optimize()
measurements.append(time.time() - start)
if len(measurements) >= min_measurements and (time.time() - total_time_start) > timeout:
break
if len(measurements) >= max_measurements:
break
return sum(measurements) / len(measurements)
class SculptBrushTest(api.Test):
def __init__(self, filepath: pathlib.Path, mode: SculptMode, brush_type: BrushType):
self.filepath = filepath
@@ -202,7 +241,28 @@ class SculptBrushTest(api.Test):
'brush_type': self.brush_type,
}
result, _ = env.run_in_blender(_run, args, [self.filepath])
result, _ = env.run_in_blender(_run_brush_test, args, [self.filepath])
return {'time': result}
class SculptRebuildBVHTest(api.Test):
def __init__(self, filepath: pathlib.Path, mode: SculptMode):
self.filepath = filepath
self.mode = mode
def name(self):
return "{}_rebuild_bvh".format(self.mode.name.lower())
def category(self):
return "sculpt"
def run(self, env, _device_id):
args = {
'mode': self.mode,
}
result, _ = env.run_in_blender(_run_bvh_test, args, [self.filepath])
return {'time': result}
@@ -211,4 +271,6 @@ def generate(env):
filepaths = env.find_blend_files('sculpt/*')
# For now, we only expect there to ever be a single file to use as the basis for generating other brush tests
assert len(filepaths) == 1
return [SculptBrushTest(filepaths[0], mode, brush_type) for mode in SculptMode for brush_type in BrushType]
brush_tests = [SculptBrushTest(filepaths[0], mode, brush_type) for mode in SculptMode for brush_type in BrushType]
bvh_tests = [SculptRebuildBVHTest(filepaths[0], mode) for mode in SculptMode]
return brush_tests + bvh_tests