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 (ARGB32). 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 (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 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 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. 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): patch for ardour, libcairo 1).

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 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).

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 2), 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 + BitBlt debug diff
  • gtk+-2.24.24 , gtkmm-2.24.4
1) 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.
2) 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.
 
wiki/ardour_windows_gdk_and_cairo.txt · Last modified: 28.10.2014 15:24 by 81.57.94.90