nvidia-greenscreen-filter.c 29 KB

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