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(); m_xrrRefreshRate = KWin::currentRefreshRate();
fpsInterval = (options->maxFpsInterval() << 10); 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; vBlankInterval = (1000 << 10) / m_xrrRefreshRate;
fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval);
} else } else
@ -648,7 +648,7 @@ void Compositor::setCompositeTimer()
uint padding = m_timeSinceLastVBlank << 10; uint padding = m_timeSinceLastVBlank << 10;
if (m_scene->waitSyncAvailable()) { if (m_scene->blocksForRetrace()) {
// TODO: make vBlankTime dynamic?! // TODO: make vBlankTime dynamic?!
// It's required because glXWaitVideoSync will *likely* block a full frame if one enters // 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() void EglOnXBackend::init()
{ {
if (!initRenderingContext()) { if (!initRenderingContext()) {
@ -88,6 +91,10 @@ void EglOnXBackend::init()
} }
} }
} }
setSyncsToVBlank(false);
setBlocksForRetrace(false);
gs_tripleBufferNeedsDetection = false;
m_swapProfiler.init();
if (surfaceHasSubPost) { if (surfaceHasSubPost) {
kDebug(1212) << "EGL implementation and surface support eglPostSubBufferNV, let's use it"; kDebug(1212) << "EGL implementation and surface support eglPostSubBufferNV, let's use it";
@ -98,7 +105,13 @@ void EglOnXBackend::init()
if (val >= 1) { if (val >= 1) {
if (eglSwapInterval(dpy, 1)) { if (eglSwapInterval(dpy, 1)) {
kDebug(1212) << "Enabled v-sync"; 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 { } else {
kWarning(1212) << "Cannot enable v-sync as max. swap interval is" << val; kWarning(1212) << "Cannot enable v-sync as max. swap interval is" << val;
@ -249,8 +262,19 @@ void EglOnXBackend::present()
const bool fullRepaint = (lastDamage() == displayRegion); const bool fullRepaint = (lastDamage() == displayRegion);
if (fullRepaint || !surfaceHasSubPost) { 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) // the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation)
eglSwapBuffers(dpy, surface); eglSwapBuffers(dpy, surface);
if (gs_tripleBufferNeedsDetection) {
eglWaitGL();
if (char result = m_swapProfiler.end()) {
gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false;
setBlocksForRetrace(result == 'd');
}
}
} else { } else {
// a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area // a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area
foreach (const QRect & r, lastDamage().rects()) { foreach (const QRect & r, lastDamage().rects()) {

View file

@ -69,6 +69,9 @@ GlxBackend::~GlxBackend()
checkGLError("Cleanup"); checkGLError("Cleanup");
} }
static bool gs_tripleBufferUndetected = true;
static bool gs_tripleBufferNeedsDetection = false;
void GlxBackend::init() void GlxBackend::init()
{ {
initGLX(); initGLX();
@ -96,26 +99,32 @@ void GlxBackend::init()
initGL(GlxPlatformInterface); initGL(GlxPlatformInterface);
// Check whether certain features are supported // Check whether certain features are supported
haveSwapInterval = glXSwapIntervalMESA || glXSwapIntervalEXT || glXSwapIntervalSGI; haveSwapInterval = glXSwapIntervalMESA || glXSwapIntervalEXT || glXSwapIntervalSGI;
setSyncsToVBlank(false);
setBlocksForRetrace(false);
haveWaitSync = false;
gs_tripleBufferNeedsDetection = false;
m_swapProfiler.init();
const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage;
if (wantSync) { if (wantSync && glXIsDirect(display(), ctx)) {
if (glXGetVideoSync && haveSwapInterval && glXIsDirect(display(), ctx)) { if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable
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);
setSwapInterval(1); 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 } 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 } else
qWarning() << "NO VSYNC! glXGetVideoSync(&uint) isn't 0 but" << glXGetVideoSync(&sync); qWarning() << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported";
} else
qWarning() << "NO VSYNC! glXGetVideoSync, haveSwapInterval, glXIsDirect" <<
bool(glXGetVideoSync) << haveSwapInterval << glXIsDirect(display(), ctx);
} else { } else {
// disable v-sync (if possible) // disable v-sync (if possible)
setSwapInterval(0); setSwapInterval(0);
@ -364,7 +373,7 @@ void GlxBackend::setSwapInterval(int interval)
void GlxBackend::waitSync() void GlxBackend::waitSync()
{ {
// NOTE that vsync has no effect with indirect rendering // NOTE that vsync has no effect with indirect rendering
if (waitSyncAvailable()) { if (haveWaitSync) {
#if VSYNC_DEBUG #if VSYNC_DEBUG
startRenderTimer(); startRenderTimer();
#endif #endif
@ -403,8 +412,19 @@ void GlxBackend::present()
if (fullRepaint) { if (fullRepaint) {
if (haveSwapInterval) { if (haveSwapInterval) {
if (gs_tripleBufferNeedsDetection) {
glXWaitGL();
m_swapProfiler.begin();
}
glXSwapBuffers(display(), glxWindow); glXSwapBuffers(display(), glxWindow);
startRenderTimer(); startRenderTimer();
if (gs_tripleBufferNeedsDetection) {
glXWaitGL();
if (char result = m_swapProfiler.end()) {
gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false;
setBlocksForRetrace(result == 'd');
}
}
} else { } else {
waitSync(); // calls startRenderTimer(); waitSync(); // calls startRenderTimer();
glXSwapBuffers(display(), glxWindow); glXSwapBuffers(display(), glxWindow);

View file

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

View file

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

View file

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

View file

@ -76,7 +76,8 @@ extern int currentRefreshRate();
//**************************************** //****************************************
OpenGLBackend::OpenGLBackend() OpenGLBackend::OpenGLBackend()
: m_overlayWindow(new OverlayWindow()) // TODO: maybe create only if needed? : m_overlayWindow(new OverlayWindow()) // TODO: maybe create only if needed?
, m_waitSync(false) , m_syncsToVBlank(false)
, m_blocksForRetrace(false)
, m_directRendering(false) , m_directRendering(false)
, m_failed(false) , m_failed(false)
{ {
@ -239,9 +240,14 @@ OverlayWindow *SceneOpenGL::overlayWindow()
return m_backend->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() void SceneOpenGL::idle()
@ -2220,4 +2226,34 @@ bool SceneOpenGLShadow::prepareBackend()
return true; 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 } // namespace

View file

@ -51,7 +51,8 @@ public:
virtual void windowDeleted(Deleted*); virtual void windowDeleted(Deleted*);
virtual void screenGeometryChanged(const QSize &size); virtual void screenGeometryChanged(const QSize &size);
virtual OverlayWindow *overlayWindow(); virtual OverlayWindow *overlayWindow();
virtual bool waitSyncAvailable() const; virtual bool blocksForRetrace() const;
virtual bool syncsToVBlank() const;
void idle(); void idle();
@ -382,6 +383,27 @@ private:
GLTexture *m_texture; 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. * @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 * @return bool @c true if VSync support is available, @c false otherwise
**/ **/
bool waitSyncAvailable() const { bool syncsToVBlank() const {
return m_waitSync; 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. * @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. * 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. * @param enabled @c true if VSync support available, @c false otherwise.
**/ **/
void setHasWaitSync(bool enabled) { void setSyncsToVBlank(bool enabled) {
m_waitSync = 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. * @brief Sets whether the OpenGL context is direct.
@ -530,15 +571,21 @@ protected:
m_renderTimer.start(); m_renderTimer.start();
} }
SwapProfiler m_swapProfiler;
private: private:
/** /**
* @brief The OverlayWindow used by this Backend. * @brief The OverlayWindow used by this Backend.
**/ **/
OverlayWindow *m_overlayWindow; 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. * @brief Whether direct rendering is used, defaults to @c false.
**/ **/