Files
orisys-app/docs/architecture/perf-report-windows-dev.json
ykcheungab 5cd92254e1 feat(studio-3d): 阶段 5 —— 发布保护(签名/可选加密/平台锁定)与性能长时报告
- What: 新增 `scene3d/protect.py`(封套 v2:Ed25519 签名 + 可选 AES-256-GCM,nonce/salt 每包唯一、
  元数据作 AAD、明文摘要进签名、KeyProvider 只问 get_content_key)与 `record/visualization3d.py`
  (session.json 的 visualization3d 块:写入版本化、读取时缺块=不支持);`package.py` 支持 v2
  (payload=plain 仅签名 / aes-gcm 签名+加密)并继续读 v1,新增解压比上限与"写入后自读回"校验;
  `contracts.py` 新增 PlatformLock/PlatformInfo/check_platform_lock、Sealing、platform_mismatch 诊断码;
  `Scene3DView` 在装配前查 OS/架构、拿到 capabilities 后查图形半区,违反即撤下场景并在面板显示原因;
  工具 `compile_scene.py` 新增 --sign-key/--write-verify-key/--encrypt-key-id/--key-file/--lock-from-probe,
  `validate_scene.py` 新增 --key-file/--key-id/--verify-key/--require-signature(发布门禁),
  新增 `bench_scene.py`(p50/p95 分阶段 + --cpu-only + --vertices N + --package)与
  `soak_scene.py`(RSS 四分位速率 + 队列上界 + 抽稀采样);两份报告落到 docs/architecture/。
- Why: 规格 §5.2 的保护目标是"提高复制成本"而不是"防提取",所以要的是签名(来源)+ 可选认证加密(机密性)
  + 密钥隔离,而不是自制加密或把 ZIP 当加密;§12 的"包保护/性能/长时"三行需要可复现的数字与判定标准,
  而不是一次性的观感。平台锁定还必须只降级三维:锁定不通过时二维照常。
- Verification: shared 全量通过(新增 test_scene3d_protect.py 35 项:封套往返/篡改/密钥/锁定/记录块/
  compile→validate 端到端发布流程;test_scene3d_package.py 20 项含 v1↔v2 兼容);apps/studio 全量通过
  (test_scene3d_view.py 30 项含锁定拒绝与 session.json 块);ui_check 566 项 0 失败;locale_check 0 缺失;
  真实资产端到端:1032 顶点编译出 signed+sealed+locked 包(56 927 B 密文),validate --require-signature 退出 0,
  改一个字节退出 2;性能报告(perf-report-windows-dev.json):1032 顶点 compose p50 0.50 ms(30 fps 上限内),
  20022 顶点/39480 三角形 compose p50 6.15 ms(约 33 ms 帧的 18%),采集线程 submit p95 < 0.4 ms(合成器被人为放慢到 50 ms);
  长时报告(soak-report-windows-dev.json):无窗口 10 min/60 fps 尾段 0.354 MB/min,GUI 4 min/30 fps 尾段 0.809 MB/min,
  队列上界(mailbox=1、单飞行合成)两者均成立。过程中修掉:GUI 进程里的合成器计时被 GIL/vsync 污染(98.8 ms vs 真值 6.1 ms)、
  跨线程读 last_stats 撕裂、key id 无 provider 时会静默写明文包、soak 工具自身记时间列表会无界增长、RSS 读取失败伪装成零增长、
  平台锁定不是终态诊断。
- Not in this change: ARM 板卡的能力报告与 2 小时门禁运行(按用户要求暂缓;命令与判定标准已写入阶段 5 文档);
  GPU 显存随时间的测量(无可移植 API,报告说明渲染器缓冲估计恒定且更新路径不新建 GPU 对象);
  `cryptography` 在此环境无法联网安装,本机验收用的是本机已有的 cp310 构建(pyproject 里已声明为可选 extra
  `protected-assets`)。
2026-09-14 16:44:33 +08:00

463 lines
14 KiB
JSON

