[Bf-extensions-cvs] [7faa91d] master: Auto Tile Size: Various improvements...

Greg Zaal noreply at git.blender.org
Sat May 2 18:06:05 CEST 2015


Commit: 7faa91ddc07db7a548b54a7ed7856d1990193ac8
Author: Greg Zaal
Date:   Sat May 2 17:26:37 2015 +0200
Branches: master
https://developer.blender.org/rBA7faa91ddc07db7a548b54a7ed7856d1990193ac8

Auto Tile Size: Various improvements...

 - Let the user choose a specific number for the target tile size (not only limited to powers of 2).
 - If there are more render threads available than tiles, reduce the tile size until all threads will be utilized - this means you can do tiny border-renders and still utilize 100% of your CPU.
 - Number of tiles is now displayed in the UI.
 - Minor tweaks to tooltips and function names.

NOTE: The math of calculating the maximum tile size possible while still using all the threads is a bit wonky - it's functional, but not nice. Occasionally when there are many CPU threads (e.g. 16), only 15 of them will be used. Math is not my forte, so some help in this area would be much appreciated.

diff --git a/render_auto_tile_size.py b/render_auto_tile_size.py
index 8ece451..a773281 100644
--- a/render_auto_tile_size.py
+++ b/render_auto_tile_size.py
@@ -20,8 +20,8 @@ bl_info = {
     "name": "Auto Tile Size",
     "description": "Estimate and set the tile size that will render the fastest",
     "author": "Greg Zaal",
-    "version": (2, 7),
-    "blender": (2, 72, 0),
+    "version": (3, 0),
+    "blender": (2, 74, 0),
     "location": "Render Settings > Performance",
     "warning": "",
     "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py/Scripts/Render/Auto_Tile_Size",
@@ -31,7 +31,7 @@ bl_info = {

 import bpy
 from bpy.app.handlers import persistent
-from math import ceil, floor
+from math import ceil, floor, sqrt

 SUPPORTED_RENDER_ENGINES = {'CYCLES', 'BLENDER_RENDER'}
@@ -55,16 +55,14 @@ class AutoTileSizeSettings(bpy.types.PropertyGroup):
         name="Target GPU Tile Size",
         items=TILE_SIZES,
         default='256',
-        description="Square dimentions of tiles",
+        description="Square dimentions of tiles for GPU rendering",
         update=_update_tile_size)
-
     cpu_choice = bpy.props.EnumProperty(
         name="Target CPU Tile Size",
         items=TILE_SIZES,
         default='32',
-        description="Square dimentions of tiles",
+        description="Square dimentions of tiles for CPU rendering",
         update=_update_tile_size)
-
     bi_choice = bpy.props.EnumProperty(
         name="Target CPU Tile Size",
         items=TILE_SIZES,
@@ -72,6 +70,37 @@ class AutoTileSizeSettings(bpy.types.PropertyGroup):
         description="Square dimentions of tiles",
         update=_update_tile_size)

+    gpu_custom = bpy.props.IntProperty(
+        name="Target Size",
+        default=256,
+        min=8,  # same as blender's own limits
+        max=65536,
+        description="Custom target tile size for GPU rendering",
+        update=_update_tile_size)
+    cpu_custom = bpy.props.IntProperty(
+        name="Target Size",
+        default=32,
+        min=8,  # same as blender's own limits
+        max=65536,
+        description="Custom target tile size for CPU rendering",
+        update=_update_tile_size)
+    bi_custom = bpy.props.IntProperty(
+        name="Target Size",
+        default=64,
+        min=8,  # same as blender's own limits
+        max=65536,
+        description="Custom target tile size",
+        update=_update_tile_size)
+
+    target_type = bpy.props.EnumProperty(
+        name="Target tile size",
+        items=(
+            ('po2', "Po2", "A choice between powers of 2 (16, 32, 64...)"),
+            ('custom', "Custom", "Choose any number as the tile size target")),
+        default='po2',
+        description="Method of choosing the target tile size",
+        update=_update_tile_size)
+
     use_optimal = bpy.props.BoolProperty(
         name="Optimal Tiles",
         default=True,
@@ -89,9 +118,16 @@ class AutoTileSizeSettings(bpy.types.PropertyGroup):
         default=False,
         description="Show extra options for more control over the calculated tile size")

+    thread_error_correct = bpy.props.BoolProperty(
+        name="Fix",
+        default=True,
+        description="Reduce the tile size so that all your available threads are used.",
+        update=_update_tile_size)
+
     # Internally used props (not for GUI)
     first_run = bpy.props.BoolProperty(default=True, options={'HIDDEN'})
     threads_error = bpy.props.BoolProperty(options={'HIDDEN'})
+    num_tiles = bpy.props.IntVectorProperty(default=(0, 0), size=2, options={'HIDDEN'})
     prev_choice = bpy.props.StringProperty(default='', options={'HIDDEN'})
     prev_engine = bpy.props.StringProperty(default='', options={'HIDDEN'})
     prev_device = bpy.props.StringProperty(default='', options={'HIDDEN'})
@@ -109,16 +145,17 @@ def ats_poll(context):
     return True

-def ats_get_engine_is_gpu(engine, device, userpref):
+def engine_is_gpu(engine, device, userpref):
     return engine == 'CYCLES' and device == 'GPU' and userpref.system.compute_device_type != 'NONE'

-def ats_get_tilesize_prop(engine, device, userpref):
-    if ats_get_engine_is_gpu(engine, device, userpref):
-        return "gpu_choice"
+def get_tilesize_prop(engine, device, userpref):
+    target_type = "_choice" if bpy.context.scene.ats_settings.target_type == 'po2' else "_custom"
+    if engine_is_gpu(engine, device, userpref):
+        return ("gpu" + target_type)
     elif engine == 'CYCLES':
-        return "cpu_choice"
-    return "bi_choice"
+        return ("cpu" + target_type)
+    return ("bi" + target_type)

 @persistent
@@ -137,9 +174,9 @@ def on_scene_update(scene):
     # scene.cycles might not always exist (Cycles is an addon)...
     device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
     border = render.use_border
-    threads = render.threads
+    threads = get_threads(context, device)

-    choice = getattr(settings, ats_get_tilesize_prop(engine, device, userpref))
+    choice = getattr(settings, get_tilesize_prop(engine, device, userpref))

     res = get_actual_res(render)
     actual_ts = (render.tile_x, render.tile_y)
@@ -150,7 +187,7 @@ def on_scene_update(scene):
                  device != settings.prev_device or
                  border != settings.prev_border or
                  threads != settings.prev_threads or
-                 choice != settings.prev_choice or
+                 str(choice) != settings.prev_choice or
                  res != settings.prev_res[:] or
                  border_res != settings.prev_border_res[:] or
                  actual_ts != settings.prev_actual_tile_size[:])
