nvidia-greenscreen-filter.c 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. #include <obs-module.h>
  2. #include <util/threading.h>
  3. #include <dxgi.h>
  4. #include <d3d11.h>
  5. #include <d3d11_1.h>
  6. #include "nvvfx-load.h"
  7. /* -------------------------------------------------------- */
  8. #define do_log(level, format, ...) \
  9. blog(level, \
  10. "[NVIDIA RTX AI Greenscreen (Background removal): '%s'] " format, \
  11. obs_source_get_name(filter->context), ##__VA_ARGS__)
  12. #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
  13. #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
  14. #define error(format, ...) do_log(LOG_ERROR, format, ##__VA_ARGS__)
  15. #ifdef _DEBUG
  16. #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
  17. #else
  18. #define debug(format, ...)
  19. #endif
  20. /* -------------------------------------------------------- */
  21. #define S_MODE "mode"
  22. #define S_MODE_QUALITY 0
  23. #define S_MODE_PERF 1
  24. #define S_THRESHOLDFX "threshold"
  25. #define S_THRESHOLDFX_DEFAULT 1.0
  26. #define MT_ obs_module_text
  27. #define TEXT_MODE MT_("Greenscreen.Mode")
  28. #define TEXT_MODE_QUALITY MT_("Greenscreen.Quality")
  29. #define TEXT_MODE_PERF MT_("Greenscreen.Performance")
  30. #define TEXT_MODE_THRESHOLD MT_("Greenscreen.Threshold")
  31. bool nvvfx_loaded = false;
  32. struct nv_greenscreen_data {
  33. obs_source_t *context;
  34. bool images_allocated;
  35. bool initial_render;
  36. volatile bool processing_stop;
  37. bool processed_frame;
  38. bool target_valid;
  39. bool got_new_frame;
  40. signal_handler_t *handler;
  41. /* RTX SDK vars */
  42. NvVFX_Handle handle;
  43. CUstream stream; // CUDA stream
  44. int mode; // 0 = quality, 1 = performance
  45. NvCVImage *src_img; // src img in obs format (RGBA ?) on GPU
  46. NvCVImage *BGR_src_img; // src img in BGR on GPU
  47. NvCVImage *A_dst_img; // mask img on GPU
  48. NvCVImage *dst_img; // mask texture
  49. NvCVImage *stage; // planar stage img used for transfer to texture
  50. /* alpha mask effect */
  51. gs_effect_t *effect;
  52. gs_texrender_t *render;
  53. gs_texrender_t *render_unorm;
  54. gs_texture_t *alpha_texture;
  55. uint32_t width; // width of texture
  56. uint32_t height; // height of texture
  57. enum gs_color_space space;
  58. gs_eparam_t *mask_param;
  59. gs_eparam_t *image_param;
  60. gs_eparam_t *threshold_param;
  61. gs_eparam_t *multiplier_param;
  62. float threshold;
  63. };
  64. static const char *nv_greenscreen_filter_name(void *unused)
  65. {
  66. UNUSED_PARAMETER(unused);
  67. return obs_module_text("NvidiaGreenscreenFilter");
  68. }
  69. static void nv_greenscreen_filter_update(void *data, obs_data_t *settings)
  70. {
  71. struct nv_greenscreen_data *filter = (struct nv_greenscreen_data *)data;
  72. NvCV_Status vfxErr;
  73. int mode = (int)obs_data_get_int(settings, S_MODE);
  74. if (filter->mode != mode) {
  75. filter->mode = mode;
  76. vfxErr = NvVFX_SetU32(filter->handle, NVVFX_MODE, mode);
  77. vfxErr = NvVFX_Load(filter->handle);
  78. if (NVCV_SUCCESS != vfxErr)
  79. error("Error loading AI Greenscreen FX %i", vfxErr);
  80. }
  81. filter->threshold = (float)obs_data_get_double(settings, S_THRESHOLDFX);
  82. }
  83. static void nv_greenscreen_filter_actual_destroy(void *data)
  84. {
  85. struct nv_greenscreen_data *filter = (struct nv_greenscreen_data *)data;
  86. if (!nvvfx_loaded) {
  87. bfree(filter);
  88. return;
  89. }
  90. os_atomic_set_bool(&filter->processing_stop, true);
  91. if (filter->images_allocated) {
  92. obs_enter_graphics();
  93. gs_texture_destroy(filter->alpha_texture);
  94. gs_texrender_destroy(filter->render);
  95. gs_texrender_destroy(filter->render_unorm);
  96. obs_leave_graphics();
  97. NvCVImage_Destroy(filter->src_img);
  98. NvCVImage_Destroy(filter->BGR_src_img);
  99. NvCVImage_Destroy(filter->A_dst_img);
  100. NvCVImage_Destroy(filter->dst_img);
  101. NvCVImage_Destroy(filter->stage);
  102. }
  103. if (filter->stream) {
  104. NvVFX_CudaStreamDestroy(filter->stream);
  105. }
  106. if (filter->handle) {
  107. NvVFX_DestroyEffect(filter->handle);
  108. }
  109. if (filter->effect) {
  110. obs_enter_graphics();
  111. gs_effect_destroy(filter->effect);
  112. obs_leave_graphics();
  113. }
  114. bfree(filter);
  115. }
  116. static void nv_greenscreen_filter_destroy(void *data)
  117. {
  118. obs_queue_task(OBS_TASK_GRAPHICS, nv_greenscreen_filter_actual_destroy,
  119. data, false);
  120. }
  121. static void nv_greenscreen_filter_reset(void *data, calldata_t *calldata)
  122. {
  123. struct nv_greenscreen_data *filter = (struct nv_greenscreen_data *)data;
  124. NvCV_Status vfxErr;
  125. os_atomic_set_bool(&filter->processing_stop, true);
  126. // first destroy
  127. if (filter->stream) {
  128. NvVFX_CudaStreamDestroy(filter->stream);
  129. }
  130. if (filter->handle) {
  131. NvVFX_DestroyEffect(filter->handle);
  132. }
  133. // recreate
  134. /* 1. Create FX */
  135. vfxErr = NvVFX_CreateEffect(NVVFX_FX_GREEN_SCREEN, &filter->handle);
  136. if (NVCV_SUCCESS != vfxErr) {
  137. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  138. error("Error recreating AI Greenscreen FX; error %i: %s",
  139. vfxErr, errString);
  140. nv_greenscreen_filter_destroy(filter);
  141. }
  142. /* 2. Set models path & initialize CudaStream */
  143. char buffer[MAX_PATH];
  144. char modelDir[MAX_PATH];
  145. nvvfx_get_sdk_path(buffer, MAX_PATH);
  146. size_t max_len = sizeof(buffer) / sizeof(char);
  147. snprintf(modelDir, max_len, "%s\\models", buffer);
  148. vfxErr = NvVFX_SetString(filter->handle, NVVFX_MODEL_DIRECTORY,
  149. modelDir);
  150. vfxErr = NvVFX_CudaStreamCreate(&filter->stream);
  151. if (NVCV_SUCCESS != vfxErr) {
  152. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  153. error("Error creating CUDA Stream; error %i: %s", vfxErr,
  154. errString);
  155. nv_greenscreen_filter_destroy(filter);
  156. }
  157. vfxErr = NvVFX_SetCudaStream(filter->handle, NVVFX_CUDA_STREAM,
  158. filter->stream);
  159. if (NVCV_SUCCESS != vfxErr) {
  160. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  161. error("Error setting CUDA Stream %i", vfxErr);
  162. nv_greenscreen_filter_destroy(filter);
  163. }
  164. /* 3. load FX */
  165. vfxErr = NvVFX_SetU32(filter->handle, NVVFX_MODE, filter->mode);
  166. vfxErr = NvVFX_Load(filter->handle);
  167. if (NVCV_SUCCESS != vfxErr)
  168. error("Error loading AI Greenscreen FX %i", vfxErr);
  169. filter->images_allocated = false;
  170. os_atomic_set_bool(&filter->processing_stop, false);
  171. }
  172. static void init_images_greenscreen(struct nv_greenscreen_data *filter)
  173. {
  174. NvCV_Status vfxErr;
  175. uint32_t width = filter->width;
  176. uint32_t height = filter->height;
  177. /* 1. create alpha texture */
  178. if (filter->alpha_texture) {
  179. gs_texture_destroy(filter->alpha_texture);
  180. }
  181. filter->alpha_texture =
  182. gs_texture_create(width, height, GS_A8, 1, NULL, 0);
  183. if (filter->alpha_texture == NULL) {
  184. error("Alpha texture couldn't be created");
  185. goto fail;
  186. }
  187. struct ID3D11Texture2D *d11texture =
  188. (struct ID3D11Texture2D *)gs_texture_get_obj(
  189. filter->alpha_texture);
  190. /* 2. Create NvCVImage which will hold final alpha texture. */
  191. if (!filter->dst_img &&
  192. (NvCVImage_Create(width, height, NVCV_A, NVCV_U8, NVCV_CHUNKY,
  193. NVCV_GPU, 1, &filter->dst_img) != NVCV_SUCCESS)) {
  194. goto fail;
  195. }
  196. vfxErr = NvCVImage_InitFromD3D11Texture(filter->dst_img, d11texture);
  197. if (vfxErr != NVCV_SUCCESS) {
  198. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  199. error("Error passing dst ID3D11Texture to img; error %i: %s",
  200. vfxErr, errString);
  201. goto fail;
  202. }
  203. /* 3. create texrenders */
  204. if (filter->render)
  205. gs_texrender_destroy(filter->render);
  206. filter->render = gs_texrender_create(
  207. gs_get_format_from_space(filter->space), GS_ZS_NONE);
  208. if (!filter->render) {
  209. error("Failed to create render texrenderer", vfxErr);
  210. goto fail;
  211. }
  212. if (filter->render_unorm)
  213. gs_texrender_destroy(filter->render_unorm);
  214. filter->render_unorm = gs_texrender_create(GS_BGRA_UNORM, GS_ZS_NONE);
  215. if (!filter->render_unorm) {
  216. error("Failed to create render_unorm texrenderer", vfxErr);
  217. goto fail;
  218. }
  219. /* 4. Create and allocate BGR NvCVImage (fx src). */
  220. if (filter->BGR_src_img) {
  221. if (NvCVImage_Realloc(filter->BGR_src_img, width, height,
  222. NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU,
  223. 1) != NVCV_SUCCESS) {
  224. goto fail;
  225. }
  226. } else {
  227. if (NvCVImage_Create(width, height, NVCV_BGR, NVCV_U8,
  228. NVCV_CHUNKY, NVCV_GPU, 1,
  229. &filter->BGR_src_img) != NVCV_SUCCESS) {
  230. goto fail;
  231. }
  232. if (NvCVImage_Alloc(filter->BGR_src_img, width, height,
  233. NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU,
  234. 1) != NVCV_SUCCESS) {
  235. goto fail;
  236. }
  237. }
  238. /* 5. Create and allocate Alpha NvCVimage (fx dst). */
  239. if (filter->A_dst_img) {
  240. if (NvCVImage_Realloc(filter->A_dst_img, width, height, NVCV_A,
  241. NVCV_U8, NVCV_CHUNKY, NVCV_GPU,
  242. 1) != NVCV_SUCCESS) {
  243. goto fail;
  244. }
  245. } else {
  246. if (NvCVImage_Create(width, height, NVCV_A, NVCV_U8,
  247. NVCV_CHUNKY, NVCV_GPU, 1,
  248. &filter->A_dst_img) != NVCV_SUCCESS) {
  249. goto fail;
  250. }
  251. if (NvCVImage_Alloc(filter->A_dst_img, width, height, NVCV_A,
  252. NVCV_U8, NVCV_CHUNKY, NVCV_GPU,
  253. 1) != NVCV_SUCCESS) {
  254. goto fail;
  255. }
  256. }
  257. /* 6. Create stage NvCVImage which will be used as buffer for transfer */
  258. if (filter->stage) {
  259. if (NvCVImage_Realloc(filter->stage, width, height, NVCV_RGBA,
  260. NVCV_U8, NVCV_PLANAR, NVCV_GPU,
  261. 1) != NVCV_SUCCESS) {
  262. goto fail;
  263. }
  264. } else {
  265. if (NvCVImage_Create(width, height, NVCV_RGBA, NVCV_U8,
  266. NVCV_PLANAR, NVCV_GPU, 1,
  267. &filter->stage) != NVCV_SUCCESS) {
  268. goto fail;
  269. }
  270. if (NvCVImage_Alloc(filter->stage, width, height, NVCV_RGBA,
  271. NVCV_U8, NVCV_PLANAR, NVCV_GPU,
  272. 1) != NVCV_SUCCESS) {
  273. goto fail;
  274. }
  275. }
  276. /* 7. Set input & output images for nv FX. */
  277. if (NvVFX_SetImage(filter->handle, NVVFX_INPUT_IMAGE,
  278. filter->BGR_src_img) != NVCV_SUCCESS) {
  279. goto fail;
  280. }
  281. if (NvVFX_SetImage(filter->handle, NVVFX_OUTPUT_IMAGE,
  282. filter->A_dst_img) != NVCV_SUCCESS) {
  283. goto fail;
  284. }
  285. filter->images_allocated = true;
  286. return;
  287. fail:
  288. error("Error during allocation of images");
  289. os_atomic_set_bool(&filter->processing_stop, true);
  290. return;
  291. }
  292. static bool process_texture_greenscreen(struct nv_greenscreen_data *filter)
  293. {
  294. /* 1. Map src img holding texture. */
  295. NvCV_Status vfxErr =
  296. NvCVImage_MapResource(filter->src_img, filter->stream);
  297. if (vfxErr != NVCV_SUCCESS) {
  298. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  299. error("Error mapping resource for source texture; error %i : %s",
  300. vfxErr, errString);
  301. goto fail;
  302. }
  303. /* 2. Convert to BGR. */
  304. vfxErr = NvCVImage_Transfer(filter->src_img, filter->BGR_src_img, 1.0f,
  305. filter->stream, filter->stage);
  306. if (vfxErr != NVCV_SUCCESS) {
  307. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  308. error("Error converting src to BGR img; error %i: %s", vfxErr,
  309. errString);
  310. goto fail;
  311. }
  312. vfxErr = NvCVImage_UnmapResource(filter->src_img, filter->stream);
  313. if (vfxErr != NVCV_SUCCESS) {
  314. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  315. error("Error unmapping resource for src texture; error %i: %s",
  316. vfxErr, errString);
  317. goto fail;
  318. }
  319. /* 3. run RTX fx */
  320. vfxErr = NvVFX_Run(filter->handle, 1);
  321. if (vfxErr != NVCV_SUCCESS) {
  322. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  323. error("Error running the FX; error %i: %s", vfxErr, errString);
  324. if (vfxErr == NVCV_ERR_CUDA)
  325. nv_greenscreen_filter_reset(filter, NULL);
  326. }
  327. /* 4. Map dst texture before transfer from dst img provided by FX */
  328. vfxErr = NvCVImage_MapResource(filter->dst_img, filter->stream);
  329. if (vfxErr != NVCV_SUCCESS) {
  330. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  331. error("Error mapping resource for dst texture; error %i: %s",
  332. vfxErr, errString);
  333. goto fail;
  334. }
  335. vfxErr = NvCVImage_Transfer(filter->A_dst_img, filter->dst_img, 1.0f,
  336. filter->stream, filter->stage);
  337. if (vfxErr != NVCV_SUCCESS) {
  338. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  339. error("Error transferring mask to alpha texture; error %i: %s ",
  340. vfxErr, errString);
  341. goto fail;
  342. }
  343. vfxErr = NvCVImage_UnmapResource(filter->dst_img, filter->stream);
  344. if (vfxErr != NVCV_SUCCESS) {
  345. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  346. error("Error unmapping resource for dst texture; error %i: %s",
  347. vfxErr, errString);
  348. goto fail;
  349. }
  350. return true;
  351. fail:
  352. os_atomic_set_bool(&filter->processing_stop, true);
  353. return false;
  354. }
  355. static void *nv_greenscreen_filter_create(obs_data_t *settings,
  356. obs_source_t *context)
  357. {
  358. struct nv_greenscreen_data *filter =
  359. (struct nv_greenscreen_data *)bzalloc(sizeof(*filter));
  360. if (!nvvfx_loaded) {
  361. nv_greenscreen_filter_destroy(filter);
  362. return NULL;
  363. }
  364. NvCV_Status vfxErr;
  365. filter->context = context;
  366. filter->mode = -1; // should be 0 or 1; -1 triggers an update
  367. filter->images_allocated = false;
  368. filter->processed_frame = true; // start processing when false
  369. filter->width = 0;
  370. filter->height = 0;
  371. filter->initial_render = false;
  372. os_atomic_set_bool(&filter->processing_stop, false);
  373. filter->handler = NULL;
  374. /* 1. Create FX */
  375. vfxErr = NvVFX_CreateEffect(NVVFX_FX_GREEN_SCREEN, &filter->handle);
  376. if (NVCV_SUCCESS != vfxErr) {
  377. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  378. error("Error creating AI Greenscreen FX; error %i: %s", vfxErr,
  379. errString);
  380. nv_greenscreen_filter_destroy(filter);
  381. return NULL;
  382. }
  383. /* 2. Set models path & initialize CudaStream */
  384. char buffer[MAX_PATH];
  385. char modelDir[MAX_PATH];
  386. nvvfx_get_sdk_path(buffer, MAX_PATH);
  387. size_t max_len = sizeof(buffer) / sizeof(char);
  388. snprintf(modelDir, max_len, "%s\\models", buffer);
  389. vfxErr = NvVFX_SetString(filter->handle, NVVFX_MODEL_DIRECTORY,
  390. modelDir);
  391. vfxErr = NvVFX_CudaStreamCreate(&filter->stream);
  392. if (NVCV_SUCCESS != vfxErr) {
  393. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  394. error("Error creating CUDA Stream; error %i: %s", vfxErr,
  395. errString);
  396. nv_greenscreen_filter_destroy(filter);
  397. return NULL;
  398. }
  399. vfxErr = NvVFX_SetCudaStream(filter->handle, NVVFX_CUDA_STREAM,
  400. filter->stream);
  401. if (NVCV_SUCCESS != vfxErr) {
  402. const char *errString = NvCV_GetErrorStringFromCode(vfxErr);
  403. error("Error setting CUDA Stream %i", vfxErr);
  404. nv_greenscreen_filter_destroy(filter);
  405. return NULL;
  406. }
  407. /* log sdk version */
  408. unsigned int version;
  409. if (NvVFX_GetVersion(&version) == NVCV_SUCCESS) {
  410. uint8_t major = (version >> 24) & 0xff;
  411. uint8_t minor = (version >> 16) & 0x00ff;
  412. uint8_t build = (version >> 8) & 0x0000ff;
  413. info("RTX VIDEO FX version: %i.%i.%i", major, minor, build);
  414. }
  415. /* 3. Load alpha mask effect. */
  416. char *effect_path = obs_module_file("rtx_greenscreen.effect");
  417. obs_enter_graphics();
  418. filter->effect = gs_effect_create_from_file(effect_path, NULL);
  419. bfree(effect_path);
  420. if (filter->effect) {
  421. filter->mask_param =
  422. gs_effect_get_param_by_name(filter->effect, "mask");
  423. filter->image_param =
  424. gs_effect_get_param_by_name(filter->effect, "image");
  425. filter->threshold_param = gs_effect_get_param_by_name(
  426. filter->effect, "threshold");
  427. filter->multiplier_param = gs_effect_get_param_by_name(
  428. filter->effect, "multiplier");
  429. }
  430. obs_leave_graphics();
  431. if (!filter->effect) {
  432. nv_greenscreen_filter_destroy(filter);
  433. return NULL;
  434. }
  435. /*---------------------------------------- */
  436. nv_greenscreen_filter_update(filter, settings);
  437. return filter;
  438. }
  439. static obs_properties_t *nv_greenscreen_filter_properties(void *data)
  440. {
  441. obs_properties_t *props = obs_properties_create();
  442. obs_property_t *mode = obs_properties_add_list(props, S_MODE, TEXT_MODE,
  443. OBS_COMBO_TYPE_LIST,
  444. OBS_COMBO_FORMAT_INT);
  445. obs_property_list_add_int(mode, TEXT_MODE_QUALITY, S_MODE_QUALITY);
  446. obs_property_list_add_int(mode, TEXT_MODE_PERF, S_MODE_PERF);
  447. obs_property_t *threshold = obs_properties_add_float_slider(
  448. props, S_THRESHOLDFX, TEXT_MODE_THRESHOLD, 0, 1, 0.05);
  449. UNUSED_PARAMETER(data);
  450. return props;
  451. }
  452. static void nv_greenscreen_filter_defaults(obs_data_t *settings)
  453. {
  454. obs_data_set_default_int(settings, S_MODE, S_MODE_QUALITY);
  455. obs_data_set_default_double(settings, S_THRESHOLDFX,
  456. S_THRESHOLDFX_DEFAULT);
  457. }
  458. static struct obs_source_frame *
  459. nv_greenscreen_filter_video(void *data, struct obs_source_frame *frame)
  460. {
  461. struct nv_greenscreen_data *filter = (struct nv_greenscreen_data *)data;
  462. filter->got_new_frame = true;
  463. return frame;
  464. }
  465. static void nv_greenscreen_filter_tick(void *data, float t)
  466. {
  467. UNUSED_PARAMETER(t);
  468. struct nv_greenscreen_data *filter = (struct nv_greenscreen_data *)data;
  469. if (filter->processing_stop) {
  470. return;
  471. }
  472. if (!obs_filter_get_target(filter->context)) {
  473. return;
  474. }
  475. obs_source_t *target = obs_filter_get_target(filter->context);
  476. filter->target_valid = true;
  477. const uint32_t cx = obs_source_get_base_width(target);
  478. const uint32_t cy = obs_source_get_base_height(target);
  479. // initially the sizes are 0
  480. if (!cx && !cy) {
  481. filter->target_valid = false;
  482. return;
  483. }
  484. /* minimum size supported by SDK is (512,288) */
  485. filter->target_valid = cx >= 512 && cy >= 288;
  486. if (!filter->target_valid) {
  487. error("Size must be larger than (512,288)");
  488. return;
  489. }
  490. if (cx != filter->width && cy != filter->height) {
  491. filter->images_allocated = false;
  492. filter->width = cx;
  493. filter->height = cy;
  494. }
  495. if (!filter->images_allocated) {
  496. obs_enter_graphics();
  497. init_images_greenscreen(filter);
  498. obs_leave_graphics();
  499. filter->initial_render = false;
  500. }
  501. filter->processed_frame = false;
  502. }
  503. static const char *
  504. get_tech_name_and_multiplier(enum gs_color_space current_space,
  505. enum gs_color_space source_space,
  506. float *multiplier)
  507. {
  508. const char *tech_name = "Draw";
  509. *multiplier = 1.f;
  510. switch (source_space) {
  511. case GS_CS_SRGB:
  512. case GS_CS_SRGB_16F:
  513. switch (current_space) {
  514. case GS_CS_709_SCRGB:
  515. tech_name = "DrawMultiply";
  516. *multiplier = obs_get_video_sdr_white_level() / 80.0f;
  517. }
  518. break;
  519. case GS_CS_709_EXTENDED:
  520. switch (current_space) {
  521. case GS_CS_SRGB:
  522. case GS_CS_SRGB_16F:
  523. tech_name = "DrawTonemap";
  524. break;
  525. case GS_CS_709_SCRGB:
  526. tech_name = "DrawMultiply";
  527. *multiplier = obs_get_video_sdr_white_level() / 80.0f;
  528. }
  529. break;
  530. case GS_CS_709_SCRGB:
  531. switch (current_space) {
  532. case GS_CS_SRGB:
  533. case GS_CS_SRGB_16F:
  534. tech_name = "DrawMultiplyTonemap";
  535. *multiplier = 80.0f / obs_get_video_sdr_white_level();
  536. break;
  537. case GS_CS_709_EXTENDED:
  538. tech_name = "DrawMultiply";
  539. *multiplier = 80.0f / obs_get_video_sdr_white_level();
  540. }
  541. }
  542. return tech_name;
  543. }
  544. static void draw_greenscreen(struct nv_greenscreen_data *filter)
  545. {
  546. /* Render alpha mask */
  547. const enum gs_color_space source_space = filter->space;
  548. float multiplier;
  549. const char *technique = get_tech_name_and_multiplier(
  550. gs_get_color_space(), source_space, &multiplier);
  551. const enum gs_color_format format =
  552. gs_get_format_from_space(source_space);
  553. if (obs_source_process_filter_begin_with_color_space(
  554. filter->context, format, source_space,
  555. OBS_ALLOW_DIRECT_RENDERING)) {
  556. gs_effect_set_texture(filter->mask_param,
  557. filter->alpha_texture);
  558. gs_effect_set_texture_srgb(
  559. filter->image_param,
  560. gs_texrender_get_texture(filter->render));
  561. gs_effect_set_float(filter->threshold_param, filter->threshold);
  562. gs_effect_set_float(filter->multiplier_param, multiplier);
  563. gs_blend_state_push();
  564. gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
  565. obs_source_process_filter_tech_end(
  566. filter->context, filter->effect, 0, 0, technique);
  567. gs_blend_state_pop();
  568. }
  569. }
  570. static void nv_greenscreen_filter_render(void *data, gs_effect_t *effect)
  571. {
  572. NvCV_Status vfxErr;
  573. struct nv_greenscreen_data *filter = (struct nv_greenscreen_data *)data;
  574. if (filter->processing_stop) {
  575. obs_source_skip_video_filter(filter->context);
  576. return;
  577. }
  578. obs_source_t *const target = obs_filter_get_target(filter->context);
  579. obs_source_t *const parent = obs_filter_get_parent(filter->context);
  580. /* Skip if processing of a frame hasn't yet started */
  581. if (!filter->target_valid || !target || !parent) {
  582. obs_source_skip_video_filter(filter->context);
  583. return;
  584. }
  585. /* Render processed image from earlier in the frame */
  586. if (filter->processed_frame) {
  587. draw_greenscreen(filter);
  588. return;
  589. }
  590. if (parent && !filter->handler) {
  591. filter->handler = obs_source_get_signal_handler(parent);
  592. signal_handler_connect(filter->handler, "update_properties",
  593. nv_greenscreen_filter_reset, filter);
  594. }
  595. /* 1. Render to retrieve texture. */
  596. if (!filter->render) {
  597. obs_source_skip_video_filter(filter->context);
  598. return;
  599. }
  600. const uint32_t target_flags = obs_source_get_output_flags(target);
  601. const uint32_t parent_flags = obs_source_get_output_flags(parent);
  602. bool custom_draw = (target_flags & OBS_SOURCE_CUSTOM_DRAW) != 0;
  603. bool async = (target_flags & OBS_SOURCE_ASYNC) != 0;
  604. const enum gs_color_space preferred_spaces[] = {
  605. GS_CS_SRGB,
  606. GS_CS_SRGB_16F,
  607. GS_CS_709_EXTENDED,
  608. };
  609. const enum gs_color_space source_space = obs_source_get_color_space(
  610. obs_filter_get_parent(filter->context),
  611. OBS_COUNTOF(preferred_spaces), preferred_spaces);
  612. if (filter->space != source_space) {
  613. filter->space = source_space;
  614. init_images_greenscreen(filter);
  615. filter->initial_render = false;
  616. }
  617. gs_texrender_t *const render = filter->render;
  618. gs_texrender_reset(render);
  619. gs_blend_state_push();
  620. gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
  621. if (gs_texrender_begin_with_color_space(render, filter->width,
  622. filter->height, source_space)) {
  623. struct vec4 clear_color;
  624. vec4_zero(&clear_color);
  625. gs_clear(GS_CLEAR_COLOR, &clear_color, 0.0f, 0);
  626. gs_ortho(0.0f, (float)filter->width, 0.0f,
  627. (float)filter->height, -100.0f, 100.0f);
  628. if (target == parent && !custom_draw && !async)
  629. obs_source_default_render(target);
  630. else
  631. obs_source_video_render(target);
  632. gs_texrender_end(render);
  633. gs_texrender_t *const render_unorm = filter->render_unorm;
  634. gs_texrender_reset(render_unorm);
  635. if (gs_texrender_begin_with_color_space(
  636. render_unorm, filter->width, filter->height,
  637. GS_CS_SRGB)) {
  638. const bool previous = gs_framebuffer_srgb_enabled();
  639. gs_enable_framebuffer_srgb(true);
  640. gs_enable_blending(false);
  641. gs_ortho(0.0f, (float)filter->width, 0.0f,
  642. (float)filter->height, -100.0f, 100.0f);
  643. const char *tech_name = "ConvertUnorm";
  644. float multiplier = 1.f;
  645. switch (source_space) {
  646. case GS_CS_709_EXTENDED:
  647. tech_name = "ConvertUnormTonemap";
  648. break;
  649. case GS_CS_709_SCRGB:
  650. tech_name = "ConvertUnormMultiplyTonemap";
  651. multiplier =
  652. 80.0f / obs_get_video_sdr_white_level();
  653. }
  654. gs_effect_set_texture_srgb(
  655. filter->image_param,
  656. gs_texrender_get_texture(render));
  657. gs_effect_set_float(filter->multiplier_param,
  658. multiplier);
  659. while (gs_effect_loop(filter->effect, tech_name)) {
  660. gs_draw(GS_TRIS, 0, 3);
  661. }
  662. gs_texrender_end(render_unorm);
  663. gs_enable_blending(true);
  664. gs_enable_framebuffer_srgb(previous);
  665. }
  666. }
  667. gs_blend_state_pop();
  668. /* 2. Initialize src_texture (only at startup or reset) */
  669. if (!filter->initial_render) {
  670. struct ID3D11Texture2D *d11texture2 =
  671. (struct ID3D11Texture2D *)gs_texture_get_obj(
  672. gs_texrender_get_texture(filter->render_unorm));
  673. if (!d11texture2) {
  674. error("Couldn't retrieve d3d11texture2d.");
  675. return;
  676. }
  677. if (!filter->src_img) {
  678. vfxErr = NvCVImage_Create(filter->width, filter->height,
  679. NVCV_BGRA, NVCV_U8,
  680. NVCV_CHUNKY, NVCV_GPU, 1,
  681. &filter->src_img);
  682. if (vfxErr != NVCV_SUCCESS) {
  683. const char *errString =
  684. NvCV_GetErrorStringFromCode(vfxErr);
  685. error("Error creating src img; error %i: %s",
  686. vfxErr, errString);
  687. os_atomic_set_bool(&filter->processing_stop,
  688. true);
  689. return;
  690. }
  691. }
  692. vfxErr = NvCVImage_InitFromD3D11Texture(filter->src_img,
  693. d11texture2);
  694. if (vfxErr != NVCV_SUCCESS) {
  695. const char *errString =
  696. NvCV_GetErrorStringFromCode(vfxErr);
  697. error("Error passing src ID3D11Texture to img; error %i: %s",
  698. vfxErr, errString);
  699. os_atomic_set_bool(&filter->processing_stop, true);
  700. return;
  701. }
  702. filter->initial_render = true;
  703. }
  704. /* 3. Process FX (outputs a mask) & draw. */
  705. if (filter->initial_render && filter->images_allocated) {
  706. bool draw = true;
  707. if (!async || filter->got_new_frame) {
  708. draw = process_texture_greenscreen(filter);
  709. filter->got_new_frame = false;
  710. }
  711. if (draw) {
  712. draw_greenscreen(filter);
  713. filter->processed_frame = true;
  714. }
  715. } else {
  716. obs_source_skip_video_filter(filter->context);
  717. }
  718. UNUSED_PARAMETER(effect);
  719. }
  720. bool load_nvvfx(void)
  721. {
  722. if (!load_nv_vfx_libs()) {
  723. blog(LOG_INFO,
  724. "[NVIDIA RTX VIDEO FX]: FX disabled, redistributable not found.");
  725. return false;
  726. }
  727. #define LOAD_SYM_FROM_LIB(sym, lib, dll) \
  728. if (!(sym = (sym##_t)GetProcAddress(lib, #sym))) { \
  729. DWORD err = GetLastError(); \
  730. printf("[NVIDIA RTX VIDEO FX]: Couldn't load " #sym \
  731. " from " dll ": %lu (0x%lx)", \
  732. err, err); \
  733. release_nv_vfx(); \
  734. goto unload_everything; \
  735. }
  736. #define LOAD_SYM(sym) LOAD_SYM_FROM_LIB(sym, nv_videofx, "NVVideoEffects.dll")
  737. LOAD_SYM(NvVFX_GetVersion);
  738. LOAD_SYM(NvVFX_CreateEffect);
  739. LOAD_SYM(NvVFX_DestroyEffect);
  740. LOAD_SYM(NvVFX_SetU32);
  741. LOAD_SYM(NvVFX_SetS32);
  742. LOAD_SYM(NvVFX_SetF32);
  743. LOAD_SYM(NvVFX_SetF64);
  744. LOAD_SYM(NvVFX_SetU64);
  745. LOAD_SYM(NvVFX_SetObject);
  746. LOAD_SYM(NvVFX_SetCudaStream);
  747. LOAD_SYM(NvVFX_SetImage);
  748. LOAD_SYM(NvVFX_SetString);
  749. LOAD_SYM(NvVFX_GetU32);
  750. LOAD_SYM(NvVFX_GetS32);
  751. LOAD_SYM(NvVFX_GetF32);
  752. LOAD_SYM(NvVFX_GetF64);
  753. LOAD_SYM(NvVFX_GetU64);
  754. LOAD_SYM(NvVFX_GetObject);
  755. LOAD_SYM(NvVFX_GetCudaStream);
  756. LOAD_SYM(NvVFX_GetImage);
  757. LOAD_SYM(NvVFX_GetString);
  758. LOAD_SYM(NvVFX_Run);
  759. LOAD_SYM(NvVFX_Load);
  760. LOAD_SYM(NvVFX_CudaStreamCreate);
  761. LOAD_SYM(NvVFX_CudaStreamDestroy);
  762. #undef LOAD_SYM
  763. #define LOAD_SYM(sym) LOAD_SYM_FROM_LIB(sym, nv_cvimage, "NVCVImage.dll")
  764. LOAD_SYM(NvCV_GetErrorStringFromCode);
  765. LOAD_SYM(NvCVImage_Init);
  766. LOAD_SYM(NvCVImage_InitView);
  767. LOAD_SYM(NvCVImage_Alloc);
  768. LOAD_SYM(NvCVImage_Realloc);
  769. LOAD_SYM(NvCVImage_Dealloc);
  770. LOAD_SYM(NvCVImage_Create);
  771. LOAD_SYM(NvCVImage_Destroy);
  772. LOAD_SYM(NvCVImage_ComponentOffsets);
  773. LOAD_SYM(NvCVImage_Transfer);
  774. LOAD_SYM(NvCVImage_TransferRect);
  775. LOAD_SYM(NvCVImage_TransferFromYUV);
  776. LOAD_SYM(NvCVImage_TransferToYUV);
  777. LOAD_SYM(NvCVImage_MapResource);
  778. LOAD_SYM(NvCVImage_UnmapResource);
  779. LOAD_SYM(NvCVImage_Composite);
  780. LOAD_SYM(NvCVImage_CompositeRect);
  781. LOAD_SYM(NvCVImage_CompositeOverConstant);
  782. LOAD_SYM(NvCVImage_FlipY);
  783. LOAD_SYM(NvCVImage_GetYUVPointers);
  784. LOAD_SYM(NvCVImage_InitFromD3D11Texture);
  785. LOAD_SYM(NvCVImage_ToD3DFormat);
  786. LOAD_SYM(NvCVImage_FromD3DFormat);
  787. LOAD_SYM(NvCVImage_ToD3DColorSpace);
  788. LOAD_SYM(NvCVImage_FromD3DColorSpace);
  789. #undef LOAD_SYM
  790. #define LOAD_SYM(sym) LOAD_SYM_FROM_LIB(sym, nv_cudart, "cudart64_110.dll")
  791. LOAD_SYM(cudaMalloc);
  792. LOAD_SYM(cudaStreamSynchronize);
  793. LOAD_SYM(cudaFree);
  794. LOAD_SYM(cudaMemcpy);
  795. LOAD_SYM(cudaMemsetAsync);
  796. #undef LOAD_SYM
  797. int err;
  798. NvVFX_Handle h = NULL;
  799. /* load the effect to check if the GPU is supported */
  800. err = NvVFX_CreateEffect(NVVFX_FX_GREEN_SCREEN, &h);
  801. if (err != NVCV_SUCCESS) {
  802. if (err == NVCV_ERR_UNSUPPORTEDGPU) {
  803. blog(LOG_INFO,
  804. "[NVIDIA RTX VIDEO FX]: disabled, unsupported GPU");
  805. } else {
  806. blog(LOG_ERROR,
  807. "[NVIDIA RTX VIDEO FX]: disabled, error %i", err);
  808. }
  809. goto unload_everything;
  810. }
  811. NvVFX_DestroyEffect(h);
  812. nvvfx_loaded = true;
  813. blog(LOG_INFO, "[NVIDIA RTX VIDEO FX]: enabled, redistributable found");
  814. return true;
  815. unload_everything:
  816. nvvfx_loaded = false;
  817. release_nv_vfx();
  818. return false;
  819. }
  820. #ifdef LIBNVVFX_ENABLED
  821. void unload_nvvfx(void)
  822. {
  823. release_nv_vfx();
  824. }
  825. #endif
  826. static enum gs_color_space nv_greenscreen_filter_get_color_space(
  827. void *data, size_t count, const enum gs_color_space *preferred_spaces)
  828. {
  829. const enum gs_color_space potential_spaces[] = {
  830. GS_CS_SRGB,
  831. GS_CS_SRGB_16F,
  832. GS_CS_709_EXTENDED,
  833. };
  834. struct nv_greenscreen_data *const filter = data;
  835. const enum gs_color_space source_space = obs_source_get_color_space(
  836. obs_filter_get_parent(filter->context),
  837. OBS_COUNTOF(potential_spaces), potential_spaces);
  838. enum gs_color_space space = source_space;
  839. for (size_t i = 0; i < count; ++i) {
  840. space = preferred_spaces[i];
  841. if (space == source_space)
  842. break;
  843. }
  844. return space;
  845. }
  846. struct obs_source_info nvidia_greenscreen_filter_info = {
  847. .id = "nv_greenscreen_filter",
  848. .type = OBS_SOURCE_TYPE_FILTER,
  849. .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB,
  850. .get_name = nv_greenscreen_filter_name,
  851. .create = nv_greenscreen_filter_create,
  852. .destroy = nv_greenscreen_filter_destroy,
  853. .get_defaults = nv_greenscreen_filter_defaults,
  854. .get_properties = nv_greenscreen_filter_properties,
  855. .update = nv_greenscreen_filter_update,
  856. .filter_video = nv_greenscreen_filter_video,
  857. .video_render = nv_greenscreen_filter_render,
  858. .video_tick = nv_greenscreen_filter_tick,
  859. .video_get_color_space = nv_greenscreen_filter_get_color_space,
  860. };