Pārlūkot izejas kodu

libobs: Add support for multiple video mixes

Split render_texture and derived fields in obs_core_video into new
obs_core_video_mix struct. Add new APIs to add additional obs_view to the render loop, each with a separate render_texture / obs_core_video_mix.
Chip Bradford 3 gadi atpakaļ
vecāks
revīzija
7e39ee291c

+ 1 - 1
libobs/obs-encoder.c

@@ -187,7 +187,7 @@ static inline bool has_scaling(const struct obs_encoder *encoder)
 
 static inline bool gpu_encode_available(const struct obs_encoder *encoder)
 {
-	struct obs_core_video *const video = &obs->video;
+	struct obs_core_video_mix *video = obs->video.main_mix;
 	return (encoder->info.caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0 &&
 	       (video->using_p010_tex || video->using_nv12_tex);
 }

+ 42 - 30
libobs/obs-internal.h

@@ -245,8 +245,9 @@ struct obs_task_info {
 	void *param;
 };
 
-struct obs_core_video {
-	graphics_t *graphics;
+struct obs_core_video_mix {
+	struct obs_view *view;
+
 	gs_stagesurf_t *active_copy_surfaces[NUM_TEXTURES][NUM_CHANNELS];
 	gs_stagesurf_t *copy_surfaces[NUM_TEXTURES][NUM_CHANNELS];
 	gs_texture_t *convert_textures[NUM_CHANNELS];
@@ -264,22 +265,13 @@ struct obs_core_video {
 	bool using_p010_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;
-	gs_effect_t *solid_effect;
-	gs_effect_t *repeat_effect;
-	gs_effect_t *conversion_effect;
-	gs_effect_t *bicubic_effect;
-	gs_effect_t *lanczos_effect;
-	gs_effect_t *area_effect;
-	gs_effect_t *bilinear_lowres_effect;
-	gs_effect_t *premultiplied_alpha_effect;
-	gs_samplerstate_t *point_sampler;
 	gs_stagesurf_t *mapped_surfaces[NUM_CHANNELS];
 	int cur_texture;
 	volatile long raw_active;
 	volatile long gpu_encoder_active;
+	bool gpu_was_active;
+	bool raw_was_active;
+	bool was_active;
 	pthread_mutex_t gpu_encoder_mutex;
 	struct circlebuf gpu_encoder_queue;
 	struct circlebuf gpu_encoder_avail_queue;
@@ -290,28 +282,49 @@ struct obs_core_video {
 	bool gpu_encode_thread_initialized;
 	volatile bool gpu_encode_stop;
 
+	video_t *video;
+
+	bool gpu_conversion;
+	const char *conversion_techs[NUM_CHANNELS];
+	bool conversion_needed;
+	float conversion_width_i;
+	float conversion_height_i;
+
+	float color_matrix[16];
+	enum obs_scale_type scale_type;
+};
+
+extern int obs_init_video_mix(struct obs_video_info *ovi,
+			      struct obs_core_video_mix *video);
+extern void obs_free_video_mix(struct obs_core_video_mix *video);
+
+struct obs_core_video {
+	graphics_t *graphics;
+	gs_effect_t *default_effect;
+	gs_effect_t *default_rect_effect;
+	gs_effect_t *opaque_effect;
+	gs_effect_t *solid_effect;
+	gs_effect_t *repeat_effect;
+	gs_effect_t *conversion_effect;
+	gs_effect_t *bicubic_effect;
+	gs_effect_t *lanczos_effect;
+	gs_effect_t *area_effect;
+	gs_effect_t *bilinear_lowres_effect;
+	gs_effect_t *premultiplied_alpha_effect;
+	gs_samplerstate_t *point_sampler;
+
 	uint64_t video_time;
 	uint64_t video_frame_interval_ns;
+	uint64_t video_half_frame_interval_ns;
 	uint64_t video_avg_frame_time_ns;
 	double video_fps;
-	video_t *video;
 	pthread_t video_thread;
 	uint32_t total_frames;
 	uint32_t lagged_frames;
 	bool thread_initialized;
 
-	bool gpu_conversion;
-	const char *conversion_techs[NUM_CHANNELS];
-	bool conversion_needed;
-	float conversion_width_i;
-	float conversion_height_i;
-
-	uint32_t output_width;
-	uint32_t output_height;
 	uint32_t base_width;
 	uint32_t base_height;
-	float color_matrix[16];
-	enum obs_scale_type scale_type;
 
 	gs_texture_t *transparent_texture;
 
@@ -330,6 +343,10 @@ struct obs_core_video {
 
 	pthread_mutex_t task_mutex;
 	struct circlebuf tasks;
+
+	pthread_mutex_t mixes_mutex;
+	DARRAY(struct obs_core_video_mix) mixes;
+	struct obs_core_video_mix *main_mix;
 };
 
 struct audio_monitor;
@@ -463,11 +480,6 @@ struct obs_graphics_context {
 	uint64_t frame_time_total_ns;
 	uint64_t fps_total_ns;
 	uint32_t fps_total_frames;
-#ifdef _WIN32
-	bool gpu_was_active;
-#endif
-	bool raw_was_active;
-	bool was_active;
 	const char *video_thread_name;
 };
 

+ 1 - 4
libobs/obs-source-deinterlace.c

@@ -148,7 +148,6 @@ static inline uint64_t uint64_diff(uint64_t ts1, uint64_t ts2)
 static inline void deinterlace_get_closest_frames(obs_source_t *s,
 						  uint64_t sys_time)
 {
-	const struct video_output_info *info;
 	uint64_t half_interval;
 
 	if (s->async_unbuffered && s->deinterlace_offset) {
@@ -169,9 +168,7 @@ static inline void deinterlace_get_closest_frames(obs_source_t *s,
 	if (!s->async_frames.num)
 		return;
 
-	info = video_output_get_info(obs->video.video);
-	half_interval = (uint64_t)info->fps_den * 500000000ULL /
-			(uint64_t)info->fps_num;
+	half_interval = obs->video.video_half_frame_interval_ns;
 
 	if (first_frame(s) || ready_deinterlace_frames(s, sys_time)) {
 		uint64_t offset;

+ 12 - 14
libobs/obs-video-gpu-encode.c

@@ -17,14 +17,12 @@
 
 #include "obs-internal.h"
 
-static void *gpu_encode_thread(void *unused)
+static void *gpu_encode_thread(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
-	uint64_t interval = video_output_get_frame_time(obs->video.video);
+	uint64_t interval = video_output_get_frame_time(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");
@@ -149,10 +147,10 @@ static void *gpu_encode_thread(void *unused)
 	return NULL;
 }
 
-bool init_gpu_encoding(struct obs_core_video *video)
+bool init_gpu_encoding(struct obs_core_video_mix *video)
 {
 #ifdef _WIN32
-	struct obs_video_info *ovi = &video->ovi;
+	const struct video_output_info *info = video_output_get_info(video->video);
 
 	video->gpu_encode_stop = false;
 
@@ -161,14 +159,14 @@ bool init_gpu_encoding(struct obs_core_video *video)
 		gs_texture_t *tex;
 		gs_texture_t *tex_uv;
 
-		if (ovi->output_format == VIDEO_FORMAT_P010) {
-			gs_texture_create_p010(&tex, &tex_uv, ovi->output_width,
-					       ovi->output_height,
+		if (info->format == VIDEO_FORMAT_P010) {
+			gs_texture_create_p010(&tex, &tex_uv, info->width,
+					       info->height,
 					       GS_RENDER_TARGET |
 						       GS_SHARED_KM_TEX);
 		} else {
-			gs_texture_create_nv12(&tex, &tex_uv, ovi->output_width,
-					       ovi->output_height,
+			gs_texture_create_nv12(&tex, &tex_uv, info->width,
+					       info->height,
 					       GS_RENDER_TARGET |
 						       GS_SHARED_KM_TEX);
 		}
@@ -191,7 +189,7 @@ bool init_gpu_encoding(struct obs_core_video *video)
 	    0)
 		return false;
 	if (pthread_create(&video->gpu_encode_thread, NULL, gpu_encode_thread,
-			   NULL) != 0)
+			   video) != 0)
 		return false;
 
 	os_event_signal(video->gpu_encode_inactive);
@@ -204,7 +202,7 @@ bool init_gpu_encoding(struct obs_core_video *video)
 #endif
 }
 
-void stop_gpu_encoding_thread(struct obs_core_video *video)
+void stop_gpu_encoding_thread(struct obs_core_video_mix *video)
 {
 	if (video->gpu_encode_thread_initialized) {
 		os_atomic_set_bool(&video->gpu_encode_stop, true);
@@ -214,7 +212,7 @@ void stop_gpu_encoding_thread(struct obs_core_video *video)
 	}
 }
 
-void free_gpu_encoding(struct obs_core_video *video)
+void free_gpu_encoding(struct obs_core_video_mix *video)
 {
 	if (video->gpu_encode_semaphore) {
 		os_sem_destroy(video->gpu_encode_semaphore);

+ 121 - 66
libobs/obs-video.c

@@ -38,7 +38,7 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time)
 
 	if (!last_time)
 		last_time = cur_time -
-			    video_output_get_frame_time(obs->video.video);
+			    obs->video.video_frame_interval_ns;
 
 	delta_time = cur_time - last_time;
 	seconds = (float)((double)delta_time / 1000000000.0);
@@ -113,7 +113,7 @@ static inline void set_render_size(uint32_t width, uint32_t height)
 	gs_set_viewport(0, 0, width, height);
 }
 
-static inline void unmap_last_surface(struct obs_core_video *video)
+static inline void unmap_last_surface(struct obs_core_video_mix *video)
 {
 	for (int c = 0; c < NUM_CHANNELS; ++c) {
 		if (video->mapped_surfaces[c]) {
@@ -124,8 +124,11 @@ static inline void unmap_last_surface(struct obs_core_video *video)
 }
 
 static const char *render_main_texture_name = "render_main_texture";
-static inline void render_main_texture(struct obs_core_video *video)
+static inline void render_main_texture(struct obs_core_video_mix *video)
 {
+	uint32_t base_width = obs->video.base_width;
+	uint32_t base_height = obs->video.base_height;
+
 	profile_start(render_main_texture_name);
 	GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_MAIN_TEXTURE,
 			      render_main_texture_name);
@@ -137,7 +140,7 @@ static inline void render_main_texture(struct obs_core_video *video)
 					      video->render_space);
 	gs_clear(GS_CLEAR_COLOR, &clear_color, 1.0f, 0);
 
-	set_render_size(video->base_width, video->base_height);
+	set_render_size(base_width, base_height);
 
 	pthread_mutex_lock(&obs->data.draw_callbacks_mutex);
 
@@ -145,13 +148,13 @@ static inline void render_main_texture(struct obs_core_video *video)
 		struct draw_callback *callback;
 		callback = obs->data.draw_callbacks.array + (i - 1);
 
-		callback->draw(callback->param, video->base_width,
-			       video->base_height);
+		callback->draw(callback->param, base_width,
+			       base_height);
 	}
 
 	pthread_mutex_unlock(&obs->data.draw_callbacks_mutex);
 
-	obs_view_render(&obs->data.main_view);
+	obs_view_render(video->view);
 
 	video->texture_rendered = true;
 
@@ -160,17 +163,20 @@ static inline void render_main_texture(struct obs_core_video *video)
 }
 
 static inline gs_effect_t *
-get_scale_effect_internal(struct obs_core_video *video)
+get_scale_effect_internal(struct obs_core_video_mix *mix)
 {
+	struct obs_core_video *video = &obs->video;
+	const struct video_output_info *info = video_output_get_info(mix->video);
+
 	/* if the dimension is under half the size of the original image,
 	 * bicubic/lanczos can't sample enough pixels to create an accurate
 	 * image, so use the bilinear low resolution effect instead */
-	if (video->output_width < (video->base_width / 2) &&
-	    video->output_height < (video->base_height / 2)) {
+	if (info->width < (video->base_width / 2) &&
+	    info->height < (video->base_height / 2)) {
 		return video->bilinear_lowres_effect;
 	}
 
-	switch (video->scale_type) {
+	switch (mix->scale_type) {
 	case OBS_SCALE_BILINEAR:
 		return video->default_effect;
 	case OBS_SCALE_LANCZOS:
@@ -193,15 +199,17 @@ static inline bool resolution_close(struct obs_core_video *video,
 	return labs(width_cmp) <= 16 && labs(height_cmp) <= 16;
 }
 
-static inline gs_effect_t *get_scale_effect(struct obs_core_video *video,
+static inline gs_effect_t *get_scale_effect(struct obs_core_video_mix *mix,
 					    uint32_t width, uint32_t height)
 {
+	struct obs_core_video *video = &obs->video;
+
 	if (resolution_close(video, width, height)) {
 		return video->default_effect;
 	} else {
 		/* if the scale method couldn't be loaded, use either bicubic
 		 * or bilinear by default */
-		gs_effect_t *effect = get_scale_effect_internal(video);
+		gs_effect_t *effect = get_scale_effect_internal(mix);
 		if (!effect)
 			effect = !!video->bicubic_effect
 					 ? video->bicubic_effect
@@ -211,17 +219,18 @@ static inline gs_effect_t *get_scale_effect(struct obs_core_video *video,
 }
 
 static const char *render_output_texture_name = "render_output_texture";
-static inline gs_texture_t *render_output_texture(struct obs_core_video *video)
+static inline gs_texture_t *render_output_texture(struct obs_core_video_mix *mix)
 {
-	gs_texture_t *texture = video->render_texture;
-	gs_texture_t *target = video->output_texture;
+	struct obs_core_video *video = &obs->video;
+	gs_texture_t *texture = mix->render_texture;
+	gs_texture_t *target = mix->output_texture;
 	uint32_t width = gs_texture_get_width(target);
 	uint32_t height = gs_texture_get_height(target);
 
-	gs_effect_t *effect = get_scale_effect(video, width, height);
+	gs_effect_t *effect = get_scale_effect(mix, width, height);
 	gs_technique_t *tech;
 
-	if (video->ovi.output_format == VIDEO_FORMAT_RGBA) {
+	if (video_output_get_format(mix->video) == VIDEO_FORMAT_RGBA) {
 		tech = gs_effect_get_technique(effect, "DrawAlphaDivide");
 	} else {
 		if ((effect == video->default_effect) &&
@@ -298,13 +307,13 @@ static void render_convert_plane(gs_effect_t *effect, gs_texture_t *target,
 }
 
 static const char *render_convert_texture_name = "render_convert_texture";
-static void render_convert_texture(struct obs_core_video *video,
+static void render_convert_texture(struct obs_core_video_mix *video,
 				   gs_texture_t *const *const convert_textures,
 				   gs_texture_t *texture)
 {
 	profile_start(render_convert_texture_name);
 
-	gs_effect_t *effect = video->conversion_effect;
+	gs_effect_t *effect = obs->video.conversion_effect;
 	gs_eparam_t *color_vec0 =
 		gs_effect_get_param_by_name(effect, "color_vec0");
 	gs_eparam_t *color_vec1 =
@@ -330,7 +339,7 @@ static void render_convert_texture(struct obs_core_video *video,
 
 	if (convert_textures[0]) {
 		const float hdr_nominal_peak_level =
-			video->hdr_nominal_peak_level;
+			obs->video.hdr_nominal_peak_level;
 		const float multiplier =
 			obs_get_video_sdr_white_level() / 10000.f;
 		gs_effect_set_texture(image, texture);
@@ -381,7 +390,7 @@ static void render_convert_texture(struct obs_core_video *video,
 
 static const char *stage_output_texture_name = "stage_output_texture";
 static inline void
-stage_output_texture(struct obs_core_video *video, int cur_texture,
+stage_output_texture(struct obs_core_video_mix *video, int cur_texture,
 		     gs_texture_t *const *const convert_textures,
 		     gs_stagesurf_t *const *const copy_surfaces,
 		     size_t channel_count)
@@ -418,7 +427,7 @@ stage_output_texture(struct obs_core_video *video, int cur_texture,
 }
 
 #ifdef _WIN32
-static inline bool queue_frame(struct obs_core_video *video, bool raw_active,
+static inline bool queue_frame(struct obs_core_video_mix *video, bool raw_active,
 			       struct obs_vframe_info *vframe_info)
 {
 	bool duplicate =
@@ -480,7 +489,7 @@ finish:
 
 extern void full_stop(struct obs_encoder *encoder);
 
-static inline void encode_gpu(struct obs_core_video *video, bool raw_active,
+static inline void encode_gpu(struct obs_core_video_mix *video, bool raw_active,
 			      struct obs_vframe_info *vframe_info)
 {
 	while (queue_frame(video, raw_active, vframe_info))
@@ -488,7 +497,7 @@ static inline void encode_gpu(struct obs_core_video *video, bool raw_active,
 }
 
 static const char *output_gpu_encoders_name = "output_gpu_encoders";
-static void output_gpu_encoders(struct obs_core_video *video, bool raw_active)
+static void output_gpu_encoders(struct obs_core_video_mix *video, bool raw_active)
 {
 	profile_start(output_gpu_encoders_name);
 
@@ -510,7 +519,7 @@ end:
 }
 #endif
 
-static inline void render_video(struct obs_core_video *video, bool raw_active,
+static inline void render_video(struct obs_core_video_mix *video, bool raw_active,
 				const bool gpu_active, int cur_texture)
 {
 	gs_begin_scene();
@@ -559,7 +568,7 @@ static inline void render_video(struct obs_core_video *video, bool raw_active,
 	gs_end_scene();
 }
 
-static inline bool download_frame(struct obs_core_video *video,
+static inline bool download_frame(struct obs_core_video_mix *video,
 				  int prev_texture, struct video_data *frame)
 {
 	if (!video->textures_copied[prev_texture])
@@ -763,7 +772,7 @@ static inline void copy_rgbx_frame(struct video_frame *output,
 	}
 }
 
-static inline void output_video_data(struct obs_core_video *video,
+static inline void output_video_data(struct obs_core_video_mix *video,
 				     struct video_data *input_frame, int count)
 {
 	const struct video_output_info *info;
@@ -786,8 +795,8 @@ static inline void output_video_data(struct obs_core_video *video,
 	}
 }
 
-static inline void video_sleep(struct obs_core_video *video, bool raw_active,
-			       const bool gpu_active, uint64_t *p_time,
+static inline void video_sleep(struct obs_core_video *video,
+			       uint64_t *p_time,
 			       uint64_t interval_ns)
 {
 	struct obs_vframe_info vframe_info;
@@ -815,12 +824,20 @@ static inline void video_sleep(struct obs_core_video *video, bool raw_active,
 	vframe_info.timestamp = cur_time;
 	vframe_info.count = count;
 
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+		struct obs_core_video_mix *video = obs->video.mixes.array + i;
+		bool raw_active = video->raw_was_active;
+		bool gpu_active = video->gpu_was_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));
+	}
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
 }
 
 static const char *output_frame_gs_context_name = "gs_context(video->graphics)";
@@ -828,9 +845,11 @@ 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, const bool gpu_active)
+static inline void output_frame(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
+	const bool raw_active = video->raw_was_active;
+	const bool gpu_active = video->gpu_was_active;
+
 	int cur_texture = video->cur_texture;
 	int prev_texture = cur_texture == 0 ? NUM_TEXTURES - 1
 					    : cur_texture - 1;
@@ -840,7 +859,7 @@ static inline void output_frame(bool raw_active, const bool gpu_active)
 	memset(&frame, 0, sizeof(struct video_data));
 
 	profile_start(output_frame_gs_context_name);
-	gs_enter_context(video->graphics);
+	gs_enter_context(obs->video.graphics);
 
 	profile_start(output_frame_render_video_name);
 	GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_RENDER_VIDEO,
@@ -877,28 +896,42 @@ static inline void output_frame(bool raw_active, const bool gpu_active)
 		video->cur_texture = 0;
 }
 
+static inline void output_frames(void)
+{
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+		struct obs_core_video_mix *mix = obs->video.mixes.array + i;
+		if (mix->view) {
+			output_frame(mix);
+		} else {
+			obs_free_video_mix(mix);
+			da_erase(obs->video.mixes, i);
+			i--;
+			num--;
+		}
+	}
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+}
+
 #define NBSP "\xC2\xA0"
 
-static void clear_base_frame_data(void)
+static void clear_base_frame_data(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
 	video->texture_rendered = false;
 	video->texture_converted = false;
 	circlebuf_free(&video->vframe_info_buffer);
 	video->cur_texture = 0;
 }
 
-static void clear_raw_frame_data(void)
+static void clear_raw_frame_data(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
 	memset(video->textures_copied, 0, sizeof(video->textures_copied));
 	circlebuf_free(&video->vframe_info_buffer);
 }
 
 #ifdef _WIN32
-static void clear_gpu_frame_data(void)
+static void clear_gpu_frame_data(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
 	circlebuf_free(&video->vframe_info_buffer_gpu);
 }
 #endif
@@ -1006,35 +1039,63 @@ static void uninit_winrt_state(struct winrt_state *winrt)
 static const char *tick_sources_name = "tick_sources";
 static const char *render_displays_name = "render_displays";
 static const char *output_frame_name = "output_frame";
-bool obs_graphics_thread_loop(struct obs_graphics_context *context)
+static inline void update_active_state(struct obs_core_video_mix *video)
 {
-	/* defer loop break to clean up sources */
-	const bool stop_requested = video_output_stopped(obs->video.video);
+	const bool raw_was_active = video->raw_was_active;
+	const bool gpu_was_active = video->gpu_was_active;
+	const bool was_active = video->was_active;
 
-	uint64_t frame_start = os_gettime_ns();
-	uint64_t frame_time_ns;
-	bool raw_active = os_atomic_load_long(&obs->video.raw_active) > 0;
+	bool raw_active = os_atomic_load_long(&video->raw_active) > 0;
 #ifdef _WIN32
 	const bool gpu_active =
-		os_atomic_load_long(&obs->video.gpu_encoder_active) > 0;
+		os_atomic_load_long(&video->gpu_encoder_active) > 0;
 	const bool active = raw_active || gpu_active;
 #else
 	const bool gpu_active = 0;
 	const bool active = raw_active;
 #endif
 
-	if (!context->was_active && active)
-		clear_base_frame_data();
-	if (!context->raw_was_active && raw_active)
-		clear_raw_frame_data();
+	if (!was_active && active)
+		clear_base_frame_data(video);
+	if (!raw_was_active && raw_active)
+		clear_raw_frame_data(video);
 #ifdef _WIN32
-	if (!context->gpu_was_active && gpu_active)
-		clear_gpu_frame_data();
+	if (!gpu_was_active && gpu_active)
+		clear_gpu_frame_data(video);
 
-	context->gpu_was_active = gpu_active;
+	video->gpu_was_active = gpu_active;
 #endif
-	context->raw_was_active = raw_active;
-	context->was_active = active;
+	video->raw_was_active = raw_active;
+	video->was_active = active;
+}
+
+static inline void update_active_states(void)
+{
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	for (size_t i = 0, num = obs->video.mixes.num; i < num; i++)
+		update_active_state(obs->video.mixes.array + i);
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+}
+
+static inline bool stop_requested(void)
+{
+	bool success = true;
+
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	for (size_t i = 0, num = obs->video.mixes.num; i < num; i++)
+		if (!video_output_stopped(obs->video.mixes.array[i].video))
+			success = false;
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+	return success;
+}
+
+bool obs_graphics_thread_loop(struct obs_graphics_context *context)
+{
+	uint64_t frame_start = os_gettime_ns();
+	uint64_t frame_time_ns;
+
+	update_active_states();
 
 	profile_start(context->video_thread_name);
 
@@ -1056,7 +1117,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context)
 #endif
 
 	profile_start(output_frame_name);
-	output_frame(raw_active, gpu_active);
+	output_frames();
 	profile_end(output_frame_name);
 
 	profile_start(render_displays_name);
@@ -1071,7 +1132,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context)
 
 	profile_reenable_thread();
 
-	video_sleep(&obs->video, raw_active, gpu_active, &obs->video.video_time,
+	video_sleep(&obs->video, &obs->video.video_time,
 		    context->interval);
 
 	context->frame_time_total_ns += frame_time_ns;
@@ -1091,7 +1152,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context)
 		context->fps_total_frames = 0;
 	}
 
-	return !stop_requested;
+	return !stop_requested();
 }
 
 void *obs_graphics_thread(void *param)
@@ -1103,10 +1164,9 @@ void *obs_graphics_thread(void *param)
 
 	is_graphics_thread = true;
 
-	const uint64_t interval = video_output_get_frame_time(obs->video.video);
+	const uint64_t interval = obs->video.video_frame_interval_ns;
 
 	obs->video.video_time = os_gettime_ns();
-	obs->video.video_frame_interval_ns = interval;
 
 	os_set_thread_name("libobs: graphics thread");
 
@@ -1118,16 +1178,11 @@ void *obs_graphics_thread(void *param)
 	srand((unsigned int)time(NULL));
 
 	struct obs_graphics_context context;
-	context.interval = video_output_get_frame_time(obs->video.video);
+	context.interval = interval;
 	context.frame_time_total_ns = 0;
 	context.fps_total_ns = 0;
 	context.fps_total_frames = 0;
 	context.last_time = 0;
-#ifdef _WIN32
-	context.gpu_was_active = false;
-#endif
-	context.raw_was_active = false;
-	context.was_active = false;
 	context.video_thread_name = video_thread_name;
 
 #ifdef __APPLE__

+ 47 - 0
libobs/obs-view.c

@@ -139,3 +139,50 @@ void obs_view_render(obs_view_t *view)
 
 	pthread_mutex_unlock(&view->channels_mutex);
 }
+
+static inline size_t find_mix_for_view(obs_view_t *view)
+{
+	for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+		if (obs->video.mixes.array[i].view == view)
+			return i;
+	}
+
+	return DARRAY_INVALID;
+}
+
+static inline void set_main_mix()
+{
+	size_t idx = find_mix_for_view(&obs->data.main_view);
+
+	struct obs_core_video_mix *mix = NULL;
+	if (idx != DARRAY_INVALID)
+		mix = obs->video.mixes.array + idx;
+	obs->video.main_mix = mix;
+}
+
+video_t *obs_view_add(obs_view_t *view)
+{
+	struct obs_core_video_mix mix = {0};
+	mix.view = view;
+	if (obs_init_video_mix(&obs->video.ovi, &mix) != 0) {
+		obs_free_video_mix(&mix);
+		return NULL;
+	}
+
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	da_push_back(obs->video.mixes, &mix);
+	set_main_mix();
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+	return mix.video;
+}
+
+void obs_view_remove(obs_view_t *view)
+{
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	size_t idx = find_mix_for_view(view);
+	if (idx != DARRAY_INVALID)
+		obs->video.mixes.array[idx].view = NULL;
+	set_main_mix();
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+}

+ 195 - 132
libobs/obs.c

@@ -44,9 +44,9 @@ static inline void make_video_info(struct video_output_info *vi,
 	vi->cache_size = 6;
 }
 
-static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
+static inline void calc_gpu_conversion_sizes(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
+	const struct video_output_info *info = video_output_get_info(video->video);
 
 	video->conversion_needed = false;
 	video->conversion_techs[0] = NULL;
@@ -55,19 +55,19 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
 	video->conversion_width_i = 0.f;
 	video->conversion_height_i = 0.f;
 
-	switch ((uint32_t)ovi->output_format) {
+	switch ((uint32_t)info->format) {
 	case VIDEO_FORMAT_I420:
 		video->conversion_needed = true;
 		video->conversion_techs[0] = "Planar_Y";
 		video->conversion_techs[1] = "Planar_U_Left";
 		video->conversion_techs[2] = "Planar_V_Left";
-		video->conversion_width_i = 1.f / (float)ovi->output_width;
+		video->conversion_width_i = 1.f / (float)info->width;
 		break;
 	case VIDEO_FORMAT_NV12:
 		video->conversion_needed = true;
 		video->conversion_techs[0] = "NV12_Y";
 		video->conversion_techs[1] = "NV12_UV";
-		video->conversion_width_i = 1.f / (float)ovi->output_width;
+		video->conversion_width_i = 1.f / (float)info->width;
 		break;
 	case VIDEO_FORMAT_I444:
 		video->conversion_needed = true;
@@ -77,13 +77,13 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
 		break;
 	case VIDEO_FORMAT_I010:
 		video->conversion_needed = true;
-		video->conversion_width_i = 1.f / (float)ovi->output_width;
-		video->conversion_height_i = 1.f / (float)ovi->output_height;
-		if (ovi->colorspace == VIDEO_CS_2100_PQ) {
+		video->conversion_width_i = 1.f / (float)info->width;
+		video->conversion_height_i = 1.f / (float)info->height;
+		if (info->colorspace == VIDEO_CS_2100_PQ) {
 			video->conversion_techs[0] = "I010_PQ_Y";
 			video->conversion_techs[1] = "I010_PQ_U";
 			video->conversion_techs[2] = "I010_PQ_V";
-		} else if (ovi->colorspace == VIDEO_CS_2100_HLG) {
+		} else if (info->colorspace == VIDEO_CS_2100_HLG) {
 			video->conversion_techs[0] = "I010_HLG_Y";
 			video->conversion_techs[1] = "I010_HLG_U";
 			video->conversion_techs[2] = "I010_HLG_V";
@@ -95,12 +95,12 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
 		break;
 	case VIDEO_FORMAT_P010:
 		video->conversion_needed = true;
-		video->conversion_width_i = 1.f / (float)ovi->output_width;
-		video->conversion_height_i = 1.f / (float)ovi->output_height;
-		if (ovi->colorspace == VIDEO_CS_2100_PQ) {
+		video->conversion_width_i = 1.f / (float)info->width;
+		video->conversion_height_i = 1.f / (float)info->height;
+		if (info->colorspace == VIDEO_CS_2100_PQ) {
 			video->conversion_techs[0] = "P010_PQ_Y";
 			video->conversion_techs[1] = "P010_PQ_UV";
-		} else if (ovi->colorspace == VIDEO_CS_2100_HLG) {
+		} else if (info->colorspace == VIDEO_CS_2100_HLG) {
 			video->conversion_techs[0] = "P010_HLG_Y";
 			video->conversion_techs[1] = "P010_HLG_UV";
 		} else {
@@ -110,22 +110,23 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
 	}
 }
 
-static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
+static bool obs_init_gpu_conversion(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
+	const struct video_output_info *info =
+		video_output_get_info(video->video);
 
-	calc_gpu_conversion_sizes(ovi);
+	calc_gpu_conversion_sizes(video);
 
-	video->using_nv12_tex = ovi->output_format == VIDEO_FORMAT_NV12
+	video->using_nv12_tex = info->format == VIDEO_FORMAT_NV12
 					? gs_nv12_available()
 					: false;
-	video->using_p010_tex = ovi->output_format == VIDEO_FORMAT_P010
+	video->using_p010_tex = info->format == VIDEO_FORMAT_P010
 					? gs_p010_available()
 					: false;
 
 	if (!video->conversion_needed) {
 		blog(LOG_INFO, "GPU conversion not available for format: %u",
-		     (unsigned int)ovi->output_format);
+		     (unsigned int)info->format);
 		video->gpu_conversion = false;
 		video->using_nv12_tex = false;
 		video->using_p010_tex = false;
@@ -154,7 +155,7 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
 		if (!gs_texture_create_nv12(
 			    &video->convert_textures_encode[0],
 			    &video->convert_textures_encode[1],
-			    ovi->output_width, ovi->output_height,
+			    info->width, info->height,
 			    GS_RENDER_TARGET | GS_SHARED_KM_TEX)) {
 			return false;
 		}
@@ -162,7 +163,7 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
 		if (!gs_texture_create_p010(
 			    &video->convert_textures_encode[0],
 			    &video->convert_textures_encode[1],
-			    ovi->output_width, ovi->output_height,
+			    info->width, info->height,
 			    GS_RENDER_TARGET | GS_SHARED_KM_TEX)) {
 			return false;
 		}
@@ -171,18 +172,16 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
 
 	bool success = true;
 
-	const struct video_output_info *info =
-		video_output_get_info(video->video);
 	switch (info->format) {
 	case VIDEO_FORMAT_I420:
 		video->convert_textures[0] =
-			gs_texture_create(ovi->output_width, ovi->output_height,
+			gs_texture_create(info->width, info->height,
 					  GS_R8, 1, NULL, GS_RENDER_TARGET);
 		video->convert_textures[1] = gs_texture_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R8, 1,
+			info->width / 2, info->height / 2, GS_R8, 1,
 			NULL, GS_RENDER_TARGET);
 		video->convert_textures[2] = gs_texture_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R8, 1,
+			info->width / 2, info->height / 2, GS_R8, 1,
 			NULL, GS_RENDER_TARGET);
 		if (!video->convert_textures[0] ||
 		    !video->convert_textures[1] || !video->convert_textures[2])
@@ -190,23 +189,23 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
 		break;
 	case VIDEO_FORMAT_NV12:
 		video->convert_textures[0] =
-			gs_texture_create(ovi->output_width, ovi->output_height,
+			gs_texture_create(info->width, info->height,
 					  GS_R8, 1, NULL, GS_RENDER_TARGET);
 		video->convert_textures[1] = gs_texture_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R8G8,
+			info->width / 2, info->height / 2, GS_R8G8,
 			1, NULL, GS_RENDER_TARGET);
 		if (!video->convert_textures[0] || !video->convert_textures[1])
 			success = false;
 		break;
 	case VIDEO_FORMAT_I444:
 		video->convert_textures[0] =
-			gs_texture_create(ovi->output_width, ovi->output_height,
+			gs_texture_create(info->width, info->height,
 					  GS_R8, 1, NULL, GS_RENDER_TARGET);
 		video->convert_textures[1] =
-			gs_texture_create(ovi->output_width, ovi->output_height,
+			gs_texture_create(info->width, info->height,
 					  GS_R8, 1, NULL, GS_RENDER_TARGET);
 		video->convert_textures[2] =
-			gs_texture_create(ovi->output_width, ovi->output_height,
+			gs_texture_create(info->width, info->height,
 					  GS_R8, 1, NULL, GS_RENDER_TARGET);
 		if (!video->convert_textures[0] ||
 		    !video->convert_textures[1] || !video->convert_textures[2])
@@ -214,13 +213,13 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
 		break;
 	case VIDEO_FORMAT_I010:
 		video->convert_textures[0] =
-			gs_texture_create(ovi->output_width, ovi->output_height,
+			gs_texture_create(info->width, info->height,
 					  GS_R16, 1, NULL, GS_RENDER_TARGET);
 		video->convert_textures[1] = gs_texture_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R16,
+			info->width / 2, info->height / 2, GS_R16,
 			1, NULL, GS_RENDER_TARGET);
 		video->convert_textures[2] = gs_texture_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R16,
+			info->width / 2, info->height / 2, GS_R16,
 			1, NULL, GS_RENDER_TARGET);
 		if (!video->convert_textures[0] ||
 		    !video->convert_textures[1] || !video->convert_textures[2])
@@ -228,10 +227,10 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
 		break;
 	case VIDEO_FORMAT_P010:
 		video->convert_textures[0] =
-			gs_texture_create(ovi->output_width, ovi->output_height,
+			gs_texture_create(info->width, info->height,
 					  GS_R16, 1, NULL, GS_RENDER_TARGET);
 		video->convert_textures[1] = gs_texture_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_RG16,
+			info->width / 2, info->height / 2, GS_RG16,
 			1, NULL, GS_RENDER_TARGET);
 		if (!video->convert_textures[0] || !video->convert_textures[1])
 			success = false;
@@ -257,72 +256,70 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
 	return success;
 }
 
-static bool obs_init_gpu_copy_surfaces(struct obs_video_info *ovi, size_t i)
+static bool obs_init_gpu_copy_surfaces(struct obs_core_video_mix *video, size_t i)
 {
-	struct obs_core_video *video = &obs->video;
-
 	const struct video_output_info *info =
 		video_output_get_info(video->video);
 	switch (info->format) {
 	case VIDEO_FORMAT_I420:
 		video->copy_surfaces[i][0] = gs_stagesurface_create(
-			ovi->output_width, ovi->output_height, GS_R8);
+			info->width, info->height, GS_R8);
 		if (!video->copy_surfaces[i][0])
 			return false;
 		video->copy_surfaces[i][1] = gs_stagesurface_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R8);
+			info->width / 2, info->height / 2, GS_R8);
 		if (!video->copy_surfaces[i][1])
 			return false;
 		video->copy_surfaces[i][2] = gs_stagesurface_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R8);
+			info->width / 2, info->height / 2, GS_R8);
 		if (!video->copy_surfaces[i][2])
 			return false;
 		break;
 	case VIDEO_FORMAT_NV12:
 		video->copy_surfaces[i][0] = gs_stagesurface_create(
-			ovi->output_width, ovi->output_height, GS_R8);
+			info->width, info->height, GS_R8);
 		if (!video->copy_surfaces[i][0])
 			return false;
 		video->copy_surfaces[i][1] = gs_stagesurface_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R8G8);
+			info->width / 2, info->height / 2, GS_R8G8);
 		if (!video->copy_surfaces[i][1])
 			return false;
 		break;
 	case VIDEO_FORMAT_I444:
 		video->copy_surfaces[i][0] = gs_stagesurface_create(
-			ovi->output_width, ovi->output_height, GS_R8);
+			info->width, info->height, GS_R8);
 		if (!video->copy_surfaces[i][0])
 			return false;
 		video->copy_surfaces[i][1] = gs_stagesurface_create(
-			ovi->output_width, ovi->output_height, GS_R8);
+			info->width, info->height, GS_R8);
 		if (!video->copy_surfaces[i][1])
 			return false;
 		video->copy_surfaces[i][2] = gs_stagesurface_create(
-			ovi->output_width, ovi->output_height, GS_R8);
+			info->width, info->height, GS_R8);
 		if (!video->copy_surfaces[i][2])
 			return false;
 		break;
 	case VIDEO_FORMAT_I010:
 		video->copy_surfaces[i][0] = gs_stagesurface_create(
-			ovi->output_width, ovi->output_height, GS_R16);
+			info->width, info->height, GS_R16);
 		if (!video->copy_surfaces[i][0])
 			return false;
 		video->copy_surfaces[i][1] = gs_stagesurface_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R16);
+			info->width / 2, info->height / 2, GS_R16);
 		if (!video->copy_surfaces[i][1])
 			return false;
 		video->copy_surfaces[i][2] = gs_stagesurface_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_R16);
+			info->width / 2, info->height / 2, GS_R16);
 		if (!video->copy_surfaces[i][2])
 			return false;
 		break;
 	case VIDEO_FORMAT_P010:
 		video->copy_surfaces[i][0] = gs_stagesurface_create(
-			ovi->output_width, ovi->output_height, GS_R16);
+			info->width, info->height, GS_R16);
 		if (!video->copy_surfaces[i][0])
 			return false;
 		video->copy_surfaces[i][1] = gs_stagesurface_create(
-			ovi->output_width / 2, ovi->output_height / 2, GS_RG16);
+			info->width / 2, info->height / 2, GS_RG16);
 		if (!video->copy_surfaces[i][1])
 			return false;
 		break;
@@ -333,9 +330,9 @@ static bool obs_init_gpu_copy_surfaces(struct obs_video_info *ovi, size_t i)
 	return true;
 }
 
-static bool obs_init_textures(struct obs_video_info *ovi)
+static bool obs_init_textures(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
+	const struct video_output_info *info = video_output_get_info(video->video);
 
 	bool success = true;
 
@@ -343,16 +340,16 @@ static bool obs_init_textures(struct obs_video_info *ovi)
 #ifdef _WIN32
 		if (video->using_nv12_tex) {
 			video->copy_surfaces_encode[i] =
-				gs_stagesurface_create_nv12(ovi->output_width,
-							    ovi->output_height);
+				gs_stagesurface_create_nv12(info->width,
+							    info->height);
 			if (!video->copy_surfaces_encode[i]) {
 				success = false;
 				break;
 			}
 		} else if (video->using_p010_tex) {
 			video->copy_surfaces_encode[i] =
-				gs_stagesurface_create_p010(ovi->output_width,
-							    ovi->output_height);
+				gs_stagesurface_create_p010(info->width,
+							    info->height);
 			if (!video->copy_surfaces_encode[i]) {
 				success = false;
 				break;
@@ -361,13 +358,13 @@ static bool obs_init_textures(struct obs_video_info *ovi)
 #endif
 
 		if (video->gpu_conversion) {
-			if (!obs_init_gpu_copy_surfaces(ovi, i)) {
+			if (!obs_init_gpu_copy_surfaces(video, i)) {
 				success = false;
 				break;
 			}
 		} else {
 			video->copy_surfaces[i][0] = gs_stagesurface_create(
-				ovi->output_width, ovi->output_height, GS_RGBA);
+				info->width, info->height, GS_RGBA);
 			if (!video->copy_surfaces[i][0]) {
 				success = false;
 				break;
@@ -376,7 +373,7 @@ static bool obs_init_textures(struct obs_video_info *ovi)
 	}
 
 	enum gs_color_format format = GS_RGBA;
-	switch (ovi->output_format) {
+	switch (info->format) {
 	case VIDEO_FORMAT_I010:
 	case VIDEO_FORMAT_P010:
 	case VIDEO_FORMAT_I210:
@@ -386,27 +383,27 @@ static bool obs_init_textures(struct obs_video_info *ovi)
 	}
 
 	enum gs_color_space space = GS_CS_SRGB;
-	switch (ovi->colorspace) {
+	switch (info->colorspace) {
 	case VIDEO_CS_2100_PQ:
 	case VIDEO_CS_2100_HLG:
 		space = GS_CS_709_EXTENDED;
 		break;
 	default:
-		switch (ovi->output_format) {
+		switch (info->format) {
 		case VIDEO_FORMAT_I010:
 		case VIDEO_FORMAT_P010:
 			space = GS_CS_SRGB_16F;
 		}
 	}
 
-	video->render_texture = gs_texture_create(ovi->base_width,
-						  ovi->base_height, format, 1,
+	video->render_texture = gs_texture_create(obs->video.base_width,
+						  obs->video.base_height, format, 1,
 						  NULL, GS_RENDER_TARGET);
 	if (!video->render_texture)
 		success = false;
 
-	video->output_texture = gs_texture_create(ovi->output_width,
-						  ovi->output_height, format, 1,
+	video->output_texture = gs_texture_create(info->width,
+						  info->height, format, 1,
 						  NULL, GS_RENDER_TARGET);
 	if (!video->output_texture)
 		success = false;
@@ -558,15 +555,15 @@ static int obs_init_graphics(struct obs_video_info *ovi)
 	return success ? OBS_VIDEO_SUCCESS : OBS_VIDEO_FAIL;
 }
 
-static inline void set_video_matrix(struct obs_core_video *video,
-				    struct obs_video_info *ovi)
+static inline void set_video_matrix(struct obs_core_video_mix *video,
+				    struct video_output_info *info)
 {
 	struct matrix4 mat;
 	struct vec4 r_row;
 
-	if (format_is_yuv(ovi->output_format)) {
+	if (format_is_yuv(info->format)) {
 		video_format_get_parameters_for_format(
-			ovi->colorspace, ovi->range, ovi->output_format,
+			info->colorspace, info->range, info->format,
 			(float *)&mat, NULL, NULL);
 		matrix4_inv(&mat, &mat);
 
@@ -581,24 +578,23 @@ static inline void set_video_matrix(struct obs_core_video *video,
 	memcpy(video->color_matrix, &mat, sizeof(float) * 16);
 }
 
-static int obs_init_video(struct obs_video_info *ovi)
+int obs_init_video_mix(struct obs_video_info *ovi,
+		       struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
 	struct video_output_info vi;
-	int errorcode;
+
+	pthread_mutex_init_value(&video->gpu_encoder_mutex);
 
 	make_video_info(&vi, ovi);
-	video->base_width = ovi->base_width;
-	video->base_height = ovi->base_height;
-	video->output_width = ovi->output_width;
-	video->output_height = ovi->output_height;
 	video->gpu_conversion = ovi->gpu_conversion;
 	video->scale_type = ovi->scale_type;
+	video->gpu_was_active = false;
+	video->raw_was_active = false;
+	video->was_active = false;
 
-	set_video_matrix(video, ovi);
-
-	errorcode = video_output_open(&video->video, &vi);
+	set_video_matrix(video, &vi);
 
+	int errorcode = video_output_open(&video->video, &vi);
 	if (errorcode != VIDEO_OUTPUT_SUCCESS) {
 		if (errorcode == VIDEO_OUTPUT_INVALIDPARAM) {
 			blog(LOG_ERROR, "Invalid video parameters specified");
@@ -609,20 +605,35 @@ static int obs_init_video(struct obs_video_info *ovi)
 		return OBS_VIDEO_FAIL;
 	}
 
-	gs_enter_context(video->graphics);
+	if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0)
+		return OBS_VIDEO_FAIL;
+
+	gs_enter_context(obs->video.graphics);
 
-	if (ovi->gpu_conversion && !obs_init_gpu_conversion(ovi))
+	if (video->gpu_conversion && !obs_init_gpu_conversion(video))
 		return OBS_VIDEO_FAIL;
-	if (!obs_init_textures(ovi))
+	if (!obs_init_textures(video))
 		return OBS_VIDEO_FAIL;
 
 	gs_leave_context();
 
-	if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0)
-		return OBS_VIDEO_FAIL;
+	return OBS_VIDEO_SUCCESS;
+}
+
+static int obs_init_video(struct obs_video_info *ovi)
+{
+	struct obs_core_video *video = &obs->video;
+	video->base_width = ovi->base_width;
+	video->base_height = ovi->base_height;
+	video->video_frame_interval_ns = util_mul_div64(1000000000ULL, ovi->fps_den, ovi->fps_num);
+	video->video_half_frame_interval_ns = util_mul_div64(500000000ULL, ovi->fps_den, ovi->fps_num);
+
 	if (pthread_mutex_init(&video->task_mutex, NULL) < 0)
 		return OBS_VIDEO_FAIL;
+	if (pthread_mutex_init(&video->mixes_mutex, NULL) < 0)
+		return OBS_VIDEO_FAIL;
 
+	int errorcode;
 #ifdef __APPLE__
 	errorcode = pthread_create(&video->video_thread, NULL,
 				   obs_graphics_thread_autorelease, obs);
@@ -635,35 +646,35 @@ static int obs_init_video(struct obs_video_info *ovi)
 
 	video->thread_initialized = true;
 	video->ovi = *ovi;
+
+	if (!obs_view_add(&obs->data.main_view))
+		return OBS_VIDEO_FAIL;
+
 	return OBS_VIDEO_SUCCESS;
 }
 
 static void stop_video(void)
 {
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	for (size_t i = 0, num = obs->video.mixes.num; i < num; i++)
+		video_output_stop(obs->video.mixes.array[i].video);
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+
 	struct obs_core_video *video = &obs->video;
 	void *thread_retval;
 
-	if (video->video) {
-		video_output_stop(video->video);
 		if (video->thread_initialized) {
 			pthread_join(video->video_thread, &thread_retval);
 			video->thread_initialized = false;
 		}
-	}
 }
 
-static void obs_free_video(void)
+static void obs_free_render_textures(struct obs_core_video_mix *video)
 {
-	struct obs_core_video *video = &obs->video;
-
-	if (video->video) {
-		video_output_close(video->video);
-		video->video = NULL;
-
-		if (!video->graphics)
+		if (!obs->video.graphics)
 			return;
 
-		gs_enter_context(video->graphics);
+		gs_enter_context(obs->video.graphics);
 
 		for (size_t c = 0; c < NUM_CHANNELS; c++) {
 			if (video->mapped_surfaces[c]) {
@@ -713,6 +724,15 @@ static void obs_free_video(void)
 		video->output_texture = NULL;
 
 		gs_leave_context();
+}
+
+void obs_free_video_mix(struct obs_core_video_mix *video)
+{
+	if (video->video) {
+		video_output_close(video->video);
+		video->video = NULL;
+
+		obs_free_render_textures(video);
 
 		circlebuf_free(&video->vframe_info_buffer);
 		circlebuf_free(&video->vframe_info_buffer_gpu);
@@ -726,15 +746,30 @@ static void obs_free_video(void)
 		pthread_mutex_init_value(&video->gpu_encoder_mutex);
 		da_free(video->gpu_encoders);
 
-		pthread_mutex_destroy(&video->task_mutex);
-		pthread_mutex_init_value(&video->task_mutex);
-		circlebuf_free(&video->tasks);
-
 		video->gpu_encoder_active = 0;
 		video->cur_texture = 0;
 	}
 }
 
+static void obs_free_video(void)
+{
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	size_t num = obs->video.mixes.num;
+	if (num)
+		blog(LOG_WARNING, "%d views remain at shutdown", num);
+	for (size_t i = 0; i < num; i++)
+		obs_free_video_mix(obs->video.mixes.array + i);
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+	pthread_mutex_destroy(&obs->video.mixes_mutex);
+	pthread_mutex_init_value(&obs->video.mixes_mutex);
+	da_free(obs->video.mixes);
+
+	pthread_mutex_destroy(&obs->video.task_mutex);
+	pthread_mutex_init_value(&obs->video.task_mutex);
+	circlebuf_free(&obs->video.tasks);
+}
+
 static void obs_free_graphics(void)
 {
 	struct obs_core_video *video = &obs->video;
@@ -892,6 +927,7 @@ static void obs_free_data(void)
 
 	data->valid = false;
 
+	obs_view_remove(&data->main_view);
 	obs_main_view_free(&data->main_view);
 
 	blog(LOG_INFO, "Freeing OBS context data");
@@ -1057,8 +1093,8 @@ static bool obs_init(const char *locale, const char *module_config_path,
 
 	pthread_mutex_init_value(&obs->audio.monitoring_mutex);
 	pthread_mutex_init_value(&obs->audio.task_mutex);
-	pthread_mutex_init_value(&obs->video.gpu_encoder_mutex);
 	pthread_mutex_init_value(&obs->video.task_mutex);
+	pthread_mutex_init_value(&obs->video.mixes_mutex);
 
 	obs->name_store_owned = !store;
 	obs->name_store = store ? store : profiler_name_store_create();
@@ -1331,15 +1367,13 @@ int obs_reset_video(struct obs_video_info *ovi)
 		return OBS_VIDEO_FAIL;
 
 	/* don't allow changing of video settings if active. */
-	if (obs->video.video && obs_video_active())
+	if (obs_video_active())
 		return OBS_VIDEO_CURRENTLY_ACTIVE;
 
 	if (!size_valid(ovi->output_width, ovi->output_height) ||
 	    !size_valid(ovi->base_width, ovi->base_height))
 		return OBS_VIDEO_INVALID_PARAM;
 
-	struct obs_core_video *video = &obs->video;
-
 	stop_video();
 	obs_free_video();
 
@@ -1347,7 +1381,7 @@ int obs_reset_video(struct obs_video_info *ovi)
 	ovi->output_width &= 0xFFFFFFFC;
 	ovi->output_height &= 0xFFFFFFFE;
 
-	if (!video->graphics) {
+	if (!obs->video.graphics) {
 		int errorcode = obs_init_graphics(ovi);
 		if (errorcode != OBS_VIDEO_SUCCESS) {
 			obs_free_graphics();
@@ -1461,12 +1495,10 @@ bool obs_reset_audio(const struct obs_audio_info *oai)
 
 bool obs_get_video_info(struct obs_video_info *ovi)
 {
-	struct obs_core_video *video = &obs->video;
-
-	if (!video->graphics)
+	if (!obs->video.graphics)
 		return false;
 
-	*ovi = video->ovi;
+	*ovi = obs->video.ovi;
 	return true;
 }
 
@@ -1617,7 +1649,7 @@ audio_t *obs_get_audio(void)
 
 video_t *obs_get_video(void)
 {
-	return obs->video.video;
+	return obs->video.main_mix->video;
 }
 
 /* TODO: optimize this later so it's not just O(N) string lookups */
@@ -1974,12 +2006,12 @@ static void obs_render_main_texture_internal(enum gs_blend_type src_c,
 					     enum gs_blend_type src_a,
 					     enum gs_blend_type dest_a)
 {
-	struct obs_core_video *video;
+	struct obs_core_video_mix *video;
 	gs_texture_t *tex;
 	gs_effect_t *effect;
 	gs_eparam_t *param;
 
-	video = &obs->video;
+	video = obs->video.main_mix;
 	if (!video->texture_rendered)
 		return;
 
@@ -2033,9 +2065,9 @@ void obs_render_main_texture_src_color_only(void)
 
 gs_texture_t *obs_get_main_texture(void)
 {
-	struct obs_core_video *video;
+	struct obs_core_video_mix *video;
 
-	video = &obs->video;
+	video = obs->video.main_mix;
 	if (!video->texture_rendered)
 		return NULL;
 
@@ -2678,12 +2710,31 @@ uint32_t obs_get_lagged_frames(void)
 	return obs->video.lagged_frames;
 }
 
+struct obs_core_video_mix *get_mix_for_video(video_t *v)
+{
+	struct obs_core_video_mix *result = NULL;
+
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+		struct obs_core_video_mix *mix = obs->video.mixes.array + i;
+
+		if (v == mix->video) {
+			result = mix;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+	return result;
+}
+
 void start_raw_video(video_t *v, const struct video_scale_info *conversion,
 		     void (*callback)(void *param, struct video_data *frame),
 		     void *param)
 {
-	struct obs_core_video *video = &obs->video;
-	os_atomic_inc_long(&video->raw_active);
+	struct obs_core_video_mix *video = get_mix_for_video(v);
+	if (video)
+		os_atomic_inc_long(&video->raw_active);
 	video_output_connect(v, conversion, callback, param);
 }
 
@@ -2691,8 +2742,9 @@ void stop_raw_video(video_t *v,
 		    void (*callback)(void *param, struct video_data *frame),
 		    void *param)
 {
-	struct obs_core_video *video = &obs->video;
-	os_atomic_dec_long(&video->raw_active);
+	struct obs_core_video_mix *video = get_mix_for_video(v);
+	if (video)
+		os_atomic_dec_long(&video->raw_active);
 	video_output_disconnect(v, callback, param);
 }
 
@@ -2701,7 +2753,7 @@ void obs_add_raw_video_callback(const struct video_scale_info *conversion,
 						 struct video_data *frame),
 				void *param)
 {
-	struct obs_core_video *video = &obs->video;
+	struct obs_core_video_mix *video = obs->video.main_mix;
 	start_raw_video(video->video, conversion, callback, param);
 }
 
@@ -2709,7 +2761,7 @@ void obs_remove_raw_video_callback(void (*callback)(void *param,
 						    struct video_data *frame),
 				   void *param)
 {
-	struct obs_core_video *video = &obs->video;
+	struct obs_core_video_mix *video = obs->video.main_mix;
 	stop_raw_video(video->video, callback, param);
 }
 
@@ -2752,13 +2804,13 @@ 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);
+extern bool init_gpu_encoding(struct obs_core_video_mix *video);
+extern void stop_gpu_encoding_thread(struct obs_core_video_mix *video);
+extern void free_gpu_encoding(struct obs_core_video_mix *video);
 
 bool start_gpu_encode(obs_encoder_t *encoder)
 {
-	struct obs_core_video *video = &obs->video;
+	struct obs_core_video_mix *video = obs->video.main_mix;
 	bool success = true;
 
 	obs_enter_graphics();
@@ -2784,7 +2836,7 @@ bool start_gpu_encode(obs_encoder_t *encoder)
 
 void stop_gpu_encode(obs_encoder_t *encoder)
 {
-	struct obs_core_video *video = &obs->video;
+	struct obs_core_video_mix *video = obs->video.main_mix;
 	bool call_free = false;
 
 	os_atomic_dec_long(&video->gpu_encoder_active);
@@ -2811,21 +2863,32 @@ void stop_gpu_encode(obs_encoder_t *encoder)
 
 bool obs_video_active(void)
 {
-	struct obs_core_video *video = &obs->video;
+	bool result = false;
+
+	pthread_mutex_lock(&obs->video.mixes_mutex);
+	for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+		struct obs_core_video_mix *video = obs->video.mixes.array + i;
 
-	return os_atomic_load_long(&video->raw_active) > 0 ||
-	       os_atomic_load_long(&video->gpu_encoder_active) > 0;
+		if (os_atomic_load_long(&video->raw_active) > 0 ||
+		    os_atomic_load_long(&video->gpu_encoder_active) > 0) {
+			result = true;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+	return result;
 }
 
 bool obs_nv12_tex_active(void)
 {
-	struct obs_core_video *video = &obs->video;
+	struct obs_core_video_mix *video = obs->video.main_mix;
 	return video->using_nv12_tex;
 }
 
 bool obs_p010_tex_active(void)
 {
-	struct obs_core_video *video = &obs->video;
+	struct obs_core_video_mix *video = obs->video.main_mix;
 	return video->using_p010_tex;
 }
 

+ 6 - 0
libobs/obs.h

@@ -888,6 +888,12 @@ EXPORT obs_source_t *obs_view_get_source(obs_view_t *view, uint32_t channel);
 /** Renders the sources of this view context */
 EXPORT void obs_view_render(obs_view_t *view);
 
+/** Adds a view to the main render loop */
+EXPORT video_t *obs_view_add(obs_view_t *view);
+
+/** Removes a view from the main render loop */
+EXPORT void obs_view_remove(obs_view_t *view);
+
 /* ------------------------------------------------------------------------- */
 /* Display context */