gpu-delay.c 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. #include <obs-module.h>
  2. #include <util/circlebuf.h>
  3. #include <util/util_uint64.h>
  4. #define S_DELAY_MS "delay_ms"
  5. #define T_DELAY_MS obs_module_text("DelayMs")
  6. struct frame {
  7. gs_texrender_t *render;
  8. enum gs_color_space space;
  9. uint64_t ts;
  10. };
  11. struct gpu_delay_filter_data {
  12. obs_source_t *context;
  13. struct circlebuf frames;
  14. uint64_t delay_ns;
  15. uint64_t interval_ns;
  16. uint32_t cx;
  17. uint32_t cy;
  18. bool target_valid;
  19. bool processed_frame;
  20. };
  21. static const char *gpu_delay_filter_get_name(void *unused)
  22. {
  23. UNUSED_PARAMETER(unused);
  24. return obs_module_text("GPUDelayFilter");
  25. }
  26. static void free_textures(struct gpu_delay_filter_data *f)
  27. {
  28. obs_enter_graphics();
  29. while (f->frames.size) {
  30. struct frame frame;
  31. circlebuf_pop_front(&f->frames, &frame, sizeof(frame));
  32. gs_texrender_destroy(frame.render);
  33. }
  34. circlebuf_free(&f->frames);
  35. obs_leave_graphics();
  36. }
  37. static size_t num_frames(struct circlebuf *buf)
  38. {
  39. return buf->size / sizeof(struct frame);
  40. }
  41. static void update_interval(struct gpu_delay_filter_data *f,
  42. uint64_t new_interval_ns)
  43. {
  44. if (!f->target_valid) {
  45. free_textures(f);
  46. return;
  47. }
  48. f->interval_ns = new_interval_ns;
  49. size_t num = (size_t)(f->delay_ns / new_interval_ns);
  50. if (num > num_frames(&f->frames)) {
  51. size_t prev_num = num_frames(&f->frames);
  52. obs_enter_graphics();
  53. circlebuf_upsize(&f->frames, num * sizeof(struct frame));
  54. for (size_t i = prev_num; i < num; i++) {
  55. struct frame *frame =
  56. circlebuf_data(&f->frames, i * sizeof(*frame));
  57. frame->render =
  58. gs_texrender_create(GS_RGBA, GS_ZS_NONE);
  59. }
  60. obs_leave_graphics();
  61. } else if (num < num_frames(&f->frames)) {
  62. obs_enter_graphics();
  63. while (num_frames(&f->frames) > num) {
  64. struct frame frame;
  65. circlebuf_pop_front(&f->frames, &frame, sizeof(frame));
  66. gs_texrender_destroy(frame.render);
  67. }
  68. obs_leave_graphics();
  69. }
  70. }
  71. static inline void check_interval(struct gpu_delay_filter_data *f)
  72. {
  73. struct obs_video_info ovi = {0};
  74. uint64_t interval_ns;
  75. obs_get_video_info(&ovi);
  76. interval_ns = util_mul_div64(ovi.fps_den, 1000000000ULL, ovi.fps_num);
  77. if (interval_ns != f->interval_ns)
  78. update_interval(f, interval_ns);
  79. }
  80. static inline void reset_textures(struct gpu_delay_filter_data *f)
  81. {
  82. f->interval_ns = 0;
  83. free_textures(f);
  84. check_interval(f);
  85. }
  86. static inline bool check_size(struct gpu_delay_filter_data *f)
  87. {
  88. obs_source_t *target = obs_filter_get_target(f->context);
  89. uint32_t cx;
  90. uint32_t cy;
  91. f->target_valid = !!target;
  92. if (!f->target_valid)
  93. return true;
  94. cx = obs_source_get_base_width(target);
  95. cy = obs_source_get_base_height(target);
  96. f->target_valid = !!cx && !!cy;
  97. if (!f->target_valid)
  98. return true;
  99. if (cx != f->cx || cy != f->cy) {
  100. f->cx = cx;
  101. f->cy = cy;
  102. reset_textures(f);
  103. return true;
  104. }
  105. return false;
  106. }
  107. static void gpu_delay_filter_update(void *data, obs_data_t *s)
  108. {
  109. struct gpu_delay_filter_data *f = data;
  110. f->delay_ns = (uint64_t)obs_data_get_int(s, S_DELAY_MS) * 1000000ULL;
  111. /* full reset */
  112. f->cx = 0;
  113. f->cy = 0;
  114. f->interval_ns = 0;
  115. free_textures(f);
  116. }
  117. static obs_properties_t *gpu_delay_filter_properties(void *data)
  118. {
  119. obs_properties_t *props = obs_properties_create();
  120. obs_property_t *p = obs_properties_add_int(props, S_DELAY_MS,
  121. T_DELAY_MS, 0, 500, 1);
  122. obs_property_int_set_suffix(p, " ms");
  123. UNUSED_PARAMETER(data);
  124. return props;
  125. }
  126. static void *gpu_delay_filter_create(obs_data_t *settings,
  127. obs_source_t *context)
  128. {
  129. struct gpu_delay_filter_data *f = bzalloc(sizeof(*f));
  130. f->context = context;
  131. obs_source_update(context, settings);
  132. return f;
  133. }
  134. static void gpu_delay_filter_destroy(void *data)
  135. {
  136. struct gpu_delay_filter_data *f = data;
  137. free_textures(f);
  138. bfree(f);
  139. }
  140. static void gpu_delay_filter_tick(void *data, float t)
  141. {
  142. UNUSED_PARAMETER(t);
  143. struct gpu_delay_filter_data *f = data;
  144. f->processed_frame = false;
  145. if (check_size(f))
  146. return;
  147. check_interval(f);
  148. }
  149. static const char *
  150. get_tech_name_and_multiplier(enum gs_color_space current_space,
  151. enum gs_color_space source_space,
  152. float *multiplier)
  153. {
  154. const char *tech_name = "Draw";
  155. *multiplier = 1.f;
  156. switch (source_space) {
  157. case GS_CS_SRGB:
  158. case GS_CS_SRGB_16F:
  159. switch (current_space) {
  160. case GS_CS_709_SCRGB:
  161. tech_name = "DrawMultiply";
  162. *multiplier = obs_get_video_sdr_white_level() / 80.0f;
  163. }
  164. break;
  165. case GS_CS_709_EXTENDED:
  166. switch (current_space) {
  167. case GS_CS_SRGB:
  168. case GS_CS_SRGB_16F:
  169. tech_name = "DrawTonemap";
  170. break;
  171. case GS_CS_709_SCRGB:
  172. tech_name = "DrawMultiply";
  173. *multiplier = obs_get_video_sdr_white_level() / 80.0f;
  174. }
  175. break;
  176. case GS_CS_709_SCRGB:
  177. switch (current_space) {
  178. case GS_CS_SRGB:
  179. case GS_CS_SRGB_16F:
  180. tech_name = "DrawMultiplyTonemap";
  181. *multiplier = 80.0f / obs_get_video_sdr_white_level();
  182. break;
  183. case GS_CS_709_EXTENDED:
  184. tech_name = "DrawMultiply";
  185. *multiplier = 80.0f / obs_get_video_sdr_white_level();
  186. }
  187. }
  188. return tech_name;
  189. }
  190. static void draw_frame(struct gpu_delay_filter_data *f)
  191. {
  192. struct frame frame;
  193. circlebuf_peek_front(&f->frames, &frame, sizeof(frame));
  194. const enum gs_color_space current_space = gs_get_color_space();
  195. float multiplier;
  196. const char *technique = get_tech_name_and_multiplier(
  197. current_space, frame.space, &multiplier);
  198. gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
  199. gs_texture_t *tex = gs_texrender_get_texture(frame.render);
  200. if (tex) {
  201. const bool previous = gs_framebuffer_srgb_enabled();
  202. gs_enable_framebuffer_srgb(true);
  203. gs_effect_set_texture_srgb(
  204. gs_effect_get_param_by_name(effect, "image"), tex);
  205. gs_effect_set_float(gs_effect_get_param_by_name(effect,
  206. "multiplier"),
  207. multiplier);
  208. while (gs_effect_loop(effect, technique))
  209. gs_draw_sprite(tex, 0, f->cx, f->cy);
  210. gs_enable_framebuffer_srgb(previous);
  211. }
  212. }
  213. static void gpu_delay_filter_render(void *data, gs_effect_t *effect)
  214. {
  215. struct gpu_delay_filter_data *f = data;
  216. obs_source_t *target = obs_filter_get_target(f->context);
  217. obs_source_t *parent = obs_filter_get_parent(f->context);
  218. if (!f->target_valid || !target || !parent || !f->frames.size) {
  219. obs_source_skip_video_filter(f->context);
  220. return;
  221. }
  222. if (f->processed_frame) {
  223. draw_frame(f);
  224. return;
  225. }
  226. struct frame frame;
  227. circlebuf_pop_front(&f->frames, &frame, sizeof(frame));
  228. const enum gs_color_space preferred_spaces[] = {
  229. GS_CS_SRGB,
  230. GS_CS_SRGB_16F,
  231. GS_CS_709_EXTENDED,
  232. };
  233. const enum gs_color_space space = obs_source_get_color_space(
  234. target, OBS_COUNTOF(preferred_spaces), preferred_spaces);
  235. const enum gs_color_format format = gs_get_format_from_space(space);
  236. if (gs_texrender_get_format(frame.render) != format) {
  237. gs_texrender_destroy(frame.render);
  238. frame.render = gs_texrender_create(format, GS_ZS_NONE);
  239. }
  240. gs_texrender_reset(frame.render);
  241. gs_blend_state_push();
  242. gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
  243. if (gs_texrender_begin_with_color_space(frame.render, f->cx, f->cy,
  244. space)) {
  245. uint32_t parent_flags = obs_source_get_output_flags(target);
  246. bool custom_draw = (parent_flags & OBS_SOURCE_CUSTOM_DRAW) != 0;
  247. bool async = (parent_flags & OBS_SOURCE_ASYNC) != 0;
  248. struct vec4 clear_color;
  249. vec4_zero(&clear_color);
  250. gs_clear(GS_CLEAR_COLOR, &clear_color, 0.0f, 0);
  251. gs_ortho(0.0f, (float)f->cx, 0.0f, (float)f->cy, -100.0f,
  252. 100.0f);
  253. if (target == parent && !custom_draw && !async)
  254. obs_source_default_render(target);
  255. else
  256. obs_source_video_render(target);
  257. gs_texrender_end(frame.render);
  258. frame.space = space;
  259. }
  260. gs_blend_state_pop();
  261. circlebuf_push_back(&f->frames, &frame, sizeof(frame));
  262. draw_frame(f);
  263. f->processed_frame = true;
  264. UNUSED_PARAMETER(effect);
  265. }
  266. static enum gs_color_space
  267. gpu_delay_filter_get_color_space(void *data, size_t count,
  268. const enum gs_color_space *preferred_spaces)
  269. {
  270. struct gpu_delay_filter_data *const f = data;
  271. obs_source_t *target = obs_filter_get_target(f->context);
  272. obs_source_t *parent = obs_filter_get_parent(f->context);
  273. if (!f->target_valid || !target || !parent || !f->frames.size) {
  274. return (count > 0) ? preferred_spaces[0] : GS_CS_SRGB;
  275. }
  276. struct frame frame;
  277. circlebuf_peek_front(&f->frames, &frame, sizeof(frame));
  278. enum gs_color_space space = frame.space;
  279. for (size_t i = 0; i < count; ++i) {
  280. space = preferred_spaces[i];
  281. if (space == frame.space)
  282. break;
  283. }
  284. return space;
  285. }
  286. struct obs_source_info gpu_delay_filter = {
  287. .id = "gpu_delay",
  288. .type = OBS_SOURCE_TYPE_FILTER,
  289. .output_flags = OBS_SOURCE_VIDEO,
  290. .get_name = gpu_delay_filter_get_name,
  291. .create = gpu_delay_filter_create,
  292. .destroy = gpu_delay_filter_destroy,
  293. .update = gpu_delay_filter_update,
  294. .get_properties = gpu_delay_filter_properties,
  295. .video_tick = gpu_delay_filter_tick,
  296. .video_render = gpu_delay_filter_render,
  297. .video_get_color_space = gpu_delay_filter_get_color_space,
  298. };