Metal: gate animations, draw cursor in texture, fix glyph baseline
- Add g_mtl_animations_enabled (default off): the 60fps compositor overlay (cyan cursor, particles, CADisplayLink) is now opt-in via (mtl-animations t) / mtl-toggle-animations. With it off, compositeToScreen only blits + presents, removing the stray cyan cursor box that tinted the frame. - Draw the cursor directly into the static texture like the NS backend (get_phys_cursor_glyph + get_phys_cursor_geometry, FRAME_CURSOR_COLOR, filled/hollow/bar/hbar), and handle DRAW_CURSOR in mtl_draw_glyph_string so the glyph under a filled cursor stays readable (inverse). - Fix glyph vertical baseline: bearing_y stored the descent instead of the distance from the cell top to the baseline, dropping every glyph ~one ascent too low and overlapping the next line / mode line. Store bh-1-raster_oy.
This commit is contained in:
parent
9c112c768d
commit
9c7a7a2128
4 changed files with 172 additions and 29 deletions
28
lisp/mtl.el
28
lisp/mtl.el
|
|
@ -136,6 +136,19 @@ Lower values are snappier; higher values are more fluid."
|
|||
:set #'set-default
|
||||
:group 'mtl)
|
||||
|
||||
(defcustom mtl-animations-enabled nil
|
||||
"If non-nil, enable the Metal GPU animation layer.
|
||||
When nil (the default), the cursor is drawn directly into the static
|
||||
texture like the NS backend and no compositor overlay is drawn, which is
|
||||
the correct, flicker-free baseline. Enable this to turn on the animated
|
||||
cursor effects, particles and the 60fps compositor."
|
||||
:type 'boolean
|
||||
:set (lambda (sym val)
|
||||
(set-default sym val)
|
||||
(when (fboundp 'mtl-animations)
|
||||
(mtl-animations val)))
|
||||
:group 'mtl)
|
||||
|
||||
(defcustom mtl-enable-on-startup nil
|
||||
"If non-nil, enable Metal GPU rendering on the initial frame at startup."
|
||||
:type 'boolean
|
||||
|
|
@ -165,8 +178,15 @@ The NS backend still handles events, menus, and scrollbars."
|
|||
(mtl-scroll-effect (mtl--scroll-easing-number mtl-scroll-easing))
|
||||
(mtl-scroll-duration mtl-scroll-duration)
|
||||
(mtl-trail-length mtl-trail-length)
|
||||
(message "Metal GPU enabled on frame: %s (device: %s)"
|
||||
f (mtl-device-name))))
|
||||
(mtl-animations mtl-animations-enabled)
|
||||
(message "Metal GPU enabled on frame: %s (device: %s, animations: %s)"
|
||||
f (mtl-device-name) (if mtl-animations-enabled "on" "off"))))
|
||||
|
||||
(defun mtl-toggle-animations ()
|
||||
"Toggle the Metal GPU animation layer on or off."
|
||||
(interactive)
|
||||
(setopt mtl-animations-enabled (not mtl-animations-enabled))
|
||||
(message "Metal animations %s" (if mtl-animations-enabled "enabled" "disabled")))
|
||||
|
||||
(defun mtl-status ()
|
||||
"Display current Metal GPU backend status in the minibuffer."
|
||||
|
|
@ -176,8 +196,9 @@ The NS backend still handles events, menus, and scrollbars."
|
|||
(if (not (mtl-backend-p))
|
||||
(message "Metal not available on this system")
|
||||
(let ((status (mtl-animation-status)))
|
||||
(message "Metal GPU: %s | Cursor: %s | Scroll: %s (%.2fs)"
|
||||
(message "Metal GPU: %s | Animations: %s | Cursor: %s | Scroll: %s (%.2fs)"
|
||||
(mtl-device-name)
|
||||
(if (cdr (assq 'animations status)) "on" "off")
|
||||
(nth (cdr (assq 'cursor-mode status))
|
||||
'(block spring torpedo sonicboom ripple pixiedust hollow beam))
|
||||
(nth (cdr (assq 'scroll-easing status))
|
||||
|
|
@ -223,6 +244,7 @@ The NS backend still handles events, menus, and scrollbars."
|
|||
(define-key map (kbd "C-c m s") #'mtl-status)
|
||||
(define-key map (kbd "C-c m c") #'mtl-set-cursor)
|
||||
(define-key map (kbd "C-c m S") #'mtl-set-scroll)
|
||||
(define-key map (kbd "C-c m a") #'mtl-toggle-animations)
|
||||
map)
|
||||
"Keymap for `mtl-mode'.")
|
||||
|
||||
|
|
|
|||
35
src/mtlfns.m
35
src/mtlfns.m
|
|
@ -359,6 +359,37 @@ DEFUN ("mtl-trail-length", Fmtl_trail_length, Smtl_trail_length, 1, 1, 0,
|
|||
return len;
|
||||
}
|
||||
|
||||
DEFUN ("mtl-animations", Fmtl_animations, Smtl_animations, 0, 1, 0,
|
||||
doc: /* Enable or disable the Metal GPU animation layer.
|
||||
With ENABLE non-nil, turn on the animated cursor effects, particles and the
|
||||
CADisplayLink 60fps compositor overlay. With ENABLE nil (the default state),
|
||||
the cursor is drawn directly into the static texture like the NS backend and
|
||||
no overlay is composited, which is the correct, flicker-free baseline.
|
||||
Returns t when animations are enabled, nil otherwise. */)
|
||||
(Lisp_Object enable)
|
||||
{
|
||||
g_mtl_animations_enabled = !NILP (enable);
|
||||
|
||||
/* Start or stop the per-frame animators on live Metal frames so the change
|
||||
takes effect immediately rather than only on the next (mtl-enable). */
|
||||
Lisp_Object tail, frame;
|
||||
FOR_EACH_FRAME (tail, frame)
|
||||
{
|
||||
struct frame *f = XFRAME (frame);
|
||||
if (!FRAME_LIVE_P (f) || !FRAME_NS_P (f))
|
||||
continue;
|
||||
MtlFrameData *fd = mtl_get_frame_data (f);
|
||||
if (!fd || !fd.animator)
|
||||
continue;
|
||||
if (g_mtl_animations_enabled)
|
||||
[fd.animator startAnimating];
|
||||
else
|
||||
[fd.animator stopAnimating];
|
||||
}
|
||||
|
||||
return g_mtl_animations_enabled ? Qt : Qnil;
|
||||
}
|
||||
|
||||
DEFUN ("mtl-draw-stats", Fmtl_draw_stats, Smtl_draw_stats, 0, 0, 0,
|
||||
doc: /* Return diagnostic counters for Metal glyph rendering.
|
||||
Returns an alist with: total-calls, no-fd (no encoder), no-font, glyphs-drawn. */)
|
||||
|
|
@ -376,7 +407,8 @@ DEFUN ("mtl-animation-status", Fmtl_animation_status, Smtl_animation_status,
|
|||
doc: /* Return an alist with current Metal animation configuration. */)
|
||||
(void)
|
||||
{
|
||||
return list4 (
|
||||
return list5 (
|
||||
Fcons (intern ("animations"), g_mtl_animations_enabled ? Qt : Qnil),
|
||||
Fcons (intern ("cursor-mode"), make_fixnum ((EMACS_INT)g_mtl_cursor_mode)),
|
||||
Fcons (intern ("scroll-easing"), make_fixnum ((EMACS_INT)g_mtl_scroll_easing)),
|
||||
Fcons (intern ("scroll-duration"),make_float (g_mtl_scroll_duration)),
|
||||
|
|
@ -403,6 +435,7 @@ syms_of_mtlfns (void)
|
|||
defsubr (&Smtl_scroll_duration);
|
||||
defsubr (&Smtl_trail_length);
|
||||
defsubr (&Smtl_animation_status);
|
||||
defsubr (&Smtl_animations);
|
||||
defsubr (&Smtl_capture_frame);
|
||||
defsubr (&Smtl_draw_stats);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@ extern MtlCursorMode g_mtl_cursor_mode;
|
|||
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 (Fase A) */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Global Metal display list
|
||||
|
|
|
|||
137
src/mtlterm.m
137
src/mtlterm.m
|
|
@ -246,6 +246,13 @@ MtlScrollEasing g_mtl_scroll_easing = MTL_EASE_OUT_QUAD;
|
|||
float g_mtl_scroll_duration = 0.15f;
|
||||
NSUInteger g_mtl_trail_len = 20;
|
||||
|
||||
/* 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
|
||||
static texture (like NS) and no compositor overlay is drawn. See TODO.org
|
||||
Fase A. Toggle from Lisp with (mtl-animations t). */
|
||||
BOOL g_mtl_animations_enabled = NO;
|
||||
|
||||
/* Phase 4: additional global pipeline state */
|
||||
static id<MTLRenderPipelineState> g_blit_pipeline = nil;
|
||||
static id<MTLRenderPipelineState> g_particle_pipeline = nil;
|
||||
|
|
@ -637,12 +644,20 @@ mtl_rasterize_glyph_id (CTFontRef font, CGGlyph cgGlyph, uint64_t key)
|
|||
entry = glyph_cache_insert (key);
|
||||
if (!entry) return NULL;
|
||||
|
||||
/* The raster placed the baseline at raster_oy measured from the BOTTOM of the
|
||||
bh-tall cell (Core Graphics draws y-up). All draw sites expect bearing_y to
|
||||
be the distance from the cell's TOP edge down to the baseline, so that
|
||||
y0 = baseline - bearing_y lands the cell top correctly. That distance is
|
||||
(bh - 1 - raster_oy). The previous code stored raster_oy itself (the
|
||||
descent), which dropped every glyph ~one ascent too low. */
|
||||
int raster_oy = (int)(floor (-bbox.origin.y) + 1);
|
||||
|
||||
entry->atlas_x = g_atlas_next_x;
|
||||
entry->atlas_y = g_atlas_next_y;
|
||||
entry->width = bw;
|
||||
entry->height = bh;
|
||||
entry->bearing_x = (int)(floor (-bbox.origin.x) + 1);
|
||||
entry->bearing_y = (int)(floor (-bbox.origin.y) + 1);
|
||||
entry->bearing_y = bh - 1 - raster_oy;
|
||||
entry->advance_x = (float)adv.width;
|
||||
|
||||
g_atlas_next_x += bw + 1;
|
||||
|
|
@ -753,10 +768,12 @@ mtl_setup_frame (struct frame *f)
|
|||
fd.uniformBuffer = ubuf;
|
||||
fd.emacsFrame = f;
|
||||
|
||||
/* Phase 4: create animator and start 60fps animation loop */
|
||||
/* Phase 4: create animator. Only start the 60fps loop when the animation
|
||||
layer is explicitly enabled (Fase A: correctness first, animation opt-in). */
|
||||
MtlAnimator *anim = [[MtlAnimator alloc] initWithFrame:f];
|
||||
fd.animator = anim;
|
||||
[anim startAnimating];
|
||||
if (g_mtl_animations_enabled)
|
||||
[anim startAnimating];
|
||||
|
||||
objc_setAssociatedObject (view, &mtl_frame_key,
|
||||
fd, OBJC_ASSOCIATION_RETAIN);
|
||||
|
|
@ -1121,7 +1138,11 @@ easing_apply (MtlScrollEasing mode, float t)
|
|||
[enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
||||
}
|
||||
|
||||
if (anim)
|
||||
/* Animation overlay (cursor effects, trail, particles) is opt-in. When off,
|
||||
the cursor lives in the static texture (drawn by mtl_draw_window_cursor),
|
||||
so the compositor only blits and presents. This is what kills the stray
|
||||
cyan cursor box drawn on top of text. */
|
||||
if (anim && g_mtl_animations_enabled)
|
||||
{
|
||||
float cx = anim.cursorMode == MTL_CURSOR_SPRING ? anim.springX.pos : anim.curTargetX;
|
||||
float cy = anim.cursorMode == MTL_CURSOR_SPRING ? anim.springY.pos : anim.curTargetY;
|
||||
|
|
@ -1634,6 +1655,16 @@ mtl_draw_glyph_string (struct glyph_string *s)
|
|||
unsigned long fg = face ? face->foreground : 0xECEFF4;
|
||||
unsigned long bg = face ? face->background : 0x2E3440;
|
||||
|
||||
/* When this string is drawn as the cursor (via draw_phys_cursor_glyph),
|
||||
invert: fill the background with the cursor color and draw the glyph in
|
||||
the face's background color so the character stays readable. Mirrors the
|
||||
NS backend (FRAME_CURSOR_COLOR + FRAME_BACKGROUND for text). */
|
||||
if (s->hl == DRAW_CURSOR)
|
||||
{
|
||||
bg = ns_color_to_pixel (FRAME_CURSOR_COLOR (f));
|
||||
fg = face ? face->background : 0x2E3440;
|
||||
}
|
||||
|
||||
/* Background fill */
|
||||
if (!s->background_filled_p)
|
||||
{
|
||||
|
|
@ -1821,36 +1852,92 @@ mtl_draw_window_cursor (struct window *w,
|
|||
enum text_cursor_kinds cursor_type,
|
||||
int cursor_width, bool on_p, bool active_p)
|
||||
{
|
||||
(void)row; (void)active_p;
|
||||
if (!on_p) return;
|
||||
(void)x; (void)y; (void)active_p;
|
||||
|
||||
struct frame *f = WINDOW_XFRAME (w);
|
||||
MtlFrameData *fd = mtl_get_frame_data (f);
|
||||
if (!fd) return;
|
||||
if (!on_p) return;
|
||||
|
||||
int ch = FRAME_COLUMN_WIDTH (f);
|
||||
int lh = FRAME_LINE_HEIGHT (f);
|
||||
int cw = cursor_width > 0 ? cursor_width : ch;
|
||||
w->phys_cursor_type = cursor_type;
|
||||
w->phys_cursor_on_p = on_p;
|
||||
|
||||
/* For static cursor modes, draw directly into the static texture */
|
||||
if (cursor_type == BAR_CURSOR || cursor_type == HBAR_CURSOR
|
||||
|| fd.animator.cursorMode == MTL_CURSOR_BLOCK)
|
||||
if (cursor_type == NO_CURSOR)
|
||||
{
|
||||
unsigned long color = ns_color_to_pixel(FRAME_FOREGROUND_COLOR(f));
|
||||
NSRect r;
|
||||
switch (cursor_type)
|
||||
{
|
||||
case BAR_CURSOR: r = NSMakeRect(x, y, cw>2?2:cw, lh); break;
|
||||
case HBAR_CURSOR: r = NSMakeRect(x, y+lh-2, ch, 2); break;
|
||||
default: r = NSMakeRect(x, y, ch, lh); break;
|
||||
}
|
||||
[fd fillRect:r color:color];
|
||||
w->phys_cursor_width = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* For animated cursor modes, just update the animator target.
|
||||
The animator composites the cursor in compositeToScreen at 60fps. */
|
||||
if (fd.animator)
|
||||
[fd.animator setCursorX:x y:y width:ch height:lh];
|
||||
/* Resolve the glyph and geometry exactly like the NS backend so the cursor
|
||||
box lines up with the character cell. See ns_draw_window_cursor. */
|
||||
struct glyph *phys_cursor_glyph = get_phys_cursor_glyph (w);
|
||||
if (phys_cursor_glyph == NULL)
|
||||
{
|
||||
if (row->exact_window_width_line_p
|
||||
&& w->phys_cursor.hpos >= row->used[TEXT_AREA])
|
||||
{
|
||||
row->cursor_in_fringe_p = 1;
|
||||
draw_fringe_bitmap (w, row, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int fx, fy, h, cursor_height;
|
||||
get_phys_cursor_geometry (w, row, phys_cursor_glyph, &fx, &fy, &h);
|
||||
|
||||
if (cursor_type == BAR_CURSOR)
|
||||
{
|
||||
struct glyph *cursor_glyph;
|
||||
if (cursor_width < 1)
|
||||
cursor_width = max (FRAME_CURSOR_WIDTH (f), 1);
|
||||
if (cursor_width < w->phys_cursor_width)
|
||||
w->phys_cursor_width = cursor_width;
|
||||
/* For R2L glyphs draw the bar on the right edge. */
|
||||
cursor_glyph = get_phys_cursor_glyph (w);
|
||||
if ((cursor_glyph->resolved_level & 1) != 0)
|
||||
fx += cursor_glyph->pixel_width - w->phys_cursor_width;
|
||||
}
|
||||
else if (cursor_type == HBAR_CURSOR)
|
||||
{
|
||||
cursor_height = (cursor_width < 1) ? lrint (0.25 * h) : cursor_width;
|
||||
if (cursor_height > row->height)
|
||||
cursor_height = row->height;
|
||||
if (h > cursor_height)
|
||||
fy += h - cursor_height;
|
||||
h = cursor_height;
|
||||
}
|
||||
|
||||
unsigned long cc = ns_color_to_pixel (FRAME_CURSOR_COLOR (f));
|
||||
int cwidth = w->phys_cursor_width;
|
||||
|
||||
switch (cursor_type)
|
||||
{
|
||||
case DEFAULT_CURSOR:
|
||||
case NO_CURSOR:
|
||||
break;
|
||||
case FILLED_BOX_CURSOR:
|
||||
/* Re-draw the glyph with DRAW_CURSOR highlight: fills the cell with the
|
||||
cursor color and draws the character in the background color, keeping
|
||||
it readable (mtl_draw_glyph_string handles DRAW_CURSOR). */
|
||||
draw_phys_cursor_glyph (w, row, DRAW_CURSOR);
|
||||
break;
|
||||
case HOLLOW_BOX_CURSOR:
|
||||
/* Outline only: four 1px edges. */
|
||||
[fd fillRect:NSMakeRect (fx, fy, cwidth, 1) color:cc];
|
||||
[fd fillRect:NSMakeRect (fx, fy + h - 1, cwidth, 1) color:cc];
|
||||
[fd fillRect:NSMakeRect (fx, fy, 1, h) color:cc];
|
||||
[fd fillRect:NSMakeRect (fx + cwidth - 1, fy, 1, h) color:cc];
|
||||
break;
|
||||
case HBAR_CURSOR:
|
||||
case BAR_CURSOR:
|
||||
[fd fillRect:NSMakeRect (fx, fy, cwidth, h) color:cc];
|
||||
break;
|
||||
}
|
||||
|
||||
/* Keep the animator target in sync for when the animation layer is enabled.
|
||||
With animations off this has no visible effect. */
|
||||
if (g_mtl_animations_enabled && fd.animator)
|
||||
[fd.animator setCursorX:fx y:fy width:cwidth height:h];
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue