py: Add in fence test using swsync
[android/external-libkmsxx.git] / py / tests / sync.py
1 #!/usr/bin/python3
3 import ctypes
4 import fcntl
5 import os
6 import pykms
7 import selectors
8 import sys
9 import time
11 bar_width = 20
12 bar_speed = 8
14 class Timer(object):
15     timers = []
17     def __init__(self, timeout, callback, data):
18         self.timeout = time.clock_gettime(time.CLOCK_MONOTONIC) + timeout
19         self.callback = callback
20         self.data = data
22         print("adding timer %f" % self.timeout)
23         self.timers.append(self)
24         self.timers.sort(key=lambda timer: timer.timeout)
26     @classmethod
27     def fire(_class):
28         clk = time.clock_gettime(time.CLOCK_MONOTONIC)
29         while len(_class.timers) > 0:
30             timer = _class.timers[0]
31             if timer.timeout > clk:
32                 break
34             del _class.timers[0]
35             print("fireing timer %f" % timer.timeout)
36             timer.callback(timer.data)
38     @classmethod
39     def next_timeout(_class):
40         clk = time.clock_gettime(time.CLOCK_MONOTONIC)
41         if len(_class.timers) == 0 or _class.timers[0].timeout < clk:
42             return None
44         return _class.timers[0].timeout - clk
47 class Timeline(object):
49     class sw_sync_create_fence_data(ctypes.Structure):
50         _fields_ = [
51             ('value', ctypes.c_uint32),
52             ('name', ctypes.c_char * 32),
53             ('fence', ctypes.c_int32),
54         ]
56     SW_SYNC_IOC_CREATE_FENCE = (3 << 30) | (ctypes.sizeof(sw_sync_create_fence_data) << 16) | (ord('W') << 8) | (0 << 0)
57     SW_SYNC_IOC_INC = (1 << 30) | (ctypes.sizeof(ctypes.c_uint32) << 16) | (ord('W') << 8) | (1 << 0)
59     class SWSync(object):
60         def __init__(self, fd):
61             self.fd = fd
62         def __del__(self):
63             os.close(self.fd)
65     def __init__(self):
66         self.value = 0
67         try:
68             self.fd = os.open('/sys/kernel/debug/sync/sw_sync', 0);
69         except:
70             raise RuntimeError('Failed to open sw_sync file')
72     def close(self):
73         os.close(self.fd)
75     def create_fence(self, value):
76         data = self.sw_sync_create_fence_data(value = value);
77         print("ioctl number %u" % self.SW_SYNC_IOC_CREATE_FENCE)
78         ret = fcntl.ioctl(self.fd, self.SW_SYNC_IOC_CREATE_FENCE, data);
79         if ret < 0:
80             raise RuntimeError('Failed to create fence')
82         return self.SWSync(data.fence)
84     def signal(self, value):
85         fcntl.ioctl(self.fd, self.SW_SYNC_IOC_INC, ctypes.c_uint32(value))
86         self.value += value
89 class FlipHandler():
90     def __init__(self, crtc, width, height):
91         super().__init__()
92         self.crtc = crtc
93         self.timeline = Timeline()
94         self.bar_xpos = 0
95         self.front_buf = 0
96         self.fb1 = pykms.DumbFramebuffer(crtc.card, width, height, "XR24");
97         self.fb2 = pykms.DumbFramebuffer(crtc.card, width, height, "XR24");
98         self.flips = 0
99         self.flips_last = 0
100         self.frame_last = 0
101         self.time_last = 0
103     def handle_page_flip(self, frame, time):
104         if self.time_last == 0:
105             self.frame_last = frame
106             self.time_last = time
108         # Verify that the page flip hasn't completed before the timeline got
109         # signaled.
110         if self.timeline.value < 2 * self.flips - 1:
111             raise RuntimeError('Page flip %u for fence %u complete before timeline (%u)!' %
112                                (self.flips, 2 * self.flips - 1, self.timeline.value))
114         self.flips += 1
116         # Print statistics every 5 seconds.
117         time_delta = time - self.time_last
118         if time_delta >= 5:
119             frame_delta = frame - self.frame_last
120             flips_delta = self.flips - self.flips_last
121             print("Frame rate: %f (%u/%u frames in %f s)" %
122                   (frame_delta / time_delta, flips_delta, frame_delta, time_delta))
124             self.frame_last = frame
125             self.flips_last = self.flips
126             self.time_last = time
128         # Draw the color bar on the back buffer.
129         if self.front_buf == 0:
130             fb = self.fb2
131         else:
132             fb = self.fb1
134         self.front_buf = self.front_buf ^ 1
136         current_xpos = self.bar_xpos;
137         old_xpos = (current_xpos + (fb.width - bar_width - bar_speed)) % (fb.width - bar_width);
138         new_xpos = (current_xpos + bar_speed) % (fb.width - bar_width);
140         self.bar_xpos = new_xpos
142         pykms.draw_color_bar(fb, old_xpos, new_xpos, bar_width)
144         # Flip the buffers with an in fence located in the future. The atomic
145         # commit is asynchronous and returns immediately, but the flip should
146         # not complete before the fence gets signaled.
147         print("flipping with fence @%u, timeline is @%u" % (2 * self.flips - 1, self.timeline.value))
148         fence = self.timeline.create_fence(2 * self.flips - 1)
149         req = pykms.AtomicReq(self.crtc.card)
150         req.add(self.crtc.primary_plane, { 'FB_ID': fb.id, 'IN_FENCE_FD': fence.fd })
151         req.commit(self)
152         del fence
154         # Arm a timer to signal the fence in 0.5s.
155         def timeline_signal(timeline):
156             print("signaling timeline @%u" % timeline.value)
157             timeline.signal(2)
159         Timer(0.5, timeline_signal, self.timeline)
162 def main(argv):
163     if len(argv) > 1:
164         conn_name = argv[1]
165     else:
166         conn_name = ''
168     card = pykms.Card()
169     if not card.has_atomic:
170         raise RuntimeError('This test requires atomic update support')
172     res = pykms.ResourceManager(card)
173     conn = res.reserve_connector(conn_name)
174     crtc = res.reserve_crtc(conn)
175     mode = conn.get_default_mode()
177     flip_handler = FlipHandler(crtc, mode.hdisplay, mode.vdisplay)
179     fb = flip_handler.fb1
180     pykms.draw_color_bar(fb, fb.width - bar_width - bar_speed, bar_speed, bar_width)
181     mode_blob = mode.blob(card)
183     req = pykms.AtomicReq(card)
184     req.add(conn, 'CRTC_ID', crtc.id)
185     req.add(crtc, { 'ACTIVE': 1, 'MODE_ID': mode_blob.id })
186     req.add(crtc.primary_plane, {
187                 'FB_ID': fb.id,
188                 'CRTC_ID': crtc.id,
189                 'SRC_X': 0 << 16,
190                 'SRC_Y': 0 << 16,
191                 'SRC_W': fb.width << 16,
192                 'SRC_H': fb.height << 16,
193                 'CRTC_X': 0,
194                 'CRTC_Y': 0,
195                 'CRTC_W': fb.width,
196                 'CRTC_H': fb.height,
197     })
198     ret = req.commit(flip_handler, allow_modeset = True)
199     if ret < 0:
200         raise RuntimeError('Atomic mode set failed with %d' % ret)
202     def bye():
203         # Signal the timeline to complete all pending page flips
204         flip_handler.timeline.signal(100)
205         exit(0)
207     def readdrm(fileobj, mask):
208         for ev in card.read_events():
209             if ev.type == pykms.DrmEventType.FLIP_COMPLETE:
210                 ev.data.handle_page_flip(ev.seq, ev.time)
212     def readkey(fileobj, mask):
213         sys.stdin.readline()
214         bye()
216     sel = selectors.DefaultSelector()
217     sel.register(card.fd, selectors.EVENT_READ, readdrm)
218     sel.register(sys.stdin, selectors.EVENT_READ, readkey)
220     while True:
221         timeout = Timer.next_timeout()
222         print("--> timeout %s" % repr(timeout))
223         try:
224             events = sel.select(timeout)
225         except KeyboardInterrupt:
226             bye()
227         for key, mask in events:
228             callback = key.data
229             callback(key.fileobj, mask)
231         Timer.fire()
233 if __name__ == '__main__':
234     main(sys.argv)