-
-
Notifications
You must be signed in to change notification settings - Fork 110
feat(windows): add capture plugin framework #519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
76b7150
a38b77e
9ac2d72
f07169e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| cmake_minimum_required(VERSION 3.20) | ||
| project(sunshine_nvfbc_plugin LANGUAGES CXX) | ||
|
|
||
| set(CMAKE_CXX_STANDARD 17) | ||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
|
|
||
| # Path to Sunshine source root (for plugin API header) | ||
| set(SUNSHINE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH | ||
| "Path to Sunshine source root") | ||
|
|
||
| add_library(sunshine_nvfbc SHARED | ||
| sunshine_nvfbc_plugin.cpp | ||
| ) | ||
|
|
||
| target_include_directories(sunshine_nvfbc PRIVATE | ||
| "${SUNSHINE_SOURCE_DIR}" | ||
| ) | ||
|
|
||
| # NvFBC does not have a public import library on Windows. | ||
| # The plugin loads NvFBC64.dll at runtime via LoadLibrary. | ||
|
|
||
| # Output to plugins/ directory for easy deployment | ||
| set_target_properties(sunshine_nvfbc PROPERTIES | ||
| OUTPUT_NAME "sunshine_nvfbc" | ||
| PREFIX "" | ||
| RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins" | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| /** | ||
| * @file examples/capture_plugin_nvfbc/sunshine_nvfbc_plugin.cpp | ||
| * @brief Example NvFBC capture plugin for Sunshine (Windows). | ||
| * | ||
| * This is a SKELETON implementation showing how to build an NvFBC capture | ||
| * plugin DLL for Sunshine. You need to fill in the actual NvFBC API calls | ||
| * based on the Windows NvFBC API definitions (from NVIDIA Grid SDK or | ||
| * reverse-engineered from keylase/nvidia-patch's nvfbcwrp). | ||
| * | ||
| * Build: Compile as a DLL named "sunshine_nvfbc.dll" and place in | ||
| * Sunshine's "plugins/" directory. | ||
| * | ||
| * Usage: Set capture = nvfbc in sunshine.conf | ||
| * | ||
| * Prerequisites: | ||
| * - NVIDIA GPU with driver patched for NvFBC (keylase/nvidia-patch) | ||
| * - NvFBC64.dll present in system (installed with NVIDIA driver) | ||
| */ | ||
|
|
||
| #include <cstring> | ||
| #include <vector> | ||
| #include <Windows.h> | ||
|
|
||
| // Include the Sunshine capture plugin API | ||
| #include "src/platform/windows/capture_plugin/capture_plugin_api.h" | ||
|
|
||
| // ============================================================================ | ||
| // Windows NvFBC API definitions (reverse-engineered / from Grid SDK) | ||
| // These must match the actual driver's NvFBC interface. | ||
| // Refer to: https://github.com/keylase/nvidia-patch/blob/master/win/nvfbcwrp/ | ||
| // ============================================================================ | ||
|
|
||
| // NvFBC function pointer type | ||
| typedef void *(*NvFBCCreateInstance_t)(unsigned int magic); | ||
|
|
||
| // TODO: Define the actual Windows NvFBC structures here: | ||
| // - NVFBC_SESSION_HANDLE | ||
| // - NVFBC_CREATE_PARAMS | ||
| // - NVFBC_TOSYS_SETUP_PARAMS | ||
| // - NVFBC_TOSYS_GRAB_FRAME_PARAMS | ||
| // etc. | ||
|
|
||
| // Magic private data to bypass consumer GPU check | ||
| static const unsigned int MAGIC_PRIVATE_DATA[4] = { | ||
| 0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA | ||
| }; | ||
|
|
||
| // ============================================================================ | ||
| // Plugin session state | ||
| // ============================================================================ | ||
|
|
||
| struct nvfbc_session { | ||
| HMODULE nvfbc_dll; // NvFBC64.dll handle | ||
| NvFBCCreateInstance_t create_fn; // NvFBCCreateInstance function | ||
| void *fbc_handle; // NvFBC session handle | ||
|
|
||
| int width; | ||
| int height; | ||
| int framerate; | ||
|
|
||
| // Frame buffer for ToSys capture | ||
| std::vector<uint8_t> frame_buffer; | ||
| bool frame_ready; | ||
| bool interrupted; | ||
| }; | ||
|
|
||
| // ============================================================================ | ||
| // Plugin API implementation | ||
| // ============================================================================ | ||
|
|
||
| extern "C" { | ||
|
|
||
| SUNSHINE_CAPTURE_EXPORT int | ||
| sunshine_capture_get_info(sunshine_capture_plugin_info_t *info) { | ||
| if (!info) return -1; | ||
|
|
||
| info->abi_version = SUNSHINE_CAPTURE_PLUGIN_ABI_VERSION; | ||
| info->name = "nvfbc"; | ||
| info->version = "0.1.0"; | ||
| info->author = "Community"; | ||
|
|
||
| // Support system memory (ToSys) and CUDA (ToCuda) | ||
| info->supported_mem_types = (1 << SUNSHINE_MEM_SYSTEM) | (1 << SUNSHINE_MEM_CUDA); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| SUNSHINE_CAPTURE_EXPORT int | ||
| sunshine_capture_enum_displays( | ||
| sunshine_mem_type_e mem_type, | ||
| sunshine_display_info_t *displays, | ||
| int max_displays) { | ||
| // NvFBC captures the entire desktop, so we expose one "display" | ||
| if (displays && max_displays > 0) { | ||
| strncpy(displays[0].name, "NvFBC Desktop", sizeof(displays[0].name) - 1); | ||
| displays[0].name[sizeof(displays[0].name) - 1] = '\0'; | ||
| displays[0].width = GetSystemMetrics(SM_CXSCREEN); | ||
| displays[0].height = GetSystemMetrics(SM_CYSCREEN); | ||
| displays[0].is_primary = 1; | ||
|
Comment on lines
+223
to
+229
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: SM_CXSCREEN returns the width of the primary display monitor's screen in pixels. SM_CXVIRTUALSCREEN returns the width of the entire virtual screen, which is the bounding rectangle encompassing all display monitors in a multi-monitor setup. In single-monitor configurations, these values are identical. GetSystemMetrics is not DPI-aware by default; for per-monitor DPI-aware apps, use GetSystemMetricsForDpi instead. Citations:
🏁 Script executed: # Search for the specific file mentioned in the review
fd "sunshine_nvfbc_plugin.cpp" --type fRepository: AlkaidLab/foundation-sunshine Length of output: 128 🏁 Script executed: # Also search for any related display enumeration code
rg "GetSystemMetrics.*SM_CXSCREEN|SM_CXVIRTUALSCREEN" -A 3 -B 3Repository: AlkaidLab/foundation-sunshine Length of output: 1773 全桌面尺寸获取错误,多显示器场景下会报告错误分辨率。 代码注释说"NvFBC captures the full desktop",但 建议修改- displays[0].width = GetSystemMetrics(SM_CXSCREEN);
- displays[0].height = GetSystemMetrics(SM_CYSCREEN);
+ displays[0].width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ displays[0].height = GetSystemMetrics(SM_CYVIRTUALSCREEN);🤖 Prompt for AI Agents |
||
| } | ||
| return 1; | ||
| } | ||
|
|
||
| SUNSHINE_CAPTURE_EXPORT int | ||
| sunshine_capture_create_session( | ||
| sunshine_mem_type_e mem_type, | ||
| const char *display_name, | ||
| const sunshine_video_config_t *config, | ||
| sunshine_capture_session_t *session) { | ||
| if (!config || !session) return -1; | ||
|
|
||
| auto *s = new nvfbc_session {}; | ||
| s->width = config->width; | ||
| s->height = config->height; | ||
| s->framerate = config->framerate; | ||
| s->frame_ready = false; | ||
| s->interrupted = false; | ||
|
|
||
| // Load NvFBC64.dll | ||
| s->nvfbc_dll = LoadLibraryExA("NvFBC64.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); | ||
| if (!s->nvfbc_dll) { | ||
| delete s; | ||
| return -1; | ||
| } | ||
|
|
||
| s->create_fn = reinterpret_cast<NvFBCCreateInstance_t>( | ||
| GetProcAddress(s->nvfbc_dll, "NvFBCCreateInstance")); | ||
| if (!s->create_fn) { | ||
| FreeLibrary(s->nvfbc_dll); | ||
| delete s; | ||
| return -1; | ||
| } | ||
|
|
||
| // TODO: Initialize NvFBC session with MAGIC_PRIVATE_DATA | ||
| // This requires the actual Windows NvFBC API structures. | ||
| // | ||
| // Pseudocode: | ||
| // NVFBC_CREATE_PARAMS create_params = {}; | ||
| // create_params.privateData = MAGIC_PRIVATE_DATA; | ||
| // create_params.privateDataSize = sizeof(MAGIC_PRIVATE_DATA); | ||
| // auto status = nvFBCCreate(&create_params, &s->fbc_handle); | ||
| // | ||
| // NVFBC_TOSYS_SETUP_PARAMS setup = {}; | ||
| // setup.bufferFormat = NVFBC_BUFFER_FORMAT_BGRA; | ||
| // setup.ppBuffer = &s->frame_buffer_ptr; | ||
| // status = nvFBCToSysSetUp(s->fbc_handle, &setup); | ||
|
|
||
| // Allocate frame buffer for ToSys mode | ||
| s->frame_buffer.resize(s->width * s->height * 4); // BGRA | ||
|
|
||
| *session = reinterpret_cast<sunshine_capture_session_t>(s); | ||
| return 0; | ||
|
Comment on lines
+271
to
+286
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 在真正接通 GrabFrame 前,不要把会话标记为可用。 Line 284 把 🛠️ 一种更安全的临时处理- s->session_valid = true;
- *session = reinterpret_cast<sunshine_capture_session_t>(s);
- return 0;
+ FreeLibrary(s->nvfbc_dll);
+ delete s;
+ return -1;Also applies to: 311-359 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| SUNSHINE_CAPTURE_EXPORT void | ||
| sunshine_capture_destroy_session(sunshine_capture_session_t session) { | ||
| if (!session) return; | ||
|
|
||
| auto *s = reinterpret_cast<nvfbc_session *>(session); | ||
|
|
||
| // TODO: Destroy NvFBC session | ||
| // nvFBCRelease(s->fbc_handle); | ||
|
|
||
| if (s->nvfbc_dll) { | ||
| FreeLibrary(s->nvfbc_dll); | ||
| } | ||
|
|
||
| delete s; | ||
| } | ||
|
|
||
| SUNSHINE_CAPTURE_EXPORT sunshine_capture_result_e | ||
| sunshine_capture_next_frame( | ||
| sunshine_capture_session_t session, | ||
| sunshine_frame_t *frame, | ||
| int timeout_ms) { | ||
| if (!session || !frame) return SUNSHINE_CAPTURE_ERROR; | ||
|
|
||
| auto *s = reinterpret_cast<nvfbc_session *>(session); | ||
|
|
||
| if (s->interrupted) { | ||
| return SUNSHINE_CAPTURE_INTERRUPTED; | ||
| } | ||
|
|
||
| // TODO: Actual NvFBC grab frame call | ||
| // | ||
| // Pseudocode: | ||
| // NVFBC_TOSYS_GRAB_FRAME_PARAMS grab = {}; | ||
| // grab.dwFlags = NVFBC_TOSYS_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY; | ||
| // grab.dwTimeoutMs = timeout_ms; | ||
| // auto status = nvFBCToSysGrabFrame(s->fbc_handle, &grab); | ||
| // | ||
| // if (status == NVFBC_SUCCESS) { | ||
| // frame->data = s->frame_buffer_ptr; // Pointer set by NvFBC | ||
| // frame->width = s->width; | ||
| // ... | ||
| // return SUNSHINE_CAPTURE_OK; | ||
| // } | ||
|
|
||
| // Placeholder: return timeout | ||
| return SUNSHINE_CAPTURE_TIMEOUT; | ||
| } | ||
|
|
||
| SUNSHINE_CAPTURE_EXPORT void | ||
| sunshine_capture_release_frame( | ||
| sunshine_capture_session_t session, | ||
| sunshine_frame_t *frame) { | ||
| // NvFBC ToSys: frames are managed by NvFBC internally, no release needed | ||
| // NvFBC ToCuda: may need to unlock CUDA resource | ||
| (void) session; | ||
| (void) frame; | ||
| } | ||
|
|
||
| SUNSHINE_CAPTURE_EXPORT int | ||
| sunshine_capture_is_hdr(sunshine_capture_session_t session) { | ||
| // NvFBC typically does not support HDR | ||
| (void) session; | ||
| return 0; | ||
| } | ||
|
|
||
| SUNSHINE_CAPTURE_EXPORT void | ||
| sunshine_capture_interrupt(sunshine_capture_session_t session) { | ||
| if (!session) return; | ||
|
|
||
| auto *s = reinterpret_cast<nvfbc_session *>(session); | ||
| s->interrupted = true; | ||
| } | ||
|
|
||
| } // extern "C" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
interruptedfield is a plainbool, but per the API documentation incapture_plugin_api.h(line 201),sunshine_capture_interruptis "called from a different thread." This creates a data race whennext_framereadss->interruptedon the capture thread whileinterruptwrites it from another thread. This should bestd::atomic<bool>to ensure thread safety.Even though this is example/skeleton code, it will likely be copied by plugin authors and should demonstrate correct patterns.