Compare commits

...

1 Commits

Author SHA1 Message Date
indigo 05201465be Validate persisted OCIO preferences against the active config
A display or view saved in preferences.ini that doesn't exist in the current
OCIO config made HdxColorCorrectionTask throw every frame
("DisplayViewTransform error. Display 'x' not found") and silently skip color
correction, which reads as a washed-out viewport -- worst on delegates like
hdEmbree whose output depends on the transform actually running.

The two are easy to invert: in the bundled ACES 1.2 config the only display is
"ACES" and "sRGB" is one of its views, but the shipped preferences had
OcioDisplay=sRGB and OcioView="ACES 1.0 SDR-video" (an OCIO-v2 studio-config
view that doesn't exist here). Nothing validated them -- LoadPreferences fed
ApplyGlobalColorCorrection directly, and the only existing fallback lived in
the preferences dialog and triggered on empty, never on invalid.

ValidateOcioPreferences() now runs right after LoadPreferences() so both the
viewport and the Material Editor see corrected values:
- display falls back to the config default when missing
- view is checked against the *resolved* display's view list, since correcting
  the display can invalidate the view; prefers the config default view when
  valid for that display, else its first view
- color space and look are cleared when they name something absent, as they
  fail the same way (note the real name is "ACES - ACEScg", not "ACEScg")
- no-op when the config is unreadable, so a missing $OCIO doesn't clobber
  values that may be correct for a config supplied later

Each correction logs a warning naming the old and new value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 10:24:24 +08:00
2 changed files with 75 additions and 0 deletions
+71
View File
@@ -208,6 +208,7 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
m_prefsPath = (dir / "preferences.ini").string(); m_prefsPath = (dir / "preferences.ini").string();
m_viewportPanel->LoadSettings(m_viewportSettingsPath); m_viewportPanel->LoadSettings(m_viewportSettingsPath);
LoadPreferences(); LoadPreferences();
ValidateOcioPreferences();
// Apply pref delegate only to tiles that have no saved delegate. // Apply pref delegate only to tiles that have no saved delegate.
m_viewportPanel->ApplyDefaultDelegate(m_prefs.renderDelegate); m_viewportPanel->ApplyDefaultDelegate(m_prefs.renderDelegate);
// Color correction is always global — apply to all tiles. // Color correction is always global — apply to all tiles.
@@ -480,6 +481,76 @@ void Application::LoadPreferences()
} }
} }
void Application::ValidateOcioPreferences()
{
// A persisted display/view that doesn't exist in the active OCIO config
// makes HdxColorCorrectionTask throw ("Display 'x' not found") every frame
// and silently skip correction, which reads as a washed-out viewport --
// worst on delegates like hdEmbree whose output depends on the transform.
// Note display and view are easy to invert: in the bundled ACES 1.2 config
// the only display is "ACES" and "sRGB" is one of its views.
const OcioConfig& cfg = GetCurrentOcioConfig();
if (!cfg.valid) {
// No $OCIO / unreadable config -- nothing to validate against. Leave
// the prefs alone rather than clobbering values that may be correct
// for a config supplied later.
return;
}
auto contains = [](const std::vector<std::string>& v, const std::string& s) {
return std::find(v.begin(), v.end(), s) != v.end();
};
// --- display ---
if (m_prefs.ocioDisplay.empty() || !contains(cfg.displays, m_prefs.ocioDisplay)) {
if (!m_prefs.ocioDisplay.empty()) {
LOG_WARNING("OCIO display '" + m_prefs.ocioDisplay
+ "' not in config; falling back to '"
+ cfg.defaultDisplay + "'");
}
m_prefs.ocioDisplay = cfg.defaultDisplay;
}
// --- view (must belong to the display resolved above) ---
auto viewsIt = cfg.views.find(m_prefs.ocioDisplay);
const std::vector<std::string>* views =
(viewsIt != cfg.views.end()) ? &viewsIt->second : nullptr;
if (views && !views->empty()) {
if (m_prefs.ocioView.empty() || !contains(*views, m_prefs.ocioView)) {
// Prefer the config default when it's valid for this display,
// otherwise take the display's first view.
const std::string fallback =
contains(*views, cfg.defaultView) ? cfg.defaultView : views->front();
if (!m_prefs.ocioView.empty()) {
LOG_WARNING("OCIO view '" + m_prefs.ocioView
+ "' not valid for display '" + m_prefs.ocioDisplay
+ "'; falling back to '" + fallback + "'");
}
m_prefs.ocioView = fallback;
}
}
// --- color space (same failure mode if it names a missing space) ---
if (!m_prefs.ocioColorSpace.empty()
&& !contains(cfg.colorSpaces, m_prefs.ocioColorSpace)) {
LOG_WARNING("OCIO color space '" + m_prefs.ocioColorSpace
+ "' not in config; clearing it");
m_prefs.ocioColorSpace.clear();
}
// --- look ---
if (!m_prefs.ocioLook.empty()
&& !contains(cfg.looks, m_prefs.ocioLook)) {
LOG_WARNING("OCIO look '" + m_prefs.ocioLook
+ "' not in config; clearing it");
m_prefs.ocioLook.clear();
}
LOG_INFO("OCIO display/view: '" + m_prefs.ocioDisplay + "' / '"
+ m_prefs.ocioView + "'");
}
void Application::SavePreferences() void Application::SavePreferences()
{ {
// Pull the latest splitter positions; called from Shutdown before the // Pull the latest splitter positions; called from Shutdown before the
+4
View File
@@ -79,6 +79,10 @@ private:
// Preferences // Preferences
void LoadPreferences(); void LoadPreferences();
void SavePreferences(); void SavePreferences();
/// Reconcile persisted OCIO display/view names against the active config.
/// Falls back to the config defaults when a name doesn't exist, so a stale
/// or mistyped preference can't silently disable color correction.
void ValidateOcioPreferences();
void ApplyPrefsToAllViewports(); void ApplyPrefsToAllViewports();
void RenderPreferencesDialog(); void RenderPreferencesDialog();