diff --git a/src/mtlfns.m b/src/mtlfns.m index 8e0305997e6..4fcfea6b86e 100644 --- a/src/mtlfns.m +++ b/src/mtlfns.m @@ -606,6 +606,27 @@ frame. Returns t while animations are enabled, nil otherwise. */) return Qt; } +DEFUN ("mtl-vsync", Fmtl_vsync, Smtl_vsync, 1, 2, 0, + doc: /* Enable (non-nil) or disable display sync for FRAME's GPU layer. +With vsync on (default) presents wait for the display refresh: redisplay +is capped at the panel rate, which keeps CPU/GPU use minimal. With it +off, presents return immediately (lower latency, uncapped, more power). +FRAME defaults to the selected frame. */) + (Lisp_Object enable, Lisp_Object frame) +{ + if (NILP (frame)) frame = Fselected_frame (); + CHECK_LIVE_FRAME (frame); + g_mtl_vsync_enabled = !NILP (enable); + MtlFrameData *fd = mtl_get_frame_data (XFRAME (frame)); + if (fd && fd.metalLayer) + { + block_input (); + fd.metalLayer.displaySyncEnabled = g_mtl_vsync_enabled; + unblock_input (); + } + return enable; +} + void syms_of_mtlfns (void) { @@ -632,6 +653,7 @@ syms_of_mtlfns (void) defsubr (&Smtl_video_move); defsubr (&Smtl_video_tick); defsubr (&Smtl_anim_tick); + defsubr (&Smtl_vsync); } #endif /* HAVE_MTL */ diff --git a/src/mtlterm.h b/src/mtlterm.h index 9f67d0793fa..568b4ff9c08 100644 --- a/src/mtlterm.h +++ b/src/mtlterm.h @@ -293,6 +293,7 @@ extern MtlScrollEasing g_mtl_scroll_easing; extern float g_mtl_scroll_duration; /* seconds, default 0.15 */ extern NSUInteger g_mtl_trail_len; /* default 20 */ extern BOOL g_mtl_animations_enabled; /* default NO (animations are opt-in) */ +extern BOOL g_mtl_vsync_enabled; /* default YES (60fps power cap) */ /* ----------------------------------------------------------------------- Global Metal display list diff --git a/src/mtlterm.m b/src/mtlterm.m index 5f58b73f175..572d1226598 100644 --- a/src/mtlterm.m +++ b/src/mtlterm.m @@ -262,6 +262,11 @@ MtlScrollEasing g_mtl_scroll_easing = MTL_EASE_OUT_QUAD; float g_mtl_scroll_duration = 0.15f; NSUInteger g_mtl_trail_len = 20; +/* When false, presents do not wait for the display refresh + (CAMetalLayer.displaySyncEnabled): redisplay never blocks on a + drawable, trading the 60 fps power cap for NS-like latency. */ +BOOL g_mtl_vsync_enabled = YES; + /* Master switch for the GPU animation layer (cursor effects, particles, CADisplayLink @60fps). OFF by default: the goal is pixel-correct parity with the NS backend first. When off, the cursor is drawn directly into the @@ -781,6 +786,7 @@ mtl_setup_frame (struct frame *f) layer.device = g_device; layer.pixelFormat = MTLPixelFormatBGRA8Unorm; layer.framebufferOnly = YES; + layer.displaySyncEnabled = g_mtl_vsync_enabled; layer.frame = view.bounds; layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;