]> git.sesse.net Git - nageru/commitdiff
Transparently send signals through a deinterlacer as needed.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 23 Dec 2015 13:35:58 +0000 (14:35 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 23 Dec 2015 13:35:58 +0000 (14:35 +0100)
bmusb
mixer.cpp
pbo_frame_allocator.cpp
pbo_frame_allocator.h
theme.cpp
theme.h
theme.lua

diff --git a/bmusb b/bmusb
index 9ada4d5213c6935c3dc2fdeb34938a375f817428..2a06d276da04a363f0a7d4043b319229b0a5fd7f 160000 (submodule)
--- a/bmusb
+++ b/bmusb
@@ -1 +1 @@
-Subproject commit 9ada4d5213c6935c3dc2fdeb34938a375f817428
+Subproject commit 2a06d276da04a363f0a7d4043b319229b0a5fd7f
index 02f4dd29c33e3afbf1b09fb8f75cfa528d195fb1..74d2bf6f19b79466f1e8d7858563e0f7d64fdd77 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -342,9 +342,8 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
        unsigned num_fields = interlaced ? 2 : 1;
        timespec frame_upload_start;
        if (interlaced) {
-               // NOTE: This isn't deinterlacing. This is just sending the two fields along
-               // as separate frames without considering anything like the half-field offset.
-               // We'll need to add a proper deinterlacer on the receiving side to get this right.
+               // Send the two fields along as separate frames; the other side will need to add
+               // a deinterlacer to actually get this right.
                assert(height % 2 == 0);
                height /= 2;
                assert(frame_length % 2 == 0);
@@ -352,6 +351,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                num_fields = 2;
                clock_gettime(CLOCK_MONOTONIC, &frame_upload_start);
        }
+       userdata->last_interlaced = interlaced;
        RefCountedFrame new_frame(video_frame);
 
        // Upload the textures.
index e1106334be7fc7c209ae431f19260052e1444eee..f64ea5ff519f0d6ce4ea3a0b0eab040a14c309aa 100644 (file)
@@ -43,6 +43,7 @@ PBOFrameAllocator::PBOFrameAllocator(size_t frame_size, GLuint width, GLuint hei
                userdata[i].last_height[0] = height;
                userdata[i].last_width[1] = 0;
                userdata[i].last_height[1] = 0;
+               userdata[i].last_interlaced = false;
                for (unsigned field = 0; field < 2; ++field) {
                        glBindTexture(GL_TEXTURE_2D, userdata[i].tex_y[field]);
                        check_error();
index 321842bcff08ee5d7477433dcd1f90ffe9d1b1fe..fa75f8a615b42bec7f2e1fbbd9babe78475519c1 100644 (file)
@@ -32,6 +32,7 @@ public:
                // The second set is only used for the second field of interlaced inputs.
                GLuint tex_y[2], tex_cbcr[2];
                GLuint last_width[2], last_height[2];
+               bool last_interlaced;
        };
 
 private:
index defdca171b54448bba2ddce336260ca3e2a95dbe..166dcb577dfa0bd6de2ef45378b99b01c527bede 100644 (file)
--- a/theme.cpp
+++ b/theme.cpp
@@ -12,6 +12,7 @@
 #include <movit/padding_effect.h>
 #include <movit/resample_effect.h>
 #include <movit/resize_effect.h>
+#include <movit/util.h>
 #include <movit/white_balance_effect.h>
 #include <movit/ycbcr.h>
 #include <movit/ycbcr_input.h>
@@ -44,6 +45,7 @@ struct InputStateInfo {
        InputStateInfo(const InputState& input_state);
 
        unsigned last_width[MAX_CARDS], last_height[MAX_CARDS];
+       bool last_interlaced[MAX_CARDS];
 };
 
 InputStateInfo::InputStateInfo(const InputState &input_state)
@@ -52,11 +54,13 @@ InputStateInfo::InputStateInfo(const InputState &input_state)
                BufferedFrame frame = input_state.buffered_frames[signal_num][0];
                if (frame.frame == nullptr) {
                        last_width[signal_num] = last_height[signal_num] = 0;
+                       last_interlaced[signal_num] = false;
                        continue;
                }
                const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata;
                last_width[signal_num] = userdata->last_width[frame.field_number];
                last_height[signal_num] = userdata->last_height[frame.field_number];
+               last_interlaced[signal_num] = userdata->last_interlaced;
        }
 }
 
