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:
@@ -75,3 +75,6 @@ desktop.ini
|
||||
|
||||
# ImGui layout (saved to %APPDATA%\UsdLayerManager at runtime)
|
||||
imgui.ini
|
||||
|
||||
# OCIO config LUT files — downloaded automatically at CMake configure time
|
||||
resources/OpenColorIO-Configs/
|
||||
|
||||
+120
-10
@@ -16,6 +16,12 @@ find_package(Imgui REQUIRED)
|
||||
find_package(Glad REQUIRED)
|
||||
find_package(FFmpeg REQUIRED)
|
||||
|
||||
# OpenColorIO — provided by the OpenUSD distribution (USD built with OCIO support)
|
||||
set(OpenColorIO_DIR
|
||||
"${OpenUSD_ROOT_DIR}/lib/cmake/OpenColorIO"
|
||||
CACHE PATH "OpenColorIO CMake config dir" FORCE)
|
||||
find_package(OpenColorIO CONFIG REQUIRED)
|
||||
|
||||
# USD build that includes hdEmbree (built with PXR_ENABLE_EMBREE_PLUGIN=ON).
|
||||
# Can be a separate install from OpenUSD_ROOT_DIR; leave empty to search there instead.
|
||||
set(HDEMBREE_USD_ROOT "" CACHE PATH "USD build containing hdEmbree plugin (separate from OpenUSD_ROOT_DIR)")
|
||||
@@ -123,6 +129,13 @@ if(WITH_CYCLES)
|
||||
-DWITH_CYCLES_DEVICE_HIP=OFF
|
||||
-DWITH_CYCLES_DEVICE_ONEAPI=OFF
|
||||
-DWITH_CYCLES_LOGGING=OFF
|
||||
# USD ships OpenVDB v9; Cycles prebuilt is v13 — ABI mismatch, both named
|
||||
# openvdb.dll. Disable openvdb in Cycles to avoid the version conflict.
|
||||
-DWITH_CYCLES_OPENVDB=OFF
|
||||
# Embree4 links tbb12.dll; USD already loads tbb.dll at process startup.
|
||||
# oneTBB detects the older tbb.dll and aborts initialization (DLL_INIT_FAILED).
|
||||
# Disable Embree so Cycles uses its own BVH traversal instead.
|
||||
-DWITH_CYCLES_EMBREE=OFF
|
||||
# ---- pxrConfig.cmake dependency overrides ----
|
||||
# pxrConfig.cmake runs find_dependency() for these before Cycles'
|
||||
# precompiled lib paths are on CMAKE_PREFIX_PATH, so we must supply
|
||||
@@ -131,12 +144,17 @@ if(WITH_CYCLES)
|
||||
-DPython3_EXECUTABLE=$ENV{LOCALAPPDATA}/Programs/Python/Python312/python.exe
|
||||
-DPython3_LIBRARY=$ENV{LOCALAPPDATA}/Programs/Python/Python312/libs/python312.lib
|
||||
-DPython3_INCLUDE_DIR=$ENV{LOCALAPPDATA}/Programs/Python/Python312/include
|
||||
-DTBB_DIR=${OpenUSD_ROOT_DIR}/lib/cmake/TBB
|
||||
-DTBB_DIR=${CMAKE_SOURCE_DIR}/third_party/cycles/lib/windows_x64/tbb/lib/cmake/TBB
|
||||
-DMaterialX_DIR=${OpenUSD_ROOT_DIR}/lib/cmake/MaterialX
|
||||
# Our USD install has no standalone OpenSubdiv/Imath cmake packages
|
||||
# (they're linked into the USD DLLs); skip those find_dependency calls.
|
||||
-DPXR_FIND_OPENSUBDIV_IN_CONFIG=OFF
|
||||
-DPXR_FIND_IMATH_IN_CONFIG=OFF
|
||||
# OpenSubdiv and Imath cmake configs are bundled in the USD install.
|
||||
# We must supply their dirs so pxrTargets.cmake can resolve the imported targets.
|
||||
-DOpenSubdiv_DIR=${OpenUSD_ROOT_DIR}/lib/cmake/OpenSubdiv
|
||||
-DImath_DIR=${OpenUSD_ROOT_DIR}/lib/cmake/Imath
|
||||
# Force Cycles to use its own prebuilt OCIO 2.5, not the OCIO 2.1
|
||||
# bundled in the new USD include dir. Without this, the new USD's
|
||||
# include/OpenColorIO/ (OCIO 2.1) shadows Cycles' prebuilt OCIO 2.5
|
||||
# headers and the colorspace.cpp API calls fail to compile.
|
||||
-DOpenColorIO_DIR=${CMAKE_SOURCE_DIR}/third_party/cycles/lib/windows_x64/opencolorio/lib/cmake/OpenColorIO
|
||||
BUILD_COMMAND
|
||||
${CMAKE_COMMAND} --build <BINARY_DIR> --config Release
|
||||
INSTALL_COMMAND
|
||||
@@ -179,6 +197,7 @@ target_link_libraries(UsdLayerManager PRIVATE
|
||||
Imgui::Imgui
|
||||
Glad::Glad
|
||||
FFmpeg::FFmpeg
|
||||
OpenColorIO::OpenColorIO
|
||||
ole32
|
||||
shell32
|
||||
)
|
||||
@@ -202,6 +221,14 @@ if(WIN32)
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
)
|
||||
|
||||
# OpenColorIO DLL — copy from USD bin/ (which already ships OpenColorIO_2_1.dll)
|
||||
add_custom_command(TARGET UsdLayerManager POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${OpenUSD_ROOT_DIR}/bin/OpenColorIO_2_1.dll"
|
||||
"$<TARGET_FILE_DIR:UsdLayerManager>"
|
||||
COMMENT "Copying OpenColorIO runtime DLL..."
|
||||
)
|
||||
|
||||
# FFmpeg DLLs — copy all .dll files from third_party/ffmpeg/bin
|
||||
file(GLOB FFMPEG_RUNTIME_DLLS "${CMAKE_SOURCE_DIR}/third_party/ffmpeg/bin/*.dll")
|
||||
if(FFMPEG_RUNTIME_DLLS)
|
||||
@@ -213,6 +240,26 @@ if(WIN32)
|
||||
)
|
||||
endif()
|
||||
|
||||
# cmake -P script to copy USD bin/ DLLs excluding CRT DLLs (msvcp, vcruntime, etc.).
|
||||
# The USD distribution bundles older CRT DLLs (14.34) that conflict with Cycles
|
||||
# binaries built against the newer system CRT (14.42+). The system CRT is
|
||||
# always present on Windows 10+ — no need to bundle our own copy.
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/copy_usd_bin_dlls.cmake"
|
||||
"file(GLOB _all_dlls \"${OpenUSD_ROOT_DIR}/bin/*.dll\")\n"
|
||||
"set(_dlls)\n"
|
||||
"foreach(_dll \${_all_dlls})\n"
|
||||
" get_filename_component(_name \"\${_dll}\" NAME)\n"
|
||||
" string(TOLOWER \"\${_name}\" _lname)\n"
|
||||
" if(_lname MATCHES \"^(msvcp|vcruntime|ucrtbase|concrt|api-ms-win-)\")\n"
|
||||
" continue()\n"
|
||||
" endif()\n"
|
||||
" list(APPEND _dlls \"\${_dll}\")\n"
|
||||
"endforeach()\n"
|
||||
"if(_dlls)\n"
|
||||
" file(COPY \${_dlls} DESTINATION \"\${DEST_DIR}\")\n"
|
||||
"endif()\n"
|
||||
)
|
||||
|
||||
# Copy runtime DLLs to output directory for each configuration
|
||||
add_custom_command(TARGET UsdLayerManager POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:UsdLayerManager>"
|
||||
@@ -220,10 +267,10 @@ if(WIN32)
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${OpenUSD_BIN_DIR}"
|
||||
"$<TARGET_FILE_DIR:UsdLayerManager>"
|
||||
# OpenUSD bin/ — tbb.dll, MaterialX, OpenEXR, OpenImageIO, etc.
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${OpenUSD_ROOT_DIR}/bin"
|
||||
"$<TARGET_FILE_DIR:UsdLayerManager>"
|
||||
# OpenUSD bin/ — tbb.dll, MaterialX, OpenEXR, OpenImageIO, etc. (CRT DLLs excluded)
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DDEST_DIR=$<TARGET_FILE_DIR:UsdLayerManager>"
|
||||
-P "${CMAKE_BINARY_DIR}/copy_usd_bin_dlls.cmake"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${OpenUSD_ROOT_DIR}/lib/usd"
|
||||
"$<TARGET_FILE_DIR:UsdLayerManager>/usd"
|
||||
@@ -307,9 +354,16 @@ if(WIN32)
|
||||
"foreach(_dll \${_all_dlls})\n"
|
||||
" get_filename_component(_name \"\${_dll}\" NAME)\n"
|
||||
" string(TOLOWER \"\${_name}\" _lname)\n"
|
||||
# Exclude CRT DLLs (system must supply these; Cycles bundles an older version).
|
||||
" if(_lname MATCHES \"^(msvcp|vcruntime|ucrtbase|concrt|api-ms-win-)\")\n"
|
||||
" continue()\n"
|
||||
" endif()\n"
|
||||
# Exclude debug-variant DLLs (not needed in release, just wasted space).
|
||||
# Pattern: OpenColorIO_d_2_5.dll → contains "_d_"
|
||||
# openjph.0.25d.dll → ends with "d.dll"
|
||||
" if(_lname MATCHES \"_d_\" OR _lname MATCHES \"d[.]dll$\")\n"
|
||||
" continue()\n"
|
||||
" endif()\n"
|
||||
" list(APPEND _dlls \"\${_dll}\")\n"
|
||||
"endforeach()\n"
|
||||
"if(_dlls)\n"
|
||||
@@ -355,10 +409,17 @@ install(DIRECTORY ${OpenUSD_BIN_DIR}/
|
||||
)
|
||||
|
||||
# Install OpenUSD bin/ DLLs — tbb.dll, tbbmalloc.dll, MaterialX, OpenEXR,
|
||||
# OpenImageIO, zlib, etc. These are separate from lib/ and also required at runtime.
|
||||
# OpenImageIO, zlib, etc. Exclude CRT DLLs: the system VC++ Redistributable supplies
|
||||
# these at runtime; bundling an older copy (14.34) causes crashes on machines with
|
||||
# Cycles or other DLLs compiled against the newer CRT (14.42+).
|
||||
install(DIRECTORY ${OpenUSD_ROOT_DIR}/bin/
|
||||
DESTINATION bin
|
||||
FILES_MATCHING PATTERN "*.dll"
|
||||
PATTERN "msvcp*" EXCLUDE
|
||||
PATTERN "vcruntime*" EXCLUDE
|
||||
PATTERN "ucrtbase*" EXCLUDE
|
||||
PATTERN "concrt*" EXCLUDE
|
||||
PATTERN "api-ms-win-*" EXCLUDE
|
||||
)
|
||||
|
||||
install(DIRECTORY ${OpenUSD_ROOT_DIR}/lib/usd
|
||||
@@ -435,6 +496,8 @@ if(OPENUSD_HAS_CYCLES)
|
||||
PATTERN "VCRUNTIME140*.dll" EXCLUDE
|
||||
PATTERN "ucrtbase*.dll" EXCLUDE
|
||||
PATTERN "api-ms-win-*.dll" EXCLUDE
|
||||
PATTERN "OpenColorIO_d_*.dll" EXCLUDE # debug OCIO build
|
||||
PATTERN "openjph.*d.dll" EXCLUDE # debug OpenJPH
|
||||
)
|
||||
# tbb12.dll (used by openvdb/embree4) lives in the precompiled lib tree, not install output
|
||||
install(FILES "${CYCLES_TBB12_DLL}"
|
||||
@@ -485,6 +548,35 @@ if(PYTHON311_DLL)
|
||||
)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OCIO: download ACES 1.2 config at configure time if not present
|
||||
# ---------------------------------------------------------------------------
|
||||
set(_ocio_config_dir "${CMAKE_SOURCE_DIR}/resources/OpenColorIO-Configs/aces_1.2")
|
||||
if(NOT EXISTS "${_ocio_config_dir}/config.ocio")
|
||||
message(STATUS "Downloading ACES 1.2 OCIO config (~124 MB) ...")
|
||||
set(_ocio_zip "${CMAKE_BINARY_DIR}/aces_1.2.zip")
|
||||
file(DOWNLOAD
|
||||
"https://github.com/colour-science/OpenColorIO-Configs/releases/download/v1.2/OpenColorIO-Config-ACES-1.2.zip"
|
||||
"${_ocio_zip}"
|
||||
SHOW_PROGRESS
|
||||
STATUS _ocio_dl_status
|
||||
)
|
||||
list(GET _ocio_dl_status 0 _ocio_dl_code)
|
||||
if(_ocio_dl_code EQUAL 0)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E tar xf "${_ocio_zip}"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
)
|
||||
file(COPY "${CMAKE_BINARY_DIR}/OpenColorIO-Config-ACES-1.2/aces_1.2/"
|
||||
DESTINATION "${_ocio_config_dir}")
|
||||
file(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/OpenColorIO-Config-ACES-1.2")
|
||||
file(REMOVE "${_ocio_zip}")
|
||||
message(STATUS "ACES 1.2 OCIO config installed to ${_ocio_config_dir}")
|
||||
else()
|
||||
message(WARNING "ACES 1.2 OCIO download failed (${_ocio_dl_status}) — OCIO will not be bundled.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Copy resources to build directory
|
||||
file(COPY ${CMAKE_SOURCE_DIR}/resources
|
||||
DESTINATION ${CMAKE_BINARY_DIR}
|
||||
@@ -503,6 +595,24 @@ add_custom_command(TARGET UsdLayerManager POST_BUILD
|
||||
COMMENT "Copying fonts and SVG icons to build output"
|
||||
)
|
||||
|
||||
# Copy ACES OCIO config to build output — only on first build (skipped if already present).
|
||||
if(EXISTS "${_ocio_config_dir}/config.ocio")
|
||||
add_custom_command(TARGET UsdLayerManager POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DSRC=${_ocio_config_dir}"
|
||||
"-DDST=$<TARGET_FILE_DIR:UsdLayerManager>/resources/OpenColorIO-Configs/aces_1.2"
|
||||
-P "${CMAKE_SOURCE_DIR}/cmake/CopyDirIfMissing.cmake"
|
||||
COMMENT "Copying ACES OCIO config to build output (first time only)"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Install OpenColorIO DLL (from the OpenUSD distribution's bin/)
|
||||
install(FILES
|
||||
"${OpenUSD_ROOT_DIR}/bin/OpenColorIO_2_1.dll"
|
||||
DESTINATION bin
|
||||
OPTIONAL
|
||||
)
|
||||
|
||||
# Install resources (icons, fonts placeholder) next to the exe so the
|
||||
# exe-relative path "resources/..." resolves correctly from install/bin/.
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install",
|
||||
"CMAKE_PREFIX_PATH": "${sourceDir}/third_party/OpenUSD_v25.05",
|
||||
"CMAKE_PREFIX_PATH": "${sourceDir}/third_party/OpenUSD-v25.05",
|
||||
"IMGUI_DIR": "${sourceDir}/third_party/imgui-1.92.7",
|
||||
"CMAKE_CXX_STANDARD": "17",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
+52
-3
@@ -1,6 +1,7 @@
|
||||
#include "ui/Application.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <pxr/base/plug/registry.h>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
@@ -37,16 +38,64 @@ static void SetUsdPluginPath() {
|
||||
FindClose(hFind);
|
||||
}
|
||||
|
||||
if (!pluginPaths.empty()) {
|
||||
// Pre-flight: attempt to load each plugin DLL before registering it.
|
||||
// This catches missing dependencies (e.g. sycl8.dll → Intel GPU drivers)
|
||||
// gracefully and shows our own warning rather than a cryptic TF_ERROR.
|
||||
std::vector<std::string> checkedPaths;
|
||||
for (const auto& infoPath : pluginPaths) {
|
||||
// Derive the DLL path from the plugInfo.json location:
|
||||
// <plugin>/resources/plugInfo.json → ../<plugin>.dll
|
||||
// e.g. usd/hdCycles/resources/plugInfo.json → usd/hdCycles.dll
|
||||
size_t resPos = infoPath.rfind("\\resources\\plugInfo.json");
|
||||
if (resPos != std::string::npos) {
|
||||
std::string pluginDir = infoPath.substr(0, resPos);
|
||||
size_t dirSlash = pluginDir.rfind('\\');
|
||||
std::string pluginName = (dirSlash != std::string::npos)
|
||||
? pluginDir.substr(dirSlash + 1) : pluginDir;
|
||||
std::string dllPath = pluginPath + "\\" + pluginName + ".dll";
|
||||
DWORD dllAttrs = GetFileAttributesA(dllPath.c_str());
|
||||
if (dllAttrs != INVALID_FILE_ATTRIBUTES) {
|
||||
// DLL file exists — verify it can actually be loaded.
|
||||
// No LOAD_WITH_ALTERED_SEARCH_PATH: keep the application
|
||||
// directory (exe dir, where embree4.dll etc. live) in the
|
||||
// DLL search path so transitive deps resolve correctly.
|
||||
HMODULE h = LoadLibraryA(dllPath.c_str());
|
||||
if (!h) {
|
||||
DWORD err = GetLastError();
|
||||
char msg[256] = {};
|
||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, err, 0, msg, static_cast<DWORD>(sizeof(msg) - 1), nullptr);
|
||||
std::string errMsg(msg);
|
||||
while (!errMsg.empty() && (errMsg.back() == '\r' || errMsg.back() == '\n'))
|
||||
errMsg.pop_back();
|
||||
char hexErr[12] = {};
|
||||
sprintf_s(hexErr, "0x%08X", static_cast<unsigned>(err));
|
||||
LOG_WARNING("Skipping plugin " + pluginName
|
||||
+ " (DLL load failed " + hexErr + "): " + errMsg);
|
||||
continue; // Skip registering this plugin
|
||||
}
|
||||
// Intentionally do NOT FreeLibrary here. Releasing the handle
|
||||
// drops the refcount to 0 and unloads the DLL; when USD's plug
|
||||
// system later loads it again DllMain re-fires, re-registering
|
||||
// any TfEnvSettings and causing "duplicate definition" warnings.
|
||||
// Keeping the handle loaded means USD's LoadLibrary is a no-op
|
||||
// (refcount bump only, no DllMain call), preventing duplicates.
|
||||
// The process holds these handles for its entire lifetime anyway.
|
||||
}
|
||||
}
|
||||
checkedPaths.push_back(infoPath);
|
||||
}
|
||||
|
||||
if (!checkedPaths.empty()) {
|
||||
auto& registry = pxr::PlugRegistry::GetInstance();
|
||||
auto registered = registry.RegisterPlugins(pluginPaths);
|
||||
auto registered = registry.RegisterPlugins(checkedPaths);
|
||||
LOG_INFO("Registered " + std::to_string(registered.size()) + " USD plugins from " + pluginPath);
|
||||
} else {
|
||||
LOG_WARNING("No USD plugins found at " + pluginPath);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int main(int /*argc*/, char* /*argv*/[]) {
|
||||
try {
|
||||
UsdLayerManager::Logger::Instance().SetLogLevel(UsdLayerManager::LogLevel::Info);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "OcioConfigParser.h"
|
||||
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE;
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
const OcioConfig& GetCurrentOcioConfig() {
|
||||
static OcioConfig s_config;
|
||||
static bool s_loaded = false;
|
||||
if (s_loaded) return s_config;
|
||||
s_loaded = true;
|
||||
|
||||
try {
|
||||
OCIO::ConstConfigRcPtr cfg = OCIO::GetCurrentConfig();
|
||||
if (!cfg) return s_config;
|
||||
|
||||
// Displays + views per display
|
||||
const char* defDisp = cfg->getDefaultDisplay();
|
||||
if (defDisp) s_config.defaultDisplay = defDisp;
|
||||
|
||||
int nD = cfg->getNumDisplays();
|
||||
for (int i = 0; i < nD; ++i) {
|
||||
const char* disp = cfg->getDisplay(i);
|
||||
if (!disp) continue;
|
||||
s_config.displays.emplace_back(disp);
|
||||
auto& viewVec = s_config.views[disp];
|
||||
int nV = cfg->getNumViews(disp);
|
||||
for (int j = 0; j < nV; ++j) {
|
||||
const char* v = cfg->getView(disp, j);
|
||||
if (v) viewVec.emplace_back(v);
|
||||
}
|
||||
}
|
||||
|
||||
if (!s_config.defaultDisplay.empty()) {
|
||||
const char* dv = cfg->getDefaultView(s_config.defaultDisplay.c_str());
|
||||
if (dv) s_config.defaultView = dv;
|
||||
}
|
||||
|
||||
// Color spaces
|
||||
int nCS = cfg->getNumColorSpaces();
|
||||
for (int i = 0; i < nCS; ++i) {
|
||||
const char* cs = cfg->getColorSpaceNameByIndex(i);
|
||||
if (cs) s_config.colorSpaces.emplace_back(cs);
|
||||
}
|
||||
|
||||
// Looks
|
||||
int nL = cfg->getNumLooks();
|
||||
for (int i = 0; i < nL; ++i) {
|
||||
const char* look = cfg->getLookNameByIndex(i);
|
||||
if (look) s_config.looks.emplace_back(look);
|
||||
}
|
||||
|
||||
s_config.valid = true;
|
||||
} catch (const std::exception&) {
|
||||
// OCIO not configured — s_config stays empty with valid=false
|
||||
}
|
||||
|
||||
return s_config;
|
||||
}
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
struct OcioConfig {
|
||||
std::vector<std::string> displays;
|
||||
std::unordered_map<std::string, std::vector<std::string>> views; // display → views
|
||||
std::vector<std::string> colorSpaces;
|
||||
std::vector<std::string> looks;
|
||||
std::string defaultDisplay;
|
||||
std::string defaultView; // default view for defaultDisplay
|
||||
bool valid = false; // false if $OCIO unavailable
|
||||
};
|
||||
|
||||
/// Returns the OCIO config loaded from the $OCIO env var via the OCIO C++ API.
|
||||
/// Result is cached — parsed once per process lifetime.
|
||||
const OcioConfig& GetCurrentOcioConfig();
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
Reference in New Issue
Block a user