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();
|
||||
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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -64,7 +64,7 @@ private:
|
|||
GLXFBConfig fbconfig;
|
||||
GLXWindow glxWindow;
|
||||
GLXContext ctx;
|
||||
bool haveSwapInterval;
|
||||
bool haveSwapInterval, haveWaitSync;
|
||||
friend class GlxTexture;
|
||||
};
|
||||
|
||||
|
|
|
@ -561,7 +561,12 @@ void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen)
|
|||
Q_UNUSED(opaqueFullscreen);
|
||||
}
|
||||
|
||||
bool Scene::waitSyncAvailable() const
|
||||
bool Scene::blocksForRetrace() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Scene::syncsToVBlank() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
3
scene.h
3
scene.h
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
**/
|
||||
|
|
Loading…
Reference in a new issue