+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;
+}
+