blob: dc2cbcc84d4ad78cae91d96e801ed18514bd2d45 [file] [log] [blame]
/* -*- 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;
}