Compare commits
2 Commits
8316e419ba
...
46d83d300d
| Author | SHA1 | Date | |
|---|---|---|---|
| 46d83d300d | |||
| fb1ae9d3a6 |
@@ -75,3 +75,6 @@ desktop.ini
|
|||||||
|
|
||||||
# ImGui layout (saved to %APPDATA%\UsdLayerManager at runtime)
|
# ImGui layout (saved to %APPDATA%\UsdLayerManager at runtime)
|
||||||
imgui.ini
|
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(Glad REQUIRED)
|
||||||
find_package(FFmpeg 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).
|
# 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.
|
# 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)")
|
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_HIP=OFF
|
||||||
-DWITH_CYCLES_DEVICE_ONEAPI=OFF
|
-DWITH_CYCLES_DEVICE_ONEAPI=OFF
|
||||||
-DWITH_CYCLES_LOGGING=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 dependency overrides ----
|
||||||
# pxrConfig.cmake runs find_dependency() for these before Cycles'
|
# pxrConfig.cmake runs find_dependency() for these before Cycles'
|
||||||
# precompiled lib paths are on CMAKE_PREFIX_PATH, so we must supply
|
# 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_EXECUTABLE=$ENV{LOCALAPPDATA}/Programs/Python/Python312/python.exe
|
||||||
-DPython3_LIBRARY=$ENV{LOCALAPPDATA}/Programs/Python/Python312/libs/python312.lib
|
-DPython3_LIBRARY=$ENV{LOCALAPPDATA}/Programs/Python/Python312/libs/python312.lib
|
||||||
-DPython3_INCLUDE_DIR=$ENV{LOCALAPPDATA}/Programs/Python/Python312/include
|
-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
|
-DMaterialX_DIR=${OpenUSD_ROOT_DIR}/lib/cmake/MaterialX
|
||||||
# Our USD install has no standalone OpenSubdiv/Imath cmake packages
|
# OpenSubdiv and Imath cmake configs are bundled in the USD install.
|
||||||
# (they're linked into the USD DLLs); skip those find_dependency calls.
|
# We must supply their dirs so pxrTargets.cmake can resolve the imported targets.
|
||||||
-DPXR_FIND_OPENSUBDIV_IN_CONFIG=OFF
|
-DOpenSubdiv_DIR=${OpenUSD_ROOT_DIR}/lib/cmake/OpenSubdiv
|
||||||
-DPXR_FIND_IMATH_IN_CONFIG=OFF
|
-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
|
BUILD_COMMAND
|
||||||
${CMAKE_COMMAND} --build <BINARY_DIR> --config Release
|
${CMAKE_COMMAND} --build <BINARY_DIR> --config Release
|
||||||
INSTALL_COMMAND
|
INSTALL_COMMAND
|
||||||
@@ -179,6 +197,7 @@ target_link_libraries(UsdLayerManager PRIVATE
|
|||||||
Imgui::Imgui
|
Imgui::Imgui
|
||||||
Glad::Glad
|
Glad::Glad
|
||||||
FFmpeg::FFmpeg
|
FFmpeg::FFmpeg
|
||||||
|
OpenColorIO::OpenColorIO
|
||||||
ole32
|
ole32
|
||||||
shell32
|
shell32
|
||||||
)
|
)
|
||||||
@@ -202,6 +221,14 @@ if(WIN32)
|
|||||||
_CRT_SECURE_NO_WARNINGS
|
_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
|
# FFmpeg DLLs — copy all .dll files from third_party/ffmpeg/bin
|
||||||
file(GLOB FFMPEG_RUNTIME_DLLS "${CMAKE_SOURCE_DIR}/third_party/ffmpeg/bin/*.dll")
|
file(GLOB FFMPEG_RUNTIME_DLLS "${CMAKE_SOURCE_DIR}/third_party/ffmpeg/bin/*.dll")
|
||||||
if(FFMPEG_RUNTIME_DLLS)
|
if(FFMPEG_RUNTIME_DLLS)
|
||||||
@@ -213,6 +240,26 @@ if(WIN32)
|
|||||||
)
|
)
|
||||||
endif()
|
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
|
# Copy runtime DLLs to output directory for each configuration
|
||||||
add_custom_command(TARGET UsdLayerManager POST_BUILD
|
add_custom_command(TARGET UsdLayerManager POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:UsdLayerManager>"
|
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:UsdLayerManager>"
|
||||||
@@ -220,10 +267,10 @@ if(WIN32)
|
|||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
"${OpenUSD_BIN_DIR}"
|
"${OpenUSD_BIN_DIR}"
|
||||||
"$<TARGET_FILE_DIR:UsdLayerManager>"
|
"$<TARGET_FILE_DIR:UsdLayerManager>"
|
||||||
# OpenUSD bin/ — tbb.dll, MaterialX, OpenEXR, OpenImageIO, etc.
|
# OpenUSD bin/ — tbb.dll, MaterialX, OpenEXR, OpenImageIO, etc. (CRT DLLs excluded)
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND}
|
||||||
"${OpenUSD_ROOT_DIR}/bin"
|
"-DDEST_DIR=$<TARGET_FILE_DIR:UsdLayerManager>"
|
||||||
"$<TARGET_FILE_DIR:UsdLayerManager>"
|
-P "${CMAKE_BINARY_DIR}/copy_usd_bin_dlls.cmake"
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
"${OpenUSD_ROOT_DIR}/lib/usd"
|
"${OpenUSD_ROOT_DIR}/lib/usd"
|
||||||
"$<TARGET_FILE_DIR:UsdLayerManager>/usd"
|
"$<TARGET_FILE_DIR:UsdLayerManager>/usd"
|
||||||
@@ -307,9 +354,16 @@ if(WIN32)
|
|||||||
"foreach(_dll \${_all_dlls})\n"
|
"foreach(_dll \${_all_dlls})\n"
|
||||||
" get_filename_component(_name \"\${_dll}\" NAME)\n"
|
" get_filename_component(_name \"\${_dll}\" NAME)\n"
|
||||||
" string(TOLOWER \"\${_name}\" _lname)\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"
|
" if(_lname MATCHES \"^(msvcp|vcruntime|ucrtbase|concrt|api-ms-win-)\")\n"
|
||||||
" continue()\n"
|
" continue()\n"
|
||||||
" endif()\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"
|
" list(APPEND _dlls \"\${_dll}\")\n"
|
||||||
"endforeach()\n"
|
"endforeach()\n"
|
||||||
"if(_dlls)\n"
|
"if(_dlls)\n"
|
||||||
@@ -355,10 +409,17 @@ install(DIRECTORY ${OpenUSD_BIN_DIR}/
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Install OpenUSD bin/ DLLs — tbb.dll, tbbmalloc.dll, MaterialX, OpenEXR,
|
# 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/
|
install(DIRECTORY ${OpenUSD_ROOT_DIR}/bin/
|
||||||
DESTINATION bin
|
DESTINATION bin
|
||||||
FILES_MATCHING PATTERN "*.dll"
|
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
|
install(DIRECTORY ${OpenUSD_ROOT_DIR}/lib/usd
|
||||||
@@ -435,6 +496,8 @@ if(OPENUSD_HAS_CYCLES)
|
|||||||
PATTERN "VCRUNTIME140*.dll" EXCLUDE
|
PATTERN "VCRUNTIME140*.dll" EXCLUDE
|
||||||
PATTERN "ucrtbase*.dll" EXCLUDE
|
PATTERN "ucrtbase*.dll" EXCLUDE
|
||||||
PATTERN "api-ms-win-*.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
|
# tbb12.dll (used by openvdb/embree4) lives in the precompiled lib tree, not install output
|
||||||
install(FILES "${CYCLES_TBB12_DLL}"
|
install(FILES "${CYCLES_TBB12_DLL}"
|
||||||
@@ -485,6 +548,35 @@ if(PYTHON311_DLL)
|
|||||||
)
|
)
|
||||||
endif()
|
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
|
# Copy resources to build directory
|
||||||
file(COPY ${CMAKE_SOURCE_DIR}/resources
|
file(COPY ${CMAKE_SOURCE_DIR}/resources
|
||||||
DESTINATION ${CMAKE_BINARY_DIR}
|
DESTINATION ${CMAKE_BINARY_DIR}
|
||||||
@@ -503,6 +595,24 @@ add_custom_command(TARGET UsdLayerManager POST_BUILD
|
|||||||
COMMENT "Copying fonts and SVG icons to build output"
|
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
|
# Install resources (icons, fonts placeholder) next to the exe so the
|
||||||
# exe-relative path "resources/..." resolves correctly from install/bin/.
|
# exe-relative path "resources/..." resolves correctly from install/bin/.
|
||||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources
|
install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
"binaryDir": "${sourceDir}/build",
|
"binaryDir": "${sourceDir}/build",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install",
|
"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",
|
"IMGUI_DIR": "${sourceDir}/third_party/imgui-1.92.7",
|
||||||
"CMAKE_CXX_STANDARD": "17",
|
"CMAKE_CXX_STANDARD": "17",
|
||||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
# ADR 0001 — Custom Viewport Color Correction (sRGB / OCIO)
|
||||||
|
|
||||||
|
- **Status:** Accepted
|
||||||
|
- **Date:** 2026-06-27
|
||||||
|
- **Component:** `src/core/UsdSceneRenderer.cpp` (`ViewportColorCorrector`, `ApplyViewportColorCorrection`)
|
||||||
|
- **Commit:** `fb1ae9d`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The viewport renders a USD stage through `UsdImagingGLEngine` (Hydra / Storm)
|
||||||
|
into an offscreen `GlfDrawTarget`, whose color texture ImGui samples for
|
||||||
|
display. Color management (linear → display) was delegated to Hydra's
|
||||||
|
`HdxColorCorrectionTask` via `UsdImagingGLEngine::SetColorCorrectionSettings()`,
|
||||||
|
configured by the per-viewport `ColorCorrectionMode` (Disabled / sRGB /
|
||||||
|
OpenColorIO) plus OCIO display/view/colorspace/look.
|
||||||
|
|
||||||
|
Two problems made the OCIO path unusable:
|
||||||
|
|
||||||
|
1. **OCIO output was black.** sRGB correction rendered correctly through the
|
||||||
|
same color AOV, proving the correction *input* was fine — the hdx OCIO
|
||||||
|
shader/LUT path itself emitted black. The exact cause stayed opaque because
|
||||||
|
USD reports it via `TF_WARN`, which this GUI app discarded (no console).
|
||||||
|
|
||||||
|
2. **The failure was sticky.** OCIO resources are built lazily *inside*
|
||||||
|
`Render()` (`HdxColorCorrectionTask::Sync`), not in `SetColorCorrectionSettings()`.
|
||||||
|
A throw there escaped `UsdSceneRenderer::Render()` and skipped
|
||||||
|
`m_drawTarget->Unbind()`, permanently unbalancing the `GlfDrawTarget`
|
||||||
|
bind/restore stack — so every later frame, **including after switching back
|
||||||
|
to sRGB**, stayed black until an app restart.
|
||||||
|
|
||||||
|
We do not control the bundled USD build, so fixing `HdxColorCorrectionTask`
|
||||||
|
itself was not an option. App and USD both link OpenColorIO 2.1, and a usable
|
||||||
|
ACES 1.2 config is bundled and pointed to via `$OCIO`.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Correct, non-black OCIO display transforms in the viewport (ACES 1.2).
|
||||||
|
- sRGB output visually identical to the previous hdx sRGB path.
|
||||||
|
- A color-management failure can never permanently corrupt the viewport.
|
||||||
|
- Overlays (grid, axis, bbox, camera/light gizmos) remain display-referred,
|
||||||
|
drawn on top of the corrected image — unchanged from the prior behavior.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Replacing Hydra's *rendering* — only its color-correction stage is replaced.
|
||||||
|
- Exposure/gamma dynamic-property UI, or interactive grading.
|
||||||
|
- Applying an explicit OCIO look override beyond the view's own looks.
|
||||||
|
- Color-managing the ImGui UI chrome outside the viewport image.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### D1 — Replace `HdxColorCorrectionTask` with our own GL post-process
|
||||||
|
|
||||||
|
**Decision:** Hydra always renders **linear** (`colorCorrectionMode = "disabled"`).
|
||||||
|
A custom fullscreen pass (`ViewportColorCorrector`) applies the correction
|
||||||
|
afterwards, fully under our control.
|
||||||
|
|
||||||
|
**Rationale:** The hdx OCIO path is a black box we cannot patch in the bundled
|
||||||
|
USD. Owning the post-process gives deterministic, debuggable behavior and
|
||||||
|
removes the lazy-throw-inside-Render hazard entirely. sRGB and OCIO now share
|
||||||
|
one code path with consistent ordering.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### D2 — Render linear into an `RGBA16F` color attachment
|
||||||
|
|
||||||
|
**Decision:** The draw-target color attachment was changed from `GL_RGBA`
|
||||||
|
(8-bit unorm) to `GL_RGBA16F`.
|
||||||
|
|
||||||
|
**Rationale:** We now store the *linear* scene as the intermediate. Display
|
||||||
|
transforms (especially ACES) need scene values outside `[0,1]`; an 8-bit unorm
|
||||||
|
buffer would clip highlights and band the shadows before correction even runs.
|
||||||
|
16F is ample headroom at negligible cost. ImGui and `CaptureFrame` sample/read
|
||||||
|
it unchanged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### D3 — OCIO via the OCIO GPU API (`GpuShaderDesc`), LUTs uploaded by us
|
||||||
|
|
||||||
|
**Decision:** For OCIO mode, build the transform directly with OCIO 2.1:
|
||||||
|
`config->getProcessor(srcColorSpace, display, view, FORWARD)` →
|
||||||
|
`getDefaultGPUProcessor()` → `extractGpuShaderInfo(GpuShaderDesc)` with
|
||||||
|
`GPU_LANGUAGE_GLSL_1_3` and function name `OCIODisplay`. We compile the
|
||||||
|
generated GLSL into our fragment shader and upload/bind every 1D/2D/3D LUT it
|
||||||
|
requests as GL textures. The source colorspace defaults to the `scene_linear`
|
||||||
|
role when unset.
|
||||||
|
|
||||||
|
**Rationale:** This is exactly what `HdxColorCorrectionTask` does internally,
|
||||||
|
but visible and ours to debug. `GLSL_1_3` matches the project's existing
|
||||||
|
`#version 130` shaders and emits modern `texture()` calls. The program and LUTs
|
||||||
|
are cached and rebuilt only when display/view/colorspace/look change.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### D4 — Graceful degradation to sRGB; never black
|
||||||
|
|
||||||
|
**Decision:** Any OCIO failure — processor build, shader compile/link, or LUT
|
||||||
|
upload — logs the reason and falls back to the sRGB encode shader for that
|
||||||
|
frame. The failing key is remembered so we do not retry (and re-spam logs)
|
||||||
|
until the parameters change.
|
||||||
|
|
||||||
|
**Rationale:** A misconfigured display/view must degrade to a usable image, not
|
||||||
|
a black viewport. sRGB is the correct neutral fallback and matches the default.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### D5 — Implemented inline in `UsdSceneRenderer.cpp` (pimpl), not new TU/files
|
||||||
|
|
||||||
|
**Decision:** `ViewportColorCorrector` is a class defined in
|
||||||
|
`UsdSceneRenderer.cpp`, held via `std::unique_ptr` behind a forward declaration
|
||||||
|
in the header.
|
||||||
|
|
||||||
|
**Rationale:** `CORE_SOURCES` is a `file(GLOB_RECURSE ...)`; adding a new `.cpp`
|
||||||
|
forces a CMake reconfigure, which risks re-triggering the heavy Cycles
|
||||||
|
`ExternalProject` build. Keeping it in an existing TU avoids that while the
|
||||||
|
pimpl keeps OCIO/GL includes out of the public header. The corrector follows
|
||||||
|
the file's existing `Init*/Destroy*` GL-resource lifecycle conventions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### D6 — Route USD diagnostics to the app log
|
||||||
|
|
||||||
|
**Decision:** Install a process-wide `TfDiagnosticMgr::Delegate`
|
||||||
|
(`InstallUsdDiagnosticLogger`) that forwards USD `TF_ERROR` / `TF_WARN` /
|
||||||
|
`TF_STATUS` to the app logger with a `[USD]` prefix.
|
||||||
|
|
||||||
|
**Rationale:** USD's own diagnostics were invisible in this GUI app. This made
|
||||||
|
the original OCIO failure undiagnosable and is generally useful for any Hydra
|
||||||
|
issue.
|
||||||
|
|
||||||
|
## Pipeline
|
||||||
|
|
||||||
|
Per frame, after Hydra renders linear into the draw target:
|
||||||
|
|
||||||
|
1. `m_drawTarget->Resolve()` — resolves MSAA into the color attachment (no-op
|
||||||
|
when MSAA is off), giving a single-sample linear image.
|
||||||
|
2. Blit that attachment into `m_ccLinearTex` — a separate copy is required
|
||||||
|
because sampling and writing the same texture in one pass is illegal.
|
||||||
|
3. Bind the draw-target render FBO (the MSAA FBO when multisampling) and draw
|
||||||
|
the corrected fullscreen triangle sampling `m_ccLinearTex`.
|
||||||
|
4. Overlays draw on top (uncorrected), then the final `Resolve()` in
|
||||||
|
`GetColorTextureID()` produces the texture ImGui samples.
|
||||||
|
|
||||||
|
This keeps overlays display-referred exactly as before, and the correction is
|
||||||
|
MSAA-correct (a flat fullscreen quad resolves to the same value per sample).
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Positive:**
|
||||||
|
- OCIO display transforms work; OCIO failures degrade to sRGB, never black.
|
||||||
|
- A color-correction failure can no longer corrupt the bind stack — the throw
|
||||||
|
hazard is gone because Hydra no longer does OCIO.
|
||||||
|
- The full transform is inspectable/loggable in our own code.
|
||||||
|
- HDR-linear intermediate enables correct tone-mapped output.
|
||||||
|
|
||||||
|
**Negative / trade-offs:**
|
||||||
|
- We reimplement and now maintain OCIO GPU plumbing (LUT upload, sampler
|
||||||
|
binding) that USD would otherwise own; OCIO major-version API changes could
|
||||||
|
require updates.
|
||||||
|
- One extra fullscreen pass, one blit, and an `RGBA16F` buffer per viewport.
|
||||||
|
- OCIO dynamic properties (exposure/gamma) and explicit look overrides are not
|
||||||
|
wired up.
|
||||||
|
- Logic lives in `UsdSceneRenderer.cpp` rather than its own module, by D5.
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
- **Keep `HdxColorCorrectionTask`, fix configuration.** Rejected: the failure
|
||||||
|
was inside the bundled USD's GPU/LUT integration, not our parameters; we
|
||||||
|
cannot patch it, and sRGB-correct/OCIO-black pointed at the hdx shader path.
|
||||||
|
- **Custom sRGB only, keep hdx for OCIO.** Rejected: OCIO is the actual
|
||||||
|
requirement; a custom sRGB-only path wouldn't address it.
|
||||||
|
- **Bake the OCIO transform to a single 3D LUT on the CPU.** Rejected: loses
|
||||||
|
HDR shaper handling and precision that `GpuShaderDesc` manages correctly.
|
||||||
|
- **New `ViewportColorCorrection.{h,cpp}` module.** Rejected for now (D5) to
|
||||||
|
avoid a CMake reconfigure / Cycles rebuild; revisit if the file grows.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Built clean (Release) and confirmed in-app by the maintainer: OCIO produces the
|
||||||
|
expected corrected (non-black) image and sRGB is unchanged. Headless automated
|
||||||
|
verification is blocked by the stale test-target CMake config (missing USD
|
||||||
|
include dirs); restoring those targets would let an OCIO-vs-sRGB center-pixel
|
||||||
|
test assert this automatically.
|
||||||
@@ -23,11 +23,21 @@
|
|||||||
#include <pxr/base/gf/rect2i.h>
|
#include <pxr/base/gf/rect2i.h>
|
||||||
#include <pxr/base/gf/frustum.h>
|
#include <pxr/base/gf/frustum.h>
|
||||||
#include <pxr/base/gf/rotation.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 <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <exception>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace OCIO = OCIO_NAMESPACE;
|
||||||
|
|
||||||
namespace UsdLayerManager {
|
namespace UsdLayerManager {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -39,6 +49,37 @@ static std::string Vec3fStr(const pxr::GfVec3f& v) {
|
|||||||
+ std::to_string(v[2]) + ")";
|
+ 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():
|
/// Port of stageView._ComputeCameraFraming():
|
||||||
/// Converts a Y-up integer viewport rect into a CameraUtilFraming whose
|
/// Converts a Y-up integer viewport rect into a CameraUtilFraming whose
|
||||||
/// display/data windows are expressed in the Y-down coordinate system that
|
/// 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;
|
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
|
// GLSL sources
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -132,6 +438,7 @@ UsdSceneRenderer::UsdSceneRenderer()
|
|||||||
, m_aaEnabled(false)
|
, m_aaEnabled(false)
|
||||||
, m_backgroundColor(0.15f, 0.15f, 0.15f)
|
, m_backgroundColor(0.15f, 0.15f, 0.15f)
|
||||||
, m_shadingMode(ShadingMode::SmoothShaded)
|
, m_shadingMode(ShadingMode::SmoothShaded)
|
||||||
|
, m_colorCorrectionMode(ColorCorrectionMode::sRGB)
|
||||||
, m_ambientLightOnly(true)
|
, m_ambientLightOnly(true)
|
||||||
, m_domeLightEnabled(false)
|
, m_domeLightEnabled(false)
|
||||||
, m_stageIsZup(false)
|
, m_stageIsZup(false)
|
||||||
@@ -171,6 +478,9 @@ UsdSceneRenderer::~UsdSceneRenderer() {
|
|||||||
DestroyBBoxResources();
|
DestroyBBoxResources();
|
||||||
DestroyCamWireResources();
|
DestroyCamWireResources();
|
||||||
DestroyLightWireResources();
|
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...");
|
LOG_INFO("UsdSceneRenderer::InitRenderer - initializing...");
|
||||||
|
|
||||||
|
// Route USD/Hydra diagnostics (incl. OCIO failures) to the app log.
|
||||||
|
InstallUsdDiagnosticLogger();
|
||||||
|
|
||||||
pxr::GlfContextCaps::InitInstance();
|
pxr::GlfContextCaps::InitInstance();
|
||||||
|
|
||||||
pxr::UsdImagingGLEngine::Parameters params;
|
pxr::UsdImagingGLEngine::Parameters params;
|
||||||
@@ -225,10 +538,14 @@ void UsdSceneRenderer::InitRenderer() {
|
|||||||
// Plugin selection: prefer HdStorm / GL-based renderers
|
// Plugin selection: prefer HdStorm / GL-based renderers
|
||||||
auto plugins = pxr::UsdImagingGLEngine::GetRendererPlugins();
|
auto plugins = pxr::UsdImagingGLEngine::GetRendererPlugins();
|
||||||
LOG_INFO("Available renderer plugins: " + std::to_string(plugins.size()));
|
LOG_INFO("Available renderer plugins: " + std::to_string(plugins.size()));
|
||||||
|
bool hasCycles = false;
|
||||||
for (const auto& p : plugins) {
|
for (const auto& p : plugins) {
|
||||||
LOG_INFO(" " + std::string(p.GetText()) + " -> "
|
LOG_INFO(" " + std::string(p.GetText()) + " -> "
|
||||||
+ pxr::UsdImagingGLEngine::GetRendererDisplayName(p));
|
+ 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();
|
pxr::TfToken currentPlugin = m_renderer->GetCurrentRendererId();
|
||||||
if (currentPlugin.IsEmpty() && !plugins.empty()) {
|
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);
|
m_drawTarget = pxr::GlfDrawTarget::New(pxr::GfVec2i(width, height), wantMSAA);
|
||||||
if (!m_drawTarget) { LOG_ERROR("Failed to create GlfDrawTarget"); return; }
|
if (!m_drawTarget) { LOG_ERROR("Failed to create GlfDrawTarget"); return; }
|
||||||
m_drawTarget->Bind();
|
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",
|
m_drawTarget->AddAttachment("depth",
|
||||||
GL_DEPTH_COMPONENT, GL_FLOAT, GL_DEPTH_COMPONENT32F);
|
GL_DEPTH_COMPONENT, GL_FLOAT, GL_DEPTH_COMPONENT32F);
|
||||||
m_drawTarget->Unbind();
|
m_drawTarget->Unbind();
|
||||||
@@ -649,9 +969,27 @@ void UsdSceneRenderer::Render(int width, int height)
|
|||||||
m_renderParams.forceRefresh = m_forceRefresh;
|
m_renderParams.forceRefresh = m_forceRefresh;
|
||||||
m_renderParams.clipPlanes = m_clipPlanes;
|
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;
|
m_forceRefresh = false;
|
||||||
|
|
||||||
|
// --- Custom color-correction post-process (linear → sRGB / OCIO) ---
|
||||||
|
if (m_colorCorrectionMode != ColorCorrectionMode::Disabled) {
|
||||||
|
ApplyViewportColorCorrection(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Optional grid overlay ---
|
// --- Optional grid overlay ---
|
||||||
if (m_showGrid) {
|
if (m_showGrid) {
|
||||||
// When MSAA is active, render the grid into the MSAA FBO so it is
|
// 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();
|
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
|
// Output
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
@@ -690,7 +1088,6 @@ uint32_t UsdSceneRenderer::GetColorTextureID()
|
|||||||
// Resolve MSAA → regular texture before the caller samples it.
|
// Resolve MSAA → regular texture before the caller samples it.
|
||||||
// Called after all overlay draws (axis, bboxes, camera wireframes) so
|
// Called after all overlay draws (axis, bboxes, camera wireframes) so
|
||||||
// every layer of MSAA-rendered content is included in the resolve.
|
// every layer of MSAA-rendered content is included in the resolve.
|
||||||
// No-op when MSAA is not enabled.
|
|
||||||
m_drawTarget->Resolve();
|
m_drawTarget->Resolve();
|
||||||
auto att = m_drawTarget->GetAttachment("color");
|
auto att = m_drawTarget->GetAttachment("color");
|
||||||
return att ? static_cast<uint32_t>(att->GetGlTextureName()) : 0;
|
return att ? static_cast<uint32_t>(att->GetGlTextureName()) : 0;
|
||||||
|
|||||||
@@ -21,9 +21,14 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace UsdLayerManager {
|
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.
|
/// Bounding box display mode for selected prims.
|
||||||
enum class BBoxMode {
|
enum class BBoxMode {
|
||||||
None, ///< No bounding boxes drawn
|
None, ///< No bounding boxes drawn
|
||||||
@@ -40,6 +45,13 @@ enum class ShadingMode {
|
|||||||
Unlit, ///< Smooth geometry, lighting disabled
|
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 {
|
class UsdSceneRenderer {
|
||||||
public:
|
public:
|
||||||
UsdSceneRenderer();
|
UsdSceneRenderer();
|
||||||
@@ -211,6 +223,18 @@ public:
|
|||||||
ShadingMode GetShadingMode() const { return m_shadingMode; }
|
ShadingMode GetShadingMode() const { return m_shadingMode; }
|
||||||
void SetShadingMode(ShadingMode m) { m_shadingMode = m; m_forceRefresh = true; }
|
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).
|
/// Default material ambient (kA, default 0.2 — matches viewSettingsDataModel.py).
|
||||||
float GetDefaultMaterialAmbient() const { return m_defaultMaterialAmbient; }
|
float GetDefaultMaterialAmbient() const { return m_defaultMaterialAmbient; }
|
||||||
void SetDefaultMaterialAmbient(float v) { m_defaultMaterialAmbient = v; }
|
void SetDefaultMaterialAmbient(float v) { m_defaultMaterialAmbient = v; }
|
||||||
@@ -239,6 +263,7 @@ private:
|
|||||||
int m_lastRenderWidth = 0;
|
int m_lastRenderWidth = 0;
|
||||||
int m_lastRenderHeight = 0;
|
int m_lastRenderHeight = 0;
|
||||||
void InitRenderer();
|
void InitRenderer();
|
||||||
|
|
||||||
void InitGridResources();
|
void InitGridResources();
|
||||||
void RebuildGridVBO(); ///< (Re)build grid line geometry after up-axis or size change.
|
void RebuildGridVBO(); ///< (Re)build grid line geometry after up-axis or size change.
|
||||||
void DestroyGridResources();
|
void DestroyGridResources();
|
||||||
@@ -310,7 +335,12 @@ private:
|
|||||||
pxr::GfVec3f m_backgroundColor;
|
pxr::GfVec3f m_backgroundColor;
|
||||||
|
|
||||||
// Lighting settings (mirrors stageView.py viewSettings)
|
// 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_ambientLightOnly; // camera headlight
|
||||||
bool m_domeLightEnabled; // dome/IBL light
|
bool m_domeLightEnabled; // dome/IBL light
|
||||||
bool m_stageIsZup; // used for dome light rotation
|
bool m_stageIsZup; // used for dome light rotation
|
||||||
@@ -361,6 +391,18 @@ private:
|
|||||||
// --- Camera wireframe cache ---
|
// --- Camera wireframe cache ---
|
||||||
pxr::SdfPathVector m_cachedCameraPaths;
|
pxr::SdfPathVector m_cachedCameraPaths;
|
||||||
bool m_cameraCacheDirty = true;
|
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
|
} // namespace UsdLayerManager
|
||||||
|
|||||||
+52
-3
@@ -1,6 +1,7 @@
|
|||||||
#include "ui/Application.h"
|
#include "ui/Application.h"
|
||||||
#include "utils/Logger.h"
|
#include "utils/Logger.h"
|
||||||
#include <pxr/base/plug/registry.h>
|
#include <pxr/base/plug/registry.h>
|
||||||
|
#include <cstdio>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -37,16 +38,64 @@ static void SetUsdPluginPath() {
|
|||||||
FindClose(hFind);
|
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& 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);
|
LOG_INFO("Registered " + std::to_string(registered.size()) + " USD plugins from " + pluginPath);
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING("No USD plugins found at " + pluginPath);
|
LOG_WARNING("No USD plugins found at " + pluginPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int /*argc*/, char* /*argv*/[]) {
|
||||||
try {
|
try {
|
||||||
UsdLayerManager::Logger::Instance().SetLogLevel(UsdLayerManager::LogLevel::Info);
|
UsdLayerManager::Logger::Instance().SetLogLevel(UsdLayerManager::LogLevel::Info);
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,18 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
|
|||||||
RefreshManagers();
|
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.
|
// Load per-viewport settings (render delegate, grid, AA, etc.) from AppData.
|
||||||
if (const char* appData = getenv("APPDATA")) {
|
if (const char* appData = getenv("APPDATA")) {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|||||||
@@ -635,7 +635,12 @@ void ViewportPanel::SaveSettings(const std::string& path) const
|
|||||||
f << "BBoxMode=" << s.bboxMode << "\n";
|
f << "BBoxMode=" << s.bboxMode << "\n";
|
||||||
f << "AmbientLightOnly=" << (s.ambientLightOnly ? 1 : 0) << "\n";
|
f << "AmbientLightOnly=" << (s.ambientLightOnly ? 1 : 0) << "\n";
|
||||||
f << "DomeLightEnabled=" << (s.domeLightEnabled ? 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 == "BBoxMode") ts.bboxMode = toInt(val, ts.bboxMode);
|
||||||
else if (key == "AmbientLightOnly") ts.ambientLightOnly = (val == "1");
|
else if (key == "AmbientLightOnly") ts.ambientLightOnly = (val == "1");
|
||||||
else if (key == "DomeLightEnabled") ts.domeLightEnabled = (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 "ViewportTile.h"
|
||||||
#include "../utils/Logger.h"
|
#include "../utils/Logger.h"
|
||||||
|
#include "../utils/OcioConfigParser.h"
|
||||||
|
|
||||||
#include <pxr/usd/usdGeom/camera.h>
|
#include <pxr/usd/usdGeom/camera.h>
|
||||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
@@ -99,7 +100,12 @@ ViewportTileSettings ViewportTile::GetSettings() const
|
|||||||
s.bboxMode = static_cast<int>(m_renderer.GetBBoxMode());
|
s.bboxMode = static_cast<int>(m_renderer.GetBBoxMode());
|
||||||
s.ambientLightOnly = m_renderer.GetAmbientLightOnly();
|
s.ambientLightOnly = m_renderer.GetAmbientLightOnly();
|
||||||
s.domeLightEnabled = m_renderer.GetDomeLightEnabled();
|
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;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +120,12 @@ void ViewportTile::ApplySettings(const ViewportTileSettings& s)
|
|||||||
m_renderer.SetAmbientLightOnly(s.ambientLightOnly);
|
m_renderer.SetAmbientLightOnly(s.ambientLightOnly);
|
||||||
m_renderer.SetDomeLightEnabled(s.domeLightEnabled);
|
m_renderer.SetDomeLightEnabled(s.domeLightEnabled);
|
||||||
m_renderer.SetShadingMode(static_cast<ShadingMode>(s.shadingMode));
|
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(),
|
if (ImGui::BeginPopupContextItem(gearPopupId.c_str(),
|
||||||
ImGuiPopupFlags_MouseButtonLeft))
|
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 ------------------------------------------------
|
// -- Shading mode ------------------------------------------------
|
||||||
if (ImGui::BeginMenu("Shading")) {
|
if (ImGui::BeginMenu("Shading")) {
|
||||||
ShadingMode cur = m_renderer.GetShadingMode();
|
ShadingMode cur = m_renderer.GetShadingMode();
|
||||||
|
|||||||
+14
-2
@@ -26,9 +26,14 @@ struct ViewportTileSettings {
|
|||||||
float bgColorG = 0.15f;
|
float bgColorG = 0.15f;
|
||||||
float bgColorB = 0.15f;
|
float bgColorB = 0.15f;
|
||||||
int bboxMode = 0; ///< BBoxMode cast to int
|
int bboxMode = 0; ///< BBoxMode cast to int
|
||||||
bool ambientLightOnly = false;
|
bool ambientLightOnly = true;
|
||||||
bool domeLightEnabled = false;
|
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.
|
/// Named orthographic view directions.
|
||||||
@@ -178,6 +183,13 @@ private:
|
|||||||
// ── Orthographic view ────────────────────────────────────────────────────
|
// ── Orthographic view ────────────────────────────────────────────────────
|
||||||
OrthoView m_orthoView = OrthoView::None;
|
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 ──────────────────────────────────────────
|
// ── Per-frame interaction flags ──────────────────────────────────────────
|
||||||
bool m_wasClickedThisFrame = false;
|
bool m_wasClickedThisFrame = false;
|
||||||
bool m_wasHoveredThisFrame = 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