duplicator-monitor-capture.c 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  1. #include <windows.h>
  2. #include <obs-module.h>
  3. #include <util/dstr.h>
  4. #include <util/threading.h>
  5. #include "cursor-capture.h"
  6. #include "../../libobs/util/platform.h"
  7. #include "../../libobs-winrt/winrt-capture.h"
  8. #define do_log(level, format, ...) \
  9. blog(level, "[duplicator-monitor-capture: '%s'] " format, \
  10. obs_source_get_name(capture->source), ##__VA_ARGS__)
  11. #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
  12. #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
  13. #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
  14. /* clang-format off */
  15. #define TEXT_MONITOR_CAPTURE obs_module_text("MonitorCapture")
  16. #define TEXT_CAPTURE_CURSOR obs_module_text("CaptureCursor")
  17. #define TEXT_COMPATIBILITY obs_module_text("Compatibility")
  18. #define TEXT_MONITOR obs_module_text("Monitor")
  19. #define TEXT_PRIMARY_MONITOR obs_module_text("PrimaryMonitor")
  20. #define TEXT_METHOD obs_module_text("Method")
  21. #define TEXT_METHOD_AUTO obs_module_text("WindowCapture.Method.Auto")
  22. #define TEXT_METHOD_DXGI obs_module_text("Method.DXGI")
  23. #define TEXT_METHOD_WGC obs_module_text("Method.WindowsGraphicsCapture")
  24. #define TEXT_FORCE_SDR obs_module_text("ForceSdr")
  25. /* clang-format on */
  26. #define RESET_INTERVAL_SEC 3.0f
  27. typedef BOOL (*PFN_winrt_capture_supported)();
  28. typedef BOOL (*PFN_winrt_capture_cursor_toggle_supported)();
  29. typedef struct winrt_capture *(*PFN_winrt_capture_init_monitor)(
  30. BOOL cursor, HMONITOR monitor, BOOL force_sdr);
  31. typedef void (*PFN_winrt_capture_free)(struct winrt_capture *capture);
  32. typedef BOOL (*PFN_winrt_capture_active)(const struct winrt_capture *capture);
  33. typedef enum gs_color_space (*PFN_winrt_capture_get_color_space)(
  34. const struct winrt_capture *capture);
  35. typedef void (*PFN_winrt_capture_render)(struct winrt_capture *capture);
  36. typedef uint32_t (*PFN_winrt_capture_width)(const struct winrt_capture *capture);
  37. typedef uint32_t (*PFN_winrt_capture_height)(
  38. const struct winrt_capture *capture);
  39. struct winrt_exports {
  40. PFN_winrt_capture_supported winrt_capture_supported;
  41. PFN_winrt_capture_cursor_toggle_supported
  42. winrt_capture_cursor_toggle_supported;
  43. PFN_winrt_capture_init_monitor winrt_capture_init_monitor;
  44. PFN_winrt_capture_free winrt_capture_free;
  45. PFN_winrt_capture_active winrt_capture_active;
  46. PFN_winrt_capture_get_color_space winrt_capture_get_color_space;
  47. PFN_winrt_capture_render winrt_capture_render;
  48. PFN_winrt_capture_width winrt_capture_width;
  49. PFN_winrt_capture_height winrt_capture_height;
  50. };
  51. enum display_capture_method {
  52. METHOD_AUTO,
  53. METHOD_DXGI,
  54. METHOD_WGC,
  55. };
  56. struct duplicator_capture {
  57. obs_source_t *source;
  58. pthread_mutex_t update_mutex;
  59. char monitor_id[128];
  60. char id[128];
  61. char alt_id[128];
  62. char monitor_name[64];
  63. enum display_capture_method method;
  64. bool reset_wgc;
  65. HMONITOR handle;
  66. bool capture_cursor;
  67. bool force_sdr;
  68. bool showing;
  69. LONG logged_width;
  70. LONG logged_height;
  71. long x;
  72. long y;
  73. int rot;
  74. uint32_t width;
  75. uint32_t height;
  76. gs_duplicator_t *duplicator;
  77. float reset_timeout;
  78. struct cursor_data cursor_data;
  79. void *winrt_module;
  80. struct winrt_exports exports;
  81. struct winrt_capture *capture_winrt;
  82. };
  83. struct duplicator_monitor_info {
  84. char device_id[128];
  85. char id[128];
  86. char alt_id[128];
  87. char name[128];
  88. RECT rect;
  89. HMONITOR handle;
  90. };
  91. /* ------------------------------------------------------------------------- */
  92. static const char *get_method_name(int method)
  93. {
  94. const char *method_name = "";
  95. switch (method) {
  96. case METHOD_DXGI:
  97. method_name = "DXGI";
  98. break;
  99. case METHOD_WGC:
  100. method_name = "WGC";
  101. break;
  102. }
  103. return method_name;
  104. }
  105. static bool GetMonitorTarget(LPCWSTR device,
  106. DISPLAYCONFIG_TARGET_DEVICE_NAME *target)
  107. {
  108. bool found = false;
  109. UINT32 numPath, numMode;
  110. if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPath,
  111. &numMode) == ERROR_SUCCESS) {
  112. DISPLAYCONFIG_PATH_INFO *paths =
  113. bmalloc(numPath * sizeof(DISPLAYCONFIG_PATH_INFO));
  114. DISPLAYCONFIG_MODE_INFO *modes =
  115. bmalloc(numMode * sizeof(DISPLAYCONFIG_MODE_INFO));
  116. if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPath, paths,
  117. &numMode, modes,
  118. NULL) == ERROR_SUCCESS) {
  119. for (size_t i = 0; i < numPath; ++i) {
  120. const DISPLAYCONFIG_PATH_INFO *const path =
  121. &paths[i];
  122. DISPLAYCONFIG_SOURCE_DEVICE_NAME
  123. source;
  124. source.header.type =
  125. DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
  126. source.header.size = sizeof(source);
  127. source.header.adapterId =
  128. path->sourceInfo.adapterId;
  129. source.header.id = path->sourceInfo.id;
  130. if (DisplayConfigGetDeviceInfo(
  131. &source.header) == ERROR_SUCCESS &&
  132. wcscmp(device, source.viewGdiDeviceName) ==
  133. 0) {
  134. target->header.type =
  135. DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
  136. target->header.size = sizeof(*target);
  137. target->header.adapterId =
  138. path->sourceInfo.adapterId;
  139. target->header.id = path->targetInfo.id;
  140. found = DisplayConfigGetDeviceInfo(
  141. &target->header) ==
  142. ERROR_SUCCESS;
  143. break;
  144. }
  145. }
  146. }
  147. bfree(modes);
  148. bfree(paths);
  149. }
  150. return found;
  151. }
  152. static void GetMonitorName(HMONITOR handle, char *name, size_t count)
  153. {
  154. MONITORINFOEXW mi;
  155. DISPLAYCONFIG_TARGET_DEVICE_NAME target;
  156. mi.cbSize = sizeof(mi);
  157. if (GetMonitorInfoW(handle, (LPMONITORINFO)&mi) &&
  158. GetMonitorTarget(mi.szDevice, &target)) {
  159. char *friendly_name;
  160. os_wcs_to_utf8_ptr(target.monitorFriendlyDeviceName, 0,
  161. &friendly_name);
  162. strcpy_s(name, count, friendly_name);
  163. bfree(friendly_name);
  164. } else {
  165. strcpy_s(name, count, "[OBS: Unknown]");
  166. }
  167. }
  168. static BOOL CALLBACK enum_monitor(HMONITOR handle, HDC hdc, LPRECT rect,
  169. LPARAM param)
  170. {
  171. UNUSED_PARAMETER(hdc);
  172. struct duplicator_monitor_info *monitor =
  173. (struct duplicator_monitor_info *)param;
  174. bool match = false;
  175. MONITORINFOEXA mi;
  176. mi.cbSize = sizeof(mi);
  177. if (GetMonitorInfoA(handle, (LPMONITORINFO)&mi)) {
  178. DISPLAY_DEVICEA device;
  179. device.cb = sizeof(device);
  180. if (EnumDisplayDevicesA(mi.szDevice, 0, &device,
  181. EDD_GET_DEVICE_INTERFACE_NAME)) {
  182. match = strcmp(monitor->device_id, device.DeviceID) ==
  183. 0;
  184. if (match) {
  185. strcpy_s(monitor->id, _countof(monitor->id),
  186. device.DeviceID);
  187. strcpy_s(monitor->alt_id,
  188. _countof(monitor->alt_id),
  189. mi.szDevice);
  190. GetMonitorName(handle, monitor->name,
  191. _countof(monitor->name));
  192. monitor->rect = *rect;
  193. monitor->handle = handle;
  194. }
  195. }
  196. }
  197. return !match;
  198. }
  199. static BOOL CALLBACK enum_monitor_fallback(HMONITOR handle, HDC hdc,
  200. LPRECT rect, LPARAM param)
  201. {
  202. UNUSED_PARAMETER(hdc);
  203. struct duplicator_monitor_info *monitor =
  204. (struct duplicator_monitor_info *)param;
  205. bool match = false;
  206. MONITORINFOEXA mi;
  207. mi.cbSize = sizeof(mi);
  208. if (GetMonitorInfoA(handle, (LPMONITORINFO)&mi)) {
  209. match = strcmp(monitor->device_id, mi.szDevice) == 0;
  210. if (match) {
  211. strcpy_s(monitor->alt_id, _countof(monitor->alt_id),
  212. mi.szDevice);
  213. GetMonitorName(handle, monitor->name,
  214. _countof(monitor->name));
  215. monitor->rect = *rect;
  216. monitor->handle = handle;
  217. }
  218. }
  219. return !match;
  220. }
  221. static void log_settings(struct duplicator_capture *capture,
  222. const char *monitor, LONG width, LONG height)
  223. {
  224. info("update settings:\n"
  225. "\tdisplay: %s (%ldx%ld)\n"
  226. "\tcursor: %s\n"
  227. "\tmethod: %s\n"
  228. "\tid: %s\n"
  229. "\talt_id: %s\n"
  230. "\tsetting_id: %s",
  231. monitor, width, height, capture->capture_cursor ? "true" : "false",
  232. get_method_name(capture->method), capture->id, capture->alt_id,
  233. capture->monitor_id);
  234. }
  235. static enum display_capture_method
  236. choose_method(enum display_capture_method method, bool wgc_supported,
  237. HMONITOR monitor)
  238. {
  239. if (!wgc_supported)
  240. method = METHOD_DXGI;
  241. if (method == METHOD_AUTO) {
  242. method = METHOD_DXGI;
  243. obs_enter_graphics();
  244. const int dxgi_index = gs_duplicator_get_monitor_index(monitor);
  245. obs_leave_graphics();
  246. if (dxgi_index == -1) {
  247. method = METHOD_WGC;
  248. } else {
  249. SYSTEM_POWER_STATUS status;
  250. if (GetSystemPowerStatus(&status) &&
  251. status.BatteryFlag < 128) {
  252. obs_enter_graphics();
  253. const uint32_t count = gs_get_adapter_count();
  254. obs_leave_graphics();
  255. if (count >= 2)
  256. method = METHOD_WGC;
  257. }
  258. }
  259. }
  260. return method;
  261. }
  262. extern bool wgc_supported;
  263. static struct duplicator_monitor_info find_monitor(const char *monitor_id)
  264. {
  265. struct duplicator_monitor_info monitor = {0};
  266. strcpy_s(monitor.device_id, _countof(monitor.device_id), monitor_id);
  267. EnumDisplayMonitors(NULL, NULL, &enum_monitor, (LPARAM)&monitor);
  268. if (monitor.handle == NULL) {
  269. EnumDisplayMonitors(NULL, NULL, &enum_monitor_fallback,
  270. (LPARAM)&monitor);
  271. }
  272. return monitor;
  273. }
  274. static inline void update_settings(struct duplicator_capture *capture,
  275. obs_data_t *settings)
  276. {
  277. pthread_mutex_lock(&capture->update_mutex);
  278. struct duplicator_monitor_info monitor =
  279. find_monitor(obs_data_get_string(settings, "monitor_id"));
  280. capture->method =
  281. choose_method((int)obs_data_get_int(settings, "method"),
  282. wgc_supported, monitor.handle);
  283. strcpy_s(capture->monitor_id, _countof(capture->monitor_id),
  284. monitor.device_id);
  285. strcpy_s(capture->id, _countof(capture->id), monitor.id);
  286. strcpy_s(capture->alt_id, _countof(capture->alt_id), monitor.alt_id);
  287. strcpy_s(capture->monitor_name, _countof(capture->monitor_name),
  288. monitor.name);
  289. capture->handle = monitor.handle;
  290. capture->capture_cursor = obs_data_get_bool(settings, "capture_cursor");
  291. capture->force_sdr = obs_data_get_bool(settings, "force_sdr");
  292. capture->logged_width = monitor.rect.right - monitor.rect.left;
  293. capture->logged_height = monitor.rect.bottom - monitor.rect.top;
  294. if (capture->duplicator) {
  295. obs_enter_graphics();
  296. gs_duplicator_destroy(capture->duplicator);
  297. capture->duplicator = NULL;
  298. obs_leave_graphics();
  299. }
  300. capture->width = 0;
  301. capture->height = 0;
  302. capture->x = 0;
  303. capture->y = 0;
  304. capture->rot = 0;
  305. capture->reset_timeout = RESET_INTERVAL_SEC;
  306. pthread_mutex_unlock(&capture->update_mutex);
  307. }
  308. /* ------------------------------------------------------------------------- */
  309. static const char *duplicator_capture_getname(void *unused)
  310. {
  311. UNUSED_PARAMETER(unused);
  312. return TEXT_MONITOR_CAPTURE;
  313. }
  314. static void duplicator_actual_destroy(void *data)
  315. {
  316. struct duplicator_capture *capture = data;
  317. if (capture->capture_winrt) {
  318. capture->exports.winrt_capture_free(capture->capture_winrt);
  319. capture->capture_winrt = NULL;
  320. }
  321. obs_enter_graphics();
  322. if (capture->duplicator) {
  323. gs_duplicator_destroy(capture->duplicator);
  324. capture->duplicator = NULL;
  325. }
  326. cursor_data_free(&capture->cursor_data);
  327. obs_leave_graphics();
  328. if (capture->winrt_module) {
  329. os_dlclose(capture->winrt_module);
  330. capture->winrt_module = NULL;
  331. }
  332. pthread_mutex_destroy(&capture->update_mutex);
  333. bfree(capture);
  334. }
  335. static void duplicator_capture_destroy(void *data)
  336. {
  337. obs_queue_task(OBS_TASK_GRAPHICS, duplicator_actual_destroy, data,
  338. false);
  339. }
  340. static void duplicator_capture_defaults(obs_data_t *settings)
  341. {
  342. obs_data_set_default_int(settings, "method", METHOD_AUTO);
  343. obs_data_set_default_string(settings, "monitor_id", "DUMMY");
  344. obs_data_set_default_int(settings, "monitor_wgc", 0);
  345. obs_data_set_default_bool(settings, "capture_cursor", true);
  346. obs_data_set_default_bool(settings, "force_sdr", false);
  347. }
  348. static void duplicator_capture_update(void *data, obs_data_t *settings)
  349. {
  350. struct duplicator_capture *mc = data;
  351. update_settings(mc, settings);
  352. log_settings(mc, mc->monitor_name, mc->logged_width, mc->logged_height);
  353. mc->reset_wgc = true;
  354. }
  355. #define WINRT_IMPORT(func) \
  356. do { \
  357. exports->func = (PFN_##func)os_dlsym(module, #func); \
  358. if (!exports->func) { \
  359. success = false; \
  360. blog(LOG_ERROR, \
  361. "Could not load function '%s' from " \
  362. "module '%s'", \
  363. #func, module_name); \
  364. } \
  365. } while (false)
  366. static bool load_winrt_imports(struct winrt_exports *exports, void *module,
  367. const char *module_name)
  368. {
  369. bool success = true;
  370. WINRT_IMPORT(winrt_capture_supported);
  371. WINRT_IMPORT(winrt_capture_cursor_toggle_supported);
  372. WINRT_IMPORT(winrt_capture_init_monitor);
  373. WINRT_IMPORT(winrt_capture_free);
  374. WINRT_IMPORT(winrt_capture_active);
  375. WINRT_IMPORT(winrt_capture_get_color_space);
  376. WINRT_IMPORT(winrt_capture_render);
  377. WINRT_IMPORT(winrt_capture_width);
  378. WINRT_IMPORT(winrt_capture_height);
  379. return success;
  380. }
  381. extern bool graphics_uses_d3d11;
  382. static void *duplicator_capture_create(obs_data_t *settings,
  383. obs_source_t *source)
  384. {
  385. struct duplicator_capture *capture;
  386. capture = bzalloc(sizeof(struct duplicator_capture));
  387. capture->source = source;
  388. pthread_mutex_init(&capture->update_mutex, NULL);
  389. if (graphics_uses_d3d11) {
  390. static const char *const module = "libobs-winrt";
  391. capture->winrt_module = os_dlopen(module);
  392. if (capture->winrt_module) {
  393. load_winrt_imports(&capture->exports,
  394. capture->winrt_module, module);
  395. }
  396. }
  397. update_settings(capture, settings);
  398. log_settings(capture, capture->monitor_name, capture->logged_width,
  399. capture->logged_height);
  400. return capture;
  401. }
  402. static void reset_capture_data(struct duplicator_capture *capture)
  403. {
  404. struct gs_monitor_info monitor_info = {0};
  405. gs_texture_t *texture = gs_duplicator_get_texture(capture->duplicator);
  406. const int dxgi_index = gs_duplicator_get_monitor_index(capture->handle);
  407. gs_get_duplicator_monitor_info(dxgi_index, &monitor_info);
  408. if (texture) {
  409. capture->width = gs_texture_get_width(texture);
  410. capture->height = gs_texture_get_height(texture);
  411. } else {
  412. capture->width = 0;
  413. capture->height = 0;
  414. }
  415. capture->x = monitor_info.x;
  416. capture->y = monitor_info.y;
  417. capture->rot = monitor_info.rotation_degrees;
  418. }
  419. static void free_capture_data(struct duplicator_capture *capture)
  420. {
  421. if (capture->capture_winrt) {
  422. capture->exports.winrt_capture_free(capture->capture_winrt);
  423. capture->capture_winrt = NULL;
  424. }
  425. if (capture->duplicator) {
  426. gs_duplicator_destroy(capture->duplicator);
  427. capture->duplicator = NULL;
  428. }
  429. cursor_data_free(&capture->cursor_data);
  430. capture->width = 0;
  431. capture->height = 0;
  432. capture->x = 0;
  433. capture->y = 0;
  434. capture->rot = 0;
  435. capture->reset_timeout = 0.0f;
  436. }
  437. static void update_monitor_handle(struct duplicator_capture *capture)
  438. {
  439. capture->handle = find_monitor(capture->monitor_id).handle;
  440. }
  441. static void duplicator_capture_tick(void *data, float seconds)
  442. {
  443. struct duplicator_capture *capture = data;
  444. /* completely shut down monitor capture if not in use, otherwise it can
  445. * sometimes generate system lag when a game is in fullscreen mode */
  446. if (!obs_source_showing(capture->source)) {
  447. if (capture->showing) {
  448. obs_enter_graphics();
  449. free_capture_data(capture);
  450. obs_leave_graphics();
  451. capture->showing = false;
  452. }
  453. return;
  454. }
  455. /* always try to load the capture immediately when the source is first
  456. * shown */
  457. if (!capture->showing) {
  458. capture->reset_timeout = RESET_INTERVAL_SEC;
  459. }
  460. obs_enter_graphics();
  461. if (capture->method == METHOD_WGC) {
  462. if (capture->reset_wgc && capture->capture_winrt) {
  463. capture->exports.winrt_capture_free(
  464. capture->capture_winrt);
  465. capture->capture_winrt = NULL;
  466. capture->reset_wgc = false;
  467. capture->reset_timeout = RESET_INTERVAL_SEC;
  468. }
  469. if (!capture->capture_winrt) {
  470. capture->reset_timeout += seconds;
  471. if (capture->reset_timeout >= RESET_INTERVAL_SEC) {
  472. if (!capture->handle)
  473. update_monitor_handle(capture);
  474. if (capture->handle) {
  475. capture->capture_winrt =
  476. capture->exports.winrt_capture_init_monitor(
  477. capture->capture_cursor,
  478. capture->handle,
  479. capture->force_sdr);
  480. if (!capture->capture_winrt) {
  481. update_monitor_handle(capture);
  482. if (capture->handle) {
  483. capture->capture_winrt =
  484. capture->exports.winrt_capture_init_monitor(
  485. capture->capture_cursor,
  486. capture->handle,
  487. capture->force_sdr);
  488. }
  489. }
  490. }
  491. capture->reset_timeout = 0.0f;
  492. }
  493. }
  494. } else {
  495. if (capture->capture_winrt) {
  496. capture->exports.winrt_capture_free(
  497. capture->capture_winrt);
  498. capture->capture_winrt = NULL;
  499. }
  500. if (!capture->duplicator) {
  501. capture->reset_timeout += seconds;
  502. if (capture->reset_timeout >= RESET_INTERVAL_SEC) {
  503. if (!capture->handle)
  504. update_monitor_handle(capture);
  505. if (capture->handle) {
  506. int dxgi_index =
  507. gs_duplicator_get_monitor_index(
  508. capture->handle);
  509. if (dxgi_index == -1) {
  510. update_monitor_handle(capture);
  511. if (capture->handle) {
  512. dxgi_index = gs_duplicator_get_monitor_index(
  513. capture->handle);
  514. }
  515. }
  516. if (dxgi_index != -1) {
  517. capture->duplicator =
  518. gs_duplicator_create(
  519. dxgi_index);
  520. }
  521. }
  522. capture->reset_timeout = 0.0f;
  523. }
  524. }
  525. if (capture->duplicator) {
  526. if (capture->capture_cursor)
  527. cursor_capture(&capture->cursor_data);
  528. if (!gs_duplicator_update_frame(capture->duplicator)) {
  529. free_capture_data(capture);
  530. } else if (capture->width == 0) {
  531. reset_capture_data(capture);
  532. }
  533. }
  534. }
  535. obs_leave_graphics();
  536. if (!capture->showing)
  537. capture->showing = true;
  538. }
  539. static uint32_t duplicator_capture_width(void *data)
  540. {
  541. struct duplicator_capture *capture = data;
  542. return (capture->method == METHOD_WGC)
  543. ? capture->exports.winrt_capture_width(
  544. capture->capture_winrt)
  545. : (capture->rot % 180 == 0 ? capture->width
  546. : capture->height);
  547. }
  548. static uint32_t duplicator_capture_height(void *data)
  549. {
  550. struct duplicator_capture *capture = data;
  551. return (capture->method == METHOD_WGC)
  552. ? capture->exports.winrt_capture_height(
  553. capture->capture_winrt)
  554. : (capture->rot % 180 == 0 ? capture->height
  555. : capture->width);
  556. }
  557. static void draw_cursor(struct duplicator_capture *capture)
  558. {
  559. cursor_draw(&capture->cursor_data, -capture->x, -capture->y,
  560. capture->rot % 180 == 0 ? capture->width : capture->height,
  561. capture->rot % 180 == 0 ? capture->height : capture->width);
  562. }
  563. static void duplicator_capture_render(void *data, gs_effect_t *unused)
  564. {
  565. UNUSED_PARAMETER(unused);
  566. struct duplicator_capture *capture = data;
  567. if (capture->method == METHOD_WGC) {
  568. if (capture->capture_winrt) {
  569. if (capture->exports.winrt_capture_active(
  570. capture->capture_winrt)) {
  571. capture->exports.winrt_capture_render(
  572. capture->capture_winrt);
  573. } else {
  574. capture->exports.winrt_capture_free(
  575. capture->capture_winrt);
  576. capture->capture_winrt = NULL;
  577. }
  578. }
  579. } else {
  580. if (!capture->duplicator)
  581. return;
  582. gs_texture_t *const texture =
  583. gs_duplicator_get_texture(capture->duplicator);
  584. if (!texture)
  585. return;
  586. const bool previous = gs_framebuffer_srgb_enabled();
  587. gs_enable_framebuffer_srgb(true);
  588. gs_enable_blending(false);
  589. const int rot = capture->rot;
  590. if (rot != 0) {
  591. float x = 0.0f;
  592. float y = 0.0f;
  593. switch (rot) {
  594. case 90:
  595. x = (float)capture->height;
  596. break;
  597. case 180:
  598. x = (float)capture->width;
  599. y = (float)capture->height;
  600. break;
  601. case 270:
  602. y = (float)capture->width;
  603. break;
  604. }
  605. gs_matrix_push();
  606. gs_matrix_translate3f(x, y, 0.0f);
  607. gs_matrix_rotaa4f(0.0f, 0.0f, 1.0f, RAD((float)rot));
  608. }
  609. const char *tech_name = "Draw";
  610. float multiplier = 1.f;
  611. const enum gs_color_space current_space = gs_get_color_space();
  612. if (gs_texture_get_color_format(texture) == GS_RGBA16F) {
  613. switch (current_space) {
  614. case GS_CS_SRGB:
  615. case GS_CS_SRGB_16F:
  616. tech_name = "DrawMultiplyTonemap";
  617. multiplier =
  618. 80.f / obs_get_video_sdr_white_level();
  619. break;
  620. case GS_CS_709_EXTENDED:
  621. tech_name = "DrawMultiply";
  622. multiplier =
  623. 80.f / obs_get_video_sdr_white_level();
  624. }
  625. } else if (current_space == GS_CS_709_SCRGB) {
  626. tech_name = "DrawMultiply";
  627. multiplier = obs_get_video_sdr_white_level() / 80.f;
  628. }
  629. gs_effect_t *const opaque_effect =
  630. obs_get_base_effect(OBS_EFFECT_OPAQUE);
  631. gs_eparam_t *multiplier_param = gs_effect_get_param_by_name(
  632. opaque_effect, "multiplier");
  633. gs_effect_set_float(multiplier_param, multiplier);
  634. gs_eparam_t *image_param =
  635. gs_effect_get_param_by_name(opaque_effect, "image");
  636. gs_effect_set_texture_srgb(image_param, texture);
  637. while (gs_effect_loop(opaque_effect, tech_name)) {
  638. gs_draw_sprite(texture, 0, 0, 0);
  639. }
  640. if (rot != 0)
  641. gs_matrix_pop();
  642. gs_enable_blending(true);
  643. gs_enable_framebuffer_srgb(previous);
  644. if (capture->capture_cursor) {
  645. gs_effect_t *const default_effect =
  646. obs_get_base_effect(OBS_EFFECT_DEFAULT);
  647. while (gs_effect_loop(default_effect, "Draw")) {
  648. draw_cursor(capture);
  649. }
  650. }
  651. }
  652. }
  653. static BOOL CALLBACK enum_monitor_props(HMONITOR handle, HDC hdc, LPRECT rect,
  654. LPARAM param)
  655. {
  656. UNUSED_PARAMETER(hdc);
  657. UNUSED_PARAMETER(rect);
  658. char monitor_name[64];
  659. GetMonitorName(handle, monitor_name, sizeof(monitor_name));
  660. MONITORINFOEXA mi;
  661. mi.cbSize = sizeof(mi);
  662. if (GetMonitorInfoA(handle, (LPMONITORINFO)&mi)) {
  663. obs_property_t *monitor_list = (obs_property_t *)param;
  664. struct dstr monitor_desc = {0};
  665. dstr_printf(&monitor_desc, "%s: %dx%d @ %d,%d", monitor_name,
  666. mi.rcMonitor.right - mi.rcMonitor.left,
  667. mi.rcMonitor.bottom - mi.rcMonitor.top,
  668. mi.rcMonitor.left, mi.rcMonitor.top);
  669. if (mi.dwFlags == MONITORINFOF_PRIMARY)
  670. dstr_catf(&monitor_desc, " (%s)", TEXT_PRIMARY_MONITOR);
  671. DISPLAY_DEVICEA device;
  672. device.cb = sizeof(device);
  673. if (EnumDisplayDevicesA(mi.szDevice, 0, &device,
  674. EDD_GET_DEVICE_INTERFACE_NAME)) {
  675. obs_property_list_add_string(monitor_list,
  676. monitor_desc.array,
  677. device.DeviceID);
  678. } else {
  679. blog(LOG_WARNING,
  680. "[duplicator-monitor-capture] EnumDisplayDevices failed for monitor (%s), falling back to szDevice",
  681. monitor_name);
  682. obs_property_list_add_string(
  683. monitor_list, monitor_desc.array, mi.szDevice);
  684. }
  685. dstr_free(&monitor_desc);
  686. } else {
  687. blog(LOG_WARNING,
  688. "[duplicator-monitor-capture] GetMonitorInfo failed for monitor: %s",
  689. monitor_name);
  690. }
  691. return TRUE;
  692. }
  693. static void update_settings_visibility(obs_properties_t *props,
  694. struct duplicator_capture *capture)
  695. {
  696. pthread_mutex_lock(&capture->update_mutex);
  697. const enum window_capture_method method = capture->method;
  698. const bool dxgi_options = method == METHOD_DXGI;
  699. const bool wgc_options = method == METHOD_WGC;
  700. const bool wgc_cursor_toggle =
  701. wgc_options &&
  702. capture->exports.winrt_capture_cursor_toggle_supported();
  703. obs_property_t *p = obs_properties_get(props, "cursor");
  704. obs_property_set_visible(p, dxgi_options || wgc_cursor_toggle);
  705. p = obs_properties_get(props, "force_sdr");
  706. obs_property_set_visible(p, wgc_options);
  707. pthread_mutex_unlock(&capture->update_mutex);
  708. }
  709. static bool display_capture_method_changed(obs_properties_t *props,
  710. obs_property_t *p,
  711. obs_data_t *settings)
  712. {
  713. UNUSED_PARAMETER(p);
  714. struct duplicator_capture *capture = obs_properties_get_param(props);
  715. if (!capture)
  716. return false;
  717. update_settings(capture, settings);
  718. update_settings_visibility(props, capture);
  719. return true;
  720. }
  721. static obs_properties_t *duplicator_capture_properties(void *data)
  722. {
  723. struct duplicator_capture *capture = data;
  724. obs_properties_t *props = obs_properties_create();
  725. obs_properties_set_param(props, capture, NULL);
  726. obs_property_t *p = obs_properties_add_list(props, "method",
  727. TEXT_METHOD,
  728. OBS_COMBO_TYPE_LIST,
  729. OBS_COMBO_FORMAT_INT);
  730. obs_property_list_add_int(p, TEXT_METHOD_AUTO, METHOD_AUTO);
  731. obs_property_list_add_int(p, TEXT_METHOD_DXGI, METHOD_DXGI);
  732. obs_property_list_add_int(p, TEXT_METHOD_WGC, METHOD_WGC);
  733. obs_property_list_item_disable(p, 2, !wgc_supported);
  734. obs_property_set_modified_callback(p, display_capture_method_changed);
  735. obs_property_t *monitors = obs_properties_add_list(
  736. props, "monitor_id", TEXT_MONITOR, OBS_COMBO_TYPE_LIST,
  737. OBS_COMBO_FORMAT_STRING);
  738. obs_properties_add_bool(props, "capture_cursor", TEXT_CAPTURE_CURSOR);
  739. obs_properties_add_bool(props, "force_sdr", TEXT_FORCE_SDR);
  740. EnumDisplayMonitors(NULL, NULL, enum_monitor_props, (LPARAM)monitors);
  741. return props;
  742. }
  743. enum gs_color_space
  744. duplicator_capture_get_color_space(void *data, size_t count,
  745. const enum gs_color_space *preferred_spaces)
  746. {
  747. enum gs_color_space capture_space = GS_CS_SRGB;
  748. struct duplicator_capture *capture = data;
  749. if (capture->method == METHOD_WGC) {
  750. if (capture->capture_winrt) {
  751. capture_space =
  752. capture->exports.winrt_capture_get_color_space(
  753. capture->capture_winrt);
  754. }
  755. } else {
  756. if (capture->duplicator) {
  757. gs_texture_t *const texture =
  758. gs_duplicator_get_texture(capture->duplicator);
  759. if (texture) {
  760. capture_space = (gs_texture_get_color_format(
  761. texture) == GS_RGBA16F)
  762. ? GS_CS_709_EXTENDED
  763. : GS_CS_SRGB;
  764. }
  765. }
  766. }
  767. enum gs_color_space space = capture_space;
  768. for (size_t i = 0; i < count; ++i) {
  769. const enum gs_color_space preferred_space = preferred_spaces[i];
  770. space = preferred_space;
  771. if (preferred_space == capture_space)
  772. break;
  773. }
  774. return space;
  775. }
  776. struct obs_source_info duplicator_capture_info = {
  777. .id = "monitor_capture",
  778. .type = OBS_SOURCE_TYPE_INPUT,
  779. .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
  780. OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_SRGB,
  781. .get_name = duplicator_capture_getname,
  782. .create = duplicator_capture_create,
  783. .destroy = duplicator_capture_destroy,
  784. .video_render = duplicator_capture_render,
  785. .video_tick = duplicator_capture_tick,
  786. .update = duplicator_capture_update,
  787. .get_width = duplicator_capture_width,
  788. .get_height = duplicator_capture_height,
  789. .get_defaults = duplicator_capture_defaults,
  790. .get_properties = duplicator_capture_properties,
  791. .icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
  792. .video_get_color_space = duplicator_capture_get_color_space,
  793. };