Skip to content

fix(mocap): retarget live body drive via landmark directions - #939

Open
fernandotonon wants to merge 8 commits into
masterfrom
feat/mocap-landmark-body-drive
Open

fix(mocap): retarget live body drive via landmark directions#939
fernandotonon wants to merge 8 commits into
masterfrom
feat/mocap-landmark-body-drive

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Live body mocap: retarget Mixamo skeletons via MediaPipe landmark segment directions (same aim math as applyMotionClip), with PoseIK debug overlay, per-frame skinning refresh, and removal of the mirror-L/R toggle.
  • ONNX Runtime settings: add OnnxRuntimeSettings QML singleton — prefer-GPU toggle in AI Settings, centralized session options for all ONNX predictors (UniRig, segmentation, mocap, PBR, etc.), optional QTMESH_ONNX_GPU package on Linux x64, and provider .so copy rules so the app links and runs cleanly.
  • CI/packaging: Linux CI builds use -DQTMESH_ONNX_GPU=OFF; .deb bundles Qt JPEG imageformat plugin (MJPEG webcams) alongside existing FFmpeg stubs.

Test plan

  • ./build_local/bin/UnitTests --gtest_filter="AnimationMergerTest.BodyRetargeter*"
  • ./build_local/bin/UnitTests --gtest_filter="OnnxRuntimeSettings*"
  • cmake --build build_local --target QtMeshEditor -j4
  • Live preview on Boss/Jump: Body + Show PoseIK debug skeleton — mesh tracks yellow FK
  • AI Settings → Settings → Prefer GPU for ONNX toggles; status note reflects provider availability
  • Record a short body clip; baked playback matches live preview

Follow-up

  • Jitter/shaking in PoseIK and retargeted skeleton — improve smoothing in a separate PR (One Euro tuning, visibility gating).

Summary by CodeRabbit

  • New Features
    • Improved Performance Capture with neutral calibration, live body retargeting, face-and-body capture, recording, and optional PoseIK debug visuals.
    • Added camera format handling, JPEG/MJPEG support, device refresh, and smoother preview startup.
    • Added ONNX GPU preference and provider-status controls in Settings.
    • Added automatic mesh-size-aware sampling for AI processing.
  • Bug Fixes
    • Improved Linux camera reliability and troubleshooting for FFmpeg, JPEG, NVIDIA, and X11 issues.
    • Corrected head pitch, mirrored body movement, and CMU motion-handling issues.
  • Documentation
    • Expanded capture guidance, troubleshooting information, and known limitations.
  • Tests
    • Expanded coverage for mocap, video conversion, ONNX settings, and mesh sampling.

Drive Mixamo skeletons from MediaPipe landmark segment directions (same
aim math as applyMotionClip) instead of quaternion deltas and mirror-L/R
swaps. Adds PoseIK debug overlay, per-frame skinning refresh, and tests.

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

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Mocap capture and retargeting

Layer / File(s) Summary
Pose contracts and camera processing
src/Mocap/MocapLiveTypes.h, src/Mocap/MocapPoseFix.h, src/Mocap/PoseIKSolver.*, src/Mocap/VideoFrameSource.*
Adds live body-frame data, canonical landmark directions, mirror and camera corrections, JPEG/MJPEG fallback decoding, camera format selection, and related tests.
Neutral-aware retargeting and recording
src/AnimationMerger.*, src/Mocap/MocapRecorder.*, src/commands/RecordMocapClipCommand.*
Adds neutral references, landmark retargeting, fallback parent resolution, role masks, yaw correction, conditional CMU handedness, live recording, and command dispatch.
Preview driving and diagnostics
src/Mocap/MocapController.*, src/Mocap/MocapPoseDebugOverlay.*, src/Mocap/MocapBodyDriveDebug.*, qml/PropertiesPanel.qml, docs/MOCAP.md
Integrates calibration, cached devices, head and body driving, debug overlays, diagnostics, lifecycle restoration, recording conditions, and capture guidance.

ONNX runtime configuration

