diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 0f5a76de2..fed3e6949 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -384,6 +384,7 @@ set(libobs_libobs_SOURCES obs-view.c obs-scene.c obs-audio.c + obs-video-gpu-encode.c obs-video.c) set(libobs_libobs_HEADERS ${libobs_PLATFORM_HEADERS} diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index e242a5ebd..25401ed3a 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -179,6 +179,12 @@ static inline bool has_scaling(const struct obs_encoder *encoder) video_height != encoder->scaled_height); } +static inline bool gpu_encode_available(const struct obs_encoder *encoder) +{ + return (encoder->info.caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0 && + obs->video.using_nv12_tex; +} + static void add_connection(struct obs_encoder *encoder) { if (encoder->info.type == OBS_ENCODER_AUDIO) { @@ -191,7 +197,12 @@ static void add_connection(struct obs_encoder *encoder) struct video_scale_info info = {0}; get_video_info(encoder, &info); - start_raw_video(encoder->media, &info, receive_video, encoder); + if (gpu_encode_available(encoder)) { + start_gpu_encode(encoder); + } else { + start_raw_video(encoder->media, &info, receive_video, + encoder); + } } set_encoder_active(encoder, true); @@ -199,11 +210,16 @@ static void add_connection(struct obs_encoder *encoder) static void remove_connection(struct obs_encoder *encoder) { - if (encoder->info.type == OBS_ENCODER_AUDIO) + if (encoder->info.type == OBS_ENCODER_AUDIO) { audio_output_disconnect(encoder->media, encoder->mixer_idx, receive_audio, encoder); - else - stop_raw_video(encoder->media, receive_video, encoder); + } else { + if (gpu_encode_available(encoder)) { + stop_gpu_encode(encoder); + } else { + stop_raw_video(encoder->media, receive_video, encoder); + } + } obs_encoder_shutdown(encoder); set_encoder_active(encoder, false); @@ -813,7 +829,7 @@ static inline void send_packet(struct obs_encoder *encoder, cb->new_packet(cb->param, packet); } -static void full_stop(struct obs_encoder *encoder) +void full_stop(struct obs_encoder *encoder) { if (encoder) { pthread_mutex_lock(&encoder->callbacks_mutex); diff --git a/libobs/obs-encoder.h b/libobs/obs-encoder.h index 7a591b4e8..326442398 100644 --- a/libobs/obs-encoder.h +++ b/libobs/obs-encoder.h @@ -30,6 +30,7 @@ extern "C" { #endif #define OBS_ENCODER_CAP_DEPRECATED (1<<0) +#define OBS_ENCODER_CAP_PASS_TEXTURE (1<<1) /** Specifies the encoder type */ enum obs_encoder_type { @@ -251,6 +252,10 @@ struct obs_encoder_info { * @return The properties data */ obs_properties_t *(*get_properties2)(void *data, void *type_data); + + bool (*encode_texture)(void *data, uint32_t handle, int64_t pts, + uint64_t lock_key, uint64_t *next_key, + struct encoder_packet *packet, bool *received_packet); }; EXPORT void obs_register_encoder_s(const struct obs_encoder_info *info, diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index d401a5130..3c48893b8 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -38,6 +38,8 @@ #define NUM_TEXTURES 2 #define MICROSECOND_DEN 1000000 +#define NUM_ENCODE_TEXTURES 3 +#define NUM_ENCODE_TEXTURE_FRAMES_TO_WAIT 1 static inline int64_t packet_dts_usec(struct encoder_packet *packet) { @@ -225,6 +227,16 @@ struct obs_vframe_info { int count; }; +struct obs_tex_frame { + gs_texture_t *tex; + gs_texture_t *tex_uv; + uint32_t handle; + uint64_t timestamp; + uint64_t lock_key; + int count; + bool released; +}; + struct obs_core_video { graphics_t *graphics; gs_stagesurf_t *copy_surfaces[NUM_TEXTURES]; @@ -238,6 +250,7 @@ struct obs_core_video { bool textures_converted[NUM_TEXTURES]; bool using_nv12_tex; struct circlebuf vframe_info_buffer; + struct circlebuf vframe_info_buffer_gpu; gs_effect_t *default_effect; gs_effect_t *default_rect_effect; gs_effect_t *opaque_effect; @@ -251,6 +264,15 @@ struct obs_core_video { gs_stagesurf_t *mapped_surface; int cur_texture; long raw_active; + long gpu_encoder_active; + pthread_mutex_t gpu_encoder_mutex; + struct circlebuf gpu_encoder_queue; + struct circlebuf gpu_encoder_avail_queue; + DARRAY(obs_encoder_t *) gpu_encoders; + os_sem_t *gpu_encode_semaphore; + pthread_t gpu_encode_thread; + bool gpu_encode_thread_initialized; + volatile bool gpu_encode_stop; uint64_t video_time; uint64_t video_avg_frame_time_ns; @@ -1015,6 +1037,9 @@ extern void obs_encoder_add_output(struct obs_encoder *encoder, extern void obs_encoder_remove_output(struct obs_encoder *encoder, struct obs_output *output); +extern bool start_gpu_encode(obs_encoder_t *encoder); +extern void stop_gpu_encode(obs_encoder_t *encoder); + extern void do_encode(struct obs_encoder *encoder, struct encoder_frame *frame); extern void send_off_encoder_packet(obs_encoder_t *encoder, bool success, bool received, struct encoder_packet *pkt); diff --git a/libobs/obs-module.c b/libobs/obs-module.c index f4aaa028b..38276f8d2 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -707,7 +707,11 @@ void obs_register_encoder_s(const struct obs_encoder_info *info, size_t size) CHECK_REQUIRED_VAL_(info, get_name, obs_register_encoder); CHECK_REQUIRED_VAL_(info, create, obs_register_encoder); CHECK_REQUIRED_VAL_(info, destroy, obs_register_encoder); - CHECK_REQUIRED_VAL_(info, encode, obs_register_encoder); + + if ((info->caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0) + CHECK_REQUIRED_VAL_(info, encode_texture, obs_register_encoder); + else + CHECK_REQUIRED_VAL_(info, encode, obs_register_encoder); if (info->type == OBS_ENCODER_AUDIO) CHECK_REQUIRED_VAL_(info, get_frame_size, obs_register_encoder); diff --git a/libobs/obs-video-gpu-encode.c b/libobs/obs-video-gpu-encode.c new file mode 100644 index 000000000..289a07b88 --- /dev/null +++ b/libobs/obs-video-gpu-encode.c @@ -0,0 +1,214 @@ +/****************************************************************************** + Copyright (C) 2018 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-internal.h" + +static void *gpu_encode_thread(void *unused) +{ + struct obs_core_video *video = &obs->video; + uint64_t interval = video_output_get_frame_time(obs->video.video); + DARRAY(obs_encoder_t *) encoders; + int wait_frames = NUM_ENCODE_TEXTURE_FRAMES_TO_WAIT; + + UNUSED_PARAMETER(unused); + da_init(encoders); + + os_set_thread_name("obs gpu encode thread"); + + while (os_sem_wait(video->gpu_encode_semaphore) == 0) { + struct obs_tex_frame tf; + uint64_t timestamp; + uint64_t lock_key; + uint64_t next_key; + int lock_count = 0; + + if (os_atomic_load_bool(&video->gpu_encode_stop)) + break; + + if (wait_frames) { + wait_frames--; + continue; + } + + /* -------------- */ + + pthread_mutex_lock(&video->gpu_encoder_mutex); + + circlebuf_pop_front(&video->gpu_encoder_queue, &tf, sizeof(tf)); + timestamp = tf.timestamp; + lock_key = tf.lock_key; + next_key = tf.lock_key; + + video_output_inc_texture_frames(video->video); + + for (size_t i = 0; i < video->gpu_encoders.num; i++) { + obs_encoder_t *encoder = video->gpu_encoders.array[i]; + da_push_back(encoders, &encoder); + obs_encoder_addref(encoder); + } + + pthread_mutex_unlock(&video->gpu_encoder_mutex); + + /* -------------- */ + + for (size_t i = 0; i < encoders.num; i++) { + struct encoder_packet pkt = {0}; + bool received = false; + bool success; + + obs_encoder_t *encoder = encoders.array[i]; + struct obs_encoder *pair = encoder->paired_encoder; + + pkt.timebase_num = encoder->timebase_num; + pkt.timebase_den = encoder->timebase_den; + pkt.encoder = encoder; + + if (!encoder->first_received && pair) { + if (!pair->first_received || + pair->first_raw_ts > timestamp) { + continue; + } + } + + if (!encoder->start_ts) + encoder->start_ts = timestamp; + + if (++lock_count == encoders.num) + next_key = 0; + else + next_key++; + + success = encoder->info.encode_texture( + encoder->context.data, tf.handle, + encoder->cur_pts, lock_key, &next_key, + &pkt, &received); + send_off_encoder_packet(encoder, success, received, + &pkt); + + lock_key = next_key; + + encoder->cur_pts += encoder->timebase_num; + } + + for (size_t i = 0; i < encoders.num; i++) + obs_encoder_release(encoders.array[i]); + + da_resize(encoders, 0); + + /* -------------- */ + + pthread_mutex_lock(&video->gpu_encoder_mutex); + + tf.lock_key = next_key; + + if (--tf.count) { + tf.timestamp += interval; + circlebuf_push_front(&video->gpu_encoder_queue, + &tf, sizeof(tf)); + + video_output_inc_texture_skipped_frames(video->video); + } else { + circlebuf_push_back( + &video->gpu_encoder_avail_queue, + &tf, sizeof(tf)); + } + + pthread_mutex_unlock(&video->gpu_encoder_mutex); + } + + da_free(encoders); + return NULL; +} + +bool init_gpu_encoding(struct obs_core_video *video) +{ +#ifdef _WIN32 + struct obs_video_info *ovi = &video->ovi; + + video->gpu_encode_stop = false; + + circlebuf_reserve(&video->gpu_encoder_avail_queue, NUM_ENCODE_TEXTURES); + for (size_t i = 0; i < NUM_ENCODE_TEXTURES; i++) { + gs_texture_t *tex; + gs_texture_t *tex_uv; + + gs_texture_create_nv12( + &tex, &tex_uv, + ovi->output_width, ovi->output_height, + GS_RENDER_TARGET | GS_SHARED_KM_TEX); + if (!tex) { + return false; + } + + uint32_t handle = gs_texture_get_shared_handle(tex); + + struct obs_tex_frame frame = { + .tex = tex, + .tex_uv = tex_uv, + .handle = handle + }; + + circlebuf_push_back(&video->gpu_encoder_avail_queue, &frame, + sizeof(frame)); + } + + if (os_sem_init(&video->gpu_encode_semaphore, 0) != 0) + return false; + if (pthread_create(&video->gpu_encode_thread, NULL, + gpu_encode_thread, NULL) != 0) + return false; + + video->gpu_encode_thread_initialized = true; + return true; +#else + UNUSED_PARAMETER(video); + return false; +#endif +} + +void stop_gpu_encoding_thread(struct obs_core_video *video) +{ + if (video->gpu_encode_thread_initialized) { + os_atomic_set_bool(&video->gpu_encode_stop, true); + os_sem_post(video->gpu_encode_semaphore); + pthread_join(video->gpu_encode_thread, NULL); + video->gpu_encode_thread_initialized = false; + } +} + +void free_gpu_encoding(struct obs_core_video *video) +{ + if (video->gpu_encode_semaphore) { + os_sem_destroy(video->gpu_encode_semaphore); + video->gpu_encode_semaphore = NULL; + } + +#define free_circlebuf(x) \ + do { \ + while (x.size) { \ + struct obs_tex_frame frame; \ + circlebuf_pop_front(&x, &frame, sizeof(frame)); \ + gs_texture_destroy(frame.tex); \ + gs_texture_destroy(frame.tex_uv); \ + } \ + circlebuf_free(&x); \ + } while (false) + + free_circlebuf(video->gpu_encoder_queue); + free_circlebuf(video->gpu_encoder_avail_queue); +#undef free_circlebuf +} diff --git a/libobs/obs-video.c b/libobs/obs-video.c index 7bca2e64c..2fb1b44d9 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -390,7 +390,98 @@ end: profile_end(stage_output_texture_name); } -static inline void render_video(struct obs_core_video *video, bool raw_active, +#ifdef _WIN32 +static inline bool queue_frame(struct obs_core_video *video, bool raw_active, + struct obs_vframe_info *vframe_info, int prev_texture) +{ + bool duplicate = !video->gpu_encoder_avail_queue.size || + (video->gpu_encoder_queue.size && vframe_info->count > 1); + + if (duplicate) { + struct obs_tex_frame *tf = circlebuf_data( + &video->gpu_encoder_queue, + video->gpu_encoder_queue.size - sizeof(*tf)); + + /* texture-based encoding is stopping */ + if (!tf) { + return false; + } + + tf->count++; + os_sem_post(video->gpu_encode_semaphore); + goto finish; + } + + struct obs_tex_frame tf; + circlebuf_pop_front(&video->gpu_encoder_avail_queue, &tf, sizeof(tf)); + + if (tf.released) { + gs_texture_acquire_sync(tf.tex, tf.lock_key, GS_WAIT_INFINITE); + tf.released = false; + } + + /* the vframe_info->count > 1 case causing a copy can only happen if by + * some chance the very first frame has to be duplicated for whatever + * reason. otherwise, it goes to the 'duplicate' case above, which + * will ensure better performance. */ + if (raw_active || vframe_info->count > 1) { + gs_copy_texture(tf.tex, video->convert_textures[prev_texture]); + } else { + gs_texture_t *tex = video->convert_textures[prev_texture]; + gs_texture_t *tex_uv = video->convert_uv_textures[prev_texture]; + + video->convert_textures[prev_texture] = tf.tex; + video->convert_uv_textures[prev_texture] = tf.tex_uv; + + tf.tex = tex; + tf.tex_uv = tex_uv; + tf.handle = gs_texture_get_shared_handle(tex); + } + + tf.count = 1; + tf.timestamp = vframe_info->timestamp; + tf.released = true; + gs_texture_release_sync(tf.tex, ++tf.lock_key); + circlebuf_push_back(&video->gpu_encoder_queue, &tf, sizeof(tf)); + + os_sem_post(video->gpu_encode_semaphore); + +finish: + return --vframe_info->count; +} + +extern void full_stop(struct obs_encoder *encoder); + +static inline void encode_gpu(struct obs_core_video *video, bool raw_active, + struct obs_vframe_info *vframe_info, int prev_texture) +{ + while (queue_frame(video, raw_active, vframe_info, prev_texture)); +} + +static const char *output_gpu_encoders_name = "output_gpu_encoders"; +static void output_gpu_encoders(struct obs_core_video *video, bool raw_active, + int prev_texture) +{ + profile_start(output_gpu_encoders_name); + + if (!video->textures_converted[prev_texture]) + goto end; + + struct obs_vframe_info vframe_info; + circlebuf_pop_front(&video->vframe_info_buffer_gpu, &vframe_info, + sizeof(vframe_info)); + + pthread_mutex_lock(&video->gpu_encoder_mutex); + encode_gpu(video, raw_active, &vframe_info, prev_texture); + pthread_mutex_unlock(&video->gpu_encoder_mutex); + +end: + profile_end(output_gpu_encoders_name); +} +#endif + +static inline void render_video(struct obs_core_video *video, + bool raw_active, const bool gpu_active, int cur_texture, int prev_texture) { gs_begin_scene(); @@ -400,9 +491,17 @@ static inline void render_video(struct obs_core_video *video, bool raw_active, render_main_texture(video, cur_texture); - if (raw_active) { + if (raw_active || gpu_active) { render_output_texture(video, cur_texture, prev_texture); +#ifdef _WIN32 + if (gpu_active) { + gs_flush(); + } +#endif + } + + if (raw_active || gpu_active) { if (video->gpu_conversion) { if (video->using_nv12_tex) render_convert_texture_nv12(video, @@ -412,7 +511,14 @@ static inline void render_video(struct obs_core_video *video, bool raw_active, cur_texture, prev_texture); } - stage_output_texture(video, cur_texture, prev_texture); +#ifdef _WIN32 + if (gpu_active) { + gs_flush(); + output_gpu_encoders(video, raw_active, prev_texture); + } +#endif + if (raw_active) + stage_output_texture(video, cur_texture, prev_texture); } gs_set_render_target(NULL, NULL); @@ -609,7 +715,8 @@ static inline void output_video_data(struct obs_core_video *video, } } -static inline void video_sleep(struct obs_core_video *video, bool active, +static inline void video_sleep(struct obs_core_video *video, + bool raw_active, const bool gpu_active, uint64_t *p_time, uint64_t interval_ns) { struct obs_vframe_info vframe_info; @@ -630,9 +737,13 @@ static inline void video_sleep(struct obs_core_video *video, bool active, vframe_info.timestamp = cur_time; vframe_info.count = count; - if (active) + + if (raw_active) circlebuf_push_back(&video->vframe_info_buffer, &vframe_info, sizeof(vframe_info)); + if (gpu_active) + circlebuf_push_back(&video->vframe_info_buffer_gpu, + &vframe_info, sizeof(vframe_info)); } static const char *output_frame_gs_context_name = "gs_context(video->graphics)"; @@ -640,12 +751,13 @@ static const char *output_frame_render_video_name = "render_video"; static const char *output_frame_download_frame_name = "download_frame"; static const char *output_frame_gs_flush_name = "gs_flush"; static const char *output_frame_output_video_data_name = "output_video_data"; -static inline void output_frame(bool raw_active) +static inline void output_frame(bool raw_active, const bool gpu_active) { struct obs_core_video *video = &obs->video; int cur_texture = video->cur_texture; int prev_texture = cur_texture == 0 ? NUM_TEXTURES-1 : cur_texture-1; struct video_data frame; + bool active = raw_active || gpu_active; bool frame_ready; memset(&frame, 0, sizeof(struct video_data)); @@ -654,7 +766,7 @@ static inline void output_frame(bool raw_active) gs_enter_context(video->graphics); profile_start(output_frame_render_video_name); - render_video(video, raw_active, cur_texture, prev_texture); + render_video(video, raw_active, gpu_active, cur_texture, prev_texture); profile_end(output_frame_render_video_name); if (raw_active) { @@ -687,17 +799,31 @@ static inline void output_frame(bool raw_active) #define NBSP "\xC2\xA0" -static void clear_frame_data(void) +static void clear_base_frame_data(void) { struct obs_core_video *video = &obs->video; - memset(video->textures_rendered, 0, sizeof(video->textures_rendered)); - memset(video->textures_output, 0, sizeof(video->textures_output)); memset(video->textures_copied, 0, sizeof(video->textures_copied)); memset(video->textures_converted, 0, sizeof(video->textures_converted)); circlebuf_free(&video->vframe_info_buffer); video->cur_texture = 0; } +static void clear_raw_frame_data(void) +{ + struct obs_core_video *video = &obs->video; + memset(video->textures_copied, 0, sizeof(video->textures_copied)); + memset(video->textures_converted, 0, sizeof(video->textures_converted)); + circlebuf_free(&video->vframe_info_buffer); +} + +#ifdef _WIN32 +static void clear_gpu_frame_data(void) +{ + struct obs_core_video *video = &obs->video; + circlebuf_free(&video->vframe_info_buffer_gpu); +} +#endif + static const char *tick_sources_name = "tick_sources"; static const char *render_displays_name = "render_displays"; static const char *output_frame_name = "output_frame"; @@ -708,7 +834,9 @@ void *obs_graphics_thread(void *param) uint64_t frame_time_total_ns = 0; uint64_t fps_total_ns = 0; uint32_t fps_total_frames = 0; + bool gpu_was_active = false; bool raw_was_active = false; + bool was_active = false; obs->video.video_time = os_gettime_ns(); @@ -725,10 +853,24 @@ void *obs_graphics_thread(void *param) uint64_t frame_start = os_gettime_ns(); uint64_t frame_time_ns; bool raw_active = obs->video.raw_active > 0; +#ifdef _WIN32 + bool gpu_active = obs->video.gpu_encoder_active > 0; +#else + const bool gpu_active = 0; +#endif + bool active = raw_active || gpu_active; + if (!was_active && active) + clear_base_frame_data(); if (!raw_was_active && raw_active) - clear_frame_data(); + clear_raw_frame_data(); +#ifdef _WIN32 + if (!gpu_was_active && gpu_active) + clear_gpu_frame_data(); +#endif raw_was_active = raw_active; + gpu_was_active = gpu_active; + was_active = active; profile_start(video_thread_name); @@ -737,7 +879,7 @@ void *obs_graphics_thread(void *param) profile_end(tick_sources_name); profile_start(output_frame_name); - output_frame(raw_active); + output_frame(raw_active, gpu_active); profile_end(output_frame_name); profile_start(render_displays_name); @@ -750,8 +892,8 @@ void *obs_graphics_thread(void *param) profile_reenable_thread(); - video_sleep(&obs->video, raw_active, &obs->video.video_time, - interval); + video_sleep(&obs->video, raw_active, gpu_active, + &obs->video.video_time, interval); frame_time_total_ns += frame_time_ns; fps_total_ns += (obs->video.video_time - last_time); diff --git a/libobs/obs.c b/libobs/obs.c index 3456fe0b1..a61c4b2de 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -388,6 +388,7 @@ static int obs_init_video(struct obs_video_info *ovi) { struct obs_core_video *video = &obs->video; struct video_output_info vi; + pthread_mutexattr_t attr; int errorcode; make_video_info(&vi, ovi); @@ -421,6 +422,13 @@ static int obs_init_video(struct obs_video_info *ovi) gs_leave_context(); + if (pthread_mutexattr_init(&attr) != 0) + return OBS_VIDEO_FAIL; + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) + return OBS_VIDEO_FAIL; + if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0) + return OBS_VIDEO_FAIL; + errorcode = pthread_create(&video->video_thread, NULL, obs_graphics_thread, obs); if (errorcode != 0) @@ -481,6 +489,7 @@ static void obs_free_video(void) gs_leave_context(); circlebuf_free(&video->vframe_info_buffer); + circlebuf_free(&video->vframe_info_buffer_gpu); memset(&video->textures_rendered, 0, sizeof(video->textures_rendered)); @@ -491,6 +500,11 @@ static void obs_free_video(void) memset(&video->textures_converted, 0, sizeof(video->textures_converted)); + pthread_mutex_destroy(&video->gpu_encoder_mutex); + pthread_mutex_init_value(&video->gpu_encoder_mutex); + da_free(video->gpu_encoders); + + video->gpu_encoder_active = 0; video->cur_texture = 0; } } @@ -792,6 +806,7 @@ static bool obs_init(const char *locale, const char *module_config_path, obs = bzalloc(sizeof(struct obs_core)); pthread_mutex_init_value(&obs->audio.monitoring_mutex); + pthread_mutex_init_value(&obs->video.gpu_encoder_mutex); obs->name_store_owned = !store; obs->name_store = store ? store : profiler_name_store_create(); @@ -2298,13 +2313,69 @@ obs_data_t *obs_get_private_data(void) return private_data; } +extern bool init_gpu_encoding(struct obs_core_video *video); +extern void stop_gpu_encoding_thread(struct obs_core_video *video); +extern void free_gpu_encoding(struct obs_core_video *video); + +bool start_gpu_encode(obs_encoder_t *encoder) +{ + struct obs_core_video *video = &obs->video; + bool success = true; + + obs_enter_graphics(); + pthread_mutex_lock(&video->gpu_encoder_mutex); + + if (!video->gpu_encoders.num) + success = init_gpu_encoding(video); + if (success) + da_push_back(video->gpu_encoders, &encoder); + else + free_gpu_encoding(video); + + pthread_mutex_unlock(&video->gpu_encoder_mutex); + obs_leave_graphics(); + + if (success) { + os_atomic_inc_long(&video->gpu_encoder_active); + video_output_inc_texture_encoders(video->video); + } + + return success; +} + +void stop_gpu_encode(obs_encoder_t *encoder) +{ + struct obs_core_video *video = &obs->video; + bool call_free = false; + + os_atomic_dec_long(&video->gpu_encoder_active); + video_output_dec_texture_encoders(video->video); + + pthread_mutex_lock(&video->gpu_encoder_mutex); + da_erase_item(video->gpu_encoders, &encoder); + if (!video->gpu_encoders.num) + call_free = true; + pthread_mutex_unlock(&video->gpu_encoder_mutex); + + if (call_free) { + stop_gpu_encoding_thread(video); + + obs_enter_graphics(); + pthread_mutex_lock(&video->gpu_encoder_mutex); + free_gpu_encoding(video); + pthread_mutex_unlock(&video->gpu_encoder_mutex); + obs_leave_graphics(); + } +} + bool obs_video_active(void) { struct obs_core_video *video = &obs->video; if (!obs) return false; - return os_atomic_load_long(&video->raw_active) > 0; + return os_atomic_load_long(&video->raw_active) > 0 || + os_atomic_load_long(&video->gpu_encoder_active) > 0; } bool obs_nv12_tex_active(void)