@@ -146,11 +150,12 @@ int EffectChain_new(lua_State* L)
 
 int EffectChain_add_live_input(lua_State* L)
 {
-       assert(lua_gettop(L) == 2);
+       assert(lua_gettop(L) == 3);
        Theme *theme = get_theme_updata(L);
        EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain");
        bool override_bounce = checkbool(L, 2);
-       return wrap_lua_object<LiveInputWrapper>(L, "LiveInputWrapper", theme, chain, override_bounce);
+       bool deinterlace = checkbool(L, 3);
+       return wrap_lua_object<LiveInputWrapper>(L, "LiveInputWrapper", theme, chain, override_bounce, deinterlace);
 }
 
 int EffectChain_add_effect(lua_State* L)
@@ -171,7 +176,7 @@ int EffectChain_add_effect(lua_State* L)
                for (int idx = 3; idx <= lua_gettop(L); ++idx) {
                        if (luaL_testudata(L, idx, "LiveInputWrapper")) {
                                LiveInputWrapper *input = (LiveInputWrapper *)lua_touserdata(L, idx);
-                               inputs.push_back(input->get_input());
+                               inputs.push_back(input->get_effect());
                        } else {
                                inputs.push_back(get_effect(L, idx));
                        }
@@ -311,6 +316,16 @@ int InputStateInfo_get_height(lua_State* L)
        return 1;
 }
 
+int InputStateInfo_get_interlaced(lua_State* L)
+{
+       assert(lua_gettop(L) == 2);
+       InputStateInfo *input_state_info = get_input_state_info(L, 1);
+       Theme *theme = get_theme_updata(L);
+       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
+       lua_pushboolean(L, input_state_info->last_interlaced[signal_num]);
+       return 1;
+}
+
 int Effect_set_float(lua_State *L)
 {
        assert(lua_gettop(L) == 3);
@@ -456,13 +471,15 @@ const luaL_Reg MixEffect_funcs[] = {
 const luaL_Reg InputStateInfo_funcs[] = {
        { "get_width", InputStateInfo_get_width },
        { "get_height", InputStateInfo_get_height },
+       { "get_interlaced", InputStateInfo_get_interlaced },
        { NULL, NULL }
 };
 
 }  // namespace
 
-LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool override_bounce)
-       : theme(theme)
+LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool override_bounce, bool deinterlace)
+       : theme(theme),
+         deinterlace(deinterlace)
 {
        ImageFormat inout_format;
        inout_format.color_space = COLORSPACE_sRGB;
@@ -490,12 +507,34 @@ LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool overri
        input_ycbcr_format.luma_coefficients = YCBCR_REC_709;
        input_ycbcr_format.full_range = false;
 
-       if (override_bounce) {
-               input = new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
+       unsigned num_inputs;
+       if (deinterlace) {
+               deinterlace_effect = new movit::DeinterlaceEffect();
+
+               // As per the comments in deinterlace_effect.h, we turn this off.
+               // The most likely interlaced input for us is either a camera
+               // (where it's fine to turn it off) or a laptop (where it _should_
+               // be turned off).
+               CHECK(deinterlace_effect->set_int("enable_spatial_interlacing_check", 0));
+
+               num_inputs = deinterlace_effect->num_inputs();
+               assert(num_inputs == FRAME_HISTORY_LENGTH);
        } else {
-               input = new YCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
+               num_inputs = 1;
+       }
+       for (unsigned i = 0; i < num_inputs; ++i) {
+               if (override_bounce) {
+                       inputs.push_back(new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR));
+               } else {
+                       inputs.push_back(new YCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR));
+               }
+               chain->add_input(inputs.back());
+       }
+
+       if (deinterlace) {
+               vector<Effect *> reverse_inputs(inputs.rbegin(), inputs.rend());
+               chain->add_effect(deinterlace_effect, reverse_inputs);
        }
