123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- #include <obs-module.h>
- #include <util/deque.h>
- #include <util/util_uint64.h>
- #define S_DELAY_MS "delay_ms"
- #define T_DELAY_MS obs_module_text("DelayMs")
- struct frame {
- gs_texrender_t *render;
- enum gs_color_space space;
- uint64_t ts;
- };
- struct gpu_delay_filter_data {
- obs_source_t *context;
- struct deque frames;
- uint64_t delay_ns;
- uint64_t interval_ns;
- uint32_t cx;
- uint32_t cy;
- bool target_valid;
- bool processed_frame;
- };
- static const char *gpu_delay_filter_get_name(void *unused)
- {
- UNUSED_PARAMETER(unused);
- return obs_module_text("GPUDelayFilter");
- }
- static void free_textures(struct gpu_delay_filter_data *f)
- {
- obs_enter_graphics();
- while (f->frames.size) {
- struct frame frame;
- deque_pop_front(&f->frames, &frame, sizeof(frame));
- gs_texrender_destroy(frame.render);
- }
- deque_free(&f->frames);
- obs_leave_graphics();
- }
- static size_t num_frames(struct deque *buf)
- {
- return buf->size / sizeof(struct frame);
- }
- static void update_interval(struct gpu_delay_filter_data *f, uint64_t new_interval_ns)
- {
- if (!f->target_valid) {
- free_textures(f);
- return;
- }
- f->interval_ns = new_interval_ns;
- size_t num = (size_t)(f->delay_ns / new_interval_ns);
- if (num > num_frames(&f->frames)) {
- size_t prev_num = num_frames(&f->frames);
- obs_enter_graphics();
- deque_upsize(&f->frames, num * sizeof(struct frame));
- for (size_t i = prev_num; i < num; i++) {
- struct frame *frame = deque_data(&f->frames, i * sizeof(*frame));
- frame->render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
- }
- obs_leave_graphics();
- } else if (num < num_frames(&f->frames)) {
- obs_enter_graphics();
- while (num_frames(&f->frames) > num) {
- struct frame frame;
- deque_pop_front(&f->frames, &frame, sizeof(frame));
- gs_texrender_destroy(frame.render);
- }
- obs_leave_graphics();
- }
- }
- static inline void check_interval(struct gpu_delay_filter_data *f)
- {
- struct obs_video_info ovi = {0};
- uint64_t interval_ns;
- obs_get_video_info(&ovi);
- interval_ns = util_mul_div64(ovi.fps_den, 1000000000ULL, ovi.fps_num);
- if (interval_ns != f->interval_ns)
- update_interval(f, interval_ns);
- }
- static inline void reset_textures(struct gpu_delay_filter_data *f)
- {
- f->interval_ns = 0;
- free_textures(f);
- check_interval(f);
- }
- static inline bool check_size(struct gpu_delay_filter_data *f)
- {
- obs_source_t *target = obs_filter_get_target(f->context);
- uint32_t cx;
- uint32_t cy;
- f->target_valid = !!target;
- if (!f->target_valid)
- return true;
- cx = obs_source_get_base_width(target);
- cy = obs_source_get_base_height(target);
- f->target_valid = !!cx && !!cy;
- if (!f->target_valid)
- return true;
- if (cx != f->cx || cy != f->cy) {
- f->cx = cx;
- f->cy = cy;
- reset_textures(f);
- return true;
- }
- return false;
- }
- static void gpu_delay_filter_update(void *data, obs_data_t *s)
- {
- struct gpu_delay_filter_data *f = data;
- f->delay_ns = (uint64_t)obs_data_get_int(s, S_DELAY_MS) * 1000000ULL;
- /* full reset */
- f->cx = 0;
- f->cy = 0;
- f->interval_ns = 0;
- free_textures(f);
- }
- static obs_properties_t *gpu_delay_filter_properties(void *data)
- {
- obs_properties_t *props = obs_properties_create();
- obs_property_t *p = obs_properties_add_int(props, S_DELAY_MS, T_DELAY_MS, 0, 500, 1);
- obs_property_int_set_suffix(p, " ms");
- UNUSED_PARAMETER(data);
- return props;
- }
- static void *gpu_delay_filter_create(obs_data_t *settings, obs_source_t *context)
- {
- struct gpu_delay_filter_data *f = bzalloc(sizeof(*f));
- f->context = context;
- obs_source_update(context, settings);
- return f;
- }
- static void gpu_delay_filter_destroy(void *data)
- {
- struct gpu_delay_filter_data *f = data;
- free_textures(f);
- bfree(f);
- }
- static void gpu_delay_filter_tick(void *data, float t)
- {
- UNUSED_PARAMETER(t);
- struct gpu_delay_filter_data *f = data;
- f->processed_frame = false;
- if (check_size(f))
- return;
- check_interval(f);
- }
- static const char *get_tech_name_and_multiplier(enum gs_color_space current_space, enum gs_color_space source_space,
- float *multiplier)
- {
- const char *tech_name = "Draw";
- *multiplier = 1.f;
- switch (source_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- if (current_space == GS_CS_709_SCRGB) {
- tech_name = "DrawMultiply";
- *multiplier = obs_get_video_sdr_white_level() / 80.0f;
- }
- break;
- case GS_CS_709_EXTENDED:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- tech_name = "DrawTonemap";
- break;
- case GS_CS_709_SCRGB:
- tech_name = "DrawMultiply";
- *multiplier = obs_get_video_sdr_white_level() / 80.0f;
- break;
- default:
- break;
- }
- break;
- case GS_CS_709_SCRGB:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- tech_name = "DrawMultiplyTonemap";
- *multiplier = 80.0f / obs_get_video_sdr_white_level();
- break;
- case GS_CS_709_EXTENDED:
- tech_name = "DrawMultiply";
- *multiplier = 80.0f / obs_get_video_sdr_white_level();
- break;
- default:
- break;
- }
- }
- return tech_name;
- }
- static void draw_frame(struct gpu_delay_filter_data *f)
- {
- struct frame frame;
- deque_peek_front(&f->frames, &frame, sizeof(frame));
- const enum gs_color_space current_space = gs_get_color_space();
- float multiplier;
- const char *technique = get_tech_name_and_multiplier(current_space, frame.space, &multiplier);
- gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
- gs_texture_t *tex = gs_texrender_get_texture(frame.render);
- if (tex) {
- const bool previous = gs_framebuffer_srgb_enabled();
- gs_enable_framebuffer_srgb(true);
- gs_effect_set_texture_srgb(gs_effect_get_param_by_name(effect, "image"), tex);
- gs_effect_set_float(gs_effect_get_param_by_name(effect, "multiplier"), multiplier);
- while (gs_effect_loop(effect, technique))
- gs_draw_sprite(tex, 0, f->cx, f->cy);
- gs_enable_framebuffer_srgb(previous);
- }
- }
- static void gpu_delay_filter_render(void *data, gs_effect_t *effect)
- {
- struct gpu_delay_filter_data *f = data;
- obs_source_t *target = obs_filter_get_target(f->context);
- obs_source_t *parent = obs_filter_get_parent(f->context);
- if (!f->target_valid || !target || !parent || !f->frames.size) {
- obs_source_skip_video_filter(f->context);
- return;
- }
- if (f->processed_frame) {
- draw_frame(f);
- return;
- }
- struct frame frame;
- deque_pop_front(&f->frames, &frame, sizeof(frame));
- const enum gs_color_space preferred_spaces[] = {
- GS_CS_SRGB,
- GS_CS_SRGB_16F,
- GS_CS_709_EXTENDED,
- };
- const enum gs_color_space space =
- obs_source_get_color_space(target, OBS_COUNTOF(preferred_spaces), preferred_spaces);
- const enum gs_color_format format = gs_get_format_from_space(space);
- if (gs_texrender_get_format(frame.render) != format) {
- gs_texrender_destroy(frame.render);
- frame.render = gs_texrender_create(format, GS_ZS_NONE);
- }
- gs_texrender_reset(frame.render);
- gs_blend_state_push();
- gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
- if (gs_texrender_begin_with_color_space(frame.render, f->cx, f->cy, space)) {
- uint32_t parent_flags = obs_source_get_output_flags(target);
- bool custom_draw = (parent_flags & OBS_SOURCE_CUSTOM_DRAW) != 0;
- bool async = (parent_flags & OBS_SOURCE_ASYNC) != 0;
- struct vec4 clear_color;
- vec4_zero(&clear_color);
- gs_clear(GS_CLEAR_COLOR, &clear_color, 0.0f, 0);
- gs_ortho(0.0f, (float)f->cx, 0.0f, (float)f->cy, -100.0f, 100.0f);
- if (target == parent && !custom_draw && !async)
- obs_source_default_render(target);
- else
- obs_source_video_render(target);
- gs_texrender_end(frame.render);
- frame.space = space;
- }
- gs_blend_state_pop();
- deque_push_back(&f->frames, &frame, sizeof(frame));
- draw_frame(f);
- f->processed_frame = true;
- UNUSED_PARAMETER(effect);
- }
- static enum gs_color_space gpu_delay_filter_get_color_space(void *data, size_t count,
- const enum gs_color_space *preferred_spaces)
- {
- struct gpu_delay_filter_data *const f = data;
- obs_source_t *target = obs_filter_get_target(f->context);
- obs_source_t *parent = obs_filter_get_parent(f->context);
- if (!f->target_valid || !target || !parent || !f->frames.size) {
- return (count > 0) ? preferred_spaces[0] : GS_CS_SRGB;
- }
- struct frame frame;
- deque_peek_front(&f->frames, &frame, sizeof(frame));
- enum gs_color_space space = frame.space;
- for (size_t i = 0; i < count; ++i) {
- space = preferred_spaces[i];
- if (space == frame.space)
- break;
- }
- return space;
- }
- struct obs_source_info gpu_delay_filter = {
- .id = "gpu_delay",
- .type = OBS_SOURCE_TYPE_FILTER,
- .output_flags = OBS_SOURCE_VIDEO,
- .get_name = gpu_delay_filter_get_name,
- .create = gpu_delay_filter_create,
- .destroy = gpu_delay_filter_destroy,
- .update = gpu_delay_filter_update,
- .get_properties = gpu_delay_filter_properties,
- .video_tick = gpu_delay_filter_tick,
- .video_render = gpu_delay_filter_render,
- .video_get_color_space = gpu_delay_filter_get_color_space,
- };
|