@@ -163,6 +200,61 @@ def get_actual_res(render):
     # floor is implicitly done by int conversion...
     return (int(render.resolution_x * rend_percent), int(render.resolution_y * rend_percent))

+def get_threads(context, device):
+    render = context.scene.render
+    engine = render.engine
+    userpref = context.user_preferences
+
+    if engine_is_gpu(engine, device, userpref):
+        gpu_device_str = userpref.system.compute_device
+        if 'MULTI' in gpu_device_str:
+            threads = int(gpu_device_str.split('_')[-1])
+        else:
+            threads = 1
+    else:
+        threads = render.threads
+
+    return threads
+
+def max_tile_size(threads, xres, yres):
+    ''' Give the largest tile size that will still use all threads '''
+
+    render_area = xres * yres
+    tile_area = render_area / threads
+    tile_length = sqrt(tile_area)
+
+    # lists: num x tiles, num y tiles, squareness, total tiles
+    perfect_attempts = []  # attempts with correct number of tiles
+    attempts = []  # all attempts, even if incorrect number of tiles
+
+    axes = [xres, yres]
+    funcs = [floor, ceil]
+
+    for axis in axes:
+        sec_axis = yres if axis == xres else xres
+        for func in funcs:
+            primary = func(axis / tile_length)
+            if primary > 0:
+                secondary = threads / primary
+                ts_p = axis/primary
+                ts_s = sec_axis/secondary
+                squareness = max(ts_p, ts_s) - min(ts_p, ts_s)
+                attempt = [primary if axis == xres else secondary, primary if axis != xres else secondary, squareness, primary * secondary]
+                if attempt not in attempts:
+                    attempts.append(attempt)
+                    if secondary.is_integer():  # will only be an integer if there are the right number of tiles
+                        perfect_attempts.append(attempt)
+
+    if perfect_attempts:  # prefer to use attempt that has exactly the right number of tiles
+        attempts = perfect_attempts
+
+    attempt = sorted(attempts, key=lambda k: k[2])[0]  # pick set with most square tiles
+    numtiles_x = round(attempt[0])
+    numtiles_y = round(attempt[1])
+    tile_x = ceil(xres / numtiles_x)
+    tile_y = ceil(yres / numtiles_y)
+
+    return (tile_x, tile_y)

 def do_set_tile_size(context):
     if not ats_poll(context):
