glx/dri3: Use four buffers until X driver supports async flips
authorKeith Packard <keithp@keithp.com>
Wed, 2 Jul 2014 20:26:22 +0000 (13:26 -0700)
committerKeith Packard <keithp@keithp.com>
Wed, 1 Oct 2014 03:08:28 +0000 (20:08 -0700)
A driver which doesn't have async flip support will queue up flips without any
way to replace them afterwards. This means we've got a scanout buffer pinned
as soon as we schedule a flip and so we need another buffer to keep from
stalling.

When vblank_mode=0, if there are only three buffers we do:

        current scanout buffer = 0 at MSC 0

        Render frame 1 to buffer 1
        PresentPixmap for buffer 1 at MSC 1

                This is sitting down in the kernel waiting for vblank to
                become the next scanout buffer

        Render frame 2 to buffer 2
        PresentPixmap for buffer 2 at MSC 1

                This cannot be displayed at MSC 1 because the
                kernel doesn't have any way to replace buffer 1 as the pending
                scanout buffer. So, best case this will get displayed at MSC 2.

Now we block after this, waiting for one of the three buffers to become idle.
We can't use buffer 0 because it is the scanout buffer. We can't use buffer 1
because it's sitting in the kernel waiting to become the next scanout buffer
and we can't use buffer 2 because that's the most recent frame which will
become the next scanout buffer if the application doesn't manage to generate
another complete frame by MSC 2.

With four buffers, we get:

        current scanout buffer = 0 at MSC 0

        Render frame 1 to buffer 1
        PresentPixmap for buffer 1 at MSC 1

                This is sitting down in the kernel waiting for vblank to
                become the next scanout buffer

        Render frame 2 to buffer 2
        PresentPixmap for buffer 2 at MSC 1

                This cannot be displayed at MSC 1 because the
                kernel doesn't have any way to replace buffer 1 as the pending
                scanout buffer. So, best case this will get displayed at MSC
                2. The X server will queue this swap until buffer 1 becomes
                the scanout buffer.

        Render frame 3 to buffer 3
        PresentPixmap for buffer 3 at MSC 1

                As soon as the X server sees this, it will replace the pending
                buffer 2 swap with this swap and release buffer 2 back to the
                application

        Render frame 4 to buffer 2
        PresentPixmap for buffer 2 at MSC 1

                Now we're in a steady state, flipping between buffer 2 and 3
                waiting for one of them to be queued to the kernel.

        ...

        current scanout buffer = 1 at MSC 1

                Now buffer 0 is free and (e.g.) buffer 2 is queued in
                the kernel to be the scanout buffer at MSC 2

        Render frames, flipping between buffer 0 and 3

When the system can replace a queued buffer, and we update Present to take
advantage of that, we can use three buffers and get:

        current scanout buffer = 0 at MSC 0

        Render frame 1 to buffer 1
        PresentPixmap for buffer 1 at MSC 1

                This is sitting waiting for vblank to become the next scanout
                buffer

        Render frame 2 to buffer 2
        PresentPixmap for buffer 2 at MSC 1

                Queue this for display at MSC 1
                1. There are three possible results:

                  1) We're still before MSC 1. Buffer 1 is released,
                     buffer 2 is queued waiting for MSC 1.

                  2) We're now after MSC 1. Buffer 0 was released at MSC 1.
                     Buffer 1 is the current scanout buffer.

                     a) If the user asked for a tearing update, we swap
                        scanout from buffer 1 to buffer 2 and release buffer
                        1.

                     b) If the user asked for non-tearing update, we
                        queue buffer 2 for the MSC 2.

                In all three cases, we have a buffer released (call it 'n'),
                ready to receive the next frame.

        Render frame 3 to buffer n
        PresentPixmap for buffer n

                If we're still before MSC 1, then we'll ask to present at MSC
                1. Otherwise, we'll ask to present at MSC 2.

Present already does this if the driver offers async flips, however it does
this by waiting for the right vblank event and sending an async flip right at
that point.

I've hacked the intel driver to offer this, but I get tearing at the top of
the screen. I think this is because flips are always done from within the
ring, and so the latency between the vblank event and the async flip happening
can cause tearing at the top of the screen.

That's why I'm keying the need for the extra buffer on the lack of 2D
driver support for async flips.

Signed-off-by: Keith Packard <keithp@keithp.com>
Acked-by: Jason Ekstrand <jason.ekstrand@intel.com>
Tested-by: Dylan Baker <baker.dylan.c@gmail.com>
src/glx/dri3_glx.c
src/glx/dri3_priv.h

index e3fc4def86e4ef4b3ae4468e336f6eb04ca3ba60..753b8d88de4579bfd73eafc5cf743b15fbbb3643 100644 (file)
@@ -271,8 +271,11 @@ static void
 dri3_update_num_back(struct dri3_drawable *priv)
 {
    priv->num_back = 1;
-   if (priv->flipping)
+   if (priv->flipping) {
+      if (!priv->is_pixmap && !(priv->present_capabilities & XCB_PRESENT_CAPABILITY_ASYNC))
+         priv->num_back++;
       priv->num_back++;
+   }
    if (priv->swap_interval == 0)
       priv->num_back++;
 }
@@ -976,6 +979,9 @@ dri3_update_drawable(__DRIdrawable *driDrawable, void *loaderPrivate)
       xcb_get_geometry_reply_t                  *geom_reply;
       xcb_void_cookie_t                         cookie;
       xcb_generic_error_t                       *error;
+      xcb_present_query_capabilities_cookie_t   present_capabilities_cookie;
+      xcb_present_query_capabilities_reply_t    *present_capabilities_reply;
+
 
       /* Try to select for input on the window.
        *
@@ -994,6 +1000,8 @@ dri3_update_drawable(__DRIdrawable *driDrawable, void *loaderPrivate)
                                                 XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY|
                                                 XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY);
 
+      present_capabilities_cookie = xcb_present_query_capabilities(c, priv->base.xDrawable);
+
       /* Create an XCB event queue to hold present events outside of the usual
        * application event queue
        */
@@ -1023,6 +1031,16 @@ dri3_update_drawable(__DRIdrawable *driDrawable, void *loaderPrivate)
 
       error = xcb_request_check(c, cookie);
 
+      present_capabilities_reply = xcb_present_query_capabilities_reply(c,
+                                                                        present_capabilities_cookie,
+                                                                        NULL);
+
+      if (present_capabilities_reply) {
+         priv->present_capabilities = present_capabilities_reply->capabilities;
+         free(present_capabilities_reply);
+      } else
+         priv->present_capabilities = 0;
+
       if (error) {
          if (error->error_code != BadWindow) {
             free(error);
index 248fa28dfc261a6422e2793dad057f1f1cc108b8..bdfe224fed02ef60aec912a2793707cb612c3acf 100644 (file)
@@ -147,7 +147,7 @@ struct dri3_context
    __DRIcontext *driContext;
 };
 
-#define DRI3_MAX_BACK   3
+#define DRI3_MAX_BACK   4
 #define DRI3_BACK_ID(i) (i)
 #define DRI3_FRONT_ID   (DRI3_MAX_BACK)
 
@@ -172,6 +172,10 @@ struct dri3_drawable {
    uint8_t is_pixmap;
    uint8_t flipping;
 
+   /* Present extension capabilities
+    */
+   uint32_t present_capabilities;
+
    /* SBC numbers are tracked by using the serial numbers
     * in the present request and complete events
     */