From 9fabdacf449b4a5e65fdde981dfd48883a29af87 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 28 Jul 2019 22:07:36 +0200 Subject: [PATCH] Update for the new Nageru 1.9.0 scene system. (Actually, we require 1.9.1 features.) --- nageru/ultimate.lua | 699 +++++++++++++++++--------------------------- 1 file changed, 263 insertions(+), 436 deletions(-) diff --git a/nageru/ultimate.lua b/nageru/ultimate.lua index 34462f2..aec368d 100644 --- a/nageru/ultimate.lua +++ b/nageru/ultimate.lua @@ -71,6 +71,8 @@ local bg_video = VideoInput.new(cef_path .. "/flow-720.mp4", Nageru.VIDEO_FORMAT -- local iptv_video = VideoInput.new("http://10.34.129.69:9060/1/v.flv", Nageru.VIDEO_FORMAT_YCBCR) local iptv_video = VideoInput.new(futatabi_server, Nageru.VIDEO_FORMAT_YCBCR) +local static_image = ImageInput.new(cef_path .. "/nageru/dsn-bg.png") + function reload_cef() cef_input:reload() cef_input:execute_javascript_async("play()") @@ -80,29 +82,11 @@ function disconnect_iptv() iptv_video:disconnect() end --- Utility function to help creating many similar chains that can differ --- in a free set of chosen parameters. -function make_cartesian_product(parms, callback) - return make_cartesian_product_internal(parms, callback, 1, {}) -end - -function make_cartesian_product_internal(parms, callback, index, args) - if index > #parms then - return callback(unpack(args)) - end - local ret = {} - for _, value in ipairs(parms[index]) do - args[index] = value - ret[value] = make_cartesian_product_internal(parms, callback, index + 1, args) - end - return ret -end - -- An overlay with variable alpha. -function make_overlay(chain, base) - local image = chain:add_html_input(cef_input) - local multiply_effect = chain:add_effect(MultiplyEffect.new()) - local overlay_effect = chain:add_effect(OverlayEffect.new(), base, multiply_effect) +function make_overlay(scene, base) + local image = scene:add_input(cef_input) + local multiply_effect = scene:add_effect(MultiplyEffect.new()) + local overlay_effect = scene:add_optional_effect(OverlayEffect.new(), base, multiply_effect) return { image = image, multiply_effect = multiply_effect, @@ -110,287 +94,144 @@ function make_overlay(chain, base) } end -function possibly_make_overlay(has_overlay, chain, base) - if has_overlay == true then - return make_overlay(chain, base) - else - return nil - end -end - -function make_fade_input(chain, signal, live, deint, scale, video) - local input, wb_effect, resample_effect, last - if video then - input = chain:add_video_input(iptv_video, false) - last = input - elseif live then - input = chain:add_live_input(false, deint) - input:connect_signal(signal) - last = input - else - input = chain:add_effect(ImageInput.new(cef_path .. "/nageru/dsn-bg.png")) - last = input - end - - -- If we cared about this for the non-main inputs, we would have - -- checked hq here and invoked ResizeEffect instead. - if scale then - resample_effect = chain:add_effect(ResampleEffect.new()) - last = resample_effect - end - - -- Make sure to put the white balance after the scaling (usually more efficient). - if live then - wb_effect = chain:add_effect(WhiteBalanceEffect.new()) - last = wb_effect - end - - return { - input = input, - wb_effect = wb_effect, - resample_effect = resample_effect, - last = last +function make_fade_input(scene) + local ret = { + input = scene:add_input(), + resample_effect = scene:add_optional_effect(ResampleEffect.new()), -- Activated if scaling. + wb_effect = scene:add_optional_effect(WhiteBalanceEffect.new()) -- Activated for video inputs. } + ret.overlay = make_overlay(scene, ret.wb_effect) + return ret end --- A chain to fade between two inputs, of which either can be a picture --- or a live input. In practice only used live, but we still support the --- hq parameter. -function make_fade_chain(input0_live, input0_deint, input0_scale, input0_video, input1_live, input1_deint, input1_scale, input1_video, has_overlay, hq) - local chain = EffectChain.new(16, 9) - - local input0 = make_fade_input(chain, INPUT0_SIGNAL_NUM, input0_live, input0_deint, input0_scale, input0_video) - local input1 = make_fade_input(chain, INPUT1_SIGNAL_NUM, input1_live, input1_deint, input1_scale, input1_video) +-- A scene to fade between two inputs, of which either can be a picture +-- or a live input. +function make_fade_scene() + local scene = Scene.new(16, 9) -- If fading between two live inputs, the overlay is put on top. -- If fading between the static picture and a live input, -- the overlay is put on the live input. - local overlay = nil - if input0_live and not input1_live then - overlay = possibly_make_overlay(has_overlay, chain, input0.last) - if overlay then - input0.last = overlay.overlay_effect - end - elseif input1_live and not input0_live then - overlay = possibly_make_overlay(has_overlay, chain, input1.last) - if overlay then - input1.last = overlay.overlay_effect - end - end + local input0 = make_fade_input(scene) + local input1 = make_fade_input(scene) + local mix_effect = scene:add_effect(MixEffect.new(), input0.overlay.overlay_effect, input1.overlay.overlay_effect) + local overlay = make_overlay(scene, mix_effect) - local mix_effect = chain:add_effect(MixEffect.new(), input0.last, input1.last) - if input0_live and input1_live then - overlay = possibly_make_overlay(has_overlay, chain, mix_effect) - end + -- At most one overlay can be active at any given time. + input0.overlay.overlay_effect:promise_to_disable_if_enabled(input1.overlay.overlay_effect) + input0.overlay.overlay_effect:promise_to_disable_if_enabled(overlay.overlay_effect) + input1.overlay.overlay_effect:promise_to_disable_if_enabled(input0.overlay.overlay_effect) + input1.overlay.overlay_effect:promise_to_disable_if_enabled(overlay.overlay_effect) + overlay.overlay_effect:promise_to_disable_if_enabled(input0.overlay.overlay_effect) + overlay.overlay_effect:promise_to_disable_if_enabled(input1.overlay.overlay_effect) - chain:finalize(hq) + scene:finalize(true) -- Only used live, don't bother creating LQ versions. return { - chain = chain, + scene = scene, input0 = input0, input1 = input1, mix_effect = mix_effect, overlay = overlay } end +fade_scene = make_fade_scene() --- Chains to fade between two inputs, in various configurations. -local fade_chains = make_cartesian_product({ - {"video", "static", "live", "livedeint"}, -- input0_type - {true, false}, -- input0_scale - {"video", "static", "live", "livedeint"}, -- input1_type - {true, false}, -- input1_scale - {true, false}, -- has_overlay - {true} -- hq -}, function(input0_type, input0_scale, input1_type, input1_scale, has_overlay, hq) - local input0_live = (input0_type ~= "static") - local input1_live = (input1_type ~= "static") - local input0_deint = (input0_type == "livedeint") - local input1_deint = (input1_type == "livedeint") - local input0_video = (input0_type == "video") - local input1_video = (input1_type == "video") - return make_fade_chain(input0_live, input0_deint, input0_scale, input0_video, input1_live, input1_deint, input1_scale, input1_video, has_overlay, hq) -end) - -function make_sbs_input(chain, signal, deint, has_overlay, hq) - local input = chain:add_live_input(not deint, deint) -- Override bounce only if not deinterlacing. - input:connect_signal(signal) - - local wb_effect = chain:add_effect(WhiteBalanceEffect.new()) -- Needs to be before the overlay. - - local overlay = nil - if has_overlay then - overlay = make_overlay(chain, wb_effect) - end - - local resample_effect = nil - local resize_effect = nil - if (hq) then - resample_effect = chain:add_effect(ResampleEffect.new()) - else - resize_effect = chain:add_effect(ResizeEffect.new()) - end - - local padding_effect = chain:add_effect(IntegralPaddingEffect.new()) - +function make_sbs_input(scene, has_overlay) + local input = scene:add_input() + local resample_effect = ResampleEffect.new() + local resize_effect = ResizeEffect.new() return { input = input, - wb_effect = wb_effect, + overlay = has_overlay and make_overlay(scene, input) or nil, resample_effect = resample_effect, resize_effect = resize_effect, - padding_effect = padding_effect, - overlay = overlay + resample_switcher = scene:add_effect({resample_effect, resize_effect}), + wb_effect = scene:add_effect(WhiteBalanceEffect.new()), + padding_effect = scene:add_effect(IntegralPaddingEffect.new()) } end --- Side-by-side chains. -function make_sbs_chain(input0_type, input0_overlay, input1_type, hq) - local chain = EffectChain.new(16, 9) - - local bg = chain:add_video_input(bg_video, false) +-- Side-by-side scene. +function make_sbs_scene() + local scene = Scene.new(16, 9) - local input0 = make_sbs_input(chain, INPUT0_SIGNAL_NUM, input0_type == "livedeint", input0_overlay, hq) - local input1 = make_sbs_input(chain, INPUT4_SIGNAL_NUM, input1_type == "livedeint", false, hq) + local bg = scene:add_input(bg_video) + local input0 = make_sbs_input(scene, true) + local input1 = make_sbs_input(scene, false) input0.padding_effect:set_vec4("border_color", 0.0, 0.0, 0.0, 0.0) input1.padding_effect:set_vec4("border_color", 0.0, 0.0, 0.0, 0.0) - local i0 = chain:add_effect(OverlayEffect.new(), bg, input0.padding_effect) - chain:add_effect(OverlayEffect.new(), i0, input1.padding_effect) - chain:finalize(hq) + input0.input:display(0) + input1.input:display(1) + + local i0 = scene:add_effect(OverlayEffect.new(), bg, input0.padding_effect) + scene:add_effect(OverlayEffect.new(), i0, input1.padding_effect) + scene:finalize() return { - chain = chain, + scene = scene, input0 = input0, input1 = input1, overlay = input0.overlay -- May be nil. } end +local sbs_scene = make_sbs_scene() +function make_simple_scene_no_finalize() + local scene = Scene.new(16, 9) --- Make all possible combinations of side-by-side chains. -local sbs_chains = make_cartesian_product({ - {"live", "livedeint"}, -- input0_type - {true, false}, -- input0_overlay - {"live", "livedeint"}, -- input1_type - {true, false} -- hq -}, function(input0_type, input0_overlay, input1_type, hq) - return make_sbs_chain(input0_type, input0_overlay, input1_type, hq) -end) - -function make_simple_chain_no_finalize(input_deint, input_video, input_scale, has_overlay, hq) - local chain = EffectChain.new(16, 9) - - local input - if input_video then - input = chain:add_video_input(iptv_video, false) - else - input = chain:add_live_input(false, input_deint) - input:connect_signal(0) -- First input card. Can be changed whenever you want. - end - - local resample_effect, resize_effect - if input_scale then - if hq then - resample_effect = chain:add_effect(ResampleEffect.new()) - else - resize_effect = chain:add_effect(ResizeEffect.new()) - end - end - - local wb_effect = chain:add_effect(WhiteBalanceEffect.new()) - local overlay = possibly_make_overlay(has_overlay, chain, wb_effect) + local input = scene:add_input() + local resample_effect = scene:add_effect({ResampleEffect.new(), ResizeEffect.new(), IdentityEffect.new()}) + local wb_effect = scene:add_effect(WhiteBalanceEffect.new()) + local overlay = make_overlay(scene, wb_effect) return { - chain = chain, + scene = scene, input = input, wb_effect = wb_effect, resample_effect = resample_effect, - resize_effect = resize_effect, overlay = overlay } end --- A chain to show a single input on screen. -function make_simple_chain(input_deint, input_video, input_scale, has_overlay, hq) - local chain = make_simple_chain_no_finalize(input_deint, input_video, input_scale, has_overlay, hq) - chain.chain:finalize(hq) - return chain +-- A scene to show a single input on screen. +function make_simple_scene() + local scene = make_simple_scene_no_finalize() + scene.scene:finalize() + return scene end --- Make all possible combinations of single-input chains. -local simple_chains = make_cartesian_product({ - {"video", "live", "livedeint"}, -- input_type - {true, false}, -- input_scale - {true, false}, -- has_overlay - {true, false} -- hq -}, function(input_type, input_scale, has_overlay, hq) - local input_deint = (input_type == "livedeint") - local input_video = (input_type == "video") - return make_simple_chain(input_deint, input_video, input_scale, has_overlay, hq) -end) - --- A chain to show a single input on screen, with a stinger on top. -function make_stinger_chain(input_deint, input_video, input_scale, has_overlay, stinger_frame, hq) - local chain = make_simple_chain_no_finalize(input_deint, input_video, input_scale, has_overlay, hq) - local filename = cef_path .. "/stinger/blur" .. string.rep("0", 3 - string.len(tostring(stinger_frame))) .. stinger_frame .. ".png" - - local last - if has_overlay then - last = chain.overlay.overlay_effect - else - last = chain.wb_effect - end - local input = chain.chain:add_effect(ImageInput.new(filename)) - chain.chain:add_effect(OverlayEffect.new(), last, input) - chain.chain:finalize(hq) - return chain +local simple_scene = make_simple_scene() + +-- Load all the stinger frames. +local stinger_images = {} +for idx=0,24 do + local filename = cef_path .. "/stinger/blur" .. string.rep("0", 3 - string.len(tostring(idx))) .. idx .. ".png" + stinger_images[idx] = ImageInput.new(filename) end --- Single-input chains with various stinger frames on top. -local stinger_chains = make_cartesian_product({ - {"video", "live", "livedeint"}, -- input_type - {true, false}, -- input_scale - {true, false}, -- has_overlay - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}, -- stinger_frame - {true, false} -- hq -}, function(input_type, input_scale, has_overlay, stinger_frame, hq) - local input_deint = (input_type == "livedeint") - local input_video = (input_type == "video") - return make_stinger_chain(input_deint, input_video, input_scale, has_overlay, stinger_frame, hq) -end) - --- A chain to show a single static picture on screen. Never with HTML overlay. -local static_chains = make_cartesian_product({ - {true, false} -- hq -}, function(hq) - local chain = EffectChain.new(16, 9) - local chain_input = chain:add_effect(ImageInput.new(cef_path .. "/nageru/dsn-bg.png")) - - chain:finalize(hq) - return { - chain = chain - } -end) +-- A scene to show a single input on screen, with a stinger on top. +function make_stinger_scene() + local scene = make_simple_scene_no_finalize() + scene.stinger_input = scene.scene:add_input() + scene.scene:add_effect(OverlayEffect.new(), scene.overlay.overlay_effect, scene.stinger_input) + scene.scene:finalize() + return scene +end +local stinger_scene = make_stinger_scene() --- A chain to show the overlay and nothing more. LQ only, --- since it is not a valid live signal. -local overlay_chain_lq = EffectChain.new(16, 9) -local overlay_chain_lq_input = overlay_chain_lq:add_html_input(cef_input) -overlay_chain_lq:finalize(false) +-- A scene to show a single static picture on screen. Never with HTML overlay. +local static_scene = Scene.new(16, 9) +static_scene:add_input(static_image) +static_scene:finalize() --- Used for indexing into the tables of chains. -function get_input_type(signals, signal_num) - if signal_num == STATIC_SIGNAL_NUM then - return "static" - elseif signal_num == VIDEO_SIGNAL_NUM then - return "video" - elseif signals:get_interlaced(signal_num) then - return "livedeint" - else - return "live" - end -end +-- A scene to show the overlay and nothing more. LQ only, +-- since it is not a valid live signal. +local overlay_scene_lq = Scene.new(16, 9) +local overlay_scene_lq_input = overlay_scene_lq:add_input(cef_input) +overlay_scene_lq:finalize(false) function needs_scale(signals, signal_num, width, height) if signal_num == STATIC_SIGNAL_NUM then @@ -401,14 +242,19 @@ function needs_scale(signals, signal_num, width, height) return (signals:get_width(signal_num) ~= width or signals:get_height(signal_num) ~= height) end -function set_scale_parameters_if_needed(chain_or_input, width, height) - if chain_or_input.resample_effect then - chain_or_input.resample_effect:set_int("width", width) - chain_or_input.resample_effect:set_int("height", height) - elseif chain_or_input.resize_effect then - chain_or_input.resize_effect:set_int("width", width) - chain_or_input.resize_effect:set_int("height", height) +function set_scale_parameters_if_needed(scene_or_input, signals, signal_num, width, height) + if needs_scale(signals, signal_num, width, height) then + if hq then + scene_or_input.resample_effect:choose(ResampleEffect) -- High-quality resampling. + else + scene_or_input.resample_effect:choose(ResizeEffect) -- Low-quality resampling. + end + scene_or_input.resample_effect:set_int("width", width) + scene_or_input.resample_effect:set_int("height", height) + else + scene_or_input.resample_effect:disable() end + end -- API ENTRY POINT @@ -759,7 +605,7 @@ function transition_clicked(num, t) state.preview_signal_num == STATIC_SIGNAL_NUM) then start_transition(FADE_TRANSITION, t, 1.0) else - -- Fades involving SBS are ignored (we have no chain for it). + -- Fades involving SBS are ignored (we have no scene for it). end end end @@ -769,52 +615,69 @@ function channel_clicked(num) state.preview_signal_num = num end -function get_fade_chain(state, signals, t, width, height, input_resolution) - local input0_type = get_input_type(signals, state.transition_src_signal) - local input0_scale = needs_scale(signals, state.transition_src_signal, width, height) - local input1_type = get_input_type(signals, state.transition_dst_signal) - local input1_scale = needs_scale(signals, state.transition_dst_signal, width, height) - local chain = fade_chains[input0_type][input0_scale][input1_type][input1_scale][state.overlay_enabled][true] - local prepare = function() - if input0_type == "live" or input0_type == "livedeint" then - chain.input0.input:connect_signal(state.transition_src_signal) - end - if input0_type ~= "static" then - set_neutral_color_from_signal(state, chain.input0.wb_effect, state.transition_src_signal) - end - set_scale_parameters_if_needed(chain.input0, width, height) - if input1_type == "live" or input1_type == "livedeint" then - chain.input1.input:connect_signal(state.transition_dst_signal) - end - if input1_type ~= "static" then - set_neutral_color_from_signal(state, chain.input1.wb_effect, state.transition_dst_signal) - end - set_scale_parameters_if_needed(chain.input1, width, height) - local tt = calc_fade_progress(t, state.transition_start, state.transition_end) - - chain.mix_effect:set_float("strength_first", 1.0 - tt) - chain.mix_effect:set_float("strength_second", tt) - - -- The commentator output has no overlay on it. - local extra_alpha_factor = 1.0 - if not simple_signal_has_overlay(state.transition_src_signal) and - not simple_signal_has_overlay(state.transition_dst_signal) then - extra_alpha_factor = 0.0 - elseif not simple_signal_has_overlay(state.transition_src_signal) then - extra_alpha_factor = tt - elseif not simple_signal_has_overlay(state.transition_dst_signal) then - extra_alpha_factor = 1.0 - tt +function setup_fade_input(state, input, signals, signal_num, width, height) + if signal_num == STATIC_SIGNAL_NUM then + input.input:display(static_image) + input.wb_effect:disable() + + -- We assume this is already correctly scaled at load time. + input.resample_effect:disable() + else + input.input:display(signal_num) + input.wb_effect:enable() + set_neutral_color(input.wb_effect, state.neutral_colors[signal_num - INPUT0_SIGNAL_NUM + 1]) + + if (signals:get_width(signal_num) ~= width or signals:get_height(signal_num) ~= height) then + input.resample_effect:enable() + input.resample_effect:set_int("width", width) + input.resample_effect:set_int("height", height) + else + input.resample_effect:disable() end - prepare_overlay_live(state, chain, t, extra_alpha_factor) end - return chain.chain, prepare end --- SBS code (live_signal_num == SBS_SIGNAL_NUM, or in a transition to/from it). -function get_sbs_chain(state, signals) - local input0_type = get_input_type(signals, INPUT0_SIGNAL_NUM) - local input1_type = get_input_type(signals, INPUT4_SIGNAL_NUM) - return sbs_chains[input0_type][state.overlay_enabled][input1_type][true] +function get_fade_scene(state, signals, t, width, height, input_resolution) + local scene = fade_scene + setup_fade_input(state, scene.input0, signals, state.transition_src_signal, width, height) + setup_fade_input(state, scene.input1, signals, state.transition_dst_signal, width, height) + + local tt = calc_fade_progress(t, state.transition_start, state.transition_end) + scene.mix_effect:set_float("strength_first", 1.0 - tt) + scene.mix_effect:set_float("strength_second", tt) + + -- The commentator output has no overlay on it. + local extra_alpha_factor = 1.0 + if not simple_signal_has_overlay(state.transition_src_signal) and + not simple_signal_has_overlay(state.transition_dst_signal) then + extra_alpha_factor = 0.0 + elseif not simple_signal_has_overlay(state.transition_src_signal) then + extra_alpha_factor = tt + elseif not simple_signal_has_overlay(state.transition_dst_signal) then + extra_alpha_factor = 1.0 - tt + end + + -- If fading between two live inputs, the overlay is put on top. + -- If fading between the static picture and a live input, + -- the overlay is put on the live input. + scene.input0.overlay.overlay_effect:disable() + scene.input1.overlay.overlay_effect:disable() + scene.overlay.overlay_effect:disable() + if state.overlay_enabled then + local input0_live = (state.transition_src_signal ~= STATIC_SIGNAL_NUM) + local input1_live = (state.transition_dst_signal ~= STATIC_SIGNAL_NUM) + if input0_live and not input1_live then + scene.input0.overlay.overlay_effect:enable() + prepare_overlay_live(state, scene.input0, t, extra_alpha_factor) + elseif input1_live and not input0_live then + scene.input1.overlay.overlay_effect:enable() + prepare_overlay_live(state, scene.input1, t, extra_alpha_factor) + else + scene.overlay.overlay_effect:enable() + prepare_overlay_live(state, scene, t, extra_alpha_factor) + end + end + return scene.scene end function fetch_input_resolution(signals, signal_num) @@ -845,7 +708,7 @@ end local last_rate = 0.0 -- API ENTRY POINT --- Called every frame. Get the chain for displaying at input , +-- Called every frame. Get the scene for displaying at input , -- where 0 is live, 1 is preview, 2 is the first channel to display -- in the bottom bar, and so on up to num_channels()+1. t is the -- current time in seconds. width and height are the dimensions of @@ -855,20 +718,12 @@ local last_rate = 0.0 -- is basically an exposed InputState, which you can use to -- query for information about the signals at the point of the current -- frame. In particular, you can call get_width() and get_height() --- for any signal number, and use that to e.g. assist in chain selection. --- --- You should return two objects; the chain itself, and then a --- function (taking no parameters) that is run just before rendering. --- The function needs to call connect_signal on any inputs, so that --- it gets updated video data for the given frame. (You are allowed --- to switch which input your input is getting from between frames, --- but not calling connect_signal results in undefined behavior.) --- If you want to change any parameters in the chain, this is also --- the right place. +-- for any signal number, and use that to e.g. assist in scene selection. -- --- NOTE: The chain returned must be finalized with the Y'CbCr flag --- if and only if num==0. -function get_chain(num, t, width, height, signals) +-- You should return scene to use, after having set any parameters you +-- want to set (through set_int() etc.). The parameters will be snapshot +-- at return time and used during rendering. +function get_scene(num, t, width, height, signals) local input_resolution = {} for signal_num=0,(NUM_CAMERAS-1) do input_resolution[signal_num] = fetch_input_resolution(signals, signal_num) @@ -876,16 +731,6 @@ function get_chain(num, t, width, height, signals) input_resolution[iptv_video:get_signal_num()] = fetch_input_resolution(signals, iptv_video:get_signal_num()) last_resolution = input_resolution - -- Make a (semi-shallow) copy of the current state, so that the returned prepare function - -- is unaffected by state changes made by the UI before it is rendered. - local state_copy = {} - for key, value in pairs(state) do - state_copy[key] = value - end - for key, value in pairs(state.neutral_colors) do - state_copy.neutral_colors[key] = value - end - -- Save some CPU time if we're not having SBS on live. local new_rate if state.live_signal_num == SBS_SIGNAL_NUM or @@ -906,29 +751,27 @@ function get_chain(num, t, width, height, signals) finish_transitions(t) if state.transition_type == ZOOM_TRANSITION then -- Transition in or out of SBS. - local chain = get_sbs_chain(state_copy, signals) - local prepare = function() - prepare_sbs_chain(state_copy, chain, calc_zoom_progress(t), state_copy.transition_type, state_copy.transition_src_signal, state_copy.transition_dst_signal, width, height, input_resolution) - prepare_overlay_live(state_copy, chain, t, 1.0) - end - return chain.chain, prepare + sbs_scene.input0.overlay.overlay_effect:enable_if(state.overlay_enabled) + prepare_sbs_scene(state, sbs_scene, calc_zoom_progress(t), state.transition_type, state.transition_src_signal, state.transition_dst_signal, width, height, input_resolution) + prepare_overlay_live(state, sbs_scene, t, 1.0) + return sbs_scene.scene elseif state.transition_type == NO_TRANSITION and state.live_signal_num == SBS_SIGNAL_NUM then -- Static SBS view. - local chain = get_sbs_chain(state_copy, signals) - local prepare = function() - prepare_sbs_chain(state_copy, chain, 0.0, NO_TRANSITION, 0, SBS_SIGNAL_NUM, width, height, input_resolution) - prepare_overlay_live(state_copy, chain, t, 1.0) - end - return chain.chain, prepare + sbs_scene.input0.overlay.overlay_effect:enable_if(state.overlay_enabled) + prepare_sbs_scene(state, sbs_scene, 0.0, NO_TRANSITION, 0, SBS_SIGNAL_NUM, width, height, input_resolution) + prepare_overlay_live(state, sbs_scene, t, 1.0) + return sbs_scene.scene elseif state.transition_type == FADE_TRANSITION then - return get_fade_chain(state_copy, signals, t, width, height, input_resolution) + return get_fade_scene(state, signals, t, width, height, input_resolution) elseif is_plain_signal(state.live_signal_num) then - local input_type = get_input_type(signals, state.live_signal_num) - local input_scale = needs_scale(signals, state.live_signal_num, width, height) local overlay_really_enabled = state.overlay_enabled and simple_signal_has_overlay(state.live_signal_num) - local chain if state.stinger_in_progress then - chain = stinger_chains[input_type][input_scale][overlay_really_enabled][state.stinger_frame][true] + stinger_scene.overlay.overlay_effect:enable_if(overlay_really_enabled) + stinger_scene.input:display(state.live_signal_num) + set_scale_parameters_if_needed(stinger_scene, signals, state.live_signal_num, width, height) + set_neutral_color_from_signal(state, stinger_scene.wb_effect, state.live_signal_num) + + stinger_scene.stinger_input:display(stinger_images[state.stinger_frame]) state.stinger_frame = state.stinger_frame + 1 if state.stinger_frame >= 25 then state.stinger_in_progress = false @@ -944,24 +787,21 @@ function get_chain(num, t, width, height, signals) state.overlay_enabled = state.stinger_save_overlay end end - else - chain = simple_chains[input_type][input_scale][overlay_really_enabled][true] - end - local prepare = function() - if input_type ~= "video" then - chain.input:connect_signal(state.live_signal_num) + + if overlay_really_enabled then + prepare_overlay_live(state, stinger_scene, t, 1.0) end - set_neutral_color_from_signal(state_copy, chain.wb_effect, state.live_signal_num) - set_scale_parameters_if_needed(chain, width, height) - prepare_overlay_live(state_copy, chain, t, 1.0) + return stinger_scene.scene + else + simple_scene.overlay.overlay_effect:enable_if(overlay_really_enabled) + simple_scene.input:display(state.live_signal_num) + set_scale_parameters_if_needed(simple_scene, signals, state.live_signal_num, width, height) + set_neutral_color_from_signal(state, simple_scene.wb_effect, state.live_signal_num) + prepare_overlay_live(state, simple_scene, t, 1.0) + return simple_scene.scene end - return chain.chain, prepare elseif state.live_signal_num == STATIC_SIGNAL_NUM then -- Static picture. - local chain = static_chains[true] - local prepare = function() - prepare_overlay_live(state_copy, chain, t, 1.0) -- FIXME: Should this ever be here? - end - return chain.chain, prepare + return static_scene else assert(false) end @@ -987,40 +827,23 @@ function get_chain(num, t, width, height, signals) -- Individual preview inputs (usually without overlay). if is_plain_signal(num - 2) then local signal_num = num - 2 - local input_type = get_input_type(signals, signal_num) - local input_scale = needs_scale(signals, signal_num, width, height) - local chain = simple_chains[input_type][input_scale][show_overlay][false] - local prepare = function() - if input_type ~= "video" then - chain.input:connect_signal(signal_num) - end - set_neutral_color(chain.wb_effect, state_copy.neutral_colors[signal_num + 1]) - set_scale_parameters_if_needed(chain, width, height) - prepare_overlay_static(chain, t) - end - return chain.chain, prepare + simple_scene.overlay.overlay_effect:enable_if(show_overlay) + simple_scene.input:display(signal_num) + set_scale_parameters_if_needed(simple_scene, signals, signal_num, width, height) + set_neutral_color_from_signal(state, simple_scene.wb_effect, signal_num) + prepare_overlay_static(simple_scene, t) + return simple_scene.scene end if num == SBS_SIGNAL_NUM + 2 then - local input0_type = get_input_type(signals, INPUT0_SIGNAL_NUM) - local input1_type = get_input_type(signals, INPUT4_SIGNAL_NUM) - local chain = sbs_chains[input0_type][show_overlay][input1_type][false] - local prepare = function() - prepare_sbs_chain(state_copy, chain, 0.0, NO_TRANSITION, 0, SBS_SIGNAL_NUM, width, height, input_resolution) - end - return chain.chain, prepare + sbs_scene.input0.overlay.overlay_effect:enable_if(show_overlay) + prepare_sbs_scene(state, sbs_scene, 0.0, NO_TRANSITION, 0, SBS_SIGNAL_NUM, width, height, input_resolution) + return sbs_scene.scene end if num == STATIC_SIGNAL_NUM + 2 then - local chain = static_chains[false] - local prepare = function() - prepare_overlay_static(chain, t) - end - return chain.chain, prepare + return static_scene end if num == OVERLAY_SIGNAL_NUM + 2 then - local prepare = function() --- prepare_overlay(overlay_chain_lq, t) - end - return overlay_chain_lq, prepare + return overlay_scene_lq end end @@ -1053,11 +876,11 @@ function pos_from_top_left(x, y, width, height, screen_width, screen_height) } end -function prepare_sbs_chain(state, chain, t, transition_type, src_signal, dst_signal, screen_width, screen_height, input_resolution) - chain.input0.input:connect_signal(0) - chain.input1.input:connect_signal(4) - set_neutral_color(chain.input0.wb_effect, state.neutral_colors[1]) - set_neutral_color(chain.input1.wb_effect, state.neutral_colors[5]) +function prepare_sbs_scene(state, scene, t, transition_type, src_signal, dst_signal, screen_width, screen_height, input_resolution) + scene.input0.input:display(0) + scene.input1.input:display(4) + set_neutral_color(scene.input0.wb_effect, state.neutral_colors[1]) + set_neutral_color(scene.input1.wb_effect, state.neutral_colors[5]) -- Both inputs are the same size (true side-by-side). local pos0 = pos_from_top_left(1280 - 616 - 16, 186, 616, 347, screen_width, screen_height) @@ -1089,8 +912,8 @@ function prepare_sbs_chain(state, chain, t, transition_type, src_signal, dst_sig end -- NOTE: input_resolution is not 1-indexed, unlike usual Lua arrays. - place_rectangle_with_affine(chain.input0.resample_effect, chain.input0.resize_effect, chain.input0.padding_effect, pos0, affine_param, screen_width, screen_height, input_resolution[0].width, input_resolution[0].height) - place_rectangle_with_affine(chain.input1.resample_effect, chain.input1.resize_effect, chain.input1.padding_effect, pos1, affine_param, screen_width, screen_height, input_resolution[1].width, input_resolution[1].height) + place_rectangle_with_affine(scene.input0, pos0, affine_param, screen_width, screen_height, input_resolution[0].width, input_resolution[0].height) + place_rectangle_with_affine(scene.input1, pos1, affine_param, screen_width, screen_height, input_resolution[1].width, input_resolution[1].height) end -- Find the transformation that changes the first rectangle to the second one. @@ -1105,40 +928,34 @@ function find_affine_param(a, b) } end -function place_rectangle_with_affine(resample_effect, resize_effect, padding_effect, pos, aff, screen_width, screen_height, input_width, input_height) +function place_rectangle_with_affine(input, pos, aff, screen_width, screen_height, input_width, input_height) local x0 = pos.x0 * aff.sx + aff.tx local x1 = pos.x1 * aff.sx + aff.tx local y0 = pos.y0 * aff.sy + aff.ty local y1 = pos.y1 * aff.sy + aff.ty - place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height) + place_rectangle(input, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height) end -function place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height) - local srcx0 = 0.0 - local srcx1 = 1.0 - local srcy0 = 0.0 - local srcy1 = 1.0 - - padding_effect:set_int("width", screen_width) - padding_effect:set_int("height", screen_height) +function place_rectangle(input, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height, hq) + input.padding_effect:set_int("width", screen_width) + input.padding_effect:set_int("height", screen_height) -- Cull. if x0 > screen_width or x1 < 0.0 or y0 > screen_height or y1 < 0.0 then - if resample_effect ~= nil then - resample_effect:set_int("width", 1) - resample_effect:set_int("height", 1) - resample_effect:set_float("zoom_x", screen_width) - resample_effect:set_float("zoom_y", screen_height) - else - resize_effect:set_int("width", 1) - resize_effect:set_int("height", 1) - end - padding_effect:set_int("left", screen_width + 100) - padding_effect:set_int("top", screen_height + 100) + input.resample_switcher:choose(ResizeEffect) -- Low-quality resizing. + input.resize_effect:set_int("width", 1) + input.resize_effect:set_int("height", 1) + input.padding_effect:set_int("left", screen_width + 100) + input.padding_effect:set_int("top", screen_height + 100) return end + local srcx0 = 0.0 + local srcx1 = 1.0 + local srcy0 = 0.0 + local srcy1 = 1.0 + -- Clip. if x0 < 0 then srcx0 = -x0 / (x1 - x0) @@ -1157,8 +974,10 @@ function place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, y1 = screen_height end - if resample_effect ~= nil then + if hq then -- High-quality resampling. + input.resample_switcher:choose(ResampleEffect) + local x_subpixel_offset = x0 - math.floor(x0) local y_subpixel_offset = y0 - math.floor(y0) @@ -1166,46 +985,54 @@ function place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, -- and then add an extra pixel so we have some leeway for the border. local width = math.ceil(x1 - x0) + 1 local height = math.ceil(y1 - y0) + 1 - resample_effect:set_int("width", width) - resample_effect:set_int("height", height) + input.resample_effect:set_int("width", width) + input.resample_effect:set_int("height", height) -- Correct the discrepancy with zoom. (This will leave a small -- excess edge of pixels and subpixels, which we'll correct for soon.) local zoom_x = (x1 - x0) / (width * (srcx1 - srcx0)) local zoom_y = (y1 - y0) / (height * (srcy1 - srcy0)) - resample_effect:set_float("zoom_x", zoom_x) - resample_effect:set_float("zoom_y", zoom_y) - resample_effect:set_float("zoom_center_x", 0.0) - resample_effect:set_float("zoom_center_y", 0.0) + input.resample_effect:set_float("zoom_x", zoom_x) + input.resample_effect:set_float("zoom_y", zoom_y) + input.resample_effect:set_float("zoom_center_x", 0.0) + input.resample_effect:set_float("zoom_center_y", 0.0) -- Padding must also be to a whole-pixel offset. - padding_effect:set_int("left", math.floor(x0)) - padding_effect:set_int("top", math.floor(y0)) + input.padding_effect:set_int("left", math.floor(x0)) + input.padding_effect:set_int("top", math.floor(y0)) -- Correct _that_ discrepancy by subpixel offset in the resampling. - resample_effect:set_float("left", srcx0 * input_width - x_subpixel_offset / zoom_x) - resample_effect:set_float("top", srcy0 * input_height - y_subpixel_offset / zoom_y) + input.resample_effect:set_float("left", srcx0 * input_width - x_subpixel_offset / zoom_x) + input.resample_effect:set_float("top", srcy0 * input_height - y_subpixel_offset / zoom_y) -- Finally, adjust the border so it is exactly where we want it. - padding_effect:set_float("border_offset_left", x_subpixel_offset) - padding_effect:set_float("border_offset_right", x1 - (math.floor(x0) + width)) - padding_effect:set_float("border_offset_top", y_subpixel_offset) - padding_effect:set_float("border_offset_bottom", y1 - (math.floor(y0) + height)) + input.padding_effect:set_float("border_offset_left", x_subpixel_offset) + input.padding_effect:set_float("border_offset_right", x1 - (math.floor(x0) + width)) + input.padding_effect:set_float("border_offset_top", y_subpixel_offset) + input.padding_effect:set_float("border_offset_bottom", y1 - (math.floor(y0) + height)) else -- Lower-quality simple resizing. + input.resample_switcher:choose(ResizeEffect) + local width = round(x1 - x0) local height = round(y1 - y0) - resize_effect:set_int("width", width) - resize_effect:set_int("height", height) + input.resize_effect:set_int("width", width) + input.resize_effect:set_int("height", height) -- Padding must also be to a whole-pixel offset. - padding_effect:set_int("left", math.floor(x0)) - padding_effect:set_int("top", math.floor(y0)) + input.padding_effect:set_int("left", math.floor(x0)) + input.padding_effect:set_int("top", math.floor(y0)) + + -- No subpixel stuff. + input.padding_effect:set_float("border_offset_left", 0.0) + input.padding_effect:set_float("border_offset_right", 0.0) + input.padding_effect:set_float("border_offset_top", 0.0) + input.padding_effect:set_float("border_offset_bottom", 0.0) end end -function prepare_overlay_live(state, chain, t, extra_alpha_factor) - if chain.overlay then +function prepare_overlay_live(state, scene, t, extra_alpha_factor) + if scene.overlay then local tt = calc_fade_progress(t, state.overlay_transition_start, state.overlay_transition_end) local overlay_alpha = state.overlay_alpha_src + tt * (state.overlay_alpha_dst - state.overlay_alpha_src) overlay_alpha = overlay_alpha * extra_alpha_factor @@ -1214,13 +1041,13 @@ function prepare_overlay_live(state, chain, t, extra_alpha_factor) state.overlay_enabled = false -- Takes effect next frame. -- print("Turning off overlay") end - chain.overlay.multiply_effect:set_vec4("factor", overlay_alpha, overlay_alpha, overlay_alpha, overlay_alpha) + scene.overlay.multiply_effect:set_vec4("factor", overlay_alpha, overlay_alpha, overlay_alpha, overlay_alpha) end end -function prepare_overlay_static(chain) - if chain.overlay then - chain.overlay.multiply_effect:set_vec4("factor", 1.0, 1.0, 1.0, 1.0) +function prepare_overlay_static(scene) + if scene.overlay then + scene.overlay.multiply_effect:set_vec4("factor", 1.0, 1.0, 1.0, 1.0) end end -- 2.39.2