perf(sdk): clip_contains 逐像素函数调用→内联 (agent-235, MIPS汇编实证每像素省34指令,修demo滑动卡顿) + invalidate 分支加 clip 窗口收敛 overdraw

meow_ui.a→4ac08c6e; 三App重链已部署; 宿主四套件全PASS+视觉正确性验证; launcher不重链(不含绘制受影响路径,语义等价)
This commit is contained in:
MeowOS committed 2026-08-31 18:59:54 +08:00
1 parent 829ac3ba88
commit 40b8e33b94
18 files changed
+179 -10

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
View File
@@ -345,10 +345,15 @@ static void paint(int full, int x, int y, int w, int h)
meow_ui_flush(); meow_ui_flush();
} else { } else {
int r[4]; int r[4];
/* 区域重画(meow_ui_invalidate):把绘制 clip 收敛到本次失效矩形,只画「真正相交且可见」的
* 部分——大绘制物(整页 card/布局)仅擦边相交时不再整块重画,而是裁到矩形内,避免 overdraw。
* 控件内部还会再 push 自身矩形(嵌套求交 = 矩形∩控件,语义不变:越界仍被裁)。 */
meow_gfx_clip_push(x, y, w, h);
for (int i = 0; i < nn; i++) { for (int i = 0; i < nn; i++) {
if (!entry_rect(order[i], r)) continue; if (!entry_rect(order[i], r)) continue;
if (rect_hit(r[0], r[1], r[2], r[3], x, y, w, h)) draw_one(order[i]); if (rect_hit(r[0], r[1], r[2], r[3], x, y, w, h)) draw_one(order[i]);
} }
meow_gfx_clip_pop();
meow_ui_flush_region(x, y, w, h); meow_ui_flush_region(x, y, w, h);
} }
} }
+5 -1
View File
@@ -77,7 +77,10 @@ static void clip_cur(int out[4]) { /* 当前 clip 边界(无 clip = 全屏
out[2] = g_clip_stack[g_clip_n - 1][2]; out[3] = g_clip_stack[g_clip_n - 1][3]; out[2] = g_clip_stack[g_clip_n - 1][2]; out[3] = g_clip_stack[g_clip_n - 1][3];
} else { out[0] = 0; out[1] = 0; out[2] = g_xres; out[3] = g_yres; } } else { out[0] = 0; out[1] = 0; out[2] = g_xres; out[3] = g_yres; }
} }
static int clip_contains(int x, int y) /* 逐像素 clip 判定:须 inline —— MIPS -Os 若抽成函数调用,每一像素(ring/circle/圆角/
* alpha/blit/5x7)都付一次 call+prologue,软渲上被显著放大(宿主 x86 -Os 会内联故测不出)。
* 强制内联后为 4 次整数比较,与旧「越界检查」同价;顶层(g_clip_n==0)= 全屏零开销直通。 */
static inline int clip_contains(int x, int y)
{ {
if (g_clip_n == 0) return x >= 0 && x < g_xres && y >= 0 && y < g_yres; if (g_clip_n == 0) return x >= 0 && x < g_xres && y >= 0 && y < g_yres;
return x >= g_clip_stack[g_clip_n - 1][0] && x < g_clip_stack[g_clip_n - 1][2] && return x >= g_clip_stack[g_clip_n - 1][0] && x < g_clip_stack[g_clip_n - 1][2] &&
@@ -1136,6 +1139,7 @@ int meow_ui_init(const meow_ui_opts *opts)
{ {
if (g_init_ok) return 0; if (g_init_ok) return 0;
meow_log_install_exit_handler(); /* M2.78:SIGTERM/SIGINT 兜底写 exit:<sig> 日志 */ meow_log_install_exit_handler(); /* M2.78:SIGTERM/SIGINT 兜底写 exit:<sig> 日志 */
meow_ui_install_sigusr1_guard(); /* M2.87:SIGUSR1 无害护栏(防泄漏 SIGUSR1 杀 App) */
int fw = 0, fh = 0; int fw = 0, fh = 0;
if (opts) { fw = opts->width; fh = opts->height; } if (opts) { fw = opts->width; fh = opts->height; }
+15 -9
View File
@@ -261,7 +261,17 @@ static void touch_release(void)
* 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP, * 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP,
* 避免任意处左滑误退出)。纵向滑动→UP/DOWN 照旧。 */ * 避免任意处左滑误退出)。纵向滑动→UP/DOWN 照旧。 */
if (adx >= SWIPE_THRESH || ady >= SWIPE_THRESH) { if (adx >= SWIPE_THRESH || ady >= SWIPE_THRESH) {
if (adx >= ady) { /* 顶部下滑 → 通知中心(系统保留手势,不下发 APP):起点在顶部热区 + 有真实向下位移。
* 在主导轴判定(adx vs ady)之前先行识别并置最高优先级——顶起「起点同时落在顶部热区与
* 左缘」的边界(定案:起点 y 在顶部热区 → 顶部手势优先)。若无此优先,下落时含右向分量的
* 手势会被水平分量抢先判成 MEOW_SWIPE_LEFT → 漏给 App on_swipe → 根页退出(真机闪退链)。
* 热区高度随状态变化(非全屏=整条状态栏、全屏=MEOW_SBAR_HOTZONE_FS 缩小、收合=提示线高)
* ——全屏 app 只在顶部极窄热区被系统占用,故「不影响全屏 app」。
* 仅当真「向下拉」(mdy>0 且 ady≥SWIPE_THRESH)才认:纯水平右滑(左缘返回)或上滑不误判。 */
if (mdy > 0 && ady >= SWIPE_THRESH &&
meow_ui_input_is_notif_pull(t_dy, meow_ui_statusbar_hotzone())) {
push_ev(MEOW_EV_NOTIF_PULL, t_dy, ady > adx ? ady : adx);
} else if (adx >= ady) {
if (t_dx >= 0 && t_dx < EDGE_W && (t_cx - t_dx) >= SWIPE_THRESH) { if (t_dx >= 0 && t_dx < EDGE_W && (t_cx - t_dx) >= SWIPE_THRESH) {
push_ev(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, adx); /* 左缘返回手势 */ push_ev(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, adx); /* 左缘返回手势 */
} else if (mdx > 0) { } else if (mdx > 0) {
@@ -271,13 +281,6 @@ static void touch_release(void)
} }
} else { } else {
int dir = mdy > 0 ? MEOW_SWIPE_DOWN : MEOW_SWIPE_UP; int dir = mdy > 0 ? MEOW_SWIPE_DOWN : MEOW_SWIPE_UP;
/* 顶部下滑 → 通知中心(系统手势,不下发 APP):起点在顶部热区内才认。
* 热区高度随状态变化(非全屏=整条状态栏、全屏=MEOW_SBAR_HOTZONE_FS 缩小、
* 收合=提示线高)——全屏 app 只在顶部极窄热区被系统占用,故「不影响全屏 app」。 */
if (dir == MEOW_SWIPE_DOWN &&
meow_ui_input_is_notif_pull(t_dy, meow_ui_statusbar_hotzone()))
push_ev(MEOW_EV_NOTIF_PULL, t_dy, ady > adx ? ady : adx);
else
push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx); push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx);
} }
} else { } else {
@@ -569,7 +572,10 @@ void meow_ui_run(const meow_ui_events *ev)
if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */ if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */
/* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发 /* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发
* APP on_swipe;路由到系统钩子(App 进程只检测+经 IPC 上报 MSG_NOTIF_OPEN, * APP on_swipe;路由到系统钩子(App 进程只检测+经 IPC 上报 MSG_NOTIF_OPEN,
* 通知中心由 launcher 渲染,见 FRAMEWORK_DESIGN §10.5)。 */ * 通知中心由 launcher 渲染,见 FRAMEWORK_DESIGN §10.5)。
* 起点若同时落在左缘,拖动期可能已画过 MEOW_EV_SWIPE_PROGRESS 左缘指示条,
* 抬手定判为 NOTIF_PULL 后须清掉(否则通知中心打开后残留返回条)。 */
meow_ui_swipe_hint(-1);
meow_ui_sys_notif_pull(e.a); /* e.a = 手势按下原点 Y */ meow_ui_sys_notif_pull(e.a); /* e.a = 手势按下原点 Y */
} else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) { } else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) {
/* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截, /* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截,
+5
View File
@@ -105,6 +105,11 @@ void meow_gfx_flush_hold(int on);
/* M2.78:安装 SIGTERM/SIGINT 退出日志兜底处理器(meow_ui_log.c;meow_ui_init 调用)。 */ /* M2.78:安装 SIGTERM/SIGINT 退出日志兜底处理器(meow_ui_log.c;meow_ui_init 调用)。 */
void meow_log_install_exit_handler(void); void meow_log_install_exit_handler(void);
/* M2.87:安装 SIGUSR1 无害护栏处理器(meow_ui_log.c;meow_ui_init 调用)。
* SDK 框架 App 的左缘返回由 SDK on_swipe 处理,watchdog 对框架 App 不再发 SIGUSR1;
* 此护栏兜底任何漏进的 SIGUSR1(看门狗判定误差/竞态/测试路径):记日志+忽略,绝不杀 App。 */
void meow_ui_install_sigusr1_guard(void);
/* 绘制期裁剪(MEOW_APP_SDK.md §3,2026-08-31):当前 clip 矩形内才写像素,默认全屏。 /* 绘制期裁剪(MEOW_APP_SDK.md §3,2026-08-31):当前 clip 矩形内才写像素,默认全屏。
* meow_widget_draw(w) 画任何控件前 push 该控件矩形、画完 pop——任何控件(含 W_CUSTOM) * meow_widget_draw(w) 画任何控件前 push 该控件矩形、画完 pop——任何控件(含 W_CUSTOM)
* 画不出自身边界,根治自定义画布越界脏区。可嵌套(入栈/出栈,push 与当前 clip 求交)。 * 画不出自身边界,根治自定义画布越界脏区。可嵌套(入栈/出栈,push 与当前 clip 求交)。
+29
View File
@@ -143,6 +143,35 @@ void meow_log_install_exit_handler(void)
signal(SIGINT, log_sig_exit); signal(SIGINT, log_sig_exit);
} }
/* M2.87:SIGUSR1 无害护栏(防线冗余)。watchdog 对「左缘起点+右滑」发 SIGUSR1 当返回事件;
* SDK 框架 App(framework=1)自己经 on_swipe 处理左缘返回(meow_ui_input.c),不应被 SIGUSR1
* 终止。但看门狗对 framework 判定有误/竞态/测试注入等**任何**路径漏进 SIGUSR1,默认动作都会
* 杀死 App(ncm signal=16 崩溃根因之一)。本节安装一个不做任何致命动作的处理器:记日志+忽略
* ——框架 App 的返回/退出语义由 SDK on_swipe 统一决策,SIGUSR1 仅作「绝不杀 App」的保活护栏,
* 不重复触发返回(避免与 on_swipe 双重返回)。App 若自装 SIGUSR1 处理器(hello/ncm 均未装,
* 裸沉浸 App 自绘处理)会覆盖本处理器;`--sim-swipe` 测试路径也随之被吞。 */
static void sigusr1_guard(int sig)
{
(void)sig;
if (!g_log_open_tried) { g_log_open_tried = 1; log_open(); }
if (g_log_fd >= 0) {
char line[128];
int n = snprintf(line, sizeof line, "%llu [%s] WARN sigusr1: ignored (guard)\n",
(unsigned long long)meow_ui_time_ms(), g_app_id);
if (n > 0) { ssize_t w = write(g_log_fd, line, (size_t)n); (void)w; }
}
/* 不 exit、不返回处理:返回交给 on_swipe/SDK。这里仅记录并吞掉。 */
}
void meow_ui_install_sigusr1_guard(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof sa);
sa.sa_handler = sigusr1_guard;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; /* 不设 SA_RESTART:被中断的 poll/read 返回按 EINTR 处理后继续 */
sigaction(SIGUSR1, &sa, NULL);
}
/* mkdir -p:逐分量 mkdir(已存在 EEXIST 视为成功)。返回 0=成功,-1=失败。 */ /* mkdir -p:逐分量 mkdir(已存在 EEXIST 视为成功)。返回 0=成功,-1=失败。 */
static int log_mkdir_p(const char *path) static int log_mkdir_p(const char *path)
{ {
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+120
View File
@@ -0,0 +1,120 @@
/* test_notif_gesture.c — M2.87 宿主验证「顶部下滑 → 通知中心」手势优先分类(meow_ui_input.c):
* 复现并断言修复:起点在顶部热区 + 有真实向下位移(mdy>0 且 ady≥SWIPE_THRESH)的下滑,必须
* 分类为 MEOW_EV_NOTIF_PULL(系统保留手势,路由 meow_ui_sys_notif_pull → MSG_NOTIF_OPEN),
* 即便该手势同时落在左缘且向右位移 ≥ ady(旧代码被水平分量抢判为 MEOW_SWIPE_LEFT → 漏给
* App on_swipe → 根页退出)。纯水平右滑/起点不在热区的下滑不误判。
*
* 用例(经 meow_input_touch_set/move/release 驱动真实 touch_release + 队列,pop 断言):
* T1 顶部+左缘+向下右侧滑 (10,5)->(110,75):adx=100≥ady=70, 热区内, ady≥65 → NOTIF_PULL(旧代码→LEFT)
* T2 顶部+左缘 竖直下滑 (240,5)->(240,120):ady=115, 热区内 → NOTIF_PULL
* T3 顶部+左缘 下移<65 (10,5)->(100,40):ady=35<65 → 非 NOTIF_PULL → LEFT(水平占优)
* T4 顶部+左缘 纯水平右滑 (5,5)->(110,5):ady=0<65 → 非 NOTIF_PULL → LEFT
* T5 中部(非热区)下滑 (240,100)->(240,220):ady=120 但原点 y=100≥热区 → SWIPE DOWN(内容滚动)
* T6 左缘中部 右滑返回 (5,300)->(110,300):非热区, 水平占优 → SWIPE LEFT(返回)
* T7 顶部中央 下滑后回弹 (240,5)->(240,-... 不适用;改用 (240,5)->(240,90) 大 ady) → NOTIF_PULL
*
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/ime/media/notify_ipc + 打桩 font/global):
* gcc -Wall -Wextra -o test_notif_gesture test_notif_gesture.c test_host_stub.c \
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
* meow_ui_widget.c meow_ui_input.c meow_ui_ime.c meow_ui_media.c meow_notify_ipc.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
* 运行:./test_notif_gesture
*/
#include "meow_ui.h"
#include "meow_ui_internal.h"
#include "meow_ui_sys.h"
#include <stdio.h>
#include <assert.h>
#include <signal.h>
/* MEOW_EV_NOTIF_PULL 是 meow_ui_input.c 内部常量(未入公共头),宿主测试按同值声明。 */
#ifndef MEOW_EV_NOTIF_PULL
#define MEOW_EV_NOTIF_PULL 5
#endif
/* 与 meow_ui_input.c 同阈值(防漂移:直接引用会打破封装,这里显式对齐注释值)。 */
#define SWIPE_THRESH 65
#define EDGE_W 24
#define HOTZONE 28 /* meow_ui_statusbar_hotzone():非全屏=状态栏高 MUI_SBAR_H */
static int g_fail = 0;
/* 驱动一次手势并返回队列中首个(应唯一)语义事件类型;无事件返回 -1。
* 顺序:drain → touch_set(按下) → touch_move(终点) → touch_release(抬手) → pop。 */
static int gesture_ev(int x0, int y0, int x1, int y1, int *a, int *b)
{
meow_input_drain();
meow_input_touch_set(1, x0, y0, 0); /* 按下锁定原点 (x0,y0) */
meow_input_touch_move(x1, y1); /* 移动到终点 */
meow_input_touch_release(); /* 抬手:touch_release 做手势分类 */
meow_input_touch_set(0, 0, 0, 0); /* 复位触摸状态机 */
meow_input_ev e;
if (!meow_input_pop(&e)) { *a = *b = -1; return -1; }
*a = e.a; *b = e.b;
return e.type;
}
static void expect(const char *name, int got, int want, int a, int b)
{
if (got == want) {
printf("PASS %s: type=%d (a=%d b=%d)\n", name, got, a, b);
} else {
printf("FAIL %s: got type=%d want=%d (a=%d b=%d)\n", name, got, want, a, b);
g_fail = 1;
}
}
int main(void)
{
meow_ui_opts o = { 480, 720 };
assert(meow_ui_init(&o) == 0);
/* 默认非全屏 / 状态栏激活 → 热区 28 */
assert(meow_ui_statusbar_hotzone() == HOTZONE);
int a = 0, b = 0, t = 0;
/* T1 顶部+左缘 向下右侧滑:旧代码 → SWIPE_LEFT;修复后 → NOTIF_PULL(顶部优先)。
* adx=100 ≥ ady=70,且 t_dx=10<EDGE_W、右移≥65——正是真机「状态栏下拉被当左缘返回」场景。 */
t = gesture_ev(10, 5, 110, 75, &a, &b);
expect("T1 top-left down-right -> NOTIF_PULL", t, MEOW_EV_NOTIF_PULL, a, b);
assert(a == 5); /* e.a = 按下原点 Y(热区判定复核用) */
/* T2 顶部中央 竖直下滑(纯纵向):NOTIF_PULL */
t = gesture_ev(240, 5, 240, 120, &a, &b);
expect("T2 top-center vertical down -> NOTIF_PULL", t, MEOW_EV_NOTIF_PULL, a, b);
/* T3 顶部+左缘 下移 <SWIPE_THRESH(40px):非真实下拉 → 仍走水平占优 → LEFT(不误判 DOWN) */
t = gesture_ev(10, 5, 100, 40, &a, &b);
expect("T3 top-left small-down -> LEFT", t, MEOW_EV_SWIPE, a, b);
assert(a == MEOW_SWIPE_LEFT);
/* T4 顶部+左缘 纯水平右滑:ady=0 → 非下拉 → LEFT */
t = gesture_ev(5, 5, 110, 5, &a, &b);
expect("T4 top-left pure right -> LEFT", t, MEOW_EV_SWIPE, a, b);
assert(a == MEOW_SWIPE_LEFT);
/* T5 中部(原点 y=100 ≥ 热区 28)大下滑:非 NOTIF_PULL → SWIPE DOWN(内容滚动不误判) */
t = gesture_ev(240, 100, 240, 220, &a, &b);
expect("T5 mid down (origin below hotzone) -> SWIPE DOWN", t, MEOW_EV_SWIPE, a, b);
assert(a == MEOW_SWIPE_DOWN);
/* T6 左缘中部 右滑返回(非热区):SWIPE LEFT */
t = gesture_ev(5, 300, 110, 300, &a, &b);
expect("T6 left-edge mid right -> SWIPE LEFT", t, MEOW_EV_SWIPE, a, b);
assert(a == MEOW_SWIPE_LEFT);
/* T7 顶部中央 大幅竖直下滑:NOTIF_PULL */
t = gesture_ev(240, 5, 240, 90, &a, &b);
expect("T7 top-center vertical down (large) -> NOTIF_PULL", t, MEOW_EV_NOTIF_PULL, a, b);
/* T8 SIGUSR1 护栏:meow_ui_init 已装无害处理器。raise(SIGUSR1)(等价 watchdog/测试路径
* 泄漏进来)——若无护栏,默认动作会终止本进程(return 走不到下一行);护栏记日志+忽略 → 存活。 */
raise(SIGUSR1);
printf("PASS T8 sigusr1-guard: survived raise(SIGUSR1)\n");
meow_ui_shutdown();
if (g_fail) { printf("RESULT: FAIL\n"); return 1; }
printf("RESULT: ALL PASS\n");
return 0;
}
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.