GNOME Bugzilla – Bug 697758
support for themes with non-trivial mask/alpha channel (semi-transparent UI)
Last modified: 2014-12-30 02:17:14 UTC
I've been asked to implement a Mutter theme with semi-transparent window titlebars. X compositing is done in terms of pixmaps with no alpha channel, and Mutter's alpha-mask implementation currently only supports circular corners, so this turned out to entail more yak-shaving in Mutter than I'd expected. My implementation lets themes draw UI elements with whatever alpha is desired, so you can do things like fully-opaque text and buttons on a semi-transparent titlebar (or even "holes" in the titlebar, if you like that sort of thing). It doesn't allow non-rectangular window borders from an input point of view, though: if part of the window border is invisible, it will still respond to right-clicks, dragging, etc. as normal. Separating the colours from the mask involves some extra Cairo drawing, so I made it opt-in via a <info><uses-alpha/></info> flag and bumped the theme format to 3.5. Themes without the uses-alpha flag will continue to use what's essentially the current code path.
Created attachment 241209 [details] [review] Let the UI layer (via the core) construct the frame mask Having the compositor understand rounded corners seems like a pretty unfortunate special case: the theme should be able to draw arbitrarily fancy transparency.
Created attachment 241210 [details] [review] Add support for alpha-blended window titles, etc.
Created attachment 241271 [details] [review] MetaFrames: factor out MetaUIFrame accessors for borders, corner radiuses This makes it a bit simpler for other functions on a MetaUIFrame to get this information. --- Previously part of Attachment #241209 [details].
Created attachment 241272 [details] [review] Let the UI layer (via the core) construct the frame mask This essentially just moves install_corners() from the compositor, through the core, into the UI layer where it arguably should have been anyway, leaving behind stub functions which call through the various layers. This removes the compositor's special knowledge of how rounded corners work, replacing it with "ask the UI for an alpha mask". The computation of border widths and heights changes a bit, because the width and height used in install_corners() are the meta_window_get_outer_rect() (which includes the visible borders but not the invisible ones), whereas the more readily-available rectangle is the MetaFrame.rect (which includes both). Computing the same width and height as meta_window_get_outer_rect() involves compensating for the invisible borders, but the UI layer is the authority on those anyway, so it seems clearer to have it do the calculations from scratch. --- Essentially the same as Attachment #241209 [details], but with a better commit message (it was getting late when I submitted the patch), and with Attachment #241271 [details] split out to appear earlier.
Created attachment 241274 [details] [review] Add support for alpha-blended window titles, etc. X compositing always uses opaque pixmaps, so we end up with the colour and shape of the window taking different routes through the system. The colour (opaque RGB) goes from the UI layer through Gdk into X, then back from X into the compositor as a pixmap. Meanwhile, the alpha channel goes directly from the UI layer to the compositor without leaving the Mutter process. The easiest way to make this happen is to call meta_frames_paint() once for the colours (drawing onto an opaque black background turns out to be correct), and calling it again, separately, for the alpha channel. This does mean spending twice as long in Cairo, so I added a <uses-alpha/> flag in the theme to switch between the current code-path and the full-alpha code path. --- Simplified from Attachment #241210 [details] to avoid going via a temporary 32-bit texture, and avoid some duplication.
Created attachment 241275 [details] [review] edited version of Adwaita with semi-transparent window titles The active window border ought to be more opaque than this, but it demonstrates the general idea. (Marking this as pre-rejected, it's not intended for actual application.)
Created attachment 241276 [details] screenshot of the edited Adwaita theme
(In reply to comment #5) > The easiest way to make this happen is to call meta_frames_paint() > once for the colours (drawing onto an opaque black background turns out > to be correct), and calling it again, separately, for the > alpha channel. This doesn't work quite right, unfortunately: when the titlebar gets redrawn, we need to invalidate the mask. At the moment Mutter is relying on titlebars' masks not changing except via changes to window geometry.
Created attachment 241284 [details] [review] Track changes to window frames' masks We have to notice when frames are repainted, and pick up an updated mask. Otherwise, we draw the RGB of the new frame with the alpha channel of the old frame: this is particularly visible if you have opaque window-title text on a semi-transparent frame, and use something whose window title changes regularly, like a web browser.
Thanks for the patches, Simon! I think the make your trick with painting over a black background fully work you need to change the pipeline a bit in meta_shaped_texture_paint() - right now it does: cogl_pipeline_set_layer_combine (pipeline_template, 1, "RGBA = MODULATE (PREVIOUS, TEXTURE[A])", NULL); But I think you want 'RGB = PREVIOUS; A = TEXTURE[A]' for the titlebar. (Blend string syntax is made up - I forget the details.) Maybe I'm missing something because your screenshot doesn't look like the colors are double-premultiplied, but it seems to me that is how the math is going to work out. Unfortunately, this is going to break window shaping, since *do* need to multiple the RGB values with the 0 alpha for parts of the window that were shaped away - we can't assume that they'll be black. You actually want 'RGB = MIN(PREVIOUS, TEXTURE[A]); A = TEXTURE[A])' You can't do that as a blend string with current Cogl, but you could use a shader. Other approaches: - Just draw the window in two passes - one pass for the titlebar, one part for the contents. (Another thing that we might want to do eventually is make MetaShapedTexture turn off blending for opaque regions, which gets into the multiple-pass thing as well.) - Draw client-side *once* into a client side buffer and separate out mask and color client-side, rather than drawing twice. Unpremultiplying is a little nasty for performance with a divide per pixel, but I think it's manageable, or the shader as described above would work. But at a higher level - the problem we have with this patch set is that we really want to move to using GTK+ CSS theming for the window borders, and stop subjecting our artists to an esoteric and ad-hoc XML theme format. This also gives us consistency with client-side-decorations for applications that use custom decorations, and as we move to Wayland. So there's going to be a pretty big rework to this area coming for 3.10 anyways, so there's not really a place for these patches to go. :-(
(In reply to comment #10) > Maybe I'm missing something > because your screenshot doesn't look like the colors are double-premultiplied, > but it seems to me that is how the math is going to work out. For what it's worth, the screenshot was with an earlier version that rendered to a 32-bit RGBA image, then copied it into the real X drawable with CAIRO_OPERATOR_ADD to an opaque black background. I'm not sure whether that makes a difference or not. I'll need to make sure the maths is right - I got a bit confused about what was expected to be premultiplied and what wasn't! Since the frame's colours and alpha currently take different routes through the system and get drawn at different times, I'm tempted to try an approach like this: - do the Cairo frame drawing into a 32-bit RGBA texture, with the clip area ensuring the middle is blank (and either copy it into the frame's X window, or leave the outer parts of the frame's X window blank) - make the mask for the frame's X window only take the client area - when we draw the window in the compositor, only draw the client area with the MetaShapedTexture, and add the 32-bit RGBA frame in the same "layer" (in terms of code structure, not the texture stacking!) as the shadow Does that sound viable? I don't suppose you happen to know a convenient example of a simple X demo with a non-trivial shape (xeyes?), and one with a non-trivial alpha channel? > But at a higher level - the problem we have with this patch set is that we > really want to move to using GTK+ CSS theming for the window borders I'd have preferred that - I agree that the Mutter theme format is pretty opaque to non-experts like me - but this doesn't help me to apply the desired theme to a 3.8-based system, so I'm going to have to keep working on this as a branch anyway. I'll keep putting patches here in case they're useful to anyone else, but if this is never going to be merged upstream anyway, I'll cut out the change to the theme format and make it take the <use-alpha/> code path unconditionally.
(In reply to comment #10) > I think the make your trick with painting over a black background fully work > you need to change the pipeline a bit in meta_shaped_texture_paint() Yes, you're right. If I set a titlebar to have rgba(240, 0, 0, 0.25) and rgba(80, 0, 0, 0.75) rectangles and put it over a black window, they're visibly different: the result should have a red channel of 60 in both cases, but the actual result looks as though it might well be r=15 and r=45 (which is what I'd expect from double-applying alpha). > But at a higher level - the problem we have with this patch set is that we > really want to move to using GTK+ CSS theming for the window borders, and stop > subjecting our artists to an esoteric and ad-hoc XML theme format. How do you want the window borders to be drawn? If I can make my compositor-side changes work the way you want, then you can at least use those as a prototype, even if my changes to the UI side are thrown away. (Or are you relying on every X application having client-side decorations?)
Review of attachment 241271 [details] [review]: I will ACK this patch and the other one, as it's a nice cleanup that will help us for CSS borders.
Review of attachment 241272 [details] [review]: You should be able to remove meta_frame_get_corner_radiuses.
Comment on attachment 241271 [details] [review] MetaFrames: factor out MetaUIFrame accessors for borders, corner radiuses c7c122539
Created attachment 241372 [details] [review] Let the UI layer (via the core) construct the frame mask --- v2 of Attachment #241272 [details]: also deletes meta_frame_get_corner_radiuses, meta_ui_get_corner_radiuses as requested.
Review of attachment 241372 [details] [review]: OK. I think in my CSS branch I called it "paint_mask", which I think is a bit clearer, but that's up to you.
Comment on attachment 241372 [details] [review] Let the UI layer (via the core) construct the frame mask c2a9ccb. (In reply to comment #17) > OK. I think in my CSS branch I called it "paint_mask", which I think is a bit > clearer, but that's up to you. I've kept it as-is (since this is the version I tested, and the version you gave positive review for :-) but feel free to rename it if you prefer the other name.
Created attachment 241757 [details] [review] Track changes to window frames' masks We have to notice when frames are repainted, and pick up an updated mask. Otherwise, we draw the RGB of the new frame with the alpha channel of the old frame: this is particularly visible if you have opaque window-title text on a semi-transparent frame, and use something whose window title changes regularly, like a web browser. --- No changes, I don't think, but it's had some third-party review now.
Created attachment 241758 [details] [review] Add support for alpha-blended window titles, etc. X compositing always uses opaque pixmaps, so we end up with the colour and shape of the window taking different routes through the system. The colour (opaque RGB) goes from the UI layer through Gdk into X, then back from X into the compositor as a pixmap. Meanwhile, the alpha channel goes directly from the UI layer to the compositor without leaving the Mutter process. The easiest way to make this happen is to call meta_frames_paint() once for the colours, then call it again, separately, for the alpha channel. Unfortunately, when we do that, we do have to compensate for Cairo and Clutter's use of premultiplied alpha, otherwise the colour channels get multiplied by the alpha and everything semitransparent comes out darker than it ought to be. --- This is a more streamlined version: I dropped the "we have an interesting alpha channel" flag in favour of always taking the "interesting alpha channel" path. Now with division by alpha, to get the colours right.
Created attachment 241759 [details] demonstration of correct alpha-blending The inactive frames (grey) are somewhat realistic, shown over wallpaper; the active frame (red) demonstrates that dividing colours by alpha works as intended.
metacity themes don't exist anymore, GTK+ themes support an alpha channel. This should be supported now.