Option to lock the interface while rendering
authorSergey Sharybin <sergey.vfx@gmail.com>
Wed, 29 Jan 2014 10:07:14 +0000 (16:07 +0600)
committerSergey Sharybin <sergey.vfx@gmail.com>
Wed, 29 Jan 2014 10:07:14 +0000 (16:07 +0600)
Added function called WM_set_locked_interface which does
two things:

- Prevents event queue from being handled, so no operators
  (see below) or values are even possible to run or change.
  This prevents any kind of "destructive" action performed
  from user while rendering.

- Locks interface refresh for regions which does have lock
  set to truth in their template. Currently it's just a 3D
  viewport, but in the future more regions could be considered
  unsafe, or we could want to lock different parts of
  interface when doing different jobs.

  This is needed because 3D viewport could be using or changing
  the same data as renderer currently uses, leading to threading
  conflict.

  Notifiers are still allowed to handle, so render progress is
  seen on the screen, but would need to doublecheck on this, in
  terms some notifiers could be changing the data.

  For now interface locking happens for render job only in case
  "Lock Interface" checkbox is enabled.

  Other tools like backing would also benefit of this option.

  It is possible to mark operator as safe to be used in locked
  interface mode by adding OPTYPE_ALLOW_LOCKED bit to operator
  template flags.

  This bit is completely handled by wm_evem_system, not
  with operator run routines, so it's still possible to
  run operators from drivers and handlers.

  Currently allowed image editor navigation and zooming.

Reviewers: brecht, campbellbarton

Reviewed By: campbellbarton

Differential Revision: https://developer.blender.org/D142

12 files changed:
release/scripts/startup/bl_ui/properties_render.py
source/blender/blenloader/intern/readfile.c
source/blender/editors/render/render_internal.c
source/blender/editors/space_image/image_ops.c
source/blender/makesdna/DNA_scene_types.h
source/blender/makesdna/DNA_windowmanager_types.h
source/blender/makesrna/intern/rna_scene.c
source/blender/render/extern/include/RE_pipeline.h
source/blender/render/intern/source/pipeline.c
source/blender/windowmanager/WM_api.h
source/blender/windowmanager/WM_types.h
source/blender/windowmanager/intern/wm_event_system.c

index 41516a74c9fd8cd7aa49f1c6b497f2c8ed1b48ed..f12615344653326265e15004aab7b00087313500 100644 (file)
@@ -71,6 +71,8 @@ class RENDER_PT_render(RenderButtonsPanel, Panel):
 
         layout.prop(rd, "display_mode", text="Display")
 
+        layout.prop(rd, "use_lock_interface")
+
 
 class RENDER_PT_dimensions(RenderButtonsPanel, Panel):
     bl_label = "Dimensions"
index 913bdfd1521bcee927af34b61d09a5b4241262c9..29de575e8b8f0a7d81ef6126e17391bcdd6817bb 100644 (file)
@@ -5554,6 +5554,7 @@ static void direct_link_windowmanager(FileData *fd, wmWindowManager *wm)
        wm->winactive = NULL;
        wm->initialized = 0;
        wm->op_undo_depth = 0;
+       wm->is_interface_locked = 0;
 }
 
 static void lib_link_windowmanager(FileData *fd, Main *main)
index 4ff4150e06a03ae6667a94c3229c5cfa2b4ef2e7..e57474a04437cb861c126270d67fa0727a0bfddc 100644 (file)
@@ -58,6 +58,7 @@
 #include "BKE_main.h"
 #include "BKE_node.h"
 #include "BKE_multires.h"
+#include "BKE_object.h"
 #include "BKE_paint.h"
 #include "BKE_report.h"
 #include "BKE_sequencer.h"
@@ -116,6 +117,7 @@ typedef struct RenderJob {
        ScrArea *sa;
        ColorManagedViewSettings view_settings;
        ColorManagedDisplaySettings display_settings;
+       bool interface_locked;
 } RenderJob;
 
 /* called inside thread! */
@@ -662,6 +664,29 @@ static void render_endjob(void *rjv)
 
                BKE_image_release_ibuf(ima, ibuf, lock);
        }
+
+       /* Finally unlock the user interface (if it was locked). */
+       if (rj->interface_locked) {
+               Scene *scene;
+
+               /* Interface was locked, so window manager couldn't have been changed
+                * and using one from Global will unlock exactly the same manager as
+                * was locked before running the job.
+                */
+               WM_set_locked_interface(G.main->wm.first, false);
+
+               /* We've freed all the derived caches before rendering, which is
+                * effectively the same as if we re-loaded the file.
+                *
+                * So let's not try being smart here and just reset all updated
+                * scene layers and use generic DAG_on_visible_update.
+                */
+               for (scene = G.main->scene.first; scene; scene = scene->id.next) {
+                       scene->lay_updated = 0;
+               }
+
+               DAG_on_visible_update(G.main, false);
+       }
 }
 
 /* called by render, check job 'stop' value or the global */
