Mir

Merge lp:~vanvugt/mir/client-side-vsync into lp:mir

Proposed by Daniel van Vugt
Status: Merged
Approved by: Cemil Azizoglu
Approved revision: no longer in the source branch.
Merged at revision: 3952
Proposed branch: lp:~vanvugt/mir/client-side-vsync
Merge into: lp:mir
Prerequisite: lp:~vanvugt/mir/fix-StaleFrames-test-race
Diff against target: 1547 lines (+1049/-22)
24 files modified
include/client/mir_toolkit/mir_buffer_stream.h (+0/-1)
include/client/mir_toolkit/mir_surface.h (+0/-1)
include/core/mir_toolkit/common.h (+7/-4)
src/client/CMakeLists.txt (+1/-0)
src/client/buffer_stream.cpp (+111/-6)
src/client/buffer_stream.h (+12/-1)
src/client/error_stream.cpp (+6/-0)
src/client/error_stream.h (+2/-0)
src/client/frame_clock.cpp (+156/-0)
src/client/frame_clock.h (+73/-0)
src/client/mir_buffer_stream_api.cpp (+8/-1)
src/client/mir_surface.cpp (+113/-5)
src/client/mir_surface.h (+10/-0)
src/client/mir_surface_api.cpp (+1/-1)
src/client/screencast_stream.cpp (+8/-0)
src/client/screencast_stream.h (+2/-0)
src/include/client/mir/mir_buffer_stream.h (+2/-0)
tests/acceptance-tests/test_client_library.cpp (+5/-0)
tests/acceptance-tests/test_latency.cpp (+27/-2)
tests/include/mir/test/doubles/mock_mir_buffer_stream.h (+2/-0)
tests/unit-tests/client/CMakeLists.txt (+1/-0)
tests/unit-tests/client/test_client_mir_surface.cpp (+81/-0)
tests/unit-tests/client/test_frame_clock.cpp (+420/-0)
tests/unit-tests/platforms/android/server/test_hwc_device.cpp (+1/-0)
To merge this branch: bzr merge lp:~vanvugt/mir/client-side-vsync
Reviewer Review Type Date Requested Status
Alan Griffiths Approve
Kevin DuBois (community) Approve
Mir CI Bot continuous-integration Approve
Cemil Azizoglu (community) Approve
Review via email: mp+314313@code.launchpad.net

Commit message

Introducing client-side vsync, part 1.
For dramatically lower latency and improved smoothness.
Fixes LP: #1240909, LP: #1388490, LP: #1591328, LP: #1624188

Part 1 covers all users of:
   eglSwapBuffers
   mir_buffer_stream_swap_buffers_sync

Part 2 will cover the rest (mir_buffer_stream_swap_buffers) but we're not
in a hurry for it as that still has working server-side vsync. Plus I
think Xmir is the only user of that function at present.

Part 3 will introduce IPC to communicate hardware vsync timestamps to the
client for phase correction and increased precision (although that's not
a prerequisite to this).

Immediate benefits:

 * Greatly reduced latency from the client to the screen. Theoretically
   three frames lower latency for windowed apps of a nested server like
   Unity8. And this has been confirmed with real measurements:
   https://docs.google.com/spreadsheets/d/1RbTVDbx04ohkF4-md3wAlgmxbSI1DttstnT6xdcXhZQ/pubchart?oid=1566479835&format=interactive
   You can also confirm using your own eyes: mir_demo_client_target

 * No client/toolkit changes required.

 * Latency and smoothness is no longer affected by process scheduling
   randomness or poor server performance. Although we presently use input
   resampling to work around some of the latency problem, that's not
   immune to latency increasing again when the server has insufficient
   time to keep up with the client. This approach is.

 * Lower latency even than double buffering (when nested), and also
   without the risk of decimation when a client takes slightly too long
   to render.

Description of the change

Future benefits:

 * We can remove Mir's input resampling now, since client-side vsync
   should make the QML touch compression algorithm safely nestable.

 * We get closer to not needing server-side queuing and multi-monitor
   frame sync logic. Although some people would like to keep it for other
   reasons, this approach (after mir_buffer_stream_swap_buffers gets
   ported) eliminates most of the need for it.

 * Overall this approach could deprecate many more lines of code than it
   adds (see above).

 * Toolkits can have greater flexibility in how they choose to implement
   their event loops without threading. If we shared timing information
   in the client API in future then a toolkit could just choose interval 0
   and do its own vsync baked into its event loop.

 * Mir is free to support dropping in the system compositor if it wants,
   and no longer requires additional fixing to throttle drivers (like
   software renderers) that don't support page flipping.

Risks:

 * We rely on the graphics driver to report accurate frame timing.
   Although it's only a serious problem if the driver is out by orders of
   magnitude (LP: #1639725). Otherwise only being slightly inaccurate is
   actually the same problem as what we experience presently with the
   59Hz input resampling when user input is driving animation.

To post a comment you must log in.
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:3911
https://mir-jenkins.ubuntu.com/job/mir-ci/2578/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/3358
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3425
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3417
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3417
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/3417
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/3387
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/3387/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3387
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3387/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/3387
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/3387/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3387
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3387/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3387
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3387/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3387
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3387/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2578/rebuild

review: Approve (continuous-integration)
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3917
https://mir-jenkins.ubuntu.com/job/mir-ci/2672/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/3472/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3539
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3529
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3529
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3529
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3499/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3499
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3499/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3499/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3499
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3499/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3499
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3499/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3499/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2672/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :
Revision history for this message
Cemil Azizoglu (cemil-azizoglu) wrote :

Nice latency numbers.

Needs fixing:

MirSurface references should be MirWindow now.

review: Needs Fixing
Revision history for this message
Kevin DuBois (kdub) wrote :

LGTM overall (still haven't fully understood the logic in FrameClock, so more comments on that later, perhaps)

discussion:
135 + * TODO: Remove these functions in future, after
136 + * mir_buffer_stream_swap_buffers has been converted to use
137 + * client-side vsync, and the server has been told to always use
138 + * dropping for BufferStream. Then there will be no need to ever
139 + * change the server swap interval from zero.

Would prefer if this read "and the server has other ways to express buffer queuing other than swapinterval". Would leave room for some of the vulkan submission modes to work via server-side queuing (jury's still out on whether this is a good idea, but I don't know if we want to limit ourselves to client-side queuing only)

needs info:
1)
+void mcl::BufferStream::adopted_by(MirSurface* surface)

Other than BufferStream's currently-terrible constructor, why couldn't the frame clock be given as a build dependency via the constructor? One more parameter to the constructor seems better than a too-long constructor list + a 2step initialization

2)
Where the code currently is might be only useful to android, post-MirRenderSurface/MirSurface/mir-egl-platform. (which still uses MirBufferStream as part of its egl stack for now). From what I've heard from Cemil, mesa is using MirPresentationChains + MirBuffers, so might need some way via an extension or a public-api to get the timing info. (android might also might be better suited for that as well, for now its just easier to re-use MirBufferStream for that platform)

nit:
348 +typedef std::unique_lock<std::mutex> Lock;
IMHO, doesn't improve readability.

I guess that's mostly a 'discussion' vote.

review: Needs Information
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3918
https://mir-jenkins.ubuntu.com/job/mir-ci/2685/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/3487/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3554
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3544
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3544
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3544
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3514
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3514/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3514
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3514/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3514
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3514/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3514
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3514/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3514
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3514/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3514/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2685/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3919
https://mir-jenkins.ubuntu.com/job/mir-ci/2703/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/3513/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3581
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3571
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3571
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3571
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3540
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3540/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3540
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3540/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3540
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3540/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3540/console
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3540/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3540
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3540/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3540/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2703/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

^^^
Bug 1647244, and more random examples failures on krillin:

13:14:00 I: The following clients failed to execute successfully:
13:14:00 I: mir_demo_client_multistream
13:14:00 I: Smoke testing complete with returncode -1

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3920
https://mir-jenkins.ubuntu.com/job/mir-ci/2708/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/3519/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3587
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3577
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3577
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3577
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3546
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3546/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3546/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3546
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3546/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3546
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3546/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3546
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3546/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3546
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3546/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2708/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

kdub:

(0) Sure you can keep server-side queueing if you want it. So long as this code still has the ability to turn it off. Because having it queueing on will reintroduce the bufferbloat-nested-queuing-lag problems that are fixed here. So you probably don't want that, but up to you :)

(1) No FrameClock is not set at construction. It's set (and changes) whenever the window moves to a new monitor (different FrameClock). We may also wish to change the FrameClock depending on occlusions in future (to replace the 10Hz dropping hack)...

(2) Yes, I expect the logic to move around with Cemil's future changes but where it is right now works for what we use right now. As for a public API, yes I agree that will be wanted; already mentioned it in the description above, but also a public API will be needed by Xmir to implement https://www.opengl.org/registry/specs/OML/glx_sync_control.txt

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Also a public API would let clients do the equivalent of client-side predictive bypass for even lower latency: https://www.opengl.org/registry/specs/NV/glx_delay_before_swap.txt

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3921
https://mir-jenkins.ubuntu.com/job/mir-ci/2711/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/3524/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3592
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3582
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3582
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3582
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3551
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3551/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3551
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3551/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3551/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3551
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3551/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3551
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3551/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3551
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3551/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2711/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3923
https://mir-jenkins.ubuntu.com/job/mir-ci/2714/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/3529/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3597
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3587
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3587
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3587
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3556
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3556/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3556
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3556/artifact/output/*zip*/output.zip
    ABORTED: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3556/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3556
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3556/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3556
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3556/artifact/output/*zip*/output.zip
    ABORTED: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3556/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2714/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Cemil Azizoglu (cemil-azizoglu) wrote :

Ok by me when CI is happy

review: Approve
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

^^^
CI aborts are bug 1655929

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3924
https://mir-jenkins.ubuntu.com/job/mir-ci/2729/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/3556/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3624
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3614
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3614
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3614
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3583
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3583/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3583
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3583/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3583
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3583/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3583/console
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3583/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3583
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3583/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3583
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3583/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2729/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

^^^
Random krillin failures running mir-demos:

03:40:30 I: The following clients failed to execute successfully:
03:40:30 I: mir_demo_client_animated_cursor
03:40:30 I: mir_demo_client_multistream
03:40:30 I: Smoke testing complete with returncode -1

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3925
https://mir-jenkins.ubuntu.com/job/mir-ci/2733/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/3564/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3632
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3622
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3622
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3622
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3591/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3591
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3591/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3591
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3591/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3591
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3591/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3591
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3591/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3591
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3591/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2733/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:3926
https://mir-jenkins.ubuntu.com/job/mir-ci/2737/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/3570
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/3638
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/3628
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/3628
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/3628
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3597
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/3597/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3597
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/3597/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3597
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/3597/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3597
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/3597/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3597
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/3597/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3597
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/3597/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2737/rebuild

review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

> kdub:
>
> (0) Sure you can keep server-side queueing if you want it. So long as this
> code still has the ability to turn it off. Because having it queueing on will
> reintroduce the bufferbloat-nested-queuing-lag problems that are fixed here.
> So you probably don't want that, but up to you :)

Sure, we need an option to turn it off, it might just be useful to have it around.

> (2) Yes, I expect the logic to move around with Cemil's future changes but
> where it is right now works for what we use right now. As for a public API,
> yes I agree that will be wanted; already mentioned it in the description
> above, but also a public API will be needed by Xmir to implement
> https://www.opengl.org/registry/specs/OML/glx_sync_control.txt

Alright, so we might need some extensions for this to work in the "MirRenderSurface" world, and the driver would manage what BufferStream is managing now.

Revision history for this message
Kevin DuBois (kdub) wrote :

well, lgtm. The frame clock management still seems like 2-step initialization, but given the state of BufferStream, doesn't bother me as much.

review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

+ * Copyright © 2016 Canonical Ltd.

Is the year really right?

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> well, lgtm. The frame clock management still seems like 2-step initialization,
> but given the state of BufferStream, doesn't bother me as much.

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'include/client/mir_toolkit/mir_buffer_stream.h'
--- include/client/mir_toolkit/mir_buffer_stream.h 2016-12-17 00:52:45 +0000
+++ include/client/mir_toolkit/mir_buffer_stream.h 2017-01-17 06:23:26 +0000
@@ -219,7 +219,6 @@
219/**219/**
220 * Set the swapinterval for the stream.220 * Set the swapinterval for the stream.
221 * \warning EGL users should use eglSwapInterval directly.221 * \warning EGL users should use eglSwapInterval directly.
222 * \warning Only swapinterval of 0 or 1 is supported.
223 * \param [in] stream The buffer stream222 * \param [in] stream The buffer stream
224 * \param [in] interval The number of vblank signals that223 * \param [in] interval The number of vblank signals that
225 * mir_buffer_stream_swap_buffers will wait for224 * mir_buffer_stream_swap_buffers will wait for
226225
=== modified file 'include/client/mir_toolkit/mir_surface.h'
--- include/client/mir_toolkit/mir_surface.h 2017-01-17 05:51:54 +0000
+++ include/client/mir_toolkit/mir_surface.h 2017-01-17 06:23:26 +0000
@@ -978,7 +978,6 @@
978/**978/**
979 * Set the swapinterval for the default stream.979 * Set the swapinterval for the default stream.
980 * \warning EGL users should use eglSwapInterval directly.980 * \warning EGL users should use eglSwapInterval directly.
981 * \warning Only swapinterval of 0 or 1 is supported.
982 * \warning If the surface was created with, or modified to have a981 * \warning If the surface was created with, or modified to have a
983 * MirSurfaceSpec containing streams added through982 * MirSurfaceSpec containing streams added through
984 * mir_window_spec_set_streams(), the default stream will983 * mir_window_spec_set_streams(), the default stream will
985984
=== modified file 'include/core/mir_toolkit/common.h'
--- include/core/mir_toolkit/common.h 2017-01-17 05:51:54 +0000
+++ include/core/mir_toolkit/common.h 2017-01-17 06:23:26 +0000
@@ -37,9 +37,9 @@
37 mir_surface_attrib_type,37 mir_surface_attrib_type,
38 mir_surface_attrib_state,38 mir_surface_attrib_state,
39 mir_surface_attrib_swapinterval, /**< \deprecated Do not listen for events39 mir_surface_attrib_swapinterval, /**< \deprecated Do not listen for events
40 reporting this attribute. Use the40 reporting this attribute. Use the
41 "mir_*_get_swapinterval()" functions41 "mir_*_get_swapinterval()" functions
42 instead if you wish query its value */42 instead if you wish query its value */
43 mir_surface_attrib_focus,43 mir_surface_attrib_focus,
44 mir_surface_attrib_dpi,44 mir_surface_attrib_dpi,
45 mir_surface_attrib_visibility,45 mir_surface_attrib_visibility,
@@ -57,7 +57,10 @@
57 /* Do not specify values...code relies on 0...N ordering. */57 /* Do not specify values...code relies on 0...N ordering. */
58 mir_window_attrib_type,58 mir_window_attrib_type,
59 mir_window_attrib_state,59 mir_window_attrib_state,
60 mir_window_attrib_swapinterval,60 mir_window_attrib_swapinterval, /**< \deprecated Do not listen for events
61 reporting this attribute. Use the
62 "mir_*_get_swapinterval()" functions
63 instead if you wish query its value */
61 mir_window_attrib_focus,64 mir_window_attrib_focus,
62 mir_window_attrib_dpi,65 mir_window_attrib_dpi,
63 mir_window_attrib_visibility,66 mir_window_attrib_visibility,
6467
=== modified file 'src/client/CMakeLists.txt'
--- src/client/CMakeLists.txt 2016-12-13 06:00:32 +0000
+++ src/client/CMakeLists.txt 2017-01-17 06:23:26 +0000
@@ -62,6 +62,7 @@
62 default_connection_configuration.cpp62 default_connection_configuration.cpp
63 connection_surface_map.cpp63 connection_surface_map.cpp
64 private.cpp64 private.cpp
65 frame_clock.cpp
65 mir_screencast.cpp66 mir_screencast.cpp
66 mir_screencast_api.cpp67 mir_screencast_api.cpp
67 mir_cursor_api.cpp68 mir_cursor_api.cpp
6869
=== modified file 'src/client/buffer_stream.cpp'
--- src/client/buffer_stream.cpp 2017-01-17 05:51:54 +0000
+++ src/client/buffer_stream.cpp 2017-01-17 06:23:26 +0000
@@ -253,6 +253,7 @@
253 client_platform(client_platform),253 client_platform(client_platform),
254 protobuf_bs{mcl::make_protobuf_object<mir::protobuf::BufferStream>(a_protobuf_bs)},254 protobuf_bs{mcl::make_protobuf_object<mir::protobuf::BufferStream>(a_protobuf_bs)},
255 user_swap_interval(parse_env_for_swap_interval()),255 user_swap_interval(parse_env_for_swap_interval()),
256 using_client_side_vsync(false),
256 interval_config{server, frontend::BufferStreamId{a_protobuf_bs.id().value()}},257 interval_config{server, frontend::BufferStreamId{a_protobuf_bs.id().value()}},
257 scale_(1.0f),258 scale_(1.0f),
258 perf_report(perf_report),259 perf_report(perf_report),
@@ -264,6 +265,16 @@
264 factory(factory),265 factory(factory),
265 render_surface_(render_surface)266 render_surface_(render_surface)
266{267{
268 /*
269 * Since we try to use client-side vsync where possible, a separate
270 * copy of the current swap interval is required to represent that
271 * the stream might have a non-zero interval while we want the server
272 * to use a zero interval. This is not stored inside interval_config
273 * because with some luck interval_config will eventually go away
274 * leaving just int current_swap_interval.
275 */
276 current_swap_interval = interval_config.swap_interval();
277
267 if (!protobuf_bs->has_id())278 if (!protobuf_bs->has_id())
268 {279 {
269 if (!protobuf_bs->has_error())280 if (!protobuf_bs->has_error())
@@ -354,6 +365,10 @@
354 std::unique_lock<decltype(mutex)> lock(mutex);365 std::unique_lock<decltype(mutex)> lock(mutex);
355 perf_report->end_frame(id);366 perf_report->end_frame(id);
356367
368 if (!using_client_side_vsync &&
369 current_swap_interval != interval_config.swap_interval())
370 set_server_swap_interval(current_swap_interval);
371
357 secured_region.reset();372 secured_region.reset();
358373
359 // TODO: We can fix the strange "ID casting" used below in the second phase374 // TODO: We can fix the strange "ID casting" used below in the second phase
@@ -406,9 +421,73 @@
406 mir_display_output_id_invalid};421 mir_display_output_id_invalid};
407}422}
408423
424void mcl::BufferStream::wait_for_vsync()
425{
426 mir::time::PosixTimestamp last, target;
427 std::shared_ptr<FrameClock> clock;
428
429 {
430 std::lock_guard<decltype(mutex)> lock(mutex);
431 if (!frame_clock)
432 return;
433 last = last_vsync;
434 clock = frame_clock;
435 }
436
437 target = clock->next_frame_after(last);
438 sleep_until(target);
439
440 {
441 std::lock_guard<decltype(mutex)> lock(mutex);
442 if (target > last_vsync)
443 last_vsync = target;
444 }
445}
446
447MirWaitHandle* mcl::BufferStream::set_server_swap_interval(int i)
448{
449 /*
450 * TODO: Remove these functions in future, after
451 * mir_buffer_stream_swap_buffers has been converted to use
452 * client-side vsync, and the server has been told to always use
453 * dropping for BufferStream. Then there will be no need to ever
454 * change the server swap interval from zero.
455 */
456 buffer_depository->set_interval(i);
457 return interval_config.set_swap_interval(i);
458}
459
409void mcl::BufferStream::swap_buffers_sync()460void mcl::BufferStream::swap_buffers_sync()
410{461{
462 {
463 std::lock_guard<decltype(mutex)> lock(mutex);
464 using_client_side_vsync = true;
465 }
466
467 /*
468 * Until recently it seemed like server-side vsync could co-exist with
469 * client-side vsync. However my original theory that it was risky has
470 * proven to be true. If we leave server-side vsync throttling to interval
471 * one at the same time as using client-side, there's a risk the server
472 * will not get scheduled sufficiently to drain the queue as fast as
473 * we fill it, creating lag. The acceptance test `ClientLatency' has now
474 * proven this is a real problem so we must be sure to put the server in
475 * interval 0 when using client-side vsync. This guarantees that random
476 * scheduling imperfections won't create queuing lag.
477 */
478 if (interval_config.swap_interval() != 0)
479 set_server_swap_interval(0);
480
411 swap_buffers([](){})->wait_for_all();481 swap_buffers([](){})->wait_for_all();
482
483 {
484 std::lock_guard<decltype(mutex)> lock(mutex);
485 using_client_side_vsync = false;
486 }
487
488 int interval = swap_interval();
489 for (int i = 0; i < interval; ++i)
490 wait_for_vsync();
412}491}
413492
414void mcl::BufferStream::request_and_wait_for_configure(MirWindowAttrib attrib, int interval)493void mcl::BufferStream::request_and_wait_for_configure(MirWindowAttrib attrib, int interval)
@@ -428,16 +507,42 @@
428507
429int mcl::BufferStream::swap_interval() const508int mcl::BufferStream::swap_interval() const
430{509{
431 return interval_config.swap_interval();510 std::lock_guard<decltype(mutex)> lock(mutex);
511 return current_swap_interval;
432}512}
433513
434MirWaitHandle* mcl::BufferStream::set_swap_interval(int interval)514MirWaitHandle* mcl::BufferStream::set_swap_interval(int interval)
435{515{
436 if (user_swap_interval.is_set())516 std::lock_guard<decltype(mutex)> lock(mutex);
437 interval = user_swap_interval.value();517 current_swap_interval = user_swap_interval.is_set() ?
438518 user_swap_interval.value() : interval;
439 buffer_depository->set_interval(interval);519
440 return interval_config.set_swap_interval(interval);520 return set_server_swap_interval(current_swap_interval);
521}
522
523void mcl::BufferStream::adopted_by(MirWindow* win)
524{
525 std::lock_guard<decltype(mutex)> lock(mutex);
526 /*
527 * Yes, we're storing raw pointers here. That's safe so long as MirWindow
528 * remembers to always call unadopted_by prior to its destruction.
529 * The alternative of MirWindow passing in a shared_ptr to itself is
530 * actually uglier than this...
531 */
532 users.insert(win);
533 if (!frame_clock)
534 frame_clock = win->get_frame_clock();
535}
536
537void mcl::BufferStream::unadopted_by(MirWindow* win)
538{
539 std::lock_guard<decltype(mutex)> lock(mutex);
540 users.erase(win);
541 if (frame_clock == win->get_frame_clock())
542 {
543 frame_clock = users.empty() ? nullptr
544 : (*users.begin())->get_frame_clock();
545 }
441}546}
442547
443MirNativeBuffer* mcl::BufferStream::get_current_buffer_package()548MirNativeBuffer* mcl::BufferStream::get_current_buffer_package()
444549
=== modified file 'src/client/buffer_stream.h'
--- src/client/buffer_stream.h 2017-01-17 05:51:54 +0000
+++ src/client/buffer_stream.h 2017-01-17 06:23:26 +0000
@@ -26,11 +26,13 @@
26#include "mir/geometry/size.h"26#include "mir/geometry/size.h"
27#include "mir/optional_value.h"27#include "mir/optional_value.h"
28#include "buffer_stream_configuration.h"28#include "buffer_stream_configuration.h"
29#include "frame_clock.h"
2930
30#include "mir_toolkit/client_types.h"31#include "mir_toolkit/client_types.h"
3132
32#include <EGL/eglplatform.h>33#include <EGL/eglplatform.h>
3334
35#include <unordered_set>
34#include <queue>36#include <queue>
35#include <memory>37#include <memory>
36#include <mutex>38#include <mutex>
@@ -97,6 +99,8 @@
9799
98 int swap_interval() const override;100 int swap_interval() const override;
99 MirWaitHandle* set_swap_interval(int interval) override;101 MirWaitHandle* set_swap_interval(int interval) override;
102 void adopted_by(MirWindow*) override;
103 void unadopted_by(MirWindow*) override;
100 void set_buffer_cache_size(unsigned int) override;104 void set_buffer_cache_size(unsigned int) override;
101105
102 EGLNativeWindowType egl_native_window() override;106 EGLNativeWindowType egl_native_window() override;
@@ -133,8 +137,9 @@
133 void process_buffer(protobuf::Buffer const& buffer, std::unique_lock<std::mutex>&);137 void process_buffer(protobuf::Buffer const& buffer, std::unique_lock<std::mutex>&);
134 void on_scale_set(float scale);138 void on_scale_set(float scale);
135 void release_cpu_region();139 void release_cpu_region();
136 MirWaitHandle* force_swap_interval(int interval);140 MirWaitHandle* set_server_swap_interval(int i);
137 void init_swap_interval();141 void init_swap_interval();
142 void wait_for_vsync();
138143
139 mutable std::mutex mutex; // Protects all members of *this144 mutable std::mutex mutex; // Protects all members of *this
140145
@@ -143,6 +148,8 @@
143 std::unique_ptr<mir::protobuf::BufferStream> protobuf_bs;148 std::unique_ptr<mir::protobuf::BufferStream> protobuf_bs;
144149
145 optional_value<int> user_swap_interval;150 optional_value<int> user_swap_interval;
151 int current_swap_interval;
152 bool using_client_side_vsync;
146 BufferStreamConfiguration interval_config;153 BufferStreamConfiguration interval_config;
147 float scale_;154 float scale_;
148155
@@ -161,6 +168,10 @@
161 std::weak_ptr<SurfaceMap> const map;168 std::weak_ptr<SurfaceMap> const map;
162 std::shared_ptr<AsyncBufferFactory> const factory;169 std::shared_ptr<AsyncBufferFactory> const factory;
163 MirRenderSurface* render_surface_;170 MirRenderSurface* render_surface_;
171
172 std::unordered_set<MirWindow*> users;
173 std::shared_ptr<FrameClock> frame_clock;
174 mir::time::PosixTimestamp last_vsync;
164};175};
165176
166}177}
167178
=== modified file 'src/client/error_stream.cpp'
--- src/client/error_stream.cpp 2017-01-17 05:51:54 +0000
+++ src/client/error_stream.cpp 2017-01-17 06:23:26 +0000
@@ -94,6 +94,12 @@
94{94{
95 BOOST_THROW_EXCEPTION(std::runtime_error(error));95 BOOST_THROW_EXCEPTION(std::runtime_error(error));
96}96}
97void mcl::ErrorStream::adopted_by(MirWindow*)
98{
99}
100void mcl::ErrorStream::unadopted_by(MirWindow*)
101{
102}
97MirNativeBuffer* mcl::ErrorStream::get_current_buffer_package()103MirNativeBuffer* mcl::ErrorStream::get_current_buffer_package()
98{104{
99 BOOST_THROW_EXCEPTION(std::runtime_error(error));105 BOOST_THROW_EXCEPTION(std::runtime_error(error));
100106
=== modified file 'src/client/error_stream.h'
--- src/client/error_stream.h 2017-01-17 05:51:54 +0000
+++ src/client/error_stream.h 2017-01-17 06:23:26 +0000
@@ -41,6 +41,8 @@
41 std::shared_ptr<MemoryRegion> secure_for_cpu_write() override;41 std::shared_ptr<MemoryRegion> secure_for_cpu_write() override;
42 int swap_interval() const override;42 int swap_interval() const override;
43 MirWaitHandle* set_swap_interval(int interval) override;43 MirWaitHandle* set_swap_interval(int interval) override;
44 void adopted_by(MirWindow*) override;
45 void unadopted_by(MirWindow*) override;
44 MirNativeBuffer* get_current_buffer_package() override;46 MirNativeBuffer* get_current_buffer_package() override;
45 MirPlatformType platform_type() override;47 MirPlatformType platform_type() override;
46 frontend::BufferStreamId rpc_id() const override;48 frontend::BufferStreamId rpc_id() const override;
4749
=== added file 'src/client/frame_clock.cpp'
--- src/client/frame_clock.cpp 1970-01-01 00:00:00 +0000
+++ src/client/frame_clock.cpp 2017-01-17 06:23:26 +0000
@@ -0,0 +1,156 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
17 */
18
19#include "frame_clock.h"
20#include <stdexcept>
21#include <cassert>
22
23using mir::client::FrameClock;
24using mir::time::PosixTimestamp;
25
26namespace
27{
28typedef std::unique_lock<std::mutex> Lock;
29} // namespace
30
31FrameClock::FrameClock(FrameClock::GetCurrentTime gct)
32 : get_current_time{gct}
33 , config_changed{false}
34 , period{0}
35 , resync_callback{std::bind(&FrameClock::fallback_resync_callback, this)}
36{
37}
38
39void FrameClock::set_period(std::chrono::nanoseconds ns)
40{
41 Lock lock(mutex);
42 period = ns;
43 config_changed = true;
44}
45
46void FrameClock::set_resync_callback(ResyncCallback cb)
47{
48 Lock lock(mutex);
49 resync_callback = cb;
50 config_changed = true;
51}
52
53PosixTimestamp FrameClock::fallback_resync_callback() const
54{
55 auto const now = get_current_time(PosixTimestamp().clock_id);
56 Lock lock(mutex);
57 /*
58 * The result here needs to be in phase for all processes that call it,
59 * so that nesting servers does not add lag.
60 */
61 return period != period.zero() ? now - (now % period) : now;
62}
63
64PosixTimestamp FrameClock::next_frame_after(PosixTimestamp prev) const
65{
66 Lock lock(mutex);
67 /*
68 * Unthrottled is an option too. But why?... Because a stream might exist
69 * that's not bound to a surface. And if it's not bound to a surface then
70 * it has no physical screen to sync to. Hence not throttled at all.
71 */
72 if (period == period.zero())
73 return prev;
74
75 /*
76 * Regardless of render times and scheduling delays, we should always
77 * target a perfectly even interval. This results in the greatest
78 * visual smoothness as well as providing a catch-up for cases where
79 * the client's render time was a little too long.
80 */
81 auto target = prev + period;
82
83 /*
84 * Count missed frames. Note how the target time would need to be more than
85 * one frame in the past to count as a missed frame. If the target time
86 * is less than one frame in the past then there's still a chance to catch
87 * up by rendering the next frame without delay. This avoids decimating
88 * to half frame rate.
89 * Compared to triple buffering, this approach is reactive rather than
90 * proactive. Both approaches have the same catch-up ability to avoid
91 * half frame rate, but this approach has the added benefit of only
92 * one frame of lag to the compositor compared to triple buffering's two
93 * frames of lag. But we're also doing better than double buffering here
94 * in that this approach avoids any additive lag when nested.
95 */
96 auto now = get_current_time(target.clock_id);
97 long const missed_frames = now > target ? (now - target) / period : 0L;
98
99 /*
100 * On the first frame and any resumption frame (after the client goes
101 * from idle to busy) this will trigger a query to ask for the latest
102 * hardware vsync timestamp. So we get in phase with the display.
103 * Crucially this is not required on most frames, so that even if it is
104 * implemented as a round trip to the server, that won't happen often.
105 */
106 if (missed_frames > 1 || config_changed)
107 {
108 lock.unlock();
109 auto const server_frame = resync_callback();
110 lock.lock();
111
112 /*
113 * Avoid mismatches (which will throw) and ensure we're always
114 * comparing timestamps of the same clock ID. This means our result
115 * 'target' might have a different clock to that of 'prev'. And that's
116 * OK... we want to use whatever clock ID the driver is using for
117 * its hardware vsync timestamps. We support migrating between clocks.
118 */
119 now = get_current_time(server_frame.clock_id);
120
121 /*
122 * It's important to target a future time and not allow 'now'. This
123 * is because we will have some buffer queues for the time being that
124 * have swapinterval 1, which if overfilled by being too agressive will
125 * cause visual lag. After all buffer queues move to always dropping
126 * (mailbox mode), this delay won't be required as a safety.
127 */
128 if (server_frame > now)
129 {
130 target = server_frame;
131 }
132 else
133 {
134 auto const age_ns = now - server_frame;
135 /*
136 * Ensure age_frames gets truncated if not already.
137 * C++ just guarantees a "signed integer type of at least 64 bits"
138 * for std::chrono::nanoseconds::rep
139 */
140 auto const age_frames = age_ns / period;
141 target = server_frame + (age_frames + 1) * period;
142 }
143 assert(target > now);
144 config_changed = false;
145 }
146 else if (missed_frames > 0)
147 {
148 /*
149 * Low number of missed frames. Try catching up without missing more...
150 */
151 // FIXME? Missing += operator
152 target = target + missed_frames * period;
153 }
154
155 return target;
156}
0157
=== added file 'src/client/frame_clock.h'
--- src/client/frame_clock.h 1970-01-01 00:00:00 +0000
+++ src/client/frame_clock.h 2017-01-17 06:23:26 +0000
@@ -0,0 +1,73 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
17 */
18
19#ifndef MIR_CLIENT_FRAME_CLOCK_H_
20#define MIR_CLIENT_FRAME_CLOCK_H_
21
22#include "mir/time/posix_timestamp.h"
23#include <chrono>
24#include <functional>
25#include <mutex>
26
27namespace mir { namespace client {
28
29class FrameClock
30{
31public:
32 typedef std::function<time::PosixTimestamp()> ResyncCallback;
33 typedef std::function<time::PosixTimestamp(clockid_t)> GetCurrentTime;
34
35 FrameClock(GetCurrentTime);
36 FrameClock() : FrameClock(mir::time::PosixTimestamp::now) {}
37
38 /**
39 * Set the precise frame period in nanoseconds (1000000000/Hz).
40 */
41 void set_period(std::chrono::nanoseconds);
42
43 /**
44 * Optionally set a callback that obtains the latest hardware vsync
45 * timestamp. This provides phase correction for increased precision but is
46 * not strictly required.
47 * Lowest precision: Don't provide a callback.
48 * Medium precision: Provide a callback which returns a recent timestamp.
49 * Highest precision: Provide a callback that queries the server.
50 */
51 void set_resync_callback(ResyncCallback);
52
53 /**
54 * Return the next timestamp to sleep_until, which comes after the last one
55 * that was slept till. On the first frame you can just provide an
56 * uninitialized timestamp.
57 */
58 time::PosixTimestamp next_frame_after(time::PosixTimestamp prev) const;
59
60private:
61 time::PosixTimestamp fallback_resync_callback() const;
62
63 GetCurrentTime const get_current_time;
64
65 mutable std::mutex mutex; // Protects below fields:
66 mutable bool config_changed;
67 std::chrono::nanoseconds period;
68 ResyncCallback resync_callback;
69};
70
71}} // namespace mir::client
72
73#endif // MIR_CLIENT_FRAME_CLOCK_H_
074
=== modified file 'src/client/mir_buffer_stream_api.cpp'
--- src/client/mir_buffer_stream_api.cpp 2017-01-17 05:51:54 +0000
+++ src/client/mir_buffer_stream_api.cpp 2017-01-17 06:23:26 +0000
@@ -113,6 +113,13 @@
113 void* context)113 void* context)
114try114try
115{115{
116 /*
117 * TODO: Add client-side vsync support for mir_buffer_stream_swap_buffers()
118 * Not in a hurry though, because the old server-side vsync is still
119 * present and AFAIK the only user of swap_buffers callbacks is Xmir.
120 * There are many ways to approach the problem and some more
121 * contentious than others, so do it later.
122 */
116 return buffer_stream->swap_buffers([buffer_stream, callback, context]{123 return buffer_stream->swap_buffers([buffer_stream, callback, context]{
117 if (callback)124 if (callback)
118 callback(buffer_stream, context);125 callback(buffer_stream, context);
@@ -209,7 +216,7 @@
209MirWaitHandle* mir_buffer_stream_set_swapinterval(MirBufferStream* buffer_stream, int interval)216MirWaitHandle* mir_buffer_stream_set_swapinterval(MirBufferStream* buffer_stream, int interval)
210try217try
211{218{
212 if ((interval < 0) || (interval > 1))219 if (interval < 0)
213 return nullptr;220 return nullptr;
214221
215 if (!buffer_stream)222 if (!buffer_stream)
216223
=== modified file 'src/client/mir_surface.cpp'
--- src/client/mir_surface.cpp 2017-01-17 05:51:54 +0000
+++ src/client/mir_surface.cpp 2017-01-17 06:23:26 +0000
@@ -21,6 +21,7 @@
21#include "cursor_configuration.h"21#include "cursor_configuration.h"
22#include "make_protobuf_object.h"22#include "make_protobuf_object.h"
23#include "mir_protobuf.pb.h"23#include "mir_protobuf.pb.h"
24#include "connection_surface_map.h"
2425
25#include "mir_toolkit/mir_client_library.h"26#include "mir_toolkit/mir_client_library.h"
26#include "mir/frontend/client_constants.h"27#include "mir/frontend/client_constants.h"
@@ -31,6 +32,7 @@
31#include "mir/input/xkb_mapper.h"32#include "mir/input/xkb_mapper.h"
32#include "mir/cookie/cookie.h"33#include "mir/cookie/cookie.h"
33#include "mir_cookie.h"34#include "mir_cookie.h"
35#include "mir/time/posix_timestamp.h"
3436
35#include <cassert>37#include <cassert>
36#include <unistd.h>38#include <unistd.h>
@@ -46,6 +48,8 @@
46namespace gp = google::protobuf;48namespace gp = google::protobuf;
47namespace md = mir::dispatch;49namespace md = mir::dispatch;
4850
51using mir::client::FrameClock;
52
49namespace53namespace
50{54{
51std::mutex handle_mutex;55std::mutex handle_mutex;
@@ -96,6 +100,7 @@
96 std::shared_ptr<MirWaitHandle> const& handle) :100 std::shared_ptr<MirWaitHandle> const& handle) :
97 surface{mcl::make_protobuf_object<mir::protobuf::Surface>()},101 surface{mcl::make_protobuf_object<mir::protobuf::Surface>()},
98 connection_(conn),102 connection_(conn),
103 frame_clock(std::make_shared<FrameClock>()),
99 creation_handle(handle)104 creation_handle(handle)
100{105{
101 surface->set_error(error);106 surface->set_error(error);
@@ -103,6 +108,8 @@
103108
104 std::lock_guard<decltype(handle_mutex)> lock(handle_mutex);109 std::lock_guard<decltype(handle_mutex)> lock(handle_mutex);
105 valid_surfaces.insert(this);110 valid_surfaces.insert(this);
111
112 configure_frame_clock();
106}113}
107114
108MirSurface::MirSurface(115MirSurface::MirSurface(
@@ -126,12 +133,19 @@
126 input_platform(input_platform),133 input_platform(input_platform),
127 keymapper(std::make_shared<mircv::XKBMapper>()),134 keymapper(std::make_shared<mircv::XKBMapper>()),
128 configure_result{mcl::make_protobuf_object<mir::protobuf::SurfaceSetting>()},135 configure_result{mcl::make_protobuf_object<mir::protobuf::SurfaceSetting>()},
136 frame_clock(std::make_shared<FrameClock>()),
129 creation_handle(handle),137 creation_handle(handle),
130 size({surface_proto.width(), surface_proto.height()}),138 size({surface_proto.width(), surface_proto.height()}),
131 format(static_cast<MirPixelFormat>(surface_proto.pixel_format())),139 format(static_cast<MirPixelFormat>(surface_proto.pixel_format())),
132 usage(static_cast<MirBufferUsage>(surface_proto.buffer_usage())),140 usage(static_cast<MirBufferUsage>(surface_proto.buffer_usage())),
133 output_id(spec.output_id.is_set() ? spec.output_id.value() : static_cast<uint32_t>(mir_display_output_id_invalid))141 output_id(spec.output_id.is_set() ? spec.output_id.value() : static_cast<uint32_t>(mir_display_output_id_invalid))
134{142{
143 if (default_stream)
144 {
145 streams.insert(default_stream);
146 default_stream->adopted_by(this);
147 }
148
135 for(int i = 0; i < surface_proto.attributes_size(); i++)149 for(int i = 0; i < surface_proto.attributes_size(); i++)
136 {150 {
137 auto const& attrib = surface_proto.attributes(i);151 auto const& attrib = surface_proto.attributes(i);
@@ -155,10 +169,23 @@
155169
156 std::lock_guard<decltype(handle_mutex)> lock(handle_mutex);170 std::lock_guard<decltype(handle_mutex)> lock(handle_mutex);
157 valid_surfaces.insert(this);171 valid_surfaces.insert(this);
172
173 configure_frame_clock();
158}174}
159175
160MirSurface::~MirSurface()176MirSurface::~MirSurface()
161{177{
178 StreamSet old_streams;
179
180 {
181 std::lock_guard<decltype(mutex)> lock(mutex);
182 old_streams = std::move(streams);
183 }
184
185 // Do callbacks without holding the lock
186 for (auto const& s : old_streams)
187 s->unadopted_by(this);
188
162 {189 {
163 std::lock_guard<decltype(handle_mutex)> lock(handle_mutex);190 std::lock_guard<decltype(handle_mutex)> lock(handle_mutex);
164 valid_surfaces.erase(this);191 valid_surfaces.erase(this);
@@ -172,6 +199,18 @@
172 close(surface->fd(i));199 close(surface->fd(i));
173}200}
174201
202void MirSurface::configure_frame_clock()
203{
204 /*
205 * TODO: Implement frame_clock->set_resync_callback(...) when IPC to get
206 * timestamps from the server exists.
207 * Until we do, client-side vsync will perform suboptimally (it is
208 * randomly up to one frame out of phase with the real display).
209 * However even while it's suboptimal it's dramatically lower latency
210 * than the old approach and still totally eliminates nesting lag.
211 */
212}
213
175MirWindowParameters MirSurface::get_parameters() const214MirWindowParameters MirSurface::get_parameters() const
176{215{
177 std::lock_guard<decltype(mutex)> lock(mutex);216 std::lock_guard<decltype(mutex)> lock(mutex);
@@ -458,6 +497,36 @@
458 default_stream->set_size(size);497 default_stream->set_size(size);
459 break;498 break;
460 }499 }
500 case mir_event_type_surface_output:
501 {
502 /*
503 * We set the frame clock according to the (maximum) refresh rate of
504 * the display. Note that we don't do the obvious thing and measure
505 * previous vsyncs mainly because that's a very unreliable predictor
506 * of the desired maximum refresh rate in the case of GSync/FreeSync
507 * and panel self-refresh. You can't assume the previous frame
508 * represents the display running at full native speed and so should
509 * not measure it. Instead we conveniently have
510 * mir_surface_output_event_get_refresh_rate that tells us the full
511 * native speed of the most relevant output...
512 */
513 auto soevent = mir_event_get_surface_output_event(&e);
514 auto rate = mir_surface_output_event_get_refresh_rate(soevent);
515 if (rate > 10.0) // should be >0, but 10 to workaround LP: #1639725
516 {
517 std::chrono::nanoseconds const ns(
518 static_cast<long>(1000000000L / rate));
519 frame_clock->set_period(ns);
520 }
521 /* else: The graphics driver has not provided valid timing so we will
522 * default to swap interval 0 behaviour.
523 * Or would people prefer some different fallback?
524 * - Re-enable server-side vsync?
525 * - Rate limit to 60Hz?
526 * - Just fix LP: #1639725?
527 */
528 break;
529 }
461 default:530 default:
462 break;531 break;
463 };532 };
@@ -609,17 +678,30 @@
609678
610 if (spec.streams.is_set())679 if (spec.streams.is_set())
611 {680 {
681 std::shared_ptr<mir::client::ConnectionSurfaceMap> map;
682 StreamSet old_streams, new_streams;
683
612 /*684 /*
613 * Note that we don't check for errors from modify_surface. So in685 * Note that we are updating our local copy of 'streams' before the
614 * updating default_stream here, we're just assuming it will succeed.686 * server has updated its copy. That's mainly because we have never yet
615 * Seems to be a harmless assumption to have made so far and much687 * implemented any kind of client-side error handling for when
616 * simpler than communicating back suceessful mappings from the server,688 * modify_surface fails and is rejected. There's a prototype started
617 * but there is a prototype started for that: lp:~vanvugt/mir/adoption689 * along those lines in lp:~vanvugt/mir/adoption, but it does not seem
690 * to be important to block on...
691 * The worst case is still perfectly acceptable: That is if the server
692 * rejects the surface spec our local copy of streams is wrong. That's
693 * a harmless consequence which just means the stream is now
694 * synchronized to the output the client intended even though the server
695 * is refusing to display it.
618 */696 */
619 {697 {
620 std::lock_guard<decltype(mutex)> lock(mutex);698 std::lock_guard<decltype(mutex)> lock(mutex);
621 default_stream = nullptr;699 default_stream = nullptr;
700 old_streams = std::move(streams);
701 streams.clear(); // Leave the container valid before we unlock
702 map = connection_->connection_surface_map();
622 }703 }
704
623 for(auto const& stream : spec.streams.value())705 for(auto const& stream : spec.streams.value())
624 {706 {
625 auto const new_stream = surface_specification->add_stream();707 auto const new_stream = surface_specification->add_stream();
@@ -631,6 +713,27 @@
631 new_stream->set_width(stream.size.value().width.as_int());713 new_stream->set_width(stream.size.value().width.as_int());
632 new_stream->set_height(stream.size.value().height.as_int());714 new_stream->set_height(stream.size.value().height.as_int());
633 }715 }
716
717 mir::frontend::BufferStreamId id(stream.stream_id);
718 if (auto bs = map->stream(id))
719 new_streams.insert(bs);
720 /* else: we could implement stream ID validation here, which we've
721 * never had before.
722 * The only problem with that plan is that we have some tests
723 * that rely on the ability to pass in invalid stream IDs so those
724 * need fixing.
725 */
726 }
727
728 // Worth optimizing and avoiding the intersection of both sets?....
729 for (auto const& old_stream : old_streams)
730 old_stream->unadopted_by(this);
731 for (auto const& new_stream : new_streams)
732 new_stream->adopted_by(this);
733 // ... probably not, since we can std::move(new_streams) this way...
734 {
735 std::unique_lock<decltype(mutex)> lock(mutex);
736 streams = std::move(new_streams);
634 }737 }
635 }738 }
636739
@@ -672,3 +775,8 @@
672{775{
673 return connection_;776 return connection_;
674}777}
778
779std::shared_ptr<FrameClock> MirSurface::get_frame_clock() const
780{
781 return frame_clock;
782}
675783
=== modified file 'src/client/mir_surface.h'
--- src/client/mir_surface.h 2017-01-17 05:51:54 +0000
+++ src/client/mir_surface.h 2017-01-17 06:23:26 +0000
@@ -21,6 +21,7 @@
21#include "cursor_configuration.h"21#include "cursor_configuration.h"
22#include "mir/mir_buffer_stream.h"22#include "mir/mir_buffer_stream.h"
23#include "mir_wait_handle.h"23#include "mir_wait_handle.h"
24#include "frame_clock.h"
24#include "rpc/mir_display_server.h"25#include "rpc/mir_display_server.h"
25#include "rpc/mir_display_server_debug.h"26#include "rpc/mir_display_server_debug.h"
2627
@@ -32,6 +33,7 @@
32#include "mir_toolkit/common.h"33#include "mir_toolkit/common.h"
33#include "mir_toolkit/mir_client_library.h"34#include "mir_toolkit/mir_client_library.h"
34#include "mir/graphics/native_buffer.h"35#include "mir/graphics/native_buffer.h"
36#include "mir/time/posix_timestamp.h"
3537
36#include <memory>38#include <memory>
37#include <functional>39#include <functional>
@@ -214,9 +216,13 @@
214216
215 MirWaitHandle* request_persistent_id(mir_surface_id_callback callback, void* context);217 MirWaitHandle* request_persistent_id(mir_surface_id_callback callback, void* context);
216 MirConnection* connection() const;218 MirConnection* connection() const;
219
220 std::shared_ptr<mir::client::FrameClock> get_frame_clock() const;
221
217private:222private:
218 std::mutex mutable mutex; // Protects all members of *this223 std::mutex mutable mutex; // Protects all members of *this
219224
225 void configure_frame_clock();
220 void on_configured();226 void on_configured();
221 void on_cursor_configured();227 void on_cursor_configured();
222 void acquired_persistent_id(mir_surface_id_callback callback, void* context);228 void acquired_persistent_id(mir_surface_id_callback callback, void* context);
@@ -242,6 +248,8 @@
242248
243 //Deprecated functions can cause MirSurfaces to be created with a default stream249 //Deprecated functions can cause MirSurfaces to be created with a default stream
244 std::shared_ptr<MirBufferStream> default_stream;250 std::shared_ptr<MirBufferStream> default_stream;
251 typedef std::unordered_set<std::shared_ptr<MirBufferStream>> StreamSet;
252 StreamSet streams;
245 std::shared_ptr<mir::input::receiver::InputPlatform> const input_platform;253 std::shared_ptr<mir::input::receiver::InputPlatform> const input_platform;
246 std::shared_ptr<mir::input::receiver::XKBMapper> const keymapper;254 std::shared_ptr<mir::input::receiver::XKBMapper> const keymapper;
247255
@@ -251,6 +259,8 @@
251 int attrib_cache[mir_window_attribs];259 int attrib_cache[mir_window_attribs];
252 MirOrientation orientation = mir_orientation_normal;260 MirOrientation orientation = mir_orientation_normal;
253261
262 std::shared_ptr<mir::client::FrameClock> const frame_clock;
263
254 std::function<void(MirEvent const*)> handle_event_callback;264 std::function<void(MirEvent const*)> handle_event_callback;
255 std::shared_ptr<mir::dispatch::ThreadedDispatcher> input_thread;265 std::shared_ptr<mir::dispatch::ThreadedDispatcher> input_thread;
256266
257267
=== modified file 'src/client/mir_surface_api.cpp'
--- src/client/mir_surface_api.cpp 2017-01-17 05:51:54 +0000
+++ src/client/mir_surface_api.cpp 2017-01-17 06:23:26 +0000
@@ -1204,7 +1204,7 @@
12041204
1205MirWaitHandle* mir_surface_set_swapinterval(MirSurface* surf, int interval)1205MirWaitHandle* mir_surface_set_swapinterval(MirSurface* surf, int interval)
1206{1206{
1207 if ((interval < 0) || (interval > 1))1207 if (interval < 0)
1208 return nullptr;1208 return nullptr;
12091209
1210 try1210 try
12111211
=== modified file 'src/client/screencast_stream.cpp'
--- src/client/screencast_stream.cpp 2017-01-17 05:51:54 +0000
+++ src/client/screencast_stream.cpp 2017-01-17 06:23:26 +0000
@@ -264,6 +264,14 @@
264 BOOST_THROW_EXCEPTION(std::logic_error("Attempt to set swap interval on screencast is invalid"));264 BOOST_THROW_EXCEPTION(std::logic_error("Attempt to set swap interval on screencast is invalid"));
265}265}
266266
267void mcl::ScreencastStream::adopted_by(MirWindow*)
268{
269}
270
271void mcl::ScreencastStream::unadopted_by(MirWindow*)
272{
273}
274
267void mcl::ScreencastStream::set_size(geom::Size)275void mcl::ScreencastStream::set_size(geom::Size)
268{276{
269 BOOST_THROW_EXCEPTION(std::logic_error("Attempt to set size on screencast is invalid"));277 BOOST_THROW_EXCEPTION(std::logic_error("Attempt to set size on screencast is invalid"));
270278
=== modified file 'src/client/screencast_stream.h'
--- src/client/screencast_stream.h 2017-01-17 05:51:54 +0000
+++ src/client/screencast_stream.h 2017-01-17 06:23:26 +0000
@@ -78,6 +78,8 @@
78 uint32_t get_current_buffer_id() override;78 uint32_t get_current_buffer_id() override;
79 int swap_interval() const override;79 int swap_interval() const override;
80 MirWaitHandle* set_swap_interval(int interval) override;80 MirWaitHandle* set_swap_interval(int interval) override;
81 void adopted_by(MirWindow*) override;
82 void unadopted_by(MirWindow*) override;
81 void set_buffer_cache_size(unsigned int) override;83 void set_buffer_cache_size(unsigned int) override;
8284
83 EGLNativeWindowType egl_native_window() override;85 EGLNativeWindowType egl_native_window() override;
8486
=== modified file 'src/include/client/mir/mir_buffer_stream.h'
--- src/include/client/mir/mir_buffer_stream.h 2017-01-17 05:51:54 +0000
+++ src/include/client/mir/mir_buffer_stream.h 2017-01-17 06:23:26 +0000
@@ -88,6 +88,8 @@
8888
89 virtual int swap_interval() const = 0;89 virtual int swap_interval() const = 0;
90 virtual MirWaitHandle* set_swap_interval(int interval) = 0;90 virtual MirWaitHandle* set_swap_interval(int interval) = 0;
91 virtual void adopted_by(MirWindow*) = 0;
92 virtual void unadopted_by(MirWindow*) = 0;
9193
92 virtual MirNativeBuffer* get_current_buffer_package() = 0;94 virtual MirNativeBuffer* get_current_buffer_package() = 0;
93 virtual MirPlatformType platform_type() = 0;95 virtual MirPlatformType platform_type() = 0;
9496
=== modified file 'tests/acceptance-tests/test_client_library.cpp'
--- tests/acceptance-tests/test_client_library.cpp 2017-01-17 05:51:54 +0000
+++ tests/acceptance-tests/test_client_library.cpp 2017-01-17 06:23:26 +0000
@@ -809,6 +809,11 @@
809809
810 buffers = 0;810 buffers = 0;
811811
812 // StubDisplayConfiguration is set to 60Hz and we now actually honour
813 // that. So to avoid 1024 frames taking 17 seconds or so...
814 mir_buffer_stream_set_swapinterval(mir_surface_get_buffer_stream(surf_one), 0);
815 mir_buffer_stream_set_swapinterval(mir_surface_get_buffer_stream(surf_two), 0);
816
812 while (buffers < 1024)817 while (buffers < 1024)
813 {818 {
814 mir_buffer_stream_swap_buffers_sync(819 mir_buffer_stream_swap_buffers_sync(
815820
=== modified file 'tests/acceptance-tests/test_latency.cpp'
--- tests/acceptance-tests/test_latency.cpp 2017-01-17 05:51:54 +0000
+++ tests/acceptance-tests/test_latency.cpp 2017-01-17 06:23:26 +0000
@@ -24,6 +24,7 @@
24#include "mir/test/doubles/null_display.h"24#include "mir/test/doubles/null_display.h"
25#include "mir/test/doubles/null_display_buffer.h"25#include "mir/test/doubles/null_display_buffer.h"
26#include "mir/test/doubles/null_display_sync_group.h"26#include "mir/test/doubles/null_display_sync_group.h"
27#include "mir/test/doubles/stub_display_configuration.h"
27#include "mir/test/fake_shared.h"28#include "mir/test/fake_shared.h"
28#include "mir_test_framework/visible_surface.h"29#include "mir_test_framework/visible_surface.h"
29#include "mir/options/option.h"30#include "mir/options/option.h"
@@ -240,12 +241,36 @@
240 TimeTrackingDisplay(Stats& stats)241 TimeTrackingDisplay(Stats& stats)
241 : group{stats}242 : group{stats}
242 {243 {
244 mg::DisplayConfigurationOutput output{
245 mg::DisplayConfigurationOutputId{1},
246 mg::DisplayConfigurationCardId{1},
247 mg::DisplayConfigurationOutputType::hdmia,
248 std::vector<MirPixelFormat>{mir_pixel_format_abgr_8888},
249 {{{1920,1080}, refresh_rate}}, // <=== Refresh rate must be valid
250 0, mir::geometry::Size{}, true, true, mir::geometry::Point{}, 0,
251 mir_pixel_format_abgr_8888, mir_power_mode_on,
252 mir_orientation_normal,
253 1.0f,
254 mir_form_factor_monitor,
255 mir_subpixel_arrangement_unknown,
256 {},
257 mir_output_gamma_unsupported,
258 {}
259 };
260 outputs.push_back(output);
261 }
262
263 std::unique_ptr<mg::DisplayConfiguration> configuration() const override
264 {
265 return std::make_unique<mtd::StubDisplayConfig>(outputs);
243 }266 }
244267
245 void for_each_display_sync_group(std::function<void(mg::DisplaySyncGroup&)> const& f) override268 void for_each_display_sync_group(std::function<void(mg::DisplaySyncGroup&)> const& f) override
246 {269 {
247 f(group);270 f(group);
248 }271 }
272
273 std::vector<mg::DisplayConfigurationOutput> outputs;
249 TimeTrackingGroup group;274 TimeTrackingGroup group;
250};275};
251 276
@@ -288,7 +313,7 @@
288};313};
289}314}
290315
291TEST_F(ClientLatency, average_latency_is_less_than_nbuffers)316TEST_F(ClientLatency, average_swap_buffers_sync_latency_is_one_frame)
292{317{
293 auto stream = mir_window_get_buffer_stream(surface);318 auto stream = mir_window_get_buffer_stream(surface);
294 auto const deadline = steady_clock::now() + 60s;319 auto const deadline = steady_clock::now() + 60s;
@@ -308,7 +333,7 @@
308333
309 auto average_latency = display.group.average_latency();334 auto average_latency = display.group.average_latency();
310335
311 EXPECT_THAT(average_latency, Lt(expected_client_buffers));336 EXPECT_NEAR(1.0f, average_latency, error_margin);
312}337}
313338
314TEST_F(ClientLatency, max_latency_is_limited_to_nbuffers)339TEST_F(ClientLatency, max_latency_is_limited_to_nbuffers)
315340
=== modified file 'tests/include/mir/test/doubles/mock_mir_buffer_stream.h'
--- tests/include/mir/test/doubles/mock_mir_buffer_stream.h 2017-01-17 05:51:54 +0000
+++ tests/include/mir/test/doubles/mock_mir_buffer_stream.h 2017-01-17 06:23:26 +0000
@@ -43,6 +43,8 @@
43 MOCK_METHOD0(secure_for_cpu_write, std::shared_ptr<client::MemoryRegion>());43 MOCK_METHOD0(secure_for_cpu_write, std::shared_ptr<client::MemoryRegion>());
44 MOCK_CONST_METHOD0(swap_interval, int());44 MOCK_CONST_METHOD0(swap_interval, int());
45 MOCK_METHOD1(set_swap_interval, MirWaitHandle*(int));45 MOCK_METHOD1(set_swap_interval, MirWaitHandle*(int));
46 MOCK_METHOD1(adopted_by, void(MirWindow*));
47 MOCK_METHOD1(unadopted_by, void(MirWindow*));
46 MOCK_METHOD0(platform_type, MirPlatformType(void));48 MOCK_METHOD0(platform_type, MirPlatformType(void));
47 MOCK_METHOD0(get_current_buffer_package, MirNativeBuffer*(void));49 MOCK_METHOD0(get_current_buffer_package, MirNativeBuffer*(void));
48 MOCK_METHOD0(get_create_wait_handle, MirWaitHandle*(void));50 MOCK_METHOD0(get_create_wait_handle, MirWaitHandle*(void));
4951
=== modified file 'tests/unit-tests/client/CMakeLists.txt'
--- tests/unit-tests/client/CMakeLists.txt 2017-01-17 05:51:54 +0000
+++ tests/unit-tests/client/CMakeLists.txt 2017-01-17 06:23:26 +0000
@@ -24,6 +24,7 @@
24 ${CMAKE_CURRENT_SOURCE_DIR}/test_client_mir_error.cpp24 ${CMAKE_CURRENT_SOURCE_DIR}/test_client_mir_error.cpp
25 ${CMAKE_CURRENT_SOURCE_DIR}/test_no_tls_future.cpp25 ${CMAKE_CURRENT_SOURCE_DIR}/test_no_tls_future.cpp
26 ${CMAKE_CURRENT_SOURCE_DIR}/test_mir_render_surface.cpp26 ${CMAKE_CURRENT_SOURCE_DIR}/test_mir_render_surface.cpp
27 ${CMAKE_CURRENT_SOURCE_DIR}/test_frame_clock.cpp
27)28)
2829
29if (NOT MIR_DISABLE_INPUT)30if (NOT MIR_DISABLE_INPUT)
3031
=== modified file 'tests/unit-tests/client/test_client_mir_surface.cpp'
--- tests/unit-tests/client/test_client_mir_surface.cpp 2017-01-17 05:51:54 +0000
+++ tests/unit-tests/client/test_client_mir_surface.cpp 2017-01-17 06:23:26 +0000
@@ -416,6 +416,87 @@
416 EXPECT_TRUE(dispatched->wait_for(std::chrono::seconds{5}));416 EXPECT_TRUE(dispatched->wait_for(std::chrono::seconds{5}));
417}417}
418418
419TEST_F(MirClientSurfaceTest, adopts_the_default_stream)
420{
421 using namespace ::testing;
422
423 auto mock_input_platform = std::make_shared<MockClientInputPlatform>();
424 auto mock_stream = std::make_shared<mtd::MockMirBufferStream>();
425
426 MirWindow* adopted_by = nullptr;
427 MirWindow* unadopted_by = nullptr;
428 EXPECT_CALL(*mock_stream, adopted_by(_))
429 .WillOnce(SaveArg<0>(&adopted_by));
430 EXPECT_CALL(*mock_stream, unadopted_by(_))
431 .WillOnce(SaveArg<0>(&unadopted_by));
432
433 {
434 MirWindow win{connection.get(), *client_comm_channel, nullptr,
435 mock_stream, mock_input_platform, spec, surface_proto, wh};
436 EXPECT_EQ(&win, adopted_by);
437 EXPECT_EQ(nullptr, unadopted_by);
438 }
439
440 EXPECT_NE(nullptr, unadopted_by);
441 EXPECT_EQ(adopted_by, unadopted_by);
442}
443
444TEST_F(MirClientSurfaceTest, adopts_custom_streams_if_set)
445{
446 using namespace testing;
447
448 auto mock_input_platform = std::make_shared<MockClientInputPlatform>();
449
450 mir::frontend::BufferStreamId const mock_old_stream_id(11);
451 auto mock_old_stream = std::make_shared<mtd::MockMirBufferStream>();
452 MirWindow* old_adopted_by = nullptr;
453 MirWindow* old_unadopted_by = nullptr;
454 EXPECT_CALL(*mock_old_stream, adopted_by(_))
455 .WillOnce(SaveArg<0>(&old_adopted_by));
456 EXPECT_CALL(*mock_old_stream, unadopted_by(_))
457 .WillOnce(SaveArg<0>(&old_unadopted_by));
458 ON_CALL(*mock_old_stream, rpc_id())
459 .WillByDefault(Return(mock_old_stream_id));
460
461 mir::frontend::BufferStreamId const mock_new_stream_id(22);
462 auto mock_new_stream = std::make_shared<mtd::MockMirBufferStream>();
463 MirWindow* new_adopted_by = nullptr;
464 MirWindow* new_unadopted_by = nullptr;
465 EXPECT_CALL(*mock_new_stream, adopted_by(_))
466 .WillOnce(SaveArg<0>(&new_adopted_by));
467 EXPECT_CALL(*mock_new_stream, unadopted_by(_))
468 .WillOnce(SaveArg<0>(&new_unadopted_by));
469 ON_CALL(*mock_new_stream, rpc_id())
470 .WillByDefault(Return(mock_new_stream_id));
471
472 surface_map->insert(mock_old_stream_id, mock_old_stream);
473 surface_map->insert(mock_new_stream_id, mock_new_stream);
474 {
475 MirWindow win{connection.get(), *client_comm_channel, nullptr,
476 mock_old_stream, mock_input_platform, spec, surface_proto, wh};
477
478 EXPECT_EQ(&win, old_adopted_by);
479 EXPECT_EQ(nullptr, old_unadopted_by);
480 EXPECT_EQ(nullptr, new_adopted_by);
481 EXPECT_EQ(nullptr, new_unadopted_by);
482
483 MirWindowSpec spec;
484 std::vector<ContentInfo> replacements
485 {
486 {geom::Displacement{0,0}, mock_new_stream_id.as_value(), geom::Size{1,1}}
487 };
488 spec.streams = replacements;
489 win.modify(spec)->wait_for_all();
490
491 EXPECT_EQ(&win, old_unadopted_by);
492 EXPECT_EQ(&win, new_adopted_by);
493 EXPECT_EQ(nullptr, new_unadopted_by);
494 }
495 EXPECT_EQ(new_adopted_by, new_unadopted_by);
496 surface_map->erase(mock_old_stream_id);
497 surface_map->erase(mock_new_stream_id);
498}
499
419TEST_F(MirClientSurfaceTest, replacing_delegate_with_nullptr_prevents_further_dispatch)500TEST_F(MirClientSurfaceTest, replacing_delegate_with_nullptr_prevents_further_dispatch)
420{501{
421 using namespace ::testing;502 using namespace ::testing;
422503
=== added file 'tests/unit-tests/client/test_frame_clock.cpp'
--- tests/unit-tests/client/test_frame_clock.cpp 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/client/test_frame_clock.cpp 2017-01-17 06:23:26 +0000
@@ -0,0 +1,420 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
17 */
18
19#include "src/client/frame_clock.h"
20#include <gtest/gtest.h>
21#include <unordered_map>
22
23using namespace ::testing;
24using namespace ::std::chrono_literals;
25using mir::time::PosixTimestamp;
26using mir::client::FrameClock;
27
28class FrameClockTest : public Test
29{
30public:
31 FrameClockTest()
32 {
33 with_fake_time = [this](clockid_t clk){ return fake_time[clk]; };
34 }
35
36 void SetUp()
37 {
38 for (auto& c : {CLOCK_MONOTONIC, CLOCK_REALTIME,
39 CLOCK_MONOTONIC_RAW, CLOCK_REALTIME_COARSE,
40 CLOCK_MONOTONIC_COARSE, CLOCK_BOOTTIME})
41 fake_time[c] = PosixTimestamp(c, c * c * 1234567ns);
42 int const hz = 60;
43 one_frame = std::chrono::nanoseconds(1000000000L/hz);
44 }
45
46 void fake_sleep_for(std::chrono::nanoseconds ns)
47 {
48 for (auto& f : fake_time)
49 f.second.nanoseconds += ns;
50 }
51
52 void fake_sleep_until(PosixTimestamp t)
53 {
54 auto& now = fake_time[t.clock_id];
55 if (t > now)
56 {
57 auto delta = t.nanoseconds - now.nanoseconds;
58 fake_sleep_for(delta);
59 }
60 }
61
62protected:
63 FrameClock::GetCurrentTime with_fake_time;
64 std::unordered_map<clockid_t,PosixTimestamp> fake_time;
65 std::chrono::nanoseconds one_frame;
66};
67
68TEST_F(FrameClockTest, unthrottled_without_a_period)
69{
70 FrameClock clock(with_fake_time);
71
72 PosixTimestamp a;
73 auto b = clock.next_frame_after(a);
74 EXPECT_EQ(a, b);
75 auto c = clock.next_frame_after(b);
76 EXPECT_EQ(b, c);
77}
78
79TEST_F(FrameClockTest, first_frame_is_within_one_frame)
80{
81 auto& now = fake_time[CLOCK_MONOTONIC];
82 auto a = now;
83 FrameClock clock(with_fake_time);
84 clock.set_period(one_frame);
85
86 PosixTimestamp b;
87 auto c = clock.next_frame_after(b);
88 EXPECT_GE(c, a);
89 EXPECT_LE(c-a, one_frame);
90}
91
92TEST_F(FrameClockTest, interval_is_perfectly_smooth)
93{
94 FrameClock clock(with_fake_time);
95 clock.set_period(one_frame);
96
97 PosixTimestamp a;
98 auto b = clock.next_frame_after(a);
99
100 fake_sleep_until(b);
101 fake_sleep_for(one_frame/13); // short render time
102
103 auto c = clock.next_frame_after(b);
104 EXPECT_EQ(one_frame, c - b);
105
106 fake_sleep_until(c);
107 fake_sleep_for(one_frame/7); // short render time
108
109 auto d = clock.next_frame_after(c);
110 EXPECT_EQ(one_frame, d - c);
111
112 fake_sleep_until(d);
113 fake_sleep_for(one_frame/5); // short render time
114
115 auto e = clock.next_frame_after(d);
116 EXPECT_EQ(one_frame, e - d);
117}
118
119TEST_F(FrameClockTest, long_render_time_is_recoverable_without_decimation)
120{
121 FrameClock clock(with_fake_time);
122 clock.set_period(one_frame);
123
124 auto& now = fake_time[CLOCK_MONOTONIC];
125 auto a = now;
126 auto b = clock.next_frame_after(a);
127
128 fake_sleep_until(b);
129 fake_sleep_for(one_frame * 5 / 4); // long render time; over a frame
130
131 auto c = clock.next_frame_after(b);
132 EXPECT_EQ(one_frame, c - b);
133
134 fake_sleep_until(c);
135 fake_sleep_for(one_frame * 7 / 6); // long render time; over a frame
136
137 auto d = clock.next_frame_after(c);
138 EXPECT_EQ(one_frame, d - c);
139
140 EXPECT_LT(d, now);
141
142 fake_sleep_until(d);
143 fake_sleep_for(one_frame/4); // short render time
144
145 // We can recover since we had a short render time...
146 auto e = clock.next_frame_after(d);
147 EXPECT_EQ(one_frame, e - d);
148}
149
150TEST_F(FrameClockTest, resuming_from_sleep_targets_the_future)
151{
152 FrameClock clock(with_fake_time);
153 clock.set_period(one_frame);
154
155 auto& now = fake_time[CLOCK_MONOTONIC];
156 PosixTimestamp a = now;
157 auto b = clock.next_frame_after(a);
158 fake_sleep_until(b);
159 auto c = clock.next_frame_after(b);
160 EXPECT_EQ(one_frame, c - b);
161 fake_sleep_until(c);
162
163 // Client idles for a while without producing new frames:
164 fake_sleep_for(567 * one_frame);
165
166 auto d = clock.next_frame_after(c);
167 EXPECT_GT(d, now); // Resumption must be in the future
168 EXPECT_LE(d, now+one_frame); // But not too far in the future
169}
170
171TEST_F(FrameClockTest, multiple_streams_in_sync)
172{
173 FrameClock clock(with_fake_time);
174 clock.set_period(one_frame);
175
176 PosixTimestamp left;
177 left = clock.next_frame_after(left);
178
179 fake_sleep_for(one_frame / 9);
180
181 PosixTimestamp right;
182 right = clock.next_frame_after(right);
183
184 ASSERT_EQ(left, right);
185 fake_sleep_until(left);
186 fake_sleep_until(right);
187
188 left = clock.next_frame_after(left);
189 fake_sleep_for(one_frame / 5);
190 right = clock.next_frame_after(right);
191
192 ASSERT_EQ(left, right);
193 fake_sleep_until(left);
194 fake_sleep_until(right);
195
196 left = clock.next_frame_after(left);
197 fake_sleep_for(one_frame / 7);
198 right = clock.next_frame_after(right);
199
200 ASSERT_EQ(left, right);
201}
202
203TEST_F(FrameClockTest, moving_between_displays_adapts_to_new_rate)
204{
205 FrameClock clock(with_fake_time);
206 clock.set_period(one_frame);
207
208 auto const one_tv_frame = std::chrono::nanoseconds(1000000000L / 25);
209 ASSERT_NE(one_frame, one_tv_frame);
210
211 PosixTimestamp a;
212 auto b = clock.next_frame_after(a);
213 fake_sleep_until(b);
214 auto c = clock.next_frame_after(b);
215 EXPECT_EQ(one_frame, c - b);
216
217 fake_sleep_until(c);
218
219 // Window moves to a new slower display:
220 clock.set_period(one_tv_frame);
221 auto d = clock.next_frame_after(c);
222 // Clock keeps ticking into the future for the new display:
223 EXPECT_GT(d, c);
224 // But not too far in the future:
225 EXPECT_LE(d, c + one_tv_frame);
226
227 // Vsync is now at the slower rate of the TV:
228 fake_sleep_until(d);
229 auto e = clock.next_frame_after(d);
230 EXPECT_EQ(one_tv_frame, e - d);
231 fake_sleep_until(e);
232 auto f = clock.next_frame_after(e);
233 EXPECT_EQ(one_tv_frame, f - e);
234
235 fake_sleep_until(f);
236
237 // Window moves back to the faster display:
238 clock.set_period(one_frame);
239 auto g = clock.next_frame_after(f);
240 // Clock keeps ticking into the future for the new display:
241 EXPECT_GT(g, f);
242 // But not too far in the future:
243 EXPECT_LE(g, f + one_tv_frame);
244
245 // Vsync is now at the faster rate again:
246 fake_sleep_until(g);
247 auto h = clock.next_frame_after(g);
248 EXPECT_EQ(one_frame, h - g);
249 fake_sleep_until(e);
250 auto i = clock.next_frame_after(h);
251 EXPECT_EQ(one_frame, i - h);
252}
253
254TEST_F(FrameClockTest, resuming_comes_in_phase_with_server_vsync)
255{
256 FrameClock clock(with_fake_time);
257 clock.set_period(one_frame);
258
259 auto& now = fake_time[CLOCK_MONOTONIC];
260 PosixTimestamp a = now;
261 auto b = clock.next_frame_after(a);
262 fake_sleep_until(b);
263 auto c = clock.next_frame_after(b);
264 EXPECT_EQ(one_frame, c - b);
265 fake_sleep_until(c);
266
267 // Client idles for a while without producing new frames:
268 fake_sleep_for(789 * one_frame);
269
270 auto last_server_frame = now - 556677ns;
271 clock.set_resync_callback([last_server_frame](){return last_server_frame;});
272
273 auto d = clock.next_frame_after(c);
274 EXPECT_GT(d, now); // Resumption must be in the future
275 EXPECT_LE(d, now+one_frame); // But not too far in the future
276
277 auto server_phase = last_server_frame % one_frame;
278 EXPECT_NE(server_phase, c % one_frame); // wasn't in phase before
279 EXPECT_EQ(server_phase, d % one_frame); // but is in phase now
280
281 // Not only did we come in phase but we're targeting the soonest frame
282 EXPECT_EQ(last_server_frame+one_frame, d);
283}
284
285TEST_F(FrameClockTest, switches_to_the_server_clock_on_startup)
286{
287 FrameClock clock(with_fake_time);
288 clock.set_period(one_frame);
289
290 PosixTimestamp a(CLOCK_MONOTONIC_COARSE, 0ns);
291
292 PosixTimestamp b;
293 EXPECT_NO_THROW({
294 b = clock.next_frame_after(a);
295 });
296
297 // The default resync callback uses CLOCK_MONOTONIC...
298 EXPECT_NE(a.clock_id, b.clock_id);
299}
300
301TEST_F(FrameClockTest, can_migrate_between_different_driver_clocks)
302{
303 FrameClock clock(with_fake_time);
304 clock.set_period(one_frame);
305
306 PosixTimestamp a = fake_time[CLOCK_MONOTONIC];
307 auto b = clock.next_frame_after(a);
308 fake_sleep_until(b);
309 auto c = clock.next_frame_after(b);
310 EXPECT_EQ(one_frame, c - b);
311
312 fake_sleep_until(c);
313
314 // Window moves between displays and in the glorious future that might even
315 // mean it switches cards, with different drivers, different clocks...
316 auto last_server_frame = fake_time[CLOCK_MONOTONIC_COARSE] - 556677ns;
317 clock.set_resync_callback([last_server_frame](){return last_server_frame;});
318
319 EXPECT_EQ(CLOCK_MONOTONIC, c.clock_id);
320 auto d = clock.next_frame_after(c);
321 EXPECT_EQ(CLOCK_MONOTONIC_COARSE, d.clock_id);
322
323 EXPECT_GT(d, fake_time[CLOCK_MONOTONIC_COARSE]);
324 EXPECT_LE(d, fake_time[CLOCK_MONOTONIC_COARSE]+one_frame);
325
326 auto server_phase = last_server_frame % one_frame;
327 EXPECT_NE(server_phase, c % one_frame); // wasn't in phase before
328 EXPECT_EQ(server_phase, d % one_frame); // but is in phase now
329
330 // Not only did we come in phase but we're targeting the soonest frame
331 EXPECT_EQ(last_server_frame+one_frame, d);
332}
333
334TEST_F(FrameClockTest, does_not_resync_on_most_frames)
335{
336 int callbacks = 0;
337
338 FrameClock clock(with_fake_time);
339 clock.set_period(one_frame);
340 clock.set_resync_callback([&callbacks]
341 {
342 ++callbacks;
343 return PosixTimestamp();
344 });
345
346 PosixTimestamp v;
347 v = clock.next_frame_after(v);
348 EXPECT_EQ(1, callbacks); // sync with server happens on first frame
349
350 fake_sleep_until(v);
351
352 v = clock.next_frame_after(v);
353 EXPECT_EQ(1, callbacks);
354
355 fake_sleep_until(v);
356
357 v = clock.next_frame_after(v);
358 EXPECT_EQ(1, callbacks);
359
360 fake_sleep_until(v);
361
362 v = clock.next_frame_after(v);
363 EXPECT_EQ(1, callbacks);
364
365 fake_sleep_for(one_frame * 10);
366
367 clock.next_frame_after(v);
368 EXPECT_EQ(2, callbacks); // resync because we went idle too long
369}
370
371TEST_F(FrameClockTest, one_frame_skipped_only_after_2_frames_take_3_periods)
372{
373 FrameClock clock(with_fake_time);
374 clock.set_period(one_frame);
375
376 PosixTimestamp a;
377 auto b = clock.next_frame_after(a);
378
379 fake_sleep_until(b);
380 fake_sleep_for(one_frame * 3 / 2); // Render time: 1.5 frames
381
382 auto c = clock.next_frame_after(b);
383 EXPECT_EQ(one_frame, c - b); // No frame skipped
384
385 fake_sleep_until(c);
386 fake_sleep_for(one_frame * 8 / 5); // Render time: 1.6 frames
387
388 auto d = clock.next_frame_after(c);
389 EXPECT_EQ(2*one_frame, d - c); // One frame skipped
390
391 fake_sleep_until(d);
392 fake_sleep_for(one_frame/4); // Short render time, immediate recovery
393
394 auto e = clock.next_frame_after(d);
395 EXPECT_EQ(one_frame, e - d); // No frame skipped. We have recovered.
396}
397
398TEST_F(FrameClockTest, nesting_adds_zero_lag)
399{
400 FrameClock inner(with_fake_time);
401 FrameClock outer(with_fake_time);
402 inner.set_period(one_frame);
403 outer.set_period(one_frame);
404
405 PosixTimestamp in0, out0;
406 auto in1 = inner.next_frame_after(in0);
407 fake_sleep_for(1234567ns); // scheduling and IPC delay
408 auto out1 = outer.next_frame_after(out0);
409 EXPECT_EQ(in1, out1);
410
411 fake_sleep_until(in1);
412 fake_sleep_until(out1);
413
414 auto in2 = inner.next_frame_after(in1);
415 fake_sleep_for(8765432ns); // scheduling and IPC delay
416 auto out2 = outer.next_frame_after(out1);
417 EXPECT_EQ(in2, out2);
418 EXPECT_EQ(one_frame, in2 - in1);
419 EXPECT_EQ(one_frame, out2 - out1);
420}
0421
=== modified file 'tests/unit-tests/platforms/android/server/test_hwc_device.cpp'
--- tests/unit-tests/platforms/android/server/test_hwc_device.cpp 2016-12-09 02:54:31 +0000
+++ tests/unit-tests/platforms/android/server/test_hwc_device.cpp 2017-01-17 06:23:26 +0000
@@ -601,6 +601,7 @@
601601
602//LP: #1369763. We could get swapinterval 0 to work with overlays, but we'd need602//LP: #1369763. We could get swapinterval 0 to work with overlays, but we'd need
603//the vsync signal from hwc to reach the client, so the client can rate-limit its submissions.603//the vsync signal from hwc to reach the client, so the client can rate-limit its submissions.
604// Update: We have client-side vsync now so it should be possible to do this...
604TEST_F(HwcDevice, rejects_list_containing_interval_0)605TEST_F(HwcDevice, rejects_list_containing_interval_0)
605{606{
606 mga::HwcDevice device(mock_device);607 mga::HwcDevice device(mock_device);

Subscribers

People subscribed via source and target branches