Browse Source

decklink-output-ui: Align render output paths

Use the render path that preview uses for non-preview rendering, which
uses the GPU to rescale rather than the CPU and pulls the image from the
GPU before pixel format conversion happens. This means it's no longer
necessary to select BGRA to use the keyer anymore.
jpark37 3 years ago
parent
commit
ddee7ff6fe

+ 150 - 89
UI/frontend-plugins/decklink-output-ui/decklink-ui-main.cpp

@@ -19,11 +19,9 @@ bool shutting_down = false;
 bool main_output_running = false;
 bool preview_output_running = false;
 
-obs_output_t *output;
-
 constexpr size_t STAGE_BUFFER_COUNT = 3;
 
-struct preview_output {
+struct decklink_ui_output {
 	bool enabled;
 	obs_source_t *current_source;
 	obs_output_t *output;
@@ -40,7 +38,7 @@ struct preview_output {
 	obs_video_info ovi;
 };
 
-static struct preview_output context = {0};
+static struct decklink_ui_output context = {0};
 
 OBSData load_settings()
 {
@@ -58,10 +56,28 @@ OBSData load_settings()
 	return nullptr;
 }
 
+static void decklink_ui_tick(void *param, float sec);
+static void decklink_ui_render(void *param);
+
 void output_stop()
 {
-	obs_output_stop(output);
-	obs_output_release(output);
+	obs_output_stop(context.output);
+	obs_output_release(context.output);
+
+	obs_remove_main_rendered_callback(decklink_ui_render, &context);
+
+	obs_enter_graphics();
+	for (gs_stagesurf_t *&surf : context.stagesurfaces) {
+		gs_stagesurface_destroy(surf);
+		surf = nullptr;
+	}
+	gs_texrender_destroy(context.texrender);
+	context.texrender = nullptr;
+	obs_leave_graphics();
+
+	video_output_close(context.video_queue);
+	obs_remove_tick_callback(decklink_ui_tick, &context);
+
 	main_output_running = false;
 
 	if (!shutting_down)
@@ -73,10 +89,51 @@ void output_start()
 	OBSData settings = load_settings();
 
 	if (settings != nullptr) {
-		output = obs_output_create("decklink_output", "decklink_output",
-					   settings, NULL);
+		obs_add_tick_callback(decklink_ui_tick, &context);
+		context.output = obs_output_create(
+			"decklink_output", "decklink_output", settings, NULL);
+
+		obs_get_video_info(&context.ovi);
 
-		bool started = obs_output_start(output);
+		const struct video_scale_info *const conversion =
+			obs_output_get_video_conversion(context.output);
+		const uint32_t width = conversion->width;
+		const uint32_t height = conversion->height;
+
+		obs_enter_graphics();
+		context.texrender_premultiplied = nullptr;
+		context.texrender = gs_texrender_create(GS_BGRA, GS_ZS_NONE);
+		for (gs_stagesurf_t *&surf : context.stagesurfaces)
+			surf = gs_stagesurface_create(width, height, GS_BGRA);
+		obs_leave_graphics();
+
+		for (bool &written : context.surf_written)
+			written = false;
+
+		context.stage_index = 0;
+
+		const video_output_info *mainVOI =
+			video_output_get_info(obs_get_video());
+
+		video_output_info vi = {0};
+		vi.format = VIDEO_FORMAT_BGRA;
+		vi.width = width;
+		vi.height = height;
+		vi.fps_den = context.ovi.fps_den;
+		vi.fps_num = context.ovi.fps_num;
+		vi.cache_size = 16;
+		vi.colorspace = mainVOI->colorspace;
+		vi.range = VIDEO_RANGE_FULL;
+		vi.name = "decklink_output";
+
+		video_output_open(&context.video_queue, &vi);
+
+		context.current_source = nullptr;
+		obs_add_main_rendered_callback(decklink_ui_render, &context);
+
+		obs_output_set_media(context.output, context.video_queue,
+				     obs_get_audio());
+		bool started = obs_output_start(context.output);
 
 		main_output_running = started;
 
@@ -113,13 +170,12 @@ OBSData load_preview_settings()
 }
 
 void on_preview_scene_changed(enum obs_frontend_event event, void *param);
-void render_preview_source(void *param, uint32_t cx, uint32_t cy);
 
-static void preview_tick(void *param, float sec)
+static void decklink_ui_tick(void *param, float sec)
 {
 	UNUSED_PARAMETER(sec);
 
-	auto ctx = (struct preview_output *)param;
+	auto ctx = (struct decklink_ui_output *)param;
 
 	if (ctx->texrender_premultiplied)
 		gs_texrender_reset(ctx->texrender_premultiplied);
@@ -132,7 +188,7 @@ void preview_output_stop()
 	obs_output_stop(context.output);
 	obs_output_release(context.output);
 
-	obs_remove_main_render_callback(render_preview_source, &context);
+	obs_remove_main_rendered_callback(decklink_ui_render, &context);
 	obs_frontend_remove_event_callback(on_preview_scene_changed, &context);
 
 	obs_source_release(context.current_source);
@@ -149,7 +205,7 @@ void preview_output_stop()
 	obs_leave_graphics();
 
 	video_output_close(context.video_queue);
-	obs_remove_tick_callback(preview_tick, &context);
+	obs_remove_tick_callback(decklink_ui_tick, &context);
 
 	preview_output_running = false;
 
@@ -162,7 +218,7 @@ void preview_output_start()
 	OBSData settings = load_preview_settings();
 
 	if (settings != nullptr) {
-		obs_add_tick_callback(preview_tick, &context);
+		obs_add_tick_callback(decklink_ui_tick, &context);
 		context.output = obs_output_create("decklink_output",
 						   "decklink_preview_output",
 						   settings, NULL);
@@ -212,7 +268,7 @@ void preview_output_start()
 			context.current_source =
 				obs_frontend_get_current_scene();
 		}
-		obs_add_main_render_callback(render_preview_source, &context);
+		obs_add_main_rendered_callback(decklink_ui_render, &context);
 
 		obs_output_set_media(context.output, context.video_queue,
 				     obs_get_audio());
@@ -237,7 +293,7 @@ void preview_output_toggle()
 
 void on_preview_scene_changed(enum obs_frontend_event event, void *param)
 {
-	auto ctx = (struct preview_output *)param;
+	auto ctx = (struct decklink_ui_output *)param;
 	switch (event) {
 	case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED:
 	case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
@@ -259,27 +315,33 @@ void on_preview_scene_changed(enum obs_frontend_event event, void *param)
 	}
 }
 
-void render_preview_source(void *param, uint32_t cx, uint32_t cy)
+static void decklink_ui_render(void *param)
 {
-	UNUSED_PARAMETER(cx);
-	UNUSED_PARAMETER(cy);
+	auto *const ctx = (struct decklink_ui_output *)param;
 
-	auto ctx = (struct preview_output *)param;
+	uint32_t width = 0;
+	uint32_t height = 0;
+	gs_texture_t *tex = nullptr;
 
-	if (!ctx->current_source)
-		return;
+	if (main_output_running) {
+		tex = obs_get_main_texture();
+		if (!tex)
+			return;
 
-	const uint32_t width = obs_source_get_base_width(ctx->current_source);
-	const uint32_t height = obs_source_get_base_height(ctx->current_source);
+		width = gs_texture_get_width(tex);
+		height = gs_texture_get_height(tex);
+	} else if (preview_output_running) {
+		if (!ctx->current_source)
+			return;
 
-	const struct video_scale_info *const conversion =
-		obs_output_get_video_conversion(context.output);
-	const uint32_t scaled_width = conversion->width;
-	const uint32_t scaled_height = conversion->height;
+		width = obs_source_get_base_width(ctx->current_source);
+		height = obs_source_get_base_height(ctx->current_source);
+
+		gs_texrender_t *const texrender_premultiplied =
+			ctx->texrender_premultiplied;
+		if (!gs_texrender_begin(texrender_premultiplied, width, height))
+			return;
 
-	gs_texrender_t *const texrender_premultiplied =
-		ctx->texrender_premultiplied;
-	if (gs_texrender_begin(texrender_premultiplied, width, height)) {
 		struct vec4 background;
 		vec4_zero(&background);
 
@@ -295,71 +357,70 @@ void render_preview_source(void *param, uint32_t cx, uint32_t cy)
 		gs_blend_state_pop();
 		gs_texrender_end(texrender_premultiplied);
 
-		if (gs_texrender_begin(ctx->texrender, scaled_width,
-				       scaled_height)) {
-			const bool previous = gs_framebuffer_srgb_enabled();
-			gs_enable_framebuffer_srgb(true);
-			gs_enable_blending(false);
-
-			gs_texture_t *const tex = gs_texrender_get_texture(
-				texrender_premultiplied);
-			gs_effect_t *const effect =
-				obs_get_base_effect(OBS_EFFECT_DEFAULT);
-			gs_effect_set_texture_srgb(
-				gs_effect_get_param_by_name(effect, "image"),
-				tex);
-			while (gs_effect_loop(effect, "DrawAlphaDivide")) {
-				gs_draw_sprite(tex, 0, 0, 0);
-			}
+		tex = gs_texrender_get_texture(texrender_premultiplied);
+	} else {
+		return;
+	}
+
+	const struct video_scale_info *const conversion =
+		obs_output_get_video_conversion(context.output);
+	const uint32_t scaled_width = conversion->width;
+	const uint32_t scaled_height = conversion->height;
 
-			gs_enable_blending(true);
-			gs_enable_framebuffer_srgb(previous);
+	if (!gs_texrender_begin(ctx->texrender, scaled_width, scaled_height))
+		return;
 
-			gs_texrender_end(ctx->texrender);
-		}
+	const bool previous = gs_framebuffer_srgb_enabled();
+	gs_enable_framebuffer_srgb(true);
+	gs_enable_blending(false);
 
-		const size_t write_stage_index = ctx->stage_index;
-		gs_stage_texture(ctx->stagesurfaces[write_stage_index],
-				 gs_texrender_get_texture(ctx->texrender));
-		ctx->surf_written[write_stage_index] = true;
-
-		const size_t read_stage_index =
-			(write_stage_index + 1) % STAGE_BUFFER_COUNT;
-		if (ctx->surf_written[read_stage_index]) {
-			struct video_frame output_frame;
-			if (video_output_lock_frame(ctx->video_queue,
-						    &output_frame, 1,
-						    os_gettime_ns())) {
-				gs_stagesurf_t *const read_surf =
-					ctx->stagesurfaces[read_stage_index];
-				if (gs_stagesurface_map(read_surf,
-							&ctx->video_data,
-							&ctx->video_linesize)) {
-					uint32_t linesize =
-						output_frame.linesize[0];
-					for (uint32_t i = 0; i < scaled_height;
-					     i++) {
-						uint32_t dst_offset =
-							linesize * i;
-						uint32_t src_offset =
-							ctx->video_linesize * i;
-						memcpy(output_frame.data[0] +
-							       dst_offset,
-						       ctx->video_data +
-							       src_offset,
-						       linesize);
-					}
-
-					gs_stagesurface_unmap(read_surf);
-					ctx->video_data = nullptr;
+	gs_effect_t *const effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
+	gs_effect_set_texture_srgb(gs_effect_get_param_by_name(effect, "image"),
+				   tex);
+	while (gs_effect_loop(effect, "DrawAlphaDivide")) {
+		gs_draw_sprite(tex, 0, 0, 0);
+	}
+
+	gs_enable_blending(true);
+	gs_enable_framebuffer_srgb(previous);
+
+	gs_texrender_end(ctx->texrender);
+
+	const size_t write_stage_index = ctx->stage_index;
+	gs_stage_texture(ctx->stagesurfaces[write_stage_index],
+			 gs_texrender_get_texture(ctx->texrender));
+	ctx->surf_written[write_stage_index] = true;
+
+	const size_t read_stage_index =
+		(write_stage_index + 1) % STAGE_BUFFER_COUNT;
+	if (ctx->surf_written[read_stage_index]) {
+		struct video_frame output_frame;
+		if (video_output_lock_frame(ctx->video_queue, &output_frame, 1,
+					    os_gettime_ns())) {
+			gs_stagesurf_t *const read_surf =
+				ctx->stagesurfaces[read_stage_index];
+			if (gs_stagesurface_map(read_surf, &ctx->video_data,
+						&ctx->video_linesize)) {
+				uint32_t linesize = output_frame.linesize[0];
+				for (uint32_t i = 0; i < scaled_height; i++) {
+					uint32_t dst_offset = linesize * i;
+					uint32_t src_offset =
+						ctx->video_linesize * i;
+					memcpy(output_frame.data[0] +
+						       dst_offset,
+					       ctx->video_data + src_offset,
+					       linesize);
 				}
 
-				video_output_unlock_frame(ctx->video_queue);
+				gs_stagesurface_unmap(read_surf);
+				ctx->video_data = nullptr;
 			}
-		}
 
-		ctx->stage_index = read_stage_index;
+			video_output_unlock_frame(ctx->video_queue);
+		}
 	}
+
+	ctx->stage_index = read_stage_index;
 }
 
 void addOutputUI(void)

+ 1 - 21
UI/frontend-plugins/decklink-output-ui/forms/output.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>785</width>
-    <height>497</height>
+    <height>484</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -106,26 +106,6 @@
      </item>
     </layout>
    </item>
-   <item>
-    <spacer name="verticalSpacer">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>40</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item>
-    <widget class="QLabel" name="keyerLabel">
-     <property name="text">
-      <string>Keyer output requires BGRA mode in advanced settings.</string>
-     </property>
-    </widget>
-   </item>
   </layout>
  </widget>
  <resources/>