Răsfoiți Sursa

libobs: Add texture-based encoding support

Allows the ability to encode by passing NV12 textures.  This uses a
separate thread for texture-based encoders with a small queue of
textures.  An output texture with a keyed mutex shared texture is locked
between OBS and each encoder.  A new encoder callback and capability
flag is used to encode with textures.
jp9000 6 ani în urmă
părinte
comite
93ba6e7128

+ 1 - 0
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}

+ 21 - 5
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);

+ 5 - 0
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,

+ 25 - 0
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);

+ 5 - 1
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);

+ 214 - 0
libobs/obs-video-gpu-encode.c

@@ -0,0 +1,214 @@
+/******************************************************************************
+    Copyright (C) 2018 by Hugh Bailey <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#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
+}

+ 157 - 15
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);

+ 72 - 1
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)