Blob Blame History Raw
From 768818c8d071f066a2ab68f83381829838b5bf29 Mon Sep 17 00:00:00 2001
From: "Owen W. Taylor" <otaylor@fishsoup.net>
Date: Thu, 8 May 2014 18:44:15 -0400
Subject: [PATCH 1/2] Add support for quad-buffer stereo

Track the stereo status of windows using the new EXT_stereo_tree
GLX extension.

When stereo is enabled or disabled, a restart is triggered via
meta_restart() after a timeout, setting a _META_ENABLE_STEREO
property on the root window to indicate whether we should
turn on a stereo stage for clutter. The property avoids a loop,
since we need to enable stereo *before* initializing Clutter and GL,
but we need GL to figure out whether we have stereo windows.

Stereo windows are drawn to the stage using new functionality
in Cogl to setup a stereo context, select which buffer to draw
to, and draw either the left or right buffer of a stereo
texture_from_pixmap.
---
 clutter/clutter/clutter-paint-nodes.c        | 103 +++++++++++
 clutter/clutter/clutter-paint-nodes.h        |  13 ++
 src/compositor/compositor.c                  |   8 +
 src/compositor/meta-compositor-x11.c         | 127 +++++++++++++
 src/compositor/meta-compositor-x11.h         |   6 +
 src/compositor/meta-shaped-texture-private.h |   5 +-
 src/compositor/meta-shaped-texture.c         | 176 +++++++++++++++----
 src/compositor/meta-surface-actor-wayland.c  |   2 +-
 src/compositor/meta-surface-actor-x11.c      |  55 +++++-
 src/compositor/meta-surface-actor-x11.h      |   5 +
 src/compositor/meta-window-actor-private.h   |   5 +
 src/compositor/meta-window-actor.c           |  22 +++
 src/core/main.c                              |   4 +
 src/core/stereo.c                            | 154 ++++++++++++++++
 src/core/stereo.h                            |  28 +++
 src/meson.build                              |   2 +
 src/wayland/meta-wayland-actor-surface.c     |   4 +-
 17 files changed, 667 insertions(+), 52 deletions(-)
 create mode 100644 src/core/stereo.c
 create mode 100644 src/core/stereo.h

diff --git a/clutter/clutter/clutter-paint-nodes.c b/clutter/clutter/clutter-paint-nodes.c
index f1f7fce318..29a673e9c7 100644
--- a/clutter/clutter/clutter-paint-nodes.c
+++ b/clutter/clutter/clutter-paint-nodes.c
@@ -1970,3 +1970,106 @@ clutter_blur_node_new (unsigned int width,
 out:
   return (ClutterPaintNode *) blur_node;
 }
+
+/*
+ * ClutterStereoNode
+ */
+
+struct _ClutterStereoNode
+{
+  ClutterPaintNode parent_instance;
+
+  CoglStereoMode stereo_mode;
+};
+
+struct _ClutterStereoNodeClass
+{
+  ClutterPaintNodeClass parent_class;
+};
+
+G_DEFINE_TYPE (ClutterStereoNode, clutter_stereo_node, CLUTTER_TYPE_PAINT_NODE)
+
+static gboolean
+clutter_stereo_node_pre_draw (ClutterPaintNode    *node,
+                              ClutterPaintContext *paint_context)
+{
+  ClutterStereoNode *stereo_node = CLUTTER_STEREO_NODE (node);
+  CoglFramebuffer *fb =
+   clutter_paint_context_get_framebuffer (paint_context);
+
+  g_warn_if_fail (cogl_framebuffer_get_is_stereo (fb));
+
+  cogl_framebuffer_set_stereo_mode (fb, stereo_node->stereo_mode);
+
+  return TRUE;
+}
+
+static void
+clutter_stereo_node_post_draw (ClutterPaintNode    *node,
+                               ClutterPaintContext *paint_context)
+{
+  CoglFramebuffer *fb =
+   clutter_paint_context_get_framebuffer (paint_context);
+
+  cogl_framebuffer_set_stereo_mode (fb, COGL_STEREO_BOTH);
+}
+
+static const char *
+stereo_mode_to_string (CoglStereoMode stereo_mode)
+{
+  switch (stereo_mode)
+    {
+    case COGL_STEREO_BOTH:
+      return "both";
+    case COGL_STEREO_LEFT:
+      return "left";
+    case COGL_STEREO_RIGHT:
+      return "right";
+    }
+
+  g_assert_not_reached ();
+}
+
+static JsonNode *
+clutter_stereo_node_serialize (ClutterPaintNode *node)
+{
+  ClutterStereoNode *stereo_node = CLUTTER_STEREO_NODE (node);
+  g_autoptr (JsonBuilder) builder = NULL;
+  const char *stereo_mode_str;
+
+  builder = json_builder_new ();
+  json_builder_begin_object (builder);
+  json_builder_set_member_name (builder, "stereo-mode");
+  stereo_mode_str = stereo_mode_to_string (stereo_node->stereo_mode);
+  json_builder_add_string_value (builder, stereo_mode_str);
+  json_builder_end_object (builder);
+
+  return json_builder_get_root (builder);
+}
+
+static void
+clutter_stereo_node_class_init (ClutterStereoNodeClass *klass)
+{
+  ClutterPaintNodeClass *node_class;
+
+  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
+  node_class->pre_draw = clutter_stereo_node_pre_draw;
+  node_class->post_draw = clutter_stereo_node_post_draw;
+  node_class->serialize = clutter_stereo_node_serialize;
+}
+
+static void
+clutter_stereo_node_init (ClutterStereoNode *stereo_node)
+{
+}
+
+ClutterPaintNode *
+clutter_stereo_node_new (CoglStereoMode stereo_mode)
+{
+  ClutterStereoNode *stereo_node;
+
+  stereo_node = _clutter_paint_node_create (CLUTTER_TYPE_STEREO_NODE);
+  stereo_node->stereo_mode = stereo_mode;
+
+  return CLUTTER_PAINT_NODE (stereo_node);
+}
diff --git a/clutter/clutter/clutter-paint-nodes.h b/clutter/clutter/clutter-paint-nodes.h
index 7f0d12857a..77d1ab05b6 100644
--- a/clutter/clutter/clutter-paint-nodes.h
+++ b/clutter/clutter/clutter-paint-nodes.h
@@ -284,6 +284,19 @@ ClutterPaintNode * clutter_blur_node_new (unsigned int width,
                                           unsigned int height,
                                           float        sigma);
 