@@ -687,10 +712,14 @@ static int render_break(void *UNUSED(rjv))
 
 /* runs in thread, no cursor setting here works. careful with notifiers too (malloc conflicts) */
 /* maybe need a way to get job send notifer? */
-static void render_drawlock(void *UNUSED(rjv), int lock)
+static void render_drawlock(void *rjv, int lock)
 {
-       BKE_spacedata_draw_locks(lock);
-       
+       RenderJob *rj = rjv;
+
+       /* If interface is locked, renderer callback shall do nothing. */
+       if (!rj->interface_locked) {
+               BKE_spacedata_draw_locks(lock);
+       }
 }
 
 /* catch esc */
@@ -721,6 +750,36 @@ static void screen_render_cancel(bContext *C, wmOperator *op)
        WM_jobs_kill_type(wm, scene, WM_JOB_TYPE_RENDER);
 }
 
+static void clean_viewport_memory(Main *bmain, Scene *scene, int renderlay)
+{
+       Object *object;
+       Scene *sce_iter;
+       Base *base;
+
+       for (object = bmain->object.first; object; object = object->id.next) {
+               object->id.flag |= LIB_DOIT;
+       }
+
+       for (SETLOOPER(scene, sce_iter, base)) {
+               if ((base->lay & renderlay) == 0) {
+                       continue;
+               }
+
+               if (RE_allow_render_generic_object(base->object)) {
+                       base->object->id.flag &= ~LIB_DOIT;
+               }
+       }
+
+       for (object = bmain->object.first; object; object = object->id.next) {
+               if ((object->id.flag & LIB_DOIT) == 0) {
+                       continue;
+               }
+               object->id.flag &= ~LIB_DOIT;
+
+               BKE_object_free_derived_caches(object);
+       }
+}
+
 /* using context, starts job */
 static int screen_render_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
@@ -832,6 +891,26 @@ static int screen_render_invoke(bContext *C, wmOperator *op, const wmEvent *even
                        rj->lay_override |= v3d->localvd->lay;
        }
 
+       /* Lock the user interface depending on render settings. */
+       if (scene->r.use_lock_interface) {
+               int renderlay = rj->lay_override ? rj->lay_override : scene->lay;
+
+               WM_set_locked_interface(CTX_wm_manager(C), true);
+
+               /* Set flag interface need to be unlocked.
+                *
+                * This is so because we don't have copy of render settings
+                * accessible from render job and copy is needed in case
+                * of non-locked rendering, so we wouldn't try to unlock
+                * anything if option was initially unset but then was
+                * enabled during rendering.
+                */
+               rj->interface_locked = true;
+
+               /* Clean memory used by viewport? */
+               clean_viewport_memory(rj->main, scene, renderlay);
+       }
+
        /* setup job */
        if (RE_seq_render_active(scene, &scene->r)) name = "Sequence Render";
        else name = "Render";
index 38d19923c3d5337df4e356929fd2848703052f27..120e6f52d95c93a6fcc2337f0facb378885537e9 100644 (file)
@@ -363,7 +363,7 @@ void IMAGE_OT_view_pan(wmOperatorType *ot)
        ot->poll = space_image_main_area_poll;
 
        /* flags */
-       ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_POINTER;
+       ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_POINTER | OPTYPE_LOCK_BYPASS;
        
        /* properties */
        RNA_def_float_vector(ot->srna, "offset", 2, NULL, -FLT_MAX, FLT_MAX,
@@ -577,7 +577,7 @@ void IMAGE_OT_view_zoom(wmOperatorType *ot)
        ot->poll = space_image_main_area_poll;
 
        /* flags */
-       ot->flag = OPTYPE_BLOCKING;
+       ot->flag = OPTYPE_BLOCKING | OPTYPE_LOCK_BYPASS;
        
        /* properties */
        RNA_def_float(ot->srna, "factor", 0.0f, -FLT_MAX, FLT_MAX,
@@ -640,6 +640,9 @@ void IMAGE_OT_view_ndof(wmOperatorType *ot)
        
        /* api callbacks */
        ot->invoke = image_view_ndof_invoke;
+
+       /* flags */
+       ot->flag = OPTYPE_LOCK_BYPASS;
 }
 
 /********************** view all operator *********************/
@@ -817,6 +820,9 @@ void IMAGE_OT_view_zoom_in(wmOperatorType *ot)
        ot->exec = image_view_zoom_in_exec;
        ot->poll = space_image_main_area_poll;
 
+       /* flags */
+       ot->flag = OPTYPE_LOCK_BYPASS;
+
        /* properties */
        RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Cursor location in screen coordinates", -10.0f, 10.0f);
 }
@@ -859,6 +865,9 @@ void IMAGE_OT_view_zoom_out(wmOperatorType *ot)
        ot->exec = image_view_zoom_out_exec;
        ot->poll = space_image_main_area_poll;
 
+       /* flags */
+       ot->flag = OPTYPE_LOCK_BYPASS;
+
        /* properties */
        RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Cursor location in screen coordinates", -10.0f, 10.0f);
 }
@@ -901,6 +910,9 @@ void IMAGE_OT_view_zoom_ratio(wmOperatorType *ot)
        ot->exec = image_view_zoom_ratio_exec;
        ot->poll = space_image_main_area_poll;
 
+       /* flags */
+       ot->flag = OPTYPE_LOCK_BYPASS;
+
        /* properties */
        RNA_def_float(ot->srna, "ratio", 0.0f, -FLT_MAX, FLT_MAX,
                      "Ratio", "Zoom ratio, 1.0 is 1:1, higher is zoomed in, lower is zoomed out", -FLT_MAX, FLT_MAX);
index 076dc9c7f59f2f911fe120aeb581c6ef65e6b375..177b687802d0df7c1cf238c732a92c103f9beb7c 100644 (file)
@@ -432,7 +432,8 @@ typedef struct RenderData {
         * Render to image editor, fullscreen or to new window.
         */
        short displaymode;
-       short pad7;
+       char use_lock_interface;
+       char pad7;
 
        /**
         * Flags for render settings. Use bit-masking to access the settings.
index ff43cc31b9aa162a5d0439e12f84ae6d591bf1c3..a17e416b5bdefbdd50e1382c196a078bc157006b 100644 (file)
@@ -154,6 +154,9 @@ typedef struct wmWindowManager {
 
        ListBase timers;                  /* active timers */
        struct wmTimer *autosavetimer;    /* timer for auto save */
+
+       char is_interface_locked;               /* indicates whether interface is locked for user interaction */
+       char par[7];
 } wmWindowManager;
 
 /* wmWindowManager.initialized */
index 4a544606f09955591204ebad882b179d68c12541..19d90ab752ba3af2aff5ad8ac14f42635b24ac79 100644 (file)
@@ -4539,6 +4539,11 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
        RNA_def_property_ui_text(prop, "Display", "Select where rendered images will be displayed");
        RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
 
+       prop = RNA_def_property(srna, "use_lock_interface", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "use_lock_interface", 1);
+       RNA_def_property_ui_text(prop, "Lock Interface", "Lock interface during rendering in favor of giving more memory to the renderer");
+       RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
+
        prop = RNA_def_property(srna, "filepath", PROP_STRING, PROP_FILEPATH);
        RNA_def_property_string_sdna(prop, NULL, "pic");
        RNA_def_property_ui_text(prop, "Output Path",
index 32c0a1d8535c1be06109bc40fdc4c4809fa092d5..bc07f0ce6c0ea161769198e8da664846040716a5 100644 (file)
@@ -290,5 +290,7 @@ struct Scene *RE_GetScene(struct Render *re);
 
 int RE_is_rendering_allowed(struct Scene *scene, struct Object *camera_override, struct ReportList *reports);
 
+bool RE_allow_render_generic_object(struct Object *ob);
+
 #endif /* __RE_PIPELINE_H__ */
 
index ec2644e4d9b3d4e5467ff455c7af8df2a94e9e56..39bc571853aaa283dd2cc4bb438dbdd6c49f560c 100644 (file)
@@ -1602,6 +1602,18 @@ static bool rlayer_node_uses_alpha(bNodeTree *ntree, bNode *node)
        return false;
 }
 
+bool RE_allow_render_generic_object(Object *ob)
+{
+       /* override not showing object when duplis are used with particles */
+       if (ob->transflag & OB_DUPLIPARTS) {
+               /* pass */  /* let particle system(s) handle showing vs. not showing */
+       }
+       else if ((ob->transflag & OB_DUPLI) && !(ob->transflag & OB_DUPLIFRAMES)) {
+               return false;
+       }
+       return true;
+}
+
 /* Issue here is that it's possible that object which is used by boolean,
  * array or shrinkwrap modifiers weren't displayed in the viewport before
  * rendering. This leads to situations when apply() of this modifiers
@@ -1619,18 +1631,6 @@ static bool rlayer_node_uses_alpha(bNodeTree *ntree, bNode *node)
 #define DEPSGRAPH_WORKAROUND_HACK
 
 #ifdef DEPSGRAPH_WORKAROUND_HACK
-static bool allow_render_mesh_object(Object *ob)
-{
-       /* override not showing object when duplis are used with particles */
-       if (ob->transflag & OB_DUPLIPARTS) {
-               /* pass */  /* let particle system(s) handle showing vs. not showing */
-       }
-       else if ((ob->transflag & OB_DUPLI) && !(ob->transflag & OB_DUPLIFRAMES)) {
-               return false;
-       }
-       return true;
-}
-
 static void tag_dependend_objects_for_render(Scene *scene, int renderlay)
 {
        Scene *sce_iter;
@@ -1643,7 +1643,7 @@ static void tag_dependend_objects_for_render(Scene *scene, int renderlay)
                }
 
                if (object->type == OB_MESH) {
-                       if (allow_render_mesh_object(object)) {
+                       if (RE_allow_render_generic_object(object)) {
                                ModifierData *md;
                                VirtualModifierData virtualModifierData;
 
index c13731e2459f1fb4d78f2b8a4fed20cd9d6abbcf..a47018e21eb5792bd6814fc890a490ddb9e6c910 100644 (file)
@@ -428,6 +428,9 @@ void        WM_main_playanim(int argc, const char **argv);
 /* debugging only, convenience function to write on crash */
 bool write_crash_blend(void);
 
+                       /* Lock the interface for any communication */
+void        WM_set_locked_interface(struct wmWindowManager *wm, bool lock);
+
 #ifdef __cplusplus
 }
 #endif
index 83c17170f9eed90f2af3391c72382d261eb86bcc..8bbdd7bd736b7da183bf86e7098900af2c9096a1 100644 (file)
@@ -134,6 +134,7 @@ struct ImBuf;
                                                                 * and don't make sense to be accessed from the
                                                                 * search menu, even if poll() returns TRUE.
                                                                 * currently only used for the search toolbox */
+#define OPTYPE_LOCK_BYPASS             128     /* Allow operator to run when interface is locked */
 
 /* context to call operator in for WM_operator_name_call */
 /* rna_ui.c contains EnumPropertyItem's of these, keep in sync */
index be4064a375f68b2c61892ad07d24e259a2033453..6e2fcf159972d10fbf9805f0e06d9acd484876bc 100644 (file)
@@ -1488,6 +1488,22 @@ static void wm_event_modalkeymap(const bContext *C, wmOperator *op, wmEvent *eve
        }
 }
 
+/* Check whether operator is allowed to run in case interface is locked,
+ * If interface is unlocked, will always return truth.
+ */
+static bool wm_operator_check_locked_interface(bContext *C, wmOperatorType *ot)
+{
+       wmWindowManager *wm = CTX_wm_manager(C);
+
+       if (wm->is_interface_locked) {
+               if ((ot->flag & OPTYPE_LOCK_BYPASS) == 0) {
+                       return false;
+               }
+       }
+
+       return true;
+}
+
 /* bad hacking event system... better restore event type for checking of KM_CLICK for example */
 /* XXX modal maps could use different method (ton) */
 static void wm_event_modalmap_end(wmEvent *event, bool dbl_click_disabled)
@@ -1514,7 +1530,12 @@ static int wm_handler_operator_call(bContext *C, ListBase *handlers, wmEventHand
                wmOperator *op = handler->op;
                wmOperatorType *ot = op->type;
 
-               if (ot->modal) {
+               if (!wm_operator_check_locked_interface(C, ot)) {
+                       /* Interface is locked and pperator is not allowed to run,
+                        * nothing to do in this case.
+                        */
+               }
+               else if (ot->modal) {
                        /* we set context to where modal handler came from */
                        wmWindowManager *wm = CTX_wm_manager(C);
                        ScrArea *area = CTX_wm_area(C);
@@ -1587,7 +1608,9 @@ static int wm_handler_operator_call(bContext *C, ListBase *handlers, wmEventHand
                wmOperatorType *ot = WM_operatortype_find(event->keymap_idname, 0);
 
                if (ot) {
-                       retval = wm_operator_invoke(C, ot, event, properties, NULL, FALSE);
+                       if (wm_operator_check_locked_interface(C, ot)) {
+                               retval = wm_operator_invoke(C, ot, event, properties, NULL, FALSE);
+                       }
                }
        }
        /* Finished and pass through flag as handled */
@@ -1793,7 +1816,11 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers
                /* comment this out to flood the console! (if you really want to test) */
                !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)
                ;
+#    define PRINT if (do_debug_handler) printf
+#else
+#  define PRINT(format, ...)
 #endif
+
        wmWindowManager *wm = CTX_wm_manager(C);
        wmEventHandler *handler, *nexthandler;
        int action = WM_HANDLER_CONTINUE;
@@ -1829,28 +1856,16 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers
                                wmKeyMap *keymap = WM_keymap_active(wm, handler->keymap);
                                wmKeyMapItem *kmi;
 
-#ifndef NDEBUG
-                               if (do_debug_handler) {
-                                       printf("%s:   checking '%s' ...", __func__, keymap->idname);
-                               }
-#endif
+                               PRINT("%s:   checking '%s' ...", __func__, keymap->idname);
 
                                if (!keymap->poll || keymap->poll(C)) {
 
-#ifndef NDEBUG
-                                       if (do_debug_handler) {
-                                               printf("pass\n");
-                                       }
-#endif
+                                       PRINT("pass\n");
 
                                        for (kmi = keymap->items.first; kmi; kmi = kmi->next) {
                                                if (wm_eventmatch(event, kmi)) {
 
-#ifndef NDEBUG
-                                                       if (do_debug_handler) {
-                                                               printf("%s:     item matched '%s'\n", __func__, kmi->idname);
-                                                       }
-#endif
+                                                       PRINT("%s:     item matched '%s'\n", __func__, kmi->idname);
 
                                                        /* weak, but allows interactive callback to not use rawkey */
                                                        event->keymap_idname = kmi->idname;
@@ -1869,32 +1884,28 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers
                                                                        if (G.debug & (G_DEBUG_EVENTS | G_DEBUG_HANDLERS))
                                                                                printf("%s:       handled - and pass on! '%s'\n", __func__, kmi->idname);
                                                                
-#ifndef NDEBUG
-                                                               if (do_debug_handler) {
-                                                                       printf("%s:       un-handled '%s'...", __func__, kmi->idname);
-                                                               }
-#endif
+                                                                       PRINT("%s:       un-handled '%s'...", __func__, kmi->idname);
                                                        }
                                                }
                                        }
                                }
                                else {
-#ifndef NDEBUG
-                                       if (do_debug_handler) {
-                                               printf("fail\n");
-                                       }
-#endif
+                                       PRINT("fail\n");
                                }
                        }
                        else if (handler->ui_handle) {
-                               action |= wm_handler_ui_call(C, handler, event, always_pass);
+                               if (!wm->is_interface_locked) {
+                                       action |= wm_handler_ui_call(C, handler, event, always_pass);
+                               }
                        }
                        else if (handler->type == WM_HANDLER_FILESELECT) {
-                               /* screen context changes here */
-                               action |= wm_handler_fileselect_call(C, handlers, handler, event);
+                               if (!wm->is_interface_locked) {
+                                       /* screen context changes here */
+                                       action |= wm_handler_fileselect_call(C, handlers, handler, event);
+                               }
                        }
                        else if (handler->dropboxes) {
-                               if (event->type == EVT_DROP) {
+                               if (!wm->is_interface_locked && event->type == EVT_DROP) {
                                        wmDropBox *drop = handler->dropboxes->first;
                                        for (; drop; drop = drop->next) {
                                                /* other drop custom types allowed */
@@ -1960,6 +1971,8 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers
        if (action == (WM_HANDLER_BREAK | WM_HANDLER_MODAL))
                wm_cursor_arrow_move(CTX_wm_window(C), event);
 
+#undef PRINT
+
        return action;
 }
 
@@ -3277,3 +3290,24 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U
        WM_event_print(&event);
 #endif
 }
+
+void WM_set_locked_interface(wmWindowManager *wm, bool lock)
+{
+       /* This will prevent events from being handled while interface is locked
+        *
+        * Use a "local" flag for now, because currently no other areas could
+        * benefit of locked interface anyway (aka using G.is_interface_locked
+        * wouldn't be useful anywhere outside of window manager, so let's not
+        * pollute global context with such an information for now).
+        */
+       wm->is_interface_locked = lock ? 1 : 0;
+
+       /* This will prevent drawing regions which uses non-threadsafe data.
+        * Currently it'll be just a 3D viewport.
+        *
+        * TODO(sergey): Make it different locked states, so different jobs
+        *               could lock different areas of blender and allow
+        *               interation with others?
+        */
+       BKE_spacedata_draw_locks(lock);
+}