# P6.5:1.21.11 主菜单 GLES 像素差异诊断 **状态:正式设备矩阵仍记 FAIL;独立主机负控已复现同位置缺失,强烈支持 trace 不满足目标 GPU 的 UBO 对齐要求;相同 APK 的设备本地对照仍待补。** 本记录不修改源码、trace fixture、golden 或 SSIM 阈值,也不把该失败归入既有 Vulkan 债务。 ## 正式失败与可比阳性 | 对照 | 结果 | SSIM / 阈值 | 用时 | 实际 arming | |---|---|---|---|---| | Linux client → Android/Adreno TCP+stream,credit=2 | FAIL,完成回放后图像不符 | 0.890161090 / 0.99 | 272.684 s | ARMED,无 lockstep/DISARMED | | WSL 同 ABI/驱动 TCP+stream loopback,credit=2 | PASS | 0.993474922 / 0.99 | 14.018 s | ARMED,无 lockstep/DISARMED | 两次目标 call 均为 `205347`,分辨率 `854×480`。正式失败的 `statusCode=6`、 `returncode=1`、`timeout_kind=null`,不是 watchdog 超时;其 mismatchPixels 为 79,246。 客户端和服务端没有 `Fatal{`、device loss、Refuse 或握手失败证据。客户端完成 38,884 次 reply wait; 服务端最后一帧计 11,551 draws。没有遗留进程。 目视检查可见背景、标题与文字仍在,主要缺失 Singleplayer、Multiplayer、Realms、Options、Quit 的灰色按钮底图,以及语言/右侧辅助功能按钮的图标或底图。loopback 中这些元素正常。 图中 Mali/Tensor 文字属于捕获的游戏画面,不能据此判定当前服务端 GPU;实际服务端日志为 Adreno。 正式 attempt: `/home/swung/p65-final-matrix-gles/DirectGLES/credit-2/minecraft-1.21.11-main-menu/attempt-a1c1263d9b814620b04ff4c8b8d6cbf1`。 原始结果、图像和角色日志位于其 `work/output/`,golden 副本位于 `artifacts/`。 loopback 证据索引为 `/home/swung/p65-mainmenu-loopback-control/control-summary.json`。 该索引记录主机 library SHA256 为 `25ca45d756bc5a4e5bd56ad4ce231e4ef89d45ba41131f389575cda0842cd6be`。 仅 loopback 阳性还不能单独区分 ABI/wire 与设备驱动差异。 ## 只读 trace 与源码证据 设备 `mobilegl.server.log:145` 及 `:358` 报告 `GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT: 32`(SSBO 为 64)。 trace 的 UBO offset 有 16 字节粒度,但未找到录制时 alignment 查询值;不能声称捕获日志明确报告 16。 `MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp:1553` 的 `ValidateBufferRangeOffsetAndSize` 查询当前 context 的 alignment;`:1580` 判断 offset 整除性, 不符合时记录 `InvalidValue` 并返回 false。`:1627` 在 `TouchBufferBindingPoint` 和修改绑定之前返回, 因此保留之前合法的 buffer/range。远端 client 采用远端能力,不能用 WSL 原生 GPU 的较小对齐要求解释该分支。 `MobileGL/MG_State/GLState/ErrorState/Error.cpp:20` 仅以 `MGLOG_D` 打印 GL error。 现有 INFO 日志没有逐次 `RecordError` 文本;下列“应被拒绝”是调用参数加现行代码得到的预测, 不是声称已有逐 call 动态错误日志。 完整只读 `apitrace dump` 中有 14,149 次 `glBindBufferRange`,没有 `glBindBuffersRange`。 其中 **3,878** 次满足 `target=GL_UNIFORM_BUFFER && offset % 32 != 0`,首 call `9683`, 末 call `205116`;全部发生在 program 158(2,808 次)或 program 15(1,070 次)。 program 158/15 的 uniform block 0 都映射到 UBO binding 0(call `6734`/`6661`)。 这两个 program 的顶点 shader 13(call `6651`)明确声明 `SpriteAnimationInfo`,内含 `ProjectionMatrix`、`SpriteMatrix`、`UPadding`、`VPadding`、`MipMapLevel`,并计算 `gl_Position = ProjectionMatrix * SpriteMatrix * vec4(positions[index], 0, 1)`。 program 158 的 fragment shader 157(call `6727`)通过 `textureLod(Sprite, texCoord0, MipMapLevel)` 填充 sprite;program 15 通过两个 sprite 采样做动画插值。由此可将不合对齐的绑定定位到 atlas 生成, 而非笼统归因于最终主菜单 draw。 与缺失按钮直接相关的调用链如下: | call | 事实 | |---|---| | `80415` | FBO 16 的 COLOR_ATTACHMENT0 设为 texture 868,后续 viewport 1024×1024 | | `80426`, `80428` | UBO buffer 44 / offset 0 / size 140,随后 atlas draw | | `80434`, `80436` | UBO buffer 44 / offset 144 / size 140(不满足 32),随后 atlas draw | | `80442`, `80444` | UBO buffer 44 / offset 288 / size 140,随后 atlas draw | | `205215`, `205221` | program 181 采样 texture 868,DrawElementsBaseVertex count 90 / basevertex 4 | | `205254`, `205262` | program 181 再采样 texture 868,count 12 / basevertex 68 | FBO 16 一共 607 次 atlas draw,其中 286 次前置 UBO bind 不满足 32 字节对齐; 该 FBO 最后一次不合对齐绑定为 `197198`(buffer 43 / offset 144 / size 144)。 最终 GUI program 181 使用的 UBO offset 是 0 或 160,本身满足 32。 它的 fragment shader 27 对 `texture(Sampler0, texCoord0)` 的零 alpha 执行 discard。 **获得独立负控支持的机制:** atlas 生成时,非法绑定保留旧 `SpriteMatrix`,新 sprite 仍写入旧区域,目标格留空或错误; 最终 GUI 从 texture 868 采样时因零 alpha 丢弃,从而出现按钮文字正常、底图/图标缺失。 下面的 blob/UV 对应进一步把缺失区域映射到具体 atlas draw。独立负控已复现主要视觉缺陷, 与设备失败图 SSIM 为 0.9987292763;这不声称两个驱动的全部像素完全一致。 只读 `apitrace dump --blobs --calls=80413,205170,205177` 把诊断副本写到 `/tmp/p65-mainmenu-blobs-ub31dw3f`。`80413` 为 buffer 44 的 69,264 字节初始化内容, 每个 `SpriteAnimationInfo` record 以 144 字节步进;`205170`/`205177` 分别是最终 buffer 39 中 15 个按钮底图 quad 和两个图标 quad 的顶点内容(每顶点 24 字节)。 将顶点 UV 乘 atlas 尺寸 1024,再与 UBO 内的 SpriteMatrix 比较,得到: | GUI 元素 / 最终采样像素区域 | buffer 44 record / atlas 矩形 | 生成调用与对齐 | |---|---|---| | 15 个底图 quad,包括三行主按钮及底部四个按钮切片;x=723…923, y=1…21 | record 279,矩形 (722, 0, 202, 22) | bind `82658` offset=40176,余数 16;draw `82660`,源 texture 1168 | | 语言图标;x=659…674, y=122…137 | record 125,矩形 (658, 121, 17, 17) | bind `81426` offset=18000,余数 16;draw `81428`,源 texture 1014 | | 右侧辅助功能图标;x=659…674, y=88…103 | record 148,矩形 (658, 87, 17, 17) | bind `81610` offset=21312 合法;draw `81612` 后紧跟 record 149 | record 149 原定矩形为 (658, 155, 17, 17),bind `81618` offset=21456 余数 16, draw `81620` 的源 texture 1038。如果该 bind 被拒绝,它沿用 record 148 的合法矩阵, 因而会覆盖右侧图标刚生成的位置。按钮背景被拒绝时沿用 record 278 的矩形 (376, 194, 22, 22), 语言图标则沿用 record 124 的矩形 (610, 287, 18, 18),都不是最终 GUI 预期采样的区域。 ## 独立控制与证据边界 原 trace 的同 ABI TCP loopback 已通过。独立代理根据 `/home/swung/p65-mainmenu-ubo32-rejected-calls.json` 的 3,878 个 call 编号, 仅在临时 trace 副本中移除这些本应被设备 client 拒绝的绑定,保留对应 draw,然后做主机 TCP 负控。 该主机结果复现了设备的按钮矩形、语言按钮和右侧图标缺失。 | 图像比较 | 全图 RGB SSIM | 对原 golden 的判断 | |---|---:|---| | 原 trace 主机 loopback vs 原 golden | 0.9934749217 | PASS | | 原 trace 设备 TCP vs 原 golden | 0.8901610899 | FAIL | | 临时删绑定 trace 主机 TCP vs 原 golden | 0.8892457298 | FAIL;仅诊断负控 | | 临时删绑定 trace 主机 TCP vs 原 trace 设备 TCP | 0.9987292763 | 比较两张失败图,不是 golden 验收 | 比较使用项目 `trace_replay_core.cpp` 的全图 RGB SSIM(C1=6.5025、C2=58.5225), 不 crop、忽略 alpha,且已校验原设备和原 loopback 的 runner 结果。四张图像的路径与 SHA256 均保存在 `/home/swung/p65-mainmenu-ubo32-diagnostic/comparison-summary.json`。 处理过程和逐 call 验证保存在 `/home/swung/p65-mainmenu-ubo32-diagnostic/preparation-summary.json`。 安装的 apitrace 14.0 不支持 `--exact`;实际使用显式保留 call 补集的 `trim --calls=@keep-calls.txt`,并核实该版本直接写出所选调用,没有增加依赖闭包。 随后完整 dump 两份 trace,逐项比较所有保留调用的顺序、thread、参数和每个 blob 的 SHA; 仅规范化 call 编号前缀和 blob 文件名,确认除名单中的 3,878 个绑定之外没有变化,headers 也一致。 原 trace 共 205,348 个 call,临时 trace 共 201,470 个,最后的 `eglSwapBuffers` 从 `205347` 重编号为 `201469`。临时 trace SHA256 为 `92e47f93ddcb7b5f3eaece05229fa8980e92adc5d24bbff3d6fe1c1b948ef6b4`。 同目录 `replay-summary.json` 记录负控用时 12.188 秒、returncode=6,实际 ARMED, 无 lockstep/DISARMED,双角色均为 TCP+stream 且 Fatal=0;结束后 supervisor 正常停止、 state 移除且端口释放。该负控使用与原 loopback 相同 SHA256 的主机库。 `apitrace-tool-evidence.json` 保留版本和 `--exact` 不支持的实际输出,`README.md` 汇总复现步骤。 原 trace 与 golden 在负控回放后重新计算 SHA256,确认均未改变。 这一负控、远端 alignment=32、frontend 拒绝路径以及精确 UV 对应共同强烈支持 **捕获 trace 对目标 GPU 的 UBO 对齐有效性问题**。它是独立于原设备 TCP 回放的机制复现, 不能把修改后的 trace 当作正式 GLES 阳性,也不能将正式 case 改记 PASS。 **相同 APK/后端的设备本地 inproc/spawn 原 trace 对照尚未执行**;需待正式设备矩阵释放设备后再补, 本记录没有将它写成已完成,也不把负控宣称为完全排除所有跨机器差异。 原 trace 保持不动,SHA256: `7abd871cce098e93dd15a5e799fdfe093d9c85784716b00fd97c94833b8b83af`。 原 golden SHA256:`ea8db1c1c1414d3b564d65b1559a708277417b06895900bc947644d2f658bb86`。 两条 `GL_STENCIL_INDEX8` NormalizePixelFormat ERROR 及 layered-blit 探测 warning 也见于通过的 1.21.4 startup/main-menu,因此不把它们单独作为此失败原因。历史 P4a 的 1.21.11 depth-sampler 失败有独立的 sampler refusal 证据,现日志没有同一 refusal;不能把旧原因直接套用到本次。