summaryrefslogtreecommitdiff
path: root/py/tests/sync.py
blob: caa1d55d7a4c84693724799d1dce4d01d200f4d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#!/usr/bin/python3

import ctypes
import fcntl
import os
import pykms
import selectors
import sys
import time

bar_width = 20
bar_speed = 8

class Timer(object):
    timers = []

    def __init__(self, timeout, callback, data):
        self.timeout = time.clock_gettime(time.CLOCK_MONOTONIC) + timeout
        self.callback = callback
        self.data = data

        print("adding timer %f" % self.timeout)
        self.timers.append(self)
        self.timers.sort(key=lambda timer: timer.timeout)

    @classmethod
    def fire(_class):
        clk = time.clock_gettime(time.CLOCK_MONOTONIC)
        while len(_class.timers) > 0:
            timer = _class.timers[0]
            if timer.timeout > clk:
                break

            del _class.timers[0]
            print("fireing timer %f" % timer.timeout)
            timer.callback(timer.data)

    @classmethod
    def next_timeout(_class):
        clk = time.clock_gettime(time.CLOCK_MONOTONIC)
        if len(_class.timers) == 0 or _class.timers[0].timeout < clk:
            return None

        return _class.timers[0].timeout - clk


class Timeline(object):

    class sw_sync_create_fence_data(ctypes.Structure):
        _fields_ = [
            ('value', ctypes.c_uint32),
            ('name', ctypes.c_char * 32),
            ('fence', ctypes.c_int32),
        ]

    SW_SYNC_IOC_CREATE_FENCE = (3 << 30) | (ctypes.sizeof(sw_sync_create_fence_data) << 16) | (ord('W') << 8) | (0 << 0)
    SW_SYNC_IOC_INC = (1 << 30) | (ctypes.sizeof(ctypes.c_uint32) << 16) | (ord('W') << 8) | (1 << 0)

    class SWSync(object):
        def __init__(self, fd):
            self.fd = fd
        def __del__(self):
            os.close(self.fd)

    def __init__(self):
        self.value = 0
        try:
            self.fd = os.open('/sys/kernel/debug/sync/sw_sync', 0);
        except e:
            raise RuntimeError('Failed to open sw_sync file')

    def close(self):
        os.close(self.fd)

    def create_fence(self, value):
        data = self.sw_sync_create_fence_data(value = value);
        print("ioctl number %u" % self.SW_SYNC_IOC_CREATE_FENCE)
        ret = fcntl.ioctl(self.fd, self.SW_SYNC_IOC_CREATE_FENCE, data);
        if ret < 0:
            raise RuntimeError('Failed to create fence')

        return self.SWSync(data.fence)

    def signal(self, value):
        fcntl.ioctl(self.fd, self.SW_SYNC_IOC_INC, ctypes.c_uint32(value))
        self.value += value


class FlipHandler(pykms.PageFlipHandlerBase):
    def __init__(self, crtc, width, height):
        super().__init__()
        self.crtc = crtc
        self.timeline = Timeline()
        self.bar_xpos = 0
        self.front_buf = 0
        self.fb1 = pykms.DumbFramebuffer(crtc.card, width, height, "XR24");
        self.fb2 = pykms.DumbFramebuffer(crtc.card, width, height, "XR24");
        self.flips = 0
        self.flips_last = 0
        self.frame_last = 0
        self.time_last = 0

    def handle_page_flip(self, frame, time):
        if self.time_last == 0:
            self.frame_last = frame
            self.time_last = time

        # Verify that the page flip hasn't completed before the timeline got
        # signaled.
        if self.timeline.value < 2 * self.flips - 1:
            raise RuntimeError('Page flip %u for fence %u complete before timeline (%u)!' %
                               (self.flips, 2 * self.flips - 1, self.timeline.value))

        self.flips += 1

        # Print statistics every 5 seconds.
        time_delta = time - self.time_last
        if time_delta >= 5:
            frame_delta = frame - self.frame_last
            flips_delta = self.flips - self.flips_last
            print("Frame rate: %f (%u/%u frames in %f s)" %
                  (frame_delta / time_delta, flips_delta, frame_delta, time_delta))

            self.frame_last = frame
            self.flips_last = self.flips
            self.time_last = time

        # Draw the color bar on the back buffer.
        if self.front_buf == 0:
            fb = self.fb2
        else:
            fb = self.fb1

        self.front_buf = self.front_buf ^ 1

        current_xpos = self.bar_xpos;
        old_xpos = (current_xpos + (fb.width - bar_width - bar_speed)) % (fb.width - bar_width);
        new_xpos = (current_xpos + bar_speed) % (fb.width - bar_width);

        self.bar_xpos = new_xpos

        pykms.draw_color_bar(fb, old_xpos, new_xpos, bar_width)

        # Flip the buffers with an in fence located in the future. The atomic
        # commit is asynchronous and returns immediately, but the flip should
        # not complete before the fence gets signaled.
        print("flipping with fence @%u, timeline is @%u" % (2 * self.flips - 1, self.timeline.value))
        fence = self.timeline.create_fence(2 * self.flips - 1)
        req = pykms.AtomicReq(self.crtc.card)
        req.add(self.crtc.primary_plane, { 'FB_ID': fb.id, 'IN_FENCE_FD': fence.fd })
        req.commit(self)
        del fence

        # Arm a timer to signal the fence in 0.5s.
        def timeline_signal(timeline):
            print("signaling timeline @%u" % timeline.value)
            timeline.signal(2)

        Timer(0.5, timeline_signal, self.timeline)


def main(argv):
    if len(argv) > 1:
        conn_name = argv[1]
    else:
        conn_name = ''

    card = pykms.Card()
    if not card.has_atomic:
        raise RuntimeError('This test requires atomic update support')

    res = pykms.ResourceManager(card)
    conn = res.reserve_connector(conn_name)
    crtc = res.reserve_crtc(conn)
    mode = conn.get_default_mode()

    flip_handler = FlipHandler(crtc, mode.hdisplay, mode.vdisplay)

    fb = flip_handler.fb1
    pykms.draw_color_bar(fb, fb.width - bar_width - bar_speed, bar_speed, bar_width)
    mode_blob = mode.blob(card)

    req = pykms.AtomicReq(card)
    req.add(conn, 'CRTC_ID', crtc.id)
    req.add(crtc, { 'ACTIVE': 1, 'MODE_ID': mode_blob.id })
    req.add(crtc.primary_plane, {
                'FB_ID': fb.id,
                'CRTC_ID': crtc.id,
                'SRC_X': 0 << 16,
                'SRC_Y': 0 << 16,
                'SRC_W': fb.width << 16,
                'SRC_H': fb.height << 16,
                'CRTC_X': 0,
                'CRTC_Y': 0,
                'CRTC_W': fb.width,
                'CRTC_H': fb.height,
    })
    ret = req.commit(flip_handler, allow_modeset = True)
    if ret < 0:
        raise RuntimeError('Atomic mode set failed with %d' % ret)

    def readdrm(fileobj, mask):
        card.call_page_flip_handlers()

    def readkey(fileobj, mask):
        # Signal the timeline to complete all pending page flips
        flip_handler.timeline. signal(100)
        sys.stdin.readline()
        exit(0)

    sel = selectors.DefaultSelector()
    sel.register(card.fd, selectors.EVENT_READ, readdrm)
    sel.register(sys.stdin, selectors.EVENT_READ, readkey)

    while True:
        timeout = Timer.next_timeout()
        print("--> timeout %s" % repr(timeout))
        events = sel.select(timeout)
        for key, mask in events:
            callback = key.data
            callback(key.fileobj, mask)

        Timer.fire()

if __name__ == '__main__':
    main(sys.argv)