clever tricks with code swizzling to slow down errant plugin GUIs for AU
authorPaul Davis <paul@linuxaudiosystems.com>
Thu, 28 Apr 2016 22:41:50 +0000 (18:41 -0400)
committerPaul Davis <paul@linuxaudiosystems.com>
Thu, 28 Apr 2016 22:42:04 +0000 (18:42 -0400)
This commit swizzles (replaces) NSView::displayIfNeeded to allow us to stop redraws of plugin
windows if the Glib idle timer fires less often then every 40msec. The next 10 plugin redraws
(for all plugin windows combined) will be skipped. Heuristics will likely need some adjustments

gtk2_ardour/au_pluginui.h
gtk2_ardour/au_pluginui.mm

index bc7d9a521b040b60a21cc973a0868cedc252bb36..807d6b751aa5044d21a13e48d82e565c856b2346 100644 (file)
@@ -23,6 +23,8 @@
 #include <vector>
 #include <string>
 
+#include <stdint.h>
+
 #include <AppKit/AppKit.h>
 #include <Carbon/Carbon.h>
 #include <AudioUnit/AudioUnitCarbonView.h>
@@ -94,7 +96,7 @@ class AUPluginUI : public PlugUIBase, public Gtk::VBox
        void lower_box_unmap ();
        void lower_box_size_request (GtkRequisition*);
        void lower_box_size_allocate (Gtk::Allocation&);
-       gboolean lower_box_expose (GdkEventExpose*);
+       bool lower_box_expose (GdkEventExpose*);
 
        void cocoa_view_resized ();
        void on_realize ();
@@ -124,14 +126,9 @@ class AUPluginUI : public PlugUIBase, public Gtk::VBox
 
        static std::vector<std::string> automation_mode_strings;
 
-       bool mapped;
        bool resizable;
-       int  min_width;
-       int  min_height;
        int  req_width;
        int  req_height;
-       int  alo_width;
-       int  alo_height;
 
        /* Cocoa */
 
@@ -167,6 +164,11 @@ class AUPluginUI : public PlugUIBase, public Gtk::VBox
        void update_view_size ();
 
        bool plugin_class_valid (Class pluginClass);
+
+       static bool idle_meter();
+       static int64_t last_idle;
+       static bool idle_meter_needed;
+       int64_t expose_cnt;
 };
 
 #endif /* __gtk2_ardour_auplugin_ui_h__  */
index 9e9e45b3e553697dd9444d910e7392c1b267a098..3a1bfbd82b63eeda239be86523e999666d7136a5 100644 (file)
@@ -1,6 +1,7 @@
 #undef  Marker
 #define Marker FuckYouAppleAndYourLackOfNameSpaces
 
+#include <sys/time.h>
 #include <gtkmm/button.h>
 #include <gdk/gdkquartz.h>
 
@@ -25,6 +26,8 @@
 
 #import <AudioUnit/AUCocoaUIView.h>
 #import <CoreAudioKit/AUGenericView.h>
+#import <objc/runtime.h>
+#include <dispatch/dispatch.h>
 
 #undef Marker
 
@@ -47,6 +50,8 @@ using namespace std;
 using namespace PBD;
 
 vector<string> AUPluginUI::automation_mode_strings;
+bool AUPluginUI::idle_meter_needed = true;
+int64_t AUPluginUI::last_idle = 0;
 
 static const gchar* _automation_mode_strings[] = {
        X_("Manual"),
@@ -66,7 +71,7 @@ dump_view_tree (NSView* view, int depth, int maxdepth)
                cerr << '\t';
        }
        NSRect frame = [view frame];
-       cerr << " view @ " <<  frame.origin.x << ", " << frame.origin.y
+       cerr << " view " << view << " @ " <<  frame.origin.x << ", " << frame.origin.y
                << ' ' << frame.size.width << " x " << frame.size.height
                << endl;
 
@@ -79,6 +84,63 @@ dump_view_tree (NSView* view, int depth, int maxdepth)
        }
 }
 
+static IMP original_nsview_draw_rect;
+static std::vector<id> plugin_views;
+static uint32_t gandalf_block = 0;
+
+static void add_plugin_view (id view)
+{
+       plugin_views.push_back (view);
+       std::cerr << "Added " << view << " now have " << plugin_views.size() << endl;
+}
+
+static void remove_plugin_view (id view)
+{
+       std::vector<id>::iterator x = find (plugin_views.begin(), plugin_views.end(), view);
+       if (x != plugin_views.end()) {
+               plugin_views.erase (x);
+               std::cerr << "Removed " << view << " now have " << plugin_views.size() << endl;
+       }
+}
+
+static bool is_plugin_view (id view)
+{
+       for (std::vector<id>::const_iterator v = plugin_views.begin(); v != plugin_views.end(); ++v) {
+               /* displayIfNeeded seems to be invoked for some toplevel NSView
+                * that we can't actually access. It is a superview of the
+                * contentView of the NSWindow for the plugin.
+                */
+
+               if (view == [(*v) superview]) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+int gandalf_draw (id receiver, SEL selector, NSRect rect)
+{
+       if (gandalf_block && is_plugin_view (receiver)) {
+               gandalf_block--;
+               /* YOU ... SHALL .... NOT ... DRAW!!!! */
+               return 0;
+       }
+       return ((int(*)(id,SEL,NSRect)) original_nsview_draw_rect) (receiver, selector, rect);
+}
+
+@implementation NSView (Tracking)
++ (void) load {
+       static dispatch_once_t once_token;
+
+       dispatch_once (&once_token, ^{
+                       Method target = class_getInstanceMethod ([NSView class], @selector(displayIfNeeded));
+                       original_nsview_draw_rect = method_setImplementation (target, (IMP) gandalf_draw);
+               }
+               );
+}
+
+@end
+
 @implementation NotificationObject
 
 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
@@ -170,14 +232,9 @@ AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
        : PlugUIBase (insert)
        , automation_mode_label (_("Automation"))
        , preset_label (_("Presets"))
-       , mapped (false)
        , resizable (false)
-       , min_width (0)
-       , min_height (0)
        , req_width (0)
        , req_height (0)
-       , alo_width (0)
-       , alo_height (0)
        , cocoa_window (0)
        , au_view (0)
        , in_live_resize (false)
@@ -185,7 +242,7 @@ AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
        , cocoa_parent (0)
        , _notify (0)
        , _resize_notify (0)
-
+       , expose_cnt (0)
 {
        if (automation_mode_strings.empty()) {
                automation_mode_strings = I18N (_automation_mode_strings);
@@ -293,12 +350,16 @@ AUPluginUI::~AUPluginUI ()
                [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
        }
 
+       NSWindow* win = get_nswindow();
+       if (au_view) {
+               remove_plugin_view ([win contentView]);
+       }
+
+#ifdef WITH_CARBON
        if (cocoa_parent) {
-               NSWindow* win = get_nswindow();
                [win removeChildWindow:cocoa_parent];
        }
 
-#ifdef WITH_CARBON
        if (carbon_window) {
                /* not parented, just overlaid on top of our window */
                DisposeWindow (carbon_window);
@@ -313,7 +374,6 @@ AUPluginUI::~AUPluginUI ()
                /* remove whatever we packed into low_box so that GTK doesn't
                   mess with it.
                 */
-
                [au_view removeFromSuperview];
        }
 }
@@ -493,8 +553,8 @@ AUPluginUI::create_cocoa_view ()
 
        // Get the initial size of the new AU View's frame
        NSRect  frame = [au_view frame];
-       min_width  = req_width  = frame.size.width;
-       min_height = req_height = frame.size.height;
+       req_width  = frame.size.width;
+       req_height = frame.size.height;
 
        resizable  = [au_view autoresizingMask];
        std::cerr << plugin->name() << " initial frame = " << [NSStringFromRect (frame) UTF8String] << " resizable ? " << resizable << std::endl;
@@ -510,6 +570,31 @@ AUPluginUI::update_view_size ()
        last_au_frame = [au_view frame];
 }
 
+bool
+AUPluginUI::idle_meter ()
+{
+       int64_t now = ARDOUR::get_microseconds ();
+
+       if (!last_idle) {
+               last_idle = now;
+               return true; /* call me again */
+       }
+
+       if ((now - last_idle) > 40000) {
+               gandalf_block = 10;
+               std::cerr << "Idle too slow (" << (now - last_idle) << " usecs), block plugin redraws for the next 10 plugin exposes\n";
+       } else {
+               std::cerr << "idle all good: elapsed was " << (now - last_idle) << endl;
+       }
+
+       /* We've been called twice. Cancel everything for now. */
+
+       idle_meter_needed = true;
+       last_idle = 0;
+
+       return false;
+}
+
 void
 AUPluginUI::cocoa_view_resized ()
 {
@@ -535,8 +620,9 @@ AUPluginUI::cocoa_view_resized ()
 
        if (plugin_requested_resize) {
                /* we tried to change the plugin frame from inside this method
-                * (to adjust the origin), and the plugin changed its size
-                * again. Ignore this second call.
+                * (to adjust the origin), which changes the frame of the AU
+                * NSView, resulting in a reentrant call to the FrameDidChange
+                * handler (this method). Ignore this reentrant call.
                 */
                std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
                return;
@@ -592,13 +678,6 @@ AUPluginUI::cocoa_view_resized ()
 
         NSUInteger old_auto_resize = [au_view autoresizingMask];
 
-       /* Stop the AU NSView from resizing itself *again* in response to
-          us changing the window size.
-       */
-
-
-        [au_view setAutoresizingMask:NSViewNotSizable];
-
         /* Some stupid AU Views change the origin of the original AU View when
            they are resized (I'm looking at you AUSampler). If the origin has
            been moved, move it back.
@@ -628,14 +707,28 @@ AUPluginUI::cocoa_view_resized ()
         * so there's no need to actually update the view size again.
         */
 
-       [window setFrame:windowFrame display:1];
+       /* We resize the window using Cocoa. We can't use GTK mechanisms
+        * because of this:
+        *
+        * http://www.lists.apple.com/archives/coreaudio-api/2005/Aug/msg00245.html
+        *
+        * "The host needs to be aware that changing the size of the window in
+        * response to the NSViewFrameDidChangeNotification can cause the view
+        * size to change depending on the autoresizing mask of the view. The
+        * host may need to cache the autoresizing mask of the view, set it to
+        * NSViewNotSizable, resize the window, and then reset the autoresizing
+        * mask of the view once the window has been sized."
+        *
+        */
 
+        [au_view setAutoresizingMask:NSViewNotSizable];
+       [window setFrame:windowFrame display:1];
         [au_view setAutoresizingMask:old_auto_resize];
 
        /* keep a copy of the size of the AU NSView. We didn't set - the plugin did */
        last_au_frame = new_frame;
-       min_width  = req_width  = new_frame.size.width;
-       min_height = req_height = new_frame.size.height;
+       req_width  = new_frame.size.width;
+       req_height = new_frame.size.height;
 
        plugin_requested_resize = 0;
 }
@@ -805,6 +898,7 @@ AUPluginUI::parent_cocoa_window ()
 
        NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
        [view addSubview:au_view];
+       add_plugin_view ([win contentView]);
 
        /* this moves the AU NSView down and over to provide a left-hand margin
         * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
@@ -929,7 +1023,6 @@ AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
 void
 AUPluginUI::lower_box_map ()
 {
-       mapped = true;
        [au_view setHidden:0];
        update_view_size ();
 }
@@ -937,7 +1030,6 @@ AUPluginUI::lower_box_map ()
 void
 AUPluginUI::lower_box_unmap ()
 {
-       mapped = false;
        [au_view setHidden:1];
 }
 
@@ -951,30 +1043,31 @@ AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
 void
 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
 {
-       alo_width  = allocation.get_width ();
-       alo_height = allocation.get_height ();
-       std::cerr << "lower box size reallocated to " << alo_width << " x " << alo_height << std::endl;
+       std::cerr << "lower box size reallocated to " << allocation.get_width() << " x " << allocation.get_height() << std::endl;
        update_view_size ();
-       std::cerr << "low box draw (0, 0, " << alo_width << " x " << alo_height << ")\n";
-       low_box.queue_draw_area (0, 0, alo_width, alo_height);
 }
 
-gboolean
+bool
 AUPluginUI::lower_box_expose (GdkEventExpose* event)
 {
        std::cerr << "lower box expose: " << event->area.x << ", " << event->area.y
                  << ' '
                  << event->area.width << " x " << event->area.height
                  << " ALLOC "
-                 << get_allocation().get_width() << " x " << get_allocation().get_height()
+                 << low_box.get_allocation().get_width() << " x " << low_box.get_allocation().get_height()
                  << std::endl;
 
-       /* hack to keep ardour responsive
-        * some UIs (e.g Addictive Drums) completely hog the CPU
-        */
-       ARDOUR::GUIIdle();
+       ++expose_cnt;
 
-       return true;
+       if (!(expose_cnt % 10)) {
+               /* every 100 exposes, check how frequently idle is being called */
+               if (idle_meter_needed) {
+                       Glib::signal_idle().connect (sigc::ptr_fun (AUPluginUI::idle_meter));
+                       idle_meter_needed = false;
+               }
+       }
+
+       return false;
 }
 
 void
@@ -1038,13 +1131,11 @@ create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
 void
 AUPluginUI::start_live_resize ()
 {
-  std::cerr << "\n\n\n++++ Entering Live Resize\n";
-  in_live_resize = true;
+       in_live_resize = true;
 }
 
 void
 AUPluginUI::end_live_resize ()
 {
-  std::cerr << "\n\n\n ----Leaving Live Resize\n";
-  in_live_resize = false;
+       in_live_resize = false;
 }