{
"kind": "orisys-scene-perf-report",
"version": 1,
"stage": "docs/architecture/studio-3d-stages.md 阶段 5(性能与长时)",
"host": {
"os": "Windows 10",
"os_family": "windows",
"machine": "amd64",
"python": "3.10.19",
"cpu": "AMD64 Family 26 Model 68 Stepping 0, AuthenticAMD",
"note": "developer workstation; the ARM boards are the second platform and have not been probed yet (user asked to defer ARM work)"
},
"method": {
"tools": "tools/scene_assets/bench_scene.py",
"gui_mode": "assembled Scene3DView (widget + camera + renderer + composer + coordinator) in a real window; frames are submitted through the same submit_packet() the acquisition thread calls, ticked and painted",
"cpu_mode": "--cpu-only: the same composer with no window in the process",
"percentiles": "p50/p95 of the per-frame measurements; latency is submit-to-paint and therefore includes the frame-rate limiter",
"compose_split": "compose_sample and compose_normals are the composer's own timers for the two halves of the work",
"why_two_modes": "In the GUI run the worker thread competes with the GUI thread for the GIL and for a vsync-throttled swap, so the composer's wall-clock timers pick up waiting that is not arithmetic: the same 20k-vertex scene reports compose p50 98.8 ms in the GUI run and 6.1 ms with --cpu-only. Per-frame CPU cost is therefore quoted from --cpu-only; upload, paint and latency are GUI-thread measurements and are unaffected.",
"artificial_delay": "the submit budget is measured with a deliberate 50 ms composer delay, because that is the case where a display could damage the acquisition loop if the mailbox were wrong"
},
"budget_from_design_document": {
"sensing_surface": "about 20k vertices / 40k triangles",
"housing": "about 10k-20k triangles",
"display_rate": "30 fps suggested; 15 fps allowed on a low-end board, acquisition and recording rates must not drop",
"dynamic_bytes_per_frame": "about 0.64 MB at 20k vertices (position/normal/scalar/valid, 32 bytes)"
},
"runs": {
"poc_gui": {
"profile_requested": "desktop33",
"frames_requested": 400,
"viewport_px": [
900,
700
],
"slow_compose_ms": 50.0,
"max_fps_requested": 30,
"context": {
"profile_used": "desktop33",
"vendor": "NVIDIA Corporation",
"renderer": "NVIDIA GeForce RTX 4080 SUPER/PCIe/SSE2",
"gl_version": "3.3.0 NVIDIA 610.88",
"glsl_version": "3.30 NVIDIA via Cg compiler",
"software_renderer": false,
"max_texture_size": 32768,
"device_pixel_ratio": 1.0,
"qt_platform": "windows"
},
"mesh": {
"sensing_vertices": 9,
"sensing_triangles": 8,
"housing_triangles": 12,
"field_domain_hw": [
3,
3
],
"estimated_gpu_bytes": 2096
},
"timings_ms": {
"submit": {
"p50": 0.0662,
"p95": 0.1042,
"max": 0.2491,
"mean": 0.0678,
"n": 400
},
"compose": {
"p50": 0.284,
"p95": 16.0868,
"max": 16.9735,
"mean": 5.4679,
"n": 400
},
"upload": {
"p50": 0.1716,
"p95": 0.3097,
"max": 0.3499,
"mean": 0.1821,
"n": 400
},
"paint": {
"p50": 0.6612,
"p95": 0.9826,
"max": 22.5669,
"mean": 0.7215,
"n": 401
},
"latency": {
"p50": 83.3294,
"p95": 100.2022,
"max": 101.038,
"mean": 86.367,
"n": 95
}
},
"display": {
"superseded_display_frames": 304,
"stale_frames": 0,
"invalid_frames": 0,
"max_inflight_composes": 1,
"mailbox_capacity": 1,
"frames_presented": 94
},
"crypto_backend": "49.0.0",
"verdict": "ok",
"exit_code": 0
},
"poc_cpu": {
"mode": "cpu",
"frames_requested": 500,
"scene": "synthetic-poc",
"mesh": {
"sensing_vertices": 9,
"sensing_triangles": 8,
"field_domain_hw": [
3,
3
]
},
"timings_ms": {
"compose": {
"p50": 0.047,
"p95": 0.0488,
"max": 0.0809,
"mean": 0.0473,
"n": 500
},
"compose_sample": {
"p50": 0.0129,
"p95": 0.0132,
"max": 0.0395,
"mean": 0.013,
"n": 500
},
"compose_normals": {
"p50": 0.0256,
"p95": 0.0265,
"max": 0.0448,
"mean": 0.0257,
"n": 500
},
"compose_wall": {
"p50": 0.0475,
"p95": 0.0494,
"max": 0.0816,
"mean": 0.0479,
"n": 500
}
},
"verdict": "ok",
"exit_code": 0
},
"real_asset_gui": {
"profile_requested": "desktop33",
"frames_requested": 300,
"viewport_px": [
900,
700
],
"slow_compose_ms": 0.0,
"max_fps_requested": 30,
"scene": "package:D:\\orisys_ws\\.repro\\build\\finger-display-1.0.0.oryscene",
"context": {
"profile_used": "desktop33",
"vendor": "NVIDIA Corporation",
"renderer": "NVIDIA GeForce RTX 4080 SUPER/PCIe/SSE2",
"gl_version": "3.3.0 NVIDIA 610.88",
"glsl_version": "3.30 NVIDIA via Cg compiler",
"software_renderer": false,
"max_texture_size": 32768,
"device_pixel_ratio": 1.0,
"qt_platform": "windows"
},
"mesh": {
"sensing_vertices": 1032,
"sensing_triangles": 1974,
"housing_triangles": 0,
"field_domain_hw": [
300,
280
],
"estimated_gpu_bytes": 90760
},
"timings_ms": {
"submit": {
"p50": 0.1666,
"p95": 0.2838,
"max": 0.4235,
"mean": 0.1796,
"n": 300
},
"compose": {
"p50": 16.0998,
"p95": 16.7335,
"max": 32.012,
"mean": 15.5992,
"n": 299
},
"compose_sample": {
"p50": 0.6744,
"p95": 15.6332,
"max": 17.8982,
"mean": 7.4582,
"n": 299
},
"compose_normals": {
"p50": 0.7236,
"p95": 1.2651,
"max": 16.4823,
"mean": 1.4222,
"n": 299
},
"compose_wall": {
"p50": 16.1051,
"p95": 16.7427,
"max": 32.0177,
"mean": 15.6058,
"n": 299
},
"upload": {
"p50": 0.2501,
"p95": 0.4001,
"max": 2.38,
"mean": 0.2757,
"n": 300
},
"paint": {
"p50": 1.109,
"p95": 1.6249,
"max": 23.3764,
"mean": 1.2774,
"n": 301
},
"latency": {
"p50": 33.0697,
"p95": 33.7423,
"max": 112.6438,
"mean": 33.3061,
"n": 299
}
},
"compose_count": 299,
"compose_note": "compose/compose_sample/compose_normals are the composer's own timers, collected on the worker thread; compose_wall adds the requested delay. latency is submit-to-paint and therefore includes the frame-rate limiter.",
"display": {
"superseded_display_frames": 1,
"stale_frames": 0,
"invalid_frames": 0,
"max_inflight_composes": 1,
"mailbox_capacity": 1,
"frames_presented": 298
},
"crypto_backend": "49.0.0",
"verdict": "ok",
"exit_code": 0
},
"real_asset_cpu": {
"mode": "cpu",
"frames_requested": 500,
"scene": "package:D:\\orisys_ws\\.repro\\build\\finger-display-1.0.0.oryscene",
"mesh": {
"sensing_vertices": 1032,
"sensing_triangles": 1974,
"field_domain_hw": [
300,
280
]
},
"timings_ms": {
"compose": {
"p50": 0.4975,
"p95": 0.748,
"max": 0.9371,
"mean": 0.5416,
"n": 500
},
"compose_sample": {
"p50": 0.1531,
"p95": 0.2425,
"max": 0.3929,
"mean": 0.1672,
"n": 500
},
"compose_normals": {
"p50": 0.3049,
"p95": 0.4742,
"max": 0.6302,
"mean": 0.3376,
"n": 500
},
"compose_wall": {
"p50": 0.4992,
"p95": 0.7509,
"max": 0.9399,
"mean": 0.5436,
"n": 500
}
},
"verdict": "ok",
"exit_code": 0
},
"grid_20k_gui": {
"profile_requested": "desktop33",
"frames_requested": 150,
"viewport_px": [
900,
700
],
"slow_compose_ms": 0.0,
"max_fps_requested": 30,
"scene": "grid:20000",
"context": {
"profile_used": "desktop33",
"vendor": "NVIDIA Corporation",
"renderer": "NVIDIA GeForce RTX 4080 SUPER/PCIe/SSE2",
"gl_version": "3.3.0 NVIDIA 610.88",
"glsl_version": "3.30 NVIDIA via Cg compiler",
"software_renderer": false,
"max_texture_size": 32768,
"device_pixel_ratio": 1.0,
"qt_platform": "windows"
},
"mesh": {
"sensing_vertices": 20022,
"sensing_triangles": 39480,
"housing_triangles": 0,
"field_domain_hw": [
142,
141
],
"estimated_gpu_bytes": 1756192
},
"timings_ms": {
"submit": {
"p50": 0.1347,
"p95": 0.249,
"max": 0.5272,
"mean": 0.146,
"n": 150
},
"compose": {
"p50": 98.7824,
"p95": 99.9672,
"max": 116.6785,
"mean": 78.3176,
"n": 31
},
"compose_sample": {
"p50": 16.1169,
"p95": 16.573,
"max": 23.1074,
"mean": 11.5654,
"n": 31
},
"compose_normals": {
"p50": 82.5348,
"p95": 83.6703,
"max": 99.7443,
"mean": 63.5145,
"n": 31
},
"compose_wall": {
"p50": 98.8445,
"p95": 99.9764,
"max": 116.6866,
"mean": 78.3673,
"n": 31
},
"upload": {
"p50": 0.2777,
"p95": 0.6052,
"max": 2.0555,
"mean": 0.3328,
"n": 150
},
"paint": {
"p50": 0.7098,
"p95": 1.7044,
"max": 12.5557,
"mean": 1.035,
"n": 151
},
"latency": {
"p50": 115.0327,
"p95": 130.3314,
"max": 134.1893,
"mean": 94.0401,
"n": 31
}
},
"compose_count": 31,
"compose_note": "compose/compose_sample/compose_normals are the composer's own timers, collected on the worker thread; compose_wall adds the requested delay. latency is submit-to-paint and therefore includes the frame-rate limiter.",
"display": {
"superseded_display_frames": 118,
"stale_frames": 0,
"invalid_frames": 0,
"max_inflight_composes": 1,
"mailbox_capacity": 1,
"frames_presented": 30
},
"crypto_backend": "49.0.0",
"verdict": "ok",
"exit_code": 0
},
"grid_20k_cpu": {
"mode": "cpu",
"frames_requested": 200,
"scene": "grid:20000",
"mesh": {
"sensing_vertices": 20022,
"sensing_triangles": 39480,
"field_domain_hw": [
142,
141
]
},
"timings_ms": {
"compose": {
"p50": 6.1455,
"p95": 6.3987,
"max": 6.5786,
"mean": 6.1376,
"n": 200
},
"compose_sample": {
"p50": 1.058,
"p95": 1.145,
"max": 1.4462,
"mean": 1.0639,
"n": 200
},
"compose_normals": {
"p50": 4.9096,
"p95": 5.072,
"max": 5.1775,
"mean": 4.8943,
"n": 200
},
"compose_wall": {
"p50": 6.2412,
"p95": 6.4834,
"max": 6.6717,
"mean": 6.2281,
"n": 200
}
},
"verdict": "ok",
"exit_code": 0
}
},
"findings": [
"The shipped 1032-vertex surface composes in 0.5 ms p50 / 0.75 ms p95 and the display holds its 30 fps cap (latency p50 33 ms = the cap, not the cost).",
"A 19881-vertex / 39200-triangle surface composes in 6.1 ms p50 / 6.4 ms p95, i.e. about 18% of a 33 ms frame: the design document's 20k budget is met on this machine with room for the upload and the paint.",
"The acquisition thread's own cost (submit_packet, including the |flow| computation and the mailbox) is 0.14-0.17 ms p50 and under 0.4 ms p95 while the composer is deliberately 50 ms slow: the display cannot push back on acquisition.",
"At 20k vertices the normals are 80% of the compose (4.9 ms of 6.1 ms): the obvious optimisation if a larger surface is wanted is the normal recomputation, not the UV sampling.",
"The queue bound holds: at most one compose in flight and a capacity-one mailbox, with every dropped frame counted as a display drop (superseded_display_frames) and none of them counted as a lost sample."
],
"not_measured_here": [
"GPU memory over time (the long-run report covers RSS and the queue bound; this report has the estimated buffer size from the renderer)",
"the ARM boards: probe_gl.py and this benchmark are ready to run there, and the platform lock can be compiled from the resulting probe report",
"a 20k-vertex surface with a *calibrated* displacement (the adapters do not produce one; the composer's deformation path is covered by unit tests)"
]
}