Fix first-frame RTX synchronization after tensor pose writes - #7138
Fix first-frame RTX synchronization after tensor pose writes#7138StriverAlex wants to merge 5 commits into
Conversation
Run the PhysX update path from forward() while suppressing IsaacLab step callbacks so rendering receives tensor-written rigid-body poses without exposing a public physics step. Constraint: Preserve SimulationContext.forward() zero-step semantics. Rejected: Move reset warmup after play | It does not sync later tensor pose writes. Rejected: Use tensor or Fabric flush APIs | Isaac Sim 6.0 did not update RTX output. Confidence: high Scope-risk: moderate Directive: Keep internal forward updates invisible to physics-step callbacks. Tested: RTX first-frame regression, SimulationContext tests, renderer utility tests, and full pre-commit. Not-tested: Full repository pytest suite.
Record the public physics-step epoch only when a rigid-object pose is written. Render performs the internal PhysX sync only while that epoch is still current, so an intervening public step consumes the pending work without adding a second update. Constraint: Preserve latest develop normal-step and clean-render performance. Rejected: Clear a boolean on every PhysX step | It adds work to the dominant develop hot path. Confidence: high Scope-risk: moderate Directive: Keep update_simulation gated by a pending rigid-pose write and skip it after a public step. Tested: pre-commit; develop-negative RTX regression; RTX first-frame regression; native callback regression; clean-render and dirty-mark microbenchmarks. Not-tested: Full SimulationContext suite is blocked by the container Newton API mismatch and a later Kit process segfault.
Confine native PhysX synchronization to a step-stamped write barrier consumed at actual Kit frame boundaries, and cover every tensor pose writer plus real first-frame rendering. Constraint: Clean forward and normal physics-step throughput must remain unchanged. Rejected: Unconditional native update in forward | It taxes every forward and violates the clean-path callback invariant. Rejected: Generic dirty state or manager-owned forward | It leaks backend policy or duplicates forwarding work. Confidence: high Scope-risk: moderate Directive: Keep update_simulation gated to pending tensor pose writes at a frame-producing Kit update. Tested: 25 writer/renderer/visualizer tests; 2 native callback tests; 4 CUDA writer tests; RTX red/green regression; Ruff; develop-candidate-develop ABA benchmark. Not-tested: Full repository suite.
Greptile SummaryThis PR adds a step-stamped PhysX tensor-pose synchronization barrier so same-step pose writes are propagated before the next Kit/RTX frame without adding work to normal physics or clean rendering paths.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; both previously reported stale-frame paths now check pending backend work and perform synchronization before producing or sampling the next Kit frame. Important Files Changed
Sequence DiagramsequenceDiagram
participant Writer as Tensor pose writer
participant PhysX as PhysxManager
participant Owner as Renderer / KitVisualizer
participant Fabric as Fabric
participant Kit as Kit app
Writer->>PhysX: notify_tensor_pose_write()
PhysX->>PhysX: Record current public-step token
Owner->>PhysX: has_pending_kit_app_update()
PhysX-->>Owner: true
Owner->>PhysX: before_kit_app_update()
PhysX->>PhysX: update_simulation()
PhysX-->>Owner: requires re-forward
Owner->>Fabric: forward()
Owner->>Kit: app.update()
Kit-->>Owner: RTX frame with updated pose
Reviews (3): Last reviewed commit: "Keep post-pump pose writes visible to Ki..." | Re-trigger Greptile |
There was a problem hiding this comment.
Isaac Lab Review Bot
Reviewed the step-stamped PhysX tensor-pose write barrier, including all six writer notification paths, token coalescing and lifecycle, and consumption at Kit/RTX frame boundaries. The proposed concern that update_simulation(dt, 0.0) advances physics is not established by the patch: the public step count remains unchanged, and native callback emission alone does not demonstrate simulation advancement or rendered/headless divergence.
- Design and architecture: The backend-specific synchronization obligation remains private to
PhysxManager, while the additive backend-neutral hook lets frame owners request synchronization without burdening normal physics steps or clean rendering paths. The renderer utility and Kit visualizer both consume the hook at an actual app-update boundary. - API:
PhysicsManager.before_kit_app_update() -> boolis an additive, documented default-no-op API. Both consumers honor its boolean re-forward contract, existing tensor writer signatures remain unchanged, and package changelog coverage is present. - Implementation: The six PhysX tensor pose implementations notify only after their native transform write, writes coalesce by public physics-step epoch, stale tokens are dropped after a real step, failed synchronization is requeued, and lifecycle resets clear pending state. Kit playback state is restored with
try/finally. Native pre/post callback emission during the synchronization is an observable tradeoff covered by tests, but the supplied finding does not establish that it advances simulation state or identify a broken callback consumer; using zero elapsed time is likewise not shown to preserve the required propagation.
No blocking issues. No inline issue met the actionable-evidence threshold; the assessment above records the review feedback.
Automated review; human maintainers own approval decisions.
Expose a backend-neutral pending-work predicate so frame owners bypass existing no-op paths only while a tensor-pose synchronization obligation remains. Constraint: Preserve the clean new-frame path and existing camera deduplication behavior Rejected: Consume before_kit_app_update to probe pending work | that would mutate backend state before deciding to produce a frame Confidence: high Scope-risk: narrow Directive: Keep before_kit_app_update consumption coupled to an actual frame-producing Kit update Tested: Ruff 0.14.10; 26 writer/renderer/visualizer tests; 2 native lifecycle tests; 1 real CUDA/RTX test; PhysX ABA benchmark Not-tested: Full repository suite
Invalidate an already-pumped Kit frame only when the active physics backend reports pending synchronization work, preserving clean recorder frame reuse. Constraint: Preserve clean recorder frame reuse and normal visualization cadence Rejected: Always pump render_rgb_array | adds unnecessary Kit work to every capture Confidence: high Scope-risk: narrow Directive: Treat backend pending work as invalidating app-pumped deduplication state Tested: Kit visualizer 6 passed; combined writer/RTX/visualizer 27 passed; native lifecycle 2 passed; real CUDA/RTX first-frame 1 passed; Ruff 0.14.10 and diff-check passed Not-tested: Full repository suite
Description
This is the restored replacement for #6661. The original pull request became unreopenable after the author fork was deleted and recreated; its reviewed implementation history was preserved, rebased onto current
develop, and pushed from the new fork identity.PhysX tensor pose writers can change simulation state without advancing the public physics step. RTX/Fabric may therefore render the previous transform in the first frame after a write.
This change keeps the normal physics and clean rendering paths lightweight. It introduces a step-stamped tensor-pose write barrier that is consumed only at an actual Kit/RTX frame boundary.
Fixes #6394.
Root cause and design
PhysicsManager.before_kit_app_update() -> boolprovides a backend-neutral frame-boundary hook. The default implementation is a no-op.PhysicsManager.has_pending_kit_app_update() -> boollets a frame owner bypass an existing dedup/visualizer early return without consuming backend state. The default isFalse.PhysxManagerrecords a private pending token only after a tensor pose mutation succeeds.update_simulation()and consumes the token.forward()is required beforeapp.update().try/finally.The resulting invariants are:
SimulationContext.forward()does not call nativeupdate_simulation().Tensor writer coverage
The barrier is notified by all six native tensor pose implementations:
Root/body-pose, indexed, and mask-based public APIs are covered through their existing delegation to these implementations.
Review concerns addressed
The performance regression reviewed on the original PR came from placing the full native PhysX update in
forward(), which taxed every call. This revision removes that behavior entirely. Synchronization is local to the PhysX backend, causally tied to a successful tensor pose write, keyed to the public physics-step epoch, and consumed only by a frame-producing Kit update.The Greptile review on this replacement PR identified two same-step stale-frame paths. Commit
06113203badds a backend-neutral pending-work predicate and makes RTX renderer dedup conditional on there being no pending synchronization. Commit79be514e0applies the same invariant to Kit visualizer capture reuse: a clean post-step capture still reuses the frame, while a pose write after that pump invalidates the reuse and performs the existing prepare/forward/update sequence. The predicate is read-only;before_kit_app_update()remains coupled to an actual Kit update.This is deliberately not a general-purpose manager dirty flag: the state models one backend-specific synchronization obligation and remains private to
PhysxManager.Current-develop validation
Rebased onto
developatcd7ea42ae27a95e955a2d317dac72228e09692d7. Current head:79be514e056a91770ce149e790e4067cab311355.469b81480a74655032c03d1deb45569ff21ebd18.developfailed as expected: first-frame red fraction0.000000, stable red fraction0.010468.0.14.10lint and formatting checks passed for all changed Python files.git diff --checkpassed.Current-develop performance ABA
The same Isaac Sim container command was run in develop-candidate-develop order with
Isaac-Cartpole-Direct, 4096 environments, 500 measured steps, 100 warmup steps, the Kit visualizer, and explicitpresets=physxbecause the task defaults have recently changed toward Newton.The two develop endpoints differed by
0.2114%. Relative to their arithmetic mean, the candidate measured-0.0477%FPS and+0.0476%iteration time, both well inside baseline drift and far from the previously reviewed+11%regression. The host CPU governor reportedpowersave, so the bracketed comparison is used instead of interpreting a single absolute run.One develop-post launch aborted in NVIDIA RTCore/Vulkan before sampling and was rerun after confirming there was no stale benchmark GPU process; no metric from the failed launch is included.
Type of change
Checklist
forward()and normal physics-step behavior.