RtxSettings.h#
Fully qualified name: omni/extras/RtxSettings.h
File members: omni/extras/RtxSettings.h
// SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.
#pragma once
#include "../../carb/dictionary/DictionaryUtils.h"
#include "../../carb/extras/StringUtils.h"
#include "../../carb/logging/Log.h"
#include "../../carb/settings/SettingsUtils.h"
#include "../../carb/tasking/TaskingUtils.h"
#include "StringHelpers.h"
#include <algorithm>
#include <sstream>
#include <string>
#include <unordered_map>
#include "md5.h"
namespace rtx
{
/*
Usage:
// init
globalOverrides = Settings::createContext();
vp0Context = Settings::createContext();
// update loop
{
// Frame begin
// clone global settings
Settings::cloneToContext(vp0Context, nullptr);
Settings::getIDictionary()->makeIntAtPath(vp0Context, "path/to/int", 10);
// ...
Settings::applyOverridesToContext(vp0Context, globalOverrides);
// ...
// will have all the overrides
Settings::getIDictionary()->getAsInt(vp0Context, "path/to/int");
}
*/
constexpr char kDefaultSettingsPath[] = "/rtx-defaults";
constexpr char kSettingsPath[] = "/rtx";
constexpr char kDefaultPersistentSettingsPath[] = "/persistent-defaults";
constexpr char kPersistentSettingsPath[] = "/persistent";
constexpr char kTransientSettingsPath[] = "/rtx-transient";
constexpr char kFlagsSettingsPath[] = "/rtx-flags";
// A note on internal setting handling.
// Settings in /rtx-transient/internal and /rtx/internal are automatically mapped to entries in /rtx-transient/hashed
// and /rtx/hashed, respectively. We use the MD5 hash of the full internal setting string as the key inside the "hashed"
// scope. See the comments in InternalSettings.h for more details.
constexpr char kInternalSettingKey[] = "internal";
constexpr char kHashedSettingKey[] = "hashed";
constexpr const char* kInternalSettingRoots[] = {
kSettingsPath,
kTransientSettingsPath,
};
typedef uint32_t SettingFlags;
constexpr SettingFlags kSettingFlagNone = 0;
constexpr SettingFlags kSettingFlagTransient = 1 << 0;
constexpr SettingFlags kSettingFlagResetDisabled = 1 << 1;
constexpr SettingFlags kSettingFlagDefault = kSettingFlagNone;
constexpr size_t kHashedSettingPrefixMaxSize =
carb_max(carb::countOf(kSettingsPath), carb::countOf(kTransientSettingsPath)) + 1 + carb::countOf(kHashedSettingKey);
constexpr size_t kHashedSettingCStringMaxLength = kHashedSettingPrefixMaxSize + MD5::kDigestStringSize + 1;
class RenderSettings
{
public:
struct HashedSettingCString
{
char data[kHashedSettingCStringMaxLength];
};
static bool isPathRenderSettingsRoot(const char* path)
{
size_t rtxSettingsPathLen = CARB_COUNTOF(kSettingsPath) - 1;
size_t pathLen = strlen(path);
if (pathLen == rtxSettingsPathLen && strcmp(path, kSettingsPath) == 0)
{
return true;
}
return false;
}
static std::string getAssociatedDefaultsPath(const char* cPath)
{
carb::cpp::string_view path(carb::cpp::unsafe_length, cPath);
size_t pos = path.find_first_of("/", 1);
carb::cpp::string_view parentPath;
carb::cpp::string_view childPath;
if (pos == carb::cpp::string_view::npos)
{
parentPath = path;
}
else
{
parentPath = path.substr(0, pos);
childPath = path.substr(pos);
}
return carb::extras::join(parentPath, "-defaults", childPath);
}
template <typename KeyType, typename ValueType>
static void stringToKeyTypeToValueTypeDictionary(const std::string& keyToValueDictStr,
std::unordered_map<KeyType, ValueType>& outputDict,
const char* debugStr)
{
// Convert from string to a dictionary mapping KeyType values to ValueType values
//
// Example syntax: 0:0; 1:1,2; 2:3,4; 3:5; 4:6; 5:6; 6:6; 7;6"
// Example syntax: "AsphaltStandard:0; AsphaltWeathered:1; ConcreteRough:2"
// I.e.: use semicolon (';') to separate dictionary entries from each other,
// use colon (':') to separate key from value and
// use comma (',') to separate entries in a list of values
std::stringstream ss(keyToValueDictStr);
static constexpr char kMappingSep = ';';
static constexpr char kKeyValueSep = ':';
// Split by ';' into key:value mappings
std::vector<std::string> keyToValueStrVec = omni::extras::split(keyToValueDictStr, kMappingSep);
for (auto& keyToValue : keyToValueStrVec)
{
// Split a mapping into its key and value
std::vector<std::string> keyToValueStrVec = omni::extras::split(keyToValue, kKeyValueSep);
if (keyToValueStrVec.size() == 2) // this is a key,value pair
{
// find an appropriate overloaded function to add a key:value pair to the dictionary
addValueAtKey(keyToValueStrVec[0], keyToValueStrVec[1], outputDict, debugStr);
}
}
}
static inline bool hasSettingFlags(carb::settings::ISettings& settings, const char* settingPath)
{
std::string settingPathStr(kFlagsSettingsPath);
settingPathStr += settingPath;
return carb::settings::detail::getItemType(&settings, settingPathStr) != carb::dictionary::ItemType::eCount;
}
static inline SettingFlags getSettingFlags(carb::settings::ISettings& settings, const char* settingPath)
{
std::string settingPathStr(kFlagsSettingsPath);
settingPathStr += settingPath;
return SettingFlags(carb::settings::detail::get<int>(&settings, settingPathStr).value_or(0));
}
static inline void setSettingFlags(carb::settings::ISettings& settings, const char* settingPath, SettingFlags flags)
{
std::string settingPathStr(kFlagsSettingsPath);
settingPathStr += settingPath;
carb::settings::detail::set(&settings, settingPathStr, int64_t(flags));
}
static inline void removeSettingFlags(carb::settings::ISettings& settings, const char* settingPath)
{
std::string settingPathStr(kFlagsSettingsPath);
settingPathStr += settingPath;
carb::settings::detail::destroyItem(&settings, settingPathStr);
}
template <typename SettingType>
static void setAndBackupDefaultSetting(const char* settingPath,
SettingType value,
SettingFlags flags = kSettingFlagDefault)
{
carb::settings::ISettings* settings = getISettings();
settings->setDefault<SettingType>(settingPath, value);
bool allowReset = (flags & (kSettingFlagTransient | kSettingFlagResetDisabled)) == 0;
if (allowReset)
{
std::string defaultSettingPath = getAssociatedDefaultsPath(settingPath);
if (!defaultSettingPath.empty())
{
static constexpr carb::cpp::string_view persistentSettingsPath(kPersistentSettingsPath);
std::string path(defaultSettingPath);
if (path.substr(0, persistentSettingsPath.size()) == persistentSettingsPath.data())
{
settings->set<SettingType>(defaultSettingPath.c_str(), value);
}
else
{
settings->setDefault<SettingType>(
defaultSettingPath.c_str(), settings->get<SettingType>(settingPath));
}
}
}
// Set per setting flag (persistent, etc.)
setSettingFlags(*settings, settingPath, flags);
}
static inline std::string getInternalSettingString(const char* s)
{
return std::string(getHashedSettingString(s).data);
}
// TODO: Make private? It looks like this function is just subscribed for change events in
// setAndBackupPersistentDefaultSetting.
static void updateSetting(const carb::dictionary::Item* changedItem, carb::dictionary::ChangeEventType, void* userData)
{
if (!changedItem)
{
CARB_LOG_ERROR("Unexpected setting deletion");
}
const carb::cpp::string_view path(carb::cpp::unsafe_length, static_cast<const char*>(userData));
carb::dictionary::IDictionary* dictionary = getIDictionary();
carb::settings::ISettings* settings = getISettings();
carb::dictionary::ItemType itemType = dictionary->getItemType(changedItem);
if (itemType == carb::dictionary::ItemType::eString)
{
auto val = carb::dictionary::detail::getStringView(dictionary, changedItem).value_or("");
carb::settings::detail::set(settings, path, val);
}
else if (itemType == carb::dictionary::ItemType::eFloat)
{
settings->setFloat(path, dictionary->getAsFloat(changedItem));
}
else if (itemType == carb::dictionary::ItemType::eInt)
{
settings->setInt(path, dictionary->getAsInt(changedItem));
}
else if (itemType == carb::dictionary::ItemType::eBool)
{
settings->setBool(path, dictionary->getAsBool(changedItem));
}
}
template <typename SettingType>
static void setAndBackupPersistentDefaultSetting(const char* settingPath, SettingType value)
{
using namespace carb;
settings::ISettings* settings = getISettings();
std::string persistentPath = extras::join(cpp::unsafe_length, "/persistent", settingPath);
settings::detail::setDefault(settings, persistentPath, value);
std::string defaultSettingPath = getAssociatedDefaultsPath(persistentPath.c_str());
if (!defaultSettingPath.empty())
{
static constexpr cpp::string_view persistentSettingsPath(kPersistentSettingsPath);
std::string path(defaultSettingPath);
if (path.substr(0, persistentSettingsPath.size()) == persistentSettingsPath.data())
{
settings::detail::set(settings, defaultSettingPath, value);
}
else
{
settings::detail::set(settings, defaultSettingPath, settings::detail::get<SettingType>(persistentPath));
}
}
(void)settings::detail::subscribeToNodeChangeEvents(
settings, persistentPath, RenderSettings::updateSetting, (void*)(settingPath));
settings::detail::set(settings, cpp::string_view(cpp::unsafe_length, settingPath),
settings::detail::get<SettingType>(settings, persistentPath));
}
template <typename SettingArrayType>
static void setAndBackupDefaultSettingArray(const char* settingPath_,
const SettingArrayType* array,
size_t arrayLength,
SettingFlags flags = kSettingFlagDefault)
{
using namespace carb;
cpp::zstring_view settingPath(cpp::unsafe_length, settingPath_);
settings::ISettings* settings = getISettings();
settings::detail::setDefaultArray(settingPath, cpp::span<const SettingArrayType>(array, arrayLength));
bool allowReset = (flags & (kSettingFlagTransient | kSettingFlagResetDisabled)) == 0;
if (allowReset)
{
std::string defaultSettingPath = getAssociatedDefaultsPath(settingPath.c_str());
if (!defaultSettingPath.empty())
{
settings::detail::destroyItem(settings, defaultSettingPath);
const size_t arrayLength = settings::detail::getArrayLength(settings, settingPath);
for (size_t idx = 0; idx < arrayLength; ++idx)
{
std::string elementPath = extras::join(settingPath, "/", std::to_string(idx));
std::string defaultElementPath = extras::join(defaultSettingPath, "/", std::to_string(idx));
settings::detail::setDefault(
defaultElementPath, settings::detail::get<SettingArrayType>(elementPath));
}
}
}
// Set per setting flag (persistent, etc.)
setSettingFlags(*settings, settingPath.c_str(), flags);
}
static void resetSettingToDefault(const char* path_)
{
using namespace carb;
dictionary::IDictionary* dictionary = getIDictionary();
settings::ISettings* settings = getISettings();
cpp::zstring_view path(cpp::unsafe_length, path_);
auto defaultsPathStorage = getAssociatedDefaultsPath(path.c_str());
// Write lock so we can safely hold the dictionary pointer while writing to settings
settings::ScopedWrite writeLock;
const dictionary::Item* srcItem = settings::detail::getSettingsDictionary(settings, defaultsPathStorage);
dictionary::ItemType srcItemType = dictionary->getItemType(srcItem);
size_t srcItemArrayLength = dictionary->getArrayLength(srcItem);
if ((srcItemType == dictionary::ItemType::eDictionary) && (srcItemArrayLength == 0))
{
resetSectionToDefault(path.c_str(), writeLock);
return;
}
switch (srcItemType)
{
case dictionary::ItemType::eBool:
{
settings::detail::set(settings, path, dictionary->getAsBool(srcItem));
break;
}
case dictionary::ItemType::eInt:
{
settings::detail::set(settings, path, dictionary->getAsInt64(srcItem));
break;
}
case dictionary::ItemType::eFloat:
{
settings::detail::set(settings, path, dictionary->getAsFloat64(srcItem));
break;
}
case dictionary::ItemType::eString:
{
auto sv = dictionary::detail::getStringView(dictionary, srcItem).value_or("");
settings::detail::set(settings, path, sv);
break;
}
case dictionary::ItemType::eDictionary:
{
if (srcItemArrayLength > 0)
{
// FIXME: This is somewhat unsafe as update() has the potential to clobber the
// input pointer if the settings trees overlap.
settings::detail::update(
settings, path, srcItem, {}, carb::dictionary::kUpdateItemOverwriteOriginal, nullptr);
}
break;
}
default:
break;
}
}
static void resetSectionToDefault(const char* path_, [[maybe_unused]] const carb::settings::ScopedWrite& writeLock)
{
using namespace carb;
carb::settings::ISettings* settings = getISettings();
cpp::zstring_view path(cpp::unsafe_length, path_);
auto defaultsPath = getAssociatedDefaultsPath(path.c_str());
if (defaultsPath.empty())
{
CARB_LOG_ERROR("%s: failed to resolve default paths", __func__);
return;
}
// Do not delete existing original settings.
// If the source item exists (rtx-default value), overwrite the destination (original rtx value).
// Otherwise, leave them as-is. We want to keep original values until we find some default values in the
// future. Plugins may load at a later time and we should not remove original values until we have some
// default values.
// FIXME: This is somewhat unsafe as update() has the potential to clobber the
// input pointer if the settings trees overlap.
settings::detail::update(settings, path, settings->getSettingsDictionary(defaultsPath), {},
dictionary::kUpdateItemOverwriteOriginal, nullptr);
}
static carb::dictionary::Item* createContext()
{
return getIDictionary()->createItem(nullptr, "<rtx context>", carb::dictionary::ItemType::eDictionary);
}
static void destroyContext(carb::dictionary::Item* ctx)
{
getIDictionary()->destroyItem(ctx);
}
static void cloneToContext(carb::dictionary::Item* dstContext, carb::dictionary::Item* srcContext)
{
carb::dictionary::IDictionary* dict = getIDictionary();
carb::settings::ISettings* settings = getISettings();
dict->destroyItem(dstContext);
if (srcContext)
{
// TODO: It is undefined behavior to use dstContext after destroyItem above
dict->update(dstContext, "", srcContext, "", carb::dictionary::kUpdateItemOverwriteOriginal, nullptr);
}
else
{
dstContext = settings->createDictionaryFromSettings(kSettingsPath);
}
}
static void applyOverridesToContext(carb::dictionary::Item* dstContext, carb::dictionary::Item* overridesContext)
{
if (!overridesContext)
{
CARB_LOG_ERROR("%s needs context to override from", __FUNCTION__);
}
carb::dictionary::IDictionary* dict = getIDictionary();
dict->update(dstContext, "", overridesContext, "", carb::dictionary::kUpdateItemOverwriteOriginal, nullptr);
}
static carb::dictionary::IDictionary* getIDictionary()
{
return carb::getCachedInterface<carb::dictionary::IDictionary>();
}
static carb::settings::ISettings* getISettings()
{
return carb::getCachedInterface<carb::settings::ISettings>();
}
static void hashInternalRenderSettings()
{
using namespace carb;
auto settings = getISettings();
auto hashRtxSetting = [settings](cpp::zstring_view itemPath, uint32_t) -> uint32_t {
const dictionary::ItemType itemType = settings::detail::getItemType(settings, itemPath);
if (itemType == dictionary::ItemType::eDictionary)
{
return 0;
}
const auto newHashedPath = getHashedSettingString(itemPath.c_str());
switch (itemType)
{
case dictionary::ItemType::eBool:
settings->set(newHashedPath.data, settings::detail::get<bool>(settings, itemPath).value_or(false));
break;
case dictionary::ItemType::eFloat:
settings->set(newHashedPath.data, settings::detail::get<double>(settings, itemPath).value_or(0.));
break;
case dictionary::ItemType::eInt:
settings->set(newHashedPath.data, settings::detail::get<int64_t>(settings, itemPath).value_or(0));
break;
case dictionary::ItemType::eString:
// grabbing the string buffer is safe because this function is called under a ScopedWrite
settings->setString(
newHashedPath.data, settings::detail::getStringView(settings, itemPath).value_or(""));
break;
case dictionary::ItemType::eDictionary:
CARB_FALLTHROUGH;
default:
CARB_ASSERT(0);
}
if (hasSettingFlags(*settings, itemPath.c_str()))
{
setSettingFlags(*settings, newHashedPath.data, getSettingFlags(*settings, itemPath.c_str()));
removeSettingFlags(*settings, itemPath.c_str());
}
return 0;
};
dictionary::IDictionary* dictionary = getIDictionary();
for (const char* root : kInternalSettingRoots)
{
auto internalRoot = extras::join(cpp::unsafe_length, root, "/", kInternalSettingKey);
// hashRtxSetting() modifies settings so we need to hold a write lock to safely walk settings
settings::ScopedWrite writeLock;
settings::detail::walkSettings(
dictionary, settings, dictionary::WalkerMode::eSkipRoot, internalRoot, 0u, hashRtxSetting);
settings::detail::destroyItem(settings, internalRoot);
}
}
private:
// Key : Value = uint32_t : std::vector<uint32_t>
static void addValueAtKey(std::string key,
std::string value,
std::unordered_map<uint32_t, std::vector<uint32_t>>& outputDict,
const char* debugStr)
{
static const char kListElemSep = ',';
int32_t intKey = 0;
if (!omni::extras::stringToInteger(key, intKey))
{
CARB_LOG_WARN("Non-integer value %s set in \"%s\"", key.c_str(), debugStr);
}
// Split the value into a list of values
std::vector<std::string> intVecStrVec = omni::extras::split(value, kListElemSep);
int32_t intVecValueEntry = 0;
outputDict[(uint32_t)intKey].clear();
for (auto& intVecValueEntryStr : intVecStrVec)
{
if (omni::extras::stringToInteger(intVecValueEntryStr, intVecValueEntry))
{
outputDict[(uint32_t)intKey].push_back((uint32_t)intVecValueEntry);
}
else
{
CARB_LOG_WARN("Non-integer value %s set in \"%s\"", intVecValueEntryStr.c_str(), debugStr);
}
}
}
// Key : Value = std::string : uint32_t
static void addValueAtKey(std::string key,
std::string value,
std::unordered_map<std::string, uint32_t>& outputDict,
const char* debugStr)
{
int32_t outInt = 0;
if (omni::extras::stringToInteger(value, outInt))
{
outputDict[key] = (uint32_t)outInt;
}
else
{
CARB_LOG_WARN("Non-integer value %s set in \"%s\"", key.c_str(), debugStr);
}
}
static HashedSettingCString getHashedSettingString(const char* settingStr)
{
constexpr size_t sizeOfRtxSettingsRoot = carb::countOf(kSettingsPath) - 1;
const bool isRtxSettingsPath =
strncmp(settingStr, kSettingsPath, sizeOfRtxSettingsRoot) == 0 && settingStr[sizeOfRtxSettingsRoot] == '/';
// Default to kTransientSettingsPath for anything outside /rtx/.
// We don't promise that anything works for paths not rooted in /rtx[-transient]/internal, but implement
// reasonable behavior rather than crash or map all invalid settings to a null string. Improperly rooted strings
// will work fine except that hashInternalRenderSettings() won't know to translate them.
const char* hashedRoot = isRtxSettingsPath ? kSettingsPath : kTransientSettingsPath;
return getHashedSettingString(
MD5::getDigestString(MD5::run((const uint8_t*)settingStr, strlen(settingStr))), hashedRoot);
}
static HashedSettingCString getHashedSettingString(const MD5::DigestString& digestStr, const char* root)
{
HashedSettingCString result = {};
char* s = std::copy_n(root, strlen(root), result.data);
*s++ = '/';
s = std::copy_n(kHashedSettingKey, carb::countOf(kHashedSettingKey) - 1, s);
*s++ = '/';
s = std::copy_n(digestStr.s, MD5::kDigestStringSize, s);
*s = '\0';
return result;
}
};
inline bool enableAperture()
{
// Enabling Aperture mode adds 2 ray types.
return false;
}
inline bool enableMDLDistilledMtls()
{
auto&& init = [] {
carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
return settings->getAsBool("/rtx/mdltranslator/distillMaterial");
};
static bool result = init();
return result;
}
inline bool enableAbiskoMode()
{
auto&& init = [] {
carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
carb::settings::ScopedRead readLock; // read lock to hold the pointer
auto val = carb::settings::detail::getStringView(settings, "/rtx/rendermode");
return val == "abisko";
};
static bool result = init();
return result;
}
// Returns how many ray types we are going to use during the whole Kit rendering. This CAN'T be changed once the
// renderer is initialized
inline uint32_t getRayTypeCount()
{
auto&& init = [] {
uint32_t rayCount = 2; // MATERIAL_RAY and VISIBILITY_RAY for RTX are always available
if (!rtx::enableAbiskoMode()) // Abisko only has two ray types
{
if (rtx::enableAperture())
{
rayCount += 2; // APERTURE_MATERIAL_RAY and APERTURE_VISIBILTY_RAY
}
if (rtx::enableMDLDistilledMtls())
{
rayCount += 1; // DISTILLED_MATERIAL_RAY
}
}
return rayCount;
};
static uint32_t result = init();
return result;
}
inline bool setDefaultBoolEx(carb::settings::ISettings* settings, const char* path, bool value)
{
// Unfortunately need a write lock since we might be writing
using namespace carb;
const cpp::string_view pathView(cpp::unsafe_length, path);
settings::ScopedWrite writeLock;
if (settings::detail::getItemType(settings, pathView) != dictionary::ItemType::eCount)
return false;
settings::detail::set(settings, pathView, value);
return true;
}
} // namespace rtx