@@ -176,7 +268,6 @@ def do_set_tile_size(context):
     engine = render.engine
     device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
     border = render.use_border
-    threads = render.threads

     realxres, realyres = xres, yres = res = get_actual_res(scene.render)

@@ -184,11 +275,12 @@ def do_set_tile_size(context):
         xres = round(xres * (render.border_max_x - render.border_min_x))
         yres = round(yres * (render.border_max_y - render.border_min_y))

-    choice = getattr(settings, ats_get_tilesize_prop(engine, device, userpref))
+    choice = getattr(settings, get_tilesize_prop(engine, device, userpref))
     target = int(choice)

     numtiles_x = ceil(xres / target)
     numtiles_y = ceil(yres / target)
+    settings.num_tiles = (numtiles_x, numtiles_y)
     if settings.use_optimal:
         tile_x = ceil(xres / numtiles_x)
         tile_y = ceil(yres / numtiles_y)
@@ -198,20 +290,25 @@ def do_set_tile_size(context):

     print("Tile size: %dx%d (%dx%d tiles)" % (tile_x, tile_y, ceil(xres / tile_x), ceil(yres / tile_y)))

-    render.tile_x = tile_x
-    render.tile_y = tile_y
-
     # Detect if there are fewer tiles than available threads
-    if ((numtiles_x * numtiles_y) < threads) and not ats_get_engine_is_gpu(engine, device, userpref):
+    threads = get_threads(context, device)
+    if ((numtiles_x * numtiles_y) < threads):
         settings.threads_error = True
+        if settings.thread_error_correct:
+            tile_x, tile_y = max_tile_size(threads, xres, yres)
+            settings.num_tiles = (ceil(xres/tile_x), ceil(yres/tile_y))
     else:
         settings.threads_error = False

+    render.tile_x = tile_x
+    render.tile_y = tile_y
+
+
     settings.prev_engine = engine
     settings.prev_device = device
     settings.prev_border = border
     settings.prev_threads = threads
-    settings.prev_choice = choice
+    settings.prev_choice = str(choice)
     settings.prev_res = res
     settings.prev_border_res = (render.border_min_x, render.border_min_y, render.border_max_x, render.border_max_y)
     settings.prev_actual_tile_size = (tile_x, tile_y)
@@ -252,16 +349,21 @@ def ui_layout(engine, layout, context):
     row.prop(settings, "is_enabled", toggle=True)
     row.prop(settings, "use_advanced_ui", toggle=True, text="", icon='PREFERENCES')

-    sub = col.column(align=True)
+    sub = col.column(align=False)
     sub.enabled = settings.is_enabled

     if settings.use_advanced_ui:
-        sub.label("Target tile size:")
+        row = sub.row(align=True)
+        row.label("Target tile size:")
+        row.separator()
+        row.prop(settings, "target_type", expand=True)

         row = sub.row(align=True)
-        row.prop(settings, ats_get_tilesize_prop(engine, device, userpref), expand=True)
+        row.prop(settings, get_tilesize_prop(engine, device, userpref), expand=True)
         sub.prop(settings, "use_optimal", text="Calculate Optimal Size")

+        sub.label("Number of tiles: %s x %s (Total: %s)" % (settings.num_tiles[0], settings.num_tiles[1], settings.num_tiles[0] * settings.num_tiles[1]))
+
     if settings.first_run:
         sub = layout.column(align=True)
         sub.operator("render.autotilesize_set", text="First-render fix", icon='ERROR')
@@ -272,9 +374,11 @@ def ui_layout(engine, layout, context):
     if (render.tile_x / render.tile_y > 2) or (render.tile_x / render.tile_y < 0.5):  # if not very square tile
         sub.label(text="Warning: Tile size is not very square", icon='ERROR')
         sub.label(text="    Try a slightly different resolution")
-        sub.label(text="    or choose \"Exact\" above")
     if settings.threads_error:
-        sub.label(text="Warning: Fewer tiles than render threads", icon='ERROR')
+        row = sub.row(align=True)
+        row.alignment = 'CENTER'
+        row.label(text="Warning: Fewer tiles than threads.", icon='ERROR')
+        row.prop(settings, 'thread_error_correct')

 def menu_func_cycles(self, context):

===================================================================

M	render_auto_tile_size.py

===================================================================

