| /* -*- mode: c; c-basic-offset: 3 -*- */ |
| /* |
| * (c) Copyright IBM Corporation 2002 |
| * All Rights Reserved. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * on the rights to use, copy, modify, merge, publish, distribute, sub |
| * license, and/or sell copies of the Software, and to permit persons to whom |
| * the Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL |
| * VA LINUX SYSTEM, IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, |
| * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| * USE OR OTHER DEALINGS IN THE SOFTWARE. |
| * |
| * Authors: |
| * Ian Romanick <idr@us.ibm.com> |
| */ |
| /* $XFree86:$ */ |
| |
| #include "glheader.h" |
| #include "xf86drm.h" |
| #include "mtypes.h" |
| #include "macros.h" |
| #include "dd.h" |
| #include "vblank.h" |
| #include "xmlpool.h" |
| |
| static unsigned int msc_to_vblank(__DRIdrawablePrivate * dPriv, int64_t msc) |
| { |
| return (unsigned int)(msc - dPriv->msc_base + dPriv->vblank_base); |
| } |
| |
| static int64_t vblank_to_msc(__DRIdrawablePrivate * dPriv, unsigned int vblank) |
| { |
| return (int64_t)(vblank - dPriv->vblank_base + dPriv->msc_base); |
| } |
| |
| |
| /****************************************************************************/ |
| /** |
| * Get the current MSC refresh counter. |
| * |
| * Stores the 64-bit count of vertical refreshes since some (arbitrary) |
| * point in time in \c count. Unless the value wraps around, which it |
| * may, it will never decrease for a given drawable. |
| * |
| * \warning This function is called from \c glXGetVideoSyncSGI, which expects |
| * a \c count of type \c unsigned (32-bit), and \c glXGetSyncValuesOML, which |
| * expects a \c count of type \c int64_t (signed 64-bit). The kernel ioctl |
| * currently always returns a \c sequence of type \c unsigned. |
| * |
| * \param priv Pointer to the DRI screen private struct. |
| * \param dPriv Pointer to the DRI drawable private struct |
| * \param count Storage to hold MSC counter. |
| * \return Zero is returned on success. A negative errno value |
| * is returned on failure. |
| */ |
| int driDrawableGetMSC32( __DRIscreenPrivate * priv, |
| __DRIdrawablePrivate * dPriv, |
| int64_t * count) |
| { |
| drmVBlank vbl; |
| int ret; |
| |
| /* Don't wait for anything. Just get the current refresh count. */ |
| |
| vbl.request.type = DRM_VBLANK_RELATIVE; |
| vbl.request.sequence = 0; |
| if ( dPriv && dPriv->vblFlags & VBLANK_FLAG_SECONDARY ) |
| vbl.request.type |= DRM_VBLANK_SECONDARY; |
| |
| ret = drmWaitVBlank( priv->fd, &vbl ); |
| |
| if (dPriv) { |
| *count = vblank_to_msc(dPriv, vbl.reply.sequence); |
| } else { |
| /* Old driver (no knowledge of drawable MSC callback) */ |
| *count = vbl.reply.sequence; |
| } |
| |
| return ret; |
| } |
| |
| /****************************************************************************/ |
| /** |
| * Wait for a specified refresh count. This implements most of the |
| * functionality of \c glXWaitForMscOML from the GLX_OML_sync_control spec. |
| * Waits for the \c target_msc refresh. If that has already passed, it |
| * waits until \f$(MSC \bmod divisor)\f$ is equal to \c remainder. If |
| * \c target_msc is 0, use the behavior of glXWaitVideoSyncSGI(), which |
| * omits the initial check against a target MSC value. |
| * |
| * This function is actually something of a hack. The problem is that, at |
| * the time of this writing, none of the existing DRM modules support an |
| * ioctl that returns a 64-bit count (at least not on 32-bit platforms). |
| * However, this function exists to support a GLX function that requires |
| * the use of 64-bit counts. As such, there is a little bit of ugly |
| * hackery at the end of this function to make the 32-bit count act like |
| * a 64-bit count. There are still some cases where this will break, but |
| * I believe it catches the most common cases. |
| * |
| * The real solution is to provide an ioctl that uses a 64-bit count. |
| * |
| * \param dpy Pointer to the \c Display. |
| * \param priv Pointer to the DRI drawable private. |
| * \param target_msc Desired refresh count to wait for. A value of 0 |
| * means to use the glXWaitVideoSyncSGI() behavior. |
| * \param divisor MSC divisor if \c target_msc is already reached. |
| * \param remainder Desired MSC remainder if \c target_msc is already |
| * reached. |
| * \param msc Buffer to hold MSC when done waiting. |
| * |
| * \return Zero on success or \c GLX_BAD_CONTEXT on failure. |
| */ |
| |
| int driWaitForMSC32( __DRIdrawablePrivate *priv, |
| int64_t target_msc, int64_t divisor, int64_t remainder, |
| int64_t * msc ) |
| { |
| drmVBlank vbl; |
| |
| |
| if ( divisor != 0 ) { |
| unsigned int target = (unsigned int)target_msc; |
| unsigned int next = target; |
| unsigned int r; |
| int dont_wait = (target_msc == 0); |
| |
| do { |
| /* dont_wait means we're using the glXWaitVideoSyncSGI() behavior. |
| * The first time around, just get the current count and proceed |
| * to the test for (MSC % divisor) == remainder. |
| */ |
| vbl.request.type = dont_wait ? DRM_VBLANK_RELATIVE : |
| DRM_VBLANK_ABSOLUTE; |
| vbl.request.sequence = next ? msc_to_vblank(priv, next) : 0; |
| if ( priv->vblFlags & VBLANK_FLAG_SECONDARY ) |
| vbl.request.type |= DRM_VBLANK_SECONDARY; |
| |
| if ( drmWaitVBlank( priv->driScreenPriv->fd, &vbl ) != 0 ) { |
| /* FIXME: This doesn't seem like the right thing to return here. |
| */ |
| return GLX_BAD_CONTEXT; |
| } |
| |
| *msc = vblank_to_msc(priv, vbl.reply.sequence); |
| |
| dont_wait = 0; |
| if (target_msc != 0 && *msc == target) |
| break; |
| |
| /* Assuming the wait-done test fails, the next refresh to wait for |
| * will be one that satisfies (MSC % divisor) == remainder. The |
| * value (MSC - (MSC % divisor) + remainder) is the refresh value |
| * closest to the current value that would satisfy the equation. |
| * If this refresh has already happened, we add divisor to obtain |
| * the next refresh after the current one that will satisfy it. |
| */ |
| r = (*msc % (unsigned int)divisor); |
| next = (*msc - r + (unsigned int)remainder); |
| if (next <= *msc) next += (unsigned int)divisor; |
| |
| } while ( r != (unsigned int)remainder ); |
| } |
| else { |
| /* If the \c divisor is zero, just wait until the MSC is greater |
| * than or equal to \c target_msc. |
| */ |
| |
| vbl.request.type = DRM_VBLANK_ABSOLUTE; |
| vbl.request.sequence = target_msc ? msc_to_vblank(priv, target_msc) : 0; |
| |
| if ( priv->vblFlags & VBLANK_FLAG_SECONDARY ) |
| vbl.request.type |= DRM_VBLANK_SECONDARY; |
| |
| if ( drmWaitVBlank( priv->driScreenPriv->fd, &vbl ) != 0 ) { |
| /* FIXME: This doesn't seem like the right thing to return here. |
| */ |
| return GLX_BAD_CONTEXT; |
| } |
| } |
| |
| *msc = vblank_to_msc(priv, vbl.reply.sequence); |
| |
| if ( *msc < target_msc ) { |
| *msc += 0x0000000100000000LL; |
| } |
| |
| return 0; |
| } |
| |
| |
| /****************************************************************************/ |
| /** |
| * Gets a set of default vertical-blank-wait flags based on the internal GLX |
| * API version and several configuration options. |
| */ |
| |
| GLuint driGetDefaultVBlankFlags( const driOptionCache *optionCache ) |
| { |
| GLuint flags = VBLANK_FLAG_INTERVAL; |
| int vblank_mode; |
| |
| |
| if ( driCheckOption( optionCache, "vblank_mode", DRI_ENUM ) ) |
| vblank_mode = driQueryOptioni( optionCache, "vblank_mode" ); |
| else |
| vblank_mode = DRI_CONF_VBLANK_DEF_INTERVAL_1; |
| |
| switch (vblank_mode) { |
| case DRI_CONF_VBLANK_NEVER: |
| flags = 0; |
| break; |
| case DRI_CONF_VBLANK_DEF_INTERVAL_0: |
| break; |
| case DRI_CONF_VBLANK_DEF_INTERVAL_1: |
| flags |= VBLANK_FLAG_THROTTLE; |
| break; |
| case DRI_CONF_VBLANK_ALWAYS_SYNC: |
| flags |= VBLANK_FLAG_SYNC; |
| break; |
| } |
| |
| return flags; |
| } |
| |
| |
| /****************************************************************************/ |
| /** |
| * Wrapper to call \c drmWaitVBlank. The main purpose of this function is to |
| * wrap the error message logging. The error message should only be logged |
| * the first time the \c drmWaitVBlank fails. If \c drmWaitVBlank is |
| * successful, \c vbl_seq will be set the sequence value in the reply. |
| * |
| * \param vbl Pointer to drmVBlank packet desribing how to wait. |
| * \param vbl_seq Location to store the current refresh counter. |
| * \param fd File descriptor use to call into the DRM. |
| * \return Zero on success or -1 on failure. |
| */ |
| |
| static int do_wait( drmVBlank * vbl, GLuint * vbl_seq, int fd ) |
| { |
| int ret; |
| |
| |
| ret = drmWaitVBlank( fd, vbl ); |
| if ( ret != 0 ) { |
| static GLboolean first_time = GL_TRUE; |
| |
| if ( first_time ) { |
| fprintf(stderr, |
| "%s: drmWaitVBlank returned %d, IRQs don't seem to be" |
| " working correctly.\nTry adjusting the vblank_mode" |
| " configuration parameter.\n", __FUNCTION__, ret); |
| first_time = GL_FALSE; |
| } |
| |
| return -1; |
| } |
| |
| *vbl_seq = vbl->reply.sequence; |
| return 0; |
| } |
| |
| |
| /****************************************************************************/ |
| /** |
| * Returns the default swap interval of the given drawable. |
| */ |
| |
| static unsigned |
| driGetDefaultVBlankInterval( const __DRIdrawablePrivate *priv ) |
| { |
| if ( (priv->vblFlags & (VBLANK_FLAG_THROTTLE | VBLANK_FLAG_SYNC)) != 0 ) { |
| return 1; |
| } |
| else { |
| return 0; |
| } |
| } |
| |
| |
| /****************************************************************************/ |
| /** |
| * Sets the default swap interval when the drawable is first bound to a |
| * direct rendering context. |
| */ |
| |
| void driDrawableInitVBlank( __DRIdrawablePrivate *priv ) |
| { |
| if ( priv->swap_interval == (unsigned)-1 && |
| !( priv->vblFlags & VBLANK_FLAG_NO_IRQ ) ) { |
| /* Get current vertical blank sequence */ |
| drmVBlank vbl; |
| |
| vbl.request.type = DRM_VBLANK_RELATIVE; |
| if ( priv->vblFlags & VBLANK_FLAG_SECONDARY ) |
| vbl.request.type |= DRM_VBLANK_SECONDARY; |
| vbl.request.sequence = 0; |
| do_wait( &vbl, &priv->vblSeq, priv->driScreenPriv->fd ); |
| priv->vblank_base = priv->vblSeq; |
| |
| priv->swap_interval = driGetDefaultVBlankInterval( priv ); |
| } |
| } |
| |
| |
| /****************************************************************************/ |
| /** |
| * Returns the current swap interval of the given drawable. |
| */ |
| |
| unsigned |
| driGetVBlankInterval( const __DRIdrawablePrivate *priv ) |
| { |
| if ( (priv->vblFlags & VBLANK_FLAG_INTERVAL) != 0 ) { |
| /* this must have been initialized when the drawable was first bound |
| * to a direct rendering context. */ |
| assert ( priv->swap_interval != (unsigned)-1 ); |
| |
| return priv->swap_interval; |
| } |
| else |
| return driGetDefaultVBlankInterval( priv ); |
| } |
| |
| |
| /****************************************************************************/ |
| /** |
| * Returns the current vertical blank sequence number of the given drawable. |
| */ |
| |
| void |
| driGetCurrentVBlank( __DRIdrawablePrivate *priv ) |
| { |
| drmVBlank vbl; |
| |
| vbl.request.type = DRM_VBLANK_RELATIVE; |
| if ( priv->vblFlags & VBLANK_FLAG_SECONDARY ) { |
| vbl.request.type |= DRM_VBLANK_SECONDARY; |
| } |
| vbl.request.sequence = 0; |
| |
| (void) do_wait( &vbl, &priv->vblSeq, priv->driScreenPriv->fd ); |
| } |
| |
| |
| /****************************************************************************/ |
| /** |
| * Waits for the vertical blank for use with glXSwapBuffers. |
| * |
| * \param missed_deadline Set to \c GL_TRUE if the MSC after waiting is later |
| * than the "target" based on \c priv->vblFlags. The idea is |
| * that if \c missed_deadline is set, then the application is |
| * not achieving its desired framerate. |
| * \return Zero on success, -1 on error. |
| */ |
| |
| int |
| driWaitForVBlank( __DRIdrawablePrivate *priv, GLboolean * missed_deadline ) |
| { |
| drmVBlank vbl; |
| unsigned original_seq; |
| unsigned deadline; |
| unsigned interval; |
| unsigned diff; |
| |
| *missed_deadline = GL_FALSE; |
| if ( (priv->vblFlags & (VBLANK_FLAG_INTERVAL | |
| VBLANK_FLAG_THROTTLE | |
| VBLANK_FLAG_SYNC)) == 0 || |
| (priv->vblFlags & VBLANK_FLAG_NO_IRQ) != 0 ) { |
| return 0; |
| } |
| |
| |
| /* VBLANK_FLAG_SYNC means to wait for at least one vertical blank. If |
| * that flag is not set, do a fake wait for zero vertical blanking |
| * periods so that we can get the current MSC. |
| * |
| * VBLANK_FLAG_INTERVAL and VBLANK_FLAG_THROTTLE mean to wait for at |
| * least one vertical blank since the last wait. Since do_wait modifies |
| * priv->vblSeq, we have to save the original value of priv->vblSeq for the |
| * VBLANK_FLAG_INTERVAL / VBLANK_FLAG_THROTTLE calculation later. |
| */ |
| |
| original_seq = priv->vblSeq; |
| interval = driGetVBlankInterval(priv); |
| deadline = original_seq + interval; |
| |
| vbl.request.type = DRM_VBLANK_RELATIVE; |
| if ( priv->vblFlags & VBLANK_FLAG_SECONDARY ) { |
| vbl.request.type |= DRM_VBLANK_SECONDARY; |
| } |
| vbl.request.sequence = ((priv->vblFlags & VBLANK_FLAG_SYNC) != 0) ? 1 : 0; |
| |
| if ( do_wait( & vbl, &priv->vblSeq, priv->driScreenPriv->fd ) != 0 ) { |
| return -1; |
| } |
| |
| diff = priv->vblSeq - deadline; |
| |
| /* No need to wait again if we've already reached the target */ |
| if (diff <= (1 << 23)) { |
| *missed_deadline = (priv->vblFlags & VBLANK_FLAG_SYNC) ? (diff > 0) : |
| GL_TRUE; |
| return 0; |
| } |
| |
| /* Wait until the target vertical blank. */ |
| vbl.request.type = DRM_VBLANK_ABSOLUTE; |
| if ( priv->vblFlags & VBLANK_FLAG_SECONDARY ) { |
| vbl.request.type |= DRM_VBLANK_SECONDARY; |
| } |
| vbl.request.sequence = deadline; |
| |
| if ( do_wait( & vbl, &priv->vblSeq, priv->driScreenPriv->fd ) != 0 ) { |
| return -1; |
| } |
| |
| diff = priv->vblSeq - deadline; |
| *missed_deadline = diff > 0 && diff <= (1 << 23); |
| |
| return 0; |
| } |