From d554c87c45c8f19697fb3086800bddee608c75ae Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 1 Oct 2026 23:16:33 -0400 Subject: [PATCH] [Fix] (Loader+Texture): tolerate drivers without EXT swap-with-damage, and pair stencil-only storage correctly - eglSwapBuffersWithDamageEXT is an extension entry point that Android's framework libEGL does not export through dlsym; requiring it was a bring-up FATAL on TB321FU. Resolve EXT, fall back to the KHR spelling (same function, interchangeable signature), then to plain eglSwapBuffers (a zero-rectangle damage call is defined to be exactly that). The GLES dlsym fallback gains a self-reference guard for MobileGL installed under a GLES soname, where dlsym would otherwise hand the backend the frontend's own entry points and recurse forever. - NormalizePixelFormat maps GL_STENCIL_INDEX8/GL_STENCIL_INDEX to GL_STENCIL_INDEX/GL_UNSIGNED_BYTE instead of falling through to RGBA/UBYTE, which no driver can pair with stencil-only storage. - Tests pin the EXT->KHR->none preference order and the stencil pair. - The anland handoff is rewritten to the post-P14 landed state: device evidence (win-smoke green screen, Konsole on screen, eglinfo/glxinfo), the remote-only recovery procedure, and the known gaps (Plasma session bring-up, cursor scale, the no-CPU-blit requirement, resize/surface loss, AVC audit). --- .../BackendLoader/BackendLoaderTest.cpp | 31 + MobileGL/MG_Test/Texture/TextureTest.cpp | 27 + .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 156 ++++- .../MG_Util/BackendLoaders/OpenGL/Loader.h | 21 +- .../Texture/TextureFormatProcessor.cpp | 20 + .../anland/handoff-legacy-mobilegl-unified.md | 554 ++---------------- 6 files changed, 303 insertions(+), 506 deletions(-) diff --git a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp index ed10ea49..716d2f4a 100644 --- a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp +++ b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp @@ -1588,3 +1588,34 @@ TEST(ComputeWorkGroupCapabilities, TakesEveryAxisFromTheIndexedQuery) { EXPECT_GT(caps.MaxComputeWorkGroupCount[0], 65535); EXPECT_GT(caps.MaxComputeWorkGroupSize[2], 64); } + +namespace { + EGLBoolean FakeSwapBuffersWithDamage(EGLDisplay dpy, EGLSurface surface, const EGLint* rects, EGLint nRects) { + (void)dpy; + (void)surface; + (void)rects; + (void)nRects; + return EGL_TRUE; + } +} // namespace + +// EXT -> KHR -> nothing, the whole policy behind the loader's single damage-present slot. +// +// EGL_EXT_swap_buffers_with_damage and EGL_KHR_swap_buffers_with_damage define the same entry +// point with the same signature, and a driver may expose either, both or neither - Adreno +// exposes only the KHR spelling, which is why resolving the EXT name alone reported the entry +// point missing on a driver that has one. A null answer is not a failure either: the caller +// (DirectGLES::Present) reads it as "present with plain eglSwapBuffers", and a zero-rectangle +// call to either spelling is defined to be equivalent to that anyway. +TEST(SwapBuffersWithDamageResolution, PrefersExtThenKhrThenAnswersNothing) { + using namespace MobileGL; + MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR ext = &FakeSwapBuffersWithDamage; + MG_External::EGL::eglSwapBuffersWithDamageKHR_PTR khr = &FakeSwapBuffersWithDamage; + + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(ext, khr), ext) + << "a driver that exports both spellings is answered with the EXT one"; + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(nullptr, khr), khr) + << "the KHR spelling alone must still fill the slot"; + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(nullptr, nullptr), nullptr) + << "neither spelling stays null, so the caller keeps eglSwapBuffers"; +} diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 7bd51045..92879be7 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -3389,6 +3389,33 @@ TEST_F(TextureTest, NormalizePixelFormatKeepsPackedTransferTypesForPackedSizedFo } } +// Stencil-only storage is the third single-channel case NormalizePixelFormat has to answer for +// (the depth and RED families are the others), and the one the format-capability probe asks +// about on every session bring-up: PopulateFormatCapabilities walks the whole logical-format +// table through BuildNativeProbeFormatInfo, which takes this function's format/type pair and +// hands it to glTexImage2D. Left to the switch defaults, GL_STENCIL_INDEX8 was reported as an +// unhandled internal format and answered with GL_RGBA/GL_UNSIGNED_BYTE - a pair no driver can +// pair with stencil-only storage - so every probe and every upload of it was an error whose +// cause the log blamed on the format table instead of on the pair. +// +// GL_STENCIL_INDEX / GL_UNSIGNED_BYTE is the pair the rest of the pipeline already uses for +// this storage: the frontend's ValidateTextureInternalFormatCompatibleWithInput requires +// STENCIL_INDEX to pair with a stencil-only internal format, the canonical shadow layout for +// StencilIndex8 is one UInt8 component, and the stencil readback path issues exactly this pair. +TEST_F(TextureTest, NormalizePixelFormatMapsStencilOnlyStorageToStencilIndex) { + using MG_Util::TextureFormatProcessor::NormalizePixelFormat; + // GL_STENCIL_INDEX is the unsized spelling that resolves to the same storage (see + // ConvertGLEnumToTextureInternalFormat), so both must answer together. + for (const GLenum internalFormat : {GL_STENCIL_INDEX8, GL_STENCIL_INDEX}) { + GLenum outInternal = 0, outFormat = 0, outType = 0; + NormalizePixelFormat(internalFormat, PixelFormatNormalizeOptionBit::None, &outInternal, &outFormat, + &outType); + EXPECT_EQ(outInternal, internalFormat) << "internalformat 0x" << std::hex << internalFormat; + EXPECT_EQ(outFormat, GL_STENCIL_INDEX) << "internalformat 0x" << std::hex << internalFormat; + EXPECT_EQ(outType, GL_UNSIGNED_BYTE) << "internalformat 0x" << std::hex << internalFormat; + } +} + // The packed16 field-order quirk (PixelFormatNormalizeOptionBit::WidenPacked16Norm): where the // driver stores some packed16 allocations with a mirrored field order (the Mali defect // behind the KHR-GL4x.copy_image rgb5/rgb5_a1/rgba4 x *2d_array* failures), the diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 4584572e..302e1f96 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -127,6 +127,57 @@ namespace MobileGL::MG_Util::BackendLoader { #endif } + // True when a symbol resolved through dlsym lives in the module this code is compiled into. + // + // That happens when MobileGL is installed under a GLES soname (a drop-in libGLESv2.so, which + // is how a replacement driver reaches an application), and there dlsym hands back MobileGL's + // OWN exported entry point: the backend would call it as if it were the driver's and land + // straight back in the frontend, once per call, for ever. Such a symbol is not the driver's. + static Bool ResolvesIntoOwnModule(void* symbol) { +#if defined(_WIN32) + (void)symbol; + return false; +#else + Dl_info symbolInfo{}; + Dl_info ownInfo{}; + if (dladdr(symbol, &symbolInfo) == 0 || + dladdr(reinterpret_cast(&ResolvesIntoOwnModule), &ownInfo) == 0) { + return false; + } + return symbolInfo.dli_fname != nullptr && ownInfo.dli_fname != nullptr && + std::strcmp(symbolInfo.dli_fname, ownInfo.dli_fname) == 0; +#endif + } + + // The GLES library the dlsym fallback in AcquireGLESFunctions reads from. Only ever opened + // once, and only when eglGetProcAddress has already failed for something: on a driver that + // answers eglGetProcAddress completely this handle is never taken. + static void* OpenFallbackGlesLibrary() { + if (UseAngle()) { + // The same library AcquireEGLFunctions loaded the EGL functions from: resolving an + // entry point off the vendor's driver while the current context is ANGLE's would + // hand the backend two different implementations. + return OpenLib({"libGLESv2_angle.so"}); + } +#if defined(__ANDROID__) + // Android's soname; the libGLESv3.so of older releases aliases the same library. + return OpenLib({"libGLESv2.so", "libGLESv3.so"}); +#else + // Versioned first: the unversioned name is a development symlink and is absent from + // runtime-only images, the same trap the libEGL lookup documents. + return OpenLib({"libGLESv2.so.2", "libGLESv2.so", "libGLESv3.so"}); +#endif + } + + MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR + ResolveSwapBuffersWithDamage(MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR ext, + MG_External::EGL::eglSwapBuffersWithDamageKHR_PTR khr) { + if (ext != nullptr) { + return ext; + } + return (MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR)khr; + } + void AcquireGLESFunctions(MG_External::GLESFunctionsTable& funcs, MG_External::EGL::eglGetProcAddress_PTR procAddress) { if (!procAddress) { @@ -134,19 +185,53 @@ namespace MobileGL::MG_Util::BackendLoader { return; } + // THE SECOND CHANCE. eglGetProcAddress is the mechanism the spec defines for finding an + // entry point, and it is what this loader used to use exclusively - but it is not + // complete on every driver. Measured on an Adreno 750 (ES 3.2, 125 extensions): + // eglGetProcAddress("glShaderStorageBlockBinding") - an ES 3.1 core function - answers + // NULL, while the driver's own libGLESv2.so exports the symbol. The load failure that + // produced was reported for a function the driver actually has, on every session. + // + // So anything eglGetProcAddress does not answer gets one more look through dlsym on the + // GLES library itself, opened lazily and only if some entry point needed it. + void* fallbackLib = nullptr; + Bool fallbackLibOpened = false; + const auto dlsymFallback = [&fallbackLib, &fallbackLibOpened](const char* name) -> void* { + if (!fallbackLibOpened) { + fallbackLibOpened = true; + fallbackLib = OpenFallbackGlesLibrary(); + } + if (fallbackLib == nullptr) { + return nullptr; + } + void* symbol = ProcAddress(fallbackLib, name); + if (symbol != nullptr && ResolvesIntoOwnModule(symbol)) { + return nullptr; + } + return symbol; + }; + #define INIT_GLES_FUNC(name) \ do { \ funcs.name = (MG_External::GLES::name##_PTR)procAddress(#name); \ + if (!funcs.name) { \ + funcs.name = (MG_External::GLES::name##_PTR)dlsymFallback(#name); \ + } \ if (!funcs.name) { \ MGLOG_E("Failed to load GLES function: %s", #name); \ } \ } while (0); // Optional (extension-provided) entry points: a null pointer is expected on drivers that lack the -// extension, so absence is not an error. The call site null-checks before use. +// extension, so absence is not an error. The call site null-checks before use. The dlsym fallback +// applies here too - an extension entry point is exactly the kind a vendor's eglGetProcAddress is +// most likely to hide. #define INIT_GLES_FUNC_OPTIONAL(name) \ do { \ funcs.name = (MG_External::GLES::name##_PTR)procAddress(#name); \ + if (!funcs.name) { \ + funcs.name = (MG_External::GLES::name##_PTR)dlsymFallback(#name); \ + } \ } while (0); { @@ -407,7 +492,15 @@ namespace MobileGL::MG_Util::BackendLoader { INIT_GLES_FUNC(glFramebufferParameteri) INIT_GLES_FUNC(glGetFramebufferParameteriv) INIT_GLES_FUNC(glGetProgramInterfaceiv) - INIT_GLES_FUNC(glShaderStorageBlockBinding) + // ES 3.1 core, but NOT every driver answers eglGetProcAddress for it: measured on + // an Adreno 750, the call returns NULL here while the driver's own libGLESv2.so + // exports the symbol - which the dlsym fallback in this function now finds. A + // driver that genuinely lacks it is not a load failure either: the only caller + // (ApplyShaderStorageBlockBinding in DirectGLES/Managers.cpp) null-checks, and + // DirectGLES's actual mechanism for moving a storage block's binding is the + // layout(binding=) qualifier it prints into the generated ESSL - this call can + // only ever add a redundant rebinding on a driver that does expose it. + INIT_GLES_FUNC_OPTIONAL(glShaderStorageBlockBinding) INIT_GLES_FUNC(glGetProgramResourceIndex) INIT_GLES_FUNC(glGetProgramResourceName) INIT_GLES_FUNC(glGetProgramResourceiv) @@ -608,6 +701,18 @@ namespace MobileGL::MG_Util::BackendLoader { return; } + // THE EXTENSION MECHANISM, RESOLVED BEFORE THE TABLE. The EGL 1.x core entry points are + // exported symbols of the library, so dlsym finds them; the EXTENSION ones are not. + // Android's libEGL.so exports none of eglSwapBuffersWithDamageEXT/KHR or + // eglUnlockSurfaceKHR even though the Adreno driver behind it implements them - the + // spec has extension entry points reached through eglGetProcAddress, never through the + // library's symbol table. Resolving them by dlsym alone therefore reported them missing + // on a driver that has them, at FATAL, on every session bring-up. So dlsym stays the + // first leg (an eglGetProcAddress dispatch stub would be wrong for the core functions) + // and eglGetProcAddress is the second. + MG_External::EGL::eglGetProcAddress_PTR getProcAddress = + reinterpret_cast(ProcAddress(eglLib, "eglGetProcAddress")); + auto resolveEGLProc = [&](const char* name) -> void* { #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) && defined(__ANDROID__) if (UseAngle()) { @@ -615,10 +720,18 @@ namespace MobileGL::MG_Util::BackendLoader { // resolve its forwarding target from the verified variant. String target = "EGL_"; target += name + 3; - return ProcAddress(angleGlesLib, target.c_str()); + if (void* resolved = ProcAddress(angleGlesLib, target.c_str())) { + return resolved; + } } #endif - return ProcAddress(eglLib, name); + if (void* resolved = ProcAddress(eglLib, name)) { + return resolved; + } + if (getProcAddress != nullptr) { + return reinterpret_cast(getProcAddress(name)); + } + return nullptr; }; #define INIT_EGL_FUNC(name) \ @@ -632,6 +745,18 @@ namespace MobileGL::MG_Util::BackendLoader { } \ } while (0); +// Extension-provided entry points. A driver that has neither the extension nor a forwarding +// alias answers NULL, which is the ordinary answer there rather than a defect, so it is +// reported at INFO and left to the caller - which null-checks (see Present's swap in +// DirectGLES.cpp). Reached through eglGetProcAddress by resolveEGLProc's second leg. +#define INIT_EGL_FUNC_OPTIONAL(name) \ + do { \ + funcs.name = (MG_External::EGL::name##_PTR)resolveEGLProc(#name); \ + if (!funcs.name) { \ + MGLOG_I("Optional EGL entry point not provided by this driver: %s", #name); \ + } \ + } while (0); + { INIT_EGL_FUNC(eglBindAPI) INIT_EGL_FUNC(eglBindTexImage) @@ -665,10 +790,9 @@ namespace MobileGL::MG_Util::BackendLoader { INIT_EGL_FUNC(eglReleaseThread) INIT_EGL_FUNC(eglSurfaceAttrib) INIT_EGL_FUNC(eglSwapBuffers) - INIT_EGL_FUNC(eglSwapBuffersWithDamageEXT) INIT_EGL_FUNC(eglSwapInterval) INIT_EGL_FUNC(eglTerminate) - INIT_EGL_FUNC(eglUnlockSurfaceKHR) + INIT_EGL_FUNC_OPTIONAL(eglUnlockSurfaceKHR) INIT_EGL_FUNC(eglWaitClient) INIT_EGL_FUNC(eglWaitGL) INIT_EGL_FUNC(eglWaitNative) @@ -681,6 +805,26 @@ namespace MobileGL::MG_Util::BackendLoader { INIT_EGL_FUNC(eglGetPlatformDisplay) INIT_EGL_FUNC(eglWaitSync) } + + { + // One slot, two names. EGL_EXT_swap_buffers_with_damage and + // EGL_KHR_swap_buffers_with_damage define the same entry point with the same + // signature, and a driver may expose either, both or neither - Adreno exposes only + // the KHR spelling - so the winner of the EXT -> KHR chain lands in the EXT slot. + // Null when the driver offers neither, which is the normal "no damage-based present + // here" answer: the caller keeps plain eglSwapBuffers (and note that a call with + // n_rects == 0 is defined to be equivalent to eglSwapBuffers anyway). + funcs.eglSwapBuffersWithDamageEXT = ResolveSwapBuffersWithDamage( + reinterpret_cast( + resolveEGLProc("eglSwapBuffersWithDamageEXT")), + reinterpret_cast( + resolveEGLProc("eglSwapBuffersWithDamageKHR"))); + if (funcs.eglSwapBuffersWithDamageEXT == nullptr) { + MGLOG_I_ONCE("This driver has no damage-based present entry point (neither " + "eglSwapBuffersWithDamageEXT nor eglSwapBuffersWithDamageKHR); " + "present calls fall back to eglSwapBuffers"); + } + } } // Detects whether indirect draws leak the command's baseInstance word ("reserved, must diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index e0d41fd3..4ecb0065 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -63,8 +63,15 @@ namespace MobileGL { typedef EGLBoolean (*eglSurfaceAttrib_PTR)(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value); typedef EGLBoolean (*eglSwapBuffers_PTR)(EGLDisplay dpy, EGLSurface surface); - typedef EGLBoolean (*eglSwapBuffersWithDamageEXT_PTR)(EGLDisplay dpy, EGLSurface surface, EGLint* rects, - EGLint n_rects); + // The damage-based present entry point, in both spellings a driver may expose it + // under. EGL_EXT_swap_buffers_with_damage and EGL_KHR_swap_buffers_with_damage + // define the same function with the same signature, so the two pointers are + // interchangeable and one slot can hold either - see + // BackendLoader::ResolveSwapBuffersWithDamage. + typedef EGLBoolean (*eglSwapBuffersWithDamageEXT_PTR)(EGLDisplay dpy, EGLSurface surface, + const EGLint* rects, EGLint n_rects); + typedef EGLBoolean (*eglSwapBuffersWithDamageKHR_PTR)(EGLDisplay dpy, EGLSurface surface, + const EGLint* rects, EGLint n_rects); typedef EGLBoolean (*eglSwapInterval_PTR)(EGLDisplay dpy, EGLint interval); typedef EGLBoolean (*eglTerminate_PTR)(EGLDisplay dpy); typedef EGLBoolean (*eglUnlockSurfaceKHR_PTR)(EGLDisplay display, EGLSurface surface); @@ -116,6 +123,8 @@ namespace MobileGL { EGL_FUNC_DECL(eglReleaseThread) EGL_FUNC_DECL(eglSurfaceAttrib) EGL_FUNC_DECL(eglSwapBuffers) + // Holds whichever spelling of the damage-based present the driver provided, EXT + // preferred; null when it provides neither (see AcquireEGLFunctions). EGL_FUNC_DECL(eglSwapBuffersWithDamageEXT) EGL_FUNC_DECL(eglSwapInterval) EGL_FUNC_DECL(eglTerminate) @@ -1358,6 +1367,14 @@ namespace MobileGL { void AcquireGLESFunctions(MG_External::GLESFunctionsTable& funcs, MG_External::EGL::eglGetProcAddress_PTR procAddress); void AcquireEGLFunctions(MG_External::EGLFunctionsTable& funcs); + // The EXT -> KHR -> nothing chain for the damage-based present entry point. A null + // result is not a failure: it means this driver offers neither spelling, and the + // caller presents with plain eglSwapBuffers, which a zero-rectangle call to either + // spelling is defined to be equivalent to anyway. Exposed so MG_Test can pin the + // preference order without a driver. + MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR + ResolveSwapBuffersWithDamage(MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR ext, + MG_External::EGL::eglSwapBuffersWithDamageKHR_PTR khr); Bool FillInGLESCapabilities(MG_External::GLESCapabilities& caps, const MG_External::GLESFunctionsTable& glesFuncs); // Detects whether indirect draws leak the command's baseInstance word into diff --git a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp index 22fc0d81..7296bbb9 100644 --- a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp +++ b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp @@ -552,6 +552,18 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { *outFormat = GL_DEPTH_STENCIL; break; + // Stencil-only. GL_STENCIL_INDEX8 is the one storage ES has (glRenderbufferStorage + // accepts it outright, and a texture takes it under EXT/OES_texture_stencil8), and + // GL_STENCIL_INDEX is the transfer format that goes with it - the same pairing the + // frontend's validator demands (Validators.cpp: + // "STENCIL_INDEX requires a stencil-only internal format") and the same one the + // stencil readback path hands the driver. The unsized spelling is here for the same + // reason the unsized depth and colour formats are: it resolves to this storage. + case GL_STENCIL_INDEX8: + case GL_STENCIL_INDEX: + *outFormat = GL_STENCIL_INDEX; + break; + default: MGLOG_E_ONCE("NormalizePixelFormat: outFormat: unhandled internalFormat: %s", MG_Util::ConvertGLEnumToString(internalFormat).c_str()); @@ -776,6 +788,14 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { *outType = GL_UNSIGNED_INT_24_8; break; + // Stencil-only: eight unsigned index bits per texel, which is the layout the + // canonical shadow holds for it (PixelStoreProcessor's StencilIndex8 row: one + // UInt8 component) and the type the stencil readback path uses. + case GL_STENCIL_INDEX8: + case GL_STENCIL_INDEX: + *outType = GL_UNSIGNED_BYTE; + break; + default: MGLOG_E_ONCE("NormalizePixelFormat: outType: unhandled internalFormat: %s", MG_Util::ConvertGLEnumToString(internalFormat).c_str()); diff --git a/docs/Disaggregated/notes/anland/handoff-legacy-mobilegl-unified.md b/docs/Disaggregated/notes/anland/handoff-legacy-mobilegl-unified.md index 99f9e6eb..47b24b65 100644 --- a/docs/Disaggregated/notes/anland/handoff-legacy-mobilegl-unified.md +++ b/docs/Disaggregated/notes/anland/handoff-legacy-mobilegl-unified.md @@ -1,511 +1,69 @@ -# Handoff:anland 5.x + 内置 MobileGL SHM server +# Handoff:anland 5.x + 内置 MobileGL 统一 server(post-P14) -更新时间:2026-10-01(用户时区 America/Cuiaba)。**本文档是 anland 统一 server 工作的多会话权威状态文档**:动手前先通读,动手后更新。当前这版已刷新到 P14(MobileGL 库内的状态归属)落地之后的真实状态。 +更新时间:2026-10-02。本文替换旧的 handoff-legacy-mobilegl-unified.md(那份描述的是 P14 之前的状态,已被事实淘汰)。 -## 先读:当前完成度与状态 +## 任务与路线 -**架构已按用户纠正落地;真机验收仍未完成。KWin、Konsole、eglinfo、glxinfo、Plasma 都没有通过 MobileGL 真机验收。构建成功、进程存在、版本字符串都不能当成验收成功。** +anland 5.x 用 MobileGL:容器(Droidspaces,arch-kde-mgl)里的 Linux client 经 SHM pipe 连到 anland APK 内嵌的统一 server(`:mobilegl` 私有 Service,同 APK 同 UID),server 在 anland 自己的 Surface 上直接渲染(路线 B:`kgsl-as-server.md`)。不按应用 fork 渲染进程——session/context/share-group 状态归属在 MobileGL 库内解决(P14,[`../design/11-state-ownership.md`](../design/11-state-ownership.md))。 -用户原始目标: +## 远端与分支 -1. 在 anland 新建 `legacy-mobilegl-unified`,让 anland 5.x 使用 MobileGL。 -2. SHM split server **必须是 anland APK 的一部分,由 anland 管理**,client 在 Droidspaces 容器内。 -3. 在联想 Y700 TB321FU 上复制 `arch-kde` 为 `arch-kde-mgl`,作为实验环境。 -4. 依次调通 KWin → Konsole → eglinfo → glxinfo → Plasma。 -5. 阶段成果形成可二分的 commits,推送 anland fork 和 MobileGL `feat/disaggregated`。 -6. 开始前阅读此目录调研,尤其 [`kgsl-as-server.md`](kgsl-as-server.md),技术路线接近 B:anland 自己的 Surface 交给 MobileGL server 直接渲染。 +- MobileGL: 分支 `feat/disaggregated` +- anland: 分支 `legacy-mobilegl-unified` -用户已明确排除两种路线,这两条纠正**仍然有效**: - -> anland fork 的 `legacy-mobilegl` 路径是配合 MobileGL feat/disaggregated-container-gbm 的错误路径。MobileGL server 不应该活在独立的 plugin app 里,应该成为 anland 的一部分。 - -> 说起来,为什么会有实现一个应用离屏 Service?如果 MobileGL 有全局状态需要解决,那应该处理的是 MobileGL 自己的全局状态,而不是想办法制造多个 MobileGL peer。 - -**当前状态**(本轮刷新:2026-10-01 约 20:40 America/Cuiaba,对应设备本地 2026-10-02 08:4x CST): - -- 被否定的 fork-worker / `MobileGLAppsService` 路径**已移除**:anland APK 不再 fork 每应用 worker、不再声明第二个 endpoint(`33e496c`)。MobileGL 侧 `--serve --max-sessions` supervisor 只作为独立工具保留代码,anland 流程不再依赖它。 -- **统一 server 设计已在 MobileGL 库内实现(P14)**:设计文档 [`../design/11-state-ownership.md`](../design/11-state-ownership.md)(`d52c031c`),主体 S1–S5 `4ed1c9ce`、S6 `ff212061`、KWin 需要的 ARB 扩展串 `5a0f13fd`。单进程多 session、per-EGLContext 独立 GLContext、真实 share group 都已在库内落地;未做完的工程欠债见下文「P14 状态归属:已落地与已知欠债」。 -- **真机验收仍在进行**(用户有并行会话在推进):KWin 的 `ANLAND_MOBILEGL` 后端与 APK 内置 embedded server 都已上设备,server 端已观察到真实 client session 建连、native context 创建与探测日志;但 KWin 真正上屏还没有验收,Konsole、eglinfo、glxinfo、Plasma 未开始。 -- **存在并行会话同时操作同一台设备/容器与 Windows 仓库**(本文刷新时最近的 KWin 尝试就在几分钟前)。动手前先做并发检查,见下文「并发警告」。 - -## 工作区、分支、保留事项 - -### MobileGL - -```text -C:\Users\Swung0x48\repos\FoldCraftLauncher\MobileGL -origin: https://github.com/MobileGL-Dev/MobileGL.git -显示分支: feat/disaggregated -当前实现 HEAD: 5a0f13fd(P14 最后一个实现 commit;本文档 commit 在其之上) -``` - -Windows 上真实本地 ref 的大小写是 `refs/heads/Feat/disaggregated`。推送使用: - -```powershell -git push origin HEAD:refs/heads/feat/disaggregated -``` - -不要用失败的 `git push origin feat/disaggregated` 重复排查网络。 - -用户原有 dirty submodule `3rdparty/glslang` 必须保留,不能提交、重置或当成本任务成果。为 split 构建初始化过缺失的 `3rdparty/flatbuffers`,没有修改 gitlink。本地 glslang 工作区实际在 `6f125987`(比记录的 gitlink `d89cf443` 新若干 commit,就是用户自己的 dirty 内容)。 - -工作区当前未提交改动**只剩**用户原有的 dirty submodule `3rdparty/glslang`。原「本任务未提交改动」三件已全部提交: - -| 原改动 | 落在 | -|---|---| -| `CMakeLists.txt`:Linux 增加 `libGL.so`、`libGL.so.1` 别名 | `6fb880cd` | -| `EGLImpl.cpp`:Wayland `Present()` 失败返回 `EGL_BAD_SURFACE`;`GLXImpl.cpp`:spawn 模式 X drawable 用远端 pbuffer,避免把 XID 当 Android ANativeWindow | `ebc552a0`(**GLX 仍无 X11 读回/present、无远端 resize**) | - -DirectGLES/BackendObject「保留一个 native context、切换多个 surface」在 P14 S4 **已经真正实现**:原生 EGL tuple 按 `(session, context token)` 键控,`InitDisplayAndContext()` 首句的无条件 `DestroyEGLContext()` 已移除,建第二个 context/surface 不再销毁第一个。证据是真实 headless EGL 的 `ServerLoopEglTest.CreatingASecondContextLeavesTheFirstNativeContextAlive` 与 `CreatingAnotherSurfaceKeepsTheSameNativeContext`。 - -### anland - -原工作区: - -```text -C:\Users\Swung0x48\repos\anland -origin: https://github.com/MobileGL-Dev/anland.git -原分支: main -``` - -原工作区用户 dirty submodules `pulseaudio`、`wayland`、`xserver` 没有修改。继续保留。 - -本任务使用独立 worktree,**后续 anland 编辑必须在这里进行**: - -```text -C:\Users\Swung0x48\repos\anland-legacy-mobilegl-unified -branch: legacy-mobilegl-unified -base: origin/legacy = 9ab13eb146bc9f3dd3cc44cabc268659d9eb0a46 -当前本地/远端 HEAD: 33e496c9564e34b48185d7dd81c0c8956a397f3d(与 origin/legacy-mobilegl-unified 一致) -``` - -工作区已与远端一致,只剩 3 个 KWin 验收相关文件有未提交小改动(见「KWin 6.7.4 后端」一节)。不要重新 checkout 远端分支来替代工作区。 - -## 已推送 commits 与适用性 - -### MobileGL `feat/disaggregated` - -| Commit | 内容 | 适用性 | -|---|---|---| -| `57781688` | GLVND EGL vendor ABI headers,来自 `6a702afa` | 可复用的独立 client 能力 | -| `75f83b20` | EGL extension strings,来自 `dfbda7cc` | 可复用 | -| `0dad3fca` | EGL GLVND vendor,来自 `c9206dd9` | 可复用 | -| `f4589243` | multiple display/vendor lookup,来自 `2860d090` | 可复用 | -| `32c79f41` | Wayland wl_shm WSI,来自 `7fd7f71d` | 初步 client 能力;有下述限制 | -| `a5b463a5` | package-neutral embedded display C ABI | 14 项 ownership/lifecycle 测试通过,可复用 | -| `2f64ede8` | Unix `--serve --max-sessions 1..64`,fork worker 并发 | **用户否定用它解决 anland 全局状态。anland 已不依赖它;代码只作为独立工具保留,不是最终架构,不能当任务完成。** | -| `4156ca6e` | 上一版 anland 交接文档 | 文档 | -| `d52c031c` | **P14 设计文档**:state ownership for multi-session/context/share-group | 设计权威:`design/11-state-ownership.md` | -| `6fb880cd` | Linux 增加 `libGL.so`/`libGL.so.1` 别名 | 已推送 | -| `ebc552a0` | Wayland `Present()` 失败上报 + GLX drawable 不拿 XID | 已推送 | -| `4ed1c9ce` | **P14 主体 S1–S5**:`bind_context` wire 记录(opcode 84)+ CreateContext/DestroyContext 控制帧、control revision 5→6;SessionRuntime 按 session 实例化(singleton 降级为 fallback)、in-process supervisor 最多 16 并发 session;前端 `ShareGroupState` + per-EGLContext `GLContext`、`pGLContext` 变 thread-local;DirectGLES 原生 tuple 按 `(session, token)` 键控、不再拆 context、`eglCreateContext` share 接到真驱动;applier 拆成 `MGPipeObjectRecords`(per share group)与 `MGPipeWorkingState`(per context) | 已合入并推送 | -| `5c6399e2` | S5 文档:记录已落地形状与剩余 twin-key 欠债 | 文档 | -| `ff212061` | **P14 S6**:八个 DirectGLES twin 表按 `{SessionKey, ShareGroupKey}` 重键(`TwinKey` + `ResolveThreadTwinKey`),`DropEveryTwinForEndedServerSession` 变 per-session | 已合入并推送 | -| `5a0f13fd` | KWin 所需的 GLSL 1.00 时代 ARB 扩展串(`GL_ARB_shader_objects`/`vertex_shader`/`fragment_shader`/`texture_non_power_of_two`);KWin 6 的 `EglContext::checkSupported()` 要这些串才起 compositing | 已合入;本文档 commit 之前它是 branch HEAD | - -这些独立 client 改动来自旧分支,但没有 cherry-pick 独立 plugin/GBM/host-frame 整体架构。`32c79f41` 冲突处理中移除了尚未实现的 device/GBM 扩展广告。 - -### anland `legacy-mobilegl-unified` - -三笔 commit 都已推送到 `origin/legacy-mobilegl-unified`: +### MobileGL 关键提交(新→旧) | Commit | 内容 | |---|---| -| `6e1fa65` | no-fd MobileGL Surface 显示协议模式:`DATA_MSG_MOBILEGL_SURFACE=201`、`ANLAND_FORMAT_MOBILEGL_SURFACE=0x4d474c53`。metadata 没有 fd,保留一 pacing slot(fd=-1、idx=0)。真实 daemon + producer + consumer 测试验证 metadata、frame ack、输入事件。没有假造 dma-buf。 | -| `2ffa1f5` | KWin `ANLAND_MOBILEGL` 后端:跳过 GPU/DRM probing、不广告 dma-buf/syncobj、渲染进 server-owned EGL window(default FBO 0、full damage、`eglSwapBuffers`);未设该变量时保留 Mesa producer。 | -| `33e496c` | **APK 内置统一 embedded server**:私有同 UID `:mobilegl` Service 加载 libMobileGL,为 compositor 和所有容器 client 提供**单个 endpoint `@anland-mobilegl`**;session 调度留在 MobileGL 库内,Anland 不 fork 每应用 worker、不传 session budget。每应用离屏 worker Service `MobileGLAppsService` **已删除**。 | +| `7a48521d` | DirectGLES:创建 server-owned window surface 前先销毁同窗口的残留 surface(此前第二个起的 session 必死 EGL_BAD_NATIVE_WINDOW) | +| `5a0f13fd` | 广告 GL_ARB_shader_objects/vertex_shader/fragment_shader/texture_non_power_of_two(KWin `checkSupported` 的硬门槛) | +| `ff212061` | P14 S6:后端 twin 表按 {session, share group} 键控 | +| `4ed1c9ce` | P14 S1–S5:`bind_context`(opcode 84)+ CreateContext/DestroyContext 控制帧(control revision 6);同进程多 session(`SessionRuntime`、线程域解析、per-session latch,inproc unix supervisor 并发上限 `MOBILEGL_IPC_INPROC_MAX_SESSIONS`=16);前端 ShareGroupState/per-context GLContext/TLS pGLContext;DirectGLES per-(session,context) native tuple(不再无条件 teardown);applier 按 {session,context}/{session,shareGroup} 分桶 | +| `ebc552a0`/`6fb880cd` | Wayland Present 失败回 EGL_BAD_SURFACE;GLX drawable 走远端 pbuffer;Linux libGL 别名 | +| `d52c031c` | P14 设计文档 | -## anland 草稿:已落地与已移除 +另有一批已审查、待验证提交的 WIP(被杀死的并行 agent 留下):EGL loader 的 `eglSwapBuffersWithDamageEXT` 缺符号从 FATAL 改为 EXT→KHR→eglSwapBuffers 回退 + GLES 符号 dlsym 回退(防自指)、stencil-only 格式配对修复,各带测试。 -### Android APK/JNI:已落地(`33e496c`) +### anland 关键提交(新→旧) -下列文件(含 `MobileGLWorker.java`、`MobileGLConnection.java`、`mobilegl_worker.c`、`MOBILEGL.md`)都已提交: - -```text -consumers/anland_v5/android_consumer/MOBILEGL.md -consumers/anland_v5/android_consumer/app/build.gradle -consumers/anland_v5/android_consumer/app/src/main/AndroidManifest.xml -consumers/anland_v5/android_consumer/app/src/main/java/com/anland/consumer/MainActivity.java -consumers/anland_v5/android_consumer/app/src/main/java/com/anland/consumer/MobileGLConnection.java -consumers/anland_v5/android_consumer/app/src/main/java/com/anland/consumer/MobileGLWorker.java -consumers/anland_v5/android_consumer/app/src/main/java/com/anland/consumer/Native.java -consumers/anland_v5/android_consumer/app/src/main/jni/CMakeLists.txt -consumers/anland_v5/android_consumer/app/src/main/jni/mobilegl_worker.c -consumers/anland_v5/android_consumer/app/src/main/jni/native_consumer.c -``` - -`MobileGLAppsService.java` 已删除;manifest/binding/health check、第二个应用 endpoint、`libMobileGLServer.so --serve --max-sessions 16` 依赖与旧文档一并移除。**现在的 anland 侧只有「统一 embedded server + 正常多 client 连接」一条路径。** - -仍有效的设计要点: - -- `MobileGLWorker` 是同 APK、同 UID 的私有 `:mobilegl` Service(不是 isolatedProcess),接收 anland 自己的 Surface,JNI 通过 `dlopen/dlsym` 使用中立 ABI。 -- 主 UI 进程不加载 MobileGL 的 GL 导出;避免影响 Android framework/UI 的 EGL。 -- `MainActivity` 等待 worker attach 后才启动 native display consumer。 -- MobileGL 模式 native consumer **不连接 ANW_API_CPU、不 dequeue/queue Surface buffer**,EGL 是唯一生产者;输入、音频、显示协议继续保留。 -- 新 Surface parcel 每次会产生不同 native wrapper。保留同一 window owner/generation 的 Java Surface,尺寸更新使用保留的 ANativeWindow,避免 resume/resize 把活动 session detach。 -- native geometry callback → one-way Binder → UI SurfaceHolder;`surfaceChanged` 先报告尺寸,再等待 native transport 停止,避免 geometry/ack 互等。 -- `Surface.getGenerationId()` 是隐藏 API,曾导致编译失败;改为 NDK native identity token + 本地生命周期 generation。 -- CMake 使用仓库根下的 authoritative common/libdisplay 源码,避免 Windows 把 symlink checkout 成 target-text 文件。 -- `onDestroy` 先 join serve thread 再拆 display,runtime 不会被从活着的 apply 下面抽走。 -- `mobileglDist` staging 在切回 unbundled 时显式 cleanup;**Gradle Sync 没有 sources 时会 NO-SOURCE,不自动删除旧文件**。 - -worker 里 `MOBILEGL_BACKEND_TYPE` 当前工作区改成了 `DirectGLES`(未提交,见下),设备上装的 APK 仍是提交版(`Espryt`)。 - -### KWin 6.7.4 后端 - -**anland 5.x 是宿主协议版本,容器当前 KWin 实际为 6.7.4。** - -已提交(`2ffa1f5`;工作区另有未提交小修,见下): - -```text -producers/kde/Arch_v5/kwin.patch -producers/kde/anland_backend_Arch_v5/src/backends/anland/anland_backend.cpp -producers/kde/anland_backend_Arch_v5/src/backends/anland/anland_backend.h -producers/kde/anland_backend_Arch_v5/src/backends/anland/anland_egl_backend.cpp -producers/kde/anland_backend_Arch_v5/src/backends/anland/anland_egl_backend.h -producers/kde/Arch_v5/mobilegl-startup.sh # 100755 -producers/kde/Arch_v5/mobilegl.md -``` - -`CMakeLists.txt` 也可能显示 dirty(曾尝试 PUBLIC include,已撤销语义改动;检查实际 diff/行尾)。 - -`ANLAND_MOBILEGL=1` 路径:跳过 GPU/DRM probing,不广告 dma-buf/syncobj,不要求 GBM/no_config/surfaceless。先选择 RGBA window config,创建 server-owned EGL window,再创建 RenderDevice/shared contexts。默认 makeCurrent 使用该 Surface。output layer marker 走 default FBO 0、full damage 和 eglSwapBuffers,不导入 consumer dma-buf。Mesa 路径保留。 - -full repaint 是因为 Android BufferQueue 自己轮换 buffers,而 client 暂未提供 buffer age。RenderTarget transform 保持原 Anland FlipY/output transform 语义。 - -native 构建已通过。public backend headers 的 include 路径已改为自足的相对 include:`main_wayland.cpp` 包含它们时也必须找到 producer/protocol header,不能靠 backend PRIVATE include dirs。 - -launcher 有 `compositor`/`plasma` modes;统一 server 只有 `@anland-mobilegl` 一个 endpoint,两个 mode 不再按 endpoint 分配环境。`mobilegl-startup.sh` 已带 executable bit `100755`。 - -**当前工作区还有 3 个未提交的 KWin 验收相关小改动**(未推送;动手前先 `git diff` 确认,不要覆盖): - -| 文件 | 改动 | +| Commit | 内容 | |---|---| -| `.../com/anland/consumer/MobileGLWorker.java` | worker 的 `MOBILEGL_BACKEND_TYPE` 由 `Espryt` 改为 `DirectGLES`(**APK 侧,需重打包才生效;设备上装的仍是提交版**) | -| `producers/kde/Arch_v5/mobilegl-startup.sh` | compositor 侧 `MOBILEGL_LOG_FILE_PATH` 默认 `/tmp/mobilegl-compositor.log`(**容器里已安装的副本还是旧版,没有这个变量**) | -| `.../backends/anland/anland_backend.cpp` | `eglChooseConfig` 判定由 `count != 1` 放宽为 `count < 1` | - -## P14 状态归属:已落地与已知欠债 - -统一 server 的状态归属已经在 MobileGL 库内实现,不再依赖“每进程隔离”绕开问题。设计与逐切片的形状、逐项源码定位(行号随实现移动)见 [`../design/11-state-ownership.md`](../design/11-state-ownership.md);本节只记交接需要的结论。 - -**已落地(`4ed1c9ce` S1–S5、`ff212061` S6)**: - -- **Wire**:`bind_context` ring record(opcode 84)在 make-current 边沿宣告当前 context;`CreateContext`/`DestroyContext` 进入 SurfaceOp 控制帧;control revision 5→6;server 保存 per-session token 表,未知 token 按名字拒绝。 -- **Session**:`SessionRuntime` 按 session 实例化,线程作用域解析,singleton 降级为 fallback;per-thread `gPipeInputs`、per-session latch domain、per-thread segment resolver;in-process unix supervisor 最多 16 个并发 session;surface mode 按 session 声明,display lease 拒绝第二个 on-screen session(不再靠 `SurfaceModeMismatch`)。 -- **Context**:前端 `pGLContext` 变 thread-local(进程默认 context 逐字保留单 context 世界),`eglCreateContext` 建真实 `GLContext` 并加入共享 context 的 share group;原生 EGL tuple 按 `(session, context token)` 键控,`bind_context` 是原生切换点;建第二个 context/surface 不再 teardown 第一个。 -- **Share group**:`ShareGroupState` 持有共享对象 store 与名字生成器(buffer/texture/renderbuffer/sampler/program),`GLContext` 持有 per-context bindings/errors/container 对象。 -- **Applier**:`MGPipeApplierState` 拆成 `MGPipeObjectRecords`(per share group)与 `MGPipeWorkingState`(per context),经 `{session, token}` registry 解析,带 thread-local 快路径。 -- **Twin**:八个 DirectGLES twin 表按 `{SessionKey, ShareGroupKey}` 重键(`TwinKey`,group 由 `MGPipeApplierShareGroupKeyFor` 派生,记录与 twin 不可能落进不同桶);`DropEveryTwinForEndedServerSession` 变 per-session(无 session 的线程保持旧的全量清理形状)。 - -**已知欠债(已写进设计文档,下一刀要补的)**: - -- **单元影子仍是进程级**:`g_activeTextureUnit`、`g_boundTexturesCache`、`g_boundSamplersCache` 与 sampler cache(`Managers.cpp` 约 11031-11032)还是全局(S6 报告记为 P14b/c)。残留风险是“同一个 twin 在一次原生 context 切换之后被缓存命中而跳过绑定”。 -- **`g_backendContextGeneration` / `g_syncContextGeneration` 仍是进程级**:销毁单个 context 会推进全局世代,其他 context 的旧 fence/query 句柄因此读作已 signal。方向安全(最坏是多等),但不是 per-context 世代。 -- **逐记录 `ContextSlot` 未做**:wire 仍走 S1 的“边沿宣告 + `bind_context`”,没有在 71 个 context 族记录的 header 与 payload 之间插 `Uint32 ContextSlot`;多线程交错下的逐记录身份只在 client 按 make-current 顺序发射时成立。 -- **驱动级 `glIsBuffer` 共享探针未通**:S4/S5/S6 都没得到可用控制(同 context 内 `glGenBuffers` 后 `glIsBuffer` 即返回 false)。S6 改走 twin 身份这条替代证据(twin 就是持有 driver id 的对象),驱动级 shared/nonshared 证据仍然是缺的。 -- **pbuffer→window 的 window 半边未验**:`pbuffer→pbuffer` 只到 `ServerLoopEglTest.CreatingAnotherSurfaceKeepsTheSameNativeContext` 的程度;真正的三 pbuffer 来回、以及 window surface 那一半需要真机 window,headless 验不了。 - -**还需要的真实验证**:相同 handle 值的两个 session 隔离并独立关闭;shared contexts 访问同一 buffer/texture;nonshared contexts 同名资源隔离;每 context 的 viewport/FBO/bindings/error 独立;跨线程移交;pbuffer→window→pbuffer 保留资源。无头/单元测试(`MultiSessionTest`、`ServerLoopEglTest` 等)已经覆盖其中一部分,**但它们都不等于真机 GPU 渲染验收**。 - -### WSI 现有限制 - -- Wayland:server pbuffer → readback RGBA → flip/swizzle → 三槽 wl_shm buffer → wl_surface;有独立 release event queue。`Present()` 失败现在返回 `EGL_BAD_SURFACE`(`ebc552a0`),不再静默忽略。 -- 未跟随 `wl_egl_window` resize;也没有隐藏窗口/pacing 完整验收。 -- 当前 GLES frontend 的版本/precision 等能力尚不能当完整 GLES 支持;`GL_VERSION` 仍 desktop 4.6,precisionformat 有 stub。KWin 优先 desktop GL,但 Plasma QtQuick 必须实际验证。 -- GLX 只让 glxinfo 的 X drawable 可以用远端 pbuffer 查询(`ebc552a0` 已提交);**没有 X11 readback/present,所以不能声称通用 GLX 窗口上屏,也没有远端 resize**。 -- Surface loss 当前会结束活动 session;无损挂起/重连尚未实现。 - -## 设备与容器:只操作 HA27Q3LQ - -```text -ADB serial: HA27Q3LQ -型号: Lenovo Y700 TB321FU -root: 可用,su context u:r:su:s0 -SELinux: Enforcing -Droidspaces: v6.4.5 -CLI: /data/local/Droidspaces/bin/droidspaces -clone: /data/local/Droidspaces/Containers/arch-kde-mgl -mount root: /mnt/Droidspaces/arch-kde-mgl -desktop user: swung0x48,uid1000 -clone UUID: bc34a4c0a69e424a8890836ed61e4a1d -clone init PID: 31212(继续前重新确认;宿主机侧是 `droidspaces --name=arch-kde-mgl start`,PID 31210/31211) -boot_id: 3f4027fc-50de-400a-8751-cc925d61e99d(设备 host boot_id,本轮未变) -``` - -有另外三台 ADB 设备,所有命令必须带 `-s HA27Q3LQ`。设备多次短暂 offline,`adb -s HA27Q3LQ reconnect` 后恢复;uptime 与 boot_id 证实设备本身没有重启(本轮 uptime 20:24)。 - -原 `arch-kde` 保持停止、配置未改。clone 从其 128GiB sparse ext4 image 拷贝约 7.7GB allocated extents;因为 Toybox/Busybox 不支持 `conv=sparse`,用 NDK SEEK_DATA/HOLE helper。不要重复全量复制。 - -clone Mesa 基线曾实测:freedreno FD750,OpenGL 4.6 Mesa 26.3.0,1280×800、scale2、120Hz。这是 **Mesa 基线,不是 MobileGL 成果**。 - -仅 clone 安装过构建依赖 GCC16.1.1/cmake/ninja/base-devel;没有 clang。clone 的 patched Xwayland `24.1.13-1.1` 为安装依赖降到仓库 `-1`,原容器未动。实验 backend 对 Xwayland 使用 `-shm` 并清除其 MobileGL loader 环境。 - -### 当前状态与目录 bind - -clone 已**重启过**(init PID 31212),**目录 bind 已生效**:容器内 `/run/anland-mobilegl` 的 inode 与宿主机 `/data/local/tmp/anland-mobilegl` 一致(本轮实测都是 59897)。使用目录 bind 是为了 daemon unlink/recreate socket 时不留下 file-bind 的旧 inode。 - -`egl_vendor.d` 的文件名问题(部分已修,**还有残留**): - -- 正确名 `50_mobilegl.json` 已就位(内容 `library_path=/opt/mobilegl/lib/libMobileGL.so`)。 -- 但同目录里**旧的 `50_mobilegl.json\r`(inode 999692)仍然存在**——构建脚本只写正确名、不删旧文件。建议清理,避免 GLVND vendor 扫描时读到带 `\r` 的坏文件名。 - -`desktop-session.service` 已 `disable`,`mobilegl-kwin-build.service` 为 inactive。**注意:本文刷新时该 service 处于 `failed` / `Result=start-limit-hit`**——并行会话在设备本地 08:19–08:26 跑起过一次 KWin(约 8 分钟),停止时 `kwin_wayland` SEGV(status=11),之后 08:27 的连续重启全部立即 SEGV、触发 start-limit。**下次手动验收前先 `systemctl reset-failed desktop-session.service`**(disable 状态下手动 start 仍可用)。 - -已在 clone config 追加(已生效): - -```text -bind_mounts=/data/local/tmp/display_daemon.sock:/run/display.sock,/data/local/tmp/anland-mobilegl:/run/anland-mobilegl -``` - -配置备份:`/data/local/Droidspaces/Containers/arch-kde-mgl/container.config.mobilegl-before`。 - -clone service override: - -```text -/etc/systemd/system/desktop-session.service.d/mobilegl.conf -[Service] -ExecStart= -ExecStart=/opt/mobilegl/bin/mobilegl-startup.sh compositor -Environment=ANLAND_SOCKET=/run/anland-mobilegl/display.sock -``` - -原 service 的 `User=swung0x48`、`PAMName=login` 保留。单纯 `droidspaces --user=... run` 不会建立正常 logind/user D-Bus session,Plasma mode 要借此 service 或等效正常登录。 - -实验 display daemon 在跑(不是 MobileGL renderer,只是原有协议 broker): - -```text -host socket: /data/local/tmp/anland-mobilegl/display.sock -binary: /data/adb/modules/anland-daemon/display_daemon -log: /data/local/tmp/anland-mobilegl/display-daemon.log -实验 PID: 13993(本轮实测仍在) -原 daemon PID: 2315,socket /data/local/tmp/display_daemon.sock(未停止) -``` - -实验 APK 启动时 `--es socket_path /data/local/tmp/anland-mobilegl/display.sock`;KWin 使用容器 `/run/anland-mobilegl/display.sock`,目录 bind 已生效。 - -### Android 安装与 root 授权(已解决) - -原有 `com.anland.consumer`(uid10253)、`com.anlandnext`、`com.droidspaces.app` 未覆盖。 - -**KernelSU root grant 已解决,不再是阻塞项**: - -- `com.anland.consumer.mobilegl` 重装后 uid 变成 **10270**(`u0_a270`);KernelSU manager 里原来的 10269 是过期条目,所以一直拨不动开关。 -- 处理办法:`force-stop` manager 让它重新枚举应用,再拨开关授权,成功。allowlist 现在有该包的有效条目。 -- 早先关于 `ksud profile` 没有 grant 子命令、`GET/SET_APP_PROFILE` ioctl 仅 manager UID 可调的调研结论仍然成立,但**不再需要自己写 profile mutation helper**。临时源仍在 `C:\Users\Swung0x48\AppData\Local\Temp\anland-mobilegl-device`(对应 upstream `4d396fda53e510d75f2608a184df56ccd33aac9c`),只作参考。 - -实验 APK 当前状态(本轮实测): - -- 装的是 **`5a0f13fd` 构建**:`app-plain-debug.apk`,7,702,188 B,sha256 `26d70bac8dd3ec1ef5f7bb79eb063ae5bbaa3b21bddc0ff60256e4f4e0f5384b`。 -- root helper 探测成功(不再跳 Settings);`:mobilegl` worker 进程内 server 在 abstract socket **`@anland-mobilegl` 上 LISTEN**;server window attached 2560x1412。 -- 已观察到 **6 个真实 client session 完成**(server log 最后一条 `in-process unix session #6 pid=13811 reaped exit=0 sessionsFaulted=0`,设备本地 08:28)。 -- server 日志在 `/data/data/com.anland.consumer.mobilegl/files/mobilegl-server.server.log`(注意 `.server.log` 后缀;`mobilegl-server.log` 不存在)。这份日志里有完整的 GL 能力探测输出,是查 KWin 失败原因的第一现场。 -- **app 必须保持前台**:回 HOME 会让 Surface detach、worker 停止。 - -Android 全局 Enforcing,但已安装 Droidspaces module 的 `sepolicy.rule` 包含 `permissive droidspacesd`。这不是本任务设置的。该 module 已有双向 fd use 与部分 unix_stream read/write 规则。**容器 → APK abstract socket、SCM_RIGHTS/memfd 的真实 AVC 尚未采集**(KWin 已能连上并建 context,说明现有规则放行到了这一步)。不要先全局 `setenforce 0`;按下面「推荐续接顺序」精确实测再加规则。 - -## 构建产物、日志、复用方法 - -### WSL - -发行版名称 **`archlinux`**,16CPU、约14GiB RAM。 - -```powershell -wsl -d archlinux -- bash -lc '...' -``` - -Linux NDK `/root/builds/android-ndk-r27c`(27.2.12479018),Linux Android SDK `/root/anland-mgl-sdk`,JDK17 task-specific 使用(默认 JDK25 未改)。 - -Windows Gradle 在 Zulu21 和 Android Studio jbr 都因 `PipeImpl/WEPollSelector` AF_UNIX “Invalid argument” 在 daemon 前失败,属于本机 Java 环境。**已用 WSL JDK17 成功构建,不要再次浪费时间试 Windows flags。** - -WSL 直接联网曾超时,Windows 下载可用。task-local CONNECT relay: - -```text -脚本: C:\Users\Swung0x48\repos\anland-legacy-mobilegl-unified\build\windows_build_proxy.py -Windows listener: 127.0.0.1:18081 -Python PID at snapshot: 29096 -exec session originally: 75964 -``` - -Gradle/sdkmanager 配置 HTTP/HTTPS proxy `127.0.0.1:18081`,没有改变主机全局设置。复用前查进程;任务结束可停止这个明确的 helper。 - -### Android MobileGL - -```text -build: /root/anland-mgl-build/android -configure log: /root/anland-mgl-configure.log -build log: /root/anland-mgl-android-build.log -arm64-v8a / android-26 / c++_static / Release / split ON -targets: MobileGL MobileGLServer -``` - -493 actions 构建成功。stripped `libMobileGL.so` 约17.6MB(unstripped约329MB)。8 个 embedded ABI exports 经 dynamic nm 验证。 - -anland 打包 dist: - -```text -C:\Users\Swung0x48\repos\FoldCraftLauncher\MobileGL\build\anland-dist -/mnt/c/Users/Swung0x48/repos/FoldCraftLauncher/MobileGL/build/anland-dist -``` - -dist 目录(`build/anland-dist`)当前只有 `libMobileGL.so`(17,789,480 B,2026-10-01 20:22 Cuiaba 产出)与 `EmbeddedServer.h`;`libMobileGLServer.so` 已被 20:28 的 clean-dist 脚本清掉。**这个 dist 是 APK 打包用的,二进制实测带 `5a0f13fd` 的 stamp(已装 APK 里那份 libMobileGL.so 也是同一个 17,789,480 B、同一个 stamp)**。但它是"某会话在当时的工作区上构建"的产物,目录本身不能证明源状态;重打 APK 前仍先确认 dist 与目标 commit 一致。 - -另一处 Android 构建(`/root/anland-mgl-build/android`)的 generated/MGGitHash.h 历史上记录过 stamp `32c79f41350186274b6d545083656d564c0ea80f`、native client stamp `a5b463a5`——那是旧构建。**无论哪一份,build 时都可能包含未提交源码,stamp 不能证明源状态**:两端最终要用同一个确认 commit、显式 `-DMOBILEGL_BUILD_STAMP=` 重新构建,并验证 wireFingerprint,不要忽略 build drift。 - -一次 WSL shell quote 失误曾留下 `/libMobileGL.so`、`/libMobileGLServer.so`、`/EmbeddedServer.h` 任务残留,正确定向产物已复制到 dist。若清理只清这些明确文件,不广泛清理 `/`。 - -### Android anland APK - -```text -C:\Users\Swung0x48\repos\anland-legacy-mobilegl-unified\consumers\anland_v5\android_consumer -artifact: app\build\outputs\apk\plain\debug\app-plain-debug.apk -``` - -当前 artifact 就是**已装到设备的 `5a0f13fd` 构建**:2026-10-01(Cuiaba)产出,**7,702,188 bytes**,SHA256 `26d70bac8dd3ec1ef5f7bb79eb063ae5bbaa3b21bddc0ff60256e4f4e0f5384b`。它是「统一 embedded server」版本,**不含 `MobileGLAppsService`**。 - -更早那版 7,381,976 bytes / SHA256 `CD15C203...7772`(含被否定路径,11 suites / 98 tests 全绿)已作废,只作历史参考。**JVM 单元测试只是逻辑测试,不能验证统一 server,更不能验证 GPU。** - -可复用构建脚本及日志: - -```powershell -wsl.exe -d archlinux -- bash /mnt/c/Users/Swung0x48/repos/anland-legacy-mobilegl-unified/build/build_bundled_apk.sh -``` - -脚本固定 JDK17/SDK/proxy/gitpaths,调用 GradleWrapperMain。WSL 日志 `/root/.gradle/daemon/8.11.1/daemon-256044.out.log`,HTML 报告 `app/build/reports/tests/testPlainDebugUnitTest/index.html`。 - -生命周期:`33e496c` 的 `onDestroy` 已经先 join serve thread 再拆 display,堵住了上一版的 shutdown 竞态;**detach/uninstall 的 3 秒超时返回值处理与 load/install 部分失败的清理仍未真机验证。** - -Gradle properties 是:`-PmobileglDist=...`、`-PanlandNdkVersion=27.2.12479018`、`-PmobileglApplicationId=com.anland.consumer.mobilegl`。**不是 `anlandApplicationId`**。 - -```sh -MOBILEGL_DIST=/mnt/c/Users/Swung0x48/repos/FoldCraftLauncher/MobileGL/build/anland-dist \ -./gradlew :app:assemblePlainDebug :app:testPlainDebugUnitTest \ - -PanlandNdkVersion=27.2.12479018 \ - -PmobileglApplicationId=com.anland.consumer.mobilegl -``` - -WSL 读 Windows worktree `.git` 文件的 `C:/...` path 有问题。Gradle 版本追踪用明确 `GIT_DIR`/`GIT_WORK_TREE` 指向 `/mnt/c/...`,不要把运行成功但版本为 unknown 的结果当最终可追溯产物。 - -### 容器 client - -```text -/root/mobilegl-unified-src # tar 同步的源码树,不是 git checkout -/root/mobilegl-unified-build -/root/anland-build-client.sh # 配置 + 构建 + 安装 -/root/mobilegl-unified-client-build.log -``` - -GCC Release split ON、LTO OFF、`ninja -j4`。当前安装: - -```text -/opt/mobilegl/lib/libMobileGL.so # 22,170,048 B,设备本地 2026-10-02 08:36 -/opt/mobilegl/lib/libEGL.so(.1) # aliases -/opt/mobilegl/lib/libGL.so(.1) # aliases -/opt/mobilegl/share/glvnd/egl_vendor.d/50_mobilegl.json -``` - -**build stamp 与内容不一致(已知、非阻塞)**:`MGGitHash.h` 里 `MOBILEGL_BUILD_STAMP_VALUE=ff212061...`,但源码树内容已经包含 `5a0f13fd`(`BackendObject_DirectGLES.cpp` 有 ARB 扩展串、`DirectGLES.h` 有 `ShareGroupKey`)。原因是 `/root/anland-build-client.sh` 不传 `-DMOBILEGL_BUILD_STAMP`,而 `CMakeCache.txt` 里留着上一次的 `ff212061`。wire fingerprint 兼容,client log 只提示 "compatible wire with different build"。**下次重建要显式 `-DMOBILEGL_BUILD_STAMP=<实际构建 commit>`。** - -**警告:容器源码树的 glslang 曾被覆盖过,重建前先验证 `External/`。** `build/anland-overlay-glslang.sh` 把容器内 `3rdparty/glslang` 重置成「记录版 gitlink `d89cf443` 的干净拷贝 **+ 保留 `External/`**」;`External/`(嵌套的 SPIRV-Tools / googletest 检出)不在那个 tar 里,而 MobileGL 顶层 `CMakeLists.txt` 无条件链 `SPIRV-Tools-opt`/`SPIRV-Tools`(`CMakeLists.txt:734`),External 一旦被删,配置/构建就失败。某会话确实制造过这个状态;脚本已改为保留 External,本轮实测容器树 `External/spirv-tools`(28 项,含 `source/`、`include/`)与 `External/googletest` 在位,并在 08:36 成功做了一次增量重链。**续接时仍先 `ls /root/mobilegl-unified-src/3rdparty/glslang/External` 确认;要干净重建就清掉旧 `.o`/构建目录再配。** 注意 overlay 用的是记录版 `d89cf443`,与 Windows 工作区里用户 dirty glslang(`6f125987`)内容不同,两端 glslang 版本有差异。 - -源码树是 tar 同步(没有 `.git`),源码更新需要手动同步;此前 tar 过宽排除 `build`/`test` 曾漏 xxHash/build/cmake 与 spirv-tools/test,已经补齐。 - -诊断程序 `/opt/mobilegl/bin/anland-egl-smoke`(另有 `anland-egl-win-smoke`)已编译,source 在 clone `/root/anland-egl-smoke.c` 和 Windows MobileGL `build/anland-egl-smoke.c`。测试 EGL pbuffer、GL renderer、clear/readback 预期 RGBA=64,128,191,255。**尚未记录到运行通过,不能算通过。** - -### 容器 KWin - -```text -source/build: /root/mobilegl-build/kwin-6.7.4/build -log: /root/mobilegl-build/kwin-build.log -installed binary: /opt/mobilegl/kwin/bin/kwin_wayland -installed library: /opt/mobilegl/kwin/lib/libkwin.so* -helper: /opt/mobilegl/bin/mobilegl-startup.sh -``` - -full build 1358 actions 通过;uid1000 `--version` 为6.7.4,ldd 验证加载 `/opt/mobilegl/kwin/lib/libkwin.so.6`。实际 build lib 在 **`build/bin`**,不是 `build/lib`。 - -执行位曾因 Windows re-tar 丢失:上游 `src/plugins/strip-effect-metadata.py` 已恢复755;不是 C++失败。 - -KWin 使用**系统 GLVND + vendor JSON**。只把 rebuilt libkwin目录放 LD_LIBRARY_PATH;不要把 `/opt/mobilegl/lib` 放入 KWin LD_LIBRARY_PATH,抢走 GLVND EGL库。glxinfo 的直接 libGL facade 可单独 scope `/opt/mobilegl/lib`。 - -### Host tests - -```text -/root/anland-mgl-build/host -/root/anland-mgl-build/host-build.log -/root/anland-mgl-build/host-server-tests.log -/root/anland-mgl-build/test-embedding -/root/anland-mgl-build/test-embedding.log -``` - -20 项测试通过:14 display/ABI、2 Unix pool、3 inprocess、1 parent-death。验证真实 SHM connections/fork/lifetime,**不是实际 GPU 渲染测试**。 - -CMake test 子目录早于 server target,显式构建 `MobileGLServer`,不能依赖 test target自动构建它。host clang22构建已完成,无 active cmake/ninja/test/server任务。 - -Host configure 使用本地GoogleTest:`-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=/root/mobilegl-build/_deps/googletest-src`,避免网络clone。已经通过的suite复跑: - -```sh -ctest --test-dir /root/anland-mgl-build/host \ - -R '^(EmbeddedServerTest|ServerDisplayTest|UnixSupervisor|InProcessServer|SupervisorChildren)\.' \ - --output-on-failure -``` - -协议测试复跑命令: - -```powershell -wsl -d archlinux -- bash /mnt/c/Users/Swung0x48/repos/anland-legacy-mobilegl-unified/tests/run_mobilegl_surface_exchange.sh -``` - -## 命令与续接注意事项 - -Android/client env(统一 server 只有 `@anland-mobilegl` 一个 endpoint,compositor 与所有容器 client 都指向它): - -```sh -MOBILEGL_TRANSPORT=spawn -MOBILEGL_IPC_DATA=shm -MOBILEGL_IPC_CONTROL=unix:@anland-mobilegl -# compositor: MOBILEGL_IPC_SURFACE=server -# applications: MOBILEGL_IPC_SURFACE=offscreen(或清除) -# worker 侧 backend: MOBILEGL_BACKEND_TYPE=DirectGLES -``` - -`MOBILEGL_TRANSPORT=shm` **无效,会落回 monolith**;`MOBILEGL_IPC_SURFACE=client` 也无效。Listen/serve endpoint是 `@name`,client必须 `unix:@name`。 - -PowerShell 的 adb compound shell command 要保持 su quoting。以下错误写法只让第一条命令成为root:`adb shell su -c 'id; id -Z'`(adb flatten后分号跑到外层)。正确例子: - -```powershell -adb -s HA27Q3LQ shell "su -c 'id; id -Z'" -``` - -更深的嵌套用 literal here-string或 push script,别使用 bash 的 `\"` 去转义 PowerShell字符串: - -```powershell -$mglCmd = @' -su -c '/data/local/Droidspaces/bin/droidspaces --name=arch-kde-mgl run bash -lc "ls -l /run/display.sock; id"' -'@ -adb -s HA27Q3LQ shell $mglCmd -``` - -## 并发警告 - -**存在并行会话同时操作同一台设备、同一个容器和这个 Windows 仓库**,已经互相覆盖过 `build/anland-dist` 与容器源码 tar。动手前的检查清单: - -1. 设备上是否有 app / KWin 在跑:`adb -s HA27Q3LQ shell "su -c 'ps -A -o PID,USER,ARGS | grep -iE \"anland|kwin|display_daemon\"'"`;容器内 `systemctl is-active desktop-session.service`、`pgrep -a kwin`。 -2. 仓库 HEAD 是否已经变过:MobileGL 与 anland 两个工作区各 `git rev-parse HEAD` 一次,并看有没有新的未提交改动。 -3. 容器里是否正在构建:`pgrep -af 'ninja|cmake'`,以及 `/root/mobilegl-unified-client-build.log` 的 mtime。 -4. 拿不准就先只读、不动文件;确实要覆盖前先和用户确认,别把别人 in-flight 的改动当残留删掉。 - -## 推荐续接顺序 - -1. **先确认没有并行会话在动设备/容器**(见上一节)。读本文、`design/11-state-ownership.md`、用户原话与 [`kgsl-as-server.md`](kgsl-as-server.md);检查两个本地 workspace,保护用户原有 dirty submodules。不要重复 clone 或基线 build。 -2. **修容器源码树的可构建性**(本轮若不重建 client 可跳过):确认 `3rdparty/glslang/External/{spirv-tools,googletest}` 在位,必要时重新同步完整源码树;清旧 `.o`/构建目录后重建,并显式 `-DMOBILEGL_BUILD_STAMP=<实际 commit>`;重建后核对 `MGGitHash.h` 的 stamp 与 wire fingerprint。 -3. **KWin 验收**:`systemctl reset-failed desktop-session.service` 后 `start`(app 保持前台、display daemon 在跑),看 KWin 是否真上屏——渲染器字符串(`Renderer:` 应为 MobileGL 而不是 freedreno/Mesa)、GPU 像素/截图、帧节奏。同时取三处证据:容器内 `journalctl -u desktop-session.service`、server 侧 `/data/data/com.anland.consumer.mobilegl/files/mobilegl-server.server.log`、Android `logcat`。上一轮 `kwin_wayland` 是 SEGV(status=11),先从 server log 尾部与 KWin 自身输出里找第一个失败点,不要靠猜。 -4. **Enforcing 下采真实 AVC**:容器 → APK abstract socket、`SCM_RIGHTS`/memfd 路径,按实际 denial 精确加规则,**不要 `setenforce 0`**;确认 renderer 仍在 anland 自己的进程/UID。 -5. Konsole、eglinfo、glxinfo、Plasma 依次验收;每项都要真实像素/渲染证据,不能只看进程存在或版本字符串。 -6. 成果 commit/push(MobileGL `feat/disaggregated` + anland `legacy-mobilegl-unified`),并把这篇文章更新成最终可复现文档。 - -目前没有合入/部署/最终验收请求被拒绝的自动审批。用户已授权实验clone、构建安装、修改、commit/push;除新的明确破坏性动作或确实缺失信息,不要重新索要相同授权。 - -## 更新记录 - -- **2026-10-01(America/Cuiaba)**:由 agent 刷新为 **P14 落地后**的状态。改动:先读节改为「架构已落地 / 真机验收未完成」;MobileGL 节 HEAD 更新到 `5a0f13fd` 并补 P14 各 commit 与已知欠债;anland 节补 `2ffa1f5`/`33e496c`,把原「未提交草稿」节标记为已落地/已移除;设备节记录 KernelSU grant 已解决(重装后 uid 10270)、clone 重启后目录 bind 生效、APK 已装 `5a0f13fd` 构建(7,702,188 B)且 worker 在 `@anland-mobilegl` LISTEN;容器 client 节记录 22,170,048 B 的 lib 与落后一个 commit 的 stamp、glslang overlay 的 `External/` 注意事项;新增并发警告并重写续接顺序。 +| `1fb419a` | mobilegl-startup.sh:默认 socket 指向实验 daemon(/etc/environment 的 pam_env 会盖掉 systemd drop-in);plasma 模式改走 kwin_wayland_wrapper(BusName=org.kde.KWinWrapper 只在 wrapper 注册,直跑 kwin 90 秒被 systemd 杀);KWin 直写 surface 不加 FlipY(此前画面上下颠倒);sync-build-kwin.sh 容器侧 canonical 同步/增量构建/原子安装管线 | +| `118ae52` | `MOBILEGL_BACKEND_TYPE=DirectGLES`(Espryt 只是别名,写错会被 pinned-backend 拒);eglChooseConfig 收 count≥1(MobileGL 默认有两份配置);compositor client 日志落文件 | +| `da03d3b`/`35699f3` | consumer flap 时不再停 compositor;APK consumer 侧不再因 Surface 生命周期事件重启 native 管线 | +| `33e496c` | 统一内嵌 server 宿主:`:mobilegl` Service 单 endpoint `@anland-mobilegl`;移除被否定的 MobileGLAppsService/按应用 fork 路径 | +| `2ffa1f5` | KWin anland backend 的 `ANLAND_MOBILEGL=1` 路径(跳过 GPU/DRM probing,server-owned window) | +| `6e1fa65` | 无 fd 的 MobileGL Surface 显示协议(DATA_MSG_MOBILEGL_SURFACE=201) | + +## 真机已验证(TB321FU,HA27Q3LQ) + +以实际画面/输出为准: + +- **链路绿屏**:client clear → server(Adreno 750 厂商驱动)→ Android Surface → 屏幕,截图可见(win-smoke,60 帧 swap 无错误)。 +- **egl-smoke**:pbuffer + clear + readback RGBA 精确(64,128,191,255)。 +- **eglinfo**:GLVND vendor JSON 正常,EGL 1.5,两组配置。 +- **glxinfo**:GLX 1.4,Espryt (Adreno 750),GL 4.6 core,exit 0(注意:GLX 只到查询,无 X11 上屏)。 +- **KWin 6.7.4**:合成器存活并呈现(黑桌面+光标可见)。 +- **Konsole**:窗口完整正确渲染(标题栏、工具栏、shell 提示符,方向正确)。 +- **多 session**:两个 win-smoke 连续运行同一窗口均 exit 0(stale surface 修复生效)。 + +## 未完成 / 已知问题(按优先级) + +1. **Plasma 完整会话未成**。startplasma 起来约 7 秒后退出导致全session拆除;`plasma-ksmserver.service` 曾 core-dump;wrapper 生命周期(wrapper 退出时 cgroup 清理带走 kwin)与 ksmserver 的 wl_display 时序没查完。plasmashell 曾进入到 shell-integration 探测阶段(说明 wl_display 已通)。下一步:抓 ksmserver 的 core/日志,确认 kwin 的 7 秒退出到底是被谁拖死。 +2. **光标巨大化**:KWin 光标 sprite 按错误缩放渲染(48px 画成几百 px)。查 output scale 与 cursor layer 的 buffer scale。 +3. **CPU blit 禁令**:用户明确要求最终形态上屏路径无 CPU blit——当前 Wayland wl_shm client 走 readback(`32c79f41`),只是过渡。目标:应用窗口内容也由 server 在 GPU 侧直接给 KWin 合成(server 侧 GPU buffer → 同进程共享给 compositor 的 share group → 合成上屏),不经过 CPU 回读。设计要进 11-state-ownership.md 的后续切片。 +4. **GLX 无上屏**:X drawable 仅远端 pbuffer 查询,无 X11 readback/present。 +5. **resize/surface loss**:wl_egl_window resize 不跟随;surface loss 仍结束活动 session(无损挂起未实现)。 +6. **AVC 审计未做**:全程在 Enforcing 下跑通(容器侧有 droidspacesd 的既有 permissive 规则),SCM_RIGHTS/memfd 的精确 AVC 采集没做。 +7. **wire stamp 漂移**:容器构建的 `MGGitHash.h` 因 tar 无 .git 而陈旧(日志 WARN "compatible wire with different build");两端应同 commit + 显式 `-DMOBILEGL_BUILD_STAMP` 重建。 + +## 设备与环境要点(复现用) + +- 设备 HA27Q3LQ(Y700 TB321FU),clone `arch-kde-mgl`(arch-kde 保持不动),容器内 `/opt/mobilegl/{lib,bin,kwin}`。 +- **KernelSU grant**:`ksud profile` 无直授命令;管理器专属 ioctl 的解法:root 下 setuid 到 manager UID(10255)→ `reboot(0xDEADBEEF,0xCAFEBABE)` 拿 `[ksu_driver]` fd → `SET_APP_PROFILE`。helper 源码在 anland worktree `build/ksu_grant.c`(gitignored,按需从本说明重建)。已持久化进 `/data/adb/ksu/.allowlist`。 +- 实验 daemon:独立实例 `display_daemon /data/local/tmp/anland-mobilegl/display.sock`(原 daemon 不动);socket 需 666、目录 777(容器内 uid1000 要连)。 +- clone config 的目录 bind:`/data/local/tmp/display_daemon.sock:/run/display.sock`、`/data/local/tmp/anland-mobilegl:/run/anland-mobilegl`。 +- KWin 启动:`desktop-session.service` + drop-in ExecStart=mobilegl-startup.sh compositor|plasma;Qt 日志默认只出 warning(info 要 QT_LOGGING_RULES)。 +- 构建:Android dist 用 WSL(`/root/anland-mgl-build/android`,NDK r27c,stamp 显式指定);APK 用 anland worktree 的 `build/build_bundled_apk.sh`(WSL JDK17 + task 代理);容器 client 用 `build/anland-sync-source.sh build`(注意 `tools/trace_replay` 789MB 必须排除、git archive 的旧 mtime 会骗过 ninja——overlay 后 touch)。 +- **3rdparty/glslang 是用户的 dirty submodule,禁止动**;它缺 5 个补丁会让 shader 编译在容器里 SEGV——容器构建用记录的 gitlink `d89cf443` 覆盖(`build/anland-overlay-glslang.sh`,保留 `External/`)。 +- 设备 30 秒无操作熄屏——熄屏可能 kill SurfaceView;长跑验证前 `svc power stayon true` 或调 screen_off_timeout。