#1176 Show Physics Shape When Editing (eye on Edit > Features), drawn on the GPU #1181

Closed
TheSlowly wants to merge 8 commits from TheSlowly/Slipstream:edit-physics-view-1176 into main
Collaborator

Fixes #1176: an eye beside Physics Shape Type (and Build > Options > Show Physics Shape When Editing) draws the physics shapes of the selected objects while editing.

Rewritten as agreed in the review: GPU-driven, with no per-frame CPU work.

  • Storage: each shape is built once into the clean mesh store as a raw slot, refcounted under a 16 MB LRU. 4,096 rows at most; both limits are constants.
  • Updates: rows are rebuilt only when the selection, a shape or a cost changes.
  • Drawing: a compute pass fills or empties each indirect draw. There are four pipelines on the selection base (hidden and visible, fill and edges) with one shared depth bias; the shared overlay bias change is gone.
  • Pose: each draw uses the object's own instance row, so drags, spin and worn attachments on their joint follow.
  • Rigged mesh: rezzed rigged mesh and animesh are drawn at the object's static pose, which is the one the server collides with; animesh gets a pose-only row. Worn rigged mesh, HUDs and glTF scene objects get no shape.
  • Colours: 64 steps of LL's cost ramp; magenta means no hull.
  • Failures: a failed build is counted by reason and logged with its key, rate-limited.

Tested in-world on a linked mesh build: placeholders while the physics data downloads, then the hulls.

Fixes #1176: an eye beside Physics Shape Type (and Build > Options > Show Physics Shape When Editing) draws the physics shapes of the selected objects while editing. Rewritten as agreed in the review: GPU-driven, with no per-frame CPU work. - **Storage:** each shape is built once into the clean mesh store as a raw slot, refcounted under a 16 MB LRU. 4,096 rows at most; both limits are constants. - **Updates:** rows are rebuilt only when the selection, a shape or a cost changes. - **Drawing:** a compute pass fills or empties each indirect draw. There are four pipelines on the selection base (hidden and visible, fill and edges) with one shared depth bias; the shared overlay bias change is gone. - **Pose:** each draw uses the object's own instance row, so drags, spin and worn attachments on their joint follow. - **Rigged mesh:** rezzed rigged mesh and animesh are drawn at the object's static pose, which is the one the server collides with; animesh gets a pose-only row. Worn rigged mesh, HUDs and glTF scene objects get no shape. - **Colours:** 64 steps of LL's cost ramp; magenta means no hull. - **Failures:** a failed build is counted by reason and logged with its key, rate-limited. Tested in-world on a linked mesh build: placeholders while the physics data downloads, then the hulls.
An eye beside the Physics Shape Type combo (and Build > Options > Show Physics Shape When
Editing) turns on ShowPhysicsShapeInEdit, now persisted. While the edit tool is up,
LLPipeline::renderSelectionPhysicsVK draws the physics shape of every selected prim (whole
linksets when selected that way, worn attachments too, not HUDs) from the world-overlay
segment, before renderSelections so the manipulators stay on top. It reuses the Physics
Shapes overlay's code: Firestorm's see-through pass first (depth GREATER, alpha x0.4: the
shape's far side and the parts inside its object or behind other geometry), then the filled
and edge passes, edges 3 px.

Attachments: VKObjXform placed every prim by its object state, which for an attachment is
relative to its attach point (a child's to its root), so it would draw near the avatar
instead of on the joint. It now takes the rendered pose (vkComposeAttachmentMatrix, the one
picking intersects). Attachment tests go through the linkset root, since only the root
carries the attachment state; the Render Metadata overlay used to let attachment children
through and draw them in the wrong place.

The edges are pulled 0.05% of the way toward the eye: the overlay's depth bias only
applies to triangles, and a shape's edges lie on its own prim's surface.

The toggle is a view setting, not an object property, so LLPanelVolume::clearCtrls (which
disables every control in the panel on a selection change) leaves it enabled, as in Firestorm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overlay segment biased only the LESS/LEQUAL "visible" tests toward the camera. A visible
pass plus an unbiased GREATER "hidden" pass over the same geometry both passed the band just
behind the scene depth, so a surface lying on the scene (a physics shape on its own prim)
drew in both, and the dim see-through colour blotched over the visible fill. With the same
bias on GREATER/GEQUAL the boundary moves for both passes and each fragment draws exactly
once. The other hidden passes (translate manipulator, parcel walls, avatar joints) lose the
same double-drawn band.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner

Review (Sky + Claude): changes requested — not mergeable yet.

Blocker, GPU-driven rule: pipeline.cpp:1829 builds a selection vector on every rendered overlay frame, :1845 culls and sorts it on the CPU, then :1852 runs four physics-shape passes; vkPhysicsShapePass (:1723) loops objects and vkPushVolume (:1484) walks faces/triangles and emits transformed vertices on the CPU through gGL. That turns a debug-only overlay into a per-frame CPU geometry path while editing, which the renderer rules forbid for new paths (persistent instance data + GPU culling + indirect draws; see doc/DEVELOPER_GUIDE.md section 5).

What it needs: keep physics geometry in bounded persistent stores, update only selection / transform / shape deltas, classify and draw on the GPU (indirect), with soft handling of allocation / decomposition failures. Keep the attachment-transform / HUD exclusion and the hidden/visible overlay semantics. Check the depth-bias change against the existing GREATER/GEQUAL overlays (reverse-Z and ortho) since it touches shared overlay ordering. This probably wants a short design note before the rewrite — happy to look at one.

Naming: the commit subject and PR text name another viewer; please describe the feature without naming other viewers (repo rule; credit goes in the release notes / About). ShowPhysicsShapeInEdit as a persisted preference is fine.

Review (Sky + Claude): **changes requested** — not mergeable yet. **Blocker, GPU-driven rule:** `pipeline.cpp:1829` builds a selection vector on every rendered overlay frame, `:1845` culls and sorts it on the CPU, then `:1852` runs four physics-shape passes; `vkPhysicsShapePass` (`:1723`) loops objects and `vkPushVolume` (`:1484`) walks faces/triangles and emits transformed vertices on the CPU through gGL. That turns a debug-only overlay into a per-frame CPU geometry path while editing, which the renderer rules forbid for new paths (persistent instance data + GPU culling + indirect draws; see doc/DEVELOPER_GUIDE.md section 5). **What it needs:** keep physics geometry in bounded persistent stores, update only selection / transform / shape deltas, classify and draw on the GPU (indirect), with soft handling of allocation / decomposition failures. Keep the attachment-transform / HUD exclusion and the hidden/visible overlay semantics. Check the depth-bias change against the existing GREATER/GEQUAL overlays (reverse-Z and ortho) since it touches shared overlay ordering. This probably wants a short design note before the rewrite — happy to look at one. **Naming:** the commit subject and PR text name another viewer; please describe the feature without naming other viewers (repo rule; credit goes in the release notes / About). `ShowPhysicsShapeInEdit` as a persisted preference is fine.
Author
Collaborator

Thanks, agreed on all points. Here's the plan for the rewrite:

  • Geometry: each physics shape is built once (the PR's shape code becomes a mesh builder) and uploaded as a raw slot with captureRawSlot, like the system bodies. Shapes are refcounted under a 16 MB LRU. A failed build retries, then shows a magenta box.
  • Per frame: no CPU work. The draw list is rebuilt only when the selection, a shape or an object's cost changes, capped at 4,096. Each draw uses the object's own instance row, so drags, spin and attachments on joints come for free.
  • GPU: a small compute fills or empties each indirect draw, and there is one indirect draw per colour.
  • Passes: four pipelines on the selection pipelines' base: see-through and visible, each as fill and as edges in line mode. They share one depth bias, and the pipeline handles reverse-Z and ortho. The shared overlay bias change (29ee8ef03a) is reverted, so other overlays are untouched.
  • Calls made:
    • HUDs, rigged attachments and objects without an instance row are skipped.
    • Hull builds run on the render thread under a per-frame budget.
    • There's no compute frustum test; 4,096 rows is cheap.
    • Shapes still draw with Hide Selection on.
    • Develop > Physics Shapes is unchanged.
  • Naming: the other viewer's name goes from the commit text.

Unless you see a problem, I'll go ahead with this.

Thanks, agreed on all points. Here's the plan for the rewrite: - **Geometry:** each physics shape is built once (the PR's shape code becomes a mesh builder) and uploaded as a raw slot with `captureRawSlot`, like the system bodies. Shapes are refcounted under a 16 MB LRU. A failed build retries, then shows a magenta box. - **Per frame:** no CPU work. The draw list is rebuilt only when the selection, a shape or an object's cost changes, capped at 4,096. Each draw uses the object's own instance row, so drags, spin and attachments on joints come for free. - **GPU:** a small compute fills or empties each indirect draw, and there is one indirect draw per colour. - **Passes:** four pipelines on the selection pipelines' base: see-through and visible, each as fill and as edges in line mode. They share one depth bias, and the pipeline handles reverse-Z and ortho. The shared overlay bias change (29ee8ef03a) is reverted, so other overlays are untouched. - **Calls made:** - HUDs, rigged attachments and objects without an instance row are skipped. - Hull builds run on the render thread under a per-frame budget. - There's no compute frustum test; 4,096 rows is cheap. - Shapes still draw with Hide Selection on. - Develop > Physics Shapes is unchanged. - **Naming:** the other viewer's name goes from the commit text. Unless you see a problem, I'll go ahead with this.
Owner

Go ahead. The plan fits the renderer's rules: shapes built once into shared, bounded storage, no per-frame CPU work, indirect draws filled on the GPU, the object's own instance row reused, and the shared overlay bias change reverted.

Two small asks:

  • Log a count of failed hull builds (shown as the magenta box) with a reason, rate-limited, so we can tell a bad asset from a bug.
  • Keep the 4,096 draw cap and the 16 MB budget as constants, not settings, unless an A/B is needed while testing (then Persist=0).

Thanks!

Go ahead. The plan fits the renderer's rules: shapes built once into shared, bounded storage, no per-frame CPU work, indirect draws filled on the GPU, the object's own instance row reused, and the shared overlay bias change reverted. Two small asks: - Log a count of failed hull builds (shown as the magenta box) with a reason, rate-limited, so we can tell a bad asset from a bug. - Keep the 4,096 draw cap and the 16 MB budget as constants, not settings, unless an A/B is needed while testing (then Persist=0). Thanks!
TheSlowly force-pushed edit-physics-view-1176 from 29ee8ef03a to 4e99117ebe 2026-10-29 17:21:25 +00:00 Compare
TheSlowly changed title from #1176 Show Physics Shape When Editing (eye on Edit > Features) to #1176 Show Physics Shape When Editing (eye on Edit > Features), drawn on the GPU 2026-10-29 17:21:47 +00:00
Author
Collaborator

Done and pushed (4e99117ebe, rebased on current main). It follows the plan, plus your two asks:

  • Failed builds: each magenta box is counted by reason (no decomposition library, no prim hull, too big for the budget, budget full, store refused, out of memory). Each is logged with its shape key and object, at most 5 lines per 60 s, and the 60 s stats line includes the counts.
  • Limits: the 4,096 rows and the 16 MB budget stay constexpr constants; the only setting is the user toggle.

Since the plan:

  • Colours: 64 colour steps, so the ramp reads as continuous. The colour table is built once.
  • Rezzed rigged mesh: it already has world rows, so only the skip was narrowed.
  • Animesh: gets a pose-only instance row (no geometry, kept current by the store's moved queue), so its shape stays at the static pose the server uses.

Tested in-world on a linked mesh build.

Done and pushed (4e99117ebe, rebased on current main). It follows the plan, plus your two asks: - **Failed builds:** each magenta box is counted by reason (no decomposition library, no prim hull, too big for the budget, budget full, store refused, out of memory). Each is logged with its shape key and object, at most 5 lines per 60 s, and the 60 s stats line includes the counts. - **Limits:** the 4,096 rows and the 16 MB budget stay `constexpr` constants; the only setting is the user toggle. Since the plan: - **Colours:** 64 colour steps, so the ramp reads as continuous. The colour table is built once. - **Rezzed rigged mesh:** it already has world rows, so only the skip was narrowed. - **Animesh:** gets a pose-only instance row (no geometry, kept current by the store's moved queue), so its shape stays at the static pose the server uses. Tested in-world on a linked mesh build.
Owner

Review: one small fix before merge.

llcleanphysshape.cpp ~93-104 uses the results of the two ll_aligned_malloc_16 calls without checking for null. ll_aligned_malloc_16 returns null on failure rather than throwing, so the bad_alloc catch doesn't cover it, and either failure crashes. Our rule is that allocation failure never crashes. Please allocate into locals, check both, free the partial allocation if only one succeeded, and assign the members only on success. Otherwise return; the existing !mHullPoints path already retries later.

Review: one small fix before merge. `llcleanphysshape.cpp` ~93-104 uses the results of the two `ll_aligned_malloc_16` calls without checking for null. `ll_aligned_malloc_16` returns null on failure rather than throwing, so the `bad_alloc` catch doesn't cover it, and either failure crashes. Our rule is that allocation failure never crashes. Please allocate into locals, check both, free the partial allocation if only one succeeded, and assign the members only on success. Otherwise return; the existing `!mHullPoints` path already retries later.
buildPrimHull wrote straight into the results of two ll_aligned_malloc_16 calls. That function
returns null on failure rather than throwing, so the bad_alloc catch didn't cover it, and either
failure crashed. Allocation failure must never crash.

Both buffers are now allocated into locals and checked. If either is missing, whatever was
allocated is freed and a one-time warning is logged. The members are set only on success, so
the existing !mHullPoints path tries again later.

Test-Plan: Edit a prim with Show Physics Shape When Editing on: its hull draws as before. (The
  failure path needs an allocation failure; it was checked by reading the code.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

Done in ccbbd19bd0: both buffers are allocated into locals and checked. If either fails, the one that succeeded is freed and a one-time warning is logged. The members are only set on success, so the !mHullPoints path retries later.

Done in ccbbd19bd0: both buffers are allocated into locals and checked. If either fails, the one that succeeded is freed and a one-time warning is logged. The members are only set on success, so the `!mHullPoints` path retries later.
Owner

Thanks! The full #1176 series is on main through the Beta 6 port, and main is ahead of this branch (allocation-failure-safe prim hull build and the stereo guard). Closing as landed.

Thanks! The full #1176 series is on main through the Beta 6 port, and main is ahead of this branch (allocation-failure-safe prim hull build and the stereo guard). Closing as landed.
sky_line closed this pull request 2026-11-03 00:27:31 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sky_line/Slipstream!1181
No description provided.