Skip to content

feat: 串流时自动优化 NVIDIA 控制面板(V-Sync/FRL/低延迟)#627

Open
qiin2333 wants to merge 3 commits into
masterfrom
feat/nvapi-stream-optimize
Open

feat: 串流时自动优化 NVIDIA 控制面板(V-Sync/FRL/低延迟)#627
qiin2333 wants to merge 3 commits into
masterfrom
feat/nvapi-stream-optimize

Conversation

@qiin2333
Copy link
Copy Markdown
Collaborator

@qiin2333 qiin2333 commented May 6, 2026

Summary

Automate the NVIDIA Control Panel knobs that are usually flipped by hand to make Frame-Generation / unlocked-FPS games stream cleanly: at stream start Sunshine writes a per-game NVIDIA driver profile (and optionally the BASE profile) that forces V-Sync = ForceOn and caps the in-game frame rate slightly below the client refresh via the driver's frame-rate limiter (FRL). At stream end every change is rolled back. A leftover nvprefs_undo.json from a Sunshine crash is replayed on the next launch, exactly like the existing modify_global_profile flow.

Why

Games with Frame Generation (and any unlocked-FPS title) silently disable in-game V-Sync. NVENC's display capture only sees frames that land inside the V-Blank window, so streamed pictures tear, miss frames, and exhibit very high PCL latency. The well-known manual remedy is:

  1. NVCP / NVApp → Manage 3D Settings → Program Settings → game EXE
  2. Vertical sync = On (driver-level, the in-game toggle is moot under FG)
  3. Max Frame Rate = client refresh − 2 fps

This PR turns that procedure into a single Web UI checkbox.

What changes

Backend (src/platform/windows/nvprefs/)

  • undo_data grows two typed entries (game_profile_t, base_extras_t) with nlohmann adl_serializer plumbing. Existing undo files keep parsing — the new fields are optional in from_json.
  • driver_settings gains four new methods that
    • locate or create a shared SunshineStreamGame profile,
    • attach the running EXE to it via NvAPI_DRS_CreateApplication,
    • write VSYNCMODE, FRL_FPS, PREFERRED_PSTATE, PRERENDERLIMIT (each idempotent — pre-write read-back skips the change and the undo entry when the driver already matches),
    • restore conservatively: a setting is rolled back only when the current value still equals what we wrote, the EXE entry is detached only if we added it, and the profile is deleted only if we created it AND no apps remain.
  • nvprefs_interface exposes apply_stream_optimizations(exe, fps) / restore_stream_optimizations() that merge into the existing %ProgramData%\Sunshine\nvprefs_undo.json manifest.

Platform abstraction (src/platform/)

  • common.h declares apply_stream_optimizations(game_cmd, client_fps) / restore_stream_optimizations().
  • linux/macos: no-op.
  • windows/misc.cpp: parses the launched game's command-line into a lower-cased EXE basename and routes to nvprefs_instance behind the master switch.
  • stream.cpp calls the new pair from the existing first-session-start / last-session-stop hooks. Zero #ifdef in stream.cpp.

Configuration (src/config.{h,cpp})

8 new video_t.nv_* fields:

key default meaning
nvenc_optimize_game disabled master switch
nvenc_force_vsync enabled VSYNCMODE = ForceOn
nvenc_lock_frame_rate enabled drive FRL_FPS
nvenc_frl_fps_offset -2 client_fps + offset = FRL target
nvenc_frl_fps_override 0 non-zero = absolute override
nvenc_prefer_max_performance disabled PREFERRED_PSTATE = PreferMax
nvenc_low_latency_mode disabled PRERENDERLIMIT = 1
nvenc_apply_to_base_profile disabled also write the BASE profile

Web UI

A new collapsible section "Stream-time NVIDIA Control Panel auto-optimizations" under the NVENC tab, Windows-only. The master switch hides every dependent control when off. 18 i18n strings each in en.json and zh.json (both validated as parseable JSON).

Safety / rollback

  • Rollback path is the same battle-tested file (nvprefs_undo.json) and the same restore_from_and_delete_undo_file_if_exists flow that already handles the existing global-profile changes — extended to also iterate the new game_profile and base_extras entries.
  • Default for nvenc_optimize_game is disabled — opt-in.
  • Default for nvenc_apply_to_base_profile is disabled — per-game leg only is machine-safe (does not touch other software).
  • Non-NVIDIA systems: nvprefs_instance.load() fails fast → entire feature silently no-ops.

How to test

  1. N 卡 Windows 主机,确认 Sunshine 启动时日志没有 nvprefs 错误。
  2. Web UI → Configuration → NVENC encoder → 展开 Stream-time NVIDIA Control Panel auto-optimizations → 勾选 Auto-tune NVIDIA Control Panel during stream
  3. 启动一次串流。
  4. NVIDIA Profile Inspector 打开 → 左上下拉选 SunshineStreamGame profile → 验证:
    • Vertical Sync = Force on
    • Frame Rate Limiter (V3) = client_fps − 2
    • 其他可选项按勾选反映
    • Application 列表里有当前游戏 EXE
  5. 停止串流 → 重新打开 Inspector → 验证 SunshineStreamGame 已被删除(除非另有用户保留的 EXE);BASE profile 复原。
  6. 崩溃恢复:再次启动并勾选 → 串流 → 直接 kill Sunshine 进程 → 重启 Sunshine → 日志应出现 Restored driver settings from undo file - deleting the file → Inspector 验证 profile 已清理。
  7. 帧生成游戏对比:开启前/后用任意支持帧生成的游戏(如 Cyberpunk 2077 w/ DLSS-FG 或 Alan Wake II)串流,对比是否还有撕裂/掉帧。

Notes

  • NvAPI SDK 当前为 59145(R590)。我们用到的 6 个 setting ID(VSYNCMODE_ID / FRL_FPS_ID / PREFERRED_PSTATE_ID / PRERENDERLIMIT_ID)与 NVApp 里的 V-Sync / Max Frame Rate / Power Mode / Low Latency Mode 四项滑块写入同一组 driver profile DB,不与 NVApp 抢占,互可读取。
  • NVIDIA Reflex 的 "Ultra" 档位 inherently 需要游戏 SDK 配合(NvAPI_D3D_SetSleepMode),不是 driver profile 知识,所以我们刻意只暴露 "Low Latency Mode = On" 等价的 PRERENDERLIMIT = 1

Out-of-scope (后续)

  • 写后回读校验 + log 埋点(用户问"到底有没有生效"时一目了然)
  • per-app override(让指定游戏关掉/换值)
  • AMD / Intel 等价知识旋钮(不存在公共 API,无对应实现)

Closes none.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

在 Windows 上新增按游戏的 NVIDIA 驱动流式优化与撤销(nvprefs)支持,扩展相关配置与 UI;在流处理路径加入平台钩子以应用/恢复优化;同时扩展 undo 数据模型并在流代码中加入麦克风加密生命周期、动态参数(分辨率/FPS 等)处理与会话快照接口;并对 Windows capture_target 做校验与告警。

Changes

NVIDIA 流优化功能

Layer / File(s) Summary
配置数据结构
src/config.h, src/config.cpp
config::video_t 中新增 NVENC 选项字段(nv_optimize_game、nv_force_vsync、nv_lock_frame_rate、nv_frl_fps_offset、nv_frl_fps_override、nv_prefer_max_performance、nv_low_latency_mode、nv_apply_to_base_profile),并在初始化与 apply_config 中赋默认值/读取(含范围约束)。
平台抽象接口
src/platform/common.h
添加平台钩子声明 platf::apply_stream_optimizations(const std::string&, int)platf::restore_stream_optimizations()(含文档注释)。
跨平台实现
src/platform/linux/misc.cpp, src/platform/macos/misc.mm, src/platform/windows/misc.cpp
Linux/macOS 提供空实现;Windows 实现包含 exe 名称提取、配置检查,并通过 nvprefs 实例委托应用/恢复优化;Windows misc.cpp 新增对 src/config.h 的包含。
nvprefs 配置传递
src/platform/windows/nvprefs/nvprefs_common.h, src/platform/windows/nvprefs/nvprefs_common.cpp
nvprefs_options 添加对应 nv_* 字段并在 get_nvprefs_options() 中从 config::video 复制这些选项。
撤销数据模型
src/platform/windows/nvprefs/undo_data.h, src/platform/windows/nvprefs/undo_data.cpp
新增 setting_undo_tgame_profile_tbase_extras_t 类型,扩展 undo_data_t 的序列化/反序列化与 set/get/clear 接口,merge 中支持新字段。
驱动设置应用逻辑
src/platform/windows/nvprefs/driver_settings.h, src/platform/windows/nvprefs/driver_settings.cpp
新增公有方法并实现:check_and_modify_game_profilerestore_game_profile_to_undocheck_and_modify_base_extrasrestore_base_extras_to_undo,内部包含 FRL 计算、desired_settings 生成、单项应用/恢复与完整写入。
NVPREFS 接口层
src/platform/windows/nvprefs/nvprefs_interface.h, src/platform/windows/nvprefs/nvprefs_interface.cpp
新增 apply_stream_optimizations(const std::wstring&, int)restore_stream_optimizations() 公共接口;新增 ensure_undo_dir_and_file() 辅助,改进撤销文件创建、合并、写盘与恢复流程,调整析构/恢复时序以保证一致性。
NVAPI Wrappers
src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
新增对若干 NvAPI DRS 函数的封装:NvAPI_DRS_FindApplicationByName、NvAPI_DRS_GetProfileInfo、NvAPI_DRS_DeleteProfile、NvAPI_DRS_DeleteApplication。
Web UI 与本地化
src_assets/.../useConfig.js, src_assets/.../NvidiaNvencEncoder.vue, src_assets/.../locale/en.json, src_assets/.../locale/zh.json
在默认配置、UI(Windows 条件渲染)及中/英本地化中新增 NVENC 优化的主开关与子选项文本与描述。

流媒体会话增强与动态参数

Layer / File(s) Summary
麦克风加密生命周期
src/stream.cpp
新增 per-client 麦克风管理 API:ensure_mic_sock_openreset_mic_encryptionremove_mic_encryptionsetup_mic_for_session,并在会话流中使用以管理 mic 套接字与加密上下文。
动态参数处理
src/stream.cpp
扩展 IDX_DYNAMIC_PARAM_CHANGE 解析:新增 RESOLUTION(宽/高 验证、旋转检测、显示重配/请求 IDR)、FPS(小端读、验证并应用)及若干单值参数的读取/验证;改用 read_le_u32/read_le_f32 以替代直接 reinterpret_cast。
会话信息查询
src/stream.cpp
新增 get_all_sessions_info(),返回当前活跃会话的快照信息(客户端/应用/端口/分辨率/帧率/码率/麦克风/HDR 等)。
头文件补充
src/stream.cpp
新增 <fstream><openssl/err.h> 的包含以支持文件操作与错误处理路径。
平台调用点
src/stream.cpp
在首次非控制会话启动时调用 platf::apply_stream_optimizations(game_cmd, fps),在最后一个非控制会话结束前调用 platf::restore_stream_optimizations()
Windows capture 校验
src/config.cpp
在 apply_config 中新增 Windows 专属校验:对 capture_target 默认/限制为 "display""window",并在选择 "window"window_title 为空时记录警告;对 FRL 偏移/覆盖值做范围夹紧。

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant StreamStart as Stream Start
    participant AppQuery as App Query
    participant PlatformHook as Platform Hook
    participant NVPrefs as NVIDIA Prefs
    participant DriverAPI as Driver API

    Client->>StreamStart: 启动第一个会话
    activate StreamStart
    StreamStart->>AppQuery: 获取当前运行的游戏命令
    AppQuery-->>StreamStart: 返回 game_cmd

    StreamStart->>PlatformHook: apply_stream_optimizations(game_cmd, fps)
    activate PlatformHook
    PlatformHook->>PlatformHook: 检查 config.nv_optimize_game
    alt 优化已启用
        PlatformHook->>NVPrefs: apply_stream_optimizations(exe_name, fps)
        activate NVPrefs
        NVPrefs->>DriverAPI: 应用游戏配置文件设置 / 应用 BASE 设置(可选)
        DriverAPI-->>NVPrefs: 返回撤销数据
        NVPrefs-->>PlatformHook: 返回成功
        deactivate NVPrefs
    end
    PlatformHook-->>StreamStart: 优化已应用
    deactivate PlatformHook
    StreamStart-->>Client: 会话已启动
    deactivate StreamStart

    Client->>StreamStart: 关闭最后一个会话
    activate StreamStart
    StreamStart->>PlatformHook: restore_stream_optimizations()
    activate PlatformHook
    PlatformHook->>NVPrefs: restore_stream_optimizations()
    NVPrefs->>DriverAPI: 从撤销数据恢复设置
    DriverAPI-->>NVPrefs: 恢复完成
    NVPrefs-->>PlatformHook: 恢复成功
    deactivate PlatformHook
    StreamStart-->>Client: 会话已关闭,优化已恢复
    deactivate StreamStart
Loading
sequenceDiagram
    participant Client
    participant ControlThread as Control Thread
    participant SessionMgr as Session Manager
    participant DisplayMgr as Display Manager
    participant Encoder as Encoder

    Client->>ControlThread: 发送 IDX_DYNAMIC_PARAM_CHANGE
    activate ControlThread
    ControlThread->>ControlThread: 解析参数载荷(小端读)

    alt 参数为 RESOLUTION
        ControlThread->>ControlThread: 验证 width/height(范围)
        ControlThread->>SessionMgr: 更新 session.config.monitor 分辨率
        SessionMgr-->>ControlThread: 已更新
        ControlThread->>DisplayMgr: 重新配置显示
        DisplayMgr-->>ControlThread: 配置完成
        ControlThread->>Encoder: 请求 IDR 帧
        Encoder-->>ControlThread: IDR 已请求
    else 参数为 FPS 或其他
        ControlThread->>ControlThread: 验证并应用新值,触发事件
    end

    ControlThread-->>Client: 参数更新已处理
    deactivate ControlThread
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR标题准确地反映了主要改动:在串流时自动优化NVIDIA控制面板的V-Sync/FRL/低延迟设置,与代码变更的核心目标完全相符。
Description check ✅ Passed PR描述详细说明了变更的目的、实现细节、配置选项、安全机制和测试步骤,与changeset的所有主要部分相关,提供了充分的上下文。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/nvapi-stream-optimize

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/platform/windows/misc.cpp`:
- Around line 1398-1416: extract_exe_basename_w currently constructs
std::filesystem::path from a narrow std::string which on Windows is interpreted
using the ANSI code page; instead convert the extracted UTF‑8 token to a wide
string using the existing from_utf8 helper before creating a path and calling
filename(), e.g. use from_utf8(token) to produce a std::wstring path, then call
std::filesystem::path(wide_token).filename().wstring(), lowercase it as before
and return it (update function extract_exe_basename_w and keep usage consistent
with resolve_command_string and nvprefs_instance.apply_stream_optimizations).

In `@src/platform/windows/nvprefs/driver_settings.cpp`:
- Around line 615-649: The current undo flow returns immediately when any
restore_uint_setting (called for undo_data.vsync, undo_data.frl,
undo_data.pstate, undo_data.prerender) fails, which skips remaining setting
restores and the cleanup logic (NvAPI_DRS_DeleteApplication /
NvAPI_DRS_DeleteProfile); change the logic in the restore function so each
restore_uint_setting call is attempted regardless of previous failures, record
any failures (e.g., set a local bool had_error = true), continue executing the
remaining restores and then always run the application/profile cleanup block
(checking undo_data.application_was_added and undo_data.profile_was_created and
calling NvAPI_DRS_DeleteApplication / NvAPI_DRS_DeleteProfile), and finally
return success only if no individual restore or cleanup failed; apply the same
change to restore_base_extras_to_undo to ensure both paths attempt all restores
and cleanups before returning an aggregated result.
- Around line 565-569: Replace the lossy wchar_t->char iterator-based
conversions when populating undo_data_t::data_t::game_profile_t (set
pending.profile_name and pending.exe_path from profile_name_used and exe_name)
with UTF-8 safe conversions using platf::to_utf8(), and likewise when reading
back from JSON before calling NvAPI_DRS_FindApplicationByName() and
NvAPI_DRS_FindProfileByName() use platf::from_utf8()/platf::to_utf8() as
appropriate so non-ASCII characters are preserved; specifically, swap the
std::string(profile_name_used.begin(), profile_name_used.end()) and
std::string(exe_name.begin(), exe_name.end()) uses for
platf::to_utf8(profile_name_used) / platf::to_utf8(exe_name) and apply the
inverse conversions in the restore paths that prepare names for the NvAPI_*
lookup calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 787bb4cc-87cc-4400-abf5-d15839080de6

📥 Commits

Reviewing files that changed from the base of the PR and between ed1c794 and 01f72a7.

📒 Files selected for processing (19)
  • src/config.cpp
  • src/config.h
  • src/platform/common.h
  • src/platform/linux/misc.cpp
  • src/platform/macos/misc.mm
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/stream.cpp
  • src_assets/common/assets/web/composables/useConfig.js
  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
  • src_assets/common/assets/web/public/assets/locale/en.json
  • src_assets/common/assets/web/public/assets/locale/zh.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/platform/common.h
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/config.h
  • src/platform/linux/misc.cpp
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/undo_data.h
  • src/platform/windows/nvprefs/driver_settings.h
  • src/config.cpp
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/stream.cpp
src/platform/**

⚙️ CodeRabbit configuration file

src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

Files:

  • src/platform/common.h
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/linux/misc.cpp
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/macos/misc.mm
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/undo_data.h
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
src_assets/**/*.{vue,js,html}

⚙️ CodeRabbit configuration file

src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

Files:

  • src_assets/common/assets/web/composables/useConfig.js
  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
🔇 Additional comments (20)
src_assets/common/assets/web/composables/useConfig.js (1)

145-153: 新增 NVENC 默认配置项设计合理。

这 8 个配置键的默认值与“总开关默认关闭、子项可预设”的策略一致,且不会引入前端侧安全风险。

src/config.h (1)

42-54: video_t 扩展字段与注释质量良好。

新增字段覆盖了功能开关、参数和作用范围,注释对回滚语义解释清晰,便于后续维护。

src/config.cpp (2)

408-415: 新增默认值策略正确。

nv_optimize_game 默认关闭,同时保留其余参数默认值,整体符合“显式启用后生效”的安全路径。


1138-1145: 配置读取链路接入完整。

8 个 nvenc_* 配置项都已映射到对应 video 字段,和结构体扩展保持一致。

src/platform/windows/nvprefs/undo_data.h (1)

23-61: Undo 数据模型与接口设计清晰、可扩展。

分层结构(setting/game/base)和配套访问器完整,适合实现保守回滚与崩溃恢复。

Also applies to: 70-87

src_assets/common/assets/web/public/assets/locale/zh.json (1)

384-400: 本地化键新增完整且语义一致。

文案覆盖面足够,能支撑新功能在中文 UI 中的可理解性与可配置性。

src/platform/windows/nvprefs/nvprefs_common.cpp (1)

39-46: nvprefs_options 映射补充完整。

新增字段与 config::video 命名一致,透传逻辑清晰,便于后续平台实现读取。

src/platform/linux/misc.cpp (1)

313-321: Linux 侧 no-op 实现合理。

该实现保持了平台接口一致性,同时避免在 Linux 上引入无效副作用。

src/platform/common.h (1)

742-763: 平台抽象接口定义清晰。

apply/restore 的职责边界和参数语义明确,便于各平台实现保持一致行为。

src/platform/macos/misc.mm (1)

241-249: LGTM!

macOS 上提供空实现作为平台抽象层的占位符是合适的,与 Linux 端的处理方式一致。

src/platform/windows/nvprefs/nvprefs_interface.h (1)

39-58: LGTM!

接口声明清晰,文档注释充分说明了语义(包括在崩溃恢复场景下的行为以及 exe_name 为空时静默跳过的约定)。

src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue (1)

196-285: LGTM!

新增的 NV 控制面板自动优化区块结构清晰:

  • 通过 platform === 'windows' 正确隐藏在非 Windows 平台。
  • 使用 <template v-if> 包裹依赖项,主开关关闭时不渲染从属控件,避免无意义的状态写入。
  • FRL offset/override 仅在 nvenc_lock_frame_rate === 'enabled' 时显示,与后端 compute_frl_fps 的判断一致。
  • 数值输入有合理的 min/max 约束。
  • 折叠面板结构与现有 NVENC Misc 节保持一致,保留了键盘可访问性。
src/platform/windows/misc.cpp (1)

1419-1443: LGTM!

入口的开关检查、nvprefs_instance.load() 失败时的静默跳过、以及 apply/restore 对称的 load/unload 配对都是合理的。Linux/macOS 端有等价的 no-op 实现,无需 #ifdef 来包裹调用方逻辑。

src/platform/windows/nvprefs/nvprefs_interface.cpp (2)

254-300: LGTM

即便 check_and_modify_game_profile 失败也继续走 base 路径,并把已写入的部分 undo 持久化,这与 driver_settings.cpp 中"失败时仍设置 undo_out"的契约相符,可以让后续 restore 尽力回滚。pimpl->undo_data->merge(fresh) 也保证了之前 modify_global_profile() 留下的 OpenGL swapchain undo 不会被覆盖。


302-338: LGTM

按 game_profile/base_extras 分别 restore 并清空对应字段,最后判断 undo_data 是否完全为空再决定删除还是改写 undo 文件,避免了把 modify_global_profile() 留下的 OpenGL swapchain 信息误删。

src/platform/windows/nvprefs/nvprefs_common.h (1)

56-71: LGTM!

主开关 nv_optimize_game = false 默认关闭,符合 PR 描述的 opt-in 策略;从属选项的默认值(强制 VSync/锁帧 = true,最大性能/低延迟/写 BASE = false)在主开关启用后是合理的"安全推荐配置"。注释中标注了每个字段对应的 NVAPI setting ID/value,可读性好。

src/platform/windows/nvprefs/driver_settings.h (1)

64-105: LGTM!

接口分组合理:check_and_modify_*restore_*_to_undo 成对出现,签名与 undo_data_t::data_t::game_profile_t / base_extras_t 类型保持一致,文档清楚说明了"应用 → 写 undo → 还原"的整个流程以及 SunshineStreamGame 配置文件按需创建的语义。

src/platform/windows/nvprefs/undo_data.cpp (1)

19-153: JSON 序列化扩展实现稳健,向后兼容性处理得当。

data_t::from_json 对新增的 game_profile / base_extras 使用 contains() 守卫读取,保证旧版 undo 文件能正常解析;game_profile_t::from_json 对结构性字段(profile_name/exe_path/...)使用 at()、对可选 setting 字段使用 contains(),划分合理。to_json 始终输出全部字段,配合现有的 adl_serializer<std::optional<T>> 让 nullopt → JSON null 可正确往返。

src/stream.cpp (2)

3030-3031: LGTM — 与 apply 路径配对正确。

restore_stream_optimizations()--running_non_control_only_sessions == 0 分支内、紧接显示设备状态恢复之后、streaming_will_stop() 之前调用,构成 "apply after start / restore before stop" 的镜像顺序,并且独立于 restore_display_state 决策——即便 app 仍在运行也会回滚 NV 控制面板,这与 PR 中 "stream 结束即回滚" 的语义一致。


3127-3136: ⚡ Quick win

生命周期挂载和边界情形处理妥当。

挂载位置正确:apply_stream_optimizationsstreaming_will_start() 之后、并且只在第一条非控制会话起来时(++running_non_control_only_sessions == 1)调用一次,与 Line 3030 的 restore_stream_optimizations 形成对称。if (auto app_id = proc::proc.running()) 的 if-init 用法干净,确保 game_cmd 在没有前台应用时保持空字符串。

Windows 端的 nvprefs 接口在 header 中明确文档化:"Empty string skips the per-game profile leg silently"(空字符串静默跳过逐游戏 profile 步骤),因此不会生成无效的 profile 条目。该安全合约由上层 apply_stream_optimizations 实现遵守,不存在副作用风险。

Comment thread src/platform/windows/misc.cpp
Comment thread src/platform/windows/nvprefs/driver_settings.cpp
Comment thread src/platform/windows/nvprefs/driver_settings.cpp
@qiin2333 qiin2333 force-pushed the feat/nvapi-stream-optimize branch from 01f72a7 to ead12a9 Compare May 6, 2026 06:09
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/platform/windows/nvprefs/undo_data.cpp (1)

226-238: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

merge() 现在无法删除已经写入的 undo 分支。

这里的语义有歧义:nullopt 同时表示“这次没有更新”和“这次明确清掉了该分支”,但 Line 231-238 只在 source 有值时覆盖。结果是旧文件里一旦写入 game_profile / base_extras,后续即使调用 clear_game_profile() / clear_base_extras(),再经过 merge() 也删不掉旧数据,残留的回滚项下次启动还会被再次回放。

建议让 merge 明确区分“保留旧值”和“显式删除”,或者不要用 merge 语义回写 restore 后的最新快照。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/windows/nvprefs/undo_data.cpp` around lines 226 - 238, merge
currently treats a missing optional the same as "no-op", so explicit clears in
newer_data are ignored; update undo_data_t::merge to detect explicit deletions
and call the corresponding clear_* methods instead of only overwriting when
get_* returns a value. Concretely, add or use explicit presence/cleared
indicators (e.g. is_game_profile_cleared/is_game_profile_set or a three-state
getter) and change merge to: if newer_data indicates "cleared" call
clear_game_profile(), else if newer_data provides a value call
set_game_profile(*game); do the same for get_base_extras/set_base_extras and for
get_opengl_swapchain (call set_opengl_swapchain or a clear_opengl_swapchain when
appropriate) so explicit deletes in newer_data remove previously written
branches.
src/stream.cpp (1)

1459-1518: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

使用 std::memcpy 替代 reinterpret_cast 进行网络 payload 反序列化

代码在多处直接将 std::string_view 缓冲区 reinterpret_cast 为 int*float* 指针:

  • 第 1459 行:*reinterpret_cast<const int *>(payload.data())
  • 第 1477、1492、1510 行:类似的 reinterpret_cast 操作

该缓冲区不保证 4 字节对齐,违反指针对齐要求,这在 C++ 中属于未定义行为(虽然 x86 可能容忍,但 ARM/PowerPC 等架构会导致崩溃或静默数据损坏)。建议改为:

std::uint32_t param_type_raw = 0;
std::memcpy(&param_type_raw, payload.data(), sizeof(param_type_raw));
const int param_type = static_cast<int>(param_type_raw);  // 需补充字节序转换

同时,网络协议通常使用固定字节序(如网络字节序),代码中其他地方使用了 boost::endian::little_uint16_at,建议在此处也显式处理字节序转换。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stream.cpp` around lines 1459 - 1518, Replace all unaligned
reinterpret_cast reads from payload.data() with safe memcpy-based
deserialization and explicit byte-order handling: read the initial parameter
type into a std::uint32_t via std::memcpy and convert to int for param_type
(used where param_type is currently computed), read width/height into
uint32_t/int values via std::memcpy before calling handle_resolution_change,
read the float FPS value into a float via std::memcpy before
validating/assigning session->config.monitor.framerate and building
video::dynamic_param_t, and read other integer params (param_value) via memcpy;
ensure you apply the appropriate endianness conversion (e.g. boost::endian
helpers or ntohl/ntohf equivalent) for each field.
♻️ Duplicate comments (1)
src/platform/windows/misc.cpp (1)

1398-1415: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

不要把 UTF-8 命令直接传给 std::filesystem::path(std::string)

这个问题前一轮已经提过,当前实现还在。Line 1411 在 Windows 上会按系统窄字符代码页而不是 UTF-8 解释 token,带中文/日文路径时 filename() 会被解码错,后续按 EXE 名匹配 NVIDIA profile 就会失败。

🛠️ 建议修改
-      auto fname = std::filesystem::path(token).filename().wstring();
+      auto fname = std::filesystem::path(from_utf8(token)).filename().wstring();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/windows/misc.cpp` around lines 1398 - 1415, The code currently
constructs std::filesystem::path from a narrow UTF-8 std::string (token) which
on Windows is interpreted using the ANSI code page; instead convert token from
UTF-8 to a wide string and build the path from that wide string. In function
extract_exe_basename_w, after extracting token, convert token to std::wstring
using a UTF-8→UTF-16 conversion (e.g. MultiByteToWideChar with CP_UTF8 or a
trusted UTF-8 conversion helper) and then call
std::filesystem::path(wide_token).filename().wstring(), keep the existing
lowercasing on the returned std::wstring, and preserve the early-empty checks
and quoting logic. Ensure the conversion handles errors/empty results
consistently.
🧹 Nitpick comments (1)
src/platform/windows/nvprefs/driver_settings.h (1)

73-79: ⚡ Quick win

修正文档参数名与函数签名不一致

@param undo_data 与实际参数 undo_out 不一致,建议统一为 undo_out,避免后续调用和维护时误读。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/windows/nvprefs/driver_settings.h` around lines 73 - 79, Doc
param name mismatches the function signature: change the Doxygen `@param` from
"undo_data" to "undo_out" in the comment for check_and_modify_game_profile so
the documented parameter matches the actual parameter undo_out; update the
comment text if needed to reflect that undo_out is an output parameter
containing the undo data (reference: function
check_and_modify_game_profile(const std::wstring &exe_name, int client_fps,
std::optional<undo_data_t::data_t::game_profile_t> &undo_out)).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue`:
- Around line 197-205: 为新增的折叠面板补齐可访问性关联:给 header(当前 <h2
class="accordion-header">)加一个稳定 id(比如 panelsStayOpen-headingNvOptimize),在触发按钮(带
data-bs-target="#panelsStayOpen-collapseNvOptimize")上添加
aria-controls="panelsStayOpen-collapseNvOptimize" 并确保有 aria-expanded
绑定,且在折叠内容容器(id="panelsStayOpen-collapseNvOptimize")上添加
aria-labelledby="panelsStayOpen-headingNvOptimize";这样 button 与 collapse 通过 id
相互引用,便于屏幕阅读器识别。

In `@src/config.cpp`:
- Around line 1138-1145: The nvenc_frl_fps_override value is currently read via
int_f(vars, "nvenc_frl_fps_override", video.nv_frl_fps_override) and can be
negative; clamp negative values to 0 immediately after that call (e.g. if
(video.nv_frl_fps_override < 0) video.nv_frl_fps_override = 0) so no negative
sentinel propagates down into the encoder/driver paths; update the config
parsing in src/config.cpp around the int_f invocation for nv_frl_fps_override to
perform this normalization.

In `@src/platform/windows/nvprefs/driver_settings.cpp`:
- Around line 527-577: The code creates a profile/application
(NvAPI_DRS_CreateProfile / NvAPI_DRS_CreateApplication) but only populates
pending/undo_out later, so if a later write or a concurrent call (e.g.
check_and_modify_base_extras() / apply_stream_optimizations() which may call
save_settings()) persists the session, the created profile/app won't be
undoable; fix by populating the undo record immediately after any successful
side-effect: as soon as NvAPI_DRS_CreateProfile returns OK (and likewise after
NvAPI_DRS_CreateApplication) fill pending.profile_name, pending.exe_path,
pending.profile_was_created and pending.application_was_added and assign
undo_out = pending (or call the existing rollback helper) so the undo state
exists before calling apply_desired_to_profile; alternatively, if you prefer not
to set undo_out early, perform an immediate rollback (delete
profile/application) on any subsequent failure before returning false.

In `@src/platform/windows/nvprefs/nvprefs_interface.cpp`:
- Around line 254-337: Destructor currently calls restore_global_profile() which
deletes the shared undo file and thus can remove game_profile/base_extras undo
data; change the teardown to either call
nvprefs_interface::restore_stream_optimizations() (so full undo is applied and
the shared undo file is trimmed/deleted correctly) or modify
restore_global_profile() to only clear the global-specific fields in
pimpl->undo_data and update/write the undo file instead of deleting it; ensure
you update/delete pimpl->undo_file only when undo_data is empty and always call
pimpl->driver_settings.save_settings() after restoring/trimming so platform
state is consistent.

---

Outside diff comments:
In `@src/platform/windows/nvprefs/undo_data.cpp`:
- Around line 226-238: merge currently treats a missing optional the same as
"no-op", so explicit clears in newer_data are ignored; update undo_data_t::merge
to detect explicit deletions and call the corresponding clear_* methods instead
of only overwriting when get_* returns a value. Concretely, add or use explicit
presence/cleared indicators (e.g. is_game_profile_cleared/is_game_profile_set or
a three-state getter) and change merge to: if newer_data indicates "cleared"
call clear_game_profile(), else if newer_data provides a value call
set_game_profile(*game); do the same for get_base_extras/set_base_extras and for
get_opengl_swapchain (call set_opengl_swapchain or a clear_opengl_swapchain when
appropriate) so explicit deletes in newer_data remove previously written
branches.

In `@src/stream.cpp`:
- Around line 1459-1518: Replace all unaligned reinterpret_cast reads from
payload.data() with safe memcpy-based deserialization and explicit byte-order
handling: read the initial parameter type into a std::uint32_t via std::memcpy
and convert to int for param_type (used where param_type is currently computed),
read width/height into uint32_t/int values via std::memcpy before calling
handle_resolution_change, read the float FPS value into a float via std::memcpy
before validating/assigning session->config.monitor.framerate and building
video::dynamic_param_t, and read other integer params (param_value) via memcpy;
ensure you apply the appropriate endianness conversion (e.g. boost::endian
helpers or ntohl/ntohf equivalent) for each field.

---

Duplicate comments:
In `@src/platform/windows/misc.cpp`:
- Around line 1398-1415: The code currently constructs std::filesystem::path
from a narrow UTF-8 std::string (token) which on Windows is interpreted using
the ANSI code page; instead convert token from UTF-8 to a wide string and build
the path from that wide string. In function extract_exe_basename_w, after
extracting token, convert token to std::wstring using a UTF-8→UTF-16 conversion
(e.g. MultiByteToWideChar with CP_UTF8 or a trusted UTF-8 conversion helper) and
then call std::filesystem::path(wide_token).filename().wstring(), keep the
existing lowercasing on the returned std::wstring, and preserve the early-empty
checks and quoting logic. Ensure the conversion handles errors/empty results
consistently.

---

Nitpick comments:
In `@src/platform/windows/nvprefs/driver_settings.h`:
- Around line 73-79: Doc param name mismatches the function signature: change
the Doxygen `@param` from "undo_data" to "undo_out" in the comment for
check_and_modify_game_profile so the documented parameter matches the actual
parameter undo_out; update the comment text if needed to reflect that undo_out
is an output parameter containing the undo data (reference: function
check_and_modify_game_profile(const std::wstring &exe_name, int client_fps,
std::optional<undo_data_t::data_t::game_profile_t> &undo_out)).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a1951689-95ce-4b7f-8a89-aac176cf5208

📥 Commits

Reviewing files that changed from the base of the PR and between 01f72a7 and ead12a9.

📒 Files selected for processing (19)
  • src/config.cpp
  • src/config.h
  • src/platform/common.h
  • src/platform/linux/misc.cpp
  • src/platform/macos/misc.mm
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/stream.cpp
  • src_assets/common/assets/web/composables/useConfig.js
  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
  • src_assets/common/assets/web/public/assets/locale/en.json
  • src_assets/common/assets/web/public/assets/locale/zh.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/config.h
  • src_assets/common/assets/web/composables/useConfig.js
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (3)
src_assets/**/*.{vue,js,html}

⚙️ CodeRabbit configuration file

src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

