Tuesday, 16 June 2009

Stupid X Tricks

Platforms like X and Win32 support trees of native windows. For example,
an application might have a "top-level" window containing child windows
representing controls such as buttons. In browsers, child windows are required
for many kinds of plugins (e.g., most uses of Flash). Managing these child
windows is quite tricky; they add performance overhead and the native
platform's support for event handling, z-ordering, clipping, graphical effects,
etc, often is not a good match for the behaviours required for Web content,
especially as the Web platform evolves. So I'm working hard to get rid of
the use of child windows and I've made a lot of progress --- more about that
later. However, we're stuck with using child windows for plugins, and recently
I've been grappling with one of the hardest problems involving child windows:
scrolling.

It's very hard to make scrolling smooth in the presence of child windows
for plugins, when all other child windows have been eliminated. In general
you want to scroll (using accelerated blitting) some sub-rectangle of the
document's window (e.g., an overflow:auto element), and some of the plugin
windows will move while others won't. You may want to change the clip region
of some of the plugins to reflect the fact that they're being clipped to the
bounds of the overflow:auto element. The big problem is that to avoid flicker
you want to move the widgets, change their clip regions, and blit the contents
of the scrolled area in one atomic operation. If these things happen
separately the window is likely to display artifacts as it passes through
undesired intermediate states. For example, if you blit the scrolled area
first, then move the plugins, the plugin will appear to slightly lag
behind as you scroll the window.

On Windows, href="http://msdn.microsoft.com/en-us/library/bb787593%28VS.85%29.aspx">
ScrollWindowEx with the SW_SCROLLCHILDREN flag gets you a long way. But
on X, the situation is dire. X simply has no API to scroll the contents of a
window including child windows! Toolkits like GTK resort to
heroic efforts such as

guffaw scrolling
to achieve the effect, but those techniques don't let
you scroll a sub-rectangle of a window, only the entire window. So for Gecko
I have had to come up with something new.

Basically I want to use
XCopyArea
and have it copy the contents of the window *and* the contents
of some of the child windows in the scrolled area, and then move the child
windows into their new locations and change their clip regions (I'm using
XShape for this) without doing any extra repainting. I tried a few things that
didn't work, before I hit a solution:


  1. Hide (unmap) all child windows that are going to be moved during the scroll
    operation
  2. Do the XCopyArea
  3. Set the clip region and position of all relevant child windows
  4. Show (map) all child windows that we hid in step 1

By hiding the child windows during the XCopyArea, we trick the X server into
treating the pixels currently on-screen for them as part of the parent window,
so they are copied. By moving and setting the clip region while each child
window is hidden, we also avoid artifacts due to a child window briefly
appearing outside the area it's supposed to be clipped to. It *does* mean that when we scroll a window containing plugins, expose events are generated to repaint the contents of the plugins' child windows, so scrolling is slower than it could be. We might be able to avoid that by enabling backing store for the plugin windows.

It's somewhat tricky to implement this. We have to compute the desired
child window locations and clip regions, store the results in a collection,
and pass that collection into our platform window abstraction's Scroll() API
so that the scrolling and widget configuration can happen as a unified
operation. But I've done it, and it works great on my Ubuntu VM. I'm not
100% sure that it will work well on all X servers and configurations; that's
something we'll need to get good testing of.

I do wonder why X has never felt the need to grow a genuinely useful
scrolling API. Oh well.



8 comments:

  1. Mark Finkle just mentioned they had to back out some CSS-as-UI-theme changes due to poor performance on box-shadow with panning.. would that be improved by this?
    http://starkravingfinkle.org/blog/2009/06/fennec-performance-is-the-theme/

    ReplyDelete
  2. I believe the motivation behind the "client-side-windows" branch of gtk+ is the same. Having gtk+ manage the widgets itself instead of making every widget a X subwindow.
    I'm glad there is progress.

    ReplyDelete
  3. Hey, it took Microsoft several releases before it grew a usable window scrolling function ;-)

    ReplyDelete
  4. > However, we're stuck with using child windows for plugins
    Sorry to hear that. I remember that you had some ideas for fixing that a few years back, letting plugins render into an invisible window and translating incoming events for them. That would solve lots of problems. But I guess that's off the table?

    ReplyDelete
  5. "It *does* mean that when we scroll a window containing plugins, expose events are generated to repaint the contents of the plugins' child windows, so scrolling is slower than it could be. We might be able to avoid that by enabling backing store for the plugin windows."
    Almost no X server out there supports backing store AFAICT. I think using a composited window manager causes expose events to be sent far less often, but I'm not sure. There might also be something in the flags you pass to XCopyArea that suppress expose events.

    ReplyDelete
  6. Why don't you contribute code to X.org, rather than complain about it and hack around it?
    The X.org developers aren't going to know what functionality you need if you don't tell them.

    ReplyDelete
  7. Robert O'Callahan16 June 2009 22:26

    > I remember that you had some ideas for fixing
    > that a few years back, letting plugins render
    > into an invisible window and translating incoming
    > events for them.
    We actually have that on trunk, Jeff Muzielaar did it for Fennec's usage, but it's a performance hit for plugin rendering.
    > There might also be something in the flags you
    > pass to XCopyArea that suppress expose events.
    That won't help, the expose events are generated by the map/unmap pair.
    > Why don't you contribute code to X.org, rather
    > than complain about it and hack around it?
    Why didn't the GTK2 developers get the X developers to fix it? They've been hacking around it for several years longer than I have. In some cases they're even the same people. At least I can assume X developers have heard about the problem.
    Also, one of the reasons contributing to X is not appealing in general is that it takes years for new X servers to be deployed.. In the mean time we have to hack around the problems anyway. So why should I do twice the work? (More than twice actually, sine we don't have any experienced X developers on staff.)

    ReplyDelete
  8. Robert O'Callahan17 June 2009 00:03

    Neil, Windows and X are about the same age. Windows got ScrollWindowEx about 15 years ago.

    ReplyDelete