1#!/usr/bin/python3 2 3import ctypes 4import fcntl 5import os 6import pykms 7import selectors 8import sys 9import time 10 11bar_width = 20 12bar_speed = 8 13 14class Timer(object): 15 timers = [] 16 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 21 22 print("adding timer %f" % self.timeout) 23 self.timers.append(self) 24 self.timers.sort(key=lambda timer: timer.timeout) 25 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 33 34 del _class.timers[0] 35 print("fireing timer %f" % timer.timeout) 36 timer.callback(timer.data) 37 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 43 44 return _class.timers[0].timeout - clk 45 46 47class Timeline(object): 48 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 ] 55 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) 58 59 class SWSync(object): 60 def __init__(self, fd): 61 self.fd = fd 62 def __del__(self): 63 os.close(self.fd) 64 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') 71 72 def close(self): 73 os.close(self.fd) 74 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') 81 82 return self.SWSync(data.fence) 83 84 def signal(self, value): 85 fcntl.ioctl(self.fd, self.SW_SYNC_IOC_INC, ctypes.c_uint32(value)) 86 self.value += value 87 88 89class 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 102 103 def handle_page_flip(self, frame, time): 104 if self.time_last == 0: 105 self.frame_last = frame 106 self.time_last = time 107 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)) 113 114 self.flips += 1 115 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)) 123 124 self.frame_last = frame 125 self.flips_last = self.flips 126 self.time_last = time 127 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 133 134 self.front_buf = self.front_buf ^ 1 135 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); 139 140 self.bar_xpos = new_xpos 141 142 pykms.draw_color_bar(fb, old_xpos, new_xpos, bar_width) 143 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() 152 del fence 153 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) 158 159 Timer(0.5, timeline_signal, self.timeline) 160 161 162def main(argv): 163 if len(argv) > 1: 164 conn_name = argv[1] 165 else: 166 conn_name = '' 167 168 card = pykms.Card() 169 if not card.has_atomic: 170 raise RuntimeError('This test requires atomic update support') 171 172 res = pykms.ResourceManager(card) 173 conn = res.reserve_connector(conn_name) 174 crtc = res.reserve_crtc(conn) 175 mode = conn.get_default_mode() 176 177 flip_handler = FlipHandler(crtc, mode.hdisplay, mode.vdisplay) 178 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) 182 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) 201 202 def bye(): 203 # Signal the timeline to complete all pending page flips 204 flip_handler.timeline.signal(100) 205 exit(0) 206 207 def readdrm(fileobj, mask): 208 for ev in card.read_events(): 209 if ev.type == pykms.DrmEventType.FLIP_COMPLETE: 210 flip_handler.handle_page_flip(ev.seq, ev.time) 211 212 def readkey(fileobj, mask): 213 sys.stdin.readline() 214 bye() 215 216 sel = selectors.DefaultSelector() 217 sel.register(card.fd, selectors.EVENT_READ, readdrm) 218 sel.register(sys.stdin, selectors.EVENT_READ, readkey) 219 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) 230 231 Timer.fire() 232 233if __name__ == '__main__': 234 main(sys.argv) 235