hasWaitSync -> blocksForRetrace and syncsToVBlank
since that's not the same. also autodetect triple buffering REVIEW: 109783
This commit is contained in:
parent
2a726bb289
commit
9446abc696
8 changed files with 166 additions and 33 deletions
|
@ -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
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -561,7 +561,12 @@ void Scene::extendPaintRegion(QRegion ®ion, 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;
|
||||||
}
|
}
|
||||||
|
|
3
scene.h
3
scene.h
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
**/
|
**/
|
||||||
|
|
Loading…
Reference in a new issue