-       chain->add_input(input);
 }
 
 void LiveInputWrapper::connect_signal(int signal_num)
@@ -507,13 +546,47 @@ void LiveInputWrapper::connect_signal(int signal_num)
 
        signal_num = theme->map_signal(signal_num);
 
-       BufferedFrame frame = theme->input_state->buffered_frames[signal_num][0];
-       const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata;
+       BufferedFrame first_frame = theme->input_state->buffered_frames[signal_num][0];
+       if (first_frame.frame == nullptr) {
+               // No data yet.
+               return;
+       }
+       unsigned width, height;
+       {
+               const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)first_frame.frame->userdata;
+               width = userdata->last_width[first_frame.field_number];
+               height = userdata->last_height[first_frame.field_number];
+       }
 
-       input->set_texture_num(0, userdata->tex_y[frame.field_number]);
-       input->set_texture_num(1, userdata->tex_cbcr[frame.field_number]);
-       input->set_width(userdata->last_width[frame.field_number]);
-       input->set_height(userdata->last_height[frame.field_number]);
+       BufferedFrame last_good_frame = first_frame;
+       for (unsigned i = 0; i < inputs.size(); ++i) {
+               BufferedFrame frame = theme->input_state->buffered_frames[signal_num][i];
+               if (frame.frame == nullptr) {
+                       // Not enough data; reuse last frame (well, field).
+                       // This is suboptimal, but we have nothing better.
+                       frame = last_good_frame;
+               }
+               const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata;
+
+               if (userdata->last_width[frame.field_number] != width ||
+                   userdata->last_height[frame.field_number] != height) {
+                       // Resolution changed; reuse last frame/field.
+                       frame = last_good_frame;
+                       userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata;
+               }
+
+               inputs[i]->set_texture_num(0, userdata->tex_y[frame.field_number]);
+               inputs[i]->set_texture_num(1, userdata->tex_cbcr[frame.field_number]);
+               inputs[i]->set_width(userdata->last_width[frame.field_number]);
+               inputs[i]->set_height(userdata->last_height[frame.field_number]);
+
+               last_good_frame = frame;
+       }
+
+       if (deinterlace) {
+               BufferedFrame frame = theme->input_state->buffered_frames[signal_num][0];
+               CHECK(deinterlace_effect->set_int("current_field_position", frame.field_number));
+       }
 }
 
 Theme::Theme(const char *filename, ResourcePool *resource_pool, unsigned num_cards)
diff --git a/theme.h b/theme.h
index b60df1490190059fb0a0900ed543aab6fa0b7266..a3bc8ea16844439f1d25bf988238701d87448e64 100644 (file)
--- a/theme.h
+++ b/theme.h
@@ -5,6 +5,7 @@
 #include <lauxlib.h>
 #include <lua.h>
 #include <movit/effect_chain.h>
+#include <movit/deinterlace_effect.h>
 #include <movit/ycbcr_input.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -78,17 +79,23 @@ private:
 
 class LiveInputWrapper {
 public:
-       LiveInputWrapper(Theme *theme, movit::EffectChain *chain, bool override_bounce);
+       LiveInputWrapper(Theme *theme, movit::EffectChain *chain, bool override_bounce, bool deinterlace);
 
        void connect_signal(int signal_num);
-       movit::YCbCrInput *get_input() const
+       movit::Effect *get_effect() const
        {
-               return input;
+               if (deinterlace) {
+                       return deinterlace_effect;
+               } else {
+                       return inputs[0];
+               }
        }
 
 private:
        Theme *theme;  // Not owned by us.
-       movit::YCbCrInput *input;  // Owned by the chain.
+       std::vector<movit::YCbCrInput *> inputs;  // Multiple ones if deinterlacing. Owned by the chain.
+       movit::Effect *deinterlace_effect = nullptr;  // Owned by the chain.
+       bool deinterlace;
 };
 
 #endif  // !defined(_THEME_H)
