nvidia-greenscreen-filter.c 32 KB

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