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 f01509d4f4
commit cdaa51a541
15 files changed
+180 -11

No files matched your search

+5
View File
@@ -345,10 +345,15 @@ static void paint(int full, int x, int y, int w, int h)
meow_ui_flush();
} else {
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++) {
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]);
}
meow_gfx_clip_pop();
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];
} 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;
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;
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;
if (opts) { fw = opts->width; fh = opts->height; }
+16 -10
View File
@@ -261,7 +261,17 @@ static void touch_release(void)
* 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP,
* 避免任意处左滑误退出)。纵向滑动→UP/DOWN 照旧。 */
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) {
push_ev(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, adx); /* 左缘返回手势 */
} else if (mdx > 0) {
@@ -271,14 +281,7 @@ static void touch_release(void)
}
} else {
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 {
push_ev(MEOW_EV_TAP, t_cx, t_cy);
@@ -569,7 +572,10 @@ void meow_ui_run(const meow_ui_events *ev)
if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */
/* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发
* 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 */
} else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) {
/* 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 调用)。 */
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_widget_draw(w) 画任何控件前 push 该控件矩形、画完 pop——任何控件(含 W_CUSTOM)
* 画不出自身边界,根治自定义画布越界脏区。可嵌套(入栈/出栈,push 与当前 clip 求交)。
+29
View File
@@ -143,6 +143,35 @@ void meow_log_install_exit_handler(void)
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=失败。 */
static int log_mkdir_p(const char *path)
{
BIN
View File
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;
}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.