diff --git a/UI/frontend-plugins/decklink-output-ui/decklink-ui-main.cpp b/UI/frontend-plugins/decklink-output-ui/decklink-ui-main.cpp index a937255e5..29819de17 100644 --- a/UI/frontend-plugins/decklink-output-ui/decklink-ui-main.cpp +++ b/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); - bool started = obs_output_start(output); + obs_get_video_info(&context.ovi); + + 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); + tex = gs_texrender_get_texture(texrender_premultiplied); + } else { + return; + } - 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); - } + 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; + 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); + } - 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_enable_blending(true); + gs_enable_framebuffer_srgb(previous); - gs_stagesurface_unmap(read_surf); - ctx->video_data = nullptr; + 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) diff --git a/UI/frontend-plugins/decklink-output-ui/forms/output.ui b/UI/frontend-plugins/decklink-output-ui/forms/output.ui index 39971d35d..945bf5eb6 100644 --- a/UI/frontend-plugins/decklink-output-ui/forms/output.ui +++ b/UI/frontend-plugins/decklink-output-ui/forms/output.ui @@ -7,7 +7,7 @@ 0 0 785 - 497 + 484 @@ -106,26 +106,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Keyer output requires BGRA mode in advanced settings. - - -