Profiling buddy

master
pullusb 2023-11-07 12:04:14 +01:00
parent 3407bc79e9
commit 78a7d6f900
3 changed files with 268 additions and 4 deletions

View File

@ -1,7 +1,9 @@
# GIT_TEMPLATE
> Template for the others repository
# PROFILING BUDDY
Profiling Panel in Modifier stack
Forked from Simon Thommes [random-blender-scripts](https://gitlab.com/simonthommes/random-blender-scripts/-/tree/master/addons/profiling_buddy?ref_type=heads)
Git_template is a template repository that must be used to generate others repository
<!-- TABLE OF CONTENTS -->
***
@ -34,7 +36,7 @@ Git_template is a template repository that must be used to generate others repos
```
2. Create your own local directory in
```sh
git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/git_template.git
git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/profiling_buddy.git
```
<!-- CONTENTS -->

22
__init__.py Executable file
View File

@ -0,0 +1,22 @@
import bpy
from . import modifier_profiling
bl_info = {
"name": "Profiling Buddy",
"author": "Simon Thommes, Samuel Bernou",
"version": (0, 2),
"blender": (3, 5, 0),
"location": "Properties > Modifier > Profiling Buddy",
"description": "Adds panels to profile execution times.",
"category": "Workflow",
}
modules = [modifier_profiling]
def register():
for m in modules:
m.register()
def unregister():
for m in modules:
m.unregister()

240
modifier_profiling.py Executable file
View File

@ -0,0 +1,240 @@
import bpy
from bpy.types import PropertyGroup, Operator
from bpy.props import (StringProperty,
FloatProperty,
PointerProperty,
CollectionProperty)
class PB_Modifier_Profiling_Panel(bpy.types.Panel):
"""
"""
bl_label = "Modifier Profiling"
bl_idname = "SCENE_PT_modifier_profiling"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "modifier"
def draw(self, context):
layout = self.layout
scene = context.scene
settings = scene.profiler_buddy_prop
layout.prop(settings, 'evaluation_live')
if settings.evaluation_live:
draw_modifier_times_live(self, context)
else:
draw_modifier_times(self, context)
col = layout.column()
# col.prop(settings, 'use_frame_set')
if settings.progress >= 0:
col.progress(factor=settings.progress / settings.evaluations_count,
text = f"Iteration {settings.progress}/{settings.evaluations_count}")
## Show modifier sub-iteration with a load ring ?
# col.progress(factor=settings.subprogress / len(context.object.modifiers),
# type = 'RING') # , text = f"{settings.subprogress}/{len(context.object.modifiers)}"
row = col.row()
row.prop(settings, 'evaluations_count')
row.operator('pb.evaluate', text='Evaluate')
def time_to_string(t):
''' Formats time in seconds to the nearest sensible unit.
'''
units = {3600.: 'h', 60.: 'm', 1.: 's', .001: 'ms'}
for factor in units.keys():
if t >= factor:
return f'{t/factor:.3g} {units[factor]}'
if t >= 1e-4:
return f'{t/factor:.3g} {units[factor]}'
else:
return f'<0.1 ms'
## Dynamic draw
def draw_modifier_times_live(self, context):
depsgraph = context.view_layer.depsgraph
ob = context.object
ob_eval = ob.evaluated_get(depsgraph)
box = self.layout.box()
times = []
total = 0
for mod_eval in ob_eval.modifiers:
t = mod_eval.execution_time
times += [t]
total += t
col_fl = box.column_flow(columns=2)
col = col_fl.column(align=True)
for mod_eval in ob_eval.modifiers:
row = col.row()
row.enabled = mod_eval.show_viewport
row.label(text=f'{mod_eval.name}:')
col = col_fl.column(align=True)
for i, t in enumerate(times):
row = col.row()
row.enabled = ob_eval.modifiers[i].show_viewport
row.alert = t >= 0.8*max(times)
row.label(text=time_to_string(t))
row = box.column_flow()
row.column().label(text=f'TOTAL:')
row.column().label(text=time_to_string(sum(times)))
## Static draw from dict
def draw_modifier_times(self, context):
box = self.layout.box()
ob = context.object
wm = bpy.context.window_manager
mod_timer = wm.get('mod_timer')
if not mod_timer:
mod_timer = wm['mod_timer'] = {}
if len(ob.modifiers) == 0:
return
if mod_timer:
# box.label(text=f'Average out of {context.scene.profiler_buddy_prop.evaluations_count}')
times = [t for _m, t in mod_timer.items()]
col_fl = box.column_flow(columns=2)
col = col_fl.column(align=True)
for i, (mod_name, t) in enumerate(mod_timer.items()):
if i > len(ob.modifiers) - 1:
continue
row = col.row()
row.enabled = ob.modifiers[i].show_viewport
row.label(text=f'{mod_name}:')
col = col_fl.column(align=True)
for i, (mod_name, t) in enumerate(mod_timer.items()):
if i > len(ob.modifiers) - 1:
continue
row = col.row()
row.enabled = ob.modifiers[i].show_viewport
row.alert = t >= 0.8*max(times)
row.label(text=time_to_string(t))
# col = col_fl.column(align=True)
# for i, (mod_name, t) in enumerate(mod_timer.items()):
# if i > len(ob.modifiers) - 1:
# continue
# row = col.row()
# row.enabled = ob.modifiers[i].show_viewport
# row.alert = t >= 0.8*max(times)
# row.label(text=f'{1000 / (t*1000):.0f} fps')
row = box.column_flow()
total_time = sum(times)
fps = f'{1000 / (total_time*1000):.1f} fps' if total_time > 0 else '-- fps'
# row.label(text = fps)
row.column().label(text=f'TOTAL: {fps}')
row.column().label(text=time_to_string(total_time))
else:
if context.scene.profiler_buddy_prop.progress < 0:
box.label(text='Click Evaluate To Profile Modifiers', icon='INFO')
class PB_OT_evalulate(Operator):
bl_idname = "pb.evaluate"
bl_label = "Evaluate Modifiers"
bl_description = "Evaluate modifier by a count "
bl_options = {"REGISTER"} # , "INTERNAL"
def execute(self, context):
wm = bpy.context.window_manager
settings = context.scene.profiler_buddy_prop
mod_timer = wm.get('mod_timer')
if not mod_timer:
mod_timer = wm['mod_timer'] = {}
else:
mod_timer = {}
wm['mod_timer'].clear() # Clear dict
tmp_dic = {}
ob = context.object
wm.progress_begin(0, settings.evaluations_count)
for i in range(settings.evaluations_count):
settings.progress = i
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
# context.area.tag_redraw()
ob.update_tag()
# if settings.use_frame_set:
# context.scene.frame_current = context.scene.frame_current
depsgraph = context.view_layer.depsgraph
depsgraph.update()
ob_eval = ob.evaluated_get(depsgraph)
## gather a list of all evaluated sessions
for mod_eval in ob_eval.modifiers:
if i == 0:
tmp_dic[mod_eval.name] = []
t = mod_eval.execution_time
# print(i, mod_eval.name, t)
# wm['mod_timer'][mod_eval.name] = t # keep last evaluated time
tmp_dic[mod_eval.name] += [t]
wm.progress_update(i)
wm.progress_end()
settings.progress = -1
for k, v in tmp_dic.items():
average = sum(v) / len(v)
# print(k, v, average)
## debugging
# if settings.evaluations_count > 1:
# if v[0] == v[1]:
# print('same:')
# for subv in v:
# print(subv)
wm['mod_timer'][k] = average
return {"FINISHED"}
class PB_PG_modifiers_profiler(PropertyGroup):
evaluation_live : bpy.props.BoolProperty(name='Live Mode', default=True)
evaluations_count : bpy.props.IntProperty(name='Evaluations', default=5)
progress : bpy.props.IntProperty(name='Progress', default=-1)
# subprogress : bpy.props.IntProperty(name='ModifierProgress', default=-1)
# use_frame_set : bpy.props.BoolProperty(name='Set Frame At Each Iteration', default=True)
classes = [
PB_OT_evalulate,
PB_Modifier_Profiling_Panel,
]
def register():
bpy.utils.register_class(PB_PG_modifiers_profiler)
bpy.types.Scene.profiler_buddy_prop = PointerProperty(type=PB_PG_modifiers_profiler)
bpy.context.window_manager['mod_timer'] = {}
for c in classes:
bpy.utils.register_class(c)
def unregister():
for c in reversed(classes):
bpy.utils.unregister_class(c)
bpy.utils.unregister_class(PB_PG_modifiers_profiler)
del bpy.types.Scene.profiler_buddy_prop
if __name__ == "__main__":
register()