Files
MobileGL/docs/CS_Refactor/FCL_Integration_Checklist.md
T
BZLZHH 4e48702f26 [Feat] (MG_Remote): a record can tell the server its answer will never be read - the bit works and cuts a frame's replies by 92%, and it proves the window's real blocker is the FIFO drain, not the pool depth
The create window could not collect its deferred answers. The previous commit
diagnosed that as the reply pool being 8 slots deep against 55,903 replies in one
frame, and left a no-reply bit as the way out. This lands that bit, measures it on
the device, and reports that the diagnosis was half wrong.

WHAT LANDED. ClientSession::EmitAndWaitTails takes a second question beside
wantReply: willReadReply. They are not the same question and the window is the case
that separates them - it emits with wantReply=false and READS the answer later, so
its records must still be answered. kRecNoReply (RingRecordFlags bit 5, which
aliases kOptional in the call-flag space; the flag-space asserts were extended and
the alias named) is stamped when willReadReply is false, and the decoder skips
PostReply while keeping every acceptance counter - "nobody reads the answer" is not
"nothing happened". The two illegal combinations are Fatal by name.

Three call sites say it: SetTextureParams for a confirmed object, the respecify
row's fire-and-forget half, and sub-data where MGPipeSubDataWantsItsReply decides.

MEASURED ON THE DEVICE (aikjs4s8cys8ba7l, rebuilt APK + client, both halves new):
    server:  skipped=50,001   answered=4,521        (a frame's replies: 55,903 -> 4,543)
    client:  Reply frames received seen=4,501, latest seq=97,181
    frame-sent: identical per op, records untouched
    create row: by_op 2 = 9,070 (was 4,536)  - because the drain's blocking take is
               now counted, so the row's number stays honest instead of reading as
               "create stopped waiting" while the waiting merely moved
AND THE WINDOW STILL READ BACK NOTHING: readfail 8,951 -> 8,872, i.e. -0.9%.

THE REAL CAUSE, from the miss probe:
    P12miss n=1     want=310 replySeq=1504   ring=[1175..1504]
    P12miss n=9001  want=310 replySeq=102915 ring=[102866..102915]
want=310 every time. The drain is STRICTLY FIFO and a failed read has no way out -
it retries the same head for ever. Any reply buffer is bounded by design (8 pool
slots, or the 8-frame ring the stream link now keeps), so with 4,536 deferred
answers at least one is overwritten before it is read, and that one wedges the
window permanently. Cutting the churn by two orders of magnitude does not help: it
turns a certainty into a certainty. That is the constraint, not the depth.

THE FIX IS NAMED AND NOT TAKEN (the goal this work ran under stops here on purpose).
On a failed read of the head, abandon it: MGPipeNoteHandleUnpublished +
NoteCreateSuspect + pop - the DECLINE path reused for "unknown". The window then
self-heals through the blocking path and the object merely gets slower. It needs its
own gate: a failed read must let the window ADVANCE.

Gates, all green (172 + 13 + 22 + 103):
  PipeWireCodecTest.TheNoReplyBitSkipsTheAnswerAndKeepsTheAcceptanceRecord - the skip,
    the kept tally, the answered/skipped split, and the negative control that a record
    WITHOUT the bit is still answered (a server that stopped answering everything
    would otherwise pass)
  RemoteClientControls.ACreateWindowDefersTheAnswersAndTheWindowOffDoesNot - now also
    asserts the answers CAME BACK (answers_taken=11, outstanding=1, fallbacks=0 against
    12 records). RED-ONCE PROVED: giving the window's creates the literal
    "fire-and-forget => skip" predicate makes this case fail.
  RingTest - the sixth and last flag alias, and the hand-written exhaustiveness list
    extended (with a note that it is only as exhaustive as that list).
  Server side: AnsweredRecords()/SkippedAnswers() - the number that made this
    debuggable, since a wire change whose effect is invisible is undebuggable.

Client-first is safe: an old server ignores bit 5 and replies as before, so the two
cross-version directions degrade to today's behaviour - this is "both ends must
upgrade to take EFFECT", not "to avoid breaking". Hit that in practice first.
2026-09-26 02:00:44 +08:00

4.0 KiB
Raw Blame History

FCL Integration Checklist (MobileGL C/S, FCL 0 改动)

状态:代码完成态(phase 2,禁用 shell,未编译/未真机)。 目标读者:MobileGL 核心开发者 + 验证者。本文件把 FCL 启动全链路与 MobileGL 组件逐点划上钩,并记录已知不支持面(以 generated_trampoline_coverage.txt 为准)。

1. 启动链路逐点映射

# FCL 行为(来源) MobileGL 组件 状态
1 安装渲染器插件 APK(top.mobilegl.plugin) android-plugin/app(:MobileGL 产 jniLibs) ✅ 配置完成
2 校验 renderer / pojavEnv(PluginNativeLoadGuard.verifyRendererLibraries) manifest: MobileGL C/S:libMobileGL_Client.so:/libMobileGL_Client.so + DLOPEN=… ✅
3 LD_LIBRARY_PATH 指向插件 native 目录 无需客户端特殊处理 ✅
4 DLOPEN 依序加载 FullServer/UtilRuntime/BackendObject mobilegl_fullserver_create 再用同路径 dlopen(幂等) ✅
5 dlopen glLib = libMobileGL_Client.so ClientTrampoline 首次调用 → InitializeFromEnvironment() ✅
6 -Dorg.lwjgl.opengl.libname=… LWJGL 加载 Client .so,经导出 egl*/gl* ✅(受支持子集)
7 游戏线程调 eglGetDisplay/Initialize/CreateContext/MakeCurrent M1 EGL trampoline(DisplayCreate/GroupCreate/SessionCreate + SetCurrentSession) ✅
8 游戏调 GL 命令 generated_wire_trampoline.cpp(Wire::SendGl* → FullServer → DirectGLES) ✅(受支持子集)
9 对象创建(glGenTextures 等) ret_bytes 通道(服务端分配 + 客户端拷贝) ✅
10 数据上传(glBufferData 等带 size 字段) AllocateShm + memcpy + ret_i64 ✅
11 glGetString SendGetString 专用通道 ✅
12 进程退出 Client::Shutdown → mobilegl_fullserver_destroy(join server thread) ✅

2. 环境变量(MOBILEGL_CS_*)

见 README「Runtime environment variables」表。插件 manifest 已固化: MOBILEGL_CS_MODE=inprocess、MOBILEGL_CS_BACKEND=DirectGLES。

3. 已知不支持面(eglGetProcAddress 返回 nullptr / 生成器跳过)

类别 例子 计划
无尺寸 out 参数 glGetIntegerv / glGetFloatv(已支持:共享 MobileGLQueryCount 表 + out_capacity) ✅ 已实现
多级指针 glShaderSource(已支持:shm NUL 拼接 + FullServer opcode 拦截重建指针数组);glGetActiveAttrib/Uniform 等仍待 部分支持 ✅/待补
输入向量 glUniform*(已支持:客户端物化 + 内联向量) ✅ 已实现
指针返回(非 GetString) glMapBufferRange(typed 通道已覆盖) 保持 typed 路径
EGL 全量 eglCreateSync / eglCreateImage 等 后续补齐

安全红线:任何返回写入都必须遵守“服务端知道写入量 ≤ 客户端缓冲区容量”。 当前仅对带 n/count/num 的 out 向量允许自动分配;无尺寸查询保持 unsupported。

4. 验证步骤(需要 shell/构建工具,phase 2 禁用,留待后续阶段)

# 1) 代码生成 + 编译(Linux)
cmake -S . -B build_agent -DMOBILEGL_BUILD_TEST=ON -DMOBILEGL_BUILD_CS_REFACTOR=ON -DMOBILEGL_FLATC=/tmp/flatc_bin/flatc
cmake --build build_agent --target MobileGL_Client MobileGL_FullServer MobileGL_UtilRuntime BackendObject_DirectGLES -j

# 2) 生成覆盖报告
cat MobileGL/MG_Client/generated_trampoline_coverage.txt

# 3) 单元/传输回归
ctest --test-dir build_agent -L unit -j

# 4) 插件 APK(Android,需要 SDK/NDK 27.3)
cd android-plugin && ./gradlew :app:assemblePluginRelease

5. 验收标准(M1/M2/M3)

  • M1:裸进程 libMobileGL_Client.so + in-process hosting → eglGetDisplay/Initialize/MakeCurrent/glClear/glReadPixels 全通。
  • M2:FCL 选择「MobileGL C/S」渲染器,进入 Minecraft 主菜单(对象创建 + 数据上传 + GetString 必通)。
  • M3:游戏可玩(渲染正确、crash-free、退出无挂起线程)。