diff --git a/render_auto_tile_size.py b/render_auto_tile_size.py
index 8ece451..a773281 100644
--- a/render_auto_tile_size.py
+++ b/render_auto_tile_size.py
@@ -20,8 +20,8 @@ bl_info = {
     "name": "Auto Tile Size",
     "description": "Estimate and set the tile size that will render the fastest",
     "author": "Greg Zaal",
-    "version": (2, 7),
-    "blender": (2, 72, 0),
+    "version": (3, 0),
+    "blender": (2, 74, 0),
     "location": "Render Settings > Performance",
     "warning": "",
     "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py/Scripts/Render/Auto_Tile_Size",
@@ -31,7 +31,7 @@ bl_info = {
 
 import bpy
 from bpy.app.handlers import persistent
-from math import ceil, floor
+from math import ceil, floor, sqrt
 
 
 SUPPORTED_RENDER_ENGINES = {'CYCLES', 'BLENDER_RENDER'}
@@ -55,16 +55,14 @@ class AutoTileSizeSettings(bpy.types.PropertyGroup):
         name="Target GPU Tile Size",
         items=TILE_SIZES,
         default='256',
-        description="Square dimentions of tiles",
+        description="Square dimentions of tiles for GPU rendering",
         update=_update_tile_size)
-
     cpu_choice = bpy.props.EnumProperty(
         name="Target CPU Tile Size",
         items=TILE_SIZES,
         default='32',
-        description="Square dimentions of tiles",
+        description="Square dimentions of tiles for CPU rendering",
         update=_update_tile_size)
-
     bi_choice = bpy.props.EnumProperty(
         name="Target CPU Tile Size",
         items=TILE_SIZES,
@@ -72,6 +70,37 @@ class AutoTileSizeSettings(bpy.types.PropertyGroup):
         description="Square dimentions of tiles",
         update=_update_tile_size)
 
+    gpu_custom = bpy.props.IntProperty(
+        name="Target Size",
+        default=256,
+        min=8,  # same as blender's own limits
+        max=65536,
+        description="Custom target tile size for GPU rendering",
+        update=_update_tile_size)
+    cpu_custom = bpy.props.IntProperty(
+        name="Target Size",
+        default=32,
+        min=8,  # same as blender's own limits
+        max=65536,
+        description="Custom target tile size for CPU rendering",
+        update=_update_tile_size)
+    bi_custom = bpy.props.IntProperty(
+        name="Target Size",
+        default=64,
+        min=8,  # same as blender's own limits
+        max=65536,
+        description="Custom target tile size",
+        update=_update_tile_size)
+
+    target_type = bpy.props.EnumProperty(
+        name="Target tile size",
+        items=(
+            ('po2', "Po2", "A choice between powers of 2 (16, 32, 64...)"),
+            ('custom', "Custom", "Choose any number as the tile size target")),
+        default='po2',
+        description="Method of choosing the target tile size",
+        update=_update_tile_size)
+
     use_optimal = bpy.props.BoolProperty(
         name="Optimal Tiles",
         default=True,
@@ -89,9 +118,16 @@ class AutoTileSizeSettings(bpy.types.PropertyGroup):
         default=False,
         description="Show extra options for more control over the calculated tile size")
 
+    thread_error_correct = bpy.props.BoolProperty(
+        name="Fix",
+        default=True,
+        description="Reduce the tile size so that all your available threads are used.",
+        update=_update_tile_size)
+
     # Internally used props (not for GUI)
     first_run = bpy.props.BoolProperty(default=True, options={'HIDDEN'})
     threads_error = bpy.props.BoolProperty(options={'HIDDEN'})
+    num_tiles = bpy.props.IntVectorProperty(default=(0, 0), size=2, options={'HIDDEN'})
     prev_choice = bpy.props.StringProperty(default='', options={'HIDDEN'})
     prev_engine = bpy.props.StringProperty(default='', options={'HIDDEN'})
     prev_device = bpy.props.StringProperty(default='', options={'HIDDEN'})
@@ -109,16 +145,17 @@ def ats_poll(context):
     return True
 
 
-def ats_get_engine_is_gpu(engine, device, userpref):
+def engine_is_gpu(engine, device, userpref):
     return engine == 'CYCLES' and device == 'GPU' and userpref.system.compute_device_type != 'NONE'
 
 
-def ats_get_tilesize_prop(engine, device, userpref):
-    if ats_get_engine_is_gpu(engine, device, userpref):
-        return "gpu_choice"
+def get_tilesize_prop(engine, device, userpref):
+    target_type = "_choice" if bpy.context.scene.ats_settings.target_type == 'po2' else "_custom"
+    if engine_is_gpu(engine, device, userpref):
+        return ("gpu" + target_type)
     elif engine == 'CYCLES':
-        return "cpu_choice"
-    return "bi_choice"
+        return ("cpu" + target_type)
+    return ("bi" + target_type)
 
 
 @persistent
@@ -137,9 +174,9 @@ def on_scene_update(scene):
     # scene.cycles might not always exist (Cycles is an addon)...
     device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
     border = render.use_border
-    threads = render.threads
+    threads = get_threads(context, device)
 
-    choice = getattr(settings, ats_get_tilesize_prop(engine, device, userpref))
+    choice = getattr(settings, get_tilesize_prop(engine, device, userpref))
 
     res = get_actual_res(render)
     actual_ts = (render.tile_x, render.tile_y)
@@ -150,7 +187,7 @@ def on_scene_update(scene):
                  device != settings.prev_device or
                  border != settings.prev_border or
                  threads != settings.prev_threads or
-                 choice != settings.prev_choice or
+                 str(choice) != settings.prev_choice or
                  res != settings.prev_res[:] or
                  border_res != settings.prev_border_res[:] or
                  actual_ts != settings.prev_actual_tile_size[:])
@@ -163,6 +200,61 @@ def get_actual_res(render):
     # floor is implicitly done by int conversion...
     return (int(render.resolution_x * rend_percent), int(render.resolution_y * rend_percent))
 
