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;
+ for (Block *block : blocks) {
+ EffectType chosen_type = block->alternatives[block->chosen_alternative(chain_idx)]->effect_type;
+ if (ret.test(block->idx) || chosen_type == IDENTITY_EFFECT) continue; // Already disabled.
+
+ for (Block::Index disabler_idx : block->disablers) {
+ Block *disabler = blocks[disabler_idx];
+ EffectType chosen_type = disabler->alternatives[disabler->chosen_alternative(chain_idx)]->effect_type;
+ if (ret.test(disabler->idx) || chosen_type == IDENTITY_EFFECT) {
+ ret.set(block->idx);
+ break;
+ }
+ }
+ }
+ } 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);
+ if (disabled.none()) {
+ return false;
+ }
+ 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;
+ }
+ }
+ 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.
return wrap_lua_existing_object_nonowned<Block>(L, "Block", block);
}
+void Scene::find_inputs_for_block(lua_State *L, Scene *scene, Block *block)
+{
+ if (lua_gettop(L) == 2) {
+ // Implicitly the last added effect.
+ assert(!scene->blocks.empty());
+ block->inputs.push_back(scene->blocks.size() - 1);
+ return;
+ }
+
+ 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);
+ }
+}
+
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);
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.
- EffectType chosen_type = block->alternatives[block->chosen_alternative(chain_idx)]->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) {
}
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;
}
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);
}
// 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);
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())");
+ }
+
+ block->disablers.push_back(disabler_block->idx);
+
+ lua_pop(L, 2);
+ return 0;
+}
+
int Block_set_int(lua_State *L)
{
assert(lua_gettop(L) == 3);