index eb5573315e357355c1a21fa59ded979c8c357998..e6338087fbaf2ab00ae9b8aec3d8886f530bf60d 100644 (file)
--- a/theme.lua
+++ b/theme.lua
@@ -34,12 +34,12 @@ local STATIC_SIGNAL_NUM = 3
 local FADE_SIGNAL_NUM = 4
 
 -- The main live chain.
-function make_sbs_chain(hq)
+function make_sbs_chain(input0_deint, input1_deint, hq)
        local chain = EffectChain.new(16, 9)
-       local input0 = chain:add_live_input(true)
+       local input0 = chain:add_live_input(not input0_deint, input0_deint)  -- Override bounce only if not deinterlacing.
        input0:connect_signal(0)
        local input0_wb_effect = chain:add_effect(WhiteBalanceEffect.new())
-       local input1 = chain:add_live_input(true)
+       local input1 = chain:add_live_input(not input1_deint, input1_deint)
        input1:connect_signal(1)
        local input1_wb_effect = chain:add_effect(WhiteBalanceEffect.new())
 
@@ -85,19 +85,29 @@ function make_sbs_chain(hq)
        }
 end
 
-local main_chain_hq = make_sbs_chain(true)
-local main_chain_lq = make_sbs_chain(false)
+-- Make all possible combinations of side-by-side chains.
+local sbs_chains = {}
+for input0_type, input0_deint in pairs({live = false, livedeint = true}) do
+       sbs_chains[input0_type] = {}
+       for input1_type, input1_deint in pairs({live = false, livedeint = true}) do
+               sbs_chains[input0_type][input1_type] = {}
+               for _, hq in pairs({true, false}) do
+                       sbs_chains[input0_type][input1_type][hq] =
+                               make_sbs_chain(input0_deint, input1_deint, hq)
+               end
+       end
+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, input1_live, hq)
+function make_fade_chain(input0_live, input0_deint, input1_live, input1_deint, hq)
        local chain = EffectChain.new(16, 9)
 
        local input0, wb0_effect, input0_last, input1, wb1_effect, input1_last
 
        if input0_live then
-               input0 = chain:add_live_input(true)
+               input0 = chain:add_live_input(false, input0_deint)
                wb0_effect = chain:add_effect(WhiteBalanceEffect.new())
                input0:connect_signal(0)
                input0_last = wb0_effect
@@ -107,7 +117,7 @@ function make_fade_chain(input0_live, input1_live, hq)
        end
 
        if input1_live then
-               input1 = chain:add_live_input(true)
+               input1 = chain:add_live_input(false, input1_deint)
                wb1_effect = chain:add_effect(WhiteBalanceEffect.new())
                input1:connect_signal(1)
                input1_last = wb1_effect
@@ -135,29 +145,43 @@ end
 
 -- Chains to fade between two inputs, in various configurations.
 local fade_chains = {}
-for input0_type, input0_live in pairs({static = false, live = true}) do
+for input0_type, input0_live in pairs({static = false, live = true, livedeint = true}) do
+       local input0_deint = (input0_live == "livedeint")
        fade_chains[input0_type] = {}
-       for input1_type, input1_live in pairs({static = false, live = true}) do
+       for input1_type, input1_live in pairs({static = false, live = true, livedeint = true}) do
+               local input1_deint = (input1_live == "livedeint")
                fade_chains[input0_type][input1_type] = {}
                for _, hq in pairs({true, false}) do
-                       fade_chains[input0_type][input1_type][hq] = make_fade_chain(input0_live, input1_live, hq)
+                       fade_chains[input0_type][input1_type][hq] =
+                               make_fade_chain(input0_live, input0_deint, input1_live, input1_deint, hq)
                end
        end
 end
 
--- A chain to show a single input on screen (HQ version).
-local simple_chain_hq = EffectChain.new(16, 9)
-local simple_chain_hq_input = simple_chain_hq:add_live_input(true)
-simple_chain_hq_input:connect_signal(0)  -- First input card. Can be changed whenever you want.
-local simple_chain_hq_wb_effect = simple_chain_hq:add_effect(WhiteBalanceEffect.new())
-simple_chain_hq:finalize(true)
+-- A chain to show a single input on screen.
+function make_simple_chain(input_deint, hq)
+       local chain = EffectChain.new(16, 9)
 
--- A chain to show a single input on screen (LQ version).
-local simple_chain_lq = EffectChain.new(16, 9)
-local simple_chain_lq_input = simple_chain_lq:add_live_input(true)
-simple_chain_lq_input:connect_signal(0)  -- First input card. Can be changed whenever you want.
-local simple_chain_lq_wb_effect = simple_chain_lq:add_effect(WhiteBalanceEffect.new())
-simple_chain_lq:finalize(false)
+       local input = chain:add_live_input(false, input_deint)
+       input:connect_signal(0)  -- First input card. Can be changed whenever you want.
+       local wb_effect = chain:add_effect(WhiteBalanceEffect.new())
+       chain:finalize(hq)
+
+       return {
+               chain = chain,
+               input = input,
+               wb_effect = wb_effect
+       }
+end
+
+-- Make all possible combinations of single-input chains.
+local simple_chains = {}
+for input_type, input_deint in pairs({live = false, livedeint = true}) do
+       simple_chains[input_type] = {}
+       for _, hq in pairs({true, false}) do
+               simple_chains[input_type][hq] = make_simple_chain(input_deint, hq)
+       end
+end
 
 -- A chain to show a single static picture on screen (HQ version).
 local static_chain_hq = EffectChain.new(16, 9)
@@ -169,6 +193,17 @@ local static_chain_lq = EffectChain.new(16, 9)
 local static_chain_lq_input = static_chain_lq:add_effect(ImageInput.new("bg.jpeg"))
 static_chain_lq:finalize(false)
 
+-- 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 signals:get_interlaced(signal_num) then
+               return "livedeint"
+       else
+               return "live"
+       end
+end
+
 -- Returns the number of outputs in addition to the live (0) and preview (1).
 -- Called only once, at the start of the program.
 function num_channels()
@@ -363,18 +398,20 @@ end
 function get_chain(num, t, width, height, signals)
        if num == 0 then  -- Live.
                if live_signal_num == INPUT0_SIGNAL_NUM or live_signal_num == INPUT1_SIGNAL_NUM then  -- Plain input.
+                       local input_type = get_input_type(signals, live_signal_num)
+                       local chain = simple_chains[input_type][true]
                        prepare = function()
-                               simple_chain_hq_input:connect_signal(live_signal_num)
-                               set_neutral_color_from_signal(simple_chain_hq_wb_effect, live_signal_num)
+                               chain.input:connect_signal(live_signal_num)
+                               set_neutral_color_from_signal(chain.wb_effect, live_signal_num)
                        end
-                       return simple_chain_hq, prepare
+                       return chain.chain, prepare
                elseif live_signal_num == STATIC_SIGNAL_NUM then  -- Static picture.
                        prepare = function()
                        end
                        return static_chain_hq, prepare
                elseif live_signal_num == FADE_SIGNAL_NUM then  -- Fade.
-                       local input0_type = (fade_src_signal == STATIC_SIGNAL_NUM) and "static" or "live"
-                       local input1_type = (fade_dst_signal == STATIC_SIGNAL_NUM) and "static" or "live"
+                       local input0_type = get_input_type(signals, fade_src_signal)
+                       local input1_type = get_input_type(signals, fade_dst_signal)
                        local chain = fade_chains[input0_type][input1_type][true]
                        prepare = function()
                                if input0_type == "live" then
@@ -394,50 +431,63 @@ function get_chain(num, t, width, height, signals)
                end
 
                -- SBS code (live_signal_num == SBS_SIGNAL_NUM).
+               local input0_type = get_input_type(signals, INPUT0_SIGNAL_NUM)
+               local input1_type = get_input_type(signals, INPUT1_SIGNAL_NUM)
                if t > transition_end and zoom_dst == 1.0 then
                        -- Special case: Show only the single image on screen.
+                       local chain = simple_chains[input0_type][true]
                        prepare = function()
-                               simple_chain_hq_input:connect_signal(INPUT0_SIGNAL_NUM)
-                               set_neutral_color(simple_chain_hq_wb_effect, input0_neutral_color)
+                               chain.input:connect_signal(INPUT0_SIGNAL_NUM)
+                               set_neutral_color(chain.wb_effect, input0_neutral_color)
                        end
-                       return simple_chain_hq, prepare
+                       return chain.chain, prepare
                end
+               local chain = sbs_chains[input0_type][input1_type][true]
                prepare = function()
                        if t < transition_start then
-                               prepare_sbs_chain(main_chain_hq, zoom_src, width, height)
+                               prepare_sbs_chain(chain, zoom_src, width, height)
                        elseif t > transition_end then
-                               prepare_sbs_chain(main_chain_hq, zoom_dst, width, height)
+                               prepare_sbs_chain(chain, zoom_dst, width, height)
                        else
                                local tt = (t - transition_start) / (transition_end - transition_start)
                                -- Smooth it a bit.
                                tt = math.sin(tt * 3.14159265358 * 0.5)
-                               prepare_sbs_chain(main_chain_hq, zoom_src + (zoom_dst - zoom_src) * tt, width, height)
+                               prepare_sbs_chain(chain, zoom_src + (zoom_dst - zoom_src) * tt, width, height)
                        end
                end
-               return main_chain_hq.chain, prepare
+               return chain.chain, prepare
        end
        if num == 1 then  -- Preview.
                num = preview_signal_num + 2
        end
+
+       -- Individual preview inputs.
        if num == INPUT0_SIGNAL_NUM + 2 then
+               local input_type = get_input_type(signals, INPUT0_SIGNAL_NUM)
+               local chain = simple_chains[input_type][false]
                prepare = function()
-                       simple_chain_lq_input:connect_signal(0)
-                       set_neutral_color(simple_chain_lq_wb_effect, input0_neutral_color)
+                       chain.input:connect_signal(INPUT0_SIGNAL_NUM)
+                       set_neutral_color(chain.wb_effect, input0_neutral_color)
                end
-               return simple_chain_lq, prepare
+               return chain.chain, prepare
        end
        if num == INPUT1_SIGNAL_NUM + 2 then
+               local input_type = get_input_type(signals, INPUT1_SIGNAL_NUM)
+               local chain = simple_chains[input_type][false]
                prepare = function()
-                       simple_chain_lq_input:connect_signal(1)
-                       set_neutral_color(simple_chain_lq_wb_effect, input1_neutral_color)
+                       chain.input:connect_signal(INPUT1_SIGNAL_NUM)
+                       set_neutral_color(chain.wb_effect, input1_neutral_color)
                end
-               return simple_chain_lq, prepare
+               return chain.chain, prepare
        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, INPUT1_SIGNAL_NUM)
+               local chain = sbs_chains[input0_type][input1_type][false]
                prepare = function()
-                       prepare_sbs_chain(main_chain_lq, 0.0, width, height)
+                       prepare_sbs_chain(chain, 0.0, width, height)
                end
-               return main_chain_lq.chain, prepare
+               return chain.chain, prepare
        end
        if num == STATIC_SIGNAL_NUM + 2 then
                prepare = function()