+def get_threads(context, device):
+    render = context.scene.render
+    engine = render.engine
+    userpref = context.user_preferences
+
+    if engine_is_gpu(engine, device, userpref):
+        gpu_device_str = userpref.system.compute_device
+        if 'MULTI' in gpu_device_str:
+            threads = int(gpu_device_str.split('_')[-1])
+        else:
+            threads = 1
+    else:
+        threads = render.threads
+
+    return threads
+
+def max_tile_size(threads, xres, yres):
+    ''' Give the largest tile size that will still use all threads '''
+
+    render_area = xres * yres
+    tile_area = render_area / threads
+    tile_length = sqrt(tile_area)
+
+    # lists: num x tiles, num y tiles, squareness, total tiles
+    perfect_attempts = []  # attempts with correct number of tiles
+    attempts = []  # all attempts, even if incorrect number of tiles
+
+    axes = [xres, yres]
+    funcs = [floor, ceil]
+
+    for axis in axes:
+        sec_axis = yres if axis == xres else xres
+        for func in funcs:
+            primary = func(axis / tile_length)
+            if primary > 0:
+                secondary = threads / primary
+                ts_p = axis/primary
+                ts_s = sec_axis/secondary
+                squareness = max(ts_p, ts_s) - min(ts_p, ts_s)
+                attempt = [primary if axis == xres else secondary, primary if axis != xres else secondary, squareness, primary * secondary]
+                if attempt not in attempts:
+                    attempts.append(attempt)
+                    if secondary.is_integer():  # will only be an integer if there are the right number of tiles
+                        perfect_attempts.append(attempt)
+
+    if perfect_attempts:  # prefer to use attempt that has exactly the right number of tiles
+        attempts = perfect_attempts
+
+    attempt = sorted(attempts, key=lambda k: k[2])[0]  # pick set with most square tiles
+    numtiles_x = round(attempt[0])
+    numtiles_y = round(attempt[1])
+    tile_x = ceil(xres / numtiles_x)
+    tile_y = ceil(yres / numtiles_y)
+
+    return (tile_x, tile_y)
 
 def do_set_tile_size(context):
     if not ats_poll(context):
@@ -176,7 +268,6 @@ def do_set_tile_size(context):
     engine = render.engine
     device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
     border = render.use_border
-    threads = render.threads
 
     realxres, realyres = xres, yres = res = get_actual_res(scene.render)
 
@@ -184,11 +275,12 @@ def do_set_tile_size(context):
         xres = round(xres * (render.border_max_x - render.border_min_x))
         yres = round(yres * (render.border_max_y - render.border_min_y))
 
-    choice = getattr(settings, ats_get_tilesize_prop(engine, device, userpref))
+    choice = getattr(settings, get_tilesize_prop(engine, device, userpref))
     target = int(choice)
 
     numtiles_x = ceil(xres / target)
     numtiles_y = ceil(yres / target)
+    settings.num_tiles = (numtiles_x, numtiles_y)
     if settings.use_optimal:
         tile_x = ceil(xres / numtiles_x)
         tile_y = ceil(yres / numtiles_y)
@@ -198,20 +290,25 @@ def do_set_tile_size(context):
 
     print("Tile size: %dx%d (%dx%d tiles)" % (tile_x, tile_y, ceil(xres / tile_x), ceil(yres / tile_y)))
 
-    render.tile_x = tile_x
-    render.tile_y = tile_y
-
     # Detect if there are fewer tiles than available threads
-    if ((numtiles_x * numtiles_y) < threads) and not ats_get_engine_is_gpu(engine, device, userpref):
+    threads = get_threads(context, device)
+    if ((numtiles_x * numtiles_y) < threads):
         settings.threads_error = True
+        if settings.thread_error_correct:
+            tile_x, tile_y = max_tile_size(threads, xres, yres)
+            settings.num_tiles = (ceil(xres/tile_x), ceil(yres/tile_y))
     else:
         settings.threads_error = False
 
+    render.tile_x = tile_x
+    render.tile_y = tile_y
+    
+
     settings.prev_engine = engine
     settings.prev_device = device
     settings.prev_border = border
     settings.prev_threads = threads
-    settings.prev_choice = choice
+    settings.prev_choice = str(choice)
     settings.prev_res = res
     settings.prev_border_res = (render.border_min_x, render.border_min_y, render.border_max_x, render.border_max_y)
     settings.prev_actual_tile_size = (tile_x, tile_y)
@

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list