Add global Preferences dialog and per-viewport render delegate

Preferences (Edit > Preferences, tabbed General/Animation/Viewport):
- Viewport tab: Default Render Delegate and Color Correction settings
- Persisted to %APPDATA%\UsdLayerManager\preferences.ini
- Render delegate: startup default only (not live-applied to running tiles)
- Color correction: global, applied live to all viewports on change

Render delegate per-viewport:
- Each tile toolbar switches its own delegate independently
- Saved per-tile in viewport_settings.ini; restored on next launch
- Tiles with no saved delegate get the pref default on startup

Color correction global:
- Removed from gear popup; lives in Preferences > Viewport only
- Applied to all tiles on init and on live change from Preferences

Fix InitRenderer always applying m_currentRendererPlugin when set,
rather than skipping when UsdImagingGLEngine auto-selects a default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 11:43:32 +08:00
parent b02a736da0
commit cb0d72d980
8 changed files with 376 additions and 216 deletions
+244 -1
View File
@@ -2,6 +2,7 @@
#include "../utils/Logger.h"
#include "../utils/FileDialog.h"
#include "../utils/PathUtils.h"
#include "../utils/OcioConfigParser.h"
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/references.h>
#include <pxr/usd/sdf/path.h>
@@ -10,6 +11,7 @@
#include <imgui_internal.h>
#include <vector>
#include <string>
#include <fstream>
#include <filesystem>
#include <algorithm>
#include <cctype>
@@ -132,6 +134,7 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
m_curveEditorPanel->SetSelectedPrimPath(paths.empty() ? "" : paths.front());
};
if (!m_stageManager->CreateInMemoryStage()) {
LOG_ERROR("Failed to create default in-memory stage");
} else {
@@ -150,13 +153,21 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
}
}
// Load per-viewport settings (render delegate, grid, AA, etc.) from AppData.
// Load per-viewport settings and global preferences from AppData.
if (const char* appData = getenv("APPDATA")) {
namespace fs = std::filesystem;
fs::path dir = fs::path(appData) / "UsdLayerManager";
fs::create_directories(dir);
m_viewportSettingsPath = (dir / "viewport_settings.ini").string();
m_prefsPath = (dir / "preferences.ini").string();
m_viewportPanel->LoadSettings(m_viewportSettingsPath);
LoadPreferences();
// Apply pref delegate only to tiles that have no saved delegate.
m_viewportPanel->ApplyDefaultDelegate(m_prefs.renderDelegate);
// Color correction is always global — apply to all tiles.
m_viewportPanel->ApplyGlobalColorCorrection(
m_prefs.colorCorrectionMode, m_prefs.ocioDisplay,
m_prefs.ocioView, m_prefs.ocioColorSpace, m_prefs.ocioLook);
}
LOG_INFO("Application initialized successfully");
@@ -178,6 +189,8 @@ void Application::Run() {
void Application::Shutdown() {
if (m_viewportPanel && !m_viewportSettingsPath.empty())
m_viewportPanel->SaveSettings(m_viewportSettingsPath);
if (!m_prefsPath.empty())
SavePreferences();
m_viewportPanel.reset();
m_sceneHierarchyPanel.reset();
@@ -300,6 +313,9 @@ void Application::RenderUI() {
ImGui::End();
}
if (m_showPreferences)
RenderPreferencesDialog();
// Capture happens after the timeline so that the "Start" button click in
// this frame is detected, and after the viewport has rendered the scene.
if (m_timelinePanel->IsPlayblasting()) {
@@ -346,6 +362,229 @@ void Application::RenderStatusBar() {
ImGui::End();
}
// ---------------------------------------------------------------------------
// Global preferences persistence
// ---------------------------------------------------------------------------
void Application::LoadPreferences()
{
std::ifstream f(m_prefsPath);
if (!f) return;
std::string line;
while (std::getline(f, line)) {
if (!line.empty() && line.back() == '\r') line.pop_back();
if (line.empty() || line.front() == '#' || line.front() == '[') continue;
size_t eq = line.find('=');
if (eq == std::string::npos) continue;
std::string key = line.substr(0, eq);
std::string val = line.substr(eq + 1);
if (key == "RenderDelegate") m_prefs.renderDelegate = val;
else if (key == "ColorCorrectionMode")
m_prefs.colorCorrectionMode = [&]{ try { return std::stoi(val); } catch(...){ return 1; } }();
else if (key == "OcioDisplay") m_prefs.ocioDisplay = val;
else if (key == "OcioView") m_prefs.ocioView = val;
else if (key == "OcioColorSpace") m_prefs.ocioColorSpace = val;
else if (key == "OcioLook") m_prefs.ocioLook = val;
}
}
void Application::SavePreferences()
{
std::ofstream f(m_prefsPath);
if (!f) return;
f << "[Preferences]\n";
f << "RenderDelegate=" << m_prefs.renderDelegate << "\n";
f << "ColorCorrectionMode=" << m_prefs.colorCorrectionMode << "\n";
f << "OcioDisplay=" << m_prefs.ocioDisplay << "\n";
f << "OcioView=" << m_prefs.ocioView << "\n";
f << "OcioColorSpace=" << m_prefs.ocioColorSpace << "\n";
f << "OcioLook=" << m_prefs.ocioLook << "\n";
}
void Application::ApplyPrefsToAllViewports()
{
if (!m_viewportPanel) return;
m_viewportPanel->ApplyGlobalColorCorrection(
m_prefs.colorCorrectionMode,
m_prefs.ocioDisplay,
m_prefs.ocioView,
m_prefs.ocioColorSpace,
m_prefs.ocioLook);
}
void Application::RenderPreferencesDialog()
{
ImGui::SetNextWindowSize(ImVec2(520, 400), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
if (!ImGui::Begin("Preferences", &m_showPreferences,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking))
{
ImGui::End();
return;
}
bool ccChanged = false;
if (ImGui::BeginTabBar("PrefTabs")) {
// ── General ─────────────────────────────────────────────────────────
if (ImGui::BeginTabItem("General")) {
ImGui::Spacing();
ImGui::TextDisabled("No general settings yet.");
ImGui::EndTabItem();
}
// ── Animation ───────────────────────────────────────────────────────
if (ImGui::BeginTabItem("Animation")) {
ImGui::Spacing();
ImGui::TextDisabled("No animation settings yet.");
ImGui::EndTabItem();
}
// ── Viewport ────────────────────────────────────────────────────────
if (ImGui::BeginTabItem("Viewport")) {
ImGui::Spacing();
// Render Delegate default — applied to viewports with no saved delegate on startup.
// Each viewport's toolbar controls its delegate independently after load.
ImGui::SeparatorText("Default Render Delegate");
ImGui::TextDisabled("Applied to viewports with no saved setting on startup.");
ImGui::Spacing();
auto plugins = UsdSceneRenderer::GetRendererPlugins();
pxr::TfToken curId(m_prefs.renderDelegate);
for (const auto& pluginId : plugins) {
std::string name = UsdSceneRenderer::GetRendererDisplayName(pluginId);
if (name.empty()) name = pluginId.GetString();
bool sel = (pluginId == curId);
if (ImGui::RadioButton(name.c_str(), sel))
m_prefs.renderDelegate = pluginId.GetString();
}
ImGui::Spacing();
// Color Correction
ImGui::SeparatorText("Color Correction");
auto ccItem = [&](const char* label, int mode) {
if (ImGui::RadioButton(label, m_prefs.colorCorrectionMode == mode)) {
m_prefs.colorCorrectionMode = mode;
m_prefOcioFieldsSynced = false;
ccChanged = true;
}
};
ccItem("Disabled", 0);
ccItem("sRGB", 1);
ccItem("OpenColorIO", 2);
if (m_prefs.colorCorrectionMode == 2) {
ImGui::Spacing();
const OcioConfig& ocfg = GetCurrentOcioConfig();
if (!m_prefOcioFieldsSynced) {
std::string disp = m_prefs.ocioDisplay.empty() ? ocfg.defaultDisplay : m_prefs.ocioDisplay;
std::string view = m_prefs.ocioView.empty() ? ocfg.defaultView : m_prefs.ocioView;
strncpy(m_prefOcioDisplayBuf, disp.c_str(), 127);
strncpy(m_prefOcioViewBuf, view.c_str(), 127);
strncpy(m_prefOcioColorSpaceBuf, m_prefs.ocioColorSpace.c_str(), 127);
strncpy(m_prefOcioLookBuf, m_prefs.ocioLook.c_str(), 127);
if (m_prefs.ocioDisplay.empty()) m_prefs.ocioDisplay = disp;
if (m_prefs.ocioView.empty()) m_prefs.ocioView = view;
m_prefOcioFieldsSynced = true;
}
ImGui::PushItemWidth(280.f);
if (ImGui::BeginCombo("Display##pref", m_prefOcioDisplayBuf)) {
for (const auto& d : ocfg.displays) {
bool sel = (d == m_prefOcioDisplayBuf);
if (ImGui::Selectable(d.c_str(), sel)) {
strncpy(m_prefOcioDisplayBuf, d.c_str(), 127);
m_prefs.ocioDisplay = d;
auto vit = ocfg.views.find(d);
if (vit != ocfg.views.end() && !vit->second.empty()) {
strncpy(m_prefOcioViewBuf, vit->second[0].c_str(), 127);
m_prefs.ocioView = vit->second[0];
}
ccChanged = true;
}
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
{
static const std::vector<std::string> kEmpty;
auto vit = ocfg.views.find(std::string(m_prefOcioDisplayBuf));
const auto& views = (vit != ocfg.views.end()) ? vit->second : kEmpty;
if (ImGui::BeginCombo("View##pref", m_prefOcioViewBuf)) {
for (const auto& v : views) {
bool sel = (v == m_prefOcioViewBuf);
if (ImGui::Selectable(v.c_str(), sel)) {
strncpy(m_prefOcioViewBuf, v.c_str(), 127);
m_prefs.ocioView = v;
ccChanged = true;
}
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
}
if (ImGui::BeginCombo("Color Space##pref",
m_prefOcioColorSpaceBuf[0] ? m_prefOcioColorSpaceBuf : "(default)")) {
if (ImGui::Selectable("(default)", m_prefOcioColorSpaceBuf[0] == '\0')) {
m_prefOcioColorSpaceBuf[0] = '\0';
m_prefs.ocioColorSpace.clear();
ccChanged = true;
}
if (m_prefOcioColorSpaceBuf[0] == '\0') ImGui::SetItemDefaultFocus();
for (const auto& cs : ocfg.colorSpaces) {
bool sel = (cs == m_prefOcioColorSpaceBuf);
if (ImGui::Selectable(cs.c_str(), sel)) {
strncpy(m_prefOcioColorSpaceBuf, cs.c_str(), 127);
m_prefs.ocioColorSpace = cs;
ccChanged = true;
}
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
if (ImGui::BeginCombo("Look##pref",
m_prefOcioLookBuf[0] ? m_prefOcioLookBuf : "(none)")) {
if (ImGui::Selectable("(none)", m_prefOcioLookBuf[0] == '\0')) {
m_prefOcioLookBuf[0] = '\0';
m_prefs.ocioLook.clear();
ccChanged = true;
}
if (m_prefOcioLookBuf[0] == '\0') ImGui::SetItemDefaultFocus();
for (const auto& look : ocfg.looks) {
bool sel = (look == m_prefOcioLookBuf);
if (ImGui::Selectable(look.c_str(), sel)) {
strncpy(m_prefOcioLookBuf, look.c_str(), 127);
m_prefs.ocioLook = look;
ccChanged = true;
}
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
if (ccChanged)
ApplyPrefsToAllViewports();
ImGui::End();
}
void Application::RenderMenuBar() {
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
@@ -403,6 +642,10 @@ void Application::RenderMenuBar() {
m_commandHistory.Redo();
ImGui::EndDisabled();
ImGui::Separator();
if (ImGui::MenuItem("Preferences..."))
m_showPreferences = true;
ImGui::EndMenu();
}
}