Files:

  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/linux/misc.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/common.h
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/config.cpp
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/stream.cpp
src/platform/**

⚙️ CodeRabbit configuration file

src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

Files:

  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/linux/misc.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/common.h
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/macos/misc.mm
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/windows/nvprefs/undo_data.cpp

Comment on lines +197 to +205
<div class="accordion-item" v-if="platform === 'windows'">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseNvOptimize">
{{ $t('config.nvenc_optimize_game_section') }}
</button>
</h2>
<div id="panelsStayOpen-collapseNvOptimize" class="accordion-collapse collapse">
<div class="accordion-body">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

给新 accordion 补齐可访问性关联属性

新增折叠面板的按钮和内容区没有建立 aria-controls / aria-labelledby 关系,屏幕阅读器无法把触发器和区域正确关联起来。这里至少补一个稳定的 header id,并让 button/collapse 互相引用。

As per coding guidelines, src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue`
around lines 197 - 205, 为新增的折叠面板补齐可访问性关联:给 header(当前 <h2
class="accordion-header">)加一个稳定 id(比如 panelsStayOpen-headingNvOptimize),在触发按钮(带
data-bs-target="#panelsStayOpen-collapseNvOptimize")上添加
aria-controls="panelsStayOpen-collapseNvOptimize" 并确保有 aria-expanded
绑定,且在折叠内容容器(id="panelsStayOpen-collapseNvOptimize")上添加
aria-labelledby="panelsStayOpen-headingNvOptimize";这样 button 与 collapse 通过 id
相互引用,便于屏幕阅读器识别。

Comment thread src/config.cpp
Comment thread src/platform/windows/nvprefs/driver_settings.cpp
Comment thread src/platform/windows/nvprefs/nvprefs_interface.cpp
@qiin2333 qiin2333 force-pushed the feat/nvapi-stream-optimize branch from ead12a9 to 8aedafd Compare May 6, 2026 09:48
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/config.cpp`:
- Around line 1141-1142: The config parsing currently trusts values set by int_f
for nvenc_frl_fps_offset and nvenc_frl_fps_override (video.nv_frl_fps_offset /
video.nv_frl_fps_override); clamp these immediately after parsing to the UI-safe
ranges (offset -> [-30, 30], override -> [0, 500]) to avoid signed overflow or
out-of-UI values, and emit a warning when a value is adjusted; update the code
around the int_f(...) calls to perform the clamp and call the existing
config/log warning helper (use the project's warning/log function) mentioning
the key name and the clamped value.

In `@src/platform/windows/nvprefs/nvprefs_interface.cpp`:
- Around line 307-334: The undo trimming and clears are happening even when
pimpl->driver_settings.save_settings() fails; move the calls that clear undo
state (pimpl->undo_data->clear_game_profile(),
pimpl->undo_data->clear_base_extras()) and the logic that deletes or rewrites
the undo file (pimpl->undo_file->delete_file(),
pimpl->undo_file->write_undo_data()) so they only run after save_settings()
returns true; keep the restore_* calls (restore_game_profile_to_undo,
restore_base_extras_to_undo) where they are, and on save_settings() failure do
not reset or delete undo_data/undo_file so crash-recovery can retry. Ensure
error_message calls remain for save_settings/delete_file/write_undo_data
failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9fb46eaa-ce53-4a85-9ab9-818ca9896f7b

📥 Commits

Reviewing files that changed from the base of the PR and between ead12a9 and 8aedafd.

📒 Files selected for processing (20)
  • src/config.cpp
  • src/config.h
  • src/platform/common.h
  • src/platform/linux/misc.cpp
  • src/platform/macos/misc.mm
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/stream.cpp
  • src_assets/common/assets/web/composables/useConfig.js
  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
  • src_assets/common/assets/web/public/assets/locale/en.json
  • src_assets/common/assets/web/public/assets/locale/zh.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src_assets/common/assets/web/public/assets/locale/en.json
  • src_assets/common/assets/web/composables/useConfig.js
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
src/platform/**

⚙️ CodeRabbit configuration file

src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

Files:

  • src/platform/macos/misc.mm
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/linux/misc.cpp
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/common.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
src_assets/**/*.{vue,js,html}

⚙️ CodeRabbit configuration file

src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

Files:

  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/linux/misc.cpp
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/common.h
  • src/config.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/config.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/stream.cpp
🔇 Additional comments (11)
src/platform/windows/nvprefs/nvprefs_common.h (1)

61-63: ⚡ Quick win

FRL 下限保护已在 compute_frl_fps() 中实现,无需修改。

nvprefs_common.h 中的注释"clamped >= 1"是准确的。driver_settings.cpp 的 compute_frl_fps() 函数(第 ~331 行)已包含 if (v < 1) v = 1; 的下限保护,确保向驱动发送的 FRL 值始终 >= 1。同时 nv_frl_fps_override 也仅在 > 0 时才使用,无效值不会被应用。

src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue (1)

198-205: 给新折叠面板补齐 ARIA 关联。

Line 198-205 这组 accordion 仍然缺少稳定的 id / aria-controls / aria-labelledby 关联,屏幕阅读器无法把触发按钮和折叠内容正确配对。

As per coding guidelines, src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

src/platform/windows/misc.cpp (1)

1411-1423: 这里仍在用窄字符串路径解析 UTF-8 命令。

Line 1411-1423 先把 UTF-8 cmd 切成 std::string token,再直接传给 std::filesystem::path(token)。在 Windows 上这会按系统代码页而不是 UTF-8 解码,游戏路径里只要有中文/日文等非 ASCII 字符,提取出来的 basename 就可能损坏,后面的应用配置查找会 miss。这里应先 from_utf8() 再取 filename()

src/platform/windows/nvprefs/nvprefs_interface.cpp (1)

255-337: 共享 undo 清单已经承载 stream 项,但 teardown 仍只回滚全局项。

Line 255-337 这里把 game_profile / base_extras 合并进同一份 undo 清单后,析构路径仍然只走 restore_global_profile()。对象在持有 undo 文件时析构,会把整份文件删掉,而 stream-time 变更还没恢复,下一次启动就拿不到 crash recovery 信息了。

As per coding guidelines, src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

src/platform/windows/nvprefs/driver_settings.cpp (3)

527-577: 先建 profile/application,后记 undo,会让失败路径不可回滚。

Line 527-577 在 pending 生成之前就可能已经成功执行 NvAPI_DRS_CreateProfile() / NvAPI_DRS_CreateApplication()。如果后面某一步失败,而同一 session 的其他改动又被 save_settings() 持久化,这里新建的 profile/application 就会残留,但 undo_out 还是空的。请在第一次产生副作用后立即填充 pending/undo_out,或者在失败时就地回滚当前 session。

As per coding guidelines, src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。


567-610: 这里的 wstring/string 互转仍然是有损的。

Line 567-569、594-610 这些基于迭代器的拷贝会直接截断非 ASCII 字符。undo 文件里只要出现中文/日文 profile 名或 exe 名,恢复时就可能找不到对应的 profile/application。这里应统一改成 platf::to_utf8() / platf::from_utf8()


619-714: 还原阶段不要在第一个 setting 失败时就提前退出。

Line 619-714 当前任一 restore_uint_setting() 失败都会立刻 return false。game-profile 路径下这会把后续 setting 的恢复以及 NvAPI_DRS_DeleteApplication() / NvAPI_DRS_DeleteProfile() 清理一起跳过;BASE 路径也会停在半恢复状态。这里应该累计失败但继续执行剩余 restore/cleanup,最后再返回汇总结果。

As per coding guidelines, src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

src/platform/windows/nvprefs/undo_data.cpp (2)

50-152: ADL 序列化扩展实现稳健,向后兼容处理得当。

data_t::from_jsoncontains() 守卫新增的 game_profile / base_extras,旧 undo 文件能继续解析;game_profile_t::from_json 内对可选项(vsync / frl / pstate / prerender)也做了 contains() 检查,必填项缺失时由 undo_data_t::read() 的 try/catch 统一兜底,行为可预期。std::optional<T> 的现有 ADL 特化链路自然支持新增子类型,整体设计保持与 opengl_swapchain 一致。


170-238: 新增 API 与 merge 行为与既有模式一致,LGTM。

set_game_profile / get_game_profile / clear_game_profile(及对应的 base_extras 版本)与现有 opengl_swapchain 接口风格统一;merge 采用"newer 存在则覆写、否则保留"的语义,与原有 opengl_swapchain 的合并行为对称。结合 clear_* 提供显式清除入口,调用方可按需控制。

src/stream.cpp (2)

3026-3032: 生命周期顺序对称,restore 调用位置合理。

start 路径上 streaming_will_start()apply_stream_optimizations(),stop 路径上 restore_stream_optimizations()streaming_will_stop(),构成 LIFO 配对;即使本次会话中 apply 实际未写入任何撤销项(master switch 关闭、非 NVIDIA 主机等),restore 依据 undo 文件自身内容静默处理,外层无需额外条件分支。


3128-3136: ⚡ Quick win

The codebase handles both empty and parameterized game_cmd correctly:

  • Empty command: When app_id == 0, game_cmd remains empty and is explicitly designed to skip the per-game profile leg silently (documented in nvprefs_interface.h:45-46 and platform/common.h:753-754).
  • Parameterized command: The Windows implementation calls extract_exe_basename_w(game_cmd) in misc.cpp:1432 to extract the executable basename from the full command line before passing to the nvprefs interface.
  • Restoration path: The restore logic in driver_settings.cpp:595-597 explicitly validates empty exe_path and no-ops safely with a log message.

No robustness issues found. The platform layer is correctly designed to handle both cases.

Comment thread src/config.cpp
Comment thread src/platform/windows/nvprefs/nvprefs_interface.cpp
@qiin2333 qiin2333 force-pushed the feat/nvapi-stream-optimize branch 3 times, most recently from 140159a to ef92775 Compare May 6, 2026 11:12
qiin2333 added 3 commits May 6, 2026 19:12
Extend the nvprefs module so a Sunshine stream can apply NVIDIA driver
profile overrides for the launched game and (optionally) the BASE
profile, then roll them back when the stream ends.

Background
----------
Frame-Generation games (and any title with unlocked FPS) disable the
in-game V-Sync and let the GPU produce frames outside of the V-Blank
window. NVENC's display capture only sees frames that land inside that
window, so the streamed picture tears, drops frames, or exhibits very
high PCL latency. The standard "fix" is manual: open NVIDIA Control
Panel / NVApp, force V-Sync = On for the game EXE and lock the in-game
FPS slightly below the streaming display's refresh rate via FRL.

This change automates that procedure and ties it to the stream
lifecycle so casual users get the optimal settings without touching
NVCP, and the host machine is left untouched after the stream ends.

What's added
------------
* undo_data: new typed entries `game_profile_t` and `base_extras_t`
  with nlohmann adl_serializer plumbing. Backward-compatible with
  pre-existing `nvprefs_undo.json` files (the new fields are optional
  in `from_json`).
* nvprefs_common: 8 new options surfaced from `config::video.nv_*`:
    - nv_optimize_game        (master switch)
    - nv_force_vsync          (VSYNCMODE = ForceOn)
    - nv_lock_frame_rate      (FRL_FPS = client_fps + offset)
    - nv_frl_fps_offset       (default -2)
    - nv_frl_fps_override     (absolute override, 0 = use offset)
    - nv_prefer_max_performance (PREFERRED_PSTATE = PreferMax)
    - nv_low_latency_mode       (PRERENDERLIMIT = 1)
    - nv_apply_to_base_profile  (also write the BASE profile)
* driver_settings: four new methods that locate or create a shared
  `SunshineStreamGame` profile, attach the EXE to it, write the
  desired uint settings, and remember the pre-existing values for
  later restore. Each setting is idempotent: pre-write read-back
  skips the change (and the undo entry) when the driver already
  matches the desired value. Restore is also conservative: a setting
  is only rolled back when the current value still equals what we
  wrote, and the per-game profile is only deleted when we created it
  AND no application entries remain.
* nvprefs_interface: two public entry points
  `apply_stream_optimizations(exe, fps)` and
  `restore_stream_optimizations()` that merge the new undo data into
  the existing `%ProgramData%/Sunshine/nvprefs_undo.json` manifest,
  so a Sunshine crash mid-stream still allows the next launch to roll
  back the changes via `restore_from_and_delete_undo_file_if_exists`.

