hasWaitSync -> blocksForRetrace and syncsToVBlank

since that's not the same.
also autodetect triple buffering

REVIEW: 109783
This commit is contained in:
Thomas Lübking 2013-03-28 21:52:26 +01:00
parent 2a726bb289
commit 9446abc696
8 changed files with 166 additions and 33 deletions

View file

@ -235,7 +235,7 @@ void Compositor::slotCompositingOptionsInitialized()
}
m_xrrRefreshRate = KWin::currentRefreshRate();
fpsInterval = (options->maxFpsInterval() << 10);
if (m_scene->waitSyncAvailable()) { // if we do vsync, set the fps to the next multiple of the vblank rate
if (m_scene->syncsToVBlank()) { // if we do vsync, set the fps to the next multiple of the vblank rate
vBlankInterval = (1000 << 10) / m_xrrRefreshRate;
fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval);
} else
@ -648,7 +648,7 @@ void Compositor::setCompositeTimer()
uint padding = m_timeSinceLastVBlank << 10;
if (m_scene->waitSyncAvailable()) {
if (m_scene->blocksForRetrace()) {
// TODO: make vBlankTime dynamic?!
// It's required because glXWaitVideoSync will *likely* block a full frame if one enters

View file

@ -53,6 +53,9 @@ EglOnXBackend::~EglOnXBackend()
}
}
static bool gs_tripleBufferUndetected = true;
static bool gs_tripleBufferNeedsDetection = false;
void EglOnXBackend::init()
{
if (!initRenderingContext()) {
@ -88,6 +91,10 @@ void EglOnXBackend::init()
}
}
}
setSyncsToVBlank(false);
setBlocksForRetrace(false);
gs_tripleBufferNeedsDetection = false;
m_swapProfiler.init();
if (surfaceHasSubPost) {
kDebug(1212) << "EGL implementation and surface support eglPostSubBufferNV, let's use it";
@ -98,7 +105,13 @@ void EglOnXBackend::init()
if (val >= 1) {
if (eglSwapInterval(dpy, 1)) {
kDebug(1212) << "Enabled v-sync";
setHasWaitSync(true);
setSyncsToVBlank(true);
const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER");
if (!tripleBuffer.isEmpty()) {
setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0);
gs_tripleBufferUndetected = false;
}
gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected;
}
} else {
kWarning(1212) << "Cannot enable v-sync as max. swap interval is" << val;
@ -249,8 +262,19 @@ void EglOnXBackend::present()
const bool fullRepaint = (lastDamage() == displayRegion);
if (fullRepaint || !surfaceHasSubPost) {
if (gs_tripleBufferNeedsDetection) {
eglWaitGL();
m_swapProfiler.begin();
}
// the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation)
eglSwapBuffers(dpy, surface);
if (gs_tripleBufferNeedsDetection) {
eglWaitGL();
if (char result = m_swapProfiler.end()) {
gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false;
setBlocksForRetrace(result == 'd');
}
}
} else {
// a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area
foreach (const QRect & r, lastDamage().rects()) {

View file

@ -69,6 +69,9 @@ GlxBackend::~GlxBackend()
checkGLError("Cleanup");
}
static bool gs_tripleBufferUndetected = true;
static bool gs_tripleBufferNeedsDetection = false;
void GlxBackend::init()
{
initGLX();
@ -96,26 +99,32 @@ void GlxBackend::init()
initGL(GlxPlatformInterface);
// Check whether certain features are supported
haveSwapInterval = glXSwapIntervalMESA || glXSwapIntervalEXT || glXSwapIntervalSGI;
setSyncsToVBlank(false);
setBlocksForRetrace(false);
haveWaitSync = false;
gs_tripleBufferNeedsDetection = false;
m_swapProfiler.init();
const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage;
if (wantSync) {
if (glXGetVideoSync && haveSwapInterval && glXIsDirect(display(), ctx)) {
unsigned int sync;
if (glXGetVideoSync(&sync) == 0) {
if (glXWaitVideoSync(1, 0, &sync) == 0) {
// NOTICE at this time we should actually check whether we can successfully
// deactivate the swapInterval "glXSwapInterval(0) == 0"
// (because we don't actually want it active unless we explicitly run a glXSwapBuffers)
// However mesa/dri will return a range error (6) because deactivating the
// swapinterval (as of today) seems completely unsupported
setHasWaitSync(true);
if (wantSync && glXIsDirect(display(), ctx)) {
if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable
setSwapInterval(1);
setSyncsToVBlank(true);
const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER");
if (!tripleBuffer.isEmpty()) {
setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0);
gs_tripleBufferUndetected = false;
}
gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected;
} else if (glXGetVideoSync) {
unsigned int sync;
if (glXGetVideoSync(&sync) == 0 && glXWaitVideoSync(1, 0, &sync) == 0) {
setSyncsToVBlank(true);
setBlocksForRetrace(true);
haveWaitSync = true;
} else
qWarning() << "NO VSYNC! glXWaitVideoSync(1,0,&uint) isn't 0 but" << glXWaitVideoSync(1, 0, &sync);
qWarning() << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken";
} else
qWarning() << "NO VSYNC! glXGetVideoSync(&uint) isn't 0 but" << glXGetVideoSync(&sync);
} else
qWarning() << "NO VSYNC! glXGetVideoSync, haveSwapInterval, glXIsDirect" <<
bool(glXGetVideoSync) << haveSwapInterval << glXIsDirect(display(), ctx);
qWarning() << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported";
} else {
// disable v-sync (if possible)
setSwapInterval(0);
@ -364,7 +373,7 @@ void GlxBackend::setSwapInterval(int interval)
void GlxBackend::waitSync()
{
// NOTE that vsync has no effect with indirect rendering
if (waitSyncAvailable()) {
if (haveWaitSync) {
#if VSYNC_DEBUG
startRenderTimer();
#endif
@ -403,8 +412,19 @@ void GlxBackend::present()
if (fullRepaint) {
if (haveSwapInterval) {
if (gs_tripleBufferNeedsDetection) {
glXWaitGL();
m_swapProfiler.begin();
}
glXSwapBuffers(display(), glxWindow);
startRenderTimer();
if (gs_tripleBufferNeedsDetection) {
glXWaitGL();
if (char result = m_swapProfiler.end()) {
gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false;
setBlocksForRetrace(result == 'd');
}
}
} else {
waitSync(); // calls startRenderTimer();
glXSwapBuffers(display(), glxWindow);

View file

@ -64,7 +64,7 @@ private:
GLXFBConfig fbconfig;
GLXWindow glxWindow;
GLXContext ctx;
bool haveSwapInterval;
bool haveSwapInterval, haveWaitSync;
friend class GlxTexture;
};

View file

@ -561,7 +561,12 @@ void Scene::extendPaintRegion(QRegion &region, bool opaqueFullscreen)
Q_UNUSED(opaqueFullscreen);
}
bool Scene::waitSyncAvailable() const
bool Scene::blocksForRetrace() const
{
return false;
}
bool Scene::syncsToVBlank() const
{
return false;
}

View file

@ -100,7 +100,8 @@ public:
enum ImageFilterType { ImageFilterFast, ImageFilterGood };
// there's nothing to paint (adjust time_diff later)
virtual void idle();
virtual bool waitSyncAvailable() const;
virtual bool blocksForRetrace() const;
virtual bool syncsToVBlank() const;
virtual OverlayWindow* overlayWindow() = 0;
public Q_SLOTS:
// a window has been destroyed

View file

@ -76,7 +76,8 @@ extern int currentRefreshRate();
//****************************************
OpenGLBackend::OpenGLBackend()
: m_overlayWindow(new OverlayWindow()) // TODO: maybe create only if needed?
, m_waitSync(false)
, m_syncsToVBlank(false)
, m_blocksForRetrace(false)
, m_directRendering(false)
, m_failed(false)
{
@ -239,9 +240,14 @@ OverlayWindow *SceneOpenGL::overlayWindow()
return m_backend->overlayWindow();
}
bool SceneOpenGL::waitSyncAvailable() const
bool SceneOpenGL::syncsToVBlank() const
{
return m_backend->waitSyncAvailable();
return m_backend->syncsToVBlank();
}
bool SceneOpenGL::blocksForRetrace() const
{
return m_backend->blocksForRetrace();
}
void SceneOpenGL::idle()
@ -2220,4 +2226,34 @@ bool SceneOpenGLShadow::prepareBackend()
return true;
}
SwapProfiler::SwapProfiler()
{
init();
}
void SwapProfiler::init()
{
m_time = 2 * 1000*1000; // we start with a long time mean of 2ms ...
m_counter = 0;
}
void SwapProfiler::begin()
{
m_timer.start();
}
char SwapProfiler::end()
{
// .. and blend in actual values.
// this way we prevent extremes from killing our long time mean
m_time = (10*m_time + m_timer.nsecsElapsed())/11;
if (++m_counter > 500) {
const bool blocks = m_time > 1000 * 1000; // 1ms, i get ~250µs and ~7ms w/o triple buffering...
kDebug(1212) << "Triple buffering detection:" << QString(blocks ? "NOT available" : "Available") <<
" - Mean block time:" << m_time/(1000.0*1000.0) << "ms";
return blocks ? 'd' : 't';
}
return 0;
}
} // namespace

View file

@ -51,7 +51,8 @@ public:
virtual void windowDeleted(Deleted*);
virtual void screenGeometryChanged(const QSize &size);
virtual OverlayWindow *overlayWindow();
virtual bool waitSyncAvailable() const;
virtual bool blocksForRetrace() const;
virtual bool syncsToVBlank() const;
void idle();
@ -382,6 +383,27 @@ private:
GLTexture *m_texture;
};
/**
* @short Profiler to detect whether we have triple buffering
* The strategy is to start setBlocksForRetrace(false) but assume blocking and have the system prove that assumption wrong
**/
class SwapProfiler
{
public:
SwapProfiler();
void init();
void begin();
/**
* @return char being 'd' for double, 't' for triple (or more - but non-blocking) buffering and
* 0 (NOT '0') otherwise, so you can act on "if (char result = SwapProfiler::end()) { fooBar(); }
**/
char end();
private:
QElapsedTimer m_timer;
qint64 m_time;
int m_counter;
};
/**
* @brief The OpenGLBackend creates and holds the OpenGL context and is responsible for Texture from Pixmap.
*
@ -462,8 +484,17 @@ public:
*
* @return bool @c true if VSync support is available, @c false otherwise
**/
bool waitSyncAvailable() const {
return m_waitSync;
bool syncsToVBlank() const {
return m_syncsToVBlank;
}
/**
* @brief Whether VSync blocks execution until the screen is in the retrace
*
* Case for waitVideoSync and non triple buffering buffer swaps
*
**/
bool blocksForRetrace() const {
return m_blocksForRetrace;
}
/**
* @brief Whether the backend uses direct rendering.
@ -497,8 +528,18 @@ protected:
* If the subclass does not call this method, the backend defaults to @c false.
* @param enabled @c true if VSync support available, @c false otherwise.
**/
void setHasWaitSync(bool enabled) {
m_waitSync = enabled;
void setSyncsToVBlank(bool enabled) {
m_syncsToVBlank = enabled;
}
/**
* @brief Sets whether the VSync iplementation blocks
*
* Should be called by the concrete subclass once it is determined how VSync works.
* If the subclass does not call this method, the backend defaults to @c false.
* @param enabled @c true if VSync blocks, @c false otherwise.
**/
void setBlocksForRetrace(bool enabled) {
m_blocksForRetrace = enabled;
}
/**
* @brief Sets whether the OpenGL context is direct.
@ -530,15 +571,21 @@ protected:
m_renderTimer.start();
}
SwapProfiler m_swapProfiler;
private:
/**
* @brief The OverlayWindow used by this Backend.
**/
OverlayWindow *m_overlayWindow;
/**
* @brief Whether VSync is available, defaults to @c false.
* @brief Whether VSync is available and used, defaults to @c false.
**/
bool m_waitSync;
bool m_syncsToVBlank;
/**
* @brief Whether present() will block execution until the next vertical retrace @c false.
**/
bool m_blocksForRetrace;
/**
* @brief Whether direct rendering is used, defaults to @c false.
**/