====== Ardour, Windows, Gdk/GTK and Cairo ====== Lab Notes.\\ Analysis the //slowness// and high CPU-usage of the [[http://ardour.org|Ardour3]] GUI on the Windows platform. ===== Findings ===== First a bit of background: * All of GDK/WIN32 is using a Windows Drawing Context (DC).\\ * When a cairo-surface is used, GdkDrawables use ''cairo_win32_surface_create(HDC*)''.\\ * That cairo-surface has the geometry of the DC (not the size of the GdkDrawable).\\ * operations on the cairo-surface are flushed to the DC. Gdk does not use an extra copy step (cairo does that). ==== A: Cairo BitBlt ==== When using a cairo-surface backed by Windows Drawing Context: - The first cairo-operation on that surface which need an Alpha-Channel creates a fallback-surface (ARGB32). This is done because windows DC does not properly support 32bit RGBA. - Then, cairo calls [[http://msdn.microsoft.com/en-us/library/windows/desktop/dd183370%28v=vs.85%29.aspx|BitBlt]] to copy the whole DC area to that new surface. - Cairo continues to operate on the fallback surface. - Eventually, Cairo ''BitBlt''s back only the changed parts when the surface is flushed or destroyed. - A surface_flush also invalidates and destroys the fallback-surface (the next cairo operation on that surface go back to **1.**). **The effective performance hog is 2.** in ''_cairo_win32_display_surface_map_to_image()''. ==== Notes ==== Cairo is doing //the right thing//. It is how Gdk/GTK uses it, which is really causing the problem, and more specifically, how Ardour uses GTK for its cairo canvas. In a lot of cases in Ardour the BitBlt (**2.**) is not even needed. Ardour Canvas Items draw their own background. All pixels that are ''BitBlt'' during step 2 above are overwritten. This is in particular true for the largest area (main canvas) of Ardour. In that case it would be helpful to have an API to tell cairo to skip the initial ''BitBlt'' -- or rather tell Gdk to not use a win32 DC backed surface in the first place. In cases where there is composition, it is however essential to initially copy the DC to the fallback surface (e.g. gtk-box background, ardour-cairo foreground with round-edges (alpha) or text) in order to properly display edges or round corners. In ardour's case this is only relevant for mostly static widgets like buttons or the toolbar when the actual DC geometry is small. Ideally Gdk should be changed to always use a cairo image/software-surface (never DC backed) and only synchronize the modified-areas to the DC as a last step (GdkWindow uses a cairo backing store already). ===== Other Findings ===== While the above issue has been analyzed in detail, (code study, gdb and print'ing) the facts below were established using //printf-debugging// for the most part. ==== B: Gdk Backing Store ==== Every GdkWindow has a backing store (gdkwindow.c): ''gdk_window_begin_paint_region()'' creates a new temporary cairo-surface (using GdkDrawable API), it is destroyed again in ''gdk_window_end_paint_region()''. Some operations on the backing-store are RGB region-copies only (no alpha) and hence do not have the BitBlt problem, but when ardour is active the vast majority or expose-events do trigger the issue. -> BitBlt the complete window. zzzZZZ. (possible workaround: unset [[https://developer.gnome.org/gtk2/stable/GtkWidget.html#gtk-widget-set-double-buffered|double buffering]]) ==== C: Gdk/Windows Drawing Context Size ==== The windows ''DC'' geometry is a region-combine of all invalidated widgets for a given expose. e.g. When invalidating a 16x16 widget top-left and one bottom-right: the drawing-context and hence the fallback-surfaces needed to BitBlt is the complete window. ==== D: Compositing ==== GdkDrawable background and pixmaps, etc are painted with ''GC'', text is done with pango-cairo. There are often multiple surface-flushes for every GdkDrawable expose, each of which implies a ''BitBlt''. ==== E: Surface Allocation ==== GdkDrawable do not retain the cairo-surface. The reference count reaches zero at the end of every expose/paint. Gdk allocates and free()s cairo-surfaces on every expose for every widget. (In about ten seconds of Ardour GUI, there's an average of 50.000 cairo surfaces created an destroyed). ==== F: Masking & Invalidation ==== Gdk never uses cairo_surface_mark_dirty_rectangle().\\ Partial exposes (BitBlt back to DC) are only done by cairo itself. Gdk always uses the complete combined region-size (see also ''C'' above.) ===== The bad news ===== Commenting out the BitBlt ''[2]'' goes a very very (did I say very?) long way for Ardour. Then effective performance and CPU usage after that is comparable to Linux & OSX. However, it introduces a handful of drawing issue: Areas where there are still gtk-widgets involved are visually affected. That includes Gtk Containers Box backgrounds (button areas and the menu). See the screenshot below. {{ :wiki:a3_no_bitblt.png?600 |}} compare to the image on the left (with BitBlt). {{:wiki:a3_win_3386.png?100 }} Various hacks would be possible to address the issue. However there's not a single simple point to easily work around this. Probably the easiest way to mitigate the performance hog is to provide a custom GdkDrawable implementation for the main canvas (which does not use a hardware backed surface) and keep using default GdkDrawable for all other widgets. A similar solution would be to make the backing-store of GdkWindow persistent and access it directly. A quick hack has been prototyped. The cairo-surface user-data API is used to flag select cairo/win surfaces to not BitBlt (ardour's main canvas & meters): [[https://gist.github.com/x42/ef0b0f6e70d416b3af17|patch for ardour, libcairo]] ((In this case a dedicated user-data key - known to ardour and libcairo - is used to tunnel the information though gdk/gtk which remains unchanged.)). Still, while performance on Linux & OSX is OKish, the expose and invalidation strategy (see B -> F above) is abysmal and unsuitable for Ardour in general. It only works because CPUs are fast :) The proper way forward: **get rid of GdkDrawable on _all_ platforms.** ===== Brainstorm ===== Use a single cairo [ARGB32] surface for the whole GUI and directly map its data to e.g an openGL texture. Cairo's ''...surface_mark_dirty()'', damage-reduction and surface-flushing algorithms are very efficient. The [[http://ardour.org/canvas.html|ardour canvas]] already has its own event management and [[http://drobilla.net/software/pugl/|puGL]] can provide the rest... That will still leave window-management, box (and table?) layout packing to be done, but those are manageable. The hard part will be tree-views, file-manager (and maybe menu), maybe some hybrid solution can be done for those cases (or code copy/paste of select GTK implementations sans Gdk). The cairo/pixmap software-surface implementation beats various Hardware-accelerations for 2D drawing performance (on any platform with most graphics chipsets/drivers). And more importantly, the cairo image/software surface works reliably regardless of underlying hardware. Until cairo 2D HW accelleration improves in a way useful to ardour ((Internally ardour uses a lot of cairo surfaces and patterns for waveform image cache, meter-gradients, button insets. They are currently not backed by a hardware.)), using a software image-surface for the complete top-level window will be most efficient. ===== Versions Used ===== * Ardour 3.5-3386-gec92524 (ardour-build-tools 11919e4, x86_64-w64-mingw32-gcc (GCC) 4.9.1) * glib-2.42.0 * cairo-1.14.0 + [[https://gist.github.com/x42/c875f8910c9186c013fb|BitBlt debug diff]] * gtk+-2.24.24 , gtkmm-2.24.4