Lifecycle integration and Web UI exposure follow in the next commits.
Add a small platform abstraction so stream.cpp stays free of NV-
specific code:

  platf::apply_stream_optimizations(game_cmd, client_fps);
  platf::restore_stream_optimizations();

* common.h declares the new pair.
* linux/macos: no-op implementations.
* windows: parses the launched game's command line into a lower-cased
  EXE basename, gates on `config::video.nv_optimize_game`, then drives
  the new nvprefs entry points behind a `load()/unload()` pair.
* config: 8 new `video_t.nv_*` fields with parser entries
  (`nvenc_optimize_game`, `nvenc_force_vsync`, `nvenc_lock_frame_rate`,
  `nvenc_frl_fps_offset`, `nvenc_frl_fps_override`,
  `nvenc_prefer_max_performance`, `nvenc_low_latency_mode`,
  `nvenc_apply_to_base_profile`). Defaults: master switch off; when
  enabled, force V-Sync + FRL = client_fps - 2 are on, BASE profile
  write is off (per-game leg only, machine-safe).
* stream.cpp wires the apply call into the first non-control-only
  session start (after `streaming_will_start`) and the restore call
  into the last session stop (before `streaming_will_stop`).
Add a dedicated collapsible section "Stream-time NVIDIA Control Panel
auto-optimizations" under the NVENC encoder configuration tab,
visible on Windows only. The master switch hides the per-feature
controls when disabled to keep the page clean.

* useConfig.js: 8 new defaults aligned with config.cpp.
* NvidiaNvencEncoder.vue: master switch + 7 dependent controls
  (force V-Sync, FRL on/off, FRL offset, FRL override, prefer max
  performance, low latency mode, apply to BASE profile).
* en.json + zh.json: 18 i18n strings each, both validated as parseable
  JSON.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/stream.cpp (1)

1500-1526: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

动态 FPS 变更不会同步刷新 NVIDIA FRL 上限。

apply_stream_optimizationssession::start 里以 session.config.monitor.framerate(初始值)作为 FRL 依据写入驱动 profile(driver_settings.h 注释:client_fps … used to derive the FRL value)。当客户端通过 IDX_DYNAMIC_PARAM_CHANGE/FPS 把帧率改成例如 60→144 后,这里只更新了 session->config.monitor.framerate 与编码侧事件,但驱动端的 FRL 仍按旧帧率封顶,会反过来限制实际输出,与“串流期间动态 FPS”预期相悖。

建议要么在 FPS 变更分支里再次调用 apply_stream_optimizations 重新写 FRL,要么在文档/UI 中明确该优化只对会话启动时的 FPS 生效。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stream.cpp` around lines 1500 - 1526, Dynamic FPS changes via
IDX_DYNAMIC_PARAM_CHANGE (video::dynamic_param_type_e::FPS) only update
session->config.monitor.framerate and raise dynamic_param_change_events, but do
not re-apply driver FRL limits (client_fps used in driver_settings.h /
apply_stream_optimizations), causing the driver to remain capped at the original
FRL; fix by invoking apply_stream_optimizations (or the specific driver profile
update routine used in session::start) after updating
session->config.monitor.framerate so the new_fps is written to the driver
profile/FRL, ensuring the encoder/driver limits are updated to match the dynamic
FPS change.
♻️ Duplicate comments (1)
src/config.cpp (1)

1141-1142: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

请在配置解析处钳制 FRL 数值范围,避免异常值下传。

Line 1141 和 Line 1142 现在接受任意整数。手工配置负值/超大值会把越界参数继续传到后续驱动配置路径,增加异常配置与算术边界风险。建议在解析后立即按 UI 约束做钳制并打印 warning(offset: [-30, 30],override: [0, 500])。

🔧 建议修改
     int_f(vars, "nvenc_frl_fps_offset", video.nv_frl_fps_offset);
     int_f(vars, "nvenc_frl_fps_override", video.nv_frl_fps_override);
+    if (video.nv_frl_fps_offset < -30 || video.nv_frl_fps_offset > 30) {
+      BOOST_LOG(warning) << "Invalid nvenc_frl_fps_offset: ["sv << video.nv_frl_fps_offset << "], clamping to [-30, 30]"sv;
+      video.nv_frl_fps_offset = std::clamp(video.nv_frl_fps_offset, -30, 30);
+    }
+    if (video.nv_frl_fps_override < 0 || video.nv_frl_fps_override > 500) {
+      BOOST_LOG(warning) << "Invalid nvenc_frl_fps_override: ["sv << video.nv_frl_fps_override << "], clamping to [0, 500]"sv;
+      video.nv_frl_fps_override = std::clamp(video.nv_frl_fps_override, 0, 500);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.cpp` around lines 1141 - 1142, nvenc FRL
参数在解析后未钳制,会把任意整数传到后续驱动路径;在解析完 int_f(vars, "nvenc_frl_fps_offset",
video.nv_frl_fps_offset) 和 int_f(vars, "nvenc_frl_fps_override",
video.nv_frl_fps_override) 后,立即对 video.nv_frl_fps_offset 做范围钳制到 [-30, 30],对
video.nv_frl_fps_override 做范围钳制到 [0, 500],并在发生钳制时输出一条
warning(包含原始值与被钳制后的值及字段名),以便定位配置异常。 Ensure this runs in the same
parsing/initialization scope where int_f is called so downstream code always
sees the clamped values.
🧹 Nitpick comments (1)
src/stream.cpp (1)

1543-1556: 💤 Low value

validate_and_raisebool 分支为不可达分支。

auto value 全部由 param_valueint)传入,decltype(value) 永远是 intif constexpr (std::is_same_v<decltype(value), bool>) 这一支永远不会被实例化使用,徒增阅读负担;同时第 1554 行失败日志固定打印 param_value,若以后真的传 bool 也会与 value 不一致。可以直接删掉 bool 分支保留 int_value 赋值,或者把日志改成打印 value

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stream.cpp` around lines 1543 - 1556, The lambda validate_and_raise has
an unreachable bool branch; remove the constexpr bool branch and always assign
param.value.int_value = value, keep the call to
session->video.dynamic_param_change_events->raise(param) and the info log but
update both BOOST_LOG(warning) and BOOST_LOG(info) to print the actual variable
value (value) rather than param_value so logs stay consistent; ensure references
to param.value.bool_value are removed and only param.value.int_value is used in
validate_and_raise.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src_assets/common/assets/web/public/assets/locale/en.json`:
- Line 390: The string value for key "nvenc_lock_frame_rate_desc" contains the
awkward phrase "at exactly = refresh"; update that JSON entry to use natural
English (for example replace with "at exactly the refresh rate") and adjust the
sentence accordingly so it reads smoothly — e.g., "Use the driver's frame rate
limiter to cap the game slightly below the client refresh rate. Keeping in-game
FPS strictly below the streaming display's refresh rate avoids the high PCL
latency that occurs at exactly the refresh rate, and prevents stutter from going
above it." Ensure only the value for nvenc_lock_frame_rate_desc is changed and
the JSON remains valid.

In `@src/platform/windows/nvprefs/driver_settings.cpp`:
- Around line 603-652: The code currently uses the profile_handle from
NvAPI_DRS_FindApplicationByName (current owner) for both restoring settings and
for deleting the application/profile, which can delete a user’s unrelated
profile; keep using profile_handle for restore_uint_setting calls but when you
reach the deletion blocks use the original profile name stored in
undo_data.profile_name: call NvAPI_DRS_FindProfileByName(session_handle,
profile_name, &original_profile_handle) (where profile_name is filled from
undo_data.profile_name) and then call
NvAPI_DRS_DeleteApplication(session_handle, original_profile_handle, app_name)
and NvAPI_DRS_GetProfileInfo(session_handle, original_profile_handle, &info) /
NvAPI_DRS_DeleteProfile(session_handle, original_profile_handle) so you only
delete the profile that Sunshine created; preserve existing NVAPI_OK /
NVAPI_EXECUTABLE_NOT_FOUND checks and error logging.
- Around line 518-560: The handlers around NvAPI_DRS_FindApplicationByName,
NvAPI_DRS_FindProfileByName and NvAPI_DRS_GetApplicationInfo currently treat any
non-NVAPI_OK as "not found" and proceed to create profiles/applications; change
each to explicitly check for the API's "not found" status codes (e.g.
NVAPI_EXECUTABLE_NOT_FOUND, NVAPI_SETTING_NOT_FOUND or other documented
not-found codes) in an else if branch to trigger the creation logic, and add a
final else that logs nvapi_error_message(status) and returns false for all other
error codes; update the logic around NvAPI_DRS_FindApplicationByName,
NvAPI_DRS_FindProfileByName and NvAPI_DRS_GetApplicationInfo accordingly and
ensure profile_was_created/profile_name_used handling remains correct.

In `@src/platform/windows/nvprefs/nvprefs_interface.cpp`:
- Around line 83-95: The code currently deletes the undo file unconditionally;
change the logic in the block that calls
pimpl->driver_settings.restore_global_profile_to_undo,
restore_game_profile_to_undo, restore_base_extras_to_undo and save_settings so
the undo file is deleted only when the full restore succeeded (ok &&
pimpl->driver_settings.save_settings()); on any failure keep the undo file, emit
an appropriate error_message (including context) and return/propagate a failure
status to the caller so the caller can retry; locate the logic around the calls
to
restore_global_profile_to_undo/restore_game_profile_to_undo/restore_base_extras_to_undo/save_settings
and update the file-deletion path and error handling accordingly.

---

Outside diff comments:
In `@src/stream.cpp`:
- Around line 1500-1526: Dynamic FPS changes via IDX_DYNAMIC_PARAM_CHANGE
(video::dynamic_param_type_e::FPS) only update session->config.monitor.framerate
and raise dynamic_param_change_events, but do not re-apply driver FRL limits
(client_fps used in driver_settings.h / apply_stream_optimizations), causing the
driver to remain capped at the original FRL; fix by invoking
apply_stream_optimizations (or the specific driver profile update routine used
in session::start) after updating session->config.monitor.framerate so the
new_fps is written to the driver profile/FRL, ensuring the encoder/driver limits
are updated to match the dynamic FPS change.

---

Duplicate comments:
In `@src/config.cpp`:
- Around line 1141-1142: nvenc FRL 参数在解析后未钳制,会把任意整数传到后续驱动路径;在解析完 int_f(vars,
"nvenc_frl_fps_offset", video.nv_frl_fps_offset) 和 int_f(vars,
"nvenc_frl_fps_override", video.nv_frl_fps_override) 后,立即对
video.nv_frl_fps_offset 做范围钳制到 [-30, 30],对 video.nv_frl_fps_override 做范围钳制到 [0,
500],并在发生钳制时输出一条 warning(包含原始值与被钳制后的值及字段名),以便定位配置异常。 Ensure this runs in the
same parsing/initialization scope where int_f is called so downstream code
always sees the clamped values.

---

Nitpick comments:
In `@src/stream.cpp`:
- Around line 1543-1556: The lambda validate_and_raise has an unreachable bool
branch; remove the constexpr bool branch and always assign param.value.int_value
= value, keep the call to
session->video.dynamic_param_change_events->raise(param) and the info log but
update both BOOST_LOG(warning) and BOOST_LOG(info) to print the actual variable
value (value) rather than param_value so logs stay consistent; ensure references
to param.value.bool_value are removed and only param.value.int_value is used in
validate_and_raise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4201fb8f-b1f2-4e62-849a-d7fe16d4208e

📥 Commits

Reviewing files that changed from the base of the PR and between 8aedafd and 45b2b0b.

📒 Files selected for processing (20)
  • src/config.cpp
  • src/config.h
  • src/platform/common.h
  • src/platform/linux/misc.cpp
  • src/platform/macos/misc.mm
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/stream.cpp
  • src_assets/common/assets/web/composables/useConfig.js
  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
  • src_assets/common/assets/web/public/assets/locale/en.json
  • src_assets/common/assets/web/public/assets/locale/zh.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (3)
