Add custom viewport color correction (sRGB/OCIO), bypassing HdxColorCorrectionTask

Hydra's HdxColorCorrectionTask rendered prims black in OCIO mode and could
corrupt the GlfDrawTarget bind stack on failure (skipping Unbind), blacking
out every later frame including sRGB. Replace it with our own GL post-process:
the scene renders linear (RGBA16F) and is corrected by a fullscreen shader --
linear->sRGB encode, or OCIO via the OCIO 2.1 GPU API (GpuShaderDesc plus
uploaded 1D/3D LUT textures). OCIO build failures fall back to sRGB (never
black) and USD diagnostics are routed to the app log.

- core: ViewportColorCorrector + ApplyViewportColorCorrection in UsdSceneRenderer
- utils: OcioConfigParser enumerates displays/views/colorspaces/looks from $OCIO
- ui: gear-menu OCIO controls (ViewportTile) + per-viewport persistence (ViewportPanel)
- Application: point $OCIO at the bundled ACES 1.2 config
- CMake: link/copy OpenColorIO, download ACES 1.2 config; plus hdCycles build
  config (disable OpenVDB/Embree, fix TBB/OpenSubdiv/Imath dirs, exclude CRT DLLs)
- main: pre-flight plugin DLL load check to skip plugins with missing deps

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 17:10:44 +08:00
parent 8316e419ba
commit fb1ae9d3a6
12 changed files with 872 additions and 23 deletions
+12
View File
@@ -138,6 +138,18 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
RefreshManagers();
}
// Set OCIO to bundled ACES 1.2 config if not already configured externally.
// Must happen before the first Render() call so Hydra picks up the env var.
if (!getenv("OCIO")) {
namespace fs = std::filesystem;
std::string ocioConfig = ResourcePath(
"resources/OpenColorIO-Configs/aces_1.2/config.ocio");
if (fs::exists(ocioConfig)) {
_putenv_s("OCIO", ocioConfig.c_str());
LOG_INFO("OCIO config: " + ocioConfig);
}
}
// Load per-viewport settings (render delegate, grid, AA, etc.) from AppData.
if (const char* appData = getenv("APPDATA")) {
namespace fs = std::filesystem;
+12 -2
View File
@@ -635,7 +635,12 @@ void ViewportPanel::SaveSettings(const std::string& path) const
f << "BBoxMode=" << s.bboxMode << "\n";
f << "AmbientLightOnly=" << (s.ambientLightOnly ? 1 : 0) << "\n";
f << "DomeLightEnabled=" << (s.domeLightEnabled ? 1 : 0) << "\n";
f << "ShadingMode=" << s.shadingMode << "\n";
f << "ShadingMode=" << s.shadingMode << "\n";
f << "ColorCorrectionMode=" << s.colorCorrectionMode << "\n";
f << "OcioDisplay=" << s.ocioDisplay << "\n";
f << "OcioView=" << s.ocioView << "\n";
f << "OcioColorSpace=" << s.ocioColorSpace << "\n";
f << "OcioLook=" << s.ocioLook << "\n";
}
}
@@ -701,7 +706,12 @@ void ViewportPanel::LoadSettings(const std::string& path)
else if (key == "BBoxMode") ts.bboxMode = toInt(val, ts.bboxMode);
else if (key == "AmbientLightOnly") ts.ambientLightOnly = (val == "1");
else if (key == "DomeLightEnabled") ts.domeLightEnabled = (val == "1");
else if (key == "ShadingMode") ts.shadingMode = toInt(val, ts.shadingMode);
else if (key == "ShadingMode") ts.shadingMode = toInt(val, ts.shadingMode);
else if (key == "ColorCorrectionMode") ts.colorCorrectionMode = toInt(val, ts.colorCorrectionMode);
else if (key == "OcioDisplay") ts.ocioDisplay = val;
else if (key == "OcioView") ts.ocioView = val;
else if (key == "OcioColorSpace") ts.ocioColorSpace = val;
else if (key == "OcioLook") ts.ocioLook = val;
}
}
+131 -1
View File
@@ -1,5 +1,6 @@
#include "ViewportTile.h"
#include "../utils/Logger.h"
#include "../utils/OcioConfigParser.h"
#include <pxr/usd/usdGeom/camera.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>
@@ -99,7 +100,12 @@ ViewportTileSettings ViewportTile::GetSettings() const
s.bboxMode = static_cast<int>(m_renderer.GetBBoxMode());
s.ambientLightOnly = m_renderer.GetAmbientLightOnly();
s.domeLightEnabled = m_renderer.GetDomeLightEnabled();
s.shadingMode = static_cast<int>(m_renderer.GetShadingMode());
s.shadingMode = static_cast<int>(m_renderer.GetShadingMode());
s.colorCorrectionMode = static_cast<int>(m_renderer.GetColorCorrectionMode());
s.ocioDisplay = m_renderer.GetOcioDisplay();
s.ocioView = m_renderer.GetOcioView();
s.ocioColorSpace = m_renderer.GetOcioColorSpace();
s.ocioLook = m_renderer.GetOcioLook();
return s;
}
@@ -114,6 +120,12 @@ void ViewportTile::ApplySettings(const ViewportTileSettings& s)
m_renderer.SetAmbientLightOnly(s.ambientLightOnly);
m_renderer.SetDomeLightEnabled(s.domeLightEnabled);
m_renderer.SetShadingMode(static_cast<ShadingMode>(s.shadingMode));
m_renderer.SetColorCorrectionMode(static_cast<ColorCorrectionMode>(s.colorCorrectionMode));
m_renderer.SetOcioDisplay(s.ocioDisplay);
m_renderer.SetOcioView(s.ocioView);
m_renderer.SetOcioColorSpace(s.ocioColorSpace);
m_renderer.SetOcioLook(s.ocioLook);
m_ocioFieldsSynced = false; // re-seed OCIO edit buffers from restored values
}
// ---------------------------------------------------------------------------
@@ -985,6 +997,124 @@ void ViewportTile::RenderCompactToolbar(int tileIndex)
if (ImGui::BeginPopupContextItem(gearPopupId.c_str(),
ImGuiPopupFlags_MouseButtonLeft))
{
// -- Color correction --------------------------------------------
if (ImGui::BeginMenu("Color Correction")) {
ColorCorrectionMode ccm = m_renderer.GetColorCorrectionMode();
auto ccItem = [&](const char* label, ColorCorrectionMode mode) {
if (ImGui::MenuItem(label, nullptr, ccm == mode)) {
m_renderer.SetColorCorrectionMode(mode);
m_ocioFieldsSynced = false;
}
};
ccItem("Disabled", ColorCorrectionMode::Disabled);
ccItem("sRGB", ColorCorrectionMode::sRGB);
ccItem("OpenColorIO", ColorCorrectionMode::OpenColorIO);
if (ccm == ColorCorrectionMode::OpenColorIO) {
ImGui::Separator();
const OcioConfig& ocfg = GetCurrentOcioConfig();
// Seed buffers from renderer, falling back to OCIO config defaults
if (!m_ocioFieldsSynced) {
std::string disp = m_renderer.GetOcioDisplay();
std::string view = m_renderer.GetOcioView();
if (disp.empty()) disp = ocfg.defaultDisplay;
if (view.empty()) view = ocfg.defaultView;
strncpy(m_ocioDisplayBuf, disp.c_str(), 127);
strncpy(m_ocioViewBuf, view.c_str(), 127);
strncpy(m_ocioColorSpaceBuf, m_renderer.GetOcioColorSpace().c_str(), 127);
strncpy(m_ocioLookBuf, m_renderer.GetOcioLook().c_str(), 127);
if (m_renderer.GetOcioDisplay().empty() && !disp.empty())
m_renderer.SetOcioDisplay(disp);
if (m_renderer.GetOcioView().empty() && !view.empty())
m_renderer.SetOcioView(view);
m_ocioFieldsSynced = true;
}
ImGui::PushItemWidth(200.f);
// Display combo
if (ImGui::BeginCombo("Display##ocio", m_ocioDisplayBuf)) {
for (const auto& d : ocfg.displays) {
bool sel = (d == m_ocioDisplayBuf);
if (ImGui::Selectable(d.c_str(), sel)) {
strncpy(m_ocioDisplayBuf, d.c_str(), 127);
m_renderer.SetOcioDisplay(d);
// Auto-select default view for new display
auto vit = ocfg.views.find(d);
if (vit != ocfg.views.end() && !vit->second.empty()) {
strncpy(m_ocioViewBuf, vit->second[0].c_str(), 127);
m_renderer.SetOcioView(vit->second[0]);
}
}
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
// View combo — filtered by current display
{
static const std::vector<std::string> kEmpty;
auto vit = ocfg.views.find(std::string(m_ocioDisplayBuf));
const auto& views = (vit != ocfg.views.end()) ? vit->second : kEmpty;
if (ImGui::BeginCombo("View##ocio", m_ocioViewBuf)) {
for (const auto& v : views) {
bool sel = (v == m_ocioViewBuf);
if (ImGui::Selectable(v.c_str(), sel)) {
strncpy(m_ocioViewBuf, v.c_str(), 127);
m_renderer.SetOcioView(v);
}
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
}
// Color Space combo
if (ImGui::BeginCombo("Color Space##ocio",
m_ocioColorSpaceBuf[0] ? m_ocioColorSpaceBuf : "(default)")) {
if (ImGui::Selectable("(default)", m_ocioColorSpaceBuf[0] == '\0')) {
m_ocioColorSpaceBuf[0] = '\0';
m_renderer.SetOcioColorSpace("");
}
if (m_ocioColorSpaceBuf[0] == '\0') ImGui::SetItemDefaultFocus();
for (const auto& cs : ocfg.colorSpaces) {
bool sel = (cs == m_ocioColorSpaceBuf);
if (ImGui::Selectable(cs.c_str(), sel)) {
strncpy(m_ocioColorSpaceBuf, cs.c_str(), 127);
m_renderer.SetOcioColorSpace(cs);
}
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
// Look combo
if (ImGui::BeginCombo("Look##ocio",
m_ocioLookBuf[0] ? m_ocioLookBuf : "(none)")) {
if (ImGui::Selectable("(none)", m_ocioLookBuf[0] == '\0')) {
m_ocioLookBuf[0] = '\0';
m_renderer.SetOcioLook("");
}
if (m_ocioLookBuf[0] == '\0') ImGui::SetItemDefaultFocus();
for (const auto& look : ocfg.looks) {
bool sel = (look == m_ocioLookBuf);
if (ImGui::Selectable(look.c_str(), sel)) {
strncpy(m_ocioLookBuf, look.c_str(), 127);
m_renderer.SetOcioLook(look);
}
if (sel) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
}
ImGui::EndMenu();
}
ImGui::Separator();
// -- Shading mode ------------------------------------------------
if (ImGui::BeginMenu("Shading")) {
ShadingMode cur = m_renderer.GetShadingMode();
+14 -2
View File
@@ -26,9 +26,14 @@ struct ViewportTileSettings {
float bgColorG = 0.15f;
float bgColorB = 0.15f;
int bboxMode = 0; ///< BBoxMode cast to int
bool ambientLightOnly = false;
bool ambientLightOnly = true;
bool domeLightEnabled = false;
int shadingMode = 0; ///< ShadingMode cast to int (0 = SmoothShaded)
int shadingMode = 0; ///< ShadingMode cast to int (0 = SmoothShaded)
int colorCorrectionMode = 1; ///< ColorCorrectionMode cast to int (1 = sRGB)
std::string ocioDisplay;
std::string ocioView;
std::string ocioColorSpace;
std::string ocioLook;
};
/// Named orthographic view directions.
@@ -178,6 +183,13 @@ private:
// ── Orthographic view ────────────────────────────────────────────────────
OrthoView m_orthoView = OrthoView::None;
// ── OCIO InputText edit buffers (per-tile, seeded on mode activation) ────
char m_ocioDisplayBuf[128] = {};
char m_ocioViewBuf[128] = {};
char m_ocioColorSpaceBuf[128] = {};
char m_ocioLookBuf[128] = {};
bool m_ocioFieldsSynced = false;
// ── Per-frame interaction flags ──────────────────────────────────────────
bool m_wasClickedThisFrame = false;
bool m_wasHoveredThisFrame = false;