| Message ID | 20260423230059.3180987-34-laurent.pinchart@ideasonboard.com |
|---|---|
| State | Accepted |
| Headers | show |
| Series |
|
| Related | show |
2026. 04. 24. 1:00 keltezéssel, Laurent Pinchart írta: > libcamera supports several environment variables that can override > configuration options. This requires components that use such overrides > to call the special envOption() and envListOption() functions, spreading > knowledge of the overrides through the code base. This will hinder > future enhancements to the global configuration, such as implementing > per-camera options that will override pipeline handler-level options. To > prepare for that, move handling of the environment variables to the > GlobalConfiguration class. > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com> > --- > Changes since v1: > > - Avoid copy in range-based loop > - Drop unneeded constructor > - Erase existing node before overriding > - Move override code to constructor > --- Looks ok to me. Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > .../libcamera/internal/global_configuration.h | 7 - > src/libcamera/camera_manager.cpp | 4 +- > src/libcamera/global_configuration.cpp | 180 +++++++++++------- > src/libcamera/ipa_manager.cpp | 9 +- > src/libcamera/ipa_proxy.cpp | 6 +- > src/libcamera/software_isp/software_isp.cpp | 4 +- > 6 files changed, 124 insertions(+), 86 deletions(-) > > diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h > index 5eb646e33fc2..0d2ccb8a1808 100644 > --- a/include/libcamera/internal/global_configuration.h > +++ b/include/libcamera/internal/global_configuration.h > @@ -42,13 +42,6 @@ public: > > std::optional<std::vector<std::string>> listOption( > const std::initializer_list<std::string_view> confPath) const; > - std::optional<std::string> envOption( > - const char *const envVariable, > - const std::initializer_list<std::string_view> confPath) const; > - std::optional<std::vector<std::string>> envListOption( > - const char *const envVariable, > - const std::initializer_list<std::string_view> confPath, > - const std::string delimiter = ":") const; > > private: > void load(); > diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp > index f774bd84291b..0dd4e0c590a1 100644 > --- a/src/libcamera/camera_manager.cpp > +++ b/src/libcamera/camera_manager.cpp > @@ -114,9 +114,7 @@ void CameraManager::Private::createPipelineHandlers() > * there is no configuration file. > */ > const auto pipesList = > - configuration().envListOption("LIBCAMERA_PIPELINES_MATCH_LIST", > - { "pipelines_match_list" }, > - ","); > + configuration().listOption({ "pipelines_match_list" }); > if (pipesList.has_value()) { > /* > * When a list of preferred pipelines is defined, iterate > diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp > index 62b9762d3e31..46a285c3c4ea 100644 > --- a/src/libcamera/global_configuration.cpp > +++ b/src/libcamera/global_configuration.cpp > @@ -7,6 +7,7 @@ > > #include "libcamera/internal/global_configuration.h" > > +#include <array> > #include <filesystem> > #include <memory> > #include <optional> > @@ -30,6 +31,99 @@ const std::vector<std::filesystem::path> globalConfigurationFiles = { > std::filesystem::path(LIBCAMERA_DATA_DIR) / "configuration.yaml", > }; > > +class EnvironmentProcessor > +{ > +public: > + virtual ~EnvironmentProcessor() = default; > + > + virtual void process(ValueNode &node, const char *env) = 0; > +}; > + > +/* A processor that sets a fixed value. */ > +template<typename T> > +class EnvironmentFixedProcessor : public EnvironmentProcessor > +{ > +public: > + EnvironmentFixedProcessor(const T &value) > + : value_(value) > + { > + } > + > + void process(ValueNode &node, [[maybe_unused]] const char *env) override > + { > + node.set(value_); > + } > + > +private: > + T value_; > +}; > + > +/* > + * A processor that parses the environment variable as a list of strings with a > + * custom delimiter. > + */ > +class EnvironmentListProcessor : public EnvironmentProcessor > +{ > +public: > + EnvironmentListProcessor(const char *delimiter) > + : delimiter_(delimiter) > + { > + } > + > + void process(ValueNode &node, const char *env) override > + { > + for (auto &&value : utils::split(env, delimiter_)) > + node.add(std::make_unique<ValueNode>(std::move(value))); > + } > + > +private: > + const std::string delimiter_; > +}; > + > +/* A processor that copies the value of the environment variable. */ > +class EnvironmentValueProcessor : public EnvironmentProcessor > +{ > +public: > + void process(ValueNode &node, const char *env) override > + { > + node.set(std::string{ env }); > + } > +}; > + > +struct EnvironmentOverride { > + const char *variable; > + std::initializer_list<std::string_view> path; > + std::unique_ptr<EnvironmentProcessor> processor; > +}; > + > +const std::array<EnvironmentOverride, 6> environmentOverrides{ { > + { > + "LIBCAMERA_IPA_CONFIG_PATH", > + { "ipa", "config_paths" }, > + std::make_unique<EnvironmentListProcessor>(":"), > + }, { > + "LIBCAMERA_IPA_FORCE_ISOLATION", > + { "ipa", "force_isolation" }, > + std::make_unique<EnvironmentFixedProcessor<bool>>(true), > + }, { > + "LIBCAMERA_IPA_MODULE_PATH", > + { "ipa", "module_paths" }, > + std::make_unique<EnvironmentListProcessor>(":"), > + }, { > + "LIBCAMERA_IPA_PROXY_PATH", > + { "ipa", "proxy_paths" }, > + std::make_unique<EnvironmentListProcessor>(":"), > + }, { > + "LIBCAMERA_PIPELINES_MATCH_LIST", > + { "pipelines_match_list" }, > + std::make_unique<EnvironmentListProcessor>(","), > + }, { > + "LIBCAMERA_SOFTISP_MODE", > + { "software_isp", "mode" }, > + std::make_unique<EnvironmentValueProcessor>(), > + }, > +} }; > + > } /* namespace */ > > LOG_DEFINE_CATEGORY(Configuration) > @@ -50,9 +144,9 @@ LOG_DEFINE_CATEGORY(Configuration) > * reported and no configuration file is used. This is to prevent libcamera from > * using an unintended configuration file. > * > - * The configuration can be accessed using the provided helpers, namely > - * option(), envOption(), listOption() and envListOption() to access individual > - * options, or configuration() to access the whole configuration. > + * The configuration can be accessed using the provided helpers, namely option() > + * and listOption() to access individual options, or configuration() to access > + * the whole configuration. > */ > > /** > @@ -66,6 +160,25 @@ GlobalConfiguration::GlobalConfiguration() > configuration_->add("version", std::make_unique<ValueNode>(1)); > configuration_->add("configuration", std::make_unique<ValueNode>()); > } > + > + /* Process environment variables that override configuration options. */ > + ValueNode *cfg = configuration_->at("configuration"); > + > + for (const EnvironmentOverride &envOverride : environmentOverrides) { > + const char *envValue = utils::secure_getenv(envOverride.variable); > + if (!envValue || !envValue[0]) > + continue; > + > + std::unique_ptr<ValueNode> node = std::make_unique<ValueNode>(); > + envOverride.processor->process(*node.get(), envValue); > + > + cfg->erase(envOverride.path); > + > + if (!cfg->add(envOverride.path, std::move(node))) > + LOG(Configuration, Error) > + << "Failed to override " > + << utils::join(envOverride.path, "/"); > + } > } > > void GlobalConfiguration::load() > @@ -185,65 +298,4 @@ std::optional<std::vector<std::string>> GlobalConfiguration::listOption( > return c->get<std::vector<std::string>>(); > } > > -/** > - * \brief Retrieve the value of environment variable with a fallback on the configuration file > - * \param[in] envVariable Environment variable to get the value from > - * \param[in] confPath The sequence of YAML section names to fall back on when > - * \a envVariable is unavailable > - * > - * This helper looks first at the given environment variable and if it is > - * defined then it returns its value (even if it is empty). Otherwise it looks > - * for \a confPath the same way as in GlobalConfiguration::option. Only string > - * values are supported. > - * > - * \note Support for using environment variables to configure libcamera behavior > - * is provided here mostly for backward compatibility reasons. Introducing new > - * configuration environment variables is discouraged. > - * > - * \return The value retrieved from the given environment if it is set, > - * otherwise the value from the configuration file if it exists, or no value if > - * it does not > - */ > -std::optional<std::string> GlobalConfiguration::envOption( > - const char *envVariable, > - const std::initializer_list<std::string_view> confPath) const > -{ > - const char *envValue = utils::secure_getenv(envVariable); > - if (envValue) > - return std::optional{ std::string{ envValue } }; > - return option<std::string>(confPath); > -} > - > -/** > - * \brief Retrieve the value of the configuration option from a file or environment > - * \param[in] envVariable Environment variable to get the value from > - * \param[in] confPath The same as in GlobalConfiguration::option > - * \param[in] delimiter Items separator in the environment variable > - * > - * This helper looks first at the given environment variable and if it is > - * defined (even if it is empty) then it splits its value by semicolons and > - * returns the resulting list of strings. Otherwise it looks for \a confPath the > - * same way as in GlobalConfiguration::option, value of which must be a list of > - * strings. > - * > - * \note Support for using environment variables to configure libcamera behavior > - * is provided here mostly for backward compatibility reasons. Introducing new > - * configuration environment variables is discouraged. > - * > - * \return A vector of strings retrieved from the given environment option or > - * configuration file or no value if not found; the vector may be empty > - */ > -std::optional<std::vector<std::string>> GlobalConfiguration::envListOption( > - const char *const envVariable, > - const std::initializer_list<std::string_view> confPath, > - const std::string delimiter) const > -{ > - const char *envValue = utils::secure_getenv(envVariable); > - if (envValue) { > - auto items = utils::split(envValue, delimiter); > - return std::vector<std::string>(items.begin(), items.end()); > - } > - return listOption(confPath); > -} > - > } /* namespace libcamera */ > diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp > index dd1f483beec3..a351f4f7b581 100644 > --- a/src/libcamera/ipa_manager.cpp > +++ b/src/libcamera/ipa_manager.cpp > @@ -114,18 +114,15 @@ IPAManager::IPAManager(const CameraManager &cm) > if (!pubKey_.isValid()) > LOG(IPAManager, Warning) << "Public key not valid"; > > - char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION"); > - forceIsolation_ = (force && force[0] != '\0') || > - (!force && configuration.option<bool>({ "ipa", "force_isolation" }) > - .value_or(false)); > + forceIsolation_ = configuration.option<bool>({ "ipa", "force_isolation" }) > + .value_or(false); > #endif > > unsigned int ipaCount = 0; > > /* User-specified paths take precedence. */ > const auto modulePaths = > - configuration.envListOption( > - "LIBCAMERA_IPA_MODULE_PATH", { "ipa", "module_paths" }) > + configuration.listOption({ "ipa", "module_paths" }) > .value_or(std::vector<std::string>()); > for (const auto &dir : modulePaths) { > if (dir.empty()) > diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp > index 6c8780a012d5..bc8ff090fa86 100644 > --- a/src/libcamera/ipa_proxy.cpp > +++ b/src/libcamera/ipa_proxy.cpp > @@ -124,11 +124,9 @@ IPAProxy::IPAProxy(IPAModule *ipam, const CameraManager &cm) > { > const GlobalConfiguration &configuration = cm._d()->configuration(); > > - configPaths_ = configuration.envListOption("LIBCAMERA_IPA_CONFIG_PATH", > - { "ipa", "config_paths" }) > + configPaths_ = configuration.listOption({ "ipa", "config_paths" }) > .value_or(std::vector<std::string>()); > - execPaths_ = configuration.envListOption("LIBCAMERA_IPA_PROXY_PATH", > - { "ipa", "proxy_paths" }) > + execPaths_ = configuration.listOption({ "ipa", "proxy_paths" }) > .value_or(std::vector<std::string>()); > } > > diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp > index cd0e9d06a1e1..d227bd8e325f 100644 > --- a/src/libcamera/software_isp/software_isp.cpp > +++ b/src/libcamera/software_isp/software_isp.cpp > @@ -106,11 +106,11 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, > > #if HAVE_DEBAYER_EGL > const GlobalConfiguration &configuration = cm._d()->configuration(); > - std::optional<std::string> softISPMode = configuration.envOption("LIBCAMERA_SOFTISP_MODE", { "software_isp", "mode" }); > + std::optional<std::string> softISPMode = configuration.option<std::string>({ "software_isp", "mode" }); > if (softISPMode) { > if (softISPMode != "gpu" && softISPMode != "cpu") { > LOG(SoftwareIsp, Error) > - << "Invalid LIBCAMERA_SOFTISP_MODE \"" > + << "Invalid software ISP mode \"" > << softISPMode.value() > << "\", must be \"cpu\" or \"gpu\""; > return; > -- > Regards, > > Laurent Pinchart >
diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h index 5eb646e33fc2..0d2ccb8a1808 100644 --- a/include/libcamera/internal/global_configuration.h +++ b/include/libcamera/internal/global_configuration.h @@ -42,13 +42,6 @@ public: std::optional<std::vector<std::string>> listOption( const std::initializer_list<std::string_view> confPath) const; - std::optional<std::string> envOption( - const char *const envVariable, - const std::initializer_list<std::string_view> confPath) const; - std::optional<std::vector<std::string>> envListOption( - const char *const envVariable, - const std::initializer_list<std::string_view> confPath, - const std::string delimiter = ":") const; private: void load(); diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index f774bd84291b..0dd4e0c590a1 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -114,9 +114,7 @@ void CameraManager::Private::createPipelineHandlers() * there is no configuration file. */ const auto pipesList = - configuration().envListOption("LIBCAMERA_PIPELINES_MATCH_LIST", - { "pipelines_match_list" }, - ","); + configuration().listOption({ "pipelines_match_list" }); if (pipesList.has_value()) { /* * When a list of preferred pipelines is defined, iterate diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp index 62b9762d3e31..46a285c3c4ea 100644 --- a/src/libcamera/global_configuration.cpp +++ b/src/libcamera/global_configuration.cpp @@ -7,6 +7,7 @@ #include "libcamera/internal/global_configuration.h" +#include <array> #include <filesystem> #include <memory> #include <optional> @@ -30,6 +31,99 @@ const std::vector<std::filesystem::path> globalConfigurationFiles = { std::filesystem::path(LIBCAMERA_DATA_DIR) / "configuration.yaml", }; +class EnvironmentProcessor +{ +public: + virtual ~EnvironmentProcessor() = default; + + virtual void process(ValueNode &node, const char *env) = 0; +}; + +/* A processor that sets a fixed value. */ +template<typename T> +class EnvironmentFixedProcessor : public EnvironmentProcessor +{ +public: + EnvironmentFixedProcessor(const T &value) + : value_(value) + { + } + + void process(ValueNode &node, [[maybe_unused]] const char *env) override + { + node.set(value_); + } + +private: + T value_; +}; + +/* + * A processor that parses the environment variable as a list of strings with a + * custom delimiter. + */ +class EnvironmentListProcessor : public EnvironmentProcessor +{ +public: + EnvironmentListProcessor(const char *delimiter) + : delimiter_(delimiter) + { + } + + void process(ValueNode &node, const char *env) override + { + for (auto &&value : utils::split(env, delimiter_)) + node.add(std::make_unique<ValueNode>(std::move(value))); + } + +private: + const std::string delimiter_; +}; + +/* A processor that copies the value of the environment variable. */ +class EnvironmentValueProcessor : public EnvironmentProcessor +{ +public: + void process(ValueNode &node, const char *env) override + { + node.set(std::string{ env }); + } +}; + +struct EnvironmentOverride { + const char *variable; + std::initializer_list<std::string_view> path; + std::unique_ptr<EnvironmentProcessor> processor; +}; + +const std::array<EnvironmentOverride, 6> environmentOverrides{ { + { + "LIBCAMERA_IPA_CONFIG_PATH", + { "ipa", "config_paths" }, + std::make_unique<EnvironmentListProcessor>(":"), + }, { + "LIBCAMERA_IPA_FORCE_ISOLATION", + { "ipa", "force_isolation" }, + std::make_unique<EnvironmentFixedProcessor<bool>>(true), + }, { + "LIBCAMERA_IPA_MODULE_PATH", + { "ipa", "module_paths" }, + std::make_unique<EnvironmentListProcessor>(":"), + }, { + "LIBCAMERA_IPA_PROXY_PATH", + { "ipa", "proxy_paths" }, + std::make_unique<EnvironmentListProcessor>(":"), + }, { + "LIBCAMERA_PIPELINES_MATCH_LIST", + { "pipelines_match_list" }, + std::make_unique<EnvironmentListProcessor>(","), + }, { + "LIBCAMERA_SOFTISP_MODE", + { "software_isp", "mode" }, + std::make_unique<EnvironmentValueProcessor>(), + }, +} }; + } /* namespace */ LOG_DEFINE_CATEGORY(Configuration) @@ -50,9 +144,9 @@ LOG_DEFINE_CATEGORY(Configuration) * reported and no configuration file is used. This is to prevent libcamera from * using an unintended configuration file. * - * The configuration can be accessed using the provided helpers, namely - * option(), envOption(), listOption() and envListOption() to access individual - * options, or configuration() to access the whole configuration. + * The configuration can be accessed using the provided helpers, namely option() + * and listOption() to access individual options, or configuration() to access + * the whole configuration. */ /** @@ -66,6 +160,25 @@ GlobalConfiguration::GlobalConfiguration() configuration_->add("version", std::make_unique<ValueNode>(1)); configuration_->add("configuration", std::make_unique<ValueNode>()); } + + /* Process environment variables that override configuration options. */ + ValueNode *cfg = configuration_->at("configuration"); + + for (const EnvironmentOverride &envOverride : environmentOverrides) { + const char *envValue = utils::secure_getenv(envOverride.variable); + if (!envValue || !envValue[0]) + continue; + + std::unique_ptr<ValueNode> node = std::make_unique<ValueNode>(); + envOverride.processor->process(*node.get(), envValue); + + cfg->erase(envOverride.path); + + if (!cfg->add(envOverride.path, std::move(node))) + LOG(Configuration, Error) + << "Failed to override " + << utils::join(envOverride.path, "/"); + } } void GlobalConfiguration::load() @@ -185,65 +298,4 @@ std::optional<std::vector<std::string>> GlobalConfiguration::listOption( return c->get<std::vector<std::string>>(); } -/** - * \brief Retrieve the value of environment variable with a fallback on the configuration file - * \param[in] envVariable Environment variable to get the value from - * \param[in] confPath The sequence of YAML section names to fall back on when - * \a envVariable is unavailable - * - * This helper looks first at the given environment variable and if it is - * defined then it returns its value (even if it is empty). Otherwise it looks - * for \a confPath the same way as in GlobalConfiguration::option. Only string - * values are supported. - * - * \note Support for using environment variables to configure libcamera behavior - * is provided here mostly for backward compatibility reasons. Introducing new - * configuration environment variables is discouraged. - * - * \return The value retrieved from the given environment if it is set, - * otherwise the value from the configuration file if it exists, or no value if - * it does not - */ -std::optional<std::string> GlobalConfiguration::envOption( - const char *envVariable, - const std::initializer_list<std::string_view> confPath) const -{ - const char *envValue = utils::secure_getenv(envVariable); - if (envValue) - return std::optional{ std::string{ envValue } }; - return option<std::string>(confPath); -} - -/** - * \brief Retrieve the value of the configuration option from a file or environment - * \param[in] envVariable Environment variable to get the value from - * \param[in] confPath The same as in GlobalConfiguration::option - * \param[in] delimiter Items separator in the environment variable - * - * This helper looks first at the given environment variable and if it is - * defined (even if it is empty) then it splits its value by semicolons and - * returns the resulting list of strings. Otherwise it looks for \a confPath the - * same way as in GlobalConfiguration::option, value of which must be a list of - * strings. - * - * \note Support for using environment variables to configure libcamera behavior - * is provided here mostly for backward compatibility reasons. Introducing new - * configuration environment variables is discouraged. - * - * \return A vector of strings retrieved from the given environment option or - * configuration file or no value if not found; the vector may be empty - */ -std::optional<std::vector<std::string>> GlobalConfiguration::envListOption( - const char *const envVariable, - const std::initializer_list<std::string_view> confPath, - const std::string delimiter) const -{ - const char *envValue = utils::secure_getenv(envVariable); - if (envValue) { - auto items = utils::split(envValue, delimiter); - return std::vector<std::string>(items.begin(), items.end()); - } - return listOption(confPath); -} - } /* namespace libcamera */ diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp index dd1f483beec3..a351f4f7b581 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -114,18 +114,15 @@ IPAManager::IPAManager(const CameraManager &cm) if (!pubKey_.isValid()) LOG(IPAManager, Warning) << "Public key not valid"; - char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION"); - forceIsolation_ = (force && force[0] != '\0') || - (!force && configuration.option<bool>({ "ipa", "force_isolation" }) - .value_or(false)); + forceIsolation_ = configuration.option<bool>({ "ipa", "force_isolation" }) + .value_or(false); #endif unsigned int ipaCount = 0; /* User-specified paths take precedence. */ const auto modulePaths = - configuration.envListOption( - "LIBCAMERA_IPA_MODULE_PATH", { "ipa", "module_paths" }) + configuration.listOption({ "ipa", "module_paths" }) .value_or(std::vector<std::string>()); for (const auto &dir : modulePaths) { if (dir.empty()) diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp index 6c8780a012d5..bc8ff090fa86 100644 --- a/src/libcamera/ipa_proxy.cpp +++ b/src/libcamera/ipa_proxy.cpp @@ -124,11 +124,9 @@ IPAProxy::IPAProxy(IPAModule *ipam, const CameraManager &cm) { const GlobalConfiguration &configuration = cm._d()->configuration(); - configPaths_ = configuration.envListOption("LIBCAMERA_IPA_CONFIG_PATH", - { "ipa", "config_paths" }) + configPaths_ = configuration.listOption({ "ipa", "config_paths" }) .value_or(std::vector<std::string>()); - execPaths_ = configuration.envListOption("LIBCAMERA_IPA_PROXY_PATH", - { "ipa", "proxy_paths" }) + execPaths_ = configuration.listOption({ "ipa", "proxy_paths" }) .value_or(std::vector<std::string>()); } diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index cd0e9d06a1e1..d227bd8e325f 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -106,11 +106,11 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, #if HAVE_DEBAYER_EGL const GlobalConfiguration &configuration = cm._d()->configuration(); - std::optional<std::string> softISPMode = configuration.envOption("LIBCAMERA_SOFTISP_MODE", { "software_isp", "mode" }); + std::optional<std::string> softISPMode = configuration.option<std::string>({ "software_isp", "mode" }); if (softISPMode) { if (softISPMode != "gpu" && softISPMode != "cpu") { LOG(SoftwareIsp, Error) - << "Invalid LIBCAMERA_SOFTISP_MODE \"" + << "Invalid software ISP mode \"" << softISPMode.value() << "\", must be \"cpu\" or \"gpu\""; return;