Layer / File(s) Summary
Shared runtime and inference integration
src/OnnxRuntimeSettings.*, src/main.cpp, src/mainwindow.cpp, src/FaceRig/*, src/ImageTo3D/*, src/Mocap/*Predictor.cpp, src/Motion*.cpp, src/PbrMapSynth.cpp, src/SkinTokensPredictor.cpp, src/TextureUpscaler.cpp, src/UniRigPredictor.*
Centralizes ONNX session configuration, adds persisted GPU provider state, updates inference consumers, reuses model sessions, and adds mesh-aware sampling.
Build, packaging, and settings UI
cmake/OnnxRuntime.cmake, CMakeLists.txt, src/CMakeLists.txt, tests/CMakeLists.txt, .github/workflows/deploy.yml, scripts/install-onnx-gpu-deps.sh, qml/AISettingsDialog.qml, src/LLMSettingsWidget.*
Adds CPU/GPU archive selection, provider-library packaging, GPU dependency installation, and GPU preference controls with status reporting.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant VideoFrameSource
  participant MocapController
  participant PoseIKSolver
  participant BodyRetargeter
  participant MocapPoseDebugOverlay

  VideoFrameSource->>MocapController: deliver decoded QVideoFrame
  MocapController->>PoseIKSolver: solve canonical body landmarks
  PoseIKSolver-->>MocapController: return quaternions and landmark directions
  MocapController->>BodyRetargeter: evaluate frame with neutral reference
  BodyRetargeter-->>MocapController: return retargeted skeleton locals
  MocapController->>MocapPoseDebugOverlay: render live landmarks and PoseIK segments
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: live body retargeting through landmark directions.
Description check ✅ Passed The description summarizes the main changes, documents testing, identifies pending validation, and notes follow-up work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mocap-landmark-body-drive

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: 8d707b632c

ℹ️ 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".

// stream recordBody expects (identity for roles unresolved that frame), and
// push a SEPARATE undo command so face + body each undo cleanly.
if (!d->bodyBones.empty() && d->bodyTake.size() >= 2) {
if (d->bodyRetargeter && d->bodyTake.size() >= 2) {

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 Preserve landmark directions when baking recordings

When body recording is enabled, this branch still serializes only bf.quats, even though the live preview now uses bf.world and bf.visibility as its primary landmark-direction inputs. MocapRecorder::recordBody() consequently falls back to applyMotionClip() with an empty clipRestDir, so motions that were corrected by the new direction-based live path are baked through the old quaternion path and can play back differently from what the user recorded. Pass the landmark stream/neutral anchors into the bake or bake the live retargeter's evaluated locals.

Useful? React with 👍 / 👎.

Comment thread src/AnimationMerger.cpp Outdated
Comment on lines +1795 to +1796
if (c < 0 || c >= Jc)
continue;

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 Propagate world transforms through unmapped parent bones

On rigs containing an unmapped helper or twist bone between canonical bones, this continue leaves W[i] without that bone's actual world transform. A later mapped descendant reads that entry as Wp and computes Wp.Inverse() * Wt, producing an invalid local orientation (and Ogre quaternion defaults may be uninitialized). Populate W for every hierarchy node using its standing/bind local transform even when the node itself is not emitted.

Useful? React with 👍 / 👎.

Comment thread src/Mocap/MocapController.cpp Outdated
Comment on lines +737 to +741
BodyManualBoneSnapshot snap;
snap.boneName = bone->getName();
snap.bindLocal = bone->getOrientation();
snap.wasManuallyControlled = bone->isManuallyControlled();
d->bodyManualRestore.push_back(std::move(snap));

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 Restore the head's original manual-control flag

When both Head and Body are enabled, the head is marked manually controlled before this body snapshot, so snap.wasManuallyControlled is always true even if it was false before preview. During restoration the dedicated head snapshot first restores false, but the subsequent body snapshot loop overwrites it back to true, leaving the head outside normal animation control after every body preview. Snapshot all body bones before enabling the head, exclude the head from this list, or reverse the restoration precedence.

Useful? React with 👍 / 👎.

Comment thread src/Mocap/MocapController.cpp Outdated
Comment on lines +774 to +777
if (!d->skinningListener)
d->skinningListener = std::make_unique<Impl::SkinningFrameListener>();
d->skinningListener->impl = d.get();
Ogre::Root::getSingleton().addFrameListener(d->skinningListener.get());

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 Unregister the skinning listener on startup failure

The listener is registered before camera->open() and predictor loading, but either failure path only calls restoreEntityState() and returns while the controller remains Idle; stopPreview() therefore cannot reach the matching removeFrameListener(). A failed preview leaves Ogre holding this listener, and later retries can register it again or controller destruction can leave a dangling callback. Register it only after all fallible setup succeeds or remove it on every early-return cleanup path.

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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Mocap/MocapRecorder.cpp (1)

339-345: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The validation harness no longer mirrors the live path.

The comment states that this branch runs "the EXACT math the LIVE preview runs". After this PR the live preview drives BodyRetargeter with MediaPipe landmark directions, but this call passes only canonical quaternions (rt.setNeutralReference(q) and rt.evaluateFrame(q, 0xFFFFFFFFu)), so the retargeter takes the quaternion-delta branch. A rendered clip from QTMESH_MOCAP_USE_RETARGETER=1 therefore validates a different code path.

If the recorder has the source landmarks per frame, pass them to both calls. If it does not, correct the comment so it does not promise live-path parity.

Also applies to: 363-364

🤖 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/Mocap/MocapRecorder.cpp` around lines 339 - 345, Update the validation
harness around BodyRetargeter rt, including its setNeutralReference and
evaluateFrame calls, to pass the per-frame MediaPipe landmark directions used by
the live preview when those source landmarks are available, preserving the live
retargeter branch for both initialization and evaluation. If MocapRecorder lacks
those landmarks, revise the nearby “EXACT math the LIVE preview runs” comment to
state that the harness uses the quaternion-delta path instead of claiming
live-path parity.
🧹 Nitpick comments (16)
src/Mocap/MocapLiveTypes.h (1)

12-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Default-initialize quats for consistency.

world and visibility use {}, but quats does not. A BodyLiveFrame body; declaration (see src/Mocap/MocapController.cpp line 202) therefore leaves quats indeterminate until valid is set. The current consumers guard on valid, so this is not an active defect. Add the initializer to keep the type safe under future changes.

♻️ Proposed change
-    std::array<std::array<float, 4>, PoseIK::kCanonicalRoles> quats;
+    std::array<std::array<float, 4>, PoseIK::kCanonicalRoles> quats{};
🤖 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/Mocap/MocapLiveTypes.h` around lines 12 - 18, Update the quats member
initializer in BodyLiveFrame to value-initialize it with {}, matching world and
visibility so default-constructed frames have initialized quaternion data.
src/Mocap/MocapPoseDebugOverlay.cpp (2)

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

m_lastScale is written but never read.

Remove the field, or use it to skip a rebuild when the scale is unchanged.

🤖 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/Mocap/MocapPoseDebugOverlay.cpp` at line 301, Address the unused
m_lastScale assignment in the MocapPoseDebugOverlay update path: either remove
the m_lastScale field and its write, or use it to detect an unchanged scale and
skip the rebuild while preserving rebuilds when the scale changes.

232-233: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Scale the overlay offset with the entity size.

setPosition(0.8f, 0.f, 0.f) is a fixed local offset, but update() scales the stick figure to the entity height. For a tall or a small entity the overlay overlaps the mesh. Derive the offset from the same height value that update() receives.

🤖 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/Mocap/MocapPoseDebugOverlay.cpp` around lines 232 - 233, Update the
overlay root positioning in MocapPoseDebugOverlay initialization to derive the x
offset from the entity height used by update(), replacing the fixed 0.8f value
while preserving the zero y and z offsets. Reuse the existing height-related
symbol or pass the height into the setup path so the overlay scales consistently
with the stick figure.
src/Mocap/MocapController.cpp (2)

145-156: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider one bone snapshot type.

BodyDriveBone and BodyManualBoneSnapshot hold the same three fields plus a role. BodyDriveBone with role = -1 already covers the snapshot case. One type reduces the parallel bookkeeping in beginPreviewWithLiveSource and restoreEntityState.

🤖 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/Mocap/MocapController.cpp` around lines 145 - 156, Replace
BodyManualBoneSnapshot with BodyDriveBone, using role = -1 for snapshot entries.
Update beginPreviewWithLiveSource and restoreEntityState to use the unified type
and remove the redundant snapshot struct while preserving existing boneName,
bindLocal, and wasManuallyControlled handling.

279-280: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the raw role bit literals with the PoseIK enumerators.

kTorsoResolvedMask uses (1u << 0) | (1u << 1) | (1u << 2) and skipHead uses (1u << 5). The PoseIK role enumerators already name these indices. Literals break silently if the role order changes.

♻️ Proposed change
-    static constexpr uint32_t kTorsoResolvedMask =
-        (1u << 0) | (1u << 1) | (1u << 2);  // hip, abdomen, chest
+    static constexpr uint32_t kTorsoResolvedMask =
+        (1u << PoseIK::Hip) | (1u << PoseIK::Abdomen) | (1u << PoseIK::Chest);
-        const uint32_t skipHead =
-            (d->headEnabled && !d->headBone.isEmpty()) ? (1u << 5) : 0u;
+        const uint32_t skipHead = (d->headEnabled && !d->headBone.isEmpty())
+                                      ? (1u << PoseIK::Head)
+                                      : 0u;

Also applies to: 1064-1065

🤖 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/Mocap/MocapController.cpp` around lines 279 - 280, Replace the raw bit
shifts in kTorsoResolvedMask and skipHead with the corresponding PoseIK role
enumerators for hip, abdomen, chest, and head, while preserving the existing
mask behavior. Use the enumerator-based shifts so role index changes remain
synchronized with these masks.
src/Mocap/MocapBodyDriveDebug.cpp (2)

147-158: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use PoseIK::kCanonicalRoles for the bound.

roleName compares against the literal 22. Use PoseIK::kCanonicalRoles, and add a static assertion that the name table matches that count.

♻️ Proposed change
-    if (role >= 0 && role < 22)
+    static_assert(std::size(names) == PoseIK::kCanonicalRoles,
+                  "role name table out of sync");
+    if (role >= 0 && role < PoseIK::kCanonicalRoles)
         return names[role];
🤖 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/Mocap/MocapBodyDriveDebug.cpp` around lines 147 - 158, Update roleName to
use PoseIK::kCanonicalRoles instead of the literal 22 when validating role
indices, and add a static assertion confirming the names table contains exactly
that number of entries. Preserve the existing "?" fallback for out-of-range
roles.

234-253: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Resolve the role-to-bone map once per frame.

The bone lookup scans every skeleton bone and calls MotionInbetween::canonicalIndexForBone for each of the ten debug roles. That is 10 * numBones string conversions per logged frame. Build one role-to-bone table before the loop.

🤖 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/Mocap/MocapBodyDriveDebug.cpp` around lines 234 - 253, Build the
role-to-bone table once before the kLimbRoles loop by scanning skel’s bones a
single time, converting each bone name with
MotionInbetween::canonicalIndexForBone, and storing the matching bone and child
relationship by role. Update the loop to retrieve entries from this table while
preserving the resolvedMask filtering and existing child selection behavior.
src/Mocap/MocapPoseDebugOverlay.h (1)

17-33: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Document or enforce the detach requirement.

The class owns an Ogre::SceneNode and two Ogre::ManualObject instances but has no destructor. If the owner is destroyed while the overlay is attached, those scene objects stay in the scene manager. Add a destructor that calls detach(), or state the "caller must call detach() before destruction" contract in a comment.

🤖 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/Mocap/MocapPoseDebugOverlay.h` around lines 17 - 33, Add a destructor to
MocapPoseDebugOverlay that calls detach(), ensuring the owned scene node and
ManualObject instances are removed when an attached overlay is destroyed. Keep
the existing detach cleanup behavior and avoid requiring callers to remember an
additional lifecycle step.
src/Mocap/PoseIKSolver_test.cpp (2)

211-212: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the comment with the assertion.

The comment mentions yaw, but the assertion measures the Hip role rotation. Reword the comment to state that the torso must stay stable during a nod.

🤖 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/Mocap/PoseIKSolver_test.cpp` around lines 211 - 212, Update the comment
above the EXPECT_LT assertion in the nodding test to state that the torso
remains stable during a nod, matching the asserted PoseIK::Hip rotation
behavior; leave the assertion unchanged.

236-262: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a fresh Solver for the corrected pass.

PoseIK::Solver keeps per-frame history (m_prevSecondary, m_prevPrimary, m_prevQuats, m_prevHipLine). fOk is the third frame on the same solver, so the twist reference and quaternion hold-over from the mirrored frame feed into it. The assertions then depend on frame order rather than on the swap itself. Solve the corrected landmarks with a second solver that starts from the same neutral baseline.

♻️ Proposed change
     Landmarks fixed = tPose();
+    PoseIK::Solver fixedSolver;
+    const auto fixedBase = fixedSolver.solveFrame(fixed.data());
     set(fixed, 15, 0.18f, -0.75f, 0.f);
     set(fixed, 13, 0.18f, -0.55f, 0.f);
     MocapPoseFix::swapMediaPipeLeftRightLandmarks(fixed.data());
-    const auto fOk = solver.solveFrame(fixed.data());
-    const double fixedRight = quatAngle(f0.quats[PoseIK::RShoulder],
+    const auto fOk = fixedSolver.solveFrame(fixed.data());
+    const double fixedRight = quatAngle(fixedBase.quats[PoseIK::RShoulder],
                                         fOk.quats[PoseIK::RShoulder]);
🤖 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/Mocap/PoseIKSolver_test.cpp` around lines 236 - 262, Use a separate
PoseIK::Solver instance for the corrected landmark pass in
PoseIKSolver.WebcamMirrorSwapFixesRightArmRaise. Initialize the new solver from
the same neutral tPose baseline, then solve the swapped fixed landmarks with it
so fOk is not influenced by fWrong’s per-frame history; keep the existing
assertions and comparison targets unchanged.
src/Mocap/PoseIKSolver.cpp (2)

235-239: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Remove the redundant landmark copy.

canon already holds the canonical points. The extra p array copies 33 vectors on every frame. Use canon directly and keep the existing p[...] accesses working through a reference.

♻️ Proposed change
-    Vec3 p[kLandmarkCount];
-    std::array<std::array<float, 3>, kLandmarkCount> canon{};
-    canonicalizeMediaPipeWorld(world, canon);
-    for (int i = 0; i < kLandmarkCount; ++i)
-        p[i] = canon[static_cast<size_t>(i)];
+    std::array<std::array<float, 3>, kLandmarkCount> canon{};
+    canonicalizeMediaPipeWorld(world, canon);
+    const auto& p = canon;
🤖 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/Mocap/PoseIKSolver.cpp` around lines 235 - 239, In the pose-solving
routine around canonicalizeMediaPipeWorld, remove the redundant p array and
population loop, then bind p as a reference or alias to canon so existing p[...]
accesses continue working while using the canonical points directly.

110-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share one segment table and use the Lm enum.

This table duplicates the limb table in solveFrame (lines 318-325) and uses raw landmark indices instead of the Lm enumerators used everywhere else in this file. Two copies can diverge silently, and a wrong literal is hard to spot. Move the table to the anonymous namespace and reuse it in both places.

♻️ Proposed change
-    struct Segment {
-        Role r;
-        int from, to;
-    };
-    static const Segment segments[] = {
-        {RShoulder, 12, 14}, {RElbow, 14, 16},
-        {LShoulder, 11, 13}, {LElbow, 13, 15},
-        {RHip, 24, 26}, {RKnee, 26, 28}, {RFoot, 28, 32},
-        {LHip, 23, 25}, {LKnee, 25, 27}, {LFoot, 27, 31},
-    };
+    // kLimbSegments lives in the anonymous namespace and is shared with solveFrame.
+    for (const LimbSegment& seg : kLimbSegments) {

Define once near the Lm enum:

struct LimbSegment { Role role; int from, to; };
constexpr LimbSegment kLimbSegments[] = {
    {RShoulder, RShoulderLm, RElbowLm}, {RElbow, RElbowLm, RWrist},
    {LShoulder, LShoulderLm, LElbowLm}, {LElbow, LElbowLm, LWrist},
    {RHip, RHipLm, RKneeLm}, {RKnee, RKneeLm, RAnkle}, {RFoot, RAnkle, RFootIndex},
    {LHip, LHipLm, LKneeLm}, {LKnee, LKneeLm, LAnkle}, {LFoot, LAnkle, LFootIndex},
};
🤖 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/Mocap/PoseIKSolver.cpp` around lines 110 - 119, Move the limb segment
definition near the Lm enum in the anonymous namespace, rename it to the shared
LimbSegment/kLimbSegments form, and replace raw landmark literals with the
appropriate Lm enumerators. Remove the local duplicate Segment table and update
both the surrounding solver logic and solveFrame to iterate over kLimbSegments,
preserving the existing segment ordering and roles.
src/AnimationMerger_test.cpp (1)

1337-1357: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert that the untouched arm stays still.

The test raises only the left elbow and wrist landmarks and asserts left-arm motion. The PR objectives list "independent arm movement" as unvalidated. Add a check on the right upper arm so a regression that couples both arms fails here.

💚 Proposed extra assertion
     auto armDir = [&]() -> Ogre::Vector3 {
         skelInst->_updateTransforms();
         return (skelInst->getBone("LeftForeArm")->_getDerivedPosition()
                 - skelInst->getBone("LeftArm")->_getDerivedPosition())
             .normalisedCopy();
     };
+    auto rightArmDir = [&]() -> Ogre::Vector3 {
+        skelInst->_updateTransforms();
+        return (skelInst->getBone("RightForeArm")->_getDerivedPosition()
+                - skelInst->getBone("RightArm")->_getDerivedPosition())
+            .normalisedCopy();
+    };
@@
     applyLocals(rt.evaluateFrame(neutralFr.quats, neutralFr.resolvedMask, 0,
                                  neutralLm.data(), nullptr));
     const Ogre::Vector3 tPoseArm = armDir();
+    const Ogre::Vector3 tPoseRightArm = rightArmDir();
@@
     const float raisedMotion = degBetween(tPoseArm, armDir());
 
     EXPECT_GT(raisedMotion, 25.0f);
+    // The right-arm landmarks did not move, so that arm must hold still.
+    EXPECT_LT(degBetween(tPoseRightArm, rightArmDir()), 5.0f);
🤖 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/AnimationMerger_test.cpp` around lines 1337 - 1357, Extend the test
around the neutral and raised evaluations to capture the right upper-arm
direction alongside tPoseArm, then compute its motion after applying
raisedLocals. Add an assertion that the untouched right arm remains effectively
stationary, while preserving the existing raisedMotion assertion for the left
arm.
docs/MOCAP.md (1)

137-146: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the new PoseIK debug overlay.

The panel now exposes "Show PoseIK debug skeleton" with cyan MediaPipe landmarks and a yellow FK stick figure. This is the main triage tool for a wrong retarget, but the document does not mention it. Add a short line in the live-mode or troubleshooting section.

🤖 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 `@docs/MOCAP.md` around lines 137 - 146, Update the live-mode or
troubleshooting documentation near the existing PoseIK behavior notes to mention
the “Show PoseIK debug skeleton” panel option, including that it displays cyan
MediaPipe landmarks and a yellow FK stick figure for diagnosing retargeting
issues.
src/AnimationMerger.cpp (1)

1491-1506: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard kParentCanon against a canonical-joint-count change.

effectiveParentRole indexes the fixed 22-entry kParentCanon with role. Callers bound role by Jc = MotionInbetween::canonicalJointCount(). If that count ever grows, the read goes out of bounds. Add a compile-time check next to the table.

♻️ Proposed guard
 constexpr int kParentCanon[22] = {
     -1, 0, 1, 2, 3, 4,   // hip, abdomen, chest, neck, neck1, head
      2, 6, 7, 8,          // rcollar, rshoulder, relbow, rhand
      2, 10, 11, 12,       // lcollar, lshoulder, lelbow, lhand
      0, 14, 15, 16,       // rbuttock, rhip, rknee, rfoot
      0, 18, 19, 20 };     // lbuttock, lhip, lknee, lfoot
+
+static_assert(std::size(kParentCanon) == 22,
+              "kParentCanon must cover every canonical role");

Add a runtime check in the retargeter constructor as well, for example d->Jc = std::min(MotionInbetween::canonicalJointCount(), 22);, or assert equality.

🤖 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/AnimationMerger.cpp` around lines 1491 - 1506, Add a compile-time
assertion immediately after the kParentCanon declaration requiring its size to
match the supported 22 canonical joints, preventing effectiveParentRole from
indexing beyond the table if the canonical-joint count changes. Also enforce the
same bound in the retargeter constructor when assigning d->Jc, either by
asserting equality or clamping the canonicalJointCount() result to the table
capacity.
src/Mocap/MocapPoseFix.h (1)

20-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Keep the live mirror helper and remove the legacy yaw helpers.

swapMediaPipeLeftRightLandmarks is still used by PoseIKSolver_test, while swapCanonicalLeftRight, kPoseToSkeletonYawPi, invertCameraYawDelta, poseDirectionToSkeleton, and poseRotationToSkeleton have no callers outside the header. Remove those unused helpers, or add a comment noting their remaining use if they are kept for future legacy clip support.

🤖 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/Mocap/MocapPoseFix.h` around lines 20 - 58, Keep
swapMediaPipeLeftRightLandmarks because PoseIKSolver_test still uses it, but
remove the unused legacy helpers swapCanonicalLeftRight, kPoseToSkeletonYawPi,
invertCameraYawDelta, poseDirectionToSkeleton, and poseRotationToSkeleton from
the header. If any are intentionally retained for future legacy clip support,
add a clear comment documenting that purpose.
🤖 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 `@qml/PropertiesPanel.qml`:
- Around line 638-645: Add a contentReady signal to CollapsibleSection, then
emit it from the contentLoader onLoaded handler after the section content
finishes loading. This enables the existing onContentReady handler in
PropertiesPanel to invoke MocapController.refreshDevices when the section is
expanded.

In `@src/AnimationMerger.cpp`:
- Around line 1793-1832: Update the loop building the live animation output so
bones skipped for invalid canonical mappings or skipRolesMask still compute and
store their bind/stand world transform in W[i] as Wp * base before continuing.
Keep these bones excluded from out, and preserve the existing transformed-role
handling for emitted bones so descendants use the correct parent world.

In `@src/Mocap/MocapBodyDriveDebug.cpp`:
- Around line 69-145: Consolidate fkPoseIkJoints and its duplicated helpers
effectiveParentRole, localArtic, and quatFromArray into one shared
implementation used by both diagnostics. Preserve the overlay behavior in the
root-less resolved-role branch by assigning worldRot for the current role from
its quaternion before placing descendants, so both callers produce identical
results.
- Around line 13-16: Add direct includes for the symbols used by
MocapBodyDriveDebug.cpp: include <algorithm> for std::max/std::min, <QtGlobal>
for qEnvironmentVariableIsSet, and <QString> for QString::fromStdString, rather
than relying on transitive includes from AnimationMerger.h or MotionInbetween.h.

In `@src/Mocap/MocapController.cpp`:
- Around line 774-781: Update the MocapController failure paths following
addFrameListener to remove the registered skinningListener before each return
false, and add equivalent cleanup to ~MocapController. Reuse the existing
stopPreview cleanup behavior or Ogre::Root::removeFrameListener with the same
listener, ensuring no registered listener can outlive the controller.
- Around line 439-445: Update the pose debug overlay attachment logic in
MocapController to use Manager::getSingletonPtr() and verify both the manager
and getSceneMgr() result before dereferencing or attaching. Apply the same guard
to the corresponding overlay attachment logic near the second affected location,
while preserving detach behavior when prerequisites are unavailable.
- Around line 366-369: Initialize d->cachedDevices by calling refreshDevices()
during construction in the normal available/non-available constructor path,
before startup model readers can access availableDevices(). Preserve the
existing behavior for unavailable devices and ensure availableDevices() returns
the populated initial cache.

In `@src/Mocap/MocapPoseDebugOverlay.cpp`:
- Around line 105-174: Move fkPoseIkJoints and the effectiveParentRole,
localArtic, and quatFromArray helpers from src/Mocap/MocapPoseDebugOverlay.cpp
lines 105-174 into a shared internal header near MocapLiveTypes.h, then update
the overlay’s update() to call that shared implementation. Remove the duplicate
helpers and FK implementation from src/Mocap/MocapBodyDriveDebug.cpp lines
69-145 and call the shared helper there, preserving the overlay’s worldRot
handling for resolved roles with unresolved parents so both debug consumers
produce identical joint positions.
- Around line 190-205: Update MocapPoseDebugOverlay::ensureMaterial to ensure
the first technique contains at least one pass before retrieving and configuring
pass 0. Preserve the existing material and technique creation flow, adding pass
creation when needed before the getPass(0) call.

In `@src/Mocap/VideoFrameSource_test.cpp`:
- Around line 201-210: Extend the MocapFrameFromVideoFrame tests to cover the
mapped Format_Jpeg branch by constructing a QVideoFrame containing JPEG-encoded
image data and passing it through mocapFrameFromVideoFrame. Assert that decoding
produces the expected known pixel value and QImage::Format_RGB888, while
preserving the existing RGB conversion test.

---

Outside diff comments:
In `@src/Mocap/MocapRecorder.cpp`:
- Around line 339-345: Update the validation harness around BodyRetargeter rt,
including its setNeutralReference and evaluateFrame calls, to pass the per-frame
MediaPipe landmark directions used by the live preview when those source
landmarks are available, preserving the live retargeter branch for both
initialization and evaluation. If MocapRecorder lacks those landmarks, revise
the nearby “EXACT math the LIVE preview runs” comment to state that the harness
uses the quaternion-delta path instead of claiming live-path parity.

---

Nitpick comments:
In `@docs/MOCAP.md`:
- Around line 137-146: Update the live-mode or troubleshooting documentation
near the existing PoseIK behavior notes to mention the “Show PoseIK debug
skeleton” panel option, including that it displays cyan MediaPipe landmarks and
a yellow FK stick figure for diagnosing retargeting issues.

In `@src/AnimationMerger_test.cpp`:
- Around line 1337-1357: Extend the test around the neutral and raised
evaluations to capture the right upper-arm direction alongside tPoseArm, then
compute its motion after applying raisedLocals. Add an assertion that the
untouched right arm remains effectively stationary, while preserving the
existing raisedMotion assertion for the left arm.

In `@src/AnimationMerger.cpp`:
- Around line 1491-1506: Add a compile-time assertion immediately after the
kParentCanon declaration requiring its size to match the supported 22 canonical
joints, preventing effectiveParentRole from indexing beyond the table if the
canonical-joint count changes. Also enforce the same bound in the retargeter
constructor when assigning d->Jc, either by asserting equality or clamping the
canonicalJointCount() result to the table capacity.

In `@src/Mocap/MocapBodyDriveDebug.cpp`:
- Around line 147-158: Update roleName to use PoseIK::kCanonicalRoles instead of
the literal 22 when validating role indices, and add a static assertion
confirming the names table contains exactly that number of entries. Preserve the
existing "?" fallback for out-of-range roles.
- Around line 234-253: Build the role-to-bone table once before the kLimbRoles
loop by scanning skel’s bones a single time, converting each bone name with
MotionInbetween::canonicalIndexForBone, and storing the matching bone and child
relationship by role. Update the loop to retrieve entries from this table while
preserving the resolvedMask filtering and existing child selection behavior.

In `@src/Mocap/MocapController.cpp`:
- Around line 145-156: Replace BodyManualBoneSnapshot with BodyDriveBone, using
role = -1 for snapshot entries. Update beginPreviewWithLiveSource and
restoreEntityState to use the unified type and remove the redundant snapshot
struct while preserving existing boneName, bindLocal, and wasManuallyControlled
handling.
- Around line 279-280: Replace the raw bit shifts in kTorsoResolvedMask and
skipHead with the corresponding PoseIK role enumerators for hip, abdomen, chest,
and head, while preserving the existing mask behavior. Use the enumerator-based
shifts so role index changes remain synchronized with these masks.

In `@src/Mocap/MocapLiveTypes.h`:
- Around line 12-18: Update the quats member initializer in BodyLiveFrame to
value-initialize it with {}, matching world and visibility so
default-constructed frames have initialized quaternion data.

In `@src/Mocap/MocapPoseDebugOverlay.cpp`:
- Line 301: Address the unused m_lastScale assignment in the
MocapPoseDebugOverlay update path: either remove the m_lastScale field and its
write, or use it to detect an unchanged scale and skip the rebuild while
preserving rebuilds when the scale changes.
- Around line 232-233: Update the overlay root positioning in
MocapPoseDebugOverlay initialization to derive the x offset from the entity
height used by update(), replacing the fixed 0.8f value while preserving the
zero y and z offsets. Reuse the existing height-related symbol or pass the
height into the setup path so the overlay scales consistently with the stick
figure.

In `@src/Mocap/MocapPoseDebugOverlay.h`:
- Around line 17-33: Add a destructor to MocapPoseDebugOverlay that calls
detach(), ensuring the owned scene node and ManualObject instances are removed
when an attached overlay is destroyed. Keep the existing detach cleanup behavior
and avoid requiring callers to remember an additional lifecycle step.

In `@src/Mocap/MocapPoseFix.h`:
- Around line 20-58: Keep swapMediaPipeLeftRightLandmarks because
PoseIKSolver_test still uses it, but remove the unused legacy helpers
swapCanonicalLeftRight, kPoseToSkeletonYawPi, invertCameraYawDelta,
poseDirectionToSkeleton, and poseRotationToSkeleton from the header. If any are
intentionally retained for future legacy clip support, add a clear comment
documenting that purpose.

In `@src/Mocap/PoseIKSolver_test.cpp`:
- Around line 211-212: Update the comment above the EXPECT_LT assertion in the
nodding test to state that the torso remains stable during a nod, matching the
asserted PoseIK::Hip rotation behavior; leave the assertion unchanged.
- Around line 236-262: Use a separate PoseIK::Solver instance for the corrected
landmark pass in PoseIKSolver.WebcamMirrorSwapFixesRightArmRaise. Initialize the
new solver from the same neutral tPose baseline, then solve the swapped fixed
landmarks with it so fOk is not influenced by fWrong’s per-frame history; keep
the existing assertions and comparison targets unchanged.

In `@src/Mocap/PoseIKSolver.cpp`:
- Around line 235-239: In the pose-solving routine around
canonicalizeMediaPipeWorld, remove the redundant p array and population loop,
then bind p as a reference or alias to canon so existing p[...] accesses
continue working while using the canonical points directly.
- Around line 110-119: Move the limb segment definition near the Lm enum in the
anonymous namespace, rename it to the shared LimbSegment/kLimbSegments form, and
replace raw landmark literals with the appropriate Lm enumerators. Remove the
local duplicate Segment table and update both the surrounding solver logic and
solveFrame to iterate over kLimbSegments, preserving the existing segment
ordering and roles.
🪄 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 Plus

Run ID: fe7df47d-a7db-4c85-bdea-d548f906fc56

📥 Commits

Reviewing files that changed from the base of the PR and between 51e7b9f and 8d707b6.

📒 Files selected for processing (25)
  • docs/MOCAP.md
  • qml/PropertiesPanel.qml
  • src/AnimationMerger.cpp
  • src/AnimationMerger.h
  • src/AnimationMerger_test.cpp
  • src/CMakeLists.txt
  • src/Mocap/FaceCapPose_test.cpp
  • src/Mocap/MocapBodyDriveDebug.cpp
  • src/Mocap/MocapBodyDriveDebug.h
  • src/Mocap/MocapCameraHints.cpp
  • src/Mocap/MocapCameraHints.h
  • src/Mocap/MocapController.cpp
  • src/Mocap/MocapController.h
  • src/Mocap/MocapLiveTypes.h
  • src/Mocap/MocapPoseDebugOverlay.cpp
  • src/Mocap/MocapPoseDebugOverlay.h
  • src/Mocap/MocapPoseFix.h
  • src/Mocap/MocapRecorder.cpp
  • src/Mocap/PoseIKSolver.cpp
  • src/Mocap/PoseIKSolver.h
  • src/Mocap/PoseIKSolver_test.cpp
  • src/Mocap/VideoFrameSource.cpp
  • src/Mocap/VideoFrameSource.h
  • src/Mocap/VideoFrameSource_test.cpp
  • src/test_main.cpp

Comment thread qml/PropertiesPanel.qml
Comment thread src/AnimationMerger.cpp
Comment on lines +13 to +16
#include <array>
#include <cmath>
#include <cstdio>
#include <vector>

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

Include the headers this file uses directly.

The file calls std::max and std::min (lines 39-41, 110-112, 198-201), qEnvironmentVariableIsSet (line 183), and QString::fromStdString (line 243). <algorithm>, <QtGlobal>, and <QString> are not included. The build currently depends on transitive includes from AnimationMerger.h and MotionInbetween.h, which differ between libstdc++, libc++, and MSVC.

🛠️ Proposed change
 `#include` <array>
+#include <algorithm>
 `#include` <cmath>
 `#include` <cstdio>
 `#include` <vector>
+
+#include <QString>
+#include <QtGlobal>

Based on the coding guideline "Ensure code compiles and runs on Windows, Linux, and macOS".

📝 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
#include <array>
#include <cmath>
#include <cstdio>
#include <vector>
`#include` <array>
`#include` <algorithm>
`#include` <cmath>
`#include` <cstdio>
`#include` <vector>
`#include` <QString>
`#include` <QtGlobal>
🤖 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/Mocap/MocapBodyDriveDebug.cpp` around lines 13 - 16, Add direct includes
for the symbols used by MocapBodyDriveDebug.cpp: include <algorithm> for
std::max/std::min, <QtGlobal> for qEnvironmentVariableIsSet, and <QString> for
QString::fromStdString, rather than relying on transitive includes from
AnimationMerger.h or MotionInbetween.h.

Source: Coding guidelines

Comment on lines +69 to +145
void fkPoseIkJoints(
const std::array<std::array<float, 4>, PoseIK::kCanonicalRoles>& quats,
uint32_t resolvedMask,
const std::array<std::array<float, 3>, PoseIK::kLandmarkCount>& canonLmPts,
std::array<Vec3, PoseIK::kCanonicalRoles>& out)
{
out.fill({0.f, 0.f, 0.f});
const Vec3 hip = {
(canonLmPts[23][0] + canonLmPts[24][0]) * 0.5f,
(canonLmPts[23][1] + canonLmPts[24][1]) * 0.5f,
(canonLmPts[23][2] + canonLmPts[24][2]) * 0.5f};
out[static_cast<size_t>(PoseIK::Hip)] = hip;

struct BoneSeg {
int role;
int fromLm;
int toLm;
};
static const BoneSeg segs[] = {
{PoseIK::Abdomen, 23, 11}, {PoseIK::Chest, 11, 12},
{PoseIK::Neck, 12, 0}, {PoseIK::Head, 0, 8},
{PoseIK::RShoulder, 12, 14}, {PoseIK::RElbow, 14, 16},
{PoseIK::RHand, 16, 16},
{PoseIK::LShoulder, 11, 13}, {PoseIK::LElbow, 13, 15},
{PoseIK::LHand, 15, 15},
{PoseIK::RHip, 24, 26}, {PoseIK::RKnee, 26, 28},
{PoseIK::RFoot, 28, 32},
{PoseIK::LHip, 23, 25}, {PoseIK::LKnee, 25, 27},
{PoseIK::LFoot, 27, 31},
};

std::array<Vec3, PoseIK::kCanonicalRoles> restOffset{};
for (const BoneSeg& s : segs) {
Vec3 dir = sub(canonLmPts[static_cast<size_t>(s.toLm)],
canonLmPts[static_cast<size_t>(s.fromLm)]);
const float d = len(dir);
if (d < 1e-5f)
dir = {0.f, 0.12f, 0.f};
else
dir = norm(dir);
restOffset[static_cast<size_t>(s.role)] = {
dir[0] * std::max(d, 0.05f),
dir[1] * std::max(d, 0.05f),
dir[2] * std::max(d, 0.05f)};
}

std::array<Ogre::Quaternion, PoseIK::kCanonicalRoles> worldRot{};
worldRot.fill(Ogre::Quaternion::IDENTITY);

for (int role = 0; role < PoseIK::kCanonicalRoles; ++role) {
if (!(resolvedMask & (1u << static_cast<unsigned>(role))))
continue;
const int parent = MotionInbetween::canonicalParentOf(role);
const Ogre::Quaternion local = localArtic(quats, role, resolvedMask);
if (parent >= 0 && (resolvedMask & (1u << static_cast<unsigned>(parent)))) {
worldRot[static_cast<size_t>(role)] =
worldRot[static_cast<size_t>(parent)] * local;
const Ogre::Vector3 off(
restOffset[static_cast<size_t>(role)][0],
restOffset[static_cast<size_t>(role)][1],
restOffset[static_cast<size_t>(role)][2]);
const Ogre::Vector3 w =
worldRot[static_cast<size_t>(parent)] * off;
out[static_cast<size_t>(role)] = {
out[static_cast<size_t>(parent)][0] + w.x,
out[static_cast<size_t>(parent)][1] + w.y,
out[static_cast<size_t>(parent)][2] + w.z};
} else if (role == PoseIK::Hip) {
worldRot[0] = quatFromArray(quats[0]);
} else {
out[static_cast<size_t>(role)] = {
hip[0] + restOffset[static_cast<size_t>(role)][0],
hip[1] + restOffset[static_cast<size_t>(role)][1],
hip[2] + restOffset[static_cast<size_t>(role)][2]};
}
}
}

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

fkPoseIkJoints is duplicated and the two copies differ.

This implementation and the one in src/Mocap/MocapPoseDebugOverlay.cpp (lines 105-174) are the same algorithm. The final else branch here does not set worldRot[role], while the overlay copy sets worldRot[role] = quatFromArray(quats[role]). Any child of a root-less resolved role is therefore placed with an identity parent rotation in the log but not in the overlay, so the two diagnostics disagree. effectiveParentRole, localArtic, and quatFromArray are duplicated as well.

See the consolidated comment for the shared fix.

🧰 Tools
🪛 Cppcheck (2.21.0)

[error] 80-80: Address of local auto-variable assigned to a function parameter.

(autoVariables)

🤖 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/Mocap/MocapBodyDriveDebug.cpp` around lines 69 - 145, Consolidate
fkPoseIkJoints and its duplicated helpers effectiveParentRole, localArtic, and
quatFromArray into one shared implementation used by both diagnostics. Preserve
the overlay behavior in the root-less resolved-role branch by assigning worldRot
for the current role from its quaternion before placing descendants, so both
callers produce identical results.

Comment on lines 366 to 369
QVariantList MocapController::availableDevices() const
{
QVariantList out;
for (const auto& dev : CameraFrameSource::availableDevices()) {
QVariantMap m;
m.insert(QStringLiteral("id"), dev.id);
m.insert(QStringLiteral("description"), dev.description);
out.append(m);
}
return out;
return d->cachedDevices;
}

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
# Find every reader of availableDevices and every caller of refreshDevices.
rg -nP -C4 '\b(availableDevices|refreshDevices)\s*\(' --glob '!**/build/**'
rg -nP -C3 'availableDevices|refreshDevices' --glob '*.qml'

Repository: fernandotonon/QtMeshEditor

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files matching MocapController =="
git ls-files | rg 'MocapController\.(h|cpp)$|mocap|Mocap|\.qml$' | sed -n '1,200p'

echo "== all textual occurrences =="
rg -n -C3 '\b(availableDevices|refreshDevices)\b' . --glob '!**/build/**' --glob '!**/.git/**' || true

echo "== MocapController.cpp relevant sections =="
if [ -f src/Mocap/MocapController.cpp ]; then
  wc -l src/Mocap/MocapController.cpp
  sed -n '300,490p' src/Mocap/MocapController.cpp | cat -n
fi

echo "== MocapController.h relevant sections =="
if [ -f src/Mocap/MocapController.h ]; then
  wc -l src/Mocap/MocapController.h
  sed -n '1,220p' src/Mocap/MocapController.h | cat -n
fi

Repository: fernandotonon/QtMeshEditor

Length of output: 29057


Populate availableDevices before startup reads.

MocapController::availableDevices() returns the empty d->cachedDevices cache, and the constructor does not call refreshDevices(). Build the first device list in the normal available/non-available constructor path, otherwise startup model readers see no devices until refreshDevices() is called.

Also applies to: 459-472

🤖 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/Mocap/MocapController.cpp` around lines 366 - 369, Initialize
d->cachedDevices by calling refreshDevices() during construction in the normal
available/non-available constructor path, before startup model readers can
access availableDevices(). Preserve the existing behavior for unavailable
devices and ensure availableDevices() returns the populated initial cache.

Comment on lines +439 to +445
if (on && d->state != Idle) {
if (Ogre::Entity* entity = d->entity())
d->poseDebugOverlay.attach(Manager::getSingleton()->getSceneMgr(),
entity->getParentSceneNode());
} else {
d->poseDebugOverlay.detach();
}

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

Guard the Manager singleton before dereferencing it.

Manager::getSingleton() is dereferenced without a null check, and getSceneMgr() may also return null. Impl::entity() (lines 327-335) already uses Manager::getSingletonPtr() with a null check. Use the same pattern here.

🛡️ Proposed change
-    if (on && d->state != Idle) {
-        if (Ogre::Entity* entity = d->entity())
-            d->poseDebugOverlay.attach(Manager::getSingleton()->getSceneMgr(),
-                                       entity->getParentSceneNode());
-    } else {
+    auto* mgr = Manager::getSingletonPtr();
+    if (on && d->state != Idle && mgr && mgr->getSceneMgr()) {
+        if (Ogre::Entity* entity = d->entity())
+            d->poseDebugOverlay.attach(mgr->getSceneMgr(),
+                                       entity->getParentSceneNode());
+    } else {
         d->poseDebugOverlay.detach();
     }

Based on the coding guideline "Preserve the singleton conventions for Manager, SelectionSet, and TransformOperator: access them through getSingleton() or getSingletonPtr()".

Also applies to: 890-892

🤖 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/Mocap/MocapController.cpp` around lines 439 - 445, Update the pose debug
overlay attachment logic in MocapController to use Manager::getSingletonPtr()
and verify both the manager and getSceneMgr() result before dereferencing or
attaching. Apply the same guard to the corresponding overlay attachment logic
near the second affected location, while preserving detach behavior when
prerequisites are unavailable.

Source: Coding guidelines

Comment thread src/Mocap/MocapController.cpp Outdated
Comment on lines +105 to +174
void fkPoseIkJoints(
const std::array<std::array<float, 4>, PoseIK::kCanonicalRoles>& quats,
uint32_t resolvedMask,
const std::array<std::array<float, 3>, PoseIK::kLandmarkCount>& canonLmPts,
std::array<Vec3, PoseIK::kCanonicalRoles>& out)
{
out.fill({0.f, 0.f, 0.f});
const Vec3 hip = mid(canonLmPts[23], canonLmPts[24]);
out[static_cast<size_t>(PoseIK::Hip)] = hip;

struct BoneSeg {
int role;
int fromLm;
int toLm;
};
static const BoneSeg segs[] = {
{PoseIK::Abdomen, 23, 11},
{PoseIK::Chest, 11, 12},
{PoseIK::Neck, 12, 0},
{PoseIK::Head, 0, 8},
{PoseIK::RShoulder, 12, 14},
{PoseIK::RElbow, 14, 16},
{PoseIK::RHand, 16, 16},
{PoseIK::LShoulder, 11, 13},
{PoseIK::LElbow, 13, 15},
{PoseIK::LHand, 15, 15},
{PoseIK::RHip, 24, 26},
{PoseIK::RKnee, 26, 28},
{PoseIK::RFoot, 28, 32},
{PoseIK::LHip, 23, 25},
{PoseIK::LKnee, 25, 27},
{PoseIK::LFoot, 27, 31},
};

std::array<Vec3, PoseIK::kCanonicalRoles> restOffset{};
for (const BoneSeg& s : segs) {
Vec3 dir = sub(canonLmPts[static_cast<size_t>(s.toLm)],
canonLmPts[static_cast<size_t>(s.fromLm)]);
const float d = len(dir);
if (d < 1e-5f)
dir = {0.f, 0.12f, 0.f};
else
dir = mul(dir, 1.f / d);
restOffset[static_cast<size_t>(s.role)] = mul(dir, std::max(d, 0.05f));
}

std::array<Ogre::Quaternion, PoseIK::kCanonicalRoles> worldRot{};
worldRot.fill(Ogre::Quaternion::IDENTITY);

for (int role = 0; role < PoseIK::kCanonicalRoles; ++role) {
if (!(resolvedMask & (1u << static_cast<unsigned>(role))))
continue;
const int parent = MotionInbetween::canonicalParentOf(role);
const Ogre::Quaternion local = localArtic(quats, role, resolvedMask);
if (parent >= 0 && (resolvedMask & (1u << static_cast<unsigned>(parent)))) {
worldRot[static_cast<size_t>(role)] =
worldRot[static_cast<size_t>(parent)] * local;
out[static_cast<size_t>(role)] =
add(out[static_cast<size_t>(parent)],
mulVec3(worldRot[static_cast<size_t>(parent)],
restOffset[static_cast<size_t>(role)]));
} else if (role == PoseIK::Hip) {
worldRot[0] = quatFromArray(quats[0]);
} else {
worldRot[static_cast<size_t>(role)] = quatFromArray(quats[role]);
out[static_cast<size_t>(role)] =
add(hip, restOffset[static_cast<size_t>(role)]);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Share one PoseIK FK helper between the two debug consumers. Both files copy fkPoseIkJoints, effectiveParentRole, localArtic, and quatFromArray. The copies already differ: the overlay sets worldRot[role] for a resolved role whose parent is unresolved, and the log does not. The overlay and the log therefore report different joint positions for the same frame, which defeats the purpose of comparing them.

  • src/Mocap/MocapPoseDebugOverlay.cpp#L105-L174: move this implementation and the three helpers into a shared internal header (for example src/Mocap/MocapPoseFK.h) next to MocapLiveTypes.h, and call it from update().
  • src/Mocap/MocapBodyDriveDebug.cpp#L69-L145: delete the local copies and call the shared helper, so the log uses the same worldRot handling as the overlay.
🧰 Tools
🪛 Cppcheck (2.21.0)

[error] 113-113: Address of local auto-variable assigned to a function parameter.

(autoVariables)

📍 Affects 2 files
  • src/Mocap/MocapPoseDebugOverlay.cpp#L105-L174 (this comment)
  • src/Mocap/MocapBodyDriveDebug.cpp#L69-L145
🤖 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/Mocap/MocapPoseDebugOverlay.cpp` around lines 105 - 174, Move
fkPoseIkJoints and the effectiveParentRole, localArtic, and quatFromArray
helpers from src/Mocap/MocapPoseDebugOverlay.cpp lines 105-174 into a shared
internal header near MocapLiveTypes.h, then update the overlay’s update() to
call that shared implementation. Remove the duplicate helpers and FK
implementation from src/Mocap/MocapBodyDriveDebug.cpp lines 69-145 and call the
shared helper there, preserving the overlay’s worldRot handling for resolved
roles with unresolved parents so both debug consumers produce identical joint
positions.

Comment on lines +190 to +205
void MocapPoseDebugOverlay::ensureMaterial()
{
if (Ogre::MaterialManager::getSingleton().resourceExists(kMatName))
return;
auto mat = Ogre::MaterialManager::getSingleton().create(
kMatName, Ogre::ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
if (!mat->getNumTechniques())
mat->createTechnique();
Ogre::Pass* pass = mat->getTechnique(0)->getPass(0);
pass->setLightingEnabled(false);
pass->setVertexColourTracking(Ogre::TVC_DIFFUSE);
pass->setCullingMode(Ogre::CULL_NONE);
pass->setDepthCheckEnabled(true);
pass->setDepthWriteEnabled(false);
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
}

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 | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Ogre 14 Material createTechnique does the new Technique contain a default Pass or must createPass be called

💡 Result:

In Ogre 14, when you call createTechnique on a Material object, the new Technique is created empty, meaning it does not automatically contain any Pass objects [1][2][3]. Consequently, you must explicitly call createPass on the newly created Technique to add at least one Pass, as a valid Technique requires at least one Pass to perform rendering [4]. While a brand-new Material object might be initialized with a default Technique and Pass depending on how it was created (e.g., via script parsing or default settings), a Technique created programmatically via Material::createTechnique is empty by design [3][5]. You are responsible for populating it with the necessary Passes to define the rendering behavior [2][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the relevant source location and any nearby use of ensureMaterial.
if [ -f src/Mocap/MocapPoseDebugOverlay.cpp ]; then
  echo "== MocapPoseDebugOverlay.cpp outline =="
  ast-grep outline src/Mocap/MocapPoseDebugOverlay.cpp --view compact || true
  echo
  echo "== Relevant source =="
  sed -n '150,230p' src/Mocap/MocapPoseDebugOverlay.cpp | nl -ba -v150
  echo
  echo "== ensureMaterial usages =="
  rg -n "ensureMaterial\(|MocapPoseDebugOverlay" src/Mocap/MocapPoseDebugOverlay.cpp src || true
else
  echo "src/Mocap/MocapPoseDebugOverlay.cpp not found"
fi

echo
echo "== Version/config mentions of Ogre =="
rg -n "Ogre 14|OGRE|Ogre" CMakeLists.txt . cmake include src/Mocap || true

Repository: fernandotonon/QtMeshEditor

Length of output: 419


🏁 Script executed:

#!/bin/bash
set -u

echo "== Relevant source =="
if [ -f src/Mocap/MocapPoseDebugOverlay.cpp ]; then
  sed -n '150,230p' src/Mocap/MocapPoseDebugOverlay.cpp
fi

echo
echo "== ensureMaterial usages =="
if [ -f src/Mocap/MocapPoseDebugOverlay.cpp ]; then
  rg -n "ensureMaterial\(|MocapPoseDebugOverlay" src/Mocap/MocapPoseDebugOverlay.cpp src || true
fi

echo
echo "== Version/config mentions of Ogre =="
rg -n "Ogre 14|OGRE|Ogre" CMakeLists.txt cmake include src/Mocap 2>/dev/null || true

Repository: fernandotonon/QtMeshEditor

Length of output: 28084


Create the pass before configuring it.

MaterialManager::create produces an empty material; createTechnique() creates an empty technique in Ogre. Calling getPass(0) on a fresh technique indexes an empty pass list, so the first rebuild can crash. Ensure the material has at least one pass before configuring it.

🤖 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/Mocap/MocapPoseDebugOverlay.cpp` around lines 190 - 205, Update
MocapPoseDebugOverlay::ensureMaterial to ensure the first technique contains at
least one pass before retrieving and configuring pass 0. Preserve the existing
material and technique creation flow, adding pass creation when needed before
the getPass(0) call.

Comment on lines +201 to +210
TEST(MocapFrameFromVideoFrame, ConvertsQVideoFrameFromQImage)
{
QImage rgb(8, 8, QImage::Format_RGB888);
rgb.fill(Qt::blue);
const QVideoFrame vf(rgb);
ASSERT_TRUE(vf.isValid());
const QImage out = mocapFrameFromVideoFrame(vf);
EXPECT_FALSE(out.isNull());
EXPECT_EQ(out.format(), QImage::Format_RGB888);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files 'src/Mocap/VideoFrameSource.cpp' 'src/Mocap/VideoFrameSource_test.cpp' | sed 's#^\./##'

echo
echo "Source outline:"
ast-grep outline src/Mocap/VideoFrameSource.cpp --view compact || true

echo
echo "Test relevant section:"
sed -n '160,230p' src/Mocap/VideoFrameSource_test.cpp | cat -n

echo
echo "Source relevant function:"
rg -n "mocapFrameFromVideoFrame|Format_Jpeg|map|QVideoFrameFormat|QImage" src/Mocap/VideoFrameSource.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 3446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Changed files/stat:"
git diff --stat || true

echo
echo "Source VideoFrame function with line numbers:"
sed -n '1,120p' src/Mocap/VideoFrameSource.cpp | cat -n

echo
echo "Test imports/macros:"
sed -n '1,80p' src/Mocap/VideoFrameSource_test.cpp | cat -n

Repository: fernandotonon/QtMeshEditor

Length of output: 7148


Cover the JPEG fallback path.

QVideoFrame vf(rgb) exercises RGB frame conversion but not the mapped Format_Jpeg branch, where QVideoFrame bytes are decoded with QImage::fromData(..., "JPEG"). Add a mapped JPEG QVideoFrame test, or extract the JPEG decode into a testable helper. Assert a known output pixel and QImage::Format_RGB888.

🤖 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/Mocap/VideoFrameSource_test.cpp` around lines 201 - 210, Extend the
MocapFrameFromVideoFrame tests to cover the mapped Format_Jpeg branch by
constructing a QVideoFrame containing JPEG-encoded image data and passing it
through mocapFrameFromVideoFrame. Assert that decoding produces the expected
known pixel value and QImage::Format_RGB888, while preserving the existing RGB
conversion test.

Source: Coding guidelines

fernandotonon and others added 4 commits August 3, 2026 23:47
…ctors

Centralize ONNX session options (CPU/CoreML/CUDA/DirectML) behind a QML
singleton with AI Settings UI. Optional QTMESH_ONNX_GPU fetch on Linux x64,
provider .so copy rules, CI stays on CPU package, and mocap predictors use
the shared path so the app links cleanly.

Co-authored-by: Cursor <cursoragent@cursor.com>
FaceCap head rotation needs yaw correction on selfie/webcam feeds while
body retargeting uses landmark directions without L/R swapping.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use vendor-neutral wording in live mocap, BodyRetargeter, docs, and debug UI
while keeping factual mixamorig examples where they describe bone naming.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wire OnnxRuntimeSettings and mocap debug sources into qtmesh_test_common,
propagate parent world transforms for unmapped bones, bake body recordings
via landmark retarget, and clean up skinning listener/head manual-control edge cases.

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

@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: 14

🧹 Nitpick comments (4)
src/Mocap/MocapRecorder.cpp (2)

425-426: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the torso mask constant instead of duplicating it.

kTorsoResolvedMask here repeats Impl::kTorsoResolvedMask in src/Mocap/MocapController.cpp (lines 287-288). Both encode the same hip/abdomen/chest role contract. Define it once next to the PoseIK role enum and include it in both files, so a role reorder cannot desynchronize the calibration gate from the preview gate.

🤖 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/Mocap/MocapRecorder.cpp` around lines 425 - 426, Move kTorsoResolvedMask
to the shared declaration area beside the PoseIK role enum, then include that
shared definition from both MocapRecorder.cpp and MocapController.cpp. Remove
the local constants, including Impl::kTorsoResolvedMask, while preserving the
existing hip/abdomen/chest mask value and all current calibration and preview
checks.

428-434: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Fixed 22-element role arrays are not tied to PoseIK::kCanonicalRoles. Both sites declare std::array<std::array<float, 4>, 22> and then fill it with a loop bounded by PoseIK::kCanonicalRoles. If the role count ever grows past 22, both loops write out of bounds.

  • src/Mocap/MocapRecorder.cpp#L428-L434: size the frameQuats destination from PoseIK::kCanonicalRoles, or add a static_assert(PoseIK::kCanonicalRoles <= 22, ...) before the loop.
  • src/Mocap/MocapController.cpp#L1050-L1052: apply the same size source or assertion to canonQuats.
🤖 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/Mocap/MocapRecorder.cpp` around lines 428 - 434, The hardcoded array size
of 22 is not tied to the loop bound PoseIK::kCanonicalRoles, creating an
out-of-bounds risk if the role count grows. At src/Mocap/MocapRecorder.cpp lines
428-434 in the frameQuats function, replace the fixed array size of 22 with
PoseIK::kCanonicalRoles or add a static_assert(PoseIK::kCanonicalRoles <= 22,
...) before the loop. At src/Mocap/MocapController.cpp lines 1050-1052 for the
canonQuats array, apply the same fix to align the hardcoded size with
PoseIK::kCanonicalRoles.
src/TextureUpscaler.cpp (1)

152-155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the duplicated intra-op thread setting.

configureSessionOptions(so) already applies SetIntraOpNumThreads(hc > 1 ? hc - 1 : 1) because SessionConfig::reserveUiThreadCore defaults to true. Lines 153-155 repeat the same computation. Keeping both makes the thread policy live in two places.

♻️ Proposed change
         OnnxRuntimeSettings::configureSessionOptions(so);
-        const unsigned hw = std::thread::hardware_concurrency();
-        const int threads = (hw > 1) ? static_cast<int>(hw - 1) : 1;
-        so.SetIntraOpNumThreads(threads);
🤖 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/TextureUpscaler.cpp` around lines 152 - 155, Remove the redundant
hardware_concurrency calculation and SetIntraOpNumThreads call following
OnnxRuntimeSettings::configureSessionOptions(so) in TextureUpscaler
initialization, leaving thread-count policy centralized in
configureSessionOptions.
src/ImageTo3D/TripoSGPredictor.cpp (1)

277-284: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Skip building the GPU session options when gpuAvailable cannot become true.

gpuAvailable is assigned only inside #ifdef __APPLE__, so on Windows and Linux it stays false and open() never selects gpu. On those platforms configureSessionOptions(gpu, gpuCfg) still appends an execution provider (DirectML or CUDA) that is then discarded. Build the gpu options only on Apple, or extend gpuAvailable to the other platforms if DirectML/CUDA use is intended for the point decoder.

♻️ Proposed change
         Ort::SessionOptions gpu;
-        OnnxRuntimeSettings::SessionConfig gpuCfg;
-        gpuCfg.coreMlStyle = OnnxRuntimeSettings::CoreMlStyle::MlProgram;
-        OnnxRuntimeSettings::configureSessionOptions(gpu, gpuCfg);
         bool gpuAvailable = false;
 `#ifdef` __APPLE__
+        OnnxRuntimeSettings::SessionConfig gpuCfg;
+        gpuCfg.coreMlStyle = OnnxRuntimeSettings::CoreMlStyle::MlProgram;
+        OnnxRuntimeSettings::configureSessionOptions(gpu, gpuCfg);
         gpuAvailable = OnnxRuntimeSettings::instance()->preferGpu();
+#else
+        OnnxRuntimeSettings::SessionConfig gpuCfg;
+        gpuCfg.appendGpu = false;
+        OnnxRuntimeSettings::configureSessionOptions(gpu, gpuCfg);
 `#endif`
🤖 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/ImageTo3D/TripoSGPredictor.cpp` around lines 277 - 284, Update the GPU
session-options setup around gpuAvailable so configureSessionOptions is invoked
only when the platform can actually set gpuAvailable true, preserving Apple’s
Core ML path and avoiding unused DirectML/CUDA provider configuration on Windows
and Linux; alternatively, explicitly enable and select those providers for the
point decoder on those platforms if that support is intended.
🤖 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 `@cmake/OnnxRuntime.cmake`:
- Around line 11-16: When selecting a CPU-only ONNX runtime archive for
unsupported GPU platforms like Linux ARM, explicitly set QTMESH_ONNX_GPU to OFF
before the archive selection is cached or exported. This ensures the flag does
not remain from a previous configuration state and prevents incorrect GPU
definitions like QTMESH_ONNX_GPU_BUILD from being set for CPU-only builds.

In `@qml/AISettingsDialog.qml`:
- Line 714: Update the note text in AISettingsDialog so the ONNX GPU preference
is described as applying only to newly created ONNX sessions, not immediately to
existing sessions; preserve the existing LLM settings wording.

In `@qml/CollapsibleSection.qml`:
- Around line 73-83: Guard both deferred callbacks in CollapsibleSection’s
onExpandedChanged and Component.onCompleted handlers by rechecking root.expanded
inside the Qt.callLater closure before setting contentLoader.loadActive = true;
keep immediate deactivation unchanged.

In `@scripts/install-onnx-gpu-deps.sh`:
- Around line 19-31: Update the dependency presence check and the post-install
validation in the script around the existing cuDNN checks to require both
$DEST/nvidia/cudnn/lib/libcudnn.so.9 and
$DEST/nvidia/cublas/lib/libcublas.so.12. Only skip pip installation when both
files exist, and fail after installation if either library is missing.

In `@src/CLIPipeline.cpp`:
- Around line 1559-1566: Move redirectStdout() in the CLI initialization flow
before MocapCameraHints::ensureMultimediaBackendSafe(), QApplication
construction, and OnnxRuntimeSettings::prepareRuntimeEnvironment(). Preserve the
existing saved-STDOUT and custom message-handler setup while ensuring these
initializers cannot write directly to stdout before JSON or informational output
is routed safely.

In `@src/Mocap/MocapController.cpp`:
- Around line 1208-1210: Replace the hardcoded 30 fps in the
RecordBodyClipCommand construction within MocapController with a rate derived
from the recorded take, using the sample timestamps and frame count (or the
existing d->liveFps measurement). Pass the computed fps to preserve the clip’s
actual timing across camera rates.
- Around line 1030-1032: The head-yaw correction via
MocapPoseFix::invertCameraYawDelta() is applied unconditionally in the
beginPreviewWithLiveSource() method, but this method is shared between
CameraFrameSource (which is mirrored) and FileFrameSource (which is not
mirrored). Add a conditional check to apply the invertCameraYawDelta correction
only when the live source is the mirrored webcam path, and skip it for the file
source path.

In `@src/Mocap/MocapRecorder.cpp`:
- Around line 499-523: The keyframe timing in the animation track creation uses
a simple frame-index calculation (dt * f) that doesn't account for gaps from
invalid frames, causing motion to play faster during lost-tracking spans. Add a
tracked sample time field to BodyLiveFrame during capture, populate it with the
actual time when each frame is recorded, and update the createNodeKeyFrame call
to use frame's tracked time instead of the dt * f calculation.
- Around line 458-497: In bakeRetargeterClip, defer skel->removeAnimation(clip)
until after the neutral calibration logic has succeeded, including the
no-confident-frames check. Preserve the existing clip when calibration returns
the "no confident body frames to calibrate" error, and remove it only
immediately before writing the successfully calibrated replacement.

In `@src/OnnxRuntimeSettings_test.cpp`:
- Around line 8-25: Update the PreferGpuPersists test to clear
QTMESH_ONNX_PREFER_GPU for the test’s duration before reading preferGpu(), using
the existing Qt environment-handling facilities and restoring the prior
environment value afterward so the test remains isolated.

In `@src/OnnxRuntimeSettings.cpp`:
- Around line 288-291: Initialize the OnnxRuntimeSettings singleton on the
main/UI thread before any worker-thread inference can begin, rather than first
constructing it through tryAppendGpuExecutionProvider or
configureSessionOptions. Add the early initialization at application startup and
establish the required settingsChanged() UI connections before background work
starts, preserving worker-thread calls through the existing
OnnxRuntimeSettings::instance() path.
- Around line 62-76: Replace the runtime LD_LIBRARY_PATH approach in
prependLdLibraryPath with a loader-visible dependency strategy: configure
build-time RPATH/RUNPATH for the CUDA dependency directory, or preload the
required CUDA libraries by absolute path with QLibrary/dlopen(RTLD_GLOBAL)
before Ort::Session creation. Ensure cudaProviderLibraryLoads and subsequent
libonnxruntime/provider loading can resolve cudnn/cublas dependencies without
relying on qputenv.
- Around line 103-112: Replace the non-atomic static bool done guard in
prepareRuntimeEnvironment() with a thread-safe call-once mechanism. Add `#include`
<mutex> at the top of the file, then declare a static std::once_flag inside the
function and wrap the prependLdLibraryPath(cudnnSearchPaths()) call with
std::call_once to ensure it executes exactly once regardless of concurrent
access from multiple threads.
- Around line 305-312: Replace the generic AppendExecutionProvider("DML", {})
call in the Windows non-MinGW32 conditional block with the DirectML-specific
low-level API SessionOptionsAppendExecutionProvider_DML(...) and configure the
required DirectML session options according to the V1.20.1 build configuration
in cmake/OnnxRuntime.cmake. Keep the existing try-catch around this call since
it can throw and should fall back to CPU when the DirectML provider is not
registered.

---

Nitpick comments:
In `@src/ImageTo3D/TripoSGPredictor.cpp`:
- Around line 277-284: Update the GPU session-options setup around gpuAvailable
so configureSessionOptions is invoked only when the platform can actually set
gpuAvailable true, preserving Apple’s Core ML path and avoiding unused
DirectML/CUDA provider configuration on Windows and Linux; alternatively,
explicitly enable and select those providers for the point decoder on those
platforms if that support is intended.

In `@src/Mocap/MocapRecorder.cpp`:
- Around line 425-426: Move kTorsoResolvedMask to the shared declaration area
beside the PoseIK role enum, then include that shared definition from both
MocapRecorder.cpp and MocapController.cpp. Remove the local constants, including
Impl::kTorsoResolvedMask, while preserving the existing hip/abdomen/chest mask
value and all current calibration and preview checks.
- Around line 428-434: The hardcoded array size of 22 is not tied to the loop
bound PoseIK::kCanonicalRoles, creating an out-of-bounds risk if the role count
grows. At src/Mocap/MocapRecorder.cpp lines 428-434 in the frameQuats function,
replace the fixed array size of 22 with PoseIK::kCanonicalRoles or add a
static_assert(PoseIK::kCanonicalRoles <= 22, ...) before the loop. At
src/Mocap/MocapController.cpp lines 1050-1052 for the canonQuats array, apply
the same fix to align the hardcoded size with PoseIK::kCanonicalRoles.

In `@src/TextureUpscaler.cpp`:
- Around line 152-155: Remove the redundant hardware_concurrency calculation and
SetIntraOpNumThreads call following
OnnxRuntimeSettings::configureSessionOptions(so) in TextureUpscaler
initialization, leaving thread-count policy centralized in
configureSessionOptions.
🪄 Autofix

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 Plus

Run ID: 92102ff2-799b-4f51-bf5d-332c100c73ba

📥 Commits

Reviewing files that changed from the base of the PR and between 8d707b6 and a21a840.

📒 Files selected for processing (46)
  • .github/workflows/deploy.yml
  • CMakeLists.txt
  • cmake/OnnxRuntime.cmake
  • docs/MOCAP.md
  • qml/AISettingsDialog.qml
  • qml/CollapsibleSection.qml
  • qml/PropertiesPanel.qml
  • qml/ThemedComboBox.qml
  • scripts/install-onnx-gpu-deps.sh
  • src/AnimationMerger.cpp
  • src/AnimationMerger_test.cpp
  • src/CLIPipeline.cpp
  • src/CMakeLists.txt
  • src/FaceRig/FaceLandmarkDetector.cpp
  • src/ImageTo3D/BackgroundRemover.cpp
  • src/ImageTo3D/MeshGenPredictor.cpp
  • src/ImageTo3D/TripoSGPredictor.cpp
  • src/LLMSettingsWidget.cpp
  • src/LLMSettingsWidget.h
  • src/LLMSettingsWidget_test.cpp
  • src/MeshSegmenter.cpp
  • src/Mocap/FaceCapPredictor.cpp
  • src/Mocap/MocapBodyDriveDebug.cpp
  • src/Mocap/MocapController.cpp
  • src/Mocap/MocapPoseDebugOverlay.cpp
  • src/Mocap/MocapPoseFix.h
  • src/Mocap/MocapRecorder.cpp
  • src/Mocap/MocapRecorder.h
  • src/Mocap/PoseCapPredictor.cpp
  • src/Mocap/PoseIKSolver.cpp
  • src/MotionGenerator.cpp
  • src/MotionInbetween.cpp
  • src/OnnxRuntimeSettings.cpp
  • src/OnnxRuntimeSettings.h
  • src/OnnxRuntimeSettings_test.cpp
  • src/PbrMapSynth.cpp
  • src/SkinTokensPredictor.cpp
  • src/TextureUpscaler.cpp
  • src/UniRigPredictor.cpp
  • src/UniRigPredictor.h
  • src/UniRigPredictor_test.cpp
  • src/commands/RecordMocapClipCommand.cpp
  • src/commands/RecordMocapClipCommand.h
  • src/main.cpp
  • src/mainwindow.cpp
  • tests/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/Mocap/MocapPoseDebugOverlay.cpp
  • src/AnimationMerger_test.cpp
  • src/AnimationMerger.cpp
  • src/Mocap/MocapBodyDriveDebug.cpp
  • src/Mocap/PoseIKSolver.cpp
  • docs/MOCAP.md
  • src/Mocap/MocapPoseFix.h
  • qml/PropertiesPanel.qml

Comment thread cmake/OnnxRuntime.cmake
Comment on lines +11 to +16
#
# Linux x64 GPU: pass -DQTMESH_ONNX_GPU=ON (auto-defaults ON when nvidia-smi is
# found outside CI) to fetch onnxruntime-linux-x64-gpu-*.tgz. The CUDA provider
# .so must ship next to the binary AND cuDNN 9 + CUDA 12 must be on the system.
# Windows MinGW is intentionally NOT wired here — the official Windows archive is
# MSVC-built and won't link under MinGW; that path degrades gracefully (the
# feature reports "rebuild with -DENABLE_ONNX"). See the #404 follow-up.
# MSVC-built and won't link under MinGW.

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:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 4 \
  'QTMESH_ONNX_GPU|_qtmesh_onnx_gpu_default|CMAKE_SYSTEM_PROCESSOR|_ort_archive|QTMESH_ONNX_GPU_BUILD' \
  cmake/OnnxRuntime.cmake CMakeLists.txt

Repository: fernandotonon/QtMeshEditor

Length of output: 6882


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf =='cmake/OnnxRuntime.cmake relevant sections=='\n
sed -n '20,70p' cmake/OnnxRuntime.cmake
printf '\n==cmake/OnnxRuntime.cmake cache/status sections=='\n
sed -n '95,125p' cmake/OnnxRuntime.cmake
printf '\n==CMakeLists.txt ONNX consume section=='\n
sed -n '298,314p' CMakeLists.txt

printf '\n==Behavioral probe for CMake-like conditional/option/processor branches=='\n
python3 - <<'PY'
import os
cases = [
    ("Linux-aarch64-unspecified", "Linux", "aarch64", False),
    ("Linux-arm/unspecified", "Linux", "arm", True),
    ("Linux-arm64-nvidia", "Linux", "arm64", True),
    ("macOS-unspecified", "Darwin", "arm64", True),
    ("macOS-unspecified-off", "Darwin", "x86_64", False),
    ("Windows-unspecified", "Windows", "AMD64", True),
    ("Windows-GPU", "Windows", "AMD64", True),
]
for name, system, processor, nvidia_smiship in cases:
    qtmesh = True # user/default-on
    if nvidia_smiship:
        qtmesh = True
    if processor == "":
        qtmesh = False
    if nvidia_smiship and system != "Linux":
        qtmesh = False
    if qtmesh:
        selected = "GPU"
        used = "CUDA"
    else:
        selected = "CPU"
        used = "CPU"
    status = "GPU" if qtmesh else "CPU"
    build_def = "QTMESH_ONNX_GPU_BUILD" if qtmesh else ""
    print(f"{name}: QTMESH_ONNX_GPU={qtmesh!s:>5}, selected={selected:>3}, status_message={status:>3}, add_definitions={build_def}")
PY

Repository: fernandotonon/QtMeshEditor

Length of output: 5539


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '==cmake/OnnxRuntime.cmake relevant sections=='\n
sed -n '20,70p' cmake/OnnxRuntime.cmake
printf '\n==cmake/OnnxRuntime.cmake cache/status sections=='\n
sed -n '95,125p' cmake/OnnxRuntime.cmake
printf '\n==CMakeLists.txt ONNX consume section=='\n
sed -n '298,314p' CMakeLists.txt

printf '\n==Behavioral probe for supported-/archive selection branch combinations=='\n
python3 - <<'PY'
cases = [
    {"name": "Linux aarch64 nvidia-smi", "system": "Linux", "os_family": "UnixApple", "processor": "aarch64", "def_option_if_nvidia": True},
    {"name": "Linux ARM nvidia-smi", "system": "Linux", "os_family": "UnixApple", "processor": "arm", "def_option_if_nvidia": True},
    {"name": "macOS option forced ON", "system": "Darwin", "os_family": "UnixApple", "processor": "arm64", "def_option_if_nvidia": False},
    {"name": "Windows option forced ON", "system": "Windows", "os_family": "Win", "processor": "AMD64", "def_option_if_nvidia": False},
]
for case in cases:
    qtmesh_onnx_gpu = case["def_option_if_nvidia"]
    if case["system"] == "Linux" and not case["os_family"] == "UnixApple" and case["os_family"] == "UnixApple" and case["processor"] in ("aarch64", "arm", "arm64"):
        archive = "onnxruntime-linux-aarch64-..tgz"
    elif case["system"] == "Linux" and qtmesh_onnx_gpu:
        archive = "onnxruntime-linux-x64-gpu-..tgz"
    elif case["system"] == "Linux":
        archive = "onnxruntime-linux-x64-..tgz"
    elif case["system"] == "Darwin":
        archive = "onnxruntime-osx-universal2-..tgz"
    elif case["system"] == "Windows" and qtmesh_onnx_gpu:
        archive = "Windows warns OFF, CPU archive"
    else:
        archive = "CPU archive"
    status = "GPU" if qtmesh_onnx_gpu else "CPU"
    print(f"{case['name']}: QTMESH_ONNX_GPU={qtmesh_onnx_gpu!s:>5}, archive={archive!r}, status_line={status}")
PY

Repository: fernandotonon/QtMeshEditor

Length of output: 5198


Clear the GPU flag when the selected archive is CPU-only.

Linux ARM can pick onnxruntime-linux-aarch64-*.tgz while QTMESH_ONNX_GPU=ON remains cached, reports GPU, and defines QTMESH_ONNX_GPU_BUILD. Force QTMESH_ONNX_GPU OFF before exporting/caching the selected CPU-only archive for unsupported platforms.

🤖 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 `@cmake/OnnxRuntime.cmake` around lines 11 - 16, When selecting a CPU-only ONNX
runtime archive for unsupported GPU platforms like Linux ARM, explicitly set
QTMESH_ONNX_GPU to OFF before the archive selection is cached or exported. This
ensures the flag does not remain from a previous configuration state and
prevents incorrect GPU definitions like QTMESH_ONNX_GPU_BUILD from being set for
CPU-only builds.

Comment thread qml/AISettingsDialog.qml
Text {
Layout.fillWidth: true
text: "Note: Settings changes will take effect when loading a new model."
text: "Note: LLM settings take effect when loading a new model. ONNX GPU preference applies immediately."

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 | 🟡 Minor | ⚡ Quick win

Correct the ONNX GPU preference note.

Line 714 states that the preference applies immediately. The preference is read when OnnxRuntimeSettings::configureSessionOptions() creates a new session. Existing ONNX sessions keep their current execution provider. State that the change applies to newly created ONNX sessions.

🤖 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 `@qml/AISettingsDialog.qml` at line 714, Update the note text in
AISettingsDialog so the ONNX GPU preference is described as applying only to
newly created ONNX sessions, not immediately to existing sessions; preserve the
existing LLM settings wording.

Comment on lines +73 to 83
onExpandedChanged: {
if (root.expanded)
Qt.callLater(function() { contentLoader.loadActive = true })
else
contentLoader.loadActive = false
}

Component.onCompleted: {
if (root.expanded)
Qt.callLater(function() { contentLoader.loadActive = true })
}

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

Guard the deferred activation against a state change during the pending callback.

Qt.callLater(function() {...}) queues a new closure on each call. It does not dedupe against an earlier pending call from a different closure instance.

If root.expanded toggles from true to false before the deferred callback runs, the callback still sets contentLoader.loadActive = true unconditionally. The section stays visually collapsed (visible is bound to root.expanded), but the Loader instantiates its content anyway and fires onLoadedcontentReady(). Downstream, qml/PropertiesPanel.qml connects onContentReady to call MocapController.refreshDevices(), so this can trigger a device refresh while the section is collapsed.

Recheck root.expanded inside the deferred callback before activating the Loader.

🐛 Proposed fix to recheck expanded state in the deferred callback
     onExpandedChanged: {
         if (root.expanded)
-            Qt.callLater(function() { contentLoader.loadActive = true })
+            Qt.callLater(function() { if (root.expanded) contentLoader.loadActive = true })
         else
             contentLoader.loadActive = false
     }
🤖 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 `@qml/CollapsibleSection.qml` around lines 73 - 83, Guard both deferred
callbacks in CollapsibleSection’s onExpandedChanged and Component.onCompleted
handlers by rechecking root.expanded inside the Qt.callLater closure before
setting contentLoader.loadActive = true; keep immediate deactivation unchanged.

Comment on lines +19 to +31
if [[ -f "$DEST/nvidia/cudnn/lib/libcudnn.so.9" ]]; then
echo "cuDNN already present at $DEST/nvidia/cudnn/lib"
else
echo "Installing nvidia-cudnn-cu12 + nvidia-cublas-cu12 into $DEST ..."
python3 -m pip install \
nvidia-cudnn-cu12 nvidia-cublas-cu12 \
-t "$DEST" --no-cache-dir --upgrade
fi

if [[ ! -f "$DEST/nvidia/cudnn/lib/libcudnn.so.9" ]]; then
echo "cuDNN install failed." >&2
exit 1
fi

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

deps="${1:-.cache/cuda-deps}"
test -f "$deps/nvidia/cudnn/lib/libcudnn.so.9"
test -f "$deps/nvidia/cublas/lib/libcublas.so.12"

Repository: fernandotonon/QtMeshEditor

Length of output: 164


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Locate and inspect the installer around the cited lines.
if [ -f scripts/install-onnx-gpu-deps.sh ]; then
  echo "== file exists =="
  wc -l scripts/install-onnx-gpu-deps.sh
  echo "== cited lines =="
  sed -n '1,80p' scripts/install-onnx-gpu-deps.sh | nl -ba
else
  echo "scripts/install-onnx-gpu-deps.sh not found"
  fd -a 'install-onnx-gpu-deps\.sh|onnx.*deps|gpu.*deps' .
fi

echo "== package paths / references =="
rg -n "nvidia-(cudnn|cublas)|libcudnn|libcublas|conda|pip install -t|DEST|cache" scripts -S 2>/dev/null || true

Repository: fernandotonon/QtMeshEditor

Length of output: 276


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== file exists =="
wc -l scripts/install-onnx-gpu-deps.sh
echo "== cited lines with line numbers using awk =="
awk '{printf "%4d  %s\n", NR, $0}' scripts/install-onnx-gpu-deps.sh | sed -n '1,80p'

echo "== package paths / references =="
rg -n "nvidia-(cudnn|cublas)|libcudnn|libcublas|conda|pip install -t|DEST|cache" scripts -S || true

Repository: fernandotonon/QtMeshEditor

Length of output: 11675


🌐 Web query:

PyPI nvidia-cudnn-cu12 package libcudnn.so.9 path nvidia-cublas-cu12 libcublas.so.12 path

💡 Result:

The PyPI packages nvidia-cudnn-cu12 and nvidia-cublas-cu12 install their respective shared library files within your Python environment's site-packages directory [1][2]. Because these libraries are not installed in standard system paths (like /usr/lib), they are not automatically discoverable by all applications, which frequently causes "cannot open shared object file" errors [1][2]. Path structure in site-packages For a standard Python virtual environment (e.g.,.venv), the libraries are located in subdirectories under the nvidia package namespace [2]: libcudnn.so.9:.venv/lib/pythonX.Y/site-packages/nvidia/cudnn/lib/libcudnn.so.9 [3][2] libcublas.so.12:.venv/lib/pythonX.Y/site-packages/nvidia/cublas/lib/libcublas.so.12 [2] (Replace pythonX.Y with your actual Python version, such as python3.12). How to resolve library discovery issues If an application cannot find these files, you can resolve the issue using one of the following methods: 1. Update LD_LIBRARY_PATH (Linux) You can point your environment to these paths so that the dynamic linker can locate the libraries [2]. You can automate this by adding the directories to your LD_LIBRARY_PATH [2]: export LD_LIBRARY_PATH=$(find /path/to/.venv -name ".so" | grep nvidia | xargs dirname | sort -u | paste -d ":" -s -):$LD_LIBRARY_PATH 2. Programmatic loading If you are writing a Python application, you can use the nvidia modules to programmatically locate and load the libraries using ctypes [4][5]: import os import ctypes import nvidia.cudnn import nvidia.cublas # Path to the library directory cudnn_lib = os.path.join(nvidia.cudnn.path, "lib", "libcudnn.so.9") cublas_lib = os.path.join(nvidia.cublas.path, "lib", "libcublas.so.12") # Load them ctypes.CDLL(cudnn_lib, mode=ctypes.RTLD_GLOBAL) ctypes.CDLL(cublas_lib, mode=ctypes.RTLD_GLOBAL) These packages are designed to be used as dependencies by frameworks like PyTorch, which typically handle this library loading automatically [1][4]. If you are encountering missing library errors while using these packages directly, it is usually because the application expects the libraries to be in a system-wide path rather than the Python-specific site-packages structure [1][2].

Citations:


Validate cuBLAS before accepting cached CUDA deps. Line 19 only checks libcudnn.so.9, so a partial cache can skip pip install and the final check can still succeed while nvidia-cublas-cu12 is missing. Check libcudnn.so.9 and libcublas.so.12 in both conditions.

🤖 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 `@scripts/install-onnx-gpu-deps.sh` around lines 19 - 31, Update the dependency
presence check and the post-install validation in the script around the existing
cuDNN checks to require both $DEST/nvidia/cudnn/lib/libcudnn.so.9 and
$DEST/nvidia/cublas/lib/libcublas.so.12. Only skip pip installation when both
files exist, and fail after installation if either library is missing.

Comment thread src/CLIPipeline.cpp
Comment on lines +1559 to +1566
#ifdef ENABLE_MOCAP
MocapCameraHints::ensureMultimediaBackendSafe();
#endif
QApplication a(argc, argv);
QCoreApplication::setOrganizationName("QtMeshEditor");
QCoreApplication::setApplicationName("QtMeshEditor");
QCoreApplication::setApplicationVersion(QTMESHEDITOR_VERSION);
OnnxRuntimeSettings::prepareRuntimeEnvironment();

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 12 \
  'redirectStdout|ensureMultimediaBackendSafe|prepareRuntimeEnvironment|stdout|printf|qInfo|qDebug' \
  src/CLIPipeline.cpp \
  src/CLIPipeline.h \
  src/Mocap/MocapCameraHints.cpp \
  src/Mocap/MocapCameraHints.h \
  src/OnnxRuntimeSettings.cpp \
  src/OnnxRuntimeSettings.h || true

Repository: fernandotonon/QtMeshEditor

Length of output: 28509


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find files in the repository that use Qt Multimedia and QCoreApplication classes/classes likely involved in initialization.
python3 - <<'PY'
import os, subprocess, re
files = subprocess.check_output(["git","ls-files"], text=True, encoding="utf-8").splitlines()
candidates = []
for f in files:
    if not f.endswith((".h",".hpp",".cpp",".cc",".cxx",".c")):
        continue
    try:
        text = open(f, "r", encoding="u8", errors="ignore").read()
    except Exception:
        continue
    hits = []
    for pat in [
        "QMediaPlayer", "QCamera", "QAudio", "QVideoFrame", "QMediaDevices", "MocapCameraHints",
        "QCoreApplication", "QGuiApplication", "QApplication", "qInfo", "qDebug", "qWarning", "qCritical"
    ]:
        if pat in text:
            hits.append(pat)
    if hits:
        candidates.append((f, hits))
print("multimedia_core_candidates:")
for f,hits in sorted(candidates):
    print(f"{f}: {', '.join(hits)}")
PY

echo
echo "All Qt Multimedia-related occurrences (focused):"
rg -n 'QMediaPlayer|QCamera|QAudio|QVideoFrame|QMediaDevices|QMediaCapture|QMediaPlaylist|QUrl|QUrlRequester|QMediaSession' \
  src \
  --glob '*.{h,hpp,cpp,cc,cxx,c}' \
  --glob '!**/.git/**' || true

echo
echo "Initialize command region:"
sed -n '1520,1585p' src/CLIPipeline.cpp

echo
echo "redirecting and handlers:"
sed -n '570,595p' src/CLIPipeline.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 34112


🌐 Web query:

Qt QMediaDevices::videoInputs stderr stdout usage PlatformMediaIntegration QPlatformMediaIntegration instance environment variable backend log stream

💡 Result:

In Qt Multimedia, QMediaDevices::videoInputs retrieves a list of connected cameras by interacting with the QPlatformMediaIntegration instance, which acts as the abstraction layer for media backends [1]. Environment Variable and Backend Selection The media backend used by Qt is determined by the QPlatformMediaIntegration class [2][3]. It selects the backend in the following priority order: 1. The value of the QT_MEDIA_BACKEND environment variable [2][3]. 2. The default backend defined for the specific platform [2]. You can identify available backends and debug the loading process by enabling logging categories. Qt Multimedia uses the category "qt.multimedia.plugin" to report backend loading status [3][4]. Logging and Debugging To view logs related to backend initialization, media devices, or FFmpeg integration, you can use the QT_LOGGING_RULES environment variable [5][6]. Common logging configurations: - Enable all multimedia logs: QT_LOGGING_RULES=".multimedia.=true" [5][6]. - Enable backend plugin logs: QT_LOGGING_RULES="qt.multimedia.plugin=true" [3][5]. - For FFmpeg-based backends, you can further debug using QT_FFMPEG_DEBUG=1 [5][6]. These logs are typically directed to stderr by default, consistent with Qt's standard logging framework. If QMediaDevices::videoInputs returns an empty list unexpectedly, these logs can help verify whether the correct backend is being loaded and whether the system is successfully detecting hardware devices [7][5]. Summary of Key Mechanisms - QMediaDevices::videoInputs relies on QPlatformMediaIntegration::instance->videoDevices [1]. - QT_MEDIA_BACKEND allows you to explicitly force a specific backend plugin if the auto-detection fails [2][3]. - Logging is controlled via QT_LOGGING_RULES, allowing granular control over which categories (like backend discovery) are output to stderr [5][6].

Citations:


Avoid stdout writes before redirectStdout().

The initialization at src/CLIPipeline.cpp:1559-1566 runs before the saved STDOUT/redirect and custom message handler at line 1570. If QApplication construction, Qt Multimedia setup, or ONNX runtime setup write to stdout, CLI JSON/info output can be corrupted. Call redirectStdout() before these initializers, or ensure the direct stdout is limited to stderr and CLI output always uses CLIPipeline::writeOutput().

🤖 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/CLIPipeline.cpp` around lines 1559 - 1566, Move redirectStdout() in the
CLI initialization flow before MocapCameraHints::ensureMultimediaBackendSafe(),
QApplication construction, and OnnxRuntimeSettings::prepareRuntimeEnvironment().
Preserve the existing saved-STDOUT and custom message-handler setup while
ensuring these initializers cannot write directly to stdout before JSON or
informational output is routed safely.

Source: Coding guidelines

Comment on lines +8 to +25
TEST(OnnxRuntimeSettings, PreferGpuPersists)
{
QSettings settings;
settings.remove("ai/onnxPreferGpu");

OnnxRuntimeSettings* ort = OnnxRuntimeSettings::instance();
ort->loadSettings();

const bool initial = ort->preferGpu();
ort->setPreferGpu(!initial);
EXPECT_EQ(ort->preferGpu(), !initial);

OnnxRuntimeSettings* reloaded = OnnxRuntimeSettings::instance();
reloaded->loadSettings();
EXPECT_EQ(reloaded->preferGpu(), !initial);

ort->setPreferGpu(initial);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the persistence test independent of QTMESH_ONNX_PREFER_GPU.

preferGpu() returns the environment override when QTMESH_ONNX_PREFER_GPU is set. If that variable is set in the developer or CI environment, EXPECT_EQ(ort->preferGpu(), !initial) fails regardless of persistence. Unset the variable for the duration of the test.

💚 Proposed fix
 TEST(OnnxRuntimeSettings, PreferGpuPersists)
 {
+    const QByteArray savedOverride = qgetenv("QTMESH_ONNX_PREFER_GPU");
+    qunsetenv("QTMESH_ONNX_PREFER_GPU");
+
     QSettings settings;
     settings.remove("ai/onnxPreferGpu");
@@
     ort->setPreferGpu(initial);
+    if (!savedOverride.isEmpty())
+        qputenv("QTMESH_ONNX_PREFER_GPU", savedOverride);
 }
📝 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
TEST(OnnxRuntimeSettings, PreferGpuPersists)
{
QSettings settings;
settings.remove("ai/onnxPreferGpu");
OnnxRuntimeSettings* ort = OnnxRuntimeSettings::instance();
ort->loadSettings();
const bool initial = ort->preferGpu();
ort->setPreferGpu(!initial);
EXPECT_EQ(ort->preferGpu(), !initial);
OnnxRuntimeSettings* reloaded = OnnxRuntimeSettings::instance();
reloaded->loadSettings();
EXPECT_EQ(reloaded->preferGpu(), !initial);
ort->setPreferGpu(initial);
}
TEST(OnnxRuntimeSettings, PreferGpuPersists)
{
const QByteArray savedOverride = qgetenv("QTMESH_ONNX_PREFER_GPU");
qunsetenv("QTMESH_ONNX_PREFER_GPU");
QSettings settings;
settings.remove("ai/onnxPreferGpu");
OnnxRuntimeSettings* ort = OnnxRuntimeSettings::instance();
ort->loadSettings();
const bool initial = ort->preferGpu();
ort->setPreferGpu(!initial);
EXPECT_EQ(ort->preferGpu(), !initial);
OnnxRuntimeSettings* reloaded = OnnxRuntimeSettings::instance();
reloaded->loadSettings();
EXPECT_EQ(reloaded->preferGpu(), !initial);
ort->setPreferGpu(initial);
if (!savedOverride.isEmpty())
qputenv("QTMESH_ONNX_PREFER_GPU", savedOverride);
}
🧰 Tools
🪛 Cppcheck (2.21.0)

[error] 22-22: There is an unknown macro here somewhere. Configuration is required. If Q_PROPERTY is a macro then please configure it.

(unknownMacro)

🤖 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/OnnxRuntimeSettings_test.cpp` around lines 8 - 25, Update the
PreferGpuPersists test to clear QTMESH_ONNX_PREFER_GPU for the test’s duration
before reading preferGpu(), using the existing Qt environment-handling
facilities and restoring the prior environment value afterward so the test
remains isolated.

Comment on lines +62 to +76
void prependLdLibraryPath(const QStringList& dirs)
{
if (dirs.isEmpty())
return;
QStringList merged = dirs;
const QByteArray old = qgetenv("LD_LIBRARY_PATH");
if (!old.isEmpty())
merged << QString::fromUtf8(old).split(':',
Qt::SkipEmptyParts);
qputenv("LD_LIBRARY_PATH", merged.join(':').toUtf8());
if (qEnvironmentVariableIsSet("QTMESH_ONNX_DEBUG")) {
fprintf(stderr, "[onnx] LD_LIBRARY_PATH=%s\n",
qgetenv("LD_LIBRARY_PATH").constData());
}
}

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

🌐 Web query:

Does setenv LD_LIBRARY_PATH at runtime affect dlopen search path glibc

💡 Result:

Yes, setting the LD_LIBRARY_PATH environment variable at runtime does affect the search path used by dlopen in glibc [1][2][3]. When the dynamic linker searches for a shared object (when a path containing a slash is not provided to dlopen), it follows a specific search order. The LD_LIBRARY_PATH environment variable is included in this search sequence, provided the program is not running in a secure-execution mode (e.g., set-user-ID or set-group-ID programs), in which case it is ignored [1][3]. The general search order for the dynamic linker is [4][5]: 1. DT_RPATH: If the calling object contains a DT_RPATH tag and no DT_RUNPATH tag exists, these directories are searched first [1][3]. 2. LD_LIBRARY_PATH: The directories specified in the LD_LIBRARY_PATH environment variable are searched next [1][3]. 3. DT_RUNPATH: If the calling object contains a DT_RUNPATH tag, these directories are searched after LD_LIBRARY_PATH [1][3]. 4. /etc/ld.so.cache: The dynamic linker checks this cache file, which is maintained by ldconfig [1][3]. 5. Default Directories: Finally, the default system library directories (/lib and /usr/lib) are searched [1][3]. Because LD_LIBRARY_PATH appears early in this sequence, it can be used to override libraries found in later locations, such as those in /etc/ld.so.cache or the default system paths, and it takes precedence over DT_RUNPATH settings [4][5]. It does not, however, override DT_RPATH settings if they are present [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'OnnxRuntimeSettings\.cpp$' . || true

echo "== file excerpt =="
file="$(fd 'OnnxRuntimeSettings\.cpp$' . | head -n1)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  sed -n '1,140p' "$file" | cat -n
fi

echo "== related symbols =="
rg -n "prependLdLibraryPath|qputenv|LD_LIBRARY_PATH|Ort::Session|cudaProviderLibraryLoads|loadCudnn|cudnn|cudart|RTLD_GLOBAL|QLibrary|dlopen|SentryReporter::addBreadcrumb" . -g '!build*' -g '!node_modules' -g '!dist' -g '!out' | head -n 200

Repository: fernandotonon/QtMeshEditor

Length of output: 24709


🌐 Web query:

glibc dynamic linker ld.so.8 LD_LIBRARY_PATH dlopen after startup LD_LIBRARY_PATH is added again

💡 Result:

In the glibc dynamic linker (ld.so), the LD_LIBRARY_PATH environment variable is processed only once during process startup [1]. The dynamic linker initializes its search path structures by reading the value of LD_LIBRARY_PATH at that time [2]. Because the environment variable is parsed and cached at startup, any subsequent modifications to the LD_LIBRARY_PATH environment variable made by the application (e.g., via setenv or putenv) after the program has started do not affect the behavior of subsequent dlopen calls [1]. The dynamic linker will continue to use the value of LD_LIBRARY_PATH as it existed when the process was initialized [2][1]. When dlopen is called, the dynamic linker searches for shared objects according to a specific, hard-coded order defined in the ld.so(8) manual page [3][4][5]. This search order includes the cached LD_LIBRARY_PATH from startup, but it does not check the current environment for any changes made since the program began execution [3][4][1]. To influence the search path for dlopen after startup, one must typically use alternatives such as setting the RPATH/RUNPATH in the binary, using dlmopen with a specific namespace, or manipulating the search path through linker-specific features if available.

Citations:


Do not rely on runtime LD_LIBRARY_PATH changes for CUDA dependency resolution.

glibc's dynamic linker builds its dlopen search paths from LD_LIBRARY_PATH at startup; later qputenv()/setenv() updates are not picked up by subsequent dlopen calls unless the program is re-executed in secure-execution mode. In this path, cudaProviderLibraryLoads() succeeds because it uses QLibrary with an absolute path, but later libonnxruntime.so/provider dlopen()/ld.so resolution can still fail to find .cache/cuda-deps cudnn/cublas libraries. Use build-time RPATH/RUNPATH, absolute-path preloading with QLibrary/dlopen(RTLD_GLOBAL), or dlmopen() before creating the Ort::Session.

🤖 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/OnnxRuntimeSettings.cpp` around lines 62 - 76, Replace the runtime
LD_LIBRARY_PATH approach in prependLdLibraryPath with a loader-visible
dependency strategy: configure build-time RPATH/RUNPATH for the CUDA dependency
directory, or preload the required CUDA libraries by absolute path with
QLibrary/dlopen(RTLD_GLOBAL) before Ort::Session creation. Ensure
cudaProviderLibraryLoads and subsequent libonnxruntime/provider loading can
resolve cudnn/cublas dependencies without relying on qputenv.

Comment on lines +103 to +112
void OnnxRuntimeSettings::prepareRuntimeEnvironment()
{
#ifdef QTMESH_ONNX_GPU_BUILD
static bool done = false;
if (done)
return;
done = true;
prependLdLibraryPath(cudnnSearchPaths());
#endif
}

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

Make the one-shot guard thread-safe.

prepareRuntimeEnvironment() is reachable from worker threads through configureSessionOptions()tryAppendGpuExecutionProvider(), and also from the UI thread and CLIPipeline. The static bool done check-then-set is not atomic, so two threads can both run prependLdLibraryPath() and race on the qgetenv/qputenv pair for LD_LIBRARY_PATH.

🔒 Proposed fix using a call-once guard
 void OnnxRuntimeSettings::prepareRuntimeEnvironment()
 {
 `#ifdef` QTMESH_ONNX_GPU_BUILD
-    static bool done = false;
-    if (done)
-        return;
-    done = true;
-    prependLdLibraryPath(cudnnSearchPaths());
+    static std::once_flag once;
+    std::call_once(once, [] { prependLdLibraryPath(cudnnSearchPaths()); });
 `#endif`
 }

Add #include <mutex>.

📝 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
void OnnxRuntimeSettings::prepareRuntimeEnvironment()
{
#ifdef QTMESH_ONNX_GPU_BUILD
static bool done = false;
if (done)
return;
done = true;
prependLdLibraryPath(cudnnSearchPaths());
#endif
}
void OnnxRuntimeSettings::prepareRuntimeEnvironment()
{
`#ifdef` QTMESH_ONNX_GPU_BUILD
static std::once_flag once;
std::call_once(once, [] { prependLdLibraryPath(cudnnSearchPaths()); });
`#endif`
}
🤖 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/OnnxRuntimeSettings.cpp` around lines 103 - 112, Replace the non-atomic
static bool done guard in prepareRuntimeEnvironment() with a thread-safe
call-once mechanism. Add `#include` <mutex> at the top of the file, then declare a
static std::once_flag inside the function and wrap the
prependLdLibraryPath(cudnnSearchPaths()) call with std::call_once to ensure it
executes exactly once regardless of concurrent access from multiple threads.

Comment on lines +288 to +291
bool OnnxRuntimeSettings::tryAppendGpuExecutionProvider(Ort::SessionOptions& so,
CoreMlStyle style)
{
prepareRuntimeEnvironment();

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find every OnnxRuntimeSettings entry point and check main-thread initialization order.
rg -n -C4 'OnnxRuntimeSettings::(instance|prepareRuntimeEnvironment)\(' --type=cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## files"
git ls-files | grep -E '(^|/)(OnnxRuntimeSettings\.(cpp|h)|main\.cpp|CLIPipeline\.(cpp|h))$|src/.*(Predi|TextureUpscaler)\.(cpp|h)$' | sed -n '1,200p'

echo "## OnnxRuntimeSettings symbols"
rg -n 'OnnxRuntimeSettings|OnnxRuntimeSettings::|instance\(' --type=cpp --type=h . | sed -n '1,220p'

Repository: fernandotonon/QtMeshEditor

Length of output: 20828


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## OnnxRuntimeSettings.h"
sed -n '1,220p' src/OnnxRuntimeSettings.h

echo "## OnnxRuntimeSettings.cpp instance/configure/load/save"
sed -n '1,380p' src/OnnxRuntimeSettings.cpp

echo "## caller contexts"
for f in src/UniRigPredictor.cpp src/MotionInbetween.cpp; do
  echo "### $f"
  ast-grep outline "$f" --match 'OnnxRuntimeSettings' --view expanded || true
  rg -n -C8 'OnnxRuntimeSettings::configureSessionOptions|tryAppendGpuExecutionProvider|QThread|thread\(' "$f" | sed -n '1,240p'
done

echo "## main.cpp relevant sections"
rg -n -C10 'OnnxRuntimeSettings|CLIPipeline|QCoreApplication|main\(' src/main.cpp

echo "## CLIPipeline relevant sections"
rg -n -C10 'OnnxRuntimeSettings|CLIPipeline|QThread|thread\(' src/CLIPipeline.cpp src/CLIPipeline.h | sed -n '1,260p'

Repository: fernandotonon/QtMeshEditor

Length of output: 37796


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## main.cpp continued / OnnxRuntimeSettings touches"
sed -n '1,290p' src/main.cpp | rg -n -C6 'OnnxRuntimeSettings|CLIPipeline::run|AppLaunchHandler::isCliInvocation|QApplication|QCoreApplication|qmlRegisterSingletonType<OnnxRuntimeSettings>'

echo "## CLIPipeline OnnxRuntimeSettings usages"
rg -n -C6 'OnnxRuntimeSettings' src/CLIPipeline.cpp src/CLIPipeline.h

echo "## UniRig/MotionInbetween threading and calls"
rg -n -C8 'QThread|moveToThread|start\(|run\(|OnnxRuntimeSettings::configureSessionOptions' src/UniRigPredictor.cpp src/UniRigPredictor.h src/MotionInbetween.cpp src/MotionInbetween.h

echo "## TextureUpscaler threading"
if [ -f src/TextureUpscaler.cpp ]; then rg -n -C8 'QThread|moveToThread|class|start\(|run\(|OnnxRuntimeSettings' src/TextureUpscaler.cpp src/TextureUpscaler.h; fi

Repository: fernandotonon/QtMeshEditor

Length of output: 17331


Ensure OnnxRuntimeSettings is created before worker-thread inference.

instance() constructs the QObject on the calling thread and does not install it in the GUI application’s thread. Worker threads can call configureSessionOptions() via UniRigPredictor, MotionInbetween, or TextureUpscaler, so signal connections and any GUI-thread settingsChanged() consumers can rely on worker-thread affinity. Add an early main-thread initialization call and require UI connections before the first possible background inference.

🤖 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/OnnxRuntimeSettings.cpp` around lines 288 - 291, Initialize the
OnnxRuntimeSettings singleton on the main/UI thread before any worker-thread
inference can begin, rather than first constructing it through
tryAppendGpuExecutionProvider or configureSessionOptions. Add the early
initialization at application startup and establish the required
settingsChanged() UI connections before background work starts, preserving
worker-thread calls through the existing OnnxRuntimeSettings::instance() path.

Comment on lines +305 to +312
#elif defined(_WIN32) && !defined(__MINGW32__)
try {
so.AppendExecutionProvider("DML", {});
return true;
} catch (const Ort::Exception& e) {
if (qEnvironmentVariableIsSet("QTMESH_ONNX_DEBUG"))
fprintf(stderr, "[onnx] DirectML EP failed: %s\n", e.what());
}

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

onnxruntime C++ AppendExecutionProvider "DML" string supported version

💡 Result:

In the C++ API for ONNX Runtime, the DirectML (DML) execution provider is typically initialized using specific provider-specific APIs rather than a generic string-based AppendExecutionProvider call [1][2][3]. The recommended method is to use the OrtDmlApi structure, which provides the SessionOptionsAppendExecutionProvider_DML function [1][2][3]. This requires retrieving the DML API through GetExecutionProviderApi using the string identifier "DML" [4][5][3]. Recommended Implementation Pattern: 1. Retrieve the DML API using ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, ...) [4][3]. 2. Use the returned OrtDmlApi pointer to call SessionOptionsAppendExecutionProvider_DML(sessionOptions, device_id) [4][3]. The legacy function OrtSessionOptionsAppendExecutionProvider_DML from dml_provider_factory.h is deprecated but remains available for backward compatibility [2][3]. While some other execution providers support a generic AppendExecutionProvider("ProviderName") interface, the DML provider historically relies on the specialized OrtDmlApi to handle device indexing and initialization [1][3]. As of recent versions, ONNX Runtime has been working toward supporting more canonical EP names in session options, but for production use with DML, the explicit OrtDmlApi approach is the standard and most reliable method [1][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant files =="
git ls-files | rg '(^|/)OnnxRuntime\.cmake$|src/OnnxRuntimeSettings\.cpp$|(^|/)CMakeLists\.txt$|(^|/)vcpkg\.json$|^README|onnxruntime|ort'

echo "== OnnxRuntime.cmake relevant occurrences =="
if [ -f cmake/OnnxRuntime.cmake ]; then
  wc -l cmake/OnnxRuntime.cmake
  sed -n '1,240p' cmake/OnnxRuntime.cmake | nl -ba
fi

echo "== OnnxRuntimeSettings.cpp relevant region =="
if [ -f src/OnnxRuntimeSettings.cpp ]; then
  wc -l src/OnnxRuntimeSettings.cpp
  sed -n '260,340p' src/OnnxRuntimeSettings.cpp | nl -ba
fi

echo "== direct AppendExecutionProvider/DML usages =="
rg -n 'AppendExecutionProvider|DML|OrtSessionOptionsAppendExecutionProvider_DML|dml_provider_factory|OnnxRuntime.cmake' .

Repository: fernandotonon/QtMeshEditor

Length of output: 2699


🌐 Web query:

ONNX Runtime v1.19 AppendExecutionProvider DirectML DML provider string "DML"

💡 Result:

In ONNX Runtime (including v1.19), the string identifier used to access the DirectML Execution Provider API is "DML" [1][2][3]. To append the DirectML Execution Provider to your session options in C++, you should use the OrtDmlApi interface rather than a generic AppendExecutionProvider function. The process involves retrieving the DML API via GetExecutionProviderApi using the "DML" identifier [1][2][3]. Recommended implementation pattern: 1. Obtain the OrtApi and retrieve the OrtDmlApi [1][3]: const OrtApi& ortApi = Ort::GetApi; const OrtDmlApi* ortDmlApi = nullptr; ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast(&ortDmlApi)); 2. Use the retrieved ortDmlApi to append the provider to your SessionOptions [1][2][3]: ortDmlApi->SessionOptionsAppendExecutionProvider_DML(sessionOptions, device_id); Note that the free function OrtSessionOptionsAppendExecutionProvider_DML is considered deprecated in favor of using the SessionOptionsAppendExecutionProvider_DML method available through the OrtDmlApi interface [4][1][2].

Citations:


🌐 Web query:

ONNX Runtime v1.21 AppendExecutionProvider DirectML DML provider string "DML"

💡 Result:

In ONNX Runtime (including version 1.21), the provider string used to identify the DirectML Execution Provider is "DML" [1][2][3]. To register or append the DirectML Execution Provider to an inference session, the following approaches are typically used: 1. Recommended C/C++ API Approach: Rather than using a generic string-based append function for DirectML, it is standard practice to use the dedicated DirectML API. You first retrieve the DML-specific API structure using the string "DML" with the GetExecutionProviderApi method, then call the specialized registration function [1][2][4]: // Example using C API const OrtDmlApi* ortDmlApi = nullptr; Ort::GetApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast(&ortDmlApi)); ortDmlApi->SessionOptionsAppendExecutionProvider_DML(sessionOptions, device_id); 2. Modern/Generic C++ Approach: Recent updates to ONNX Runtime allow for a more generic registration method that avoids including the legacy dml_provider_factory.h header [5]. This can be accomplished by calling the AppendExecutionProvider method directly on session options using the "DML" string [2][5]: sessionOptions.AppendExecutionProvider("DML", provider_options); Note on Usage: - The legacy function OrtSessionOptionsAppendExecutionProvider_DML (found in dml_provider_factory.h) is deprecated in favor of the newer SessionOptionsAppendExecutionProvider_DML accessible via the DML API pointer [6][1]. - In some language bindings (like C#), the DirectML provider may have limitations regarding how easily provider options (such as device_filter) can be passed compared to other execution providers, often necessitating the use of the explicit DML API over generic registration methods [7].

Citations:


🌐 Web query:

ONNX Runtime v1.22 AppendExecutionProvider DirectML DML provider string "DML"

💡 Result:

In ONNX Runtime (including v1.22), the DirectML Execution Provider is not configured via a simple string passed to a generic AppendExecutionProvider method [1][2]. Instead, it uses a specific API interface retrieved via GetExecutionProviderApi [3][1][4]. The provider string used to retrieve the DirectML API via GetExecutionProviderApi is "DML" [3][1][5]. Key implementation details: 1. Retrieval: You must first obtain the OrtDmlApi interface by calling the standard ORT API with the provider string "DML" [3][1][6]. 2. Initialization: Once you have the OrtDmlApi pointer, you use it to call specific initialization functions, such as SessionOptionsAppendExecutionProvider_DML or SessionOptionsAppendExecutionProvider_DML2 (which allows for advanced options like device filtering) [7][1][6]. 3. Deprecation: The older free function OrtSessionOptionsAppendExecutionProvider_DML (found in dml_provider_factory.h) is considered deprecated; usage of the API retrieved through GetExecutionProviderApi is the recommended approach [7][1]. Example usage pattern (C++): OrtApi const& ortApi = Ort::GetApi; OrtDmlApi const* ortDmlApi = nullptr; ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast(&ortDmlApi)); Ort::SessionOptions sessionOptions; //... configure other options... ortDmlApi->SessionOptionsAppendExecutionProvider_DML(sessionOptions, /device_id/ 0); [1][4] For high-level languages like Python, the provider string used in the providers list is 'DmlExecutionProvider' [6]. The.NET/C# environment typically exposes a dedicated AppendExecutionProvider_DML method on the SessionOptions object, rather than requiring the general-purpose append method [8][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== find exact files =="
git ls-files cmake/OnnxRuntime.cmake src/OnnxRuntimeSettings.cpp

echo "== version/pinned references in OnnxRuntime.cmake =="
sed -n '1,160p' cmake/OnnxRuntime.cmake

echo "== OnnxRuntimeSettings.cpp DirectML region =="
sed -n '280,330p' src/OnnxRuntimeSettings.cpp

echo "== all DirectML / AppendExecutionProvider references =="
rg 'AppendExecutionProvider|GetExecutionProviderApi|SessionOptionsAppendExecutionProvider_DML|OrtDmlApi|OrtSessionOptionsAppendExecutionProvider_DML|dml_provider_factory|ProviderName|DML|DirectML' .

Repository: fernandotonon/QtMeshEditor

Length of output: 8288


🌐 Web query:

ONNX Runtime 1.20.1 SessionOptions AppendExecutionProvider DML provider_options official

💡 Result:

In ONNX Runtime, the DirectML (DML) execution provider is primarily managed via specialized factory functions rather than the generic SessionOptions::AppendExecutionProvider method used by some other providers [1][2]. For C++ and C API users, the recommended approach is to use the SessionOptionsAppendExecutionProvider_DML function (or SessionOptionsAppendExecutionProvider_DML2 for advanced options), which is exposed through the OrtDmlApi structure [3][4][5]. Configuration steps: 1. Retrieve the DML API: Obtain the OrtDmlApi via Ort::GetApi().GetExecutionProviderApi("DML", ...) [4][5]. 2. Configure Session Options: You must explicitly disable memory pattern and set execution mode to sequential, as required by the DML provider [1][4]. session_options.DisableMemPattern; session_options.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL); 3. Append the Provider: Use the retrieved OrtDmlApi to append the DML provider [4][6]. ortDmlApi->SessionOptionsAppendExecutionProvider_DML(session_options, device_id); The older OrtSessionOptionsAppendExecutionProvider_DML function found in dml_provider_factory.h is deprecated but remains available for backward compatibility [3][4]. For C# users, the Microsoft.ML.OnnxRuntime.DirectML NuGet package provides a convenient method: sessionOptions.AppendExecutionProvider_DML(device_id); [1][7] When advanced configuration is needed, SessionOptionsAppendExecutionProvider_DML2 accepts OrtDmlDeviceOptions, which allows for specifying performance preferences (e.g., HighPerformance) and device filtering (e.g., GPU) [3][5]. Some high-level APIs or specific language bindings (like ORT GenAI) may support configuration via dictionary-based provider options, but the standard C/C++ path relies on these factory APIs [8].

Citations:


🌐 Web query:

ONNX Runtime 1.20.1 dml provider AppendExecutionProvider provider_options

💡 Result:

The DirectML (DML) execution provider in ONNX Runtime does not use a generic AppendExecutionProvider function with a provider_options dictionary to configure its settings. Instead, it utilizes a specialized, DML-specific API defined in include/onnxruntime/core/providers/dml/dml_provider_factory.h [1][2]. To configure and enable the DML execution provider, you should use the functions provided by the OrtDmlApi structure rather than the general SessionOptions::AppendExecutionProvider method used by other providers like QNN or XNNPACK [3][4][5]. Key APIs for DirectML Configuration: 1. SessionOptionsAppendExecutionProvider_DML(OrtSessionOptions* options, int device_id): This function enables the DML execution provider on a specific hardware adapter (device index) [1][2]. It is the standard approach for basic configuration [3]. Note that older versions of the API (e.g., OrtSessionOptionsAppendExecutionProvider_DML) are deprecated [2][3]. 2. SessionOptionsAppendExecutionProvider_DML2(OrtSessionOptions* options, OrtDmlDeviceOptions* device_opts): This function provides advanced configuration options [2][6]. The OrtDmlDeviceOptions struct allows you to specify: - Performance preference (e.g., HighPerformance, LowPower, Default) [2]. - Device filter (e.g., Gpu, Npu, None) [2]. Implementation Steps: To use these APIs in C++, follow this pattern [3][4]: 1. Obtain the DML API: Use Ort::GetApi().GetExecutionProviderApi("DML", ORT_API_VERSION, ...) to retrieve the OrtDmlApi pointer [3][4]. 2. Set Mandatory Session Options: Before appending the DML provider, you must configure the session options as follows [1][3]: - sessionOptions.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL); - sessionOptions.DisableMemPattern(); 3. Append the Provider: Use the retrieved ortDmlApi to call either SessionOptionsAppendExecutionProvider_DML (for simple device ID selection) or SessionOptionsAppendExecutionProvider_DML2 (for advanced performance and device filtering) [2][3][4]. Do not use the generic AppendExecutionProvider("DML", provider_options) method, as it is not the supported interface for DirectML [4][5].

Citations:


🌐 Web query:

GitHub microsoft/onnxruntime 1.20.1 source AppendExecutionProvider DirectML provider_options

💡 Result:

In ONNX Runtime 1.20.1, the DirectML execution provider can be configured using provider_options when initializing an InferenceSession [1][2]. While C++ interfaces often rely on specialized factory functions in dml_provider_factory.h (such as SessionOptionsAppendExecutionProvider_DML1 or DML2) [3][4], the Python API allows passing a dictionary of options directly via the providers list [2]. Configuration Options The following keys can be used within the dictionary passed to the 'DmlExecutionProvider' in the providers list: - device_id (int): Specifies the hardware adapter index (e.g., 0 for the default GPU) [3][2]. - performance_preference (str): Defines performance behavior; common values include 'high_performance', 'default', or 'minimum_power' (or 'low_power') [5][2]. - device_filter (str): Filters the target device; typically supports 'gpu', 'npu', or 'any' [2]. Example (Python) To use specific configuration options in Python: session = ort.InferenceSession( "model.onnx", providers=[( 'DmlExecutionProvider', { 'device_id': 0, 'performance_preference': 'high_performance' })]) C++ Implementation For C++ development, direct manipulation of SessionOptions via AppendExecutionProvider is less common for DirectML than using the explicit API functions provided by the DML execution provider interface [6][4]. You typically retrieve the DML API and use functions like SessionOptionsAppendExecutionProvider_DML2 to apply settings like performance preferences and device filters [3][2]: // Example snippet for C++ const OrtDmlApi* dml_api = nullptr; Ort::ThrowOnError(Ort::GetApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast(&dml_api))); OrtDmlDeviceOptions device_options; device_options.Preference = OrtDmlPerformancePreference::HighPerformance; device_options.Filter = OrtDmlDeviceFilter::Gpu; dml_api->SessionOptionsAppendExecutionProvider_DML2(session_options, &device_options); Important Constraints The DirectML execution provider does not support memory pattern optimizations or parallel execution mode [4][7]. When configuring your session, you must ensure: - enable_mem_pattern is set to false [4][7]. - execution_mode is set to ExecutionMode::ORT_SEQUENTIAL [4][7]. These requirements apply to both C++ and other language bindings to avoid runtime errors [4][7].

Citations:


🌐 Web query:

ONNX Runtime 1.18 AppendExecutionProvider DML provider_options C++

💡 Result:

In ONNX Runtime (including version 1.18), the DirectML (DML) Execution Provider is typically enabled in C++ using specialized factory functions rather than a generic AppendExecutionProvider call [1][2]. ### Recommended Approach The recommended way to use DirectML in C++ is to use the OrtDmlApi interface to call SessionOptionsAppendExecutionProvider_DML [3][4]. This method is preferred over the deprecated free function OrtSessionOptionsAppendExecutionProvider_DML [3][4]. #### Implementation Example To use this, you retrieve the OrtDmlApi from the ONNX Runtime API and configure the required session options [3][5]: cpp // 1. Retrieve the DML API OrtApi const& ortApi = Ort::GetApi(); OrtDmlApi const* ortDmlApi = nullptr; ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast<const void>(&ortDmlApi)); // 2. Configure mandatory session options for DML Ort::SessionOptions sessionOptions; sessionOptions.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL); // Mandatory sessionOptions.DisableMemPattern(); // Mandatory // 3. Append the DML Execution Provider // device_id 0 corresponds to the default adapter ortDmlApi->SessionOptionsAppendExecutionProvider_DML(sessionOptions, 0); ### Important Notes * Mandatory Settings: When using the DirectML EP, you must call DisableMemPattern() and set the execution mode to ExecutionMode::ORT_SEQUENTIAL on your SessionOptions object, or the session creation will fail [1][2]. * Device Options: For more advanced configurations (such as performance preferences or device filtering), you can use the SessionOptionsAppendExecutionProvider_DML2 function, which accepts OrtDmlDeviceOptions [4]. * Alternative (Newer) API: In environments using the newer Windows ML-style explicit EP selection (where you enumerate devices first), you may use AppendExecutionProvider_V2 with an env and ep_options map [6][7]. However, for standard cross-platform DirectML usage as typically deployed in ONNX Runtime, the OrtDmlApi approach above remains the standard [3][1]. The headers required for these APIs are located in include/onnxruntime/core/providers/dml/dml_provider_factory.h [4][1].

Citations:


🌐 Web query:

ONNX Runtime 1.22.0 Release Notes AppendExecutionProvider DirectML provider_options C++ provider_options AppendExecutionProvider_CUDA CoreML DML

💡 Result:

ONNX Runtime 1.22.0 was released on May 10, 2025 [1][2]. Key updates in this release include integrated KleidiAI support for Arm architectures, MatMulNBits support for 8-bit quantized weights, support for TensorRT 10.9, and a new execution provider specifically for NVIDIA RTX GPUs [1][2]. In the ONNX Runtime C++ API, execution providers are configured through SessionOptions [3][4]. The AppendExecutionProvider family of methods is used to add these providers to an inference session [3][5]. AppendExecutionProvider Overview: The primary method for adding execution providers with custom options is: SessionOptions& AppendExecutionProvider(const std::string& provider_name, const std::unordered_map<std::string, std::string>& provider_options = {}); [3][5] Specific Provider Append Methods: For many providers, dedicated C++ methods exist that accept specific structs for configuration, ensuring type safety [3][4]. - AppendExecutionProvider_CUDA: Accepts OrtCUDAProviderOptions [3][5]. - AppendExecutionProvider_TensorRT: Accepts OrtTensorRTProviderOptions [3][5]. - AppendExecutionProvider_OpenVINO: Accepts OrtOpenVINOProviderOptions [3][5]. Usage Examples: - CoreML: Uses the string-based AppendExecutionProvider method with a map of options [6][7]. so.AppendExecutionProvider("CoreML", {{"ModelFormat", "MLProgram"}, {"MLComputeUnits", "ALL"}}); [6] - DirectML: While often configured via JSON in higher-level APIs like Generative AI [8], in the C++ API, it is typically managed by adding the provider string to the session options [8]. - Modern Approach (V2): For newer workflows, especially those involving complex hardware selection, AppendExecutionProvider_V2 is recommended, which allows passing a list of devices (via env.GetEpDevices) and provider options as a map [3][4][9]. Note on CUDA: Starting with version 1.22.0, ONNX Runtime GPU packages require CUDA 12.x; packages built for CUDA 11.x are no longer published [2][10].

Citations:


🌐 Web query:

"V1.20.1 (Jan 30, 2024)" "Providers" "ONNX Runtime"

💡 Result:

The premise that ONNX Runtime version 1.20.1 was released on January 30, 2024, is incorrect. Official records from the Microsoft ONNX Runtime repository indicate that version 1.20.1 was published on November 21, 2024 [1][2]. As of August 2026, version 1.20.1 is a legacy release, with subsequent versions up to 1.28.0 and beyond having been released in the interim [3].

Citations:


🌐 Web query:

ONNX Runtime 1.22.0 Release Notes providers C++ provider_options AppendExecutionProvider DirectML

💡 Result:

In ONNX Runtime 1.22.0, adding the DirectML Execution Provider to a session is primarily handled via the C API's OrtDmlApi struct or language-specific wrappers that interact with it [1][2]. The DirectML Execution Provider does not have a dedicated AppendExecutionProvider_DML method in the standard C++ Ort::SessionOptions class, unlike providers such as CUDA or TensorRT [3][4]. Instead, to add DirectML, you typically retrieve the DirectML API from the session options and invoke the provider-specific initialization function [1]. Key points for managing DirectML provider options in C++: 1. Retrieval: You can obtain the OrtDmlApi pointer using OrtApi::GetExecutionProviderApi with the provider name "DML" [1]. 2. Append Function: The OrtDmlApi provides the SessionOptionsAppendExecutionProvider_DML function, which accepts the OrtSessionOptions and the device ID as arguments [1]. 3. SessionOptions::AppendExecutionProvider: While Ort::SessionOptions contains a generic AppendExecutionProvider(const std::string& provider_name, ...) method, this is generally intended for providers that support the standard dictionary-based options interface [3][4]. As of 1.22.0, the recommended approach for DirectML remains utilizing the specific OrtDmlApi to ensure correct initialization of the DML device [1]. Release 1.22.0 introduced significant updates across other providers (such as TensorRT 10.9 support and new Nvidia RTX-specific execution providers) and new C++ wrapper classes for various provider options [5][6][7], but DirectML integration in the C++ API continues to rely on the established OrtDmlApi pattern [1][2].

Citations:


🌐 Web query:

ONNX Runtime SessionProviderOptionsAppendExecutionProvider DirectML 1.22.0 AppendExecutionProvider provider_options

💡 Result:

In ONNX Runtime 1.22.0, there is no single function named SessionProviderOptionsAppendExecutionProviderDirectML. Instead, the DirectML Execution Provider (EP) is typically added to an inference session using specific APIs provided by the OrtDmlApi or the C++ SessionOptions interface [1][2][3]. The primary methods for adding the DirectML EP are as follows: 1. SessionOptionsAppendExecutionProvider_DML: This function takes an OrtSessionOptions object and a device_id (integer representing the adapter index) to initialize the DirectML provider [2][3]. It is accessed via the OrtDmlApi [3][4]. 2. SessionOptionsAppendExecutionProvider_DML1: This function is used when you need to provide a specific DirectML device (IDMLDevice) and D3D12 command queue (ID3D12CommandQueue) rather than relying on an adapter index [1][2]. Usage Note: The older function OrtSessionOptionsAppendExecutionProvider_DML is officially deprecated in favor of the SessionOptionsAppendExecutionProvider_DML method accessed through GetExecutionProviderApi [2][3]. Regarding the term "provider_options" (as seen in generic AppendExecutionProvider calls for other EPs like CUDA or TensorRT): The DirectML EP does not expose a configuration dictionary via the standard AppendExecutionProvider(name, options) pattern used by other providers in all languages [5]. Users often find that attempting to pass a dictionary of provider options (e.g., for device filtering) to the DirectML EP via generic C# or C++ wrappers fails because the DirectML implementation expects specific initialization APIs rather than a generic configuration map [5]. To use the DirectML EP, you must also ensure your session options are configured correctly, specifically by disabling memory pattern optimizations and setting the execution mode to sequential [1][3][4]: - sessionOptions.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL); - sessionOptions.DisableMemPattern; [1] Top results: [1][2][3][5]

Citations:


🌐 Web query:

"AppendExecutionProvider_CUDA" "AppendExecutionProvider" "DML" "ONNXRuntime" "v1.22.0"

💡 Result:

In ONNX Runtime, you typically configure Execution Providers (EPs) by “appending” them to SessionOptions (C/C++) or by specifying an ordered provider list (Python). For ONNX Runtime 1.22.0, the relevant CUDA EP and DirectML EP enablement APIs are: 1) AppendExecutionProvider_CUDA (CUDAExecutionProvider) - ONNX Runtime exposes the CUDA EP through the SessionOptions C API, including the function SessionOptionsAppendExecutionProvider_CUDA (and a newer “_V2” variant using a structured options object). An example from the official CUDA EP doc shows configuring an OrtCUDAProviderOptions (device_id, gpu_mem_limit, etc.) and calling SessionOptionsAppendExecutionProvider_CUDA(session_options, &options). It also shows the V2 flow with CreateCUDAProviderOptions, UpdateCUDAProviderOptions, then SessionOptionsAppendExecutionProvider_CUDA_V2(session_options, cuda_options). [1] - Operational note (from the same official CUDA EP doc): “Starting with version 1.27, GPU packages published to PyPI (onnxruntime-gpu) and NuGet … are built with CUDA 13.0 by default. Older GPU package versions are built with CUDA 12.8 by default.” (This is a versioning note; it’s in the CUDA EP doc and is what the doc explicitly states.) [1] 2) DirectML enablement (DML) - ONNX Runtime has a DirectML Execution Provider doc describing enabling it via the DML provider factory / C API (when using a DML-enabled build). [2] - The DirectML C API entry point is OrtSessionOptionsAppendExecutionProvider_DML (in dml_provider_factory.h). That header comment states this export is deprecated and recommends using the OrtDmlApi / SessionOptionsAppendExecutionProvider_DML instead. [3] - The DirectML doc also describes what device_id means (adapter index; 0 is default adapter; negative invalid) and that there is an alternative DML API that uses a provided D3D12 command queue/device (the doc discusses “SessionOptionsAppendExecutionProvider_DML1”). [2] How to “AppendExecutionProvider_CUDA + DML” together (conceptual) - ONNX Runtime uses an EP priority order: when multiple EPs are registered, nodes are selected from them based on precedence, with fallback to CPU where needed. The execution-provider overview and Python API docs explicitly describe that the provider list is ordered by priority and that CUDAExecutionProvider is used if capable, otherwise CPU is used. [4][5] - Therefore, to use both in one session, you would: a) Build/obtain an ONNX Runtime binary that includes both CUDA EP and DML EP. b) Register both EPs on the same session options object in the desired precedence order (CUDA higher priority than DML, or vice versa), and let ONNX Runtime place each node on the first EP that supports it. c) Use the CUDA EP options struct/API for CUDA (AppendExecutionProvider_CUDA or SessionOptionsAppendExecutionProvider_CUDA_V2) and the DML EP API (SessionOptionsAppendExecutionProvider_DML / DML1 variants) for DML. [1][2][3] About the “AppendExecutionProvider_DML” C++ method name - Directly relevant caveat: there is not always a direct C++ wrapper method named AppendExecutionProvider_DML; the DML API is often accessed via the DML execution provider API pointer (ortDmlApi) rather than a C++ SessionOptions member function, while the C free function remains deprecated. [6] If you want a concrete code snippet for your exact language binding (C API vs C++ wrapper vs Python) and platform (Windows for DML, Linux/Windows for CUDA), tell me which one you’re using and whether you want provider priority CUDA first or DML first.

Citations:


🌐 Web query:

"provider_options" "DirectML" "ONNX Runtime" "C++" "AppendExecutionProvider"

💡 Result:

To use the DirectML execution provider in ONNX Runtime with C++, you should avoid the deprecated free function OrtSessionOptionsAppendExecutionProvider_DML and instead use the OrtDmlApi via the GetExecutionProviderApi method [1][2][3]. ### Recommended Approach The modern, recommended approach is to access the DirectML-specific API through the ONNX Runtime C API. This provides access to more advanced functions like SessionOptionsAppendExecutionProvider_DML (the non-deprecated version) and SessionOptionsAppendExecutionProvider_DML2 [1][4][3]. 1. Retrieve the DML API: Use Ort::GetApi().GetExecutionProviderApi to obtain a pointer to the OrtDmlApi structure [4][5][3]. 2. Configure Session Options: You must set specific session options for DirectML to function correctly: * Set execution mode to sequential: session_options.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL); [6][2][3] * Disable memory pattern: session_options.DisableMemPattern(); [6][2][3] 3. Append the Provider: Use the retrieved OrtDmlApi to call the append function [4][5][3]. ### C++ Code Example cpp `#include` <onnxruntime_cxx_api.h> `#include` <dml_provider_factory.h> // 1. Get the DirectML API const OrtApi& ortApi = Ort::GetApi(); const OrtDmlApi* ortDmlApi = nullptr; Ort::ThrowOnError(ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast<const void>(&ortDmlApi))); // 2. Setup Session Options Ort::SessionOptions sessionOptions; sessionOptions.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL); sessionOptions.DisableMemPattern(); // 3. Append the DirectML Execution Provider // For basic usage (device index 0): Ort::ThrowOnError(ortDmlApi->SessionOptionsAppendExecutionProvider_DML(sessionOptions, 0)); // Alternatively, for advanced configuration (e.g., High Performance/GPU filter): // OrtDmlDeviceOptions device_options; // device_options.Preference = OrtDmlPerformancePreference::HighPerformance; // device_options.Filter = OrtDmlDeviceFilter::Gpu; // Ort::ThrowOnError(ortDmlApi->SessionOptionsAppendExecutionProvider_DML2(sessionOptions, &device_options)); // Create session Ort::Session session(env, L"model.onnx", sessionOptions); ### Key Differences * Deprecated: The free function OrtSessionOptionsAppendExecutionProvider_DML found in dml_provider_factory.h is deprecated [1][3]. While it may still work in some versions, it is not recommended for new development [3]. * Modern API: SessionOptionsAppendExecutionProvider_DML (via OrtDmlApi) is the current standard [1][3]. * Advanced Features: SessionOptionsAppendExecutionProvider_DML2 allows for more fine-grained control, such as specifying performance preferences (e.g., high power, low power) and device filtering (e.g., GPU vs. NPU) [1][4].

Citations:


🌐 Web query:

ONNX Runtime v1.22.1 v1.23 AppendExecutionProvider DirectML provider_options C++ "DML"

💡 Result:

In ONNX Runtime (including recent versions like 1.22.1 and 1.23), the DirectML (DML) Execution Provider is configured in C++ primarily through the dedicated OrtDmlApi interface rather than the generic AppendExecutionProvider method used by some other providers [1][2][3]. To use the DirectML Execution Provider, you must interact with the dml_provider_factory.h header and the OrtDmlApi [4][5][2]. Recommended C++ Integration Pattern: 1. Obtain the DML API: Use GetExecutionProviderApi to retrieve the OrtDmlApi struct, which provides access to the DML-specific functions [1][2][3]. cpp const OrtApi& ortApi = Ort::GetApi(); const OrtDmlApi* ortDmlApi = nullptr; ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast<const void>(&ortDmlApi)); 2. Configure Session Options: DirectML requires specific session settings. You must disable memory pattern optimizations and set the execution mode to sequential [4][1][2]. cpp Ort::SessionOptions sessionOptions; sessionOptions.SetExecutionMode(ExecutionMode::ORT_SEQUENTIAL); sessionOptions.DisableMemPattern(); 3. Append the DirectML Provider: Use the recommended SessionOptionsAppendExecutionProvider_DML function provided by OrtDmlApi [1][2]. cpp // Append DML provider for device index 0 (default adapter) ortDmlApi->SessionOptionsAppendExecutionProvider_DML(sessionOptions, 0); Advanced Configuration: If you need more control, such as specifying performance preferences (e.g., high power, low power) or hardware device filtering (GPU/NPU), dml_provider_factory.h exports SessionOptionsAppendExecutionProvider_DML2 [5]. This function accepts an OrtDmlDeviceOptions struct, allowing for more granular hardware selection and power management than the basic device index [5]. Note on Deprecation: While older tutorials may reference OrtSessionOptionsAppendExecutionProvider_DML (a free function), it is deprecated [5][1][2]. The SessionOptionsAppendExecutionProvider_DML method accessed via OrtDmlApi is the current standard for C++ implementations [1][2]. Memory Management: Because DirectML operates directly on D3D12 resources, ensure your application manages the lifecycle of any custom D3D12 devices or command queues passed through advanced DML APIs (e.g., SessionOptionsAppendExecutionProvider_DML1), as the inference session will maintain references to these objects [4][5].

Citations:


🌐 Web query:

ONNX Runtime C++ API generic provider_options provider registration ProviderName map AppendExecutionProvider V2 AppendExecutionProvider_CUDA CoreML DirectML DML

💡 Result:

In ONNX Runtime (ORT), execution provider (EP) registration and configuration via the C++ API are managed through the Ort::SessionOptions class, which provides specialized methods for standard EPs and a generic interface for others [1][2]. Generic Provider Registration For EPs not having dedicated Append... methods, use the generic AppendExecutionProvider method. This requires the provider name and a map of options: session_options.AppendExecutionProvider("ProviderName", {{"option_key", "option_value"}}); [1][2] Advanced/Plugin Registration (AppendExecutionProvider_V2) For plugin EPs or scenarios requiring precise hardware device selection (such as specific NPU devices), AppendExecutionProvider_V2 is used [3][4]. This method works with Ort::Env::GetEpDevices() to retrieve available hardware devices and accepts a map of key-value pairs for configuration [3][5]. 1. Enumerate available devices: std::vectorOrt::ConstEpDevice devices = env.GetEpDevices; [3][5] 2. Select desired devices, then append: session_options.AppendExecutionProvider_V2(env, {selected_devices}, ep_options); [3][5] CUDA and Specialized EPs Dedicated methods exist for EPs like CUDA, TensorRT, and others, often supporting both original and "V2" versions of option structures [2][6]. - CUDA: Use AppendExecutionProvider_CUDA (for OrtCUDAProviderOptions) or AppendExecutionProvider_CUDA_V2 (for OrtCUDAProviderOptionsV2) [2][6]. - CoreML/DirectML: While specialized APIs may vary by version and platform, general provider options are passed via the standard AppendExecutionProvider or session-specific configuration methods [4][7]. For DirectML (DML), typically one uses the generic AppendExecutionProvider with appropriate key-value pairs if not using the specific Windows ML helper methods [4][5]. Key API Notes: - V2 methods: Generally introduce updated option structures (e.g., OrtCUDAProviderOptionsV2) that allow for more flexible, string-based, or structured updates to provider configurations [2][8][6]. - API Header: These methods are defined in onnxruntime_cxx_api.h [2][9]. - Consistency: While AppendExecutionProvider is the most generic, always check the specific documentation for an EP to see if a dedicated Append... function is provided, as these offer type-safe configuration structs [2][8].

Citations:


🌐 Web query:

ORTHelper ProviderName provider_options parser DirectML DML SessionOptionsAppendExecutionProvider_DML C++ provider_options

💡 Result:

In ONNX Runtime (ORT), the configuration of execution providers (EPs) like DirectML (DML) via provider_options is handled through specific APIs depending on your programming language and the specific requirements of the provider [1][2]. DirectML C++ Configuration For the DirectML execution provider in C++, you should generally use the SessionOptionsAppendExecutionProvider_DML method available via the OrtDmlApi [3][4]. This is the recommended, non-deprecated way to append the DML provider and configure it [3][4]. 1. Obtain the OrtDmlApi: Use GetExecutionProviderApi from the primary OrtApi [3][5]. 2. Append the provider: Use SessionOptionsAppendExecutionProvider_DML on your SessionOptions object [3][5]. Example C++ usage: const OrtApi& ortApi = Ort::GetApi; const OrtDmlApi* ortDmlApi = nullptr; ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast(&ortDmlApi)); Ort::SessionOptions sessionOptions; // Configure session options... ortDmlApi->SessionOptionsAppendExecutionProvider_DML(sessionOptions, device_id); Note: The older free function OrtSessionOptionsAppendExecutionProvider_DML is deprecated [4][6]. For advanced configurations (like specifying performance preferences or device filters), SessionOptionsAppendExecutionProvider_DML2 is available [4]. ProviderOptions and Parsing While some higher-level ORT bindings (like Python or GenAI) support passing a dictionary/map of provider_options directly [7][2][8], the core C++ engine uses a specific mechanism for parsing these options [9][10]. - ProviderOptionsParser: This class (found in onnxruntime::ProviderOptionsParser) is used internally within ORT to register and parse provider-specific options [9][10]. It allows you to add value parsers, assignment to references, or enum mappings to handle string-based configuration values during the initialization of the execution provider [9][11]. - "ORTHelper": This appears to be a context-specific or internal reference, likely related to how specific language bindings or wrappers (like Objective-C/Swift) bridge the gap between their dictionary-based options and the C++ SessionOptions::AppendExecutionProvider interface [1][12]. For example, the Objective-C API translates an NSDictionary of provider options into a C++ std::unordered_map<std::string, std::string> before calling AppendExecutionProvider [12]. If you are encountering difficulty configuring specific DML options (like metacommands) in GenAI, it is often because the GenAI configuration layer needs to explicitly support passing those keys through to the underlying ORT DML provider [13]. Not all ORT provider options are automatically exposed in all higher-level config files [13].

Citations:


Use the DirectML-specific API instead of generic options.

AppendExecutionProvider("DML", {}) does not match the DirectML provider options supported by cmake/OnnxRuntime.cmake versions. On V1.20.1, use the DirectML API/low-level SessionOptionsAppendExecutionProvider_DML(...), and configure the required DirectML session options; this call can throw and fail back to CPU because no DirectML provider is registered.

🤖 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/OnnxRuntimeSettings.cpp` around lines 305 - 312, Replace the generic
AppendExecutionProvider("DML", {}) call in the Windows non-MinGW32 conditional
block with the DirectML-specific low-level API
SessionOptionsAppendExecutionProvider_DML(...) and configure the required
DirectML session options according to the V1.20.1 build configuration in
cmake/OnnxRuntime.cmake. Keep the existing try-catch around this call since it
can throw and should fall back to CPU when the DirectML provider is not
registered.

fernandotonon and others added 3 commits August 4, 2026 09:35
Free function in an anonymous namespace could not access private Impl;
move unregisterSkinningListener onto Impl so MocapController call sites compile.

Co-authored-by: Cursor <cursoragent@cursor.com>
The review fix skipped the head bone entirely in the body setup loop, which
also skipped zeroing its animation blend mask — idle clips could fight manual
drive. Still exclude head from bodyManualRestore (preserve headWasManuallyControlled)
but zero blend masks on all bones; track skinning listener registration.

Co-authored-by: Cursor <cursoragent@cursor.com>
Expose bodyCalibrationHint on MocapController and surface it in the
Performance Capture panel while torso neutral is being captured.

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

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Mocap/MocapController.cpp (2)

913-922: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Record the startup timeout cause.

This path shows a user-facing error and stops preview, but it does not record why preview stopped. Add a breadcrumb before stopPreview() that identifies the no-frame timeout.

As per coding guidelines, “Track all user-facing actions and significant operations with SentryReporter::addBreadcrumb.”

Proposed change
         emit errorOccurred(d->status);
+        SentryReporter::addBreadcrumb(
+            "ai.assist.mocap_live", "preview start timeout: no frames");
         stopPreview();
🤖 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/Mocap/MocapController.cpp` around lines 913 - 922, Add a
SentryReporter::addBreadcrumb call in the cameraStartupTimer timeout handler
after emitting errorOccurred and immediately before stopPreview(), recording
that preview stopped because no frames arrived during camera startup. Keep the
existing user-facing status message and shutdown flow unchanged.

Source: Coding guidelines


772-805: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restore all body-drive overrides on the pose-model fallback.

Lines 774-805 set every non-head bone to manual control and zero every animation blend-mask entry. The fallback restores only bodyBones, which excludes non-canonical bones, and does not restore bodyAnimMaskRestore. A face/head-only fallback retains body-drive overrides until preview stops. Restore the complete manual-bone and blend-mask snapshots here before clearing bodyRetargeter. Preserve the head-bone setup.

Also applies to: 876-881

🤖 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/Mocap/MocapController.cpp` around lines 772 - 805, Update the pose-model
fallback cleanup near the body-drive teardown, including the analogous path
around the secondary location, to restore every snapshot in bodyManualRestore
rather than only canonical bodyBones, and restore each saved entry in
bodyAnimMaskRestore before clearing bodyRetargeter. Preserve the existing
head-bone setup and ensure non-canonical bones and animation blend-mask values
return to their captured states.
🤖 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/Mocap/MocapController.cpp`:
- Around line 775-779: Move the body manual-bone snapshot logic around the
preview reset so it captures each bone’s pre-reset orientation and
manual-control state before calling skel->reset(true). After the reset, use the
post-reset orientation when populating BodyDriveBone bind data, and include the
manually controlled head bone in the same pre-reset bodyManualRestore capture so
restoreEntityState() can restore all original controls.

---

Outside diff comments:
In `@src/Mocap/MocapController.cpp`:
- Around line 913-922: Add a SentryReporter::addBreadcrumb call in the
cameraStartupTimer timeout handler after emitting errorOccurred and immediately
before stopPreview(), recording that preview stopped because no frames arrived
during camera startup. Keep the existing user-facing status message and shutdown
flow unchanged.
- Around line 772-805: Update the pose-model fallback cleanup near the
body-drive teardown, including the analogous path around the secondary location,
to restore every snapshot in bodyManualRestore rather than only canonical
bodyBones, and restore each saved entry in bodyAnimMaskRestore before clearing
bodyRetargeter. Preserve the existing head-bone setup and ensure non-canonical
bones and animation blend-mask values return to their captured states.
🪄 Autofix

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 Plus

Run ID: 7e6f8541-7fe8-49ae-9e29-2471adfa1157

📥 Commits

Reviewing files that changed from the base of the PR and between a21a840 and c217684.

📒 Files selected for processing (4)
  • qml/PropertiesPanel.qml
  • src/AnimationMerger.cpp
  • src/Mocap/MocapController.cpp
  • src/Mocap/MocapController.h
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Mocap/MocapController.h
  • qml/PropertiesPanel.qml
  • src/AnimationMerger.cpp

Comment on lines +775 to +779
BodyManualBoneSnapshot snap;
snap.boneName = bone->getName();
snap.bindLocal = bone->getOrientation();
snap.wasManuallyControlled = bone->isManuallyControlled();
d->bodyManualRestore.push_back(std::move(snap));

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

# Inspect reset, snapshot, and restoration ordering.
rg -n -C 8 'skel->reset\(true\)|bodyManualRestore|headBindLocal|restoreEntityState' \
  src/Mocap/MocapController.cpp

# Inspect vendored OGRE headers when present.
fd -a -t f 'OgreSkeleton*.h' . -E build -E .git |
  xargs -r rg -n -C 4 'reset\s*\(\s*bool\s+resetManualBones'

Repository: fernandotonon/QtMeshEditor

Length of output: 9396


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map structure and nearby implementation details.
ast-grep outline src/Mocap/MocapController.cpp --match 'void MocapController::startMocapPreview' --view expanded || true
ast-grep outline src/Mocap/MocapController.cpp --match 'struct BodyManualBoneSnapshot' --view expanded || true

# Locate OGRE includes/usage around reset.
rg -n -C 12 '`#include` ["<].*OgreSkeleton.*["<]|reset\s*\(\s*true\s*\)|getSkeleton\(\s*\)|SKELETON_\w+|manualBone|_notifyManualBonesDirty|_getManualBone' src include third_party . --glob '!build' --glob '!build/*' --glob '!out' --glob '!*.pyc' | head -n 240

# Search any explicit OGRE reset documentation/comments in repository.
rg -n -C 5 'resetManualBones|manual bones|manualBone|manual bone|SKELETON_RESET_MANUAL_BONE|reset\(true\)' . --glob '!build' --glob '!build/*' --glob '!out' | head -n 240

Repository: fernandotonon/QtMeshEditor

Length of output: 17650


🌐 Web query:

OGRE 14 SkeletonInstance reset resetManualBones manual bones binding pose documentation

💡 Result:

In OGRE 14, the SkeletonInstance class inherits the reset method from its base class, Ogre::Skeleton [1][2]. This method is used to return all bones in a skeleton to their original binding pose [1][2]. The reset method has the following signature: void Ogre::Skeleton::reset(bool resetManualBones = false) Parameters: resetManualBones: When set to false (the default), manual bones are excluded from the reset process, allowing their state to persist even when keyframe animation is applied [1][2]. If set to true, the state of manual bones is also reset to the binding pose [1][2]. Key Concepts: Binding Pose: This is the default state in which bones are bound to a mesh. Animations are stored as relative transformations (deltas) from this pose [3][2]. Manual Bones: These are bones designated for manual manipulation by the application at runtime [4][5]. You can set a bone as manually controlled using the Ogre::Bone::setManuallyControlled(bool) method [4][5]. Because manual bones are typically handled by custom code, Ogre's animation routines generally do not reset them during normal animation updates or standard resets, unless specifically requested via the resetManualBones parameter [1][4][2]. If you are using manual bones, ensure there are no AnimationTrack objects referencing them, or disable those tracks, to prevent conflicts between your manual adjustments and scripted animations [4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant declarations and control-flow slices without running repository code.
rg -n -C 3 'struct|class BodyManualBoneSnapshot|struct BodyDriveBone|void MocapController::stopMocapPreview|MocapController::startMocapPreview|startMocapPreview\(' src/Mocap/MocapController.cpp

sed -n '130,190p' src/Mocap/MocapController.cpp
sed -n '680,800p' src/Mocap/MocapController.cpp
sed -n '1320,1365p' src/Mocap/MocapController.cpp
sed -n '1258,1288p' src/Mocap/MocapController.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 13221


Snapshot manual bones before calling skel->reset(true).

skel->reset(true) resets manual bones to their binding pose, so the bodyManualRestore snapshots record the reset pose instead of the pre-preview pose. restoreEntityState() cannot restore the original manual controls after preview stops. Capture manual bone state before resetting, then use the post-reset orientation for BodyDriveBone bind data. Include the head bone in that same pre-reset capture when the head is manually controlled.

🤖 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/Mocap/MocapController.cpp` around lines 775 - 779, Move the body
manual-bone snapshot logic around the preview reset so it captures each bone’s
pre-reset orientation and manual-control state before calling skel->reset(true).
After the reset, use the post-reset orientation when populating BodyDriveBone
bind data, and include the manually controlled head bone in the same pre-reset
bodyManualRestore capture so restoreEntityState() can restore all original
controls.

@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

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.

1 participant