scale-filter.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <util/dstr.h>
  4. #include <obs-module.h>
  5. #include <util/platform.h>
  6. #include <graphics/vec2.h>
  7. #include <graphics/math-defs.h>
  8. /* clang-format off */
  9. #define S_RESOLUTION "resolution"
  10. #define S_SAMPLING "sampling"
  11. #define S_UNDISTORT "undistort"
  12. #define T_RESOLUTION obs_module_text("Resolution")
  13. #define T_NONE obs_module_text("None")
  14. #define T_SAMPLING obs_module_text("ScaleFiltering")
  15. #define T_SAMPLING_POINT obs_module_text("ScaleFiltering.Point")
  16. #define T_SAMPLING_BILINEAR obs_module_text("ScaleFiltering.Bilinear")
  17. #define T_SAMPLING_BICUBIC obs_module_text("ScaleFiltering.Bicubic")
  18. #define T_SAMPLING_LANCZOS obs_module_text("ScaleFiltering.Lanczos")
  19. #define T_SAMPLING_AREA obs_module_text("ScaleFiltering.Area")
  20. #define T_UNDISTORT obs_module_text("UndistortCenter")
  21. #define T_BASE obs_module_text("Base.Canvas")
  22. #define S_SAMPLING_POINT "point"
  23. #define S_SAMPLING_BILINEAR "bilinear"
  24. #define S_SAMPLING_BICUBIC "bicubic"
  25. #define S_SAMPLING_LANCZOS "lanczos"
  26. #define S_SAMPLING_AREA "area"
  27. /* clang-format on */
  28. struct scale_filter_data {
  29. obs_source_t *context;
  30. gs_effect_t *effect;
  31. gs_eparam_t *image_param;
  32. gs_eparam_t *dimension_param;
  33. gs_eparam_t *dimension_i_param;
  34. gs_eparam_t *undistort_factor_param;
  35. gs_eparam_t *multiplier_param;
  36. struct vec2 dimension;
  37. struct vec2 dimension_i;
  38. double undistort_factor;
  39. int cx_in;
  40. int cy_in;
  41. int cx_out;
  42. int cy_out;
  43. enum obs_scale_type sampling;
  44. gs_samplerstate_t *point_sampler;
  45. bool aspect_ratio_only;
  46. bool target_valid;
  47. bool valid;
  48. bool can_undistort;
  49. bool undistort;
  50. bool upscale;
  51. bool base_canvas_resolution;
  52. };
  53. static const char *scale_filter_name(void *unused)
  54. {
  55. UNUSED_PARAMETER(unused);
  56. return obs_module_text("ScaleFilter");
  57. }
  58. static void scale_filter_update(void *data, obs_data_t *settings)
  59. {
  60. struct scale_filter_data *filter = data;
  61. int ret;
  62. const char *res_str = obs_data_get_string(settings, S_RESOLUTION);
  63. const char *sampling = obs_data_get_string(settings, S_SAMPLING);
  64. filter->valid = true;
  65. filter->base_canvas_resolution = false;
  66. if (strcmp(res_str, T_BASE) == 0) {
  67. struct obs_video_info ovi;
  68. obs_get_video_info(&ovi);
  69. filter->aspect_ratio_only = false;
  70. filter->base_canvas_resolution = true;
  71. filter->cx_in = ovi.base_width;
  72. filter->cy_in = ovi.base_height;
  73. } else {
  74. ret = sscanf(res_str, "%dx%d", &filter->cx_in, &filter->cy_in);
  75. if (ret == 2) {
  76. filter->aspect_ratio_only = false;
  77. } else {
  78. ret = sscanf(res_str, "%d:%d", &filter->cx_in,
  79. &filter->cy_in);
  80. if (ret != 2) {
  81. filter->valid = false;
  82. return;
  83. }
  84. filter->aspect_ratio_only = true;
  85. }
  86. }
  87. if (astrcmpi(sampling, S_SAMPLING_POINT) == 0) {
  88. filter->sampling = OBS_SCALE_POINT;
  89. } else if (astrcmpi(sampling, S_SAMPLING_BILINEAR) == 0) {
  90. filter->sampling = OBS_SCALE_BILINEAR;
  91. } else if (astrcmpi(sampling, S_SAMPLING_LANCZOS) == 0) {
  92. filter->sampling = OBS_SCALE_LANCZOS;
  93. } else if (astrcmpi(sampling, S_SAMPLING_AREA) == 0) {
  94. filter->sampling = OBS_SCALE_AREA;
  95. } else { /* S_SAMPLING_BICUBIC */
  96. filter->sampling = OBS_SCALE_BICUBIC;
  97. }
  98. filter->can_undistort = obs_data_get_bool(settings, S_UNDISTORT);
  99. }
  100. static void scale_filter_destroy(void *data)
  101. {
  102. struct scale_filter_data *filter = data;
  103. obs_enter_graphics();
  104. gs_samplerstate_destroy(filter->point_sampler);
  105. obs_leave_graphics();
  106. bfree(data);
  107. }
  108. static void *scale_filter_create(obs_data_t *settings, obs_source_t *context)
  109. {
  110. struct scale_filter_data *filter =
  111. bzalloc(sizeof(struct scale_filter_data));
  112. struct gs_sampler_info sampler_info = {0};
  113. filter->context = context;
  114. obs_enter_graphics();
  115. filter->point_sampler = gs_samplerstate_create(&sampler_info);
  116. obs_leave_graphics();
  117. scale_filter_update(filter, settings);
  118. return filter;
  119. }
  120. static void scale_filter_tick(void *data, float seconds)
  121. {
  122. struct scale_filter_data *filter = data;
  123. enum obs_base_effect type;
  124. obs_source_t *target;
  125. bool lower_than_2x;
  126. double cx_f;
  127. double cy_f;
  128. int cx;
  129. int cy;
  130. if (filter->base_canvas_resolution) {
  131. struct obs_video_info ovi;
  132. obs_get_video_info(&ovi);
  133. filter->cx_in = ovi.base_width;
  134. filter->cy_in = ovi.base_height;
  135. }
  136. target = obs_filter_get_target(filter->context);
  137. filter->cx_out = 0;
  138. filter->cy_out = 0;
  139. filter->target_valid = !!target;
  140. if (!filter->target_valid)
  141. return;
  142. cx = obs_source_get_base_width(target);
  143. cy = obs_source_get_base_height(target);
  144. if (!cx || !cy) {
  145. filter->target_valid = false;
  146. return;
  147. }
  148. filter->cx_out = cx;
  149. filter->cy_out = cy;
  150. if (!filter->valid)
  151. return;
  152. /* ------------------------- */
  153. cx_f = (double)cx;
  154. cy_f = (double)cy;
  155. double old_aspect = cx_f / cy_f;
  156. double new_aspect = (double)filter->cx_in / (double)filter->cy_in;
  157. if (filter->aspect_ratio_only) {
  158. if (fabs(old_aspect - new_aspect) <= EPSILON) {
  159. filter->target_valid = false;
  160. return;
  161. } else {
  162. if (new_aspect > old_aspect) {
  163. filter->cx_out = (int)(cy_f * new_aspect);
  164. } else {
  165. filter->cy_out = (int)(cx_f / new_aspect);
  166. }
  167. }
  168. } else {
  169. filter->cx_out = filter->cx_in;
  170. filter->cy_out = filter->cy_in;
  171. }
  172. vec2_set(&filter->dimension, (float)cx, (float)cy);
  173. vec2_set(&filter->dimension_i, 1.0f / (float)cx, 1.0f / (float)cy);
  174. filter->undistort = false;
  175. filter->upscale = false;
  176. /* ------------------------- */
  177. lower_than_2x = filter->cx_out < cx / 2 || filter->cy_out < cy / 2;
  178. if (lower_than_2x && filter->sampling != OBS_SCALE_POINT) {
  179. type = OBS_EFFECT_BILINEAR_LOWRES;
  180. } else {
  181. switch (filter->sampling) {
  182. default:
  183. case OBS_SCALE_POINT:
  184. case OBS_SCALE_BILINEAR:
  185. type = OBS_EFFECT_DEFAULT;
  186. break;
  187. case OBS_SCALE_BICUBIC:
  188. type = OBS_EFFECT_BICUBIC;
  189. filter->undistort = filter->can_undistort;
  190. break;
  191. case OBS_SCALE_LANCZOS:
  192. type = OBS_EFFECT_LANCZOS;
  193. filter->undistort = filter->can_undistort;
  194. break;
  195. case OBS_SCALE_AREA:
  196. type = OBS_EFFECT_AREA;
  197. if ((filter->cx_out >= cx) && (filter->cy_out >= cy))
  198. filter->upscale = true;
  199. break;
  200. }
  201. }
  202. filter->undistort_factor = filter->undistort ? (new_aspect / old_aspect)
  203. : 1.0;
  204. filter->effect = obs_get_base_effect(type);
  205. filter->image_param =
  206. gs_effect_get_param_by_name(filter->effect, "image");
  207. if (type != OBS_EFFECT_DEFAULT) {
  208. filter->dimension_param = gs_effect_get_param_by_name(
  209. filter->effect, "base_dimension");
  210. filter->dimension_i_param = gs_effect_get_param_by_name(
  211. filter->effect, "base_dimension_i");
  212. } else {
  213. filter->dimension_param = NULL;
  214. filter->dimension_i_param = NULL;
  215. }
  216. if (type == OBS_EFFECT_BICUBIC || type == OBS_EFFECT_LANCZOS) {
  217. filter->undistort_factor_param = gs_effect_get_param_by_name(
  218. filter->effect, "undistort_factor");
  219. } else {
  220. filter->undistort_factor_param = NULL;
  221. }
  222. filter->multiplier_param =
  223. gs_effect_get_param_by_name(filter->effect, "multiplier");
  224. UNUSED_PARAMETER(seconds);
  225. }
  226. static const char *
  227. get_tech_name_and_multiplier(const struct scale_filter_data *filter,
  228. enum gs_color_space current_space,
  229. enum gs_color_space source_space,
  230. float *multiplier)
  231. {
  232. *multiplier = 1.f;
  233. switch (source_space) {
  234. case GS_CS_SRGB:
  235. case GS_CS_SRGB_16F:
  236. case GS_CS_709_EXTENDED:
  237. if (current_space == GS_CS_709_SCRGB)
  238. *multiplier = obs_get_video_sdr_white_level() / 80.f;
  239. break;
  240. case GS_CS_709_SCRGB:
  241. switch (current_space) {
  242. case GS_CS_SRGB:
  243. case GS_CS_SRGB_16F:
  244. case GS_CS_709_EXTENDED:
  245. *multiplier = 80.f / obs_get_video_sdr_white_level();
  246. break;
  247. case GS_CS_709_SCRGB:
  248. break;
  249. }
  250. }
  251. const char *tech_name = "Draw";
  252. if (filter->undistort) {
  253. tech_name = "DrawUndistort";
  254. switch (source_space) {
  255. case GS_CS_SRGB:
  256. case GS_CS_SRGB_16F:
  257. if (current_space == GS_CS_709_SCRGB)
  258. tech_name = "DrawUndistortMultiply";
  259. break;
  260. case GS_CS_709_EXTENDED:
  261. switch (current_space) {
  262. case GS_CS_SRGB:
  263. case GS_CS_SRGB_16F:
  264. tech_name = "DrawUndistortTonemap";
  265. break;
  266. case GS_CS_709_SCRGB:
  267. tech_name = "DrawUndistortMultiply";
  268. break;
  269. case GS_CS_709_EXTENDED:
  270. break;
  271. }
  272. break;
  273. case GS_CS_709_SCRGB:
  274. switch (current_space) {
  275. case GS_CS_SRGB:
  276. case GS_CS_SRGB_16F:
  277. tech_name = "DrawUndistortMultiplyTonemap";
  278. break;
  279. case GS_CS_709_EXTENDED:
  280. tech_name = "DrawUndistortMultiply";
  281. break;
  282. case GS_CS_709_SCRGB:
  283. break;
  284. }
  285. }
  286. } else if (filter->upscale) {
  287. tech_name = "DrawUpscale";
  288. switch (source_space) {
  289. case GS_CS_SRGB:
  290. case GS_CS_SRGB_16F:
  291. if (current_space == GS_CS_709_SCRGB)
  292. tech_name = "DrawUpscaleMultiply";
  293. break;
  294. case GS_CS_709_EXTENDED:
  295. switch (current_space) {
  296. case GS_CS_SRGB:
  297. case GS_CS_SRGB_16F:
  298. tech_name = "DrawUpscaleTonemap";
  299. break;
  300. case GS_CS_709_SCRGB:
  301. tech_name = "DrawUpscaleMultiply";
  302. break;
  303. case GS_CS_709_EXTENDED:
  304. break;
  305. }
  306. break;
  307. case GS_CS_709_SCRGB:
  308. switch (current_space) {
  309. case GS_CS_SRGB:
  310. case GS_CS_SRGB_16F:
  311. tech_name = "DrawUpscaleMultiplyTonemap";
  312. break;
  313. case GS_CS_709_EXTENDED:
  314. tech_name = "DrawUpscaleMultiply";
  315. break;
  316. case GS_CS_709_SCRGB:
  317. break;
  318. }
  319. }
  320. } else {
  321. switch (source_space) {
  322. case GS_CS_SRGB:
  323. case GS_CS_SRGB_16F:
  324. if (current_space == GS_CS_709_SCRGB)
  325. tech_name = "DrawMultiply";
  326. break;
  327. case GS_CS_709_EXTENDED:
  328. switch (current_space) {
  329. case GS_CS_SRGB:
  330. case GS_CS_SRGB_16F:
  331. tech_name = "DrawTonemap";
  332. break;
  333. case GS_CS_709_SCRGB:
  334. tech_name = "DrawMultiply";
  335. break;
  336. case GS_CS_709_EXTENDED:
  337. break;
  338. }
  339. break;
  340. case GS_CS_709_SCRGB:
  341. switch (current_space) {
  342. case GS_CS_SRGB:
  343. case GS_CS_SRGB_16F:
  344. tech_name = "DrawMultiplyTonemap";
  345. break;
  346. case GS_CS_709_EXTENDED:
  347. tech_name = "DrawMultiply";
  348. break;
  349. case GS_CS_709_SCRGB:
  350. break;
  351. }
  352. }
  353. }
  354. return tech_name;
  355. }
  356. static void scale_filter_render(void *data, gs_effect_t *effect)
  357. {
  358. UNUSED_PARAMETER(effect);
  359. struct scale_filter_data *filter = data;
  360. if (!filter->valid || !filter->target_valid) {
  361. obs_source_skip_video_filter(filter->context);
  362. return;
  363. }
  364. const enum gs_color_space preferred_spaces[] = {
  365. GS_CS_SRGB,
  366. GS_CS_SRGB_16F,
  367. GS_CS_709_EXTENDED,
  368. };
  369. const enum gs_color_space source_space = obs_source_get_color_space(
  370. obs_filter_get_target(filter->context),
  371. OBS_COUNTOF(preferred_spaces), preferred_spaces);
  372. float multiplier;
  373. const char *technique = get_tech_name_and_multiplier(
  374. filter, gs_get_color_space(), source_space, &multiplier);
  375. const enum gs_color_format format =
  376. gs_get_format_from_space(source_space);
  377. if (obs_source_process_filter_begin_with_color_space(
  378. filter->context, format, source_space,
  379. OBS_NO_DIRECT_RENDERING)) {
  380. if (filter->dimension_param)
  381. gs_effect_set_vec2(filter->dimension_param,
  382. &filter->dimension);
  383. if (filter->dimension_i_param)
  384. gs_effect_set_vec2(filter->dimension_i_param,
  385. &filter->dimension_i);
  386. if (filter->undistort_factor_param)
  387. gs_effect_set_float(filter->undistort_factor_param,
  388. (float)filter->undistort_factor);
  389. if (filter->multiplier_param)
  390. gs_effect_set_float(filter->multiplier_param,
  391. multiplier);
  392. if (filter->sampling == OBS_SCALE_POINT)
  393. gs_effect_set_next_sampler(filter->image_param,
  394. filter->point_sampler);
  395. gs_blend_state_push();
  396. gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
  397. obs_source_process_filter_tech_end(filter->context,
  398. filter->effect,
  399. filter->cx_out,
  400. filter->cy_out, technique);
  401. gs_blend_state_pop();
  402. }
  403. }
  404. static const double downscale_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5,
  405. (1.0 / 0.6), 1.75, 2.0, 2.25,
  406. 2.5, 2.75, 3.0};
  407. #define NUM_DOWNSCALES (sizeof(downscale_vals) / sizeof(double))
  408. static const char *aspects[] = {"16:9", "16:10", "4:3", "1:1"};
  409. #define NUM_ASPECTS (sizeof(aspects) / sizeof(const char *))
  410. static bool sampling_modified(obs_properties_t *props, obs_property_t *p,
  411. obs_data_t *settings)
  412. {
  413. const char *sampling = obs_data_get_string(settings, S_SAMPLING);
  414. bool has_undistort;
  415. if (astrcmpi(sampling, S_SAMPLING_POINT) == 0) {
  416. has_undistort = false;
  417. } else if (astrcmpi(sampling, S_SAMPLING_BILINEAR) == 0) {
  418. has_undistort = false;
  419. } else if (astrcmpi(sampling, S_SAMPLING_LANCZOS) == 0) {
  420. has_undistort = true;
  421. } else if (astrcmpi(sampling, S_SAMPLING_AREA) == 0) {
  422. has_undistort = false;
  423. } else { /* S_SAMPLING_BICUBIC */
  424. has_undistort = true;
  425. }
  426. obs_property_set_visible(obs_properties_get(props, S_UNDISTORT),
  427. has_undistort);
  428. UNUSED_PARAMETER(p);
  429. return true;
  430. }
  431. static obs_properties_t *scale_filter_properties(void *data)
  432. {
  433. obs_properties_t *props = obs_properties_create();
  434. struct obs_video_info ovi;
  435. obs_property_t *p;
  436. uint32_t cx;
  437. uint32_t cy;
  438. struct {
  439. int cx;
  440. int cy;
  441. } downscales[NUM_DOWNSCALES];
  442. /* ----------------- */
  443. obs_get_video_info(&ovi);
  444. cx = ovi.base_width;
  445. cy = ovi.base_height;
  446. for (size_t i = 0; i < NUM_DOWNSCALES; i++) {
  447. downscales[i].cx = (int)((double)cx / downscale_vals[i]);
  448. downscales[i].cy = (int)((double)cy / downscale_vals[i]);
  449. }
  450. p = obs_properties_add_list(props, S_SAMPLING, T_SAMPLING,
  451. OBS_COMBO_TYPE_LIST,
  452. OBS_COMBO_FORMAT_STRING);
  453. obs_property_set_modified_callback(p, sampling_modified);
  454. obs_property_list_add_string(p, T_SAMPLING_POINT, S_SAMPLING_POINT);
  455. obs_property_list_add_string(p, T_SAMPLING_BILINEAR,
  456. S_SAMPLING_BILINEAR);
  457. obs_property_list_add_string(p, T_SAMPLING_BICUBIC, S_SAMPLING_BICUBIC);
  458. obs_property_list_add_string(p, T_SAMPLING_LANCZOS, S_SAMPLING_LANCZOS);
  459. obs_property_list_add_string(p, T_SAMPLING_AREA, S_SAMPLING_AREA);
  460. /* ----------------- */
  461. p = obs_properties_add_list(props, S_RESOLUTION, T_RESOLUTION,
  462. OBS_COMBO_TYPE_EDITABLE,
  463. OBS_COMBO_FORMAT_STRING);
  464. obs_property_list_add_string(p, T_NONE, T_NONE);
  465. obs_property_list_add_string(p, T_BASE, T_BASE);
  466. for (size_t i = 0; i < NUM_ASPECTS; i++)
  467. obs_property_list_add_string(p, aspects[i], aspects[i]);
  468. for (size_t i = 0; i < NUM_DOWNSCALES; i++) {
  469. char str[32];
  470. snprintf(str, sizeof(str), "%dx%d", downscales[i].cx,
  471. downscales[i].cy);
  472. obs_property_list_add_string(p, str, str);
  473. }
  474. obs_properties_add_bool(props, S_UNDISTORT, T_UNDISTORT);
  475. /* ----------------- */
  476. UNUSED_PARAMETER(data);
  477. return props;
  478. }
  479. static void scale_filter_defaults(obs_data_t *settings)
  480. {
  481. obs_data_set_default_string(settings, S_SAMPLING, S_SAMPLING_BICUBIC);
  482. obs_data_set_default_string(settings, S_RESOLUTION, T_NONE);
  483. obs_data_set_default_bool(settings, S_UNDISTORT, 0);
  484. }
  485. static uint32_t scale_filter_width(void *data)
  486. {
  487. struct scale_filter_data *filter = data;
  488. return (uint32_t)filter->cx_out;
  489. }
  490. static uint32_t scale_filter_height(void *data)
  491. {
  492. struct scale_filter_data *filter = data;
  493. return (uint32_t)filter->cy_out;
  494. }
  495. static enum gs_color_space
  496. scale_filter_get_color_space(void *data, size_t count,
  497. const enum gs_color_space *preferred_spaces)
  498. {
  499. const enum gs_color_space potential_spaces[] = {
  500. GS_CS_SRGB,
  501. GS_CS_SRGB_16F,
  502. GS_CS_709_EXTENDED,
  503. };
  504. struct scale_filter_data *const filter = data;
  505. const enum gs_color_space source_space = obs_source_get_color_space(
  506. obs_filter_get_target(filter->context),
  507. OBS_COUNTOF(potential_spaces), potential_spaces);
  508. enum gs_color_space space = source_space;
  509. for (size_t i = 0; i < count; ++i) {
  510. space = preferred_spaces[i];
  511. if (space == source_space)
  512. break;
  513. }
  514. return space;
  515. }
  516. struct obs_source_info scale_filter = {
  517. .id = "scale_filter",
  518. .type = OBS_SOURCE_TYPE_FILTER,
  519. .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB,
  520. .get_name = scale_filter_name,
  521. .create = scale_filter_create,
  522. .destroy = scale_filter_destroy,
  523. .video_tick = scale_filter_tick,
  524. .video_render = scale_filter_render,
  525. .update = scale_filter_update,
  526. .get_properties = scale_filter_properties,
  527. .get_defaults = scale_filter_defaults,
  528. .get_width = scale_filter_width,
  529. .get_height = scale_filter_height,
  530. .video_get_color_space = scale_filter_get_color_space,
  531. };