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