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.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",
|
||||
|
||||
@@ -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/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;
|
||||
|
||||
// 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();
|
||||
@@ -311,6 +336,11 @@ private:
|
||||
|
||||
// Lighting settings (mirrors stageView.py viewSettings)
|
||||
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;
|
||||
|
||||
@@ -636,6 +636,11 @@ void ViewportPanel::SaveSettings(const std::string& path) const
|
||||
f << "AmbientLightOnly=" << (s.ambientLightOnly ? 1 : 0) << "\n";
|
||||
f << "DomeLightEnabled=" << (s.domeLightEnabled ? 1 : 0) << "\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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,6 +707,11 @@ void ViewportPanel::LoadSettings(const std::string& path)
|
||||
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 == "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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -100,6 +101,11 @@ ViewportTileSettings ViewportTile::GetSettings() const
|
||||
s.ambientLightOnly = m_renderer.GetAmbientLightOnly();
|
||||
s.domeLightEnabled = m_renderer.GetDomeLightEnabled();
|
||||
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();
|
||||
|
||||
+13
-1
@@ -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 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