diff options
-rwxr-xr-x | py/tests/sync.py | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/py/tests/sync.py b/py/tests/sync.py new file mode 100755 index 0000000..366996e --- /dev/null +++ b/py/tests/sync.py @@ -0,0 +1,228 @@ +#!/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: + 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(): + 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): + for ev in card.read_events(): + if ev.type == pykms.DrmEventType.FLIP_COMPLETE: + ev.data.handle_page_flip(ev.seq, ev.time) + + 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) |