Skip to content

HDR: Slice D — Skybox + tone-mapping compositor (#470)#782

Open
fernandotonon wants to merge 1 commit into
masterfrom
feat/hdr-slice-d-470
Open

HDR: Slice D — Skybox + tone-mapping compositor (#470)#782
fernandotonon wants to merge 1 commit into
masterfrom
feat/hdr-slice-d-470

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary

Implements epic #466 Slice D (#470): HDR viewport skybox + FP16 scene render + tonemap compositor.

  • Skybox: HDREnvironmentManager::applySkyBox() binds the loaded cubemap to QtMesh/HdrSkybox via SceneManager::setSkyBox. Per-viewport visibility uses Viewport::setSkiesEnabled (IBL stays active when skies are hidden).
  • HDR RT + compositor: QtMeshHdrPipeline renders the scene to PF_FLOAT16_RGBA, then a fullscreen render_quad pass applies QtMesh/HdrTonemapPass (Reinhard / ACES / AgX + EV exposure + sRGB output).
  • Global defaults on HDREnvironmentManager (exposureEv, tonemapOperator, whitePoint, defaultSkyBoxVisible) with tonemapChanged signal and render.hdr.tonemap Sentry breadcrumbs.
  • Per-viewport wiring via HdrViewportController + HdrViewportPipeline, hooked from OgreWidget (register on init, disable when no env loaded — SDR path unchanged).
  • Unit tests: HdrTonemap_test.cpp validates 1-stop exposure scaling and operator output differences on synthetic HDR constants.

Not in this slice: Inspector/Material Editor controls (#471), bundled HDRIs (#472), CLI/MCP (#473).

Test plan

  • ./build_local/bin/UnitTests --gtest_filter="HdrTonemapTest.*"
  • CI unit-tests-linux
  • Manual: HDREnvironmentManager::loadEnvironment(path) → skybox visible + tonemapped viewport; toggle setSkyBoxVisible(widget, false) hides sky only

Closes #470

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added HDR rendering support with tone mapping options and exposure controls.
    • Introduced per-viewport HDR rendering that can be enabled automatically when an environment is available.
    • Added a shared skybox experience with visibility control.
  • Bug Fixes

    • Improved handling of HDR environments so viewports refresh correctly when settings change.
    • Ensured HDR views stay in sync during render-window rebuilds and regular frame updates.
  • Tests

    • Added coverage for exposure scaling, tone-mapping behavior, and color conversion consistency.

Add FP16 scene render + fullscreen tonemap compositor (Reinhard/ACES/AgX)
with EV exposure on editor viewports when an HDR environment is loaded.
Skybox uses the environment cubemap; per-viewport skies toggle via
Viewport::setSkiesEnabled without disabling IBL. Global tonemap defaults
live on HDREnvironmentManager; CPU reference tests cover exposure scaling
and operator divergence.

Closes #470

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a complete HDR viewport pipeline: CPU-side Reinhard/ACES/AgX tonemapping math (HdrTonemap), an Ogre compositor and material for the fullscreen tonemap pass, extended HDREnvironmentManager with tonemap/skybox defaults, a per-viewport HdrViewportPipeline, a singleton HdrViewportController, and OgreWidget lifecycle hooks.

Changes

HDR Skybox + Tonemap Compositor Pipeline

Layer / File(s) Summary
HdrTonemap math: types, operators, and CPU tonemappers
src/HDR/HdrTonemap.h, src/HDR/HdrTonemap.cpp, src/HDR/HdrTonemap_test.cpp
Defines HdrTonemap namespace with Rgb struct, Operator enum, arithmetic helpers; implements applyExposure, Reinhard/ACES/AgX tonemappers, the tonemap dispatcher, and linearToSrgb; adds six unit tests.
Ogre compositor and material assets
cfg/resources*.cfg.in, media/compositors/QtMeshHdr.compositor, media/materials/scripts/QtMeshHdr.material
Registers media/compositors as a resource path, defines the QtMeshHdrPipeline compositor (HDR RT → tonemap quad), and declares GLSL programs plus HdrTonemapPass and HdrSkybox materials.
HDREnvironmentManager: tonemap defaults and skybox API
src/HDR/HDREnvironmentManager.h, src/HDR/HDREnvironmentManager.cpp
Adds TonemapOperator alias, tonemap getter/setter methods, tonemapChanged() signal, hasEnvironment(), and applySkyBox/removeSkyBox/updateSkyBoxMaterial with Ogre material management.
HdrViewportPipeline: per-viewport compositor lifecycle
src/HDR/HdrViewportPipeline.h, src/HDR/HdrViewportPipeline.cpp
Implements per-viewport lazy compositor wiring, tonemap uniform updates (exposureMul, tonemapOp, whitePoint), and skybox visibility propagation to the Ogre viewport.
HdrViewportController: singleton orchestrator
src/HDR/HdrViewportController.h, src/HDR/HdrViewportController.cpp
Singleton owning per-widget HdrViewportPipeline instances; reacts to environmentChanged/tonemapChanged signals to refresh pipelines and update skybox; exposes tickActiveViewports for per-frame uniform updates.
OgreWidget integration and build wiring
src/OgreWidget.cpp, src/CMakeLists.txt
Hooks OgreWidget destructor, initOgreWindow, rebuildRenderWindow, and frameStarted to register/unregister with HdrViewportController and tick viewports; adds new HDR source/header files to the build.

Sequence Diagram

sequenceDiagram
  participant OgreWidget
  participant HdrViewportController
  participant HdrViewportPipeline
  participant HDREnvironmentManager
  participant OgreCompositor

  OgreWidget->>HdrViewportController: registerWidget(this)
  HdrViewportController->>HdrViewportPipeline: new HdrViewportPipeline(widget)
  HdrViewportPipeline->>HDREnvironmentManager: hasEnvironment()
  HdrViewportPipeline->>OgreCompositor: addCompositor / enable

  HDREnvironmentManager->>HdrViewportController: environmentChanged()
  HdrViewportController->>HDREnvironmentManager: applySkyBox / removeSkyBox
  HdrViewportController->>HdrViewportPipeline: refresh()

  HDREnvironmentManager->>HdrViewportController: tonemapChanged()
  HdrViewportController->>HdrViewportPipeline: refresh()

  OgreWidget->>HdrViewportController: tickActiveViewports()
  HdrViewportController->>HdrViewportPipeline: updateTonemapUniforms()
  HdrViewportPipeline->>OgreCompositor: setNamedConstant(exposureMul / tonemapOp / whitePoint)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • fernandotonon/QtMeshEditor#778: Provides the HDREnvironmentManager singleton and cubemap-loading infrastructure that this PR extends with tonemap defaults and skybox management.

Poem

🐇 Hoppity hop through the render queue,
Reinhard, ACES, AgX — pick your hue!
Exposure stops climb, two times the light,
The skybox shimmers, oh what a sight.
Compositor pass takes the linear glow,
And maps it to sRGB, all in a row! 🌅

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.34% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive Most objectives appear covered, but the compositor shader file media/materials/programs/GLSL150/QtMeshHdrTonemapFp.glsl was filtered out, so tone-map output can't be fully verified. Please review media/materials/programs/GLSL150/QtMeshHdrTonemapFp.glsl or relax the !**/*.glsl filter so the tonemap formula and sRGB output can be verified.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately captures the main change: HDR skybox plus tone-mapping compositor for slice D.
Description check ✅ Passed It includes a solid summary, technical details, tests, and out-of-scope notes, even though the template headings aren't matched exactly.
Out of Scope Changes check ✅ Passed The changes stay focused on HDR skybox, tone mapping, and viewport wiring, with no obvious unrelated additions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/hdr-slice-d-470

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 251f723fbb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/OgreWidget.cpp
Comment on lines +302 to +303
if (auto* hdrVp = HdrViewportController::getSingletonPtr())
hdrVp->registerWidget(this);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Instantiate the HDR viewport controller before registration

registerWidget() is only reached when s_singleton already exists, but the new code never creates the controller on the normal app path (repo-wide references only hit getSingletonPtr() from OgreWidget plus the definition). As a result, loading an HDR environment emits environmentChanged with no controller listening, so neither the skybox nor the compositor pipeline is installed for any viewport.

Useful? React with 👍 / 👎.


target_output
{
input on rtHdr

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bind the HDR render target in the render_quad pass

OGRE compositor scripts bind local render textures to the fullscreen material with input <texUnit> <name> inside the pass render_quad; this target-level input on rtHdr does not bind rtHdr to the tonemap shader sampler, and on is not a texture unit. When the HDR compositor is enabled, it will either fail script parsing or run the tonemap material without the FP16 scene texture, so the viewport cannot display the intended tonemapped scene.

Useful? React with 👍 / 👎.

Comment on lines +44 to +45
if (!widget || m_pipelines.find(widget) != m_pipelines.end())
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh existing pipelines after viewport rebuilds

OgreWidget::rebuildRenderWindow() keeps the same OgreWidget* but destroys and recreates its OGRE viewport; after HDR has been enabled, the subsequent registration hits this early return and leaves the existing HdrViewportPipeline holding the old viewport/compositor pointers. Any render-window rebuild path such as MainWindow::rebuildAllOgreViewports() then loses HDR on the recreated window and can later remove a stale compositor from the wrong viewport.

Useful? React with 👍 / 👎.

Comment on lines +82 to +83
m_skyBoxVisible = HDREnvironmentManager::getSingleton()->defaultSkyBoxVisible();
m_viewport->setSkiesEnabled(m_skyBoxVisible);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve per-viewport skybox visibility on refresh

enablePipeline() runs during every tone-map refresh, so this assignment overwrites a viewport hidden through HdrViewportController::setSkyBoxVisible(widget, false) with the global default whenever exposure/operator settings change. That makes the per-viewport sky toggle unstable across unrelated tonemap updates.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/HDR/HDREnvironmentManager.cpp (1)

473-479: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

setDefaultSkyBoxVisible reuses tonemapChanged and skips a breadcrumb.

This setter emits tonemapChanged() even though sky visibility is not a tonemap parameter, conflating two concerns for downstream listeners. The three tonemap setters above also add a render.hdr.tonemap Sentry breadcrumb for the user action; this one does not. Consider a dedicated signal/breadcrumb for skybox-visibility changes.

All user-facing actions should add a Sentry breadcrumb via the established categories.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/HDR/HDREnvironmentManager.cpp` around lines 473 - 479, The
`setDefaultSkyBoxVisible` setter is incorrectly using `tonemapChanged()` and
does not record a breadcrumb, so update
`HDREnvironmentManager::setDefaultSkyBoxVisible` to use a dedicated
skybox-visibility notification instead of reusing tonemap state changes. Add a
Sentry breadcrumb for this user action using the same established pattern as the
other HDR setters, but with an appropriate `render.hdr.*` category for skybox
visibility, and keep the change localized so downstream listeners can
distinguish skybox updates from tonemap updates.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/HDR/HdrTonemap.cpp`:
- Around line 47-62: The tonemapAgx function is clipping HDR highlights too
early by using clamp01(x) before the AgX transform; change it to preserve values
above 1.0 while still preventing negative inputs, then keep the final
saturate(contrast) clamp on the returned result. Update the AgX input handling
inside tonemapAgx so only the lower bound is clamped before the matrix/contrast
steps.

In `@src/HDR/HdrViewportPipeline.cpp`:
- Around line 82-83: `HdrViewportPipeline::enablePipeline` is resetting
per-viewport skybox state from `HDREnvironmentManager::defaultSkyBoxVisible()`
on every refresh, which overwrites values previously set by
`setSkyBoxVisible(...)`. Update the logic so `m_skyBoxVisible` is initialized
from the global default only once (for example during construction or
registration), and have `enablePipeline` simply reapply the existing
`m_skyBoxVisible` when calling `m_viewport->setSkiesEnabled(...)`. Keep the
per-viewport toggle state owned by `HdrViewportPipeline` rather than re-reading
the global default on each refresh.
- Around line 88-97: The viewport rebuild path leaves HdrViewportPipeline
holding an old compositor, so refresh()/enablePipeline() never reattaches HDR
tonemapping to the new viewport because the addCompositor() guard only checks
m_compositor. Update the rebuild flow in rebuildRenderWindow() and the pipeline
attachment logic in HdrViewportPipeline so the compositor is cleared or
re-created when m_viewport changes, or make the guard validate viewport identity
before skipping addCompositor(). Ensure updateTonemapMaterialConstants() and
m_enabled reflect the newly attached viewport.

In `@src/OgreWidget.cpp`:
- Around line 301-303: The HDR viewport registration in
OgreWidget::initOgreWindow() and OgreWidget::rebuildRenderWindow() only handles
the case where HdrViewportController already exists, so widgets created earlier
are never registered if the controller is instantiated later. Fix this by either
ensuring HdrViewportController is created before any OgreWidget viewport setup,
or by having HdrViewportController’s constructor/init path discover and register
already-existing OgreWidget instances so they get attached to the HDR pipeline.

---

Nitpick comments:
In `@src/HDR/HDREnvironmentManager.cpp`:
- Around line 473-479: The `setDefaultSkyBoxVisible` setter is incorrectly using
`tonemapChanged()` and does not record a breadcrumb, so update
`HDREnvironmentManager::setDefaultSkyBoxVisible` to use a dedicated
skybox-visibility notification instead of reusing tonemap state changes. Add a
Sentry breadcrumb for this user action using the same established pattern as the
other HDR setters, but with an appropriate `render.hdr.*` category for skybox
visibility, and keep the change localized so downstream listeners can
distinguish skybox updates from tonemap updates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e947cc6-331c-4b8f-87ab-3ac600624ee4

📥 Commits

Reviewing files that changed from the base of the PR and between d82b2f7 and 251f723.

⛔ Files ignored due to path filters (1)
  • media/materials/programs/GLSL150/QtMeshHdrTonemapFp.glsl is excluded by !**/*.glsl
📒 Files selected for processing (15)
  • cfg/resources.cfg.in
  • cfg/resources_d.cfg.in
  • media/compositors/QtMeshHdr.compositor
  • media/materials/scripts/QtMeshHdr.material
  • src/CMakeLists.txt
  • src/HDR/HDREnvironmentManager.cpp
  • src/HDR/HDREnvironmentManager.h
  • src/HDR/HdrTonemap.cpp
  • src/HDR/HdrTonemap.h
  • src/HDR/HdrTonemap_test.cpp
  • src/HDR/HdrViewportController.cpp
  • src/HDR/HdrViewportController.h
  • src/HDR/HdrViewportPipeline.cpp
  • src/HDR/HdrViewportPipeline.h
  • src/OgreWidget.cpp

Comment thread src/HDR/HdrTonemap.cpp
Comment on lines +47 to +62
Rgb tonemapAgx(Rgb x)
{
// Troy Sobotka's AgX default contrast approximation (Blender 4+ default).
const Rgb lin = clamp01(x);
const Rgb agx = {
0.224282f * lin.r + 0.130789f * lin.g + 0.044929f * lin.b,
0.050223f * lin.r + 0.873461f * lin.g + 0.076316f * lin.b,
0.020833f * lin.r + 0.080745f * lin.g + 0.898422f * lin.b,
};
const Rgb contrast = {
std::pow(std::max(0.f, agx.r), 1.35f),
std::pow(std::max(0.f, agx.g), 1.35f),
std::pow(std::max(0.f, agx.b), 1.35f),
};
return saturate(contrast);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file list =="
git ls-files src/HDR/HdrTonemap.cpp

echo
echo "== relevant excerpt =="
nl -ba src/HDR/HdrTonemap.cpp | sed -n '1,220p'

echo
echo "== search for tonemap references =="
rg -n "tonemapAgx|tonemapAces|tonemapReinhard|clamp01|AgX|Agx" src -S

Repository: fernandotonon/QtMeshEditor

Length of output: 267


🌐 Web query:

AgX default contrast approximation clamp input 0 1 Blender 4 reference tonemapAgx

💡 Result:

In Blender's AgX implementation, the input is explicitly clamped or constrained to ensure the color processing (particularly the AgX inset matrix and log-space encoding) behaves predictably [1][2]. Key details regarding AgX and input clamping include: 1. Input Handling: AgX implementations, including the one used in Blender, typically perform a conversion to linear Rec.2020 space [1][2]. Preventing negative input values is necessary because negative values can cause the AgX inset matrix to produce undesirable artifacts or darker, more saturated colors than intended [1][2]. Consequently, implementations often use a max(color, 0.0) operation or a similar floor before the inset matrix [1][2]. 2. Log-Space Clamping: After the inset matrix, the values are converted to log2 space [1][2]. The log-transformed data is then clamped within a specific dynamic range (typically defined by min_ev and max_ev, e.g., -10 to +6.5 in some reference implementations) before being passed to the sigmoid approximation function [1][2]. This ensures the input to the contrast approximation remains within a stable range [2]. 3. LUT Output Clamping: In Blender's official AgX view transform, the 3D LUTs used to approximate the tone mapping process are designed to clip output values to the [3] range [4]. Earlier versions of these LUTs sometimes included values slightly above 1.0, which was deemed incorrect for a tone mapper and redundant because Blender's display pipeline clips these values regardless [4]. 4. TonemapAgx References: While standard Blender compositor nodes include a Tonemap node (with Reinhard/Reinhard-Devlin algorithms) [5], AgX itself is implemented as an OCIO-based view transform rather than a standard compositor tone mapping node [3][6][7]. References to tonemapAgx in code (such as in external engines like Godot) are approximations or manual implementations of the AgX logic found in Blender's config [1][2]. In summary, the clamping to [3] is a fundamental part of the AgX output stage to conform to standard SDR display requirements [4][7], while input clamping (at 0.0) is a technical requirement to maintain the integrity of the color math during the transformation process [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/HDR/HdrTonemap.cpp excerpt =="
sed -n '1,220p' src/HDR/HdrTonemap.cpp

echo
echo "== clamp01 definition and tonemap references =="
rg -n "clamp01|tonemapAgx|tonemapAces|tonemapReinhard|tonemap" src/HDR -S

Repository: fernandotonon/QtMeshEditor

Length of output: 5894


Preserve HDR values before AgX
clamp01(x) clips all highlights above 1.0 before the AgX transform, so the operator cannot roll off HDR intensities. Clamp negatives only here; keep the final output clamp.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/HDR/HdrTonemap.cpp` around lines 47 - 62, The tonemapAgx function is
clipping HDR highlights too early by using clamp01(x) before the AgX transform;
change it to preserve values above 1.0 while still preventing negative inputs,
then keep the final saturate(contrast) clamp on the returned result. Update the
AgX input handling inside tonemapAgx so only the lower bound is clamped before
the matrix/contrast steps.

Comment on lines +82 to +83
m_skyBoxVisible = HDREnvironmentManager::getSingleton()->defaultSkyBoxVisible();
m_viewport->setSkiesEnabled(m_skyBoxVisible);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

enablePipeline resets per-viewport skybox visibility on every refresh.

Line 82 overwrites m_skyBoxVisible with the global defaultSkyBoxVisible() each time the pipeline is enabled. Since refresh() runs on every environmentChanged/tonemapChanged (via HdrViewportController::refreshAll), any per-viewport state previously set through setSkyBoxVisible(...) is silently discarded back to the global default. This conflicts with the PR objective of a per-viewport skybox toggle that survives independent of global defaults.

Consider seeding from the default only on first enable, and otherwise re-applying the current m_skyBoxVisible:

♻️ Proposed fix
     m_viewport = const_cast<Ogre::Viewport*>(vpConst);
-    m_skyBoxVisible = HDREnvironmentManager::getSingleton()->defaultSkyBoxVisible();
-    m_viewport->setSkiesEnabled(m_skyBoxVisible);
+    m_viewport->setSkiesEnabled(m_skyBoxVisible);

(Initialize m_skyBoxVisible from the default once at construction/registration instead of on every enable.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
m_skyBoxVisible = HDREnvironmentManager::getSingleton()->defaultSkyBoxVisible();
m_viewport->setSkiesEnabled(m_skyBoxVisible);
m_viewport->setSkiesEnabled(m_skyBoxVisible);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/HDR/HdrViewportPipeline.cpp` around lines 82 - 83,
`HdrViewportPipeline::enablePipeline` is resetting per-viewport skybox state
from `HDREnvironmentManager::defaultSkyBoxVisible()` on every refresh, which
overwrites values previously set by `setSkyBoxVisible(...)`. Update the logic so
`m_skyBoxVisible` is initialized from the global default only once (for example
during construction or registration), and have `enablePipeline` simply reapply
the existing `m_skyBoxVisible` when calling `m_viewport->setSkiesEnabled(...)`.
Keep the per-viewport toggle state owned by `HdrViewportPipeline` rather than
re-reading the global default on each refresh.

Comment on lines +88 to +97
if (!m_compositor) {
m_compositor = Ogre::CompositorManager::getSingleton().addCompositor(
m_viewport, kCompositorName);
if (m_compositor)
Ogre::CompositorManager::getSingleton().setCompositorEnabled(
m_viewport, kCompositorName, true);
}

updateTonemapMaterialConstants();
m_enabled = m_compositor != nullptr;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether the viewport rebuild path tears down the HDR pipeline/compositor first.
rg -nP -C4 '\brebuildRenderWindow\b' src/OgreWidget.cpp
rg -nP -C3 '(unregisterWidget|registerWidget|disablePipeline|refresh)\b' src/HDR/HdrViewportController.cpp src/OgreWidget.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 2735


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '348,410p' src/OgreWidget.cpp
printf '\n----\n'
sed -n '1,180p' src/HDR/HdrViewportController.cpp
printf '\n----\n'
sed -n '1,180p' src/HDR/HdrViewportPipeline.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 8915


Rebuild needs to reattach the compositor to the new viewport. rebuildRenderWindow() recreates the viewport, but the existing HdrViewportPipeline stays registered on the widget, so refresh()/enablePipeline() skips addCompositor() once m_compositor is already set. Clear the pipeline on viewport rebuild or key the guard off viewport identity, otherwise HDR tonemapping stays bound to the old viewport.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/HDR/HdrViewportPipeline.cpp` around lines 88 - 97, The viewport rebuild
path leaves HdrViewportPipeline holding an old compositor, so
refresh()/enablePipeline() never reattaches HDR tonemapping to the new viewport
because the addCompositor() guard only checks m_compositor. Update the rebuild
flow in rebuildRenderWindow() and the pipeline attachment logic in
HdrViewportPipeline so the compositor is cleared or re-created when m_viewport
changes, or make the guard validate viewport identity before skipping
addCompositor(). Ensure updateTonemapMaterialConstants() and m_enabled reflect
the newly attached viewport.

Comment thread src/OgreWidget.cpp
Comment on lines +301 to +303

if (auto* hdrVp = HdrViewportController::getSingletonPtr())
hdrVp->registerWidget(this);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Who creates the controller (getSingleton) vs only reads it (getSingletonPtr)?
rg -nP 'HdrViewportController::(getSingleton|getSingletonPtr|kill)\s*\(' src
echo "--- controller ctor / registerWidget call sites ---"
rg -nP '\bregisterWidget\s*\(' src
echo "--- does the controller enumerate existing widgets on creation? ---"
ast-grep outline src/HDR/HdrViewportController.cpp --view expanded

Repository: fernandotonon/QtMeshEditor

Length of output: 1251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- src/HDR/HdrViewportController.cpp ---"
wc -l src/HDR/HdrViewportController.cpp
cat -n src/HDR/HdrViewportController.cpp | sed -n '1,220p'

echo "--- src/OgreWidget.cpp around registration sites ---"
wc -l src/OgreWidget.cpp
cat -n src/OgreWidget.cpp | sed -n '120,410p'

Repository: fernandotonon/QtMeshEditor

Length of output: 14872


Late controller creation leaves existing viewports unregistered. initOgreWindow() and rebuildRenderWindow() only register when HdrViewportController::getSingletonPtr() is already non-null, and the controller ctor doesn’t pick up already-live OgreWidgets. If the controller is first created after some viewports exist, those widgets never get HDR pipelines. Create the controller before viewport init or register existing widgets when it’s instantiated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/OgreWidget.cpp` around lines 301 - 303, The HDR viewport registration in
OgreWidget::initOgreWindow() and OgreWidget::rebuildRenderWindow() only handles
the case where HdrViewportController already exists, so widgets created earlier
are never registered if the controller is instantiated later. Fix this by either
ensuring HdrViewportController is created before any OgreWidget viewport setup,
or by having HdrViewportController’s constructor/init path discover and register
already-existing OgreWidget instances so they get attached to the HDR pipeline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HDR: Slice D — Skybox + tone-mapping compositor (Reinhard / ACES / AgX)

1 participant