using namespace movit;
using namespace std;
-bool display(Block *block, lua_State *L, int idx);
+static bool display(Block *block, lua_State *L, int idx);
EffectType current_type(const Block *block)
{
return -1;
}
+string get_declaration_point(lua_State *L)
+{
+ lua_Debug ar;
+ lua_getstack(L, 1, &ar);
+ lua_getinfo(L, "nSl", &ar);
+ char buf[256];
+ snprintf(buf, sizeof(buf), "%s:%d", ar.source, ar.currentline);
+ return buf;
+}
+
Scene::Scene(Theme *theme, float aspect_nom, float aspect_denom)
: theme(theme), aspect_nom(aspect_nom), aspect_denom(aspect_denom), resource_pool(theme->get_resource_pool()) {}
{
assert(chains.size() > 0);
assert(chains.size() % 2 == 0);
- size_t chain_number = compute_chain_number_for_block(blocks.size() - 1);
+ bitset<256> disabled = find_disabled_blocks(size_t(-1));
+
+ size_t chain_number = compute_chain_number_for_block(blocks.size() - 1, disabled);
assert(chain_number < chains.size() / 2);
if (is_main_chain) {
chain_number += chains.size() / 2;
return chain_number;
}
-size_t Scene::compute_chain_number_for_block(size_t block_idx) const
+size_t Scene::compute_chain_number_for_block(size_t block_idx, const bitset<256> &disabled) const
{
Block *block = blocks[block_idx];
size_t chain_number;
+
+ size_t currently_chosen_alternative;
+ if (disabled.test(block_idx)) {
+ // It doesn't matter, so pick the canonical choice
+ // (this is the only one that is actually instantiated).
+ currently_chosen_alternative = block->canonical_alternative;
+ } else {
+ currently_chosen_alternative = block->currently_chosen_alternative;
+ }
+ assert(currently_chosen_alternative < block->alternatives.size());
+
if (block_idx == 0) {
assert(block->cardinality_base == 1);
- chain_number = block->currently_chosen_alternative;
+ chain_number = currently_chosen_alternative;
} else {
- chain_number = compute_chain_number_for_block(block_idx - 1) + block->cardinality_base * block->currently_chosen_alternative;
+ chain_number = compute_chain_number_for_block(block_idx - 1, disabled) + block->cardinality_base * currently_chosen_alternative;
}
- assert(block->currently_chosen_alternative < int(block->alternatives.size()));
return chain_number;
}
+bitset<256> Scene::find_disabled_blocks(size_t chain_idx) const
+{
+ assert(blocks.size() < 256);
+
+ // The find_disabled_blocks() recursion logic needs only one pass by itself,
+ // but the disabler logic is not so smart, so we just run multiple times
+ // until it converges.
+ bitset<256> prev, ret;
+ do {
+ find_disabled_blocks(chain_idx, blocks.size() - 1, /*currently_disabled=*/false, &ret);
+ prev = ret;
+
+ // Propagate DISABLE_IF_OTHER_DISABLED constraints (we can always do this).
+ for (Block *block : blocks) {
+ if (ret.test(block->idx)) continue; // Already disabled.
+
+ EffectType chosen_type = block->alternatives[block->chosen_alternative(chain_idx)]->effect_type;
+ if (chosen_type == IDENTITY_EFFECT) {
+ ret.set(block->idx);
+ continue;
+ }
+
+ for (const Block::Disabler &disabler : block->disablers) {
+ Block *other = blocks[disabler.block_idx];
+ EffectType chosen_type = other->alternatives[other->chosen_alternative(chain_idx)]->effect_type;
+ bool other_disabled = ret.test(disabler.block_idx) || chosen_type == IDENTITY_EFFECT;
+ if (other_disabled && disabler.condition == Block::Disabler::DISABLE_IF_OTHER_DISABLED) {
+ ret.set(block->idx);
+ break;
+ }
+ }
+ }
+
+ // We cannot propagate DISABLE_IF_OTHER_ENABLED in all cases;
+ // the problem is that if A is disabled if B is enabled,
+ // then we cannot disable A unless we actually know for sure
+ // that B _is_ enabled. (E.g., imagine that B is disabled
+ // if C is enabled -- we couldn't disable A before we knew if
+ // C was enabled or not!)
+ //
+ // We could probably fix a fair amount of these, but the
+ // primary use case for DISABLE_IF_OTHER_ENABLED is really
+ // mutual exclusion; A must be disabled if B is enabled
+ // _and_ vice versa. These loops cannot be automatically
+ // resolved; it would depend on what A and B is. Thus,
+ // we simply declare this kind of constraint to be a promise
+ // from the user, not something that we'll solve for them.
+ } while (prev != ret);
+ return ret;
+}
+
+void Scene::find_disabled_blocks(size_t chain_idx, size_t block_idx, bool currently_disabled, bitset<256> *disabled) const
+{
+ if (currently_disabled) {
+ disabled->set(block_idx);
+ }
+ Block *block = blocks[block_idx];
+ EffectType chosen_type = block->alternatives[block->chosen_alternative(chain_idx)]->effect_type;
+ for (size_t input_idx = 0; input_idx < block->inputs.size(); ++input_idx) {
+ if (chosen_type == IDENTITY_EFFECT && input_idx > 0) {
+ // Multi-input effect that has been replaced by
+ // IdentityEffect, so every effect but the first are
+ // disabled and will not participate in the chain.
+ find_disabled_blocks(chain_idx, block->inputs[input_idx], /*currently_disabled=*/true, disabled);
+ } else {
+ // Just keep on recursing down.
+ find_disabled_blocks(chain_idx, block->inputs[input_idx], currently_disabled, disabled);
+ }
+ }
+}
+
+bool Scene::is_noncanonical_chain(size_t chain_idx) const
+{
+ bitset<256> disabled = find_disabled_blocks(chain_idx);
+ assert(blocks.size() < 256);
+ for (size_t block_idx = 0; block_idx < blocks.size(); ++block_idx) {
+ Block *block = blocks[block_idx];
+ if (disabled.test(block_idx) && block->chosen_alternative(chain_idx) != block->canonical_alternative) {
+ return true;
+ }
+
+ // Test if we're supposed to be disabled by some other block being enabled;
+ // the disabled bit mask does not fully capture this.
+ if (!disabled.test(block_idx)) {
+ for (const Block::Disabler &disabler : block->disablers) {
+ if (disabler.condition == Block::Disabler::DISABLE_IF_OTHER_ENABLED &&
+ !disabled.test(disabler.block_idx)) {
+ return true;
+ }
+ }
+
+ // Auto white balance is always disabled for image inputs.
+ if (block->white_balance_controller_block != nullptr) {
+ const Block *input = block->white_balance_controller_block;
+ if (input->alternatives[input->chosen_alternative(chain_idx)]->effect_type == IMAGE_INPUT) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
int Scene::add_input(lua_State* L)
{
assert(lua_gettop(L) == 1 || lua_gettop(L) == 2);
Scene *scene = (Scene *)luaL_checkudata(L, 1, "Scene");
Block *block = new Block;
+ block->declaration_point = get_declaration_point(L);
block->idx = scene->blocks.size();
if (lua_gettop(L) == 1) {
// No parameter given, so a flexible input.
if (lua_isnumber(L, 2)) {
block->alternatives.emplace_back(new EffectBlueprint(LIVE_INPUT_YCBCR));
block->alternatives.emplace_back(new EffectBlueprint(LIVE_INPUT_YCBCR_WITH_DEINTERLACE));
+#ifdef HAVE_SRT
+ if (global_flags.srt_port >= 0) {
+ block->alternatives.emplace_back(new EffectBlueprint(LIVE_INPUT_YCBCR_PLANAR));
+ }
+#endif
#ifdef HAVE_CEF
} else if (luaL_testudata(L, 2, "HTMLInput")) {
block->alternatives.emplace_back(new EffectBlueprint(LIVE_INPUT_BGRA));
return wrap_lua_existing_object_nonowned<Block>(L, "Block", block);
}
+Block *Scene::find_block_from_arg(lua_State *L, Scene *scene, int idx)
+{
+ if (luaL_testudata(L, idx, "Block")) {
+ return *(Block **)luaL_checkudata(L, idx, "Block");
+ } else {
+ EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, idx, "EffectBlueprint");
+
+ // Search through all the blocks to figure out which one contains this effect.
+ for (Block *block : scene->blocks) {
+ if (find(block->alternatives.begin(), block->alternatives.end(), blueprint) != block->alternatives.end()) {
+ return block;
+ }
+ }
+ luaL_error(L, "Input effect in parameter #%d has not been added to this scene", idx - 1);
+ return nullptr; // Dead code.
+ }
+}
+
+void Scene::find_inputs_for_block(lua_State *L, Scene *scene, Block *block, int first_input_idx)
+{
+ if (lua_gettop(L) == first_input_idx - 1) {
+ // Implicitly the last added effect.
+ assert(!scene->blocks.empty());
+ block->inputs.push_back(scene->blocks.size() - 1);
+ return;
+ }
+
+ for (int idx = first_input_idx; idx <= lua_gettop(L); ++idx) {
+ block->inputs.push_back(find_block_from_arg(L, scene, idx)->idx);
+ }
+}
+
int Scene::add_effect(lua_State* L)
{
assert(lua_gettop(L) >= 2);
Scene *scene = (Scene *)luaL_checkudata(L, 1, "Scene");
Block *block = new Block;
+ block->declaration_point = get_declaration_point(L);
block->idx = scene->blocks.size();
if (lua_istable(L, 2)) {
block->alternatives.push_back(blueprint);
}
- // Find the inputs.
- if (lua_gettop(L) == 2) {
- assert(!scene->blocks.empty());
- block->inputs.push_back(scene->blocks.size() - 1);
+ int identity_index = find_index_of(block, IDENTITY_EFFECT);
+ if (identity_index == -1) {
+ block->canonical_alternative = 0;
} else {
- for (int idx = 3; idx <= lua_gettop(L); ++idx) {
- Block *input_block = nullptr;
- if (luaL_testudata(L, idx, "Block")) {
- input_block = *(Block **)luaL_checkudata(L, idx, "Block");
- } else {
- EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, idx, "EffectBlueprint");
-
- // Search through all the blocks to figure out which one contains this effect.
- for (Block *block : scene->blocks) {
- if (find(block->alternatives.begin(), block->alternatives.end(), blueprint) != block->alternatives.end()) {
- input_block = block;
- break;
- }
- }
- if (input_block == nullptr) {
- luaL_error(L, "Input effect in parameter #%d has not been added to this scene", idx - 1);
- }
- }
- block->inputs.push_back(input_block->idx);
- }
+ // Pick the IdentityEffect as the canonical alternative, in case it
+ // helps us disable more stuff.
+ block->canonical_alternative = identity_index;
}
+ find_inputs_for_block(L, scene, block);
scene->blocks.push_back(block);
return wrap_lua_existing_object_nonowned<Block>(L, "Block", block);
assert(lua_gettop(L) >= 2);
Scene *scene = (Scene *)luaL_checkudata(L, 1, "Scene");
- // NOTE: We only support effects with a single parent, since that's what IdentityEffect does.
Block *block = new Block;
+ block->declaration_point = get_declaration_point(L);
block->idx = scene->blocks.size();
EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, 2, "EffectBlueprint");
// An IdentityEffect will be the alternative for when the effect is disabled.
block->alternatives.push_back(new EffectBlueprint(IDENTITY_EFFECT));
- block->inputs.push_back(scene->blocks.size() - 1);
+ block->canonical_alternative = 1;
+
+ find_inputs_for_block(L, scene, block);
+ scene->blocks.push_back(block);
+
+ return wrap_lua_existing_object_nonowned<Block>(L, "Block", block);
+}
+
+const Block *Scene::find_root_input_block(lua_State *L, const Block *block)
+{
+ if (block->is_input) {
+ assert(block->inputs.size() == 0);
+ return block;
+ }
+
+ const Block *ret = nullptr;
+ for (size_t input_idx : block->inputs) {
+ const Block *parent = find_root_input_block(L, blocks[input_idx]);
+ if (parent != nullptr) {
+ if (ret != nullptr) {
+ luaL_error(L, "add_white_balance() was connected to more than one input");
+ }
+ ret = parent;
+ }
+ }
+ return ret;
+}
+
+int Scene::add_white_balance(lua_State* L)
+{
+ assert(lua_gettop(L) >= 1);
+ Scene *scene = (Scene *)luaL_checkudata(L, 1, "Scene");
+
+ Block *block = new Block;
+ block->declaration_point = get_declaration_point(L);
+ block->idx = scene->blocks.size();
+
+ block->alternatives.push_back(new EffectBlueprint(WHITE_BALANCE_EFFECT));
+ block->alternatives.push_back(new EffectBlueprint(IDENTITY_EFFECT));
+
+ block->canonical_alternative = 1;
+
+ if (lua_gettop(L) == 1) {
+ // The last added effect is implicitly both the input and gives the white balance controller.
+ assert(!scene->blocks.empty());
+ block->inputs.push_back(scene->blocks.size() - 1);
+ block->white_balance_controller_block = scene->find_root_input_block(L, block);
+ } else if (lua_gettop(L) == 2) {
+ // The given effect is both the input and the white balance controller.
+ block->inputs.push_back(find_block_from_arg(L, scene, 2)->idx);
+ block->white_balance_controller_block = scene->find_root_input_block(L, block);
+ } else if (lua_gettop(L) == 3) {
+ // We have explicit input and white balance controller.
+ block->inputs.push_back(find_block_from_arg(L, scene, 2)->idx);
+ block->white_balance_controller_block = find_block_from_arg(L, scene, 3);
+ } else {
+ luaL_error(L, "add_white_balance([input], [white_balance_controller]) takes zero, one or two arguments");
+ }
+ if (block->white_balance_controller_block == nullptr || !block->white_balance_controller_block->is_input) {
+ luaL_error(L, "add_white_balance() does not get its white balance from an input");
+ }
+
scene->blocks.push_back(block);
return wrap_lua_existing_object_nonowned<Block>(L, "Block", block);
Effect *Scene::instantiate_effects(const Block *block, size_t chain_idx, Scene::Instantiation *instantiation)
{
+ // Find the chosen alternative for this block in this instance.
+ EffectType chosen_type = block->alternatives[block->chosen_alternative(chain_idx)]->effect_type;
+
vector<Effect *> inputs;
for (size_t input_idx : block->inputs) {
inputs.push_back(instantiate_effects(blocks[input_idx], chain_idx, instantiation));
- }
- // Find the chosen alternative for this block in this instance.
- size_t chosen_alternative = (chain_idx / block->cardinality_base) % block->alternatives.size();
- EffectType chosen_type = block->alternatives[chosen_alternative]->effect_type;
+ // As a special case, we allow IdentityEffect to take only one input
+ // even if the other alternative (or alternatives) is multi-input.
+ // Thus, even if there are more than one inputs, instantiate only
+ // the first one.
+ if (chosen_type == IDENTITY_EFFECT) {
+ break;
+ }
+ }
Effect *effect;
switch (chosen_type) {
break;
}
default:
- effect = instantiate_effect(instantiation->chain.get(), block->alternatives[chosen_alternative]->effect_type);
+ effect = instantiate_effect(instantiation->chain.get(), chosen_type);
instantiation->chain->add_effect(effect, inputs);
break;
}
}
const size_t cardinality = base;
- const size_t total_cardinality = cardinality * (only_one_mode ? 1 : 2);
+ size_t real_cardinality = 0;
+ for (size_t chain_idx = 0; chain_idx < cardinality; ++chain_idx) {
+ if (!scene->is_noncanonical_chain(chain_idx)) {
+ ++real_cardinality;
+ }
+ }
+ const size_t total_cardinality = real_cardinality * (only_one_mode ? 1 : 2);
if (total_cardinality > 200) {
print_warning(L, "The given Scene will instantiate %zu different versions. This will take a lot of time and RAM to compile; see if you could limit some options by e.g. locking the input type in some cases (by giving a fixed input to add_input()).\n",
total_cardinality);
Block *output_block = scene->blocks.back();
for (bool is_main_chain : { false, true }) {
for (size_t chain_idx = 0; chain_idx < cardinality; ++chain_idx) {
- if (only_one_mode && is_main_chain != chosen_mode) {
+ if ((only_one_mode && is_main_chain != chosen_mode) ||
+ scene->is_noncanonical_chain(chain_idx)) {
scene->chains.emplace_back();
continue;
}
return 0;
}
+int find_card_to_connect(Theme *theme, lua_State *L, const Block *block)
+{
+ if (block->signal_type_to_connect == Block::CONNECT_SIGNAL) {
+ return theme->map_signal_to_card(block->signal_to_connect);
+#ifdef HAVE_CEF
+ } else if (block->signal_type_to_connect == Block::CONNECT_CEF) {
+ return block->cef_to_connect->get_card_index();
+#endif
+ } else if (block->signal_type_to_connect == Block::CONNECT_VIDEO) {
+ return block->video_to_connect->get_card_index();
+ } else if (block->signal_type_to_connect == Block::CONNECT_NONE) {
+ luaL_error(L, "An input in a scene was not connected to anything (forgot to call display())");
+ } else {
+ assert(false);
+ }
+ return -1;
+}
+
std::pair<movit::EffectChain *, std::function<void()>>
Scene::get_chain(Theme *theme, lua_State *L, unsigned num, const InputState &input_state)
{
InputStateInfo info(input_state);
for (Block *block : blocks) {
if (block->is_input && block->signal_type_to_connect == Block::CONNECT_SIGNAL) {
- EffectType chosen_type = current_type(block);
- assert(chosen_type == LIVE_INPUT_YCBCR || chosen_type == LIVE_INPUT_YCBCR_WITH_DEINTERLACE);
- if (info.last_interlaced[block->signal_to_connect]) {
+ int card_index = theme->map_signal_to_card(block->signal_to_connect);
+ if (info.last_interlaced[card_index]) {
+ assert(info.last_pixel_format[card_index] == bmusb::PixelFormat_8BitYCbCr ||
+ info.last_pixel_format[card_index] == bmusb::PixelFormat_10BitYCbCr);
block->currently_chosen_alternative = find_index_of(block, LIVE_INPUT_YCBCR_WITH_DEINTERLACE);
+ } else if (info.last_pixel_format[card_index] == bmusb::PixelFormat_8BitYCbCrPlanar) {
+ block->currently_chosen_alternative = find_index_of(block, LIVE_INPUT_YCBCR_PLANAR);
+ } else if (info.last_pixel_format[card_index] == bmusb::PixelFormat_8BitBGRA) {
+ block->currently_chosen_alternative = find_index_of(block, LIVE_INPUT_BGRA);
} else {
block->currently_chosen_alternative = find_index_of(block, LIVE_INPUT_YCBCR);
}
+ if (block->currently_chosen_alternative == -1) {
+ fprintf(stderr, "ERROR: Input connected to a video card pixel format that it was not ready for.\n");
+ abort();
+ }
+ }
+ }
+
+ // Find all auto white balance blocks, turn on and off the effect as needed,
+ // and fetch the actual white balance set (it is stored in Theme).
+ map<Block *, array<float, 3>> white_balance;
+ for (size_t block_idx = 0; block_idx < blocks.size(); ++block_idx) {
+ Block *block = blocks[block_idx];
+ const Block *input = block->white_balance_controller_block;
+ if (input == nullptr) {
+ continue; // Not an auto white balance block.
+ }
+
+ EffectType chosen_type = current_type(input);
+ if (chosen_type == IMAGE_INPUT) {
+ // Image inputs never get white balance applied.
+ block->currently_chosen_alternative = find_index_of(block, IDENTITY_EFFECT);
+ continue;
+ }
+
+ assert(chosen_type == LIVE_INPUT_YCBCR ||
+ chosen_type == LIVE_INPUT_YCBCR_WITH_DEINTERLACE ||
+ chosen_type == LIVE_INPUT_YCBCR_PLANAR ||
+ chosen_type == LIVE_INPUT_BGRA);
+ int card_idx = find_card_to_connect(theme, L, input);
+ RGBTriplet wb = theme->get_white_balance_for_card(card_idx);
+ if (fabs(wb.r - 1.0) < 1e-3 && fabs(wb.g - 1.0) < 1e-3 && fabs(wb.b - 1.0) < 1e-3) {
+ // Neutral white balance.
+ block->currently_chosen_alternative = find_index_of(block, IDENTITY_EFFECT);
+ } else {
+ block->currently_chosen_alternative = find_index_of(block, WHITE_BALANCE_EFFECT);
+ white_balance.emplace(block, array<float, 3>{ wb.r, wb.g, wb.b });
}
}
// the Lua code later.
bool is_main_chain = (num == 0);
size_t chain_idx = compute_chain_number(is_main_chain);
+ if (is_noncanonical_chain(chain_idx)) {
+ // This should be due to promise_to_disable_if_enabled(). Find out what
+ // happened, to give the user some help.
+ bitset<256> disabled = find_disabled_blocks(chain_idx);
+ for (size_t block_idx = 0; block_idx < blocks.size(); ++block_idx) {
+ Block *block = blocks[block_idx];
+ if (disabled.test(block_idx)) continue;
+ for (const Block::Disabler &disabler : block->disablers) {
+ if (disabler.condition == Block::Disabler::DISABLE_IF_OTHER_ENABLED &&
+ !disabled.test(disabler.block_idx)) {
+ fprintf(stderr, "Promise declared at %s violated.\n", disabler.declaration_point.c_str());
+ abort();
+ }
+ }
+ }
+ assert(false); // Something else happened, seemingly.
+ }
const Scene::Instantiation &instantiation = chains[chain_idx];
EffectChain *effect_chain = instantiation.chain.get();
- map<LiveInputWrapper *, int> signals_to_connect;
+ map<LiveInputWrapper *, int> cards_to_connect;
map<ImageInput *, string> images_to_select;
map<pair<Effect *, string>, int> int_to_set;
map<pair<Effect *, string>, float> float_to_set;
for (const auto &index_and_input : instantiation.inputs) {
Block *block = blocks[index_and_input.first];
EffectType chosen_type = current_type(block);
- LiveInputWrapper *input = index_and_input.second;
if (chosen_type == LIVE_INPUT_YCBCR ||
chosen_type == LIVE_INPUT_YCBCR_WITH_DEINTERLACE ||
chosen_type == LIVE_INPUT_YCBCR_PLANAR ||
chosen_type == LIVE_INPUT_BGRA) {
- if (block->signal_type_to_connect == Block::CONNECT_SIGNAL) {
- signals_to_connect.emplace(input, block->signal_to_connect);
-#ifdef HAVE_CEF
- } else if (block->signal_type_to_connect == Block::CONNECT_CEF) {
- signals_to_connect.emplace(input, block->cef_to_connect->get_card_index());
-#endif
- } else if (block->signal_type_to_connect == Block::CONNECT_VIDEO) {
- signals_to_connect.emplace(input, block->video_to_connect->get_card_index());
- } else if (block->signal_type_to_connect == Block::CONNECT_NONE) {
- luaL_error(L, "An input in a scene was not connected to anything (forgot to call display())");
- } else {
- assert(false);
- }
+ LiveInputWrapper *input = index_and_input.second;
+ cards_to_connect.emplace(input, find_card_to_connect(theme, L, block));
}
}
for (const auto &index_and_input : instantiation.image_inputs) {
Block *block = blocks[index_and_effect.first];
Effect *effect = index_and_effect.second;
+ bool missing_width = (current_type(block) == RESIZE_EFFECT ||
+ current_type(block) == RESAMPLE_EFFECT ||
+ current_type(block) == PADDING_EFFECT);
+ bool missing_height = missing_width;
+
// Get the effects currently set on the block.
if (current_type(block) != IDENTITY_EFFECT) { // Ignore settings on optional effects.
+ if (block->int_parameters.count("width") && block->int_parameters["width"] > 0) {
+ missing_width = false;
+ }
+ if (block->int_parameters.count("height") && block->int_parameters["height"] > 0) {
+ missing_height = false;
+ }
for (const auto &key_and_tuple : block->int_parameters) {
int_to_set.emplace(make_pair(effect, key_and_tuple.first), key_and_tuple.second);
}
for (const auto &key_and_tuple : block->float_parameters) {
float_to_set.emplace(make_pair(effect, key_and_tuple.first), key_and_tuple.second);
}
+ if (white_balance.count(block)) {
+ vec3_to_set.emplace(make_pair(effect, "neutral_color"), white_balance[block]);
+ }
for (const auto &key_and_tuple : block->vec3_parameters) {
vec3_to_set.emplace(make_pair(effect, key_and_tuple.first), key_and_tuple.second);
}
// so they are set afterwards.
if (!block->alternatives.empty()) {
EffectBlueprint *blueprint = block->alternatives[block->currently_chosen_alternative];
+ if (blueprint->int_parameters.count("width") && blueprint->int_parameters["width"] > 0) {
+ missing_width = false;
+ }
+ if (blueprint->int_parameters.count("height") && blueprint->int_parameters["height"] > 0) {
+ missing_height = false;
+ }
for (const auto &key_and_tuple : blueprint->int_parameters) {
int_to_set[make_pair(effect, key_and_tuple.first)] = key_and_tuple.second;
}
vec4_to_set[make_pair(effect, key_and_tuple.first)] = key_and_tuple.second;
}
}
+
+ if (missing_width || missing_height) {
+ fprintf(stderr, "WARNING: Unset or nonpositive width/height for effect declared at %s "
+ "when getting scene for signal %u; setting to 1x1 to avoid crash.\n",
+ block->declaration_point.c_str(), num);
+ int_to_set[make_pair(effect, "width")] = 1;
+ int_to_set[make_pair(effect, "height")] = 1;
+ }
}
lua_pop(L, 1);
- auto setup_chain = [L, theme, signals_to_connect, images_to_select, int_to_set, float_to_set, vec3_to_set, vec4_to_set, input_state]{
+ auto setup_chain = [L, theme, cards_to_connect, images_to_select, int_to_set, float_to_set, vec3_to_set, vec4_to_set, input_state]{
lock_guard<mutex> lock(theme->m);
- // Set up state, including connecting signals.
- for (const auto &input_and_signal : signals_to_connect) {
- LiveInputWrapper *input = input_and_signal.first;
- input->connect_signal_raw(input_and_signal.second, input_state);
+ // Set up state, including connecting cards.
+ for (const auto &input_and_card : cards_to_connect) {
+ LiveInputWrapper *input = input_and_card.first;
+ input->connect_card(input_and_card.second, input_state);
}
for (const auto &input_and_filename : images_to_select) {
input_and_filename.first->switch_image(input_and_filename.second);
bool display(Block *block, lua_State *L, int idx)
{
if (lua_isnumber(L, idx)) {
- Theme *theme = get_theme_updata(L);
int signal_idx = luaL_checknumber(L, idx);
block->signal_type_to_connect = Block::CONNECT_SIGNAL;
- block->signal_to_connect = theme->map_signal(signal_idx);
+ block->signal_to_connect = signal_idx;
block->currently_chosen_alternative = find_index_of(block, LIVE_INPUT_YCBCR); // Will be changed to deinterlaced at get_chain() time if needed.
return true;
#ifdef HAVE_CEF
return 0;
}
+int Block_enable_if(lua_State *L)
+{
+ assert(lua_gettop(L) == 2);
+ Block *block = *(Block **)luaL_checkudata(L, 1, "Block");
+
+ if (block->alternatives.size() != 2 ||
+ block->alternatives[1]->effect_type != IDENTITY_EFFECT) {
+ luaL_error(L, "enable_if() called on something that wasn't added with add_optional_effect()");
+ }
+ bool enabled = checkbool(L, 2);
+ block->currently_chosen_alternative = enabled ? 0 : 1;
+ return 0;
+}
+
int Block_disable(lua_State *L)
{
assert(lua_gettop(L) == 1);
return 0;
}
+int Block_always_disable_if_disabled(lua_State *L)
+{
+ assert(lua_gettop(L) == 2);
+ Block *block = *(Block **)luaL_checkudata(L, 1, "Block");
+ Block *disabler_block = *(Block **)luaL_checkudata(L, 2, "Block");
+
+ int my_alternative = find_index_of(block, IDENTITY_EFFECT);
+ int their_alternative = find_index_of(disabler_block, IDENTITY_EFFECT);
+ if (my_alternative == -1) {
+ luaL_error(L, "always_disable_if_disabled() called on something that didn't have an IdentityEffect fallback (try add_optional_effect())");
+ }
+ if (their_alternative == -1) {
+ luaL_error(L, "always_disable_if_disabled() with an argument that didn't have an IdentityEffect fallback (try add_optional_effect())");
+ }
+
+ // The declaration point isn't actually used, but it's nice for completeness.
+ block->disablers.push_back(Block::Disabler{ disabler_block->idx, Block::Disabler::DISABLE_IF_OTHER_DISABLED, get_declaration_point(L) });
+
+ lua_pop(L, 2);
+ return 0;
+}
+
+int Block_promise_to_disable_if_enabled(lua_State *L)
+{
+ assert(lua_gettop(L) == 2);
+ Block *block = *(Block **)luaL_checkudata(L, 1, "Block");
+ Block *disabler_block = *(Block **)luaL_checkudata(L, 2, "Block");
+
+ int my_alternative = find_index_of(block, IDENTITY_EFFECT);
+ int their_alternative = find_index_of(disabler_block, IDENTITY_EFFECT);
+ if (my_alternative == -1) {
+ luaL_error(L, "promise_to_disable_if_enabled() called on something that didn't have an IdentityEffect fallback (try add_optional_effect())");
+ }
+ if (their_alternative == -1) {
+ luaL_error(L, "promise_to_disable_if_enabled() with an argument that didn't have an IdentityEffect fallback (try add_optional_effect())");
+ }
+
+ block->disablers.push_back(Block::Disabler{ disabler_block->idx, Block::Disabler::DISABLE_IF_OTHER_ENABLED, get_declaration_point(L) });
+
+ lua_pop(L, 2);
+ return 0;
+}
+
int Block_set_int(lua_State *L)
{
assert(lua_gettop(L) == 3);