From 4f28fe782db71979cca35fa7f74a96d3b6b77f25 Mon Sep 17 00:00:00 2001
From: Bastien Montagne <montagne29@wanadoo.fr>
Date: Fri, 26 Jun 2015 21:52:38 +0200
Subject: [PATCH] Fix typo (dimention > dimension), and cleanup the ugly
 windows EOL (grrrrr).

---
 render_auto_tile_size.py | 857 +++++++++++++++++++--------------------
 1 file changed, 428 insertions(+), 429 deletions(-)

diff --git a/render_auto_tile_size.py b/render_auto_tile_size.py
index 9941dfd93..8aef71dcc 100644
--- a/render_auto_tile_size.py
+++ b/render_auto_tile_size.py
@@ -1,429 +1,428 @@
-# BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# END GPL LICENSE BLOCK #####
-
-bl_info = {
-    "name": "Auto Tile Size",
-    "description": "Estimate and set the tile size that will render the fastest",
-    "author": "Greg Zaal",
-    "version": (3, 1),
-    "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",
-    "category": "Render",
-}
-
-
-import bpy
-from bpy.app.handlers import persistent
-from math import ceil, floor, sqrt
-
-
-SUPPORTED_RENDER_ENGINES = {'CYCLES', 'BLENDER_RENDER'}
-TILE_SIZES = (
-    ('16', "16", "16 x 16"),
-    ('32', "32", "32 x 32"),
-    ('64', "64", "64 x 64"),
-    ('128', "128", "128 x 128"),
-    ('256', "256", "256 x 256"),
-    ('512', "512", "512 x 512"),
-    ('1024', "1024", "1024 x 1024"),
-)
-
-
-def _update_tile_size(self, context):
-    do_set_tile_size(context)
-
-
-class AutoTileSizeSettings(bpy.types.PropertyGroup):
-    gpu_choice = bpy.props.EnumProperty(
-        name="Target GPU Tile Size",
-        items=TILE_SIZES,
-        default='256',
-        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 for CPU rendering",
-        update=_update_tile_size)
-    bi_choice = bpy.props.EnumProperty(
-        name="Target CPU Tile Size",
-        items=TILE_SIZES,
-        default='64',
-        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,
-        description="Try to find a similar tile size for best performance, instead of using exact selected one",
-        update=_update_tile_size)
-
-    is_enabled = bpy.props.BoolProperty(
-        name="Auto Tile Size",
-        default=True,
-        description="Calculate the best tile size based on factors of the render size and the chosen target",
-        update=_update_tile_size)
-
-    use_advanced_ui = bpy.props.BoolProperty(
-        name="Advanced Settings",
-        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'})
-    prev_res = bpy.props.IntVectorProperty(default=(0, 0), size=2, options={'HIDDEN'})
-    prev_border = bpy.props.BoolProperty(default=False, options={'HIDDEN'})
-    prev_border_res = bpy.props.FloatVectorProperty(default=(0, 0, 0, 0), size=4, options={'HIDDEN'})
-    prev_actual_tile_size = bpy.props.IntVectorProperty(default=(0, 0), size=2, options={'HIDDEN'})
-    prev_threads = bpy.props.IntProperty(default=0, options={'HIDDEN'})
-
-
-def ats_poll(context):
-    scene = context.scene
-    if scene.render.engine not in SUPPORTED_RENDER_ENGINES or not scene.ats_settings.is_enabled:
-        return False
-    return True
-
-
-def engine_is_gpu(engine, device, userpref):
-    return engine == 'CYCLES' and device == 'GPU' and userpref.system.compute_device_type != 'NONE'
-
-
-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" + target_type)
-    return ("bi" + target_type)
-
-
-@persistent
-def on_scene_update(scene):
-    context = bpy.context
-
-    if not ats_poll(context):
-        return
-
-    userpref = context.user_preferences
-
-    settings = scene.ats_settings
-    render = scene.render
-    engine = render.engine
-
-    # 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 = get_threads(context, device)
-
-    choice = getattr(settings, get_tilesize_prop(engine, device, userpref))
-
-    res = get_actual_res(render)
-    actual_ts = (render.tile_x, render.tile_y)
-    border_res = (render.border_min_x, render.border_min_y, render.border_max_x, render.border_max_y)
-
-    # detect relevant changes in scene
-    do_change = (engine != settings.prev_engine or
-                 device != settings.prev_device or
-                 border != settings.prev_border or
-                 threads != settings.prev_threads 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[:])
-    if do_change:
-        do_set_tile_size(context)
-
-
-def get_actual_res(render):
-    rend_percent = render.resolution_percentage * 0.01
-    # 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):
-        return False
-
-    scene = context.scene
-    userpref = context.user_preferences
-
-    settings = scene.ats_settings
-    render = scene.render
-    engine = render.engine
-    device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
-    border = render.use_border
-
-    realxres, realyres = xres, yres = res = get_actual_res(scene.render)
-
-    if border:
-        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, 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)
-    else:
-        tile_x = target
-        tile_y = target
-
-    # Print tile size (for debug purposes)
-    # print("Tile size: %dx%d (%dx%d tiles)" % (tile_x, tile_y, ceil(xres / tile_x), ceil(yres / tile_y)))
-
-    # Detect if there are fewer tiles than available threads
-    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
-
-    # Make sure tile sizes are within the internal limit
-    tile_x = max(8, tile_x)
-    tile_y = max(8, tile_y)
-    tile_x = min(65536, tile_x)
-    tile_y = min(65536, tile_y)
-
-    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 = 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)
-    settings.first_run = False
-
-    return True
-
-
-class SetTileSize(bpy.types.Operator):
-    """The first render may not obey the tile-size set here"""
-    bl_idname = "render.autotilesize_set"
-    bl_label = "Set"
-
-    @classmethod
-    def poll(clss, context):
-        return ats_poll(context)
-
-    def execute(self, context):
-        if do_set_tile_size(context):
-            return {'FINISHED'}
-        return {'CANCELLED'}
-
-
-# ##### INTERFACE #####
-
-def ui_layout(engine, layout, context):
-    scene = context.scene
-    userpref = context.user_preferences
-
-    settings = scene.ats_settings
-    render = scene.render
-    engine = render.engine
-    device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
-
-    col = layout.column(align=True)
-    sub = col.column(align=True)
-    row = sub.row(align=True)
-    row.prop(settings, "is_enabled", toggle=True)
-    row.prop(settings, "use_advanced_ui", toggle=True, text="", icon='PREFERENCES')
-
-    sub = col.column(align=False)
-    sub.enabled = settings.is_enabled
-
-    if settings.use_advanced_ui:
-        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, 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')
-    elif settings.prev_device != device:
-        sub = layout.column(align=True)
-        sub.operator("render.autotilesize_set", text="Device changed - fix", icon='ERROR')
-
-    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")
-    if settings.threads_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):
-    ui_layout('CYCLES', self.layout, context)
-
-
-def menu_func_bi(self, context):
-    ui_layout('BLENDER_RENDER', self.layout, context)
-
-
-# ##### REGISTRATION #####
-
-def register():
-    bpy.utils.register_module(__name__)
-
-    bpy.types.Scene.ats_settings = bpy.props.PointerProperty(type=AutoTileSizeSettings)
-
-    # Note, the Cycles addon must be registered first, otherwise this panel doesn't exist - better be safe here!
-    cycles_panel = getattr(bpy.types, "CyclesRender_PT_performance", None)
-    if cycles_panel is not None:
-        cycles_panel.append(menu_func_cycles)
-
-    bpy.types.RENDER_PT_performance.append(menu_func_bi)
-    bpy.app.handlers.scene_update_post.append(on_scene_update)
-
-
-def unregister():
-    bpy.app.handlers.scene_update_post.remove(on_scene_update)
-    bpy.types.RENDER_PT_performance.remove(menu_func_bi)
-
-    cycles_panel = getattr(bpy.types, "CyclesRender_PT_performance", None)
-    if cycles_panel is not None:
-        cycles_panel.remove(menu_func_cycles)
-
-    del bpy.types.Scene.ats_settings
-
-    bpy.utils.unregister_module(__name__)
-
-
-if __name__ == "__main__":
-    register()
+# BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# END GPL LICENSE BLOCK #####
+
+bl_info = {
+    "name": "Auto Tile Size",
+    "description": "Estimate and set the tile size that will render the fastest",
+    "author": "Greg Zaal",
+    "version": (3, 1, 1),
+    "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",
+    "category": "Render",
+}
+
+
+import bpy
+from bpy.app.handlers import persistent
+from math import ceil, floor, sqrt
+
+
+SUPPORTED_RENDER_ENGINES = {'CYCLES', 'BLENDER_RENDER'}
+TILE_SIZES = (
+    ('16', "16", "16 x 16"),
+    ('32', "32", "32 x 32"),
+    ('64', "64", "64 x 64"),
+    ('128', "128", "128 x 128"),
+    ('256', "256", "256 x 256"),
+    ('512', "512", "512 x 512"),
+    ('1024', "1024", "1024 x 1024"),
+)
+
+
+def _update_tile_size(self, context):
+    do_set_tile_size(context)
+
+
+class AutoTileSizeSettings(bpy.types.PropertyGroup):
+    gpu_choice = bpy.props.EnumProperty(
+        name="Target GPU Tile Size",
+        items=TILE_SIZES,
+        default='256',
+        description="Square dimensions 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 dimensions of tiles for CPU rendering",
+        update=_update_tile_size)
+    bi_choice = bpy.props.EnumProperty(
+        name="Target CPU Tile Size",
+        items=TILE_SIZES,
+        default='64',
+        description="Square dimensions 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,
+        description="Try to find a similar tile size for best performance, instead of using exact selected one",
+        update=_update_tile_size)
+
+    is_enabled = bpy.props.BoolProperty(
+        name="Auto Tile Size",
+        default=True,
+        description="Calculate the best tile size based on factors of the render size and the chosen target",
+        update=_update_tile_size)
+
+    use_advanced_ui = bpy.props.BoolProperty(
+        name="Advanced Settings",
+        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'})
+    prev_res = bpy.props.IntVectorProperty(default=(0, 0), size=2, options={'HIDDEN'})
+    prev_border = bpy.props.BoolProperty(default=False, options={'HIDDEN'})
+    prev_border_res = bpy.props.FloatVectorProperty(default=(0, 0, 0, 0), size=4, options={'HIDDEN'})
+    prev_actual_tile_size = bpy.props.IntVectorProperty(default=(0, 0), size=2, options={'HIDDEN'})
+    prev_threads = bpy.props.IntProperty(default=0, options={'HIDDEN'})
+
+
+def ats_poll(context):
+    scene = context.scene
+    if scene.render.engine not in SUPPORTED_RENDER_ENGINES or not scene.ats_settings.is_enabled:
+        return False
+    return True
+
+
+def engine_is_gpu(engine, device, userpref):
+    return engine == 'CYCLES' and device == 'GPU' and userpref.system.compute_device_type != 'NONE'
+
+
+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" + target_type)
+    return ("bi" + target_type)
+
+
+@persistent
+def on_scene_update(scene):
+    context = bpy.context
+
+    if not ats_poll(context):
+        return
+
+    userpref = context.user_preferences
+
+    settings = scene.ats_settings
+    render = scene.render
+    engine = render.engine
+
+    # 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 = get_threads(context, device)
+
+    choice = getattr(settings, get_tilesize_prop(engine, device, userpref))
+
+    res = get_actual_res(render)
+    actual_ts = (render.tile_x, render.tile_y)
+    border_res = (render.border_min_x, render.border_min_y, render.border_max_x, render.border_max_y)
+
+    # detect relevant changes in scene
+    do_change = (engine != settings.prev_engine or
+                 device != settings.prev_device or
+                 border != settings.prev_border or
+                 threads != settings.prev_threads 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[:])
+    if do_change:
+        do_set_tile_size(context)
+
+
+def get_actual_res(render):
+    rend_percent = render.resolution_percentage * 0.01
+    # 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):
+        return False
+
+    scene = context.scene
+    userpref = context.user_preferences
+
+    settings = scene.ats_settings
+    render = scene.render
+    engine = render.engine
+    device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
+    border = render.use_border
+
+    realxres, realyres = xres, yres = res = get_actual_res(scene.render)
+
+    if border:
+        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, 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)
+    else:
+        tile_x = target
+        tile_y = target
+
+    # Print tile size (for debug purposes)
+    # print("Tile size: %dx%d (%dx%d tiles)" % (tile_x, tile_y, ceil(xres / tile_x), ceil(yres / tile_y)))
+
+    # Detect if there are fewer tiles than available threads
+    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
+
+    # Make sure tile sizes are within the internal limit
+    tile_x = max(8, tile_x)
+    tile_y = max(8, tile_y)
+    tile_x = min(65536, tile_x)
+    tile_y = min(65536, tile_y)
+
+    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 = 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)
+    settings.first_run = False
+
+    return True
+
+
+class SetTileSize(bpy.types.Operator):
+    """The first render may not obey the tile-size set here"""
+    bl_idname = "render.autotilesize_set"
+    bl_label = "Set"
+
+    @classmethod
+    def poll(clss, context):
+        return ats_poll(context)
+
+    def execute(self, context):
+        if do_set_tile_size(context):
+            return {'FINISHED'}
+        return {'CANCELLED'}
+
+
+# ##### INTERFACE #####
+
+def ui_layout(engine, layout, context):
+    scene = context.scene
+    userpref = context.user_preferences
+
+    settings = scene.ats_settings
+    render = scene.render
+    engine = render.engine
+    device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
+
+    col = layout.column(align=True)
+    sub = col.column(align=True)
+    row = sub.row(align=True)
+    row.prop(settings, "is_enabled", toggle=True)
+    row.prop(settings, "use_advanced_ui", toggle=True, text="", icon='PREFERENCES')
+
+    sub = col.column(align=False)
+    sub.enabled = settings.is_enabled
+
+    if settings.use_advanced_ui:
+        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, 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')
+    elif settings.prev_device != device:
+        sub = layout.column(align=True)
+        sub.operator("render.autotilesize_set", text="Device changed - fix", icon='ERROR')
+
+    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")
+    if settings.threads_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):
+    ui_layout('CYCLES', self.layout, context)
+
+
+def menu_func_bi(self, context):
+    ui_layout('BLENDER_RENDER', self.layout, context)
+
+
+# ##### REGISTRATION #####
+
+def register():
+    bpy.utils.register_module(__name__)
+
+    bpy.types.Scene.ats_settings = bpy.props.PointerProperty(type=AutoTileSizeSettings)
+
+    # Note, the Cycles addon must be registered first, otherwise this panel doesn't exist - better be safe here!
+    cycles_panel = getattr(bpy.types, "CyclesRender_PT_performance", None)
+    if cycles_panel is not None:
+        cycles_panel.append(menu_func_cycles)
+
+    bpy.types.RENDER_PT_performance.append(menu_func_bi)
+    bpy.app.handlers.scene_update_post.append(on_scene_update)
+
+
+def unregister():
+    bpy.app.handlers.scene_update_post.remove(on_scene_update)
+    bpy.types.RENDER_PT_performance.remove(menu_func_bi)
+
+    cycles_panel = getattr(bpy.types, "CyclesRender_PT_performance", None)
+    if cycles_panel is not None:
+        cycles_panel.remove(menu_func_cycles)
+
+    del bpy.types.Scene.ats_settings
+
+    bpy.utils.unregister_module(__name__)
+
+
+if __name__ == "__main__":
+    register()
-- 
GitLab