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
+400 -3
View File
@@ -23,11 +23,21 @@
#include <pxr/base/gf/rect2i.h>
#include <pxr/base/gf/frustum.h>
#include <pxr/base/gf/rotation.h>
#include <pxr/base/tf/diagnosticMgr.h>
#include <pxr/base/tf/error.h>
#include <pxr/base/tf/warning.h>
#include <pxr/base/tf/status.h>
#include <OpenColorIO/OpenColorIO.h>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <exception>
#include <unordered_set>
namespace OCIO = OCIO_NAMESPACE;
namespace UsdLayerManager {
// ---------------------------------------------------------------------------
@@ -39,6 +49,37 @@ static std::string Vec3fStr(const pxr::GfVec3f& v) {
+ std::to_string(v[2]) + ")";
}
// Forwards USD's TfError / TF_WARN / TF_STATUS messages into the app log.
// Without this, Hydra diagnostics (e.g. the reason HdxColorCorrectionTask
// fails to build an OCIO processor) only go to a stderr console the GUI
// never shows. Installed once, process-wide, from InitRenderer().
namespace {
class UsdDiagnosticLogger : public pxr::TfDiagnosticMgr::Delegate {
public:
void IssueError(pxr::TfError const& err) override {
LOG_ERROR("[USD] " + err.GetCommentary());
}
void IssueFatalError(pxr::TfCallContext const&,
std::string const& msg) override {
LOG_ERROR("[USD fatal] " + msg);
}
void IssueStatus(pxr::TfStatus const& status) override {
LOG_INFO("[USD] " + status.GetCommentary());
}
void IssueWarning(pxr::TfWarning const& warning) override {
LOG_WARNING("[USD] " + warning.GetCommentary());
}
};
void InstallUsdDiagnosticLogger() {
static UsdDiagnosticLogger s_logger; // process-lifetime; never removed
static bool s_installed = false;
if (s_installed) return;
s_installed = true;
pxr::TfDiagnosticMgr::GetInstance().AddDelegate(&s_logger);
}
} // namespace
/// Port of stageView._ComputeCameraFraming():
/// Converts a Y-up integer viewport rect into a CameraUtilFraming whose
/// display/data windows are expressed in the Y-down coordinate system that
@@ -102,6 +143,271 @@ static GLuint LinkProgram(GLuint vs, GLuint fs) {
return prog;
}
// ===========================================================================
// ViewportColorCorrector — custom GL color correction (sRGB / OCIO)
// ===========================================================================
//
// Replaces HdxColorCorrectionTask. The scene is rendered linear; Apply()
// samples that linear texture and draws a corrected fullscreen triangle into
// the currently-bound FBO. For OCIO it uses the OCIO GPU shader API directly
// (GpuShaderDesc) so the exact transform — including 1D/3D LUTs — is built and
// bound under our control, independent of Hydra.
class ViewportColorCorrector {
public:
enum class Mode { sRGB, OpenColorIO };
~ViewportColorCorrector() { DestroyGLResources(); }
/// Draw a corrected fullscreen quad into the bound FBO sampling srcTex
/// (single-sample, linear RGBA). Caller has set the GL viewport.
/// Returns true if a pass was drawn. On OCIO build failure it falls back
/// to the sRGB encode so the viewport degrades gracefully (never black).
bool Apply(GLuint srcTex, Mode mode,
const std::string& disp, const std::string& view,
const std::string& cs, const std::string& look)
{
if (!EnsureCommon()) return false;
GLuint prog = m_srgbProgram;
std::vector<LutTex>* luts = nullptr;
if (mode == Mode::OpenColorIO && EnsureOcioProgram(disp, view, cs, look)) {
prog = m_ocioProgram;
luts = &m_ocioLuts;
}
if (!prog) return false;
// Save the GL state we touch.
GLboolean depthTest = glIsEnabled(GL_DEPTH_TEST);
GLboolean blend = glIsEnabled(GL_BLEND);
GLboolean depthMask = GL_TRUE;
glGetBooleanv(GL_DEPTH_WRITEMASK, &depthMask);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glDepthMask(GL_FALSE);
glUseProgram(prog);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, srcTex);
glUniform1i(glGetUniformLocation(prog, "uTex"), 0);
if (luts) {
int unit = 1;
for (const auto& l : *luts) {
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(l.target, l.id);
GLint loc = glGetUniformLocation(prog, l.sampler.c_str());
if (loc >= 0) glUniform1i(loc, unit);
++unit;
}
glActiveTexture(GL_TEXTURE0);
}
glBindVertexArray(m_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
// Restore state.
if (depthTest) glEnable(GL_DEPTH_TEST);
if (blend) glEnable(GL_BLEND);
glDepthMask(depthMask);
return true;
}
void DestroyGLResources() {
DestroyOcioResources();
if (m_srgbProgram) { glDeleteProgram(m_srgbProgram); m_srgbProgram = 0; }
if (m_vao) { glDeleteVertexArrays(1, &m_vao); m_vao = 0; }
}
private:
struct LutTex { GLuint id; GLenum target; std::string sampler; };
bool EnsureCommon() {
if (m_vao == 0) glGenVertexArrays(1, &m_vao);
if (m_srgbProgram == 0) {
GLuint vs = CompileShader(kFullscreenVS, GL_VERTEX_SHADER);
GLuint fs = CompileShader(kSrgbFS, GL_FRAGMENT_SHADER);
if (vs && fs) m_srgbProgram = LinkProgram(vs, fs);
}
return m_vao != 0 && m_srgbProgram != 0;
}
bool EnsureOcioProgram(const std::string& disp, const std::string& view,
const std::string& cs, const std::string& look)
{
const std::string key = disp + "|" + view + "|" + cs + "|" + look;
if (m_ocioProgram && key == m_ocioKey) return true;
if (key == m_ocioFailedKey) return false;
DestroyOcioResources();
m_ocioKey.clear();
std::string fragText;
OCIO::GpuShaderDescRcPtr desc;
try {
OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
if (!config) throw std::runtime_error("no current OCIO config");
const char* srcCS = cs.empty() ? OCIO::ROLE_SCENE_LINEAR : cs.c_str();
OCIO::ConstProcessorRcPtr proc = config->getProcessor(
srcCS, disp.c_str(), view.c_str(), OCIO::TRANSFORM_DIR_FORWARD);
OCIO::ConstGPUProcessorRcPtr gpu = proc->getDefaultGPUProcessor();
desc = OCIO::GpuShaderDesc::CreateShaderDesc();
desc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3);
desc->setFunctionName("OCIODisplay");
desc->setResourcePrefix("ocio_");
gpu->extractGpuShaderInfo(desc);
fragText = desc->getShaderText();
} catch (const std::exception& e) {
LOG_WARNING("Custom OCIO build failed (" + key
+ "), using sRGB: " + std::string(e.what()));
m_ocioFailedKey = key;
return false;
}
// Assemble the fragment shader: OCIO declares its samplers + the
// OCIODisplay(vec4) function; our main() samples the linear input and
// runs it through.
std::string fs = "#version 130\n"
"uniform sampler2D uTex;\n"
"in vec2 vUv;\n"
"out vec4 outColor;\n"
+ fragText +
"\nvoid main(){ outColor = OCIODisplay(texture(uTex, vUv)); }\n";
GLuint vsh = CompileShader(kFullscreenVS, GL_VERTEX_SHADER);
GLuint fsh = CompileShader(fs.c_str(), GL_FRAGMENT_SHADER);
GLuint prog = (vsh && fsh) ? LinkProgram(vsh, fsh) : 0;
if (!prog) {
LOG_WARNING("Custom OCIO shader compile/link failed for " + key
+ " — using sRGB");
m_ocioFailedKey = key;
return false;
}
// Upload the LUT textures OCIO requested.
if (!CreateOcioTextures(desc)) {
glDeleteProgram(prog);
DestroyOcioResources();
m_ocioFailedKey = key;
return false;
}
m_ocioProgram = prog;
m_ocioKey = key;
LOG_INFO("Custom OCIO program built: " + key
+ " (" + std::to_string(m_ocioLuts.size()) + " LUTs)");
return true;
}
bool CreateOcioTextures(const OCIO::GpuShaderDescRcPtr& desc) {
// 3D LUTs (RGB).
for (unsigned i = 0; i < desc->getNum3DTextures(); ++i) {
const char* texName = nullptr; const char* samplerName = nullptr;
unsigned edgelen = 0; OCIO::Interpolation interp = OCIO::INTERP_LINEAR;
desc->get3DTexture(i, texName, samplerName, edgelen, interp);
const float* values = nullptr;
desc->get3DTextureValues(i, values);
if (!values || edgelen == 0 || !samplerName) return false;
GLuint id = 0;
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_3D, id);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB32F, edgelen, edgelen, edgelen,
0, GL_RGB, GL_FLOAT, values);
GLint filt = (interp == OCIO::INTERP_NEAREST) ? GL_NEAREST : GL_LINEAR;
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, filt);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, filt);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
m_ocioLuts.push_back({ id, GL_TEXTURE_3D, samplerName });
}
// 1D / 2D LUTs.
for (unsigned i = 0; i < desc->getNumTextures(); ++i) {
const char* texName = nullptr; const char* samplerName = nullptr;
unsigned width = 0, height = 0;
OCIO::GpuShaderDesc::TextureType channel = OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL;
OCIO::Interpolation interp = OCIO::INTERP_LINEAR;
desc->getTexture(i, texName, samplerName, width, height, channel, interp);
const float* values = nullptr;
desc->getTextureValues(i, values);
if (!values || width == 0 || !samplerName) return false;
const bool isRed = (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL);
const GLint internal = isRed ? GL_R32F : GL_RGB32F;
const GLenum format = isRed ? GL_RED : GL_RGB;
const GLint filt = (interp == OCIO::INTERP_NEAREST) ? GL_NEAREST : GL_LINEAR;
const GLenum target = (height > 1) ? GL_TEXTURE_2D : GL_TEXTURE_1D;
GLuint id = 0;
glGenTextures(1, &id);
glBindTexture(target, id);
if (target == GL_TEXTURE_2D) {
glTexImage2D(GL_TEXTURE_2D, 0, internal, width, height, 0,
format, GL_FLOAT, values);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else {
glTexImage1D(GL_TEXTURE_1D, 0, internal, width, 0,
format, GL_FLOAT, values);
}
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filt);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filt);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
m_ocioLuts.push_back({ id, target, samplerName });
}
return true;
}
void DestroyOcioResources() {
for (auto& l : m_ocioLuts) glDeleteTextures(1, &l.id);
m_ocioLuts.clear();
if (m_ocioProgram) { glDeleteProgram(m_ocioProgram); m_ocioProgram = 0; }
m_ocioKey.clear();
m_ocioFailedKey.clear();
}
static const char* kFullscreenVS;
static const char* kSrgbFS;
GLuint m_vao = 0;
GLuint m_srgbProgram = 0;
GLuint m_ocioProgram = 0;
std::string m_ocioKey;
std::string m_ocioFailedKey;
std::vector<LutTex> m_ocioLuts;
};
// Attribute-less fullscreen triangle; UV in [0,2] covers the [0,1] screen.
const char* ViewportColorCorrector::kFullscreenVS = R"(#version 130
out vec2 vUv;
void main() {
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
vUv = p;
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
}
)";
// Linear → sRGB encode (matches HdxColorCorrectionTask's sRGB path).
const char* ViewportColorCorrector::kSrgbFS = R"(#version 130
uniform sampler2D uTex;
in vec2 vUv;
out vec4 outColor;
vec3 lin2srgb(vec3 c) {
vec3 lo = c * 12.92;
vec3 hi = 1.055 * pow(max(c, vec3(0.0)), vec3(1.0/2.4)) - 0.055;
bvec3 cut = lessThanEqual(c, vec3(0.0031308));
return mix(hi, lo, vec3(cut));
}
void main() {
vec4 c = texture(uTex, vUv);
outColor = vec4(lin2srgb(c.rgb), c.a);
}
)";
// ---------------------------------------------------------------------------
// GLSL sources
// ---------------------------------------------------------------------------
@@ -132,6 +438,7 @@ UsdSceneRenderer::UsdSceneRenderer()
, m_aaEnabled(false)
, m_backgroundColor(0.15f, 0.15f, 0.15f)
, m_shadingMode(ShadingMode::SmoothShaded)
, m_colorCorrectionMode(ColorCorrectionMode::sRGB)
, m_ambientLightOnly(true)
, m_domeLightEnabled(false)
, m_stageIsZup(false)
@@ -171,6 +478,9 @@ UsdSceneRenderer::~UsdSceneRenderer() {
DestroyBBoxResources();
DestroyCamWireResources();
DestroyLightWireResources();
m_colorCorrector.reset(); // deletes its GL program / LUT textures
if (m_ccLinearFBO) glDeleteFramebuffers(1, &m_ccLinearFBO);
if (m_ccLinearTex) glDeleteTextures(1, &m_ccLinearTex);
}
// ===========================================================================
@@ -211,6 +521,9 @@ void UsdSceneRenderer::InitRenderer() {
LOG_INFO("UsdSceneRenderer::InitRenderer - initializing...");
// Route USD/Hydra diagnostics (incl. OCIO failures) to the app log.
InstallUsdDiagnosticLogger();
pxr::GlfContextCaps::InitInstance();
pxr::UsdImagingGLEngine::Parameters params;
@@ -225,10 +538,14 @@ void UsdSceneRenderer::InitRenderer() {
// Plugin selection: prefer HdStorm / GL-based renderers
auto plugins = pxr::UsdImagingGLEngine::GetRendererPlugins();
LOG_INFO("Available renderer plugins: " + std::to_string(plugins.size()));
bool hasCycles = false;
for (const auto& p : plugins) {
LOG_INFO(" " + std::string(p.GetText()) + " -> "
+ pxr::UsdImagingGLEngine::GetRendererDisplayName(p));
if (std::string(p.GetText()) == "HdCyclesPlugin") hasCycles = true;
}
if (!hasCycles)
LOG_WARNING("HdCyclesPlugin not available (see startup log for DLL load errors).");
pxr::TfToken currentPlugin = m_renderer->GetCurrentRendererId();
if (currentPlugin.IsEmpty() && !plugins.empty()) {
@@ -518,7 +835,10 @@ void UsdSceneRenderer::Render(int width, int height)
m_drawTarget = pxr::GlfDrawTarget::New(pxr::GfVec2i(width, height), wantMSAA);
if (!m_drawTarget) { LOG_ERROR("Failed to create GlfDrawTarget"); return; }
m_drawTarget->Bind();
m_drawTarget->AddAttachment("color", GL_RGBA, GL_FLOAT, GL_RGBA);
// RGBA16F so the scene is stored linear with HDR headroom: we render
// linear (Hydra correction disabled) and apply our own color correction
// afterwards, which needs values outside [0,1] for OCIO/ACES.
m_drawTarget->AddAttachment("color", GL_RGBA, GL_FLOAT, GL_RGBA16F);
m_drawTarget->AddAttachment("depth",
GL_DEPTH_COMPONENT, GL_FLOAT, GL_DEPTH_COMPONENT32F);
m_drawTarget->Unbind();
@@ -649,9 +969,27 @@ void UsdSceneRenderer::Render(int width, int height)
m_renderParams.forceRefresh = m_forceRefresh;
m_renderParams.clipPlanes = m_clipPlanes;
m_renderer->Render(m_stage->GetPseudoRoot(), m_renderParams);
// Color correction is done by our own GL post-process (ApplyViewport-
// ColorCorrection below), not HdxColorCorrectionTask — so Hydra always
// renders linear ("disabled"). This bypasses the hdx OCIO path entirely.
m_renderParams.colorCorrectionMode = pxr::TfToken("disabled");
m_renderer->SetColorCorrectionSettings(pxr::TfToken("disabled"));
// Guard the Hydra render: if it throws, the m_drawTarget->Unbind() below
// would be skipped, permanently unbalancing the GlfDrawTarget bind stack
// and blacking out every later frame.
try {
m_renderer->Render(m_stage->GetPseudoRoot(), m_renderParams);
} catch (const std::exception& e) {
LOG_ERROR("Hydra render failed: " + std::string(e.what()));
}
m_forceRefresh = false;
// --- Custom color-correction post-process (linear → sRGB / OCIO) ---
if (m_colorCorrectionMode != ColorCorrectionMode::Disabled) {
ApplyViewportColorCorrection(width, height);
}
// --- Optional grid overlay ---
if (m_showGrid) {
// When MSAA is active, render the grid into the MSAA FBO so it is
@@ -680,6 +1018,66 @@ void UsdSceneRenderer::Render(int width, int height)
m_drawTarget->Unbind();
}
// ===========================================================================
// Custom color-correction post-process
// ===========================================================================
void UsdSceneRenderer::ApplyViewportColorCorrection(int width, int height)
{
if (!m_drawTarget) return;
if (!m_colorCorrector)
m_colorCorrector = std::make_unique<ViewportColorCorrector>();
// (Re)create the single-sample linear copy texture + its FBO on size change.
if (m_ccLinearTex == 0 || m_ccLinearW != width || m_ccLinearH != height) {
if (m_ccLinearTex == 0) glGenTextures(1, &m_ccLinearTex);
glBindTexture(GL_TEXTURE_2D, m_ccLinearTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0,
GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
if (m_ccLinearFBO == 0) glGenFramebuffers(1, &m_ccLinearFBO);
glBindFramebuffer(GL_FRAMEBUFFER, m_ccLinearFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, m_ccLinearTex, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
m_ccLinearW = width;
m_ccLinearH = height;
}
// Resolve MSAA (no-op otherwise) so the color attachment holds the linear
// single-sample image, then copy it into m_ccLinearTex — sampling and
// writing the same texture in one pass is illegal, hence the copy.
m_drawTarget->Resolve();
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_drawTarget->GetFramebufferId());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_ccLinearFBO);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
// Draw the corrected result back into the draw-target render FBO (the MSAA
// FBO when multisampling, so it resolves together with the overlays drawn
// on top of it afterwards).
GLuint dstFbo = m_drawTarget->HasMSAA()
? m_drawTarget->GetFramebufferMSId()
: m_drawTarget->GetFramebufferId();
glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
glViewport(0, 0, width, height);
ViewportColorCorrector::Mode mode =
(m_colorCorrectionMode == ColorCorrectionMode::OpenColorIO)
? ViewportColorCorrector::Mode::OpenColorIO
: ViewportColorCorrector::Mode::sRGB;
m_colorCorrector->Apply(m_ccLinearTex, mode,
m_ocioDisplay, m_ocioView,
m_ocioColorSpace, m_ocioLook);
}
// ===========================================================================
// Output
// ===========================================================================
@@ -690,7 +1088,6 @@ uint32_t UsdSceneRenderer::GetColorTextureID()
// Resolve MSAA → regular texture before the caller samples it.
// Called after all overlay draws (axis, bboxes, camera wireframes) so
// every layer of MSAA-rendered content is included in the resolve.
// No-op when MSAA is not enabled.
m_drawTarget->Resolve();
auto att = m_drawTarget->GetAttachment("color");
return att ? static_cast<uint32_t>(att->GetGlTextureName()) : 0;
+43 -1
View File
@@ -21,9 +21,14 @@
#include <vector>
#include <string>
#include <memory>
namespace UsdLayerManager {
/// Custom viewport color-correction post-process (sRGB / OCIO) implemented in
/// our own GL shader instead of HdxColorCorrectionTask. Defined in the .cpp.
class ViewportColorCorrector;
/// Bounding box display mode for selected prims.
enum class BBoxMode {
None, ///< No bounding boxes drawn
@@ -40,6 +45,13 @@ enum class ShadingMode {
Unlit, ///< Smooth geometry, lighting disabled
};
/// Viewport color correction mode — maps to UsdImagingGLRenderParams::colorCorrectionMode.
enum class ColorCorrectionMode {
Disabled, ///< No correction (raw linear output)
sRGB, ///< Linear → sRGB gamma (default)
OpenColorIO, ///< Full OCIO pipeline via bundled ACES 1.2 config
};
class UsdSceneRenderer {
public:
UsdSceneRenderer();
@@ -211,6 +223,18 @@ public:
ShadingMode GetShadingMode() const { return m_shadingMode; }
void SetShadingMode(ShadingMode m) { m_shadingMode = m; m_forceRefresh = true; }
ColorCorrectionMode GetColorCorrectionMode() const { return m_colorCorrectionMode; }
void SetColorCorrectionMode(ColorCorrectionMode m) { m_colorCorrectionMode = m; m_forceRefresh = true; }
const std::string& GetOcioDisplay() const { return m_ocioDisplay; }
void SetOcioDisplay(std::string v) { m_ocioDisplay = std::move(v); m_forceRefresh = true; }
const std::string& GetOcioView() const { return m_ocioView; }
void SetOcioView(std::string v) { m_ocioView = std::move(v); m_forceRefresh = true; }
const std::string& GetOcioColorSpace() const { return m_ocioColorSpace; }
void SetOcioColorSpace(std::string v){ m_ocioColorSpace = std::move(v); m_forceRefresh = true; }
const std::string& GetOcioLook() const { return m_ocioLook; }
void SetOcioLook(std::string v) { m_ocioLook = std::move(v); m_forceRefresh = true; }
/// Default material ambient (kA, default 0.2 — matches viewSettingsDataModel.py).
float GetDefaultMaterialAmbient() const { return m_defaultMaterialAmbient; }
void SetDefaultMaterialAmbient(float v) { m_defaultMaterialAmbient = v; }
@@ -239,6 +263,7 @@ private:
int m_lastRenderWidth = 0;
int m_lastRenderHeight = 0;
void InitRenderer();
void InitGridResources();
void RebuildGridVBO(); ///< (Re)build grid line geometry after up-axis or size change.
void DestroyGridResources();
@@ -310,7 +335,12 @@ private:
pxr::GfVec3f m_backgroundColor;
// Lighting settings (mirrors stageView.py viewSettings)
ShadingMode m_shadingMode; // draw mode + lighting flags
ShadingMode m_shadingMode; // draw mode + lighting flags
ColorCorrectionMode m_colorCorrectionMode; // viewport color correction
std::string m_ocioDisplay;
std::string m_ocioView;
std::string m_ocioColorSpace;
std::string m_ocioLook;
bool m_ambientLightOnly; // camera headlight
bool m_domeLightEnabled; // dome/IBL light
bool m_stageIsZup; // used for dome light rotation
@@ -361,6 +391,18 @@ private:
// --- Camera wireframe cache ---
pxr::SdfPathVector m_cachedCameraPaths;
bool m_cameraCacheDirty = true;
// --- Custom color-correction post-process ---------------------------------
// The scene is rendered linear (Hydra correction "disabled") and corrected
// by our own GL shader, bypassing HdxColorCorrectionTask. ApplyViewport-
// ColorCorrection() resolves the linear result into m_ccLinearTex, then
// draws a corrected fullscreen quad back into the draw-target FBO.
void ApplyViewportColorCorrection(int width, int height);
std::unique_ptr<ViewportColorCorrector> m_colorCorrector;
GLuint m_ccLinearFBO = 0; ///< FBO wrapping m_ccLinearTex (correction input)
GLuint m_ccLinearTex = 0; ///< single-sample linear copy of the Hydra output
int m_ccLinearW = 0;
int m_ccLinearH = 0;
};
} // namespace UsdLayerManager