This is an old revision of the document!


Ardour, Windows, Gdk/GTK and Cairo

Lab Notes. Analysis the slowness and high CPU-usage of the 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:

  1. The first cairo-operation on that surface which need an Alpha-Channel creates a fallback-surface (RGB32). This is done because windows DC does not properly support 32bit RGBA.
  2. Then, cairo calls BitBlt to copy the whole DC area to that new surface.
  3. Cairo continues to operate on the fallback surface.
  4. Eventually, Cairo BitBlts back only the changed parts when the surface is flushed or destroyed.
  5. A surface_flush also invalidates and destroys the fallback-surface (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 draws its own background. Everything that is BitBlt during step 2 above is 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 surface in the first place.

For other cases it is however essential to initially copy the DC to the fallback surface in cases where there's composition (gtk-box background, ardour-cairo foreground with round-edges (alpha) or text). In ardour's case that's mostly static things like buttons, 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).

The cairo/pixmap software implementation beats various HW acceleration methods for 2D drawing performance (also on other platforms) and the cairo image/software surface works reliably regardless of underlying hardware.

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.

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 16×16 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.

compare to the image on the left (with BitBlt).

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.

Still, while performance on Linux & OSX is OKish, the expose 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.

The ardour canvas already has its own event management and 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).

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 + BitBlt debug diff
  • gtk+-2.24.24 , gtkmm-2.24.4
 
wiki/ardour_windows_gdk_and_cairo.1414326508.txt.gz · Last modified: 26.10.2014 13:28 by rgareus