FPS未来规划

This commit is contained in:
zhongluofeng
2026-07-29 10:07:25 +08:00
parent bd5866f65f
commit f953f22f24
+136 -3
View File
@@ -205,6 +205,139 @@ Rust (Tauri App) <---HTTP / SSE (127.0.0.1:8730)---> ThingHK.exe (独立进程
| [HttpEndpoints.cs](file:///d:/Atie/Gitea/Thing/ThingHK/HttpEndpoints.cs) | 6 个路由(含 `/shutdown` 优雅关闭)+ 全局异常中间件 + `KernelHost`(统一生命周期) | | [HttpEndpoints.cs](file:///d:/Atie/Gitea/Thing/ThingHK/HttpEndpoints.cs) | 6 个路由(含 `/shutdown` 优雅关闭)+ 全局异常中间件 + `KernelHost`(统一生命周期) |
| [rd.xml](file:///d:/Atie/Gitea/Thing/ThingHK/rd.xml) | LHB 反射根描述符,`preserve="all"` 兜底 AOT trimming | | [rd.xml](file:///d:/Atie/Gitea/Thing/ThingHK/rd.xml) | LHB 反射根描述符,`preserve="all"` 兜底 AOT trimming |
## 7. FPS 监控方案(后续计划,暂不实施)
为扩展硬件监控模块对游戏 FPS 的支持,调研了四种实现方案。当前作为后续规划储备,暂不实施。
> 注:本节为后续规划,与第 8 节提权机制章节编号独立,不影响现有架构。
### 7.1 方案对比
| 维度 | ① DWM API | ② ETW Present | ③ PresentMon | ④ RTSS |
|------|-----------|---------------|--------------|--------|
| 依赖 | 无(系统内置) | 无(系统内置) | PresentMon Service(需安装 MSI ~30MB | RTSS(需安装) |
| 体积增量 | 0 KB | 0 KB | ~30MB+ | 外部进程 |
| 代码量 | ~60 行 | ~400 行 | ~1000+ 行(跨栈) | ~200 行(读共享内存) |
| 精度 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 1% Low | ❌ | ❌(需自行实现统计算法) | ✅ 内置 | ✅ |
| 进程级监控 | ❌ | ✅ | ✅ | ✅ |
| 全屏独占兼容 | ❌ | ✅ | ✅ | ✅ |
| 需管理员权限 | ❌ | ✅ | ✅ | ❌ |
| 桌面静止时 | 显示刷新率(60/144,误导) | FPS=0(准确) | FPS=0(准确) | FPS=0 |
| AOT 风险 | 低 | 低(P/Invoke | 中(P/Invoke C API | 低 |
| 与"最小依赖"偏好 | ✅ 完美契合 | ✅ 契合 | ❌ 冲突 | ❌ 冲突 |
### 7.2 方案 ①:DWM API(最简单,降级方案)
**原理**:调用 `DwmGetCompositionTimingInfo` 获取 DWM 合成帧计数,两次采样差值 / 时间差 = FPS。
**优点**
- 零依赖、零体积增量,`dwmapi.dll` 是 Windows 系统组件
- 实现极简(~60 行),AOT 原生友好
- 无需提权
**缺点**
- 全屏独占(FS Exclusive)模式下 DWM 不参与合成,FPS 读数为 0
- 桌面静止时仍以显示器刷新率合成,FPS 显示 60/144(误导)
- 仅全局 FPS,无法按进程过滤
**定位**:降级方案。在用户未提权或仅需粗略 FPS 时使用。
**核心 P/Invoke**
```csharp
[DllImport("dwmapi.dll", PreserveSig = false)]
static extern void DwmGetCompositionTimingInfo(IntPtr hwnd, ref DWM_TIMING_INFO pTimingInfo);
```
### 7.3 方案 ②:自己监听 ETW 的 Present 事件(推荐主力方案)
**原理**:订阅 `Microsoft-Windows-DXGI`GUID `CA11C036-0102-4A2D-A6AD-F03CEDF728C7`)的 Present 事件(Event ID 0),滑动窗口统计单位时间 Present 调用次数 = FPS。
**实现路径**:原生 P/Invoke `advapi32.dll` 的 ETW API`StartTraceW``EnableTraceEx2``OpenTrace``ProcessTrace`),零依赖。
**优点**
- 零依赖、零体积增量
- 进程级监控(按 PID 过滤前台游戏)
- 全屏独占兼容(DXGI Present 事件仍触发)
- AOT 原生友好
**缺点**
- 需管理员权限(与 ThingHK 提权体系兼容)
- 实现复杂度中高(~400 行):需手工定义 `EVENT_TRACE_PROPERTIES`/`EVENT_RECORD` 等结构体
- DXGI Present 事件 payload 无公开 schema,需参考 PresentMon 源码硬编码偏移
- `ProcessTrace` 是阻塞调用,需后台线程管理生命周期
- 会话未正确清理会残留 ETW 会话影响系统性能
**FPS 计算**:1 秒滑动窗口,500ms 清理过期时间戳,`FPS = 队列长度 / 窗口时长`
**实现步骤**
1. 新增 `EtwFpsMonitor.cs`P/Invoke + 会话管理 + FPS 计算)
2. `KernelHost` 持有实例,启动时检测管理员权限,未提权则 `Fps=null`
3. `Contracts.cs``SensorSnapshot` 增加 `Fps` 字段,递增 `SchemaVersion`
4. `SamplingScheduler.FastLoopAsync` 每 tick 调用 `SampleFps()`
5. 前端 OSD/详情页增加"FPS"特殊项
**参考**[PresentMon/IntelPresentMon/PresentData/ETW/Microsoft_Windows_DXGI.cpp](https://github.com/GameTechDev/PresentMon/blob/main/IntelPresentMon/PresentData/ETW/Microsoft_Windows_DXGI.cpp)
### 7.4 方案 ③:PresentMon Service + API(功能最全,但依赖重)
**原理**:安装 PresentMon Service(官方 MSI),ThingHK 通过 P/Invoke `PresentMonAPI2.dll` 连接 Service 获取 140+ 指标。
**部署约束(关键)**
- Service 是独立进程,不能"只丢几个 DLL"
- 官方明确警告:客户端自带 DLL 不保证与服务二进制兼容,**必须用官方 MSI 安装**
- MSI 含 CEF 运行时,安装包 ~30MB,安装后常驻进程 ~30-50MB 内存
- Service 需管理员权限
**API 用法**
```c
pmOpenSession(&hSession);
pmStartTrackingProcess(hSession, processId);
pmRegisterDynamicQuery(hSession, &hQuery, elements, 2, 1000, 0);
pmPollDynamicQuery(hQuery, processId, blob, &numSwapChains);
```
**优点**:功能最全(FPS / 1% Low / 0.1% Low / 帧时间 / GPU 延迟 / 功耗),官方维护。
**缺点**
- 与"minimal installation size, few dependencies"偏好冲突
- 实现复杂度高(~1000+ 行跨栈:C# P/Invoke + Rust 下载安装管理 + 前端)
- AOT 下 P/Invoke C API 需验证
- "游戏模式按需下载"实际是下载 MSI 静默安装,体验重
**定位**:不推荐。若用户已自行安装 PresentMon Service,可检测后切换为高精度模式(不在应用内下载安装)。
### 7.5 方案 ④:RTSS(需安装第三方软件)
**原理**RTSSRivaTuner Statistics Server)提供共享内存暴露 FPS/帧时间,ThingHK 读取共享内存。
**优点**:成熟稳定,精度高,支持 1% Low。
**缺点**
- 需用户安装 RTSS(商业软件,与 MSi Afterburner 捆绑)
- 共享内存 schema 无官方文档,需逆向
- 与"few dependencies"偏好冲突
**定位**:不推荐。仅作为已安装 RTSS 用户的可选对接。
### 7.6 推荐路线
**分两步走**
1. **先实现方案 ①(DWM API**:验证 FPS 展示链路(数据契约 → SSE → 前端 OSD)打通,0 风险、0 依赖。UI 标注"桌面合成 FPS"。
2. **升级为方案 ②(ETW Present**:替换 `Sample()` 实现,前端无改动,数据契约不变。获得精确的进程级 FPS。
**统一数据契约**(两种方案共用):
```csharp
public float? Fps { get; set; } // 实时 FPS(无游戏时为 0 或 null)
public uint? FpsProcessId { get; set; } // 目标进程(可选,0=全局)
```
**权限策略**
- 未提权:降级为 DWM API 或返回 null
- 已提权:启用 ETW 方案
### Rust 集成 ### Rust 集成
| 文件 | 职责 | | 文件 | 职责 |
@@ -237,9 +370,9 @@ ThingHK.exe serve --port 8730 --basic --fast-ms 1000 --slow-ms 5000
ThingHK.exe scan --json --basic ThingHK.exe scan --json --basic
``` ```
## 7. 提权机制 ## 8. 提权机制
### 7.1 背景 ### 8.1 背景
LibreHardwareMonitor 访问 CPU MSR(温度/时钟)、存储 SMART、SMBus、EC 传感器需要管理员权限。普通权限下可读传感器受限: LibreHardwareMonitor 访问 CPU MSR(温度/时钟)、存储 SMART、SMBus、EC 传感器需要管理员权限。普通权限下可读传感器受限:
@@ -270,7 +403,7 @@ LibreHardwareMonitor 访问 CPU MSR(温度/时钟)、存储 SMART、SMBus、
[HttpEndpoints.cs](file:///d:/Atie/Gitea/Thing/ThingHK/HttpEndpoints.cs) 增加 `POST /shutdown` 路由,触发 `CancellationTokenSource.Cancel``app.RunAsync` 优雅退出。响应先返回 200,延迟 100ms 后取消,确保响应体完整发送。 [HttpEndpoints.cs](file:///d:/Atie/Gitea/Thing/ThingHK/HttpEndpoints.cs) 增加 `POST /shutdown` 路由,触发 `CancellationTokenSource.Cancel``app.RunAsync` 优雅退出。响应先返回 200,延迟 100ms 后取消,确保响应体完整发送。
## 8. 后续可选优化 ## 9. 后续可选优化
1. **ProcessManager Mutex 锁阻塞问题**`check_and_cleanup` 中 800ms `sleep`(端口等待)在持有 `processes` Mutex 锁的情况下执行,会阻塞所有进程状态查询。建议将重启逻辑移出锁作用域(已知问题,与 proxy 模块共享)。 1. **ProcessManager Mutex 锁阻塞问题**`check_and_cleanup` 中 800ms `sleep`(端口等待)在持有 `processes` Mutex 锁的情况下执行,会阻塞所有进程状态查询。建议将重启逻辑移出锁作用域(已知问题,与 proxy 模块共享)。
2. **免 UAC 提权**:当前每次提权需弹 UAC,可通过 Windows 任务计划程序注册"以最高权限运行"的任务实现免 UAC 启动(需一次性管理员权限注册任务)。 2. **免 UAC 提权**:当前每次提权需弹 UAC,可通过 Windows 任务计划程序注册"以最高权限运行"的任务实现免 UAC 启动(需一次性管理员权限注册任务)。