Skip to content

feat(gpui): rotate the camera for portrait-mounted cameras - #2778

Open
YoavLevavi wants to merge 5 commits into
CapSoftware:mainfrom
YoavLevavi:feat/gpui-camera-rotation
Open

YoavLevavi wants to merge 5 commits into
CapSoftware:mainfrom
YoavLevavi:feat/gpui-camera-rotation

Conversation

@YoavLevavi

@YoavLevavi YoavLevavi commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

GPUI port of #2375.

  • Camera::rotation (quarter turns, normalized, defaults to 0) with rotation_degrees() / swaps_axes().
  • Renderer rotates the camera feed in the composite shader and swaps the camera's aspect for 90/270.
  • GPUI: a Rotation segmented control (0° / 90° / 180° / 270°) in the editor's camera tab.

Testing: cargo test -p cap-project -p cap-rendering --lib (all pass), cargo check in apps/desktop-gpui.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; both previous findings are addressed.

Summary

Adds camera rotation at 0°, 90°, 180°, and 270°.

  • The camera feed can turn to match its mounting direction.

Both previous findings are addressed. No new issues were found.

Reviews (3) · Last reviewed commit: "Fail the rotation pixel test when the GP..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

@YoavLevavi is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

@YoavLevavi

Copy link
Copy Markdown
Contributor Author

@greptileai

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks for the GPUI port @YoavLevavi, this is nicely contained and rotation 0 stays an identity path in the shader. It now conflicts with main in crates/rendering/src/lib.rs: main replaced options.camera_size with a local camera_size taken from the current camera frame's dimensions (commit 98b5cfc, covered by crates/rendering/tests/camera_frame_geometry.rs). When you rebase, please build oriented_camera_size (lib.rs:4118) from that local camera_size rather than options.camera_size, so the live-dimension fix is kept, and use it at the three conflicting sites (split layout, camera, camera_only). Heads up that #2779 (camera crop) also adds a field after background_blur in Camera, so whichever lands second will need a trivial rebase, and it would be great to sanity check crop + rotation + mirror together once both are in. Thanks!

Camera rotation in quarter turns (project config + renderer) with a
Rotation control in the GPUI editor's camera tab.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@YoavLevavi
YoavLevavi force-pushed the feat/gpui-camera-rotation branch from bee44c5 to 1a21d1c Compare October 21, 2026 16:23
@YoavLevavi

Copy link
Copy Markdown
Contributor Author

Thanks @richiemcilroy. I rebased onto main: oriented_camera_size is now built from the live-frame camera_size (98b5cfc), so that fix is kept, and it's used at the split layout, camera and camera_only sites. cargo test -p cap-rendering passes, including camera_frame_geometry. Happy to do the crop + rotation + mirror check once #2779 lands too.

@richiemcilroy

Copy link
Copy Markdown
Member

@greptileai review

Comment on lines +422 to +425
fn rotated_frame_uv(uv: vec2<f32>) -> vec2<f32> {
let quarter_turns = u32(round(uniforms.frame_rotation / 90.0)) % 4u;
if quarter_turns == 1u {
return vec2<f32>(uv.y, 1.0 - uv.x);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Camera rotation lacks rendering tests

The added test checks number normalization and legacy defaults, while camera_frame_geometry.rs leaves rotation at zero. Neither checks the pixels produced by these new mappings, so reversed rotation or incorrect cropping can pass the current tests.

Add a rendering test with an asymmetric camera image at all four angles, including a rectangular source and mirroring. This is a non-blocking coverage improvement.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/rendering/src/shaders/composite-video-frame.wgsl
Line: 422-425

Comment:
**Camera rotation lacks rendering tests**

The added test checks number normalization and legacy defaults, while `camera_frame_geometry.rs` leaves rotation at zero. Neither checks the pixels produced by these new mappings, so reversed rotation or incorrect cropping can pass the current tests.

Add a rendering test with an asymmetric camera image at all four angles, including a rectangular source and mirroring. This is a non-blocking coverage improvement.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@richiemcilroy

Copy link
Copy Markdown
Member

Hey @YoavLevavi, thanks for this one, portrait-mounted cameras are a real pain point and the quarter-turn approach is nice and simple.

Greptile's only finding this round (crates/rendering/src/shaders/composite-video-frame.wgsl:422-425) is a coverage request rather than a bug. We traced the mapping by hand and it looks consistent to us: oriented_camera_size swaps the axes before frame_size and the crop bounds are computed, mirroring is applied in oriented space, and rotated_frame_uv maps back to texture space afterwards. That said, a pixel test would lock it in. There is already a GPU pixel test pattern in crates/rendering/tests/cursor_texture_edges.rs that skips cleanly when no adapter is available, so something similar that renders an asymmetric, non-square camera texture at 0/90/180/270 (with and without mirror) and checks a few corner pixels would cover it. If you'd rather not add it here, replying on Greptile's thread with the reasoning above is fine too.

One extra thing from our side that Greptile didn't flag here (it did on #2779): Camera gains a rotation field in crates/project/src/configuration.rs:564, and apps/desktop/src/utils/tauri.ts is still generated from these types. Per AGENTS.md, please regenerate the bindings (a debug run of the Tauri desktop app does this) and commit them with the change rather than editing by hand.

Once pushed, could you comment @greptileai review so it re-scores the latest commit? Thanks!

YoavLevavi and others added 2 commits October 25, 2026 17:07
- camera_rotation_pixels renders a non-square camera with a different colour
  per quadrant, camera-only, at 0/90/180/270 with and without mirroring, and
  checks the quadrant in each output corner against the shader's mapping.
  It skips when no GPU adapter is available.
- Regenerates apps/desktop/src/utils/tauri.ts for Camera.rotation.
- Merges origin/main.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@YoavLevavi

Copy link
Copy Markdown
Contributor Author

Thanks @richiemcilroy, both are in 94c294e (also merged main):

  • Pixel test. crates/rendering/tests/camera_rotation_pixels.rs renders a non-square 80x60 camera with a different colour per quadrant, camera-only, at 0/90/180/270 with and without mirroring. At each output corner it checks the quadrant expected from rotated_frame_uv, with mirroring applied in oriented space. It skips without a GPU adapter. I mutation-checked it: flipping the mirror or reversing the 90° mapping makes it fail.
  • Bindings. apps/desktop/src/utils/tauri.ts is regenerated for Camera.rotation, and tsc is clean.

@greptileai review

Comment on lines +63 to +65
else {
eprintln!("no GPU adapter available, skipping camera rotation pixels");
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Device failures pass the test

The new pixel test treats every error from RenderVideoConstants::new_with_options as a missing GPU adapter. That call can also fail when an adapter exists but request_device fails. The test then passes without checking any pixels and prints the wrong reason.

This is non-blocking, but it hides failed test setup. Skip only RenderingError::NoAdapter and fail on other errors.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/rendering/tests/camera_rotation_pixels.rs
Line: 63-65

Comment:
**Device failures pass the test**

The new pixel test treats every error from `RenderVideoConstants::new_with_options` as a missing GPU adapter. That call can also fail when an adapter exists but `request_device` fails. The test then passes without checking any pixels and prints the wrong reason.

This is non-blocking, but it hides failed test setup. Skip only `RenderingError::NoAdapter` and fail on other errors.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@YoavLevavi

Copy link
Copy Markdown
Contributor Author

Fixed in 3f69fd2: the pixel test now skips only on RenderingError::NoAdapter and panics on any other setup error, so a failed request_device no longer passes silently.

@greptileai review

# Conflicts:
#	apps/desktop-gpui/src/editor_tabs.rs

This branch has not been deployed

No deployments
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.

2 participants