+#define CLUTTER_TYPE_STEREO_NODE                (clutter_stereo_node_get_type ())
+#define CLUTTER_STEREO_NODE(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_STEREO_NODE, ClutterStereoNode))
+#define CLUTTER_IS_STEREO_NODE(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_STEREO_NODE))
+
+typedef struct _ClutterStereoNode               ClutterStereoNode;
+typedef struct _ClutterStereoNodeClass          ClutterStereoNodeClass;
+
+CLUTTER_EXPORT
+GType clutter_stereo_node_get_type (void) G_GNUC_CONST;
+
+CLUTTER_EXPORT
+ClutterPaintNode * clutter_stereo_node_new (CoglStereoMode stereo_mode);
+
 G_END_DECLS
 
 #endif /* __CLUTTER_PAINT_NODES_H__ */
diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c
index 1770550d4c..a4bd1252ae 100644
--- a/src/compositor/compositor.c
+++ b/src/compositor/compositor.c
@@ -66,6 +66,7 @@
 #include "compositor/meta-window-actor-private.h"
 #include "compositor/meta-window-group-private.h"
 #include "core/frame.h"
+#include "core/stereo.h"
 #include "core/util-private.h"
 #include "core/window-private.h"
 #include "meta/compositor-mutter.h"
@@ -910,6 +911,7 @@ meta_compositor_sync_stack (MetaCompositor  *compositor,
     meta_compositor_get_instance_private (compositor);
   MetaWindowActor *top_window_actor;
   GList *old_stack;
+  int stereo_window_count = 0;
 
   /* This is painful because hidden windows that we are in the process
    * of animating out of existence. They'll be at the bottom of the
@@ -986,12 +988,18 @@ meta_compositor_sync_stack (MetaCompositor  *compositor,
        */
       priv->windows = g_list_prepend (priv->windows, actor);
 
+      if (meta_window_actor_is_stereo (actor))
+        stereo_window_count++;
+
       stack = g_list_remove (stack, window);
       old_stack = g_list_remove (old_stack, actor);
     }
 
   sync_actor_stacking (compositor);
 
+  if (!meta_is_wayland_compositor ())
+    meta_stereo_set_have_stereo_windows (stereo_window_count > 0);
+
   top_window_actor = get_top_visible_window_actor (compositor);
 
   if (priv->top_window_actor == top_window_actor)
diff --git a/src/compositor/meta-compositor-x11.c b/src/compositor/meta-compositor-x11.c
index 1d0ba4c8d8..afbe3f57e2 100644
--- a/src/compositor/meta-compositor-x11.c
+++ b/src/compositor/meta-compositor-x11.c
@@ -31,6 +31,8 @@
 #include "compositor/meta-sync-ring.h"
 #include "compositor/meta-window-actor-x11.h"
 #include "core/display-private.h"
+#include "core/stack-tracker.h"
+#include "core/stereo.h"
 #include "x11/meta-x11-display-private.h"
 
 struct _MetaCompositorX11
@@ -50,8 +52,24 @@ struct _MetaCompositorX11
   gboolean xserver_uses_monotonic_clock;
   int64_t xserver_time_query_time_us;
   int64_t xserver_time_offset_us;
+
+  int glx_opcode;
+  gboolean stereo_tree_ext;
+  gboolean have_stereo_windows;
 };
 
+typedef struct
+{
+  int type;
+  unsigned long serial;
+  Bool send_event;
+  Display *display;
+  int extension;
+  int evtype;
+  Drawable window;
+  Bool stereo_tree;
+} StereoNotifyEvent;
+
 G_DEFINE_TYPE (MetaCompositorX11, meta_compositor_x11, META_TYPE_COMPOSITOR)
 
 static void
@@ -95,6 +113,27 @@ meta_compositor_x11_process_xevent (MetaCompositorX11 *compositor_x11,
       if (window)
         process_damage (compositor_x11, (XDamageNotifyEvent *) xevent, window);
     }
+  else if (xevent->type == GenericEvent &&
+           xevent->xcookie.extension == compositor_x11->glx_opcode)
+    {
+      if (xevent->xcookie.evtype == GLX_STEREO_NOTIFY_EXT)
+        {
+          StereoNotifyEvent *stereo_event =
+            (StereoNotifyEvent *) (xevent->xcookie.data);
+
+          window = meta_x11_display_lookup_x_window (x11_display,
+                                                     stereo_event->window);
+          if (window)
+            {
+              MetaWindowActor *window_actor = meta_window_actor_from_window (window);
+              MetaDisplay *display = meta_window_get_display (window);
+
+              meta_window_actor_stereo_notify (window_actor,
+                                               stereo_event->stereo_tree);
+              meta_stack_tracker_queue_sync_stack (display->stack_tracker);
+            }
+        }
+    }
 
   if (compositor_x11->have_x11_sync_object)
     meta_sync_ring_handle_event (xevent);
@@ -107,6 +146,85 @@ meta_compositor_x11_process_xevent (MetaCompositorX11 *compositor_x11,
     meta_x11_handle_event (xevent);
 }
 
+#define GLX_STEREO_TREE_EXT        0x20F5
+#define GLX_STEREO_NOTIFY_MASK_EXT 0x00000001
+#define GLX_STEREO_NOTIFY_EXT      0x00000000
+
+static gboolean
+display_has_stereo_tree_ext (MetaX11Display *x11_display)
+{
+  Display     *xdisplay = x11_display->xdisplay;
+  const char  *extensions_string;
+
+  static const char * (*query_extensions_string) (Display *display,
+                                                  int      screen);
+
+  if (query_extensions_string == NULL)
+    query_extensions_string =
+      (const char * (*) (Display *, int))
+      cogl_get_proc_address ("glXQueryExtensionsString");
+
+  extensions_string = query_extensions_string (xdisplay,
+                                               meta_x11_display_get_screen_number (x11_display));
+
+  return extensions_string && strstr (extensions_string, "EXT_stereo_tree") != 0;
+}
+
+#include <GL/gl.h>
+
+gboolean
+meta_compositor_x11_window_is_stereo (MetaCompositorX11 *compositor_x11,
+                                      Window             xwindow)
+{
+  MetaCompositor *compositor = META_COMPOSITOR (compositor_x11);
+  MetaDisplay *display = meta_compositor_get_display (compositor);
+  Display *xdisplay = meta_x11_display_get_xdisplay (display->x11_display);
+
+  static int (*query_drawable) (Display      *dpy,
+                                Drawable      draw,
+                                int           attribute,
+                                unsigned int *value);
+
+  if (compositor_x11->stereo_tree_ext)
+    {
+      unsigned int stereo_tree = 0;
+
+      if (query_drawable == NULL)
+        query_drawable =
+          (int (*) (Display *, Drawable, int, unsigned int *))
+          cogl_get_proc_address ("glXQueryDrawable");
+
+      query_drawable (xdisplay, xwindow, GLX_STEREO_TREE_EXT, &stereo_tree);
+
+      return stereo_tree != 0;
+    }
+  else
+    return FALSE;
+}
+
+void
+meta_compositor_x11_select_stereo_notify (MetaCompositorX11 *compositor_x11,
+                                          Window             xwindow)
+{
+  MetaCompositor *compositor = META_COMPOSITOR (compositor_x11);
+  MetaDisplay *display = meta_compositor_get_display (compositor);
+  Display *xdisplay = meta_x11_display_get_xdisplay (display->x11_display);
+
+  static void (*select_event) (Display      *dpy,
+                               Drawable      draw,
+                               unsigned long event_mask);
+
+  if (compositor_x11->stereo_tree_ext)
+    {
+      if (select_event == NULL)
+        select_event =
+          (void (*) (Display *, Drawable, unsigned long))
+          cogl_get_proc_address ("glXSelectEvent");
+
+      select_event (xdisplay, xwindow, GLX_STEREO_NOTIFY_MASK_EXT);
+    }
+}
+
 static void
 determine_server_clock_source (MetaCompositorX11 *compositor_x11)
 {
@@ -142,6 +260,7 @@ meta_compositor_x11_manage (MetaCompositor  *compositor,
   MetaX11Display *x11_display = display->x11_display;
   Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
   int composite_version;
+  int glx_major_opcode, glx_first_event, glx_first_error;
   MetaBackend *backend = meta_get_backend ();
   Window xwindow;
 
@@ -166,10 +285,18 @@ meta_compositor_x11_manage (MetaCompositor  *compositor,
       return FALSE;
     }
 
+  if (XQueryExtension (xdisplay,
+                       "GLX",
+                       &glx_major_opcode, &glx_first_event, &glx_first_error))
+    compositor_x11->glx_opcode = glx_major_opcode;
+
   determine_server_clock_source (compositor_x11);
 
   meta_x11_display_set_cm_selection (display->x11_display);
 
+  compositor_x11->stereo_tree_ext =
+    display_has_stereo_tree_ext (display->x11_display);
+
   compositor_x11->output = display->x11_display->composite_overlay_window;
 
   xwindow = meta_backend_x11_get_xwindow (META_BACKEND_X11 (backend));
diff --git a/src/compositor/meta-compositor-x11.h b/src/compositor/meta-compositor-x11.h
index 42554feb39..61f3cd5950 100644
--- a/src/compositor/meta-compositor-x11.h
+++ b/src/compositor/meta-compositor-x11.h
@@ -36,4 +36,10 @@ void meta_compositor_x11_process_xevent (MetaCompositorX11 *compositor_x11,
 
 Window meta_compositor_x11_get_output_xwindow (MetaCompositorX11 *compositor_x11);
 
+gboolean meta_compositor_x11_window_is_stereo (MetaCompositorX11 *compositor_x11,
+                                               Window             xwindow);
+
+void meta_compositor_x11_select_stereo_notify (MetaCompositorX11 *compositor_x11,
+                                               Window             xwindow);
+
 #endif /* META_COMPOSITOR_X11_H */
diff --git a/src/compositor/meta-shaped-texture-private.h b/src/compositor/meta-shaped-texture-private.h
index 2fe1b8ea48..fadad07d69 100644
--- a/src/compositor/meta-shaped-texture-private.h
+++ b/src/compositor/meta-shaped-texture-private.h
@@ -31,8 +31,9 @@
 #include "meta/meta-shaped-texture.h"
 
 MetaShapedTexture *meta_shaped_texture_new (void);
-void meta_shaped_texture_set_texture (MetaShapedTexture *stex,
-                                      CoglTexture       *texture);
+void meta_shaped_texture_set_textures (MetaShapedTexture *stex,
+                                       CoglTexture       *texture,
+                                       CoglTexture       *texture_right);
 void meta_shaped_texture_set_is_y_inverted (MetaShapedTexture *stex,
                                             gboolean           is_y_inverted);
 void meta_shaped_texture_set_snippet (MetaShapedTexture *stex,
diff --git a/src/compositor/meta-shaped-texture.c b/src/compositor/meta-shaped-texture.c
index 6a8af828f0..43170a195d 100644
--- a/src/compositor/meta-shaped-texture.c
+++ b/src/compositor/meta-shaped-texture.c
@@ -83,8 +83,10 @@ struct _MetaShapedTexture
   GObject parent;
 
   MetaTextureTower *paint_tower;
+  MetaTextureTower *paint_tower_right;
 
   CoglTexture *texture;
+  CoglTexture *texture_right;
   CoglTexture *mask_texture;
   CoglSnippet *snippet;
 
@@ -151,6 +153,7 @@ static void
 meta_shaped_texture_init (MetaShapedTexture *stex)
 {
   stex->paint_tower = meta_texture_tower_new ();
+  stex->paint_tower_right = NULL;
 
   stex->buffer_scale = 1;
   stex->texture = NULL;
@@ -251,11 +254,11 @@ meta_shaped_texture_dispose (GObject *object)
 
   g_clear_handle_id (&stex->remipmap_timeout_id, g_source_remove);
 
-  if (stex->paint_tower)
-    meta_texture_tower_free (stex->paint_tower);
-  stex->paint_tower = NULL;
+  g_clear_pointer (&stex->paint_tower, meta_texture_tower_free);
+  g_clear_pointer (&stex->paint_tower_right, meta_texture_tower_free);
 
   g_clear_pointer (&stex->texture, cogl_object_unref);
+  g_clear_pointer (&stex->texture_right, cogl_object_unref);
 
   meta_shaped_texture_set_mask_texture (stex, NULL);
   meta_shaped_texture_reset_pipelines (stex);
@@ -521,14 +524,19 @@ paint_clipped_rectangle_node (MetaShapedTexture     *stex,
 }
 
 static void
-set_cogl_texture (MetaShapedTexture *stex,
-                  CoglTexture       *cogl_tex)
+set_cogl_textures (MetaShapedTexture *stex,
+                   CoglTexture       *cogl_tex,
+                   CoglTexture       *cogl_tex_right)
 {
   int width, height;
 
   cogl_clear_object (&stex->texture);
+  cogl_clear_object (&stex->texture_right);
 
-  if (cogl_tex != NULL)
+  stex->texture = cogl_tex;
+  stex->texture_right = cogl_tex_right;
+
+  if (cogl_tex)
     {
       stex->texture = cogl_object_ref (cogl_tex);
       width = cogl_texture_get_width (COGL_TEXTURE (cogl_tex));
@@ -540,6 +548,9 @@ set_cogl_texture (MetaShapedTexture *stex,
       height = 0;
     }
 
+  if (cogl_tex_right)
+    cogl_object_ref (cogl_tex_right);
+
   if (stex->tex_width != width ||
       stex->tex_height != height)
     {
@@ -553,8 +564,23 @@ set_cogl_texture (MetaShapedTexture *stex,
    * previous buffer. We only queue a redraw in response to surface
    * damage. */
 
+  if (cogl_tex_right)
+    {
+      if (!stex->paint_tower_right)
+        stex->paint_tower_right = meta_texture_tower_new ();
+    }
+  else
+    {
+      g_clear_pointer (&stex->paint_tower_right, meta_texture_tower_free);
+    }
+
   if (stex->create_mipmaps)
-    meta_texture_tower_set_base_texture (stex->paint_tower, cogl_tex);
+    {
+      meta_texture_tower_set_base_texture (stex->paint_tower, cogl_tex);
+
+      if (stex->paint_tower_right)
+        meta_texture_tower_set_base_texture (stex->paint_tower_right, cogl_tex_right);
+    }
 }
 
 static gboolean
@@ -582,6 +608,19 @@ flip_ints (int *x,
   *y = tmp;
 }
 
+static CoglFramebuffer *
+get_target_framebuffer (ClutterPaintNode    *root_node,
+                        ClutterPaintContext *paint_context)
+{
+  CoglFramebuffer *framebuffer;
+
+  framebuffer = clutter_paint_node_get_framebuffer (root_node);
+  if (!framebuffer)
+    framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+
+  return framebuffer;
+}
+
 static void
 do_paint_content (MetaShapedTexture   *stex,
                   ClutterPaintNode    *root_node,
@@ -622,9 +661,7 @@ do_paint_content (MetaShapedTexture   *stex,
    * improves performance, especially with software rendering.
    */
 
-  framebuffer = clutter_paint_node_get_framebuffer (root_node);
-  if (!framebuffer)
-    framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+  framebuffer = get_target_framebuffer (root_node, paint_context);
 
   if (stex->has_viewport_src_rect)
     {
@@ -826,13 +863,27 @@ do_paint_content (MetaShapedTexture   *stex,
 
 static CoglTexture *
 select_texture_for_paint (MetaShapedTexture   *stex,
-                          ClutterPaintContext *paint_context)
+                          ClutterPaintContext *paint_context,
+                          CoglStereoMode       stereo_mode)
 {
   CoglTexture *texture = NULL;
   int64_t now;
+  gboolean use_right_texture = FALSE;
 
-  if (!stex->texture)
-    return NULL;
+  switch (stereo_mode)
+    {
+    case COGL_STEREO_LEFT:
+    case COGL_STEREO_BOTH:
+      if (!stex->texture)
+        return NULL;
+      use_right_texture = FALSE;
+      break;
+    case COGL_STEREO_RIGHT:
+      if (!stex->texture_right)
+        return NULL;
+      use_right_texture = TRUE;
+      break;
+    }
 
   now = g_get_monotonic_time ();
 
@@ -843,14 +894,24 @@ select_texture_for_paint (MetaShapedTexture   *stex,
       if (age >= MIN_MIPMAP_AGE_USEC ||
           stex->fast_updates < MIN_FAST_UPDATES_BEFORE_UNMIPMAP)
         {
-          texture = meta_texture_tower_get_paint_texture (stex->paint_tower,
+          MetaTextureTower *paint_tower;
+
+          if (use_right_texture)
+            paint_tower = stex->paint_tower_right;
+          else
+            paint_tower = stex->paint_tower;
+
+          texture = meta_texture_tower_get_paint_texture (paint_tower,
                                                           paint_context);
         }
     }
 
   if (!texture)
     {
-      texture = stex->texture;
+      if (use_right_texture)
+        texture = stex->texture_right;
+      else
+        texture = stex->texture;
 
       if (stex->create_mipmaps)
         {
@@ -876,35 +937,57 @@ meta_shaped_texture_paint_content (ClutterContent      *content,
 {
   MetaShapedTexture *stex = META_SHAPED_TEXTURE (content);
   ClutterActorBox alloc;
-  CoglTexture *paint_tex = NULL;
   uint8_t opacity;
+  CoglFramebuffer *framebuffer;
+  gboolean is_stereo;
 
   if (stex->clip_region && cairo_region_is_empty (stex->clip_region))
     return;
 
-  /* The GL EXT_texture_from_pixmap extension does allow for it to be
-   * used together with SGIS_generate_mipmap, however this is very
-   * rarely supported. Also, even when it is supported there
-   * are distinct performance implications from:
-   *
-   *  - Updating mipmaps that we don't need
-   *  - Having to reallocate pixmaps on the server into larger buffers
-   *
-   * So, we just unconditionally use our mipmap emulation code. If we
-   * wanted to use SGIS_generate_mipmap, we'd have to  query COGL to
-   * see if it was supported (no API currently), and then if and only
-   * if that was the case, set the clutter texture quality to HIGH.
-   * Setting the texture quality to high without SGIS_generate_mipmap
-   * support for TFP textures will result in fallbacks to XGetImage.
-   */
-  paint_tex = select_texture_for_paint (stex, paint_context);
-  if (!paint_tex)
+  if (!stex->texture)
     return;
 
   opacity = clutter_actor_get_paint_opacity (actor);
   clutter_actor_get_content_box (actor, &alloc);
 
-  do_paint_content (stex, root_node, paint_context, paint_tex, &alloc, opacity);
+  framebuffer = get_target_framebuffer (root_node, paint_context);
+  is_stereo = (stex->texture_right &&
+               cogl_framebuffer_get_is_stereo (framebuffer));
+
+  if (is_stereo)
+    {
+      CoglTexture *texture_left;
+      CoglTexture *texture_right;
+      g_autoptr (ClutterPaintNode) left_node = NULL;
+      g_autoptr (ClutterPaintNode) right_node = NULL;
+
+      texture_left = select_texture_for_paint (stex, paint_context,
+                                               COGL_STEREO_LEFT);
+      texture_right = select_texture_for_paint (stex, paint_context,
+                                                COGL_STEREO_RIGHT);
+
+      left_node = clutter_stereo_node_new (COGL_STEREO_LEFT);
+      clutter_paint_node_set_static_name (left_node, "MetaShapedTexture (left)");
+      right_node = clutter_stereo_node_new (COGL_STEREO_RIGHT);
+      clutter_paint_node_set_static_name (right_node, "MetaShapedTexture (right)");
+
+      clutter_paint_node_add_child (root_node, left_node);
+      clutter_paint_node_add_child (root_node, right_node);
+
+      do_paint_content (stex, left_node, paint_context,
+                        texture_left, &alloc, opacity);
+      do_paint_content (stex, right_node, paint_context,
+                        texture_right, &alloc, opacity);
+    }
+  else
+    {
+      CoglTexture *texture;
+
+      texture = select_texture_for_paint (stex, paint_context,
+                                          COGL_STEREO_BOTH);
+      do_paint_content (stex, root_node, paint_context,
+                        texture, &alloc, opacity);
+    }
 }
 
 static gboolean
@@ -946,6 +1029,12 @@ meta_shaped_texture_set_create_mipmaps (MetaShapedTexture *stex,
       stex->create_mipmaps = create_mipmaps;
       base_texture = create_mipmaps ? stex->texture : NULL;
       meta_texture_tower_set_base_texture (stex->paint_tower, base_texture);
+
+      if (stex->paint_tower_right)
+        {
+          base_texture = create_mipmaps ? stex->texture_right : NULL;
+          meta_texture_tower_set_base_texture (stex->paint_tower_right, base_texture);
+        }
     }
 }
 
@@ -1079,6 +1168,14 @@ meta_shaped_texture_update_area (MetaShapedTexture     *stex,
                                   y,
                                   width,
                                   height);
+  if (stex->paint_tower_right)
+    {
+      meta_texture_tower_update_area (stex->paint_tower_right,
+                                      x,
+                                      y,
+                                      width,
+                                      height);
+    }
 
   stex->prev_invalidation = stex->last_invalidation;
   stex->last_invalidation = g_get_monotonic_time ();
@@ -1098,20 +1195,21 @@ meta_shaped_texture_update_area (MetaShapedTexture     *stex,
 }
 
 /**
- * meta_shaped_texture_set_texture:
+ * meta_shaped_texture_set_textures:
  * @stex: The #MetaShapedTexture
  * @pixmap: The #CoglTexture to display
  */
 void
-meta_shaped_texture_set_texture (MetaShapedTexture *stex,
-                                 CoglTexture       *texture)
+meta_shaped_texture_set_textures (MetaShapedTexture *stex,
+                                  CoglTexture       *texture,
+                                  CoglTexture       *texture_right)
 {
   g_return_if_fail (META_IS_SHAPED_TEXTURE (stex));
 
-  if (stex->texture == texture)
+  if (stex->texture == texture && stex->texture_right == texture_right)
     return;
 
-  set_cogl_texture (stex, texture);
+  set_cogl_textures (stex, texture, texture_right);
 }
 
 /**
diff --git a/src/compositor/meta-surface-actor-wayland.c b/src/compositor/meta-surface-actor-wayland.c
index a182ad8513..1ddc83db2b 100644
--- a/src/compositor/meta-surface-actor-wayland.c
+++ b/src/compositor/meta-surface-actor-wayland.c
@@ -148,7 +148,7 @@ meta_surface_actor_wayland_dispose (GObject *object)
 
   stex = meta_surface_actor_get_texture (META_SURFACE_ACTOR (self));
   if (stex)
-    meta_shaped_texture_set_texture (stex, NULL);
+    meta_shaped_texture_set_textures (stex, NULL, NULL);
 
   if (self->surface)
     {
diff --git a/src/compositor/meta-surface-actor-x11.c b/src/compositor/meta-surface-actor-x11.c
index 41ae2dffbc..c7c3d08c36 100644
--- a/src/compositor/meta-surface-actor-x11.c
+++ b/src/compositor/meta-surface-actor-x11.c
@@ -30,6 +30,7 @@
 #include <X11/extensions/Xcomposite.h>
 
 #include "cogl/winsys/cogl-texture-pixmap-x11.h"
+#include "compositor/meta-compositor-x11.h"
 #include "compositor/meta-cullable.h"
 #include "compositor/meta-shaped-texture-private.h"
 #include "compositor/meta-window-actor-private.h"
@@ -47,6 +48,7 @@ struct _MetaSurfaceActorX11
   MetaDisplay *display;
 
   CoglTexture *texture;
+  CoglTexture *texture_right;
   Pixmap pixmap;
   Damage damage;
 
@@ -62,6 +64,8 @@ struct _MetaSurfaceActorX11
   guint size_changed : 1;
 
   guint unredirected   : 1;
+
+  guint stereo : 1;
 };
 
 G_DEFINE_TYPE (MetaSurfaceActorX11,
@@ -101,7 +105,7 @@ detach_pixmap (MetaSurfaceActorX11 *self)
    * you are supposed to be able to free a GLXPixmap after freeing the underlying
    * pixmap, but it certainly doesn't work with current DRI/Mesa
    */
-  meta_shaped_texture_set_texture (stex, NULL);
+  meta_shaped_texture_set_textures (stex, NULL, NULL);
   cogl_flush ();
 
   meta_x11_error_trap_push (display->x11_display);
@@ -110,6 +114,7 @@ detach_pixmap (MetaSurfaceActorX11 *self)
   meta_x11_error_trap_pop (display->x11_display);
 
   g_clear_pointer (&self->texture, cogl_object_unref);
+  g_clear_pointer (&self->texture_right, cogl_object_unref);
 }
 
 static void
@@ -119,23 +124,37 @@ set_pixmap (MetaSurfaceActorX11 *self,
   CoglContext *ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
   MetaShapedTexture *stex = meta_surface_actor_get_texture (META_SURFACE_ACTOR (self));
   GError *error = NULL;
-  CoglTexture *texture;
+  CoglTexturePixmapX11 *texture;
+  CoglTexturePixmapX11 *texture_right;
 
   g_assert (self->pixmap == None);
   self->pixmap = pixmap;
 
-  texture = COGL_TEXTURE (cogl_texture_pixmap_x11_new (ctx, self->pixmap, FALSE, &error));
+  if (self->stereo)
+    texture = cogl_texture_pixmap_x11_new_left (ctx, pixmap, FALSE, &error);
+  else
+    texture = cogl_texture_pixmap_x11_new (ctx, pixmap, FALSE, &error);
+
+  if (self->stereo)
+    texture_right = cogl_texture_pixmap_x11_new_right (texture);
+  else
+    texture_right = NULL;
 
   if (error != NULL)
     {
       g_warning ("Failed to allocate stex texture: %s", error->message);
       g_error_free (error);
     }
-  else if (G_UNLIKELY (!cogl_texture_pixmap_x11_is_using_tfp_extension (COGL_TEXTURE_PIXMAP_X11 (texture))))
+  else if (G_UNLIKELY (!cogl_texture_pixmap_x11_is_using_tfp_extension (texture)))
     g_warning ("NOTE: Not using GLX TFP!");
 
-  self->texture = texture;
-  meta_shaped_texture_set_texture (stex, texture);
+  self->texture = COGL_TEXTURE (texture);
+  if (self->stereo)
+    self->texture_right = COGL_TEXTURE (texture_right);
+
+  meta_shaped_texture_set_textures (stex,
+                                    COGL_TEXTURE (texture),
+                                    COGL_TEXTURE (texture_right));;
 }
 
 static void
@@ -372,8 +391,8 @@ reset_texture (MetaSurfaceActorX11 *self)
   /* Setting the texture to NULL will cause all the FBO's cached by the
    * shaped texture's MetaTextureTower to be discarded and recreated.
    */
-  meta_shaped_texture_set_texture (stex, NULL);
-  meta_shaped_texture_set_texture (stex, self->texture);
+  meta_shaped_texture_set_textures (stex, NULL, NULL);
+  meta_shaped_texture_set_textures (stex, self->texture, self->texture_right);
 }
 
 MetaSurfaceActor *
@@ -381,12 +400,18 @@ meta_surface_actor_x11_new (MetaWindow *window)
 {
   MetaSurfaceActorX11 *self = g_object_new (META_TYPE_SURFACE_ACTOR_X11, NULL);
   MetaDisplay *display = meta_window_get_display (window);
+  MetaCompositorX11 *compositor_x11 = META_COMPOSITOR_X11 (display->compositor);
+  Window xwindow;
 
   g_assert (!meta_is_wayland_compositor ());
 
   self->window = window;
   self->display = display;
 
+  xwindow = meta_window_x11_get_toplevel_xwindow (window);
+  self->stereo = meta_compositor_x11_window_is_stereo (compositor_x11, xwindow);
+  meta_compositor_x11_select_stereo_notify (compositor_x11, xwindow);
+
   g_signal_connect_object (self->display, "gl-video-memory-purged",
                            G_CALLBACK (reset_texture), self, G_CONNECT_SWAPPED);
 
@@ -420,3 +445,17 @@ meta_surface_actor_x11_set_size (MetaSurfaceActorX11 *self,
   self->last_height = height;
   meta_shaped_texture_set_fallback_size (stex, width, height);
 }
+
+void
+meta_surface_actor_x11_stereo_notify (MetaSurfaceActorX11 *self,
+                                      gboolean             stereo_tree)
+{
+  self->stereo = stereo_tree != FALSE;
+  detach_pixmap (self);
+}
+
+gboolean
+meta_surface_actor_x11_is_stereo (MetaSurfaceActorX11 *self)
+{
+  return self->stereo;
+}
diff --git a/src/compositor/meta-surface-actor-x11.h b/src/compositor/meta-surface-actor-x11.h
index 0a8517236a..369f631ae0 100644
--- a/src/compositor/meta-surface-actor-x11.h
+++ b/src/compositor/meta-surface-actor-x11.h
@@ -57,6 +57,11 @@ gboolean meta_surface_actor_x11_is_visible (MetaSurfaceActorX11 *self);
 
 void meta_surface_actor_x11_handle_updates (MetaSurfaceActorX11 *self);
 
+void meta_surface_actor_x11_stereo_notify (MetaSurfaceActorX11 *self,
+                                           gboolean             stereo_tree);
+
+gboolean meta_surface_actor_x11_is_stereo (MetaSurfaceActorX11 *self);
+
 G_END_DECLS
 
 #endif /* __META_SURFACE_ACTOR_X11_H__ */
diff --git a/src/compositor/meta-window-actor-private.h b/src/compositor/meta-window-actor-private.h
index 64741e4167..d498879902 100644
--- a/src/compositor/meta-window-actor-private.h
+++ b/src/compositor/meta-window-actor-private.h
@@ -99,4 +99,9 @@ void meta_window_actor_update_regions (MetaWindowActor *self);
 
 gboolean meta_window_actor_can_freeze_commits (MetaWindowActor *self);
 
+void meta_window_actor_stereo_notify (MetaWindowActor *actor,
+                                      gboolean         stereo_tree);
+
+gboolean meta_window_actor_is_stereo (MetaWindowActor *actor);
+
 #endif /* META_WINDOW_ACTOR_PRIVATE_H */
diff --git a/src/compositor/meta-window-actor.c b/src/compositor/meta-window-actor.c
index d4fc9a43a0..56c85e1788 100644
--- a/src/compositor/meta-window-actor.c
+++ b/src/compositor/meta-window-actor.c
@@ -1557,3 +1557,25 @@ out:
   clutter_actor_uninhibit_culling (actor);
   return surface;
 }
+
+void
+meta_window_actor_stereo_notify (MetaWindowActor *self,
+                                 gboolean         stereo_tree)
+{
+  MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self);
+
+  if (META_IS_SURFACE_ACTOR_X11 (priv->surface))
+    meta_surface_actor_x11_stereo_notify (META_SURFACE_ACTOR_X11 (priv->surface),
+                                          stereo_tree);
+}
+
+gboolean
+meta_window_actor_is_stereo (MetaWindowActor *self)
+{
+  MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self);
+
+  if (META_IS_SURFACE_ACTOR_X11 (priv->surface))
+    return meta_surface_actor_x11_is_stereo (META_SURFACE_ACTOR_X11 (priv->surface));
+  else
+    return FALSE;
+}
diff --git a/src/core/main.c b/src/core/main.c
index 6dabcfe73e..a07dda9ecc 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -88,6 +88,7 @@
 #include "meta/meta-backend.h"
 #include "meta/meta-x11-errors.h"
 #include "meta/prefs.h"
+#include "stereo.h"
 #include "ui/ui.h"
 #include "x11/session.h"
 
@@ -848,6 +849,9 @@ meta_init (void)
   if (!meta_is_wayland_compositor ())
     meta_select_display (opt_display_name);
 
+  if (!meta_is_wayland_compositor ())
+    meta_stereo_init ();
+
   meta_init_backend (backend_gtype, n_properties, prop_names, prop_values);
 
   for (i = 0; i < n_properties; i++)
diff --git a/src/core/stereo.c b/src/core/stereo.c
new file mode 100644
index 0000000000..817056527f
--- /dev/null
+++ b/src/core/stereo.c
@@ -0,0 +1,154 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * SECTION:stereo
+ * @short_description: Keep track of whether we are a stereo compositor
+ *
+ * With GLX, we need to use a different GL context for stereo and
+ * non-stereo support. Support for multiple GL contexts is unfinished
+ * in Cogl and entirely lacking in Clutter, so it's by far easier
+ * to just restart Mutter when we detect a stereo window.
+ *
+ * A property _MUTTER_ENABLE_STEREO is maintained on the root window
+ * to know whether we should initialize clutter for stereo or not.
+ * When the presence or absence of stereo windows mismatches the
+ * stereo-enabled state for a sufficiently long period of time,
+ * we restart Mutter.
+ */
+
+#include <config.h>
+
+#include <clutter/x11/clutter-x11.h>
+#include <gio/gunixinputstream.h>
+#include <X11/Xatom.h>
+
+#include "display-private.h"
+#include <meta/main.h>
+#include <meta/meta-x11-display.h>
+#include <meta/util.h>
+#include "stereo.h"
+#include "ui/ui.h"
+#include "util-private.h"
+
+static guint stereo_switch_id = 0;
+static gboolean stereo_enabled = FALSE;
+/* -1 so the first time meta_stereo_set_have_stereo_windows() is called
+ * we avoid the short-circuit and set up a timeout to restart
+ * if necessary */
+static gboolean stereo_have_windows = (gboolean)-1;
+static gboolean stereo_restart = FALSE;
+
+#define STEREO_ENABLE_WAIT 1000
+#define STEREO_DISABLE_WAIT 5000
+
+void
+meta_stereo_init (void)
+{
+  Display *xdisplay;
+  Window root;
+  Atom atom_enable_stereo;
+  Atom type;
+  int format;
+  unsigned long n_items, bytes_after;
+  guchar *data;
+
+  xdisplay = XOpenDisplay (NULL);
+  if (xdisplay == NULL)
+    meta_fatal ("Unable to open X display %s\n", XDisplayName (NULL));
+
+  root = DefaultRootWindow (xdisplay);
+  atom_enable_stereo = XInternAtom (xdisplay, "_MUTTER_ENABLE_STEREO", False);
+
+  XGetWindowProperty (xdisplay, root, atom_enable_stereo,
+                      0, 1, False, XA_INTEGER,
+                      &type, &format, &n_items, &bytes_after, &data);
+  if (type == XA_INTEGER)
+    {
+      if (format == 32 && n_items == 1 && bytes_after == 0)
+        {
+          stereo_enabled = *(long *)data;
+        }
+      else
+        {
+          meta_warning ("Bad value for _MUTTER_ENABLE_STEREO property\n");
+        }
+
+      XFree (data);
+    }
+  else if (type != None)
+    {
+      meta_warning ("Bad type for _MUTTER_ENABLE_STEREO property\n");
+    }
+
+  meta_verbose ("On startup, _MUTTER_ENABLE_STEREO=%s",
+                stereo_enabled ? "yes" : "no");
+  clutter_x11_set_use_stereo_stage (stereo_enabled);
+  XCloseDisplay (xdisplay);
+}
+
+static gboolean
+meta_stereo_switch (gpointer data)
+{
+  stereo_switch_id = 0;
+  stereo_restart = TRUE;
+
+  meta_restart (stereo_have_windows ?
+                _("Enabling stereo...") :
+                _("Disabling stereo..."));
+
+  return FALSE;
+}
+
+void
+meta_stereo_set_have_stereo_windows (gboolean have_windows)
+{
+  have_windows = have_windows != FALSE;
+
+  if (!stereo_restart && have_windows != stereo_have_windows)
+    {
+      MetaDisplay *display = meta_get_display ();
+      Display *xdisplay = meta_x11_display_get_xdisplay (display->x11_display);
+      Window root = DefaultRootWindow (xdisplay);
+      Atom atom_enable_stereo = XInternAtom (xdisplay, "_MUTTER_ENABLE_STEREO", False);
+      long value;
+
+      stereo_have_windows = have_windows;
+
+      if (stereo_have_windows)
+        meta_verbose ("Detected stereo windows\n");
+      else
+        meta_verbose ("No stereo windows detected\n");
+
+      value = stereo_have_windows;
+      XChangeProperty (xdisplay, root,
+                       atom_enable_stereo, XA_INTEGER, 32,
+                       PropModeReplace, (guchar *)&value, 1);
+
+      if (stereo_switch_id != 0)
+        {
+          g_source_remove (stereo_switch_id);
+          stereo_switch_id = 0;
+        }
+
+      if (stereo_have_windows != stereo_enabled)
+        stereo_switch_id = g_timeout_add (stereo_have_windows ? STEREO_ENABLE_WAIT : STEREO_DISABLE_WAIT,
+                                          meta_stereo_switch, NULL);
+    }
+}
diff --git a/src/core/stereo.h b/src/core/stereo.h
new file mode 100644
index 0000000000..ccd1d702a1
--- /dev/null
+++ b/src/core/stereo.h
@@ -0,0 +1,28 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef META_STEREO_H
+#define META_STEREO_H
+
+void     meta_stereo_init                    (void);
+void     meta_stereo_set_have_stereo_windows (gboolean have_windows);
+gboolean meta_stereo_is_restart              (void);
+void     meta_stereo_finish_restart          (void);
+
+#endif
diff --git a/src/meson.build b/src/meson.build
index 284bdf5220..c56438fbbe 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -394,6 +394,8 @@ mutter_sources = [
   'core/stack.h',
   'core/stack-tracker.c',
   'core/stack-tracker.h',
+  'core/stereo.c',
+  'core/stereo.h',
   'core/startup-notification.c',
   'core/startup-notification-private.h',
   'core/util.c',
diff --git a/src/wayland/meta-wayland-actor-surface.c b/src/wayland/meta-wayland-actor-surface.c
index 797795f861..d8d1f3ce16 100644
--- a/src/wayland/meta-wayland-actor-surface.c
+++ b/src/wayland/meta-wayland-actor-surface.c
@@ -193,7 +193,7 @@ meta_wayland_actor_surface_real_sync_actor_state (MetaWaylandActorSurface *actor
       snippet = meta_wayland_buffer_create_snippet (buffer);
       is_y_inverted = meta_wayland_buffer_is_y_inverted (buffer);
 
-      meta_shaped_texture_set_texture (stex, surface->texture);
+      meta_shaped_texture_set_textures (stex, surface->texture, NULL);
       meta_shaped_texture_set_snippet (stex, snippet);
       meta_shaped_texture_set_is_y_inverted (stex, is_y_inverted);
       meta_shaped_texture_set_buffer_scale (stex, surface->scale);
@@ -201,7 +201,7 @@ meta_wayland_actor_surface_real_sync_actor_state (MetaWaylandActorSurface *actor
     }
   else
     {
-      meta_shaped_texture_set_texture (stex, NULL);
+      meta_shaped_texture_set_textures (stex, NULL, NULL);
     }
 
   surface_rect = (cairo_rectangle_int_t) {
-- 
2.31.1


From 36517cd245584c5bcd3b730efaf6c3d2e7c9472d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Wed, 2 Jun 2021 16:55:45 +0200
Subject: [PATCH 2/2] compositor: Only check for stereo when using GLX

If EGL Xlib is used, we'll get bogus return value and crash.
---
 src/compositor/meta-compositor-x11.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/compositor/meta-compositor-x11.c b/src/compositor/meta-compositor-x11.c
index afbe3f57e2..4345f38b6f 100644
--- a/src/compositor/meta-compositor-x11.c
+++ b/src/compositor/meta-compositor-x11.c
@@ -153,9 +153,17 @@ meta_compositor_x11_process_xevent (MetaCompositorX11 *compositor_x11,
 static gboolean
 display_has_stereo_tree_ext (MetaX11Display *x11_display)
 {
+  MetaBackend *backend = meta_get_backend ();
+  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
+  CoglContext *cogl_context =
+    clutter_backend_get_cogl_context (clutter_backend);
+  CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
   Display     *xdisplay = x11_display->xdisplay;
   const char  *extensions_string;
 
+  if (cogl_renderer_get_winsys_id (cogl_renderer) != COGL_WINSYS_ID_GLX)
+    return FALSE;
+
   static const char * (*query_extensions_string) (Display *display,
                                                   int      screen);
 
-- 
2.31.1