src_assets/**/*.{vue,js,html}

⚙️ CodeRabbit configuration file

src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

Files:

  • src_assets/common/assets/web/composables/useConfig.js
  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/driver_settings.h
  • src/config.h
  • src/platform/common.h
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/linux/misc.cpp
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/config.cpp
  • src/stream.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
src/platform/**

⚙️ CodeRabbit configuration file

src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

Files:

  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/common.h
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/linux/misc.cpp
  • src/platform/macos/misc.mm
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
🔇 Additional comments (13)
src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp (1)

139-157: 新增的 NvAPI DRS 封装接口实现方式一致且稳妥。

这几处转发与现有封装保持同一模式,错误处理路径也复用统一逻辑,便于维护。

src/config.h (1)

42-54: 新增配置字段定义清晰,语义完整。

字段命名、默认语义注释和回滚说明都比较明确,便于后续配置解析与 UI 映射。

src/platform/windows/nvprefs/nvprefs_common.h (1)

56-71: nvprefs_options 扩展设计合理,注释可读性高。

新增字段与业务目标对齐,默认值和行为说明也足够明确。

src/platform/linux/misc.cpp (1)

318-326: Linux 侧 no-op 钩子补齐得当。

接口对齐跨平台调用点,同时保持当前平台行为稳定,不会引入副作用。

src/platform/windows/nvprefs/driver_settings.h (1)

64-105: 接口声明清晰,文档充分。

新增的四个方法(check_and_modify_game_profile / restore_game_profile_to_undo / check_and_modify_base_extras / restore_base_extras_to_undo)成对出现且 Doxygen 注释完整地说明了 undo 语义、输入/输出参数与失败行为,与 check_and_modify_global_profile 已有风格一致,便于维护。

src/platform/windows/nvprefs/undo_data.cpp (3)

46-68: data_t 序列化扩展向后兼容性处理得当。

to_json 始终写入三个键(opengl_swapchain / game_profile / base_extras),且依赖前面已有的 adl_serializer<std::optional<T>>nullopt 序列化为 JSON nullfrom_json 对新加字段使用 j.contains(...) 先做存在性检查,确保旧版本 undo 文件(仅含 opengl_swapchain)仍可被解析,符合 PR 中“向后兼容”的设计目标。


104-152: game_profile_t / base_extras_t 序列化器实现一致且对未来扩展友好。

对四个可选设置(vsync/frl/pstate/prerender)都使用 if (j.contains(...)) 守护读取,使得后续新增字段或读取旧记录都不会抛 out_of_rangeprofile_was_created / application_was_added 这类必填字段则直接 get_to,语义合理。整体与 opengl_swapchain 的处理风格一致。


226-239: merge 正确扩展了新字段的合并语义。

合并逻辑沿用 opengl_swapchain 的“仅当较新数据存在时覆盖”策略,与 nvprefs_undo.json 跨会话合并、崩溃后回放的整体流程一致。建议在后续若再增字段时保持同一模式。

src/stream.cpp (3)

1459-1474: read_le_u32 / read_le_f32 解决对齐问题,写法稳健。

通过 std::memcpy + boost::endian::little_to_native 既避免了 ARM/PowerPC 上的非对齐访问 UB,也明确了线缆字节序为小端;read_le_f32 通过 uint32_t 中转再 memcpyfloat 也比 reinterpret_cast/*(float*) 更安全。注释把动机讲清楚了,建议保留。


3022-3050: 最后一个非仅控制流会话结束时的恢复顺序合理。

--running_non_control_only_sessions == 0 分支中先 restore_stream_optimizations()streaming_will_stop(),与 start 一侧 streaming_will_start()apply_stream_optimizations() 的入栈/出栈顺序对称;并且本路径已被 join 包裹的 10s 强杀任务覆盖,崩溃时也能依赖 nvprefs_undo.json 在下次启动回放,整体安全网较完整。

需要注意的一个已知行为:apply 仅在“首个非控制流会话”触发,restore 仅在“最后一个”触发——若中途有多 session 同时串流不同游戏,只会对第一个 game_cmd 写 profile,其他游戏不会获得优化;这与 PR 描述一致,但建议在用户文档里点明。


3145-3154: ⚡ Quick win

已在 Windows 平台层正确处理 game_cmd 的解析,无需修改。

所有提出的疑虑已在实现中得到妥善处理:

  1. 引号与参数剥离extract_exe_basename_w() (misc.cpp:1414-1436) 显式处理带引号的路径,使用 cmd.find('"', 1) 剥离引号,未引号路径则截断于首个空格/制表符,文档示例明确:"\"C:\\Games\\foo\\bar.exe\" --opt"L"bar.exe"

  2. Basename 提取与小写转换:使用 std::filesystem::path::filename() 提取文件名,再通过 towlower() 完成小写转换,符合 NvAPI 预期

  3. 空字符串处理:当 proc::proc.running() 返回 0 时,game_cmd 为空,extract_exe_basename_w() 在第 1415 行直接返回空 wstring,后续 driver_settings.cpp:494-496 明确检查 exe_name.empty() 并作为 no-op 返回 true,符合 nvprefs_interface.h:46 的文档说明

src/platform/windows/nvprefs/undo_data.h (2)

23-61: 新增类型设计良好,注释清晰。

setting_undo_tour_value/undo_value 二元组、game_profile_tprofile_was_created/application_was_added 默认初始化为 false,以及 base_extras_tgame_profile_t 的字段命名一致(vsync/frl/pstate/prerender),都让 restore_*_to_undo 实现可以采用统一模式。undo_value == nullopt → 还原即删除这一约定通过头注释表达,避免了语义歧义。


70-86: accessor API 与现有 opengl_swapchain 风格一致。

set/get/clear 三件套与已有 set_opengl_swapchain/get_opengl_swapchain 配套,便于 nvprefs_interface 在合并/写盘时统一处理;set_game_profile/set_base_extras 接收 const 引用、get_* 返回 std::optional<...> 的副本也避免了悬挂引用问题。

"nvenc_force_vsync": "Force V-Sync = On",
"nvenc_force_vsync_desc": "Set the driver V-Sync mode to ForceOn for the game's application profile. This is the single most important setting for Frame Generation games — it overrides the in-game V-Sync (which Frame Generation disables) and forces all generated frames back into the V-Blank window so NVENC can capture them.",
"nvenc_lock_frame_rate": "Limit game frame rate (FRL)",
"nvenc_lock_frame_rate_desc": "Use the driver's frame rate limiter to cap the game slightly below the client refresh rate. Keeping in-game FPS strictly below the streaming display's refresh rate avoids the high PCL latency that occurs at exactly = refresh, and prevents stutter from going above it.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

文案语法可再润色:at exactly = refresh 表达不自然。

建议改成更自然的英文(例如 at exactly the refresh rate),避免用户阅读卡顿。

✏️ 建议修改
-    "nvenc_lock_frame_rate_desc": "Use the driver's frame rate limiter to cap the game slightly below the client refresh rate. Keeping in-game FPS strictly below the streaming display's refresh rate avoids the high PCL latency that occurs at exactly = refresh, and prevents stutter from going above it.",
+    "nvenc_lock_frame_rate_desc": "Use the driver's frame rate limiter to cap the game slightly below the client refresh rate. Keeping in-game FPS strictly below the streaming display's refresh rate avoids the high PCL latency that occurs at exactly the refresh rate, and prevents stutter from going above it.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"nvenc_lock_frame_rate_desc": "Use the driver's frame rate limiter to cap the game slightly below the client refresh rate. Keeping in-game FPS strictly below the streaming display's refresh rate avoids the high PCL latency that occurs at exactly = refresh, and prevents stutter from going above it.",
"nvenc_lock_frame_rate_desc": "Use the driver's frame rate limiter to cap the game slightly below the client refresh rate. Keeping in-game FPS strictly below the streaming display's refresh rate avoids the high PCL latency that occurs at exactly the refresh rate, and prevents stutter from going above it.",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src_assets/common/assets/web/public/assets/locale/en.json` at line 390, The
string value for key "nvenc_lock_frame_rate_desc" contains the awkward phrase
"at exactly = refresh"; update that JSON entry to use natural English (for
example replace with "at exactly the refresh rate") and adjust the sentence
accordingly so it reads smoothly — e.g., "Use the driver's frame rate limiter to
cap the game slightly below the client refresh rate. Keeping in-game FPS
strictly below the streaming display's refresh rate avoids the high PCL latency
that occurs at exactly the refresh rate, and prevents stutter from going above
it." Ensure only the value for nvenc_lock_frame_rate_desc is changed and the
JSON remains valid.

Comment on lines +518 to +560
status = NvAPI_DRS_FindApplicationByName(session_handle, app_name, &profile_handle, &app_info);
if (status == NVAPI_OK) {
// Found — fetch the profile name for the undo manifest.
NVDRS_PROFILE existing = {};
existing.version = NVDRS_PROFILE_VER;
if (NvAPI_DRS_GetProfileInfo(session_handle, profile_handle, &existing) == NVAPI_OK) {
profile_name_used.assign(reinterpret_cast<const wchar_t *>(existing.profileName));
}
}
else {
// No profile owns this exe: get/create our SunshineStreamGame profile and add the exe to it.
NvAPI_UnicodeString profile_name = {};
fill_nvapi_string(profile_name, sunshine_game_profile_name);
status = NvAPI_DRS_FindProfileByName(session_handle, profile_name, &profile_handle);
if (status != NVAPI_OK) {
NVDRS_PROFILE profile = {};
profile.version = NVDRS_PROFILE_VER1;
fill_nvapi_string(profile.profileName, sunshine_game_profile_name);
status = NvAPI_DRS_CreateProfile(session_handle, &profile, &profile_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_CreateProfile() SunshineStreamGame failed");
return false;
}
profile_was_created = true;
}
profile_name_used = sunshine_game_profile_name;

NVDRS_APPLICATION application = {};
application.version = NVDRS_APPLICATION_VER_V1;
status = NvAPI_DRS_GetApplicationInfo(session_handle, profile_handle, app_name, &application);
if (status != NVAPI_OK) {
application = {};
application.version = NVDRS_APPLICATION_VER_V1;
application.isPredefined = 0;
fill_nvapi_string(application.appName, exe_name.c_str());
fill_nvapi_string(application.userFriendlyName, exe_name.c_str());
fill_nvapi_string(application.launcher, L"");

status = NvAPI_DRS_CreateApplication(session_handle, profile_handle, &application);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message(std::wstring(L"NvAPI_DRS_CreateApplication() failed for ") + exe_name);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

wc -l src/platform/windows/nvprefs/driver_settings.cpp

Repository: AlkaidLab/foundation-sunshine

Length of output: 126


🏁 Script executed:

# Read the target file around the flagged lines
sed -n '500,670p' src/platform/windows/nvprefs/driver_settings.cpp | cat -n

Repository: AlkaidLab/foundation-sunshine

Length of output: 8554


🏁 Script executed:

# Also check the NvAPI header files to understand error codes
find . -name "*nvapi*" -type f | head -20

Repository: AlkaidLab/foundation-sunshine

Length of output: 133


🏁 Script executed:

# Find NvAPI header files to understand error codes
find . -path "*/nvapi*" -name "*.h" -o -name "*.hpp" | grep -i nvapi

Repository: AlkaidLab/foundation-sunshine

Length of output: 55


🏁 Script executed:

# Search for NVAPI error code definitions in the codebase
rg "NVAPI_" --type cpp --type h -i "not_found|executable|error" | head -40

Repository: AlkaidLab/foundation-sunshine

Length of output: 140


🏁 Script executed:

# Check what error codes are used in driver_settings.cpp
rg "NVAPI_" src/platform/windows/nvprefs/driver_settings.cpp | head -30

Repository: AlkaidLab/foundation-sunshine

Length of output: 1545


🏁 Script executed:

# Look for NvAPI documentation or comments about error handling
rg "FindApplicationByName|FindProfileByName|GetApplicationInfo" -A 3 -B 1 src/platform/windows/nvprefs/driver_settings.cpp

Repository: AlkaidLab/foundation-sunshine

Length of output: 2326


🏁 Script executed:

# Search for NvAPI error code enum or definitions
rg "NVAPI_EXECUTABLE_NOT_FOUND|NVAPI_PROFILE_NOT_FOUND|NVAPI_APPLICATION_NOT_FOUND" -A 1 -B 1

Repository: AlkaidLab/foundation-sunshine

Length of output: 415


🏁 Script executed:

# Check NvAPI wrapper or header files
find . -type f -name "*.h" -o -name "*.cpp" | xargs grep -l "NVAPI_" | head -10

Repository: AlkaidLab/foundation-sunshine

Length of output: 233


🏁 Script executed:

# Look for NvAPI error code documentation or patterns in the codebase
rg "NVAPI_ERROR_|NVAPI_.*NOT_FOUND" | head -20

Repository: AlkaidLab/foundation-sunshine

Length of output: 1178


🏁 Script executed:

# Search for how other lookup functions handle their return values
rg "FindProfile|FindApplication" -B 2 -A 5 src/platform/windows/nvprefs/ | head -60

Repository: AlkaidLab/foundation-sunshine

Length of output: 5849


🏁 Script executed:

# Verify the exact semantics: check if there are more specific error codes for FindProfile/FindApplication operations
rg "NVAPI.*_NOT_FOUND|NVAPI.*NOT_FOUND" src/platform/windows/nvprefs/

Repository: AlkaidLab/foundation-sunshine

Length of output: 1178


🏁 Script executed:

# Check the NvAPI header/wrapper to see what error codes might be returned by these functions
head -150 src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp

Repository: AlkaidLab/foundation-sunshine

Length of output: 5309


分别处理查找失败和其他 NvAPI 错误,避免隐瞒真实故障。

代码在行 520、533、550 处对 FindApplicationByName()FindProfileByName()GetApplicationInfo() 的所有非 NVAPI_OK 返回值都作为"不存在"处理。然而同一文件中其他地方(行 135、165 等)明确区分了 NVAPI_EXECUTABLE_NOT_FOUNDNVAPI_SETTING_NOT_FOUND 等具体状态码,说明 NvAPI 会返回不同的错误码来表示"不存在"和其他故障(权限错误、会话错误等)。

当前做法会吞掉真实的 NvAPI 故障,即使在异常状态下仍继续创建 SunshineStreamGame profile 或新 application,可能导致状态污染。建议只对真正的"not found"状态走创建逻辑,其他状态直接返回错误。

等级:可根据 NvAPI 文档确认各函数返回的具体"not found"码,然后改为:

if (status == NVAPI_OK) { /* found */ }
else if (status == NVAPI_EXECUTABLE_NOT_FOUND || status == /*other specific not-found codes*/) { /* not found, create */ }
else { /* real error, log and return false */ }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/windows/nvprefs/driver_settings.cpp` around lines 518 - 560, The
handlers around NvAPI_DRS_FindApplicationByName, NvAPI_DRS_FindProfileByName and
NvAPI_DRS_GetApplicationInfo currently treat any non-NVAPI_OK as "not found" and
proceed to create profiles/applications; change each to explicitly check for the
API's "not found" status codes (e.g. NVAPI_EXECUTABLE_NOT_FOUND,
NVAPI_SETTING_NOT_FOUND or other documented not-found codes) in an else if
branch to trigger the creation logic, and add a final else that logs
nvapi_error_message(status) and returns false for all other error codes; update
the logic around NvAPI_DRS_FindApplicationByName, NvAPI_DRS_FindProfileByName
and NvAPI_DRS_GetApplicationInfo accordingly and ensure
profile_was_created/profile_name_used handling remains correct.

Comment on lines +603 to +652
NvDRSProfileHandle profile_handle = 0;
NVDRS_APPLICATION app_info = {};
app_info.version = NVDRS_APPLICATION_VER;
status = NvAPI_DRS_FindApplicationByName(session_handle, app_name, &profile_handle, &app_info);
if (status != NVAPI_OK) {
// Fall back to the named profile we (might have) created.
const std::wstring saved_name(undo_data.profile_name.begin(), undo_data.profile_name.end());
NvAPI_UnicodeString profile_name = {};
fill_nvapi_string(profile_name, saved_name.c_str());
status = NvAPI_DRS_FindProfileByName(session_handle, profile_name, &profile_handle);
if (status != NVAPI_OK) {
info_message(std::wstring(L"No profile found for ") + exe_name + L" during restore, skipping");
return true;
}
}

if (undo_data.vsync && !restore_uint_setting(session_handle, profile_handle, VSYNCMODE_ID, *undo_data.vsync, L"VSYNCMODE")) {
return false;
}
if (undo_data.frl && !restore_uint_setting(session_handle, profile_handle, FRL_FPS_ID, *undo_data.frl, L"FRL_FPS")) {
return false;
}
if (undo_data.pstate && !restore_uint_setting(session_handle, profile_handle, PREFERRED_PSTATE_ID, *undo_data.pstate, L"PREFERRED_PSTATE")) {
return false;
}
if (undo_data.prerender && !restore_uint_setting(session_handle, profile_handle, PRERENDERLIMIT_ID, *undo_data.prerender, L"PRERENDERLIMIT")) {
return false;
}

if (undo_data.application_was_added) {
status = NvAPI_DRS_DeleteApplication(session_handle, profile_handle, app_name);
if (status != NVAPI_OK && status != NVAPI_EXECUTABLE_NOT_FOUND) {
nvapi_error_message(status);
error_message(std::wstring(L"NvAPI_DRS_DeleteApplication() failed for ") + exe_name);
// Non-fatal: the user can clean up manually if it ever happens.
}
}

if (undo_data.profile_was_created) {
// Only delete the profile we created if it has no other applications attached.
NVDRS_PROFILE info = {};
info.version = NVDRS_PROFILE_VER;
if (NvAPI_DRS_GetProfileInfo(session_handle, profile_handle, &info) == NVAPI_OK && info.numOfApps == 0) {
status = NvAPI_DRS_DeleteProfile(session_handle, profile_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_DeleteProfile() SunshineStreamGame failed");
// Non-fatal.
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# 查看 driver_settings.cpp 的完整上下文
wc -l src/platform/windows/nvprefs/driver_settings.cpp

Repository: AlkaidLab/foundation-sunshine

Length of output: 126


🏁 Script executed:

# 查找 undo_data 的结构定义
rg -A 20 "struct.*undo_data|class.*undo_data" src/platform/windows/nvprefs/

Repository: AlkaidLab/foundation-sunshine

Length of output: 1872


🏁 Script executed:

# 查找这个函数的完整实现
sed -n '550,700p' src/platform/windows/nvprefs/driver_settings.cpp | head -200

Repository: AlkaidLab/foundation-sunshine

Length of output: 6308


🏁 Script executed:

# 查找 undo_data 的定义
rg "undo_data" src/platform/windows/nvprefs/driver_settings.cpp | head -5

Repository: AlkaidLab/foundation-sunshine

Length of output: 374


恢复 application/profile 时应使用原始 profile,而非跟随当前 owner。

这段代码在还原驱动程序设置时,先用 FindApplicationByName() 查找 exe 的当前所有者 profile(用意是处理用户在串流期间可能移动 exe 的情况),然后对该 profile 执行删除 application 和 profile 的操作。

问题在于:若用户在串流期间将 exe 移到了另一个 profile,代码会从那个 profile(而非原始的 Sunshine 创建的 profile)删除 application。如果目标 profile 只关联了这一个 exe,后续的 DeleteProfile() 甚至会删掉整个用户 profile,造成数据丢失。

应该改为:

  • 恢复驱动程序设置(vsync/frl/pstate/prerender)时,可以跟随当前 owner
  • 但删除 application/profile 时,应只操作 undo_data.profile_name 中记录的原始 profile,确保只清理 Sunshine 创建或修改的内容
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/windows/nvprefs/driver_settings.cpp` around lines 603 - 652, The
code currently uses the profile_handle from NvAPI_DRS_FindApplicationByName
(current owner) for both restoring settings and for deleting the
application/profile, which can delete a user’s unrelated profile; keep using
profile_handle for restore_uint_setting calls but when you reach the deletion
blocks use the original profile name stored in undo_data.profile_name: call
NvAPI_DRS_FindProfileByName(session_handle, profile_name,
&original_profile_handle) (where profile_name is filled from
undo_data.profile_name) and then call
NvAPI_DRS_DeleteApplication(session_handle, original_profile_handle, app_name)
and NvAPI_DRS_GetProfileInfo(session_handle, original_profile_handle, &info) /
NvAPI_DRS_DeleteProfile(session_handle, original_profile_handle) so you only
delete the profile that Sunshine created; preserve existing NVAPI_OK /
NVAPI_EXECUTABLE_NOT_FOUND checks and error logging.

Comment thread src/platform/windows/nvprefs/nvprefs_interface.cpp
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/platform/windows/nvprefs/undo_data.cpp (1)

86-152: 💤 Low value

新 ADL serializer 实现正确,但 game_profile_t::from_json 对必填字段无兜底。

game_profile_t::from_jsonprofile_name / exe_path / profile_was_created / application_was_added 全部走 j.at(...).get_to(...),任一缺失即抛异常。当前由 to_json 保证这些字段总会写出,所以正常路径没问题;不过如果以后被人手改 nvprefs_undo.json,或部分写入后掉电,这一异常会冒到 read() 的 catch 把整个 data 清空,连带 opengl_swapchain 的撤销信息也丢失。可考虑沿用 base_extras_t::from_jsonj.contains 风格,对每个字段加一层 contains,缺失就走默认值,让恢复链路对部分损坏更鲁棒。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/windows/nvprefs/undo_data.cpp` around lines 86 - 152,
game_profile_t::from_json currently uses j.at(...).get_to(...) for profile_name,
exe_path, profile_was_created and application_was_added which will throw on
missing keys; change it to follow base_extras_t::from_json style by checking
j.contains("...") before calling j.at(...).get_to(...) so absent fields keep
their defaults instead of throwing and wiping data; update
game_profile_t::from_json only (leave to_json unchanged) and ensure the four
named fields are handled with contains checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/platform/windows/nvprefs/driver_settings.cpp`:
- Around line 636-656: The cleanup currently logs failures from
NvAPI_DRS_DeleteApplication and NvAPI_DRS_DeleteProfile but still treats the
overall undo as successful, which causes restore_stream_optimizations() to drop
the undo; change the logic so that any non-OK return from
NvAPI_DRS_DeleteApplication (except NVAPI_EXECUTABLE_NOT_FOUND if that is
acceptable) or from NvAPI_DRS_DeleteProfile sets the function's overall result
to failure (e.g., return false or set ok=false) so the undo remains available
for retry; locate the checks around undo_data.application_was_added,
NvAPI_DRS_DeleteApplication, undo_data.profile_was_created,
NvAPI_DRS_GetProfileInfo and NvAPI_DRS_DeleteProfile and ensure they propagate
error status instead of only logging it.

In `@src/platform/windows/nvprefs/nvprefs_interface.cpp`:
- Around line 221-239: After restore_global_profile_to_undo succeeds but before
calling pimpl->undo_file->write_undo_data(*pimpl->undo_data), remove the
restored global-profile undo branch from pimpl->undo_data so the in-memory
manifest is trimmed; if that removal makes the manifest empty then call
pimpl->undo_file->delete_file(), clear pimpl->undo_data and pimpl->undo_file
(std::nullopt) and return appropriately instead of writing, otherwise proceed to
write the trimmed manifest via write_undo_data and handle any write error as
already done; use the existing symbols restore_global_profile_to_undo,
pimpl->undo_data, pimpl->undo_file, write_undo_data and delete_file to locate
the change.

---

Nitpick comments:
In `@src/platform/windows/nvprefs/undo_data.cpp`:
- Around line 86-152: game_profile_t::from_json currently uses
j.at(...).get_to(...) for profile_name, exe_path, profile_was_created and
application_was_added which will throw on missing keys; change it to follow
base_extras_t::from_json style by checking j.contains("...") before calling
j.at(...).get_to(...) so absent fields keep their defaults instead of throwing
and wiping data; update game_profile_t::from_json only (leave to_json unchanged)
and ensure the four named fields are handled with contains checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6b40482e-4561-4f96-8fc0-8351e264391c

📥 Commits

Reviewing files that changed from the base of the PR and between 45b2b0b and ef92775.

📒 Files selected for processing (20)
  • src/config.cpp
  • src/config.h
  • src/platform/common.h
  • src/platform/linux/misc.cpp
  • src/platform/macos/misc.mm
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/windows/nvprefs/undo_data.h
  • src/stream.cpp
  • src_assets/common/assets/web/composables/useConfig.js
  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
  • src_assets/common/assets/web/public/assets/locale/en.json
  • src_assets/common/assets/web/public/assets/locale/zh.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/platform/macos/misc.mm
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/platform/common.h
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/linux/misc.cpp
  • src/config.h
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/config.cpp
  • src/stream.cpp
  • src/platform/windows/nvprefs/undo_data.h
src/platform/**

⚙️ CodeRabbit configuration file

src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

Files:

  • src/platform/common.h
  • src/platform/windows/nvprefs/nvprefs_interface.h
  • src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
  • src/platform/windows/nvprefs/nvprefs_common.h
  • src/platform/windows/nvprefs/undo_data.cpp
  • src/platform/linux/misc.cpp
  • src/platform/windows/nvprefs/nvprefs_common.cpp
  • src/platform/windows/misc.cpp
  • src/platform/windows/nvprefs/nvprefs_interface.cpp
  • src/platform/windows/nvprefs/driver_settings.h
  • src/platform/windows/nvprefs/driver_settings.cpp
  • src/platform/windows/nvprefs/undo_data.h
src_assets/**/*.{vue,js,html}

⚙️ CodeRabbit configuration file

src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

Files:

  • src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue
  • src_assets/common/assets/web/composables/useConfig.js
🔇 Additional comments (16)
src/config.cpp (2)

1138-1156: FRL 相关 clamp 已落实,吸收了前次审查关于负值 / 越界的反馈。

nv_frl_fps_offset 限定在 [-30, 30]nv_frl_fps_override 限定在 [0, 500],越界都会打 warning 日志再 clamp,正好与 UI slider 范围对齐,也避免了 client_fps + offset 之后被传到 NvAPI 路径上发生有符号溢出 / 非法 FRL 值。已对应历史反馈,无需新动作。


408-415: 默认值与字段顺序匹配 config.h,含义与注释一致。

nv_optimize_game = false(总开关默认关闭)、nv_apply_to_base_profile = false(避免无意中写全局 BASE 配置文件)符合 PR 中"opt-in、非 NVIDIA 系统静默 no-op"的安全策略;nv_frl_fps_offset = -2 为 FRL 留出 2fps 余量也符合实际串流体验。该聚合初始化器的字段顺序与 video_t 中插入位置(nv_sunshine_high_power_mode 之后、vdd_keep_enabled 之前)一致。

src/platform/common.h (1)

745-766: 接口声明清晰,文档完备。

apply_stream_optimizations/restore_stream_optimizations 的位置紧邻现有 streaming_will_start/streaming_will_stop,行为契约(NVIDIA-only、空字符串允许、无 #ifdef)以及 client_fps 用途都在 doxygen 中讲清楚。后续平台实现按此契约即可。

src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp (1)

138-157: 新增 NvAPI 包装函数与既有模式一致。

四个新 NvAPI_DRS_* 包装严格沿用 call_interface<decltype(...)>(name, args...) 的调度方式,未引入新的资源/线程语义,正常通过 nvapi_QueryInterface 表查找;与 NvAPI_DRS_GetBaseProfile 等老 wrapper 风格一致。

src/platform/windows/nvprefs/undo_data.cpp (1)

50-66: JSON schema 扩展处理向后兼容到位。

to_json 一并写入 opengl_swapchain / game_profile / base_extras(即便是 nullopt 也通过 adl_serializer<std::optional<T>> 序列化为 null),from_json 对新字段使用 j.contains 做兜底——读旧版崩溃残留的 undo 文件不会抛异常;写新版时三段字段始终齐全,未来版本读取也安全。

src/platform/windows/nvprefs/undo_data.h (1)

23-86: 类型与访问器设计对称、文档齐全。

setting_undo_t / game_profile_t / base_extras_t 的字段语义(our_value/undo_valuenullopt = 该设置先前不存在 = 恢复时删除)写得很清楚,三组 set/get/clear 也与 opengl_swapchain 既有对称。game_profile_t::profile_was_created / application_was_added 默认 false 也符合"未触动"的安全恢复语义。

src/platform/windows/nvprefs/driver_settings.h (1)

64-105: 接口签名与文档对齐 PR 设计。

四个方法的语义(按 EXE basename 路由、带 client_fps 派生 FRL、显式 out-param 撤销数据、并提示调用方需 merge 之前的 undo 数据)描述清晰;std::wstring 与 NvAPI NvAPI_UnicodeString 也匹配。后续审查 driver_settings.cpp(不在本次改动列表)时再核对实际语义是否与文档一致。

src/config.h (1)

41-54: 字段分组、命名与默认值(在 config.cpp 中)一致,opt-in 定位明确。

nv_optimize_game 作为总开关,且默认 false、与 nv_apply_to_base_profile 同样默认关闭,符合 PR 描述的"非 NVIDIA 系统快速 no-op、机器级写入显式 opt-in"目标。两个 int 字段(nv_frl_fps_offset / nv_frl_fps_override)也对应 config.cpp 中的解析与 clamp 路径。

src/platform/linux/misc.cpp (1)

318-326: ⚡ Quick win

Linux 端 no-op 实现合理。

参数命名留空(const std::string &, int)符合 no-op 语义,注释也明确说明 Linux 暂未提供等价能力。已确认 src/platform/macos/misc.mm 中存在对应的 no-op 实现(lines 247-249、252-254),其中 apply_stream_optimizationsrestore_stream_optimizations 均已实现,无未定义符号风险。

src_assets/common/assets/web/configs/tabs/encoders/NvidiaNvencEncoder.vue (1)

197-205: 新的折叠面板仍缺少 ARIA 关联。

这个 accordion 的 button 和折叠内容区之间还没有补齐稳定的 header id、aria-controlsaria-labelledby,屏幕阅读器依然无法把触发器和内容区域正确关联起来。

As per coding guidelines, src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

src/platform/windows/nvprefs/driver_settings.cpp (1)

592-650: 清理 application/profile 时不要沿用当前 owner 的 profile_handle

这里先跟随 FindApplicationByName() 的当前 owner 来恢复 setting 没问题,但后面的 DeleteApplication() / DeleteProfile() 如果继续使用这个 handle,用户在串流期间把 exe 挪到别的 profile 时,就会误删那个 profile 上的记录,甚至删掉整个用户 profile。清理阶段应回到 undo_data.profile_name 记录的原始 profile 上执行。

As per coding guidelines, src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

src/platform/windows/nvprefs/nvprefs_interface.cpp (1)

85-107: 恢复不完整时不要删除 undo 文件。

现在只要任一 restore 或 save_settings() 失败,代码仍然会继续删除 undo 文件。这样下次启动就无法再重试回滚,用户可能永久保留一部分被 Sunshine 改过的驱动状态。只有完整恢复并成功保存后才应该删文件。

As per coding guidelines, src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

src_assets/common/assets/web/public/assets/locale/en.json (1)

390-390: 文案语法问题仍存在(历史已提过)。

Line 390 的 at exactly = refresh 依然不自然,建议改为 at exactly the refresh rate

src/platform/windows/nvprefs/nvprefs_common.h (1)

56-71: 新增 NV 优化配置项设计清晰,默认策略稳妥。

Line 59-71 采用“主开关默认关闭、子项给出推荐默认值”的方式,且注释对作用域与回滚时机说明完整,可维护性不错。

src/stream.cpp (2)

1459-1474: 动态参数解析的未对齐读取风险处理得当。

这段改为 memcpy + little_to_native 读取 payload,规避了直接指针转换带来的未对齐访问 UB,跨平台内存安全性更好。

Also applies to: 1494-1496, 1509-1509, 1536-1536


3048-3049: 流开始/结束时的 GPU 优化钩子时机对称。

首个非控制流会话时应用优化、最后一个非控制流会话结束时恢复优化,生命周期闭环完整,行为边界清晰。

Also applies to: 3146-3154

Comment on lines +636 to +656
if (undo_data.application_was_added) {
status = NvAPI_DRS_DeleteApplication(session_handle, profile_handle, app_name);
if (status != NVAPI_OK && status != NVAPI_EXECUTABLE_NOT_FOUND) {
nvapi_error_message(status);
error_message(std::wstring(L"NvAPI_DRS_DeleteApplication() failed for ") + exe_name);
// Non-fatal: the user can clean up manually if it ever happens.
}
}

if (undo_data.profile_was_created) {
// Only delete the profile we created if it has no other applications attached.
NVDRS_PROFILE info = {};
info.version = NVDRS_PROFILE_VER;
if (NvAPI_DRS_GetProfileInfo(session_handle, profile_handle, &info) == NVAPI_OK && info.numOfApps == 0) {
status = NvAPI_DRS_DeleteProfile(session_handle, profile_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_DeleteProfile() SunshineStreamGame failed");
// Non-fatal.
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

清理失败也要让 restore 返回失败。

这里删除 application/profile 的失败只记日志,不会影响 ok。但上层 restore_stream_optimizations() 会在本函数返回 true 后清掉这段 undo,所以一旦清理失败,残留的 Sunshine profile/application 以后就不会再自动重试回滚了。

🛠️ 建议修改
     if (undo_data.application_was_added) {
       status = NvAPI_DRS_DeleteApplication(session_handle, profile_handle, app_name);
       if (status != NVAPI_OK && status != NVAPI_EXECUTABLE_NOT_FOUND) {
         nvapi_error_message(status);
         error_message(std::wstring(L"NvAPI_DRS_DeleteApplication() failed for ") + exe_name);
-        // Non-fatal: the user can clean up manually if it ever happens.
+        ok = false;
       }
     }

     if (undo_data.profile_was_created) {
       // Only delete the profile we created if it has no other applications attached.
       NVDRS_PROFILE info = {};
       info.version = NVDRS_PROFILE_VER;
-      if (NvAPI_DRS_GetProfileInfo(session_handle, profile_handle, &info) == NVAPI_OK && info.numOfApps == 0) {
+      status = NvAPI_DRS_GetProfileInfo(session_handle, profile_handle, &info);
+      if (status != NVAPI_OK) {
+        nvapi_error_message(status);
+        error_message("NvAPI_DRS_GetProfileInfo() SunshineStreamGame failed");
+        ok = false;
+      }
+      else if (info.numOfApps == 0) {
         status = NvAPI_DRS_DeleteProfile(session_handle, profile_handle);
         if (status != NVAPI_OK) {
           nvapi_error_message(status);
           error_message("NvAPI_DRS_DeleteProfile() SunshineStreamGame failed");
-          // Non-fatal.
+          ok = false;
         }
       }
     }

As per coding guidelines, src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/windows/nvprefs/driver_settings.cpp` around lines 636 - 656, The
cleanup currently logs failures from NvAPI_DRS_DeleteApplication and
NvAPI_DRS_DeleteProfile but still treats the overall undo as successful, which
causes restore_stream_optimizations() to drop the undo; change the logic so that
any non-OK return from NvAPI_DRS_DeleteApplication (except
NVAPI_EXECUTABLE_NOT_FOUND if that is acceptable) or from
NvAPI_DRS_DeleteProfile sets the function's overall result to failure (e.g.,
return false or set ok=false) so the undo remains available for retry; locate
the checks around undo_data.application_was_added, NvAPI_DRS_DeleteApplication,
undo_data.profile_was_created, NvAPI_DRS_GetProfileInfo and
NvAPI_DRS_DeleteProfile and ensure they propagate error status instead of only
logging it.

Comment thread src/platform/windows/nvprefs/nvprefs_interface.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant