This repository has been archived on 2023-02-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-benchmark-bundle/benchmark/space/__init__.py

794 lines
24 KiB
Python
Raw Normal View History

import datetime
import json
import os
import platform
import sys
from threading import Thread, Lock
import blf
import bpy
2018-08-06 14:55:07 +02:00
from bpy.types import Panel, Scene
from bpy.props import EnumProperty, IntProperty, FloatProperty
from ..foundation import (benchrunner,
buildbot,
config,
logger,
progress,
system_info,
util)
from ..foundation import context as benchmark_context
################################################################################
# Global state.
QUICK_SCENES = ["bmw27",
"classroom"]
COMPLETE_SCENES = ["barbershop_interior",
"bmw27",
"classroom",
"fishy_cat",
"koro",
"pavillon_barcelona",
"victor"]
global_result_platform = None
global_progress_status = None
global_result_stats = None
global_result_dict = None
global_background_image_path = ""
global_scene_status = {}
2018-08-06 12:27:30 +02:00
global_cancel = False
2018-08-06 14:55:07 +02:00
global_cached_system_info = None
global_cached_compute_devices = None
images = {}
current_progress = 0.0
progress_lock = Lock()
################################################################################
# Draw Utilities.
font_id = 0
def viewport_size():
import bgl
viewport = bgl.Buffer(bgl.GL_INT, 4)
bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport)
return viewport[2], viewport[3]
def draw_text_center(text, x, y):
dim = blf.dimensions(font_id, text)
2018-08-03 17:02:27 +02:00
cx = x - int(dim[0]/2)
cy = y - int(dim[1]/2)
blf.position(font_id, cx, cy, 0)
blf.draw(font_id, text)
def draw_text_multiline(text, x, y):
2018-08-03 17:02:27 +02:00
ui_scale = bpy.context.user_preferences.system.ui_scale
height = int(blf.dimensions(font_id, "Dummy Text")[1])
space = int(8 * ui_scale)
for line in text.split('\n'):
2018-08-03 17:02:27 +02:00
blf.position(font_id, x, y - height, 0)
blf.draw(font_id, line)
2018-08-03 17:02:27 +02:00
y -= height + space
def draw_rect(x, y, w, h, color):
import gpu
gpu.draw.rect(x, y, x + w, y + h, color[0], color[1], color[2], color[3])
def draw_image(filepath, x, y, w, h):
global images
if filepath not in images:
ima = bpy.data.images.load(filepath)
images[filepath] = ima
import gpu
gpu.draw.image(images[filepath], x, y, x + w, y + h)
################################################################################
# Draw.
def benchmark_draw_post_pixel(arg1, arg2):
global progress_lock
progress_lock.acquire()
progress_status = global_progress_status
result_platform = global_result_platform
result_stats = global_result_stats
result_dict = global_result_dict
progress_lock.release()
ui_scale = bpy.context.user_preferences.system.ui_scale
blf.color(font_id, 1.0, 1.0, 1.0, 1.0)
window_width, window_height = viewport_size()
# Image
image_h = 370 * ui_scale
image_y = window_height - image_h
if global_background_image_path:
draw_image(global_background_image_path, 0, image_y, window_width, image_h)
else:
splash_dir = os.path.dirname(os.path.abspath(__file__))
splash_filepath = os.path.join(splash_dir, 'splash.png')
draw_image(splash_filepath, 0, image_y, window_width, image_h)
if result_dict:
x = 0.5 * window_width
y = 0.70 * window_height
score = 0
for stat in global_result_dict["stats"]:
if stat["result"] == "OK":
score += stat["total_render_time"]
else:
score = -1
blf.size(font_id, int(32 * ui_scale), 72)
if score >= 0:
draw_text_center("Render time is: {}" . format(
util.humanReadableTimeDifference(score)), x, y)
else:
# TODO(sergey): What is the score?
draw_text_center("CRASH :(", x, y)
elif result_stats or result_platform or progress_status:
blf.size(font_id, int(12 * ui_scale), 72)
x = 50.0 * ui_scale
y = image_y - 20 * ui_scale
# Stats
if result_platform:
draw_text_multiline(result_platform, 0.5 * window_width + x, y)
if result_stats:
draw_text_multiline(result_stats, x, y)
# Progress
progress_x = 0.0
progress_y = image_y + 1
progress_w = window_width * current_progress
progress_h = 15.0 * ui_scale
progress_color = [0.8, 1.0, 1.0, 0.2]
draw_rect(progress_x, progress_y, progress_w, progress_h, progress_color)
# Current status
if global_progress_status:
blf.size(font_id, int(18 * ui_scale), 72)
draw_text_multiline(global_progress_status,
progress_x + 8.0 * ui_scale,
progress_y + progress_h + int(22 * ui_scale))
else:
# Title
x = 0.5 * window_width
y = 0.70 * window_height
blf.size(font_id, int(32 * ui_scale), 72)
draw_text_center("Blender Benchmark 1.0", x, y)
y -= 32 * ui_scale
blf.size(font_id, int(12 * ui_scale), 72)
draw_text_center("Explore the results on opendata.blender.org", x, y)
# Bottom bar
bottom_x = 0
bottom_y = 0
bottom_w = window_width
bottom_h = 52 * ui_scale
bottom_color = [0.2, 0.2, 0.2, 1.0]
draw_rect(bottom_x, bottom_y, bottom_w, bottom_h, bottom_color)
# Logo
logo_width_unscaled = 524
logo_height_unscaled = 152
logo_dir = os.path.dirname(os.path.abspath(__file__))
logo_filepath = os.path.join(logo_dir, 'blender.png')
logo_scale_factor = 1.0
while logo_height_unscaled * logo_scale_factor > bottom_h:
logo_scale_factor *= 0.5
logo_width = logo_width_unscaled * logo_scale_factor
logo_height = logo_height_unscaled * logo_scale_factor
logo_padding = (bottom_h - logo_height) * 0.5
draw_image(logo_filepath,
logo_padding, logo_padding,
logo_width, logo_height)
handle_draw = bpy.types.SpaceBenchmark.draw_handler_add(
benchmark_draw_post_pixel, (None, None), 'WINDOW', 'POST_PIXEL')
################################################################################
# Benchmark foundation integration.
class ProgressProviderSink:
current_progress = 0.0
current_step = ''
current_scene = ''
2018-08-06 12:27:30 +02:00
process = None
def __init__(self):
self.current_progress = 0.0
self.current_step = ''
2018-08-06 12:27:30 +02:00
self.process = None
def progress(self, count, total, prefix="", suffix=""):
progress_lock.acquire()
if total != 0:
self.current_progress = float(count) / float(total)
else:
self.current_progress = 0.0
progress_lock.release()
def clear(self):
progress_lock.acquire()
self.current_progress = 0
progress_lock.release()
def step(self, step_name):
progress_lock.acquire()
if self.current_step != step_name:
self.current_step = step_name
self.current_progress = 0
progress_lock.release()
def scene(self, scene_name):
progress_lock.acquire()
self.current_scene = scene_name
if scene_name:
global global_scene_status
global_scene_status[scene_name] = "Rendering..."
progress_lock.release()
def scene_stats(self, scene_name, stats):
progress_lock.acquire()
global global_scene_status
if stats:
global_scene_status[scene_name] = util.humanReadableTimeDifference(
stats.total_render_time)
else:
global_scene_status[scene_name] = "Crashed :("
progress_lock.release()
2018-08-06 12:27:30 +02:00
def render_process(self, process):
self.process = process
def is_canceled(self):
global global_cancel
return global_cancel
class LoggerProviderSink:
def HEADER(self, *args):
pass
def WARNING(self, *args):
pass
def ERROR(self, *args):
pass
def OK(self, *args):
pass
def BOLD(self, *args):
pass
def INFO(self, *args):
pass
def DEBUG(self, *args):
pass
def FATAL(self, *args):
pass
################################################################################
# Benchmark thread.
def string_strip_trademark(name):
return name.replace("(R)", "").replace("(TM)", "")
def correct_device_name(name):
if (name.startswith("TITAN") or
name.startswith("Quadro") or
name.startswith("GeForce")):
return "Nvidia " + name;
if (name.startswith("Radeon")):
return "AMD " + name;
return name
def construct_gpu_string(system_info):
gpu_names = []
for device in system_info["devices"]:
if device["type"] == "CPU":
continue
gpu_names.append(correct_device_name(device["name"]))
return ", " . join(gpu_names)
def construct_platform_string(system_info):
"""
Construct human readable platform string to show in the interface.
"""
result = ""
result += "Operation System: {} {} bit" . format(system_info["system"],
system_info["bitness"])
result += "\nCPU: {}" . format(
string_strip_trademark(system_info["cpu_brand"]))
gpus = construct_gpu_string(system_info)
if gpus:
result += "\nGPU(s): {}" . format(gpus)
return result
def convert_result_to_json_dict(ctx, results):
# Convert custom classes to dictionaries for easier JSON dump.
json_results = results
stats = json_results['stats']
json_results['stats'] = []
for scene in ctx.scenes:
if scene not in stats:
continue
stat = stats[scene]
if stat:
stat = stat.asDict()
stat['result'] = 'OK'
else:
stat = {'result': 'CRASH'}
stat["scene_name"] = scene
json_results['stats'] .append(stat)
return json_results
2018-08-06 14:55:07 +02:00
def system_info_get(ctx):
# This is all system information Blender knows.
# NOTE: We override executable since cpuinfo uses it, and it is set
# to blender.
old_executable = sys.executable
sys.executable = bpy.app.binary_path_python
info = system_info.gatherSystemInfo(ctx)
sys.executable = old_executable
return info
def benchmark_thread(ctx):
global progress_lock, global_result_platform, global_progress_status
2018-08-06 12:27:30 +02:00
global global_cancel
progress_lock.acquire()
global_progress_status = "Collecting system information..."
2018-08-06 12:27:30 +02:00
if global_cancel:
progress_lock.release()
return
progress_lock.release()
2018-08-06 14:55:07 +02:00
blender_system_info = system_info_get(ctx)
# This is actual device configuration which is used to render the
# benchmark scene.
blender_device_info = benchrunner.benchmarkGetDeviceInfo(ctx)
if not blender_device_info['device_type']:
# TODO(sergey): Report an error somehow.
return
progress_lock.acquire()
global_result_platform = construct_platform_string(blender_system_info)
2018-08-06 12:27:30 +02:00
if global_cancel:
progress_lock.release()
return
global_progress_status = "Prepating render..."
progress_lock.release()
all_stats = benchrunner.benchmarkAll(ctx)
# Gather all information together.
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
result = convert_result_to_json_dict(ctx, {
"timestamp": timestamp,
"blender_version": system_info.getBlenderVersion(ctx),
"system_info": blender_system_info,
"device_info": blender_device_info,
"stats": all_stats if all_stats else {}
})
2018-08-06 12:27:30 +02:00
progress_lock.acquire()
if global_cancel:
progress_lock.release()
return
progress_lock.release()
global global_result_dict
global_result_dict = result
################################################################################
# Panels
class BENCHMARK_PT_main(Panel):
bl_label = "Benchmark"
bl_options = {'HIDE_HEADER'}
bl_space_type = 'BENCHMARK'
bl_region_type = 'WINDOW'
def draw_welcome(self, context):
layout = self.layout
split = layout.split(0.65)
split.label()
split = split.split(0.97)
col = split.column()
sub = col.row()
sub.scale_y = 64.0
sub.separator()
2018-08-06 16:42:46 +02:00
compute_device_list_get(None, context)
if len(global_cached_compute_devices) > 1:
sub = col.row()
sub.scale_y = 1.5
sub.prop(context.scene, "compute_device", text="")
sub.separator()
2018-08-06 14:55:07 +02:00
sub = col.row()
sub.scale_y = 2.25
props = sub.operator("benchmark.run", text="QUICK BENCHMARK")
props.run_type = 'QUICK'
col.separator()
sub = col.row()
sub.emboss = 'NONE'
sub.scale_y = 1.5
props = sub.operator("benchmark.run", text="COMPLETE BENCHMARK (approx. 1.h)")
props.run_type = 'COMPLETE'
split.label()
def draw_submit(self, context):
layout = self.layout
split = layout.split(0.65)
split.label()
split = split.split(0.97)
col = split.column()
sub = col.row()
sub.scale_y = 64.0
sub.separator()
sub = col.row()
sub.scale_y = 2.25
sub.operator("benchmark.share", text="SHARE")
sub = col.row()
sub.emboss = 'NONE'
sub.scale_y = 1.5
sub.operator("benchmark.save", text="SAVE")
split.label()
def draw(self, context):
screen_index = 0
global progress_lock
progress_lock.acquire()
if global_result_dict:
screen_index = 2
elif global_result_stats or global_progress_status:
screen_index = 1
progress_lock.release()
if screen_index == 0:
self.draw_welcome(context)
elif screen_index == 2:
self.draw_submit(context)
################################################################################
# Operator
def blender_executable_get():
benchmark_binary_dir = os.path.dirname(bpy.app.binary_path)
system = platform.system()
if system == "Linux":
return os.path.join(benchmark_binary_dir, "blender", "blender")
elif system == "Windows":
return os.path.join(benchmark_binary_dir, "blender", "blender.exe")
else:
raise Exception("Needs implementation")
def scenes_dir_get():
benchmark_binary_dir = os.path.dirname(bpy.app.binary_path)
system = platform.system()
if system == "Linux" or system == "Windows":
return os.path.join(benchmark_binary_dir, "scenes")
else:
raise Exception("Needs implementation")
2018-08-06 14:55:07 +02:00
def configure_script_get():
script_directory = os.path.dirname(os.path.realpath(__file__))
benchmark_script_directory = os.path.dirname(script_directory)
return os.path.join(benchmark_script_directory, "configure.py")
class BENCHMARK_OT_run(bpy.types.Operator):
"Run Blender benchmark"
bl_label = "Run Benchmark"
bl_idname = "benchmark.run"
enum_run_type = (
('QUICK', "QUICK", ""),
('COMPLETE', "COMPLETE", ""),
)
run_type: EnumProperty(
name="Run Type",
description="",
items=enum_run_type,
default='QUICK',
)
benchmark_context = None
thread = None
timer = None
progress_provider = None
logger_provider = None
def setup_sink(self):
self.progress_provider = ProgressProviderSink()
self.logger_provider = LoggerProviderSink()
progress.setProvider(self.progress_provider)
logger.setProvider(self.logger_provider)
def update_status(self, context):
global global_progress_status, global_background_image_path
2018-08-06 12:27:30 +02:00
global global_cancel
progress_lock.acquire()
step = self.progress_provider.current_step
2018-08-06 12:27:30 +02:00
if global_cancel:
global_progress_status = "Cancelling..."
elif step == 'WARM_UP':
global_progress_status = "Rendering warm-up pass..."
elif step == 'RUN':
global current_progress
current_progress = self.progress_provider.current_progress
global_progress_status = "Rendering..."
context.area.tag_redraw()
# Path to currently displayed background image.
current_scene = self.progress_provider.current_scene
if current_scene:
global_background_image_path = os.path.join(
self.benchmark_context.scenes_dir,
current_scene,
current_scene + ".png")
else:
global_background_image_path = ""
# Update per-scene status string
global global_result_stats
global_result_stats = ""
for scene in global_scene_status:
global_result_stats += "{}: {}\n" . format(
scene, global_scene_status[scene])
progress_lock.release()
def done(self, context):
global global_progress_status, global_result_stats, current_progress
2018-08-06 12:27:30 +02:00
global global_result_dict
wm = context.window_manager
wm.event_timer_remove(self.timer)
# Restore all modifications to the benchmark foundation.
progress.restoreDefaultProvider()
logger.restoreDefaultProvider()
# Destroy objects of sinks.
del self.progress_provider
del self.logger_provider
self.progress_provider = None
self.logger_provider = None
# Construct final stats string
2018-08-06 12:27:30 +02:00
if global_cancel:
global_result_dict = None
self.init_state()
2018-08-06 12:27:30 +02:00
else:
global_result_stats = ""
for stat in global_result_dict["stats"]:
if global_result_stats:
global_result_stats += "\n"
if stat["result"] == "OK":
global_result_stats += "{}: {}" . format(stat["scene_name"],
util.humanReadableTimeDifference(
stat["total_render_time"]))
else:
global_result_stats += "{}: {}" . format(stat["scene_name"],
stat["result"])
# TOGO(sergey): Use some more nice picture for the final slide.
global global_background_image_path
global_background_image_path = ""
# Tag for nice redraw
global_progress_status = None
current_progress = 0.0
context.area.tag_redraw()
def modal(self, context, event):
if event.type == 'TIMER':
if self.thread.is_alive():
self.update_status(context)
return {'PASS_THROUGH'}
else:
self.done(context)
return {'FINISHED'}
2018-08-06 12:27:30 +02:00
elif event.type == 'ESC':
self.cancel_request(context)
return {'PASS_THROUGH'}
def invoke(self, context, event):
global global_result_platform, global_progress_status
2018-08-06 12:27:30 +02:00
global global_scene_status, global_cancel
global_cancel = False
global_result_platform = ""
global_progress_status = "Initializing..."
context.area.tag_redraw()
2018-08-06 14:55:07 +02:00
compute_device = context.scene.compute_device
device_type, device_name = compute_device.split(":")
# Before doing anything, make sure we have all sinks set up, so we do
# not miss any progress report.
self.setup_sink()
wm = context.window_manager
ctx = benchmark_context.Context()
ctx.blender = blender_executable_get()
2018-08-06 14:55:07 +02:00
ctx.configure_script = configure_script_get()
if self.run_type == 'QUICK':
ctx.scenes = QUICK_SCENES
else:
ctx.scenes = COMPLETE_SCENES
for scene in ctx.scenes:
global_scene_status[scene] = "N/A"
ctx.scenes_dir = scenes_dir_get()
2018-08-06 14:55:07 +02:00
ctx.device_type = device_type
ctx.device_name = device_name
ctx.single_compute_scene = True
# Set this to True when having multiple GPUs of same name and only
# one of the mis to be enabled. Or when requesting GPU render without
# specifying GPU name.
ctx.device_single = True
# ctx.image_output_dir = "/tmp/"
self.benchmark_context = ctx
# Create thread for the actual benchmark.
self.thread = Thread(target = benchmark_thread,
args = (self.benchmark_context, ))
self.thread.start()
# Create timer to query thread status
self.timer = wm.event_timer_add(0.1, context.window)
# Register self as modal.
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def init_state(self):
2018-08-06 12:27:30 +02:00
global global_result_platform
global global_progress_status
global global_result_stats
global global_result_dict
global global_background_image_path
global global_scene_status
global_result_platform = None
global_progress_status = None
global_result_stats = None
global_result_dict = None
global_background_image_path = ""
global_scene_status = {}
def cancel_request(self, context):
global global_cancel
progress_lock.acquire()
global_cancel = True
context.area.tag_redraw()
progress_lock.release()
if self.progress_provider.process:
if platform.system() == "Windows":
self.progress_provider.process.kill()
else:
import signal
# Corresponds to usual greeting of blender to hit Ctrl-C
# again to abort rendering.
self.progress_provider.process.send_signal(signal.SIGINT)
self.progress_provider.process.send_signal(signal.SIGINT)
2018-08-06 12:27:30 +02:00
def cancel(self, context):
2018-08-06 12:27:30 +02:00
global global_cancel
self.cancel_request(context)
if self.timer:
wm = context.window_manager
wm.event_timer_remove(self.timer)
if self.thread:
self.thread.join()
class BENCHMARK_OT_save(bpy.types.Operator):
bl_idname = "benchmark.save"
bl_label = "Save Benchmark Result"
filepath: bpy.props.StringProperty(
subtype='FILE_PATH',
options={'SKIP_SAVE'},
)
def execute(self, context):
2018-08-06 09:59:52 +02:00
with open(self.filepath, "w") as f:
f.write(json.dumps(global_result_dict, sort_keys=True, indent=2))
return {'FINISHED'}
def invoke(self, context, event):
import os
if not self.filepath:
self.filepath = os.path.join(
os.path.expanduser("~"), "benchmark-result.txt")
wm = context.window_manager
wm.fileselect_add(self)
return {'RUNNING_MODAL'}
class BENCHMARK_OT_share(bpy.types.Operator):
bl_idname = "benchmark.share"
bl_label = "Share Benchmark Result"
def invoke(self, context, event):
return {'FINISHED'}
2018-08-06 14:55:07 +02:00
################################################################################
# Configuration.
def compute_device_list_get(self, context):
global global_cached_system_info
global global_cached_compute_devices
if global_cached_compute_devices:
return global_cached_compute_devices
compute_devices = [('CPU:', "CPU", "")]
if not global_cached_system_info:
ctx = benchmark_context.Context()
ctx.blender = blender_executable_get()
ctx.configure_script = configure_script_get()
global_cached_system_info = system_info_get(ctx)
for device in global_cached_system_info["devices"]:
device_type = device["type"]
if device_type == "CPU":
continue
elif device_type in ("OPENCL", "CUDA"):
device_name = correct_device_name(device["name"])
device_id = "{}:{}" . format(device_type, device["name"])
compute_devices.append((device_id, device_name, ""))
global_cached_compute_devices = compute_devices
return compute_devices
################################################################################
# Tweak User Preferences
userpref = bpy.context.user_preferences
theme = userpref.themes[0]
theme.user_interface.wcol_tool.inner = [0.408, 0.690, 0.129, 1.0]
theme.user_interface.wcol_tool.inner_sel = [0.308, 0.590, 0.029, 1.0]
theme.user_interface.wcol_tool.outline = [0.408, 0.690, 0.129]
2018-08-06 14:55:07 +02:00
userpref.view.use_quit_dialog = False
theme.benchmark.space.back = [0.26, 0.26, 0.26]
style = userpref.ui_styles[0]
style.widget.points = 12
################################################################################
# Registration
classes = (
BENCHMARK_PT_main,
BENCHMARK_OT_run,
BENCHMARK_OT_save,
BENCHMARK_OT_share,
)
2018-08-06 14:55:07 +02:00
Scene.compute_device = EnumProperty(
items=compute_device_list_get,
name="Compute Device",
description="Compute device to run benchmark on")