Skip to content

perf(rendering): cheaper blur sampling and overlay capture - #3584

Open
AatmanAJ wants to merge 10 commits into
CapSoftware:mainfrom
AatmanAJ:perf/render-shaders-overlay
Open

AatmanAJ wants to merge 10 commits into
CapSoftware:mainfrom
AatmanAJ:perf/render-shaders-overlay

Conversation

@AatmanAJ

@AatmanAJ AatmanAJ commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Motion blur in composite-video-frame.wgsl uses sample_texture_blur (single textureSample per tap) instead of the sharpened 5-tap downscale path used for the main composite sample.
  • Picker overlay backdrop capture runs less often: stale interval 8s when crop/display size match the last capture, 2s minimum when they changed; still waits for crop settle before retake.
  • Cropped Windows Graphics Capture copies into a pooled texture (TrimPool) per frame so in-flight frames are not overwritten by the next crop copy.

Deferred

  • Windows fragmented M4S / GPU encode path: No safe small change found without a larger Media Foundation muxer or capture-pipeline rework (e.g. deeper staging ring between D3D readback and encoder thread, or zero-copy handoff into HW encoder). The existing CAP_MUXER_BUFFER_SIZE knob (default 240) remains the primary backpressure lever; a dedicated GPU encode staging ring is left for a follow-up.

Test plan

  • Export or preview a clip with directional/zoom motion blur; confirm blur still looks correct and GPU time drops on downscaled sources.
  • Open area/window picker overlay; confirm glass backdrop still updates after crop moves and does not flicker mid-drag.
  • Windows cropped capture (window/display crop): record while encoder is slow; confirm no torn or duplicated crop frames.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; all previous findings are addressed and no new blocking issue was found.

Summary

This PR reduces work in motion blur and picker backdrop capture. It also keeps cropped Windows textures alive while the encoder copies them.

  • Directional and zoom blur use one texture read for each tap.
  • Picker backdrops refresh on a slower schedule when their bounds match.
  • Windows cropped frames keep their own pooled texture while in use.
  • The Linux harness starts and focuses Cap with Hyprland's Lua commands.
  • The export harness reads GIF frame delays from the image blocks.
  • Codec verification uses the Web export bitrate for its smaller video case.

Reviews (5) · Last reviewed commit: "fix(ci): walk GIF blocks when measuring ..." · Reviewed by Greptile

Motion-blur taps use a single texture read instead of sharpened downscale sampling; overlay backdrops refresh less often when crop and display size are stable; cropped D3D capture frames reuse a texture pool so queued frames are not overwritten.
@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

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

A member of the Team first needs to authorize it.

Comment on lines +450 to +452
let cropped_uv = cropped_sample_uv(uv, crop_bounds_uv);

let crop_size = crop_bounds_uv.zw - crop_bounds_uv.xy;

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.

P1 Video shader cannot load

Moving safe_min and safe_max into cropped_sample_uv leaves them undefined in sample_texture, which still uses both names for neighboring samples. WGSL rejects the whole shader when CompositeVideoFramePipeline::new creates it, so video rendering fails even with motion blur disabled. Keep the bounds available inside sample_texture, or return them from the helper.

Suggested change
let cropped_uv = cropped_sample_uv(uv, crop_bounds_uv);
let crop_size = crop_bounds_uv.zw - crop_bounds_uv.xy;
let cropped_uv = cropped_sample_uv(uv, crop_bounds_uv);
let texel_offset = 1.0 / uniforms.frame_size;
let safe_min = crop_bounds_uv.xy + texel_offset;
let safe_max = crop_bounds_uv.zw - texel_offset;
let crop_size = crop_bounds_uv.zw - crop_bounds_uv.xy;
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/rendering/src/shaders/composite-video-frame.wgsl
Line: 450-452

Comment:
**Video shader cannot load**

Moving `safe_min` and `safe_max` into `cropped_sample_uv` leaves them undefined in `sample_texture`, which still uses both names for neighboring samples. WGSL rejects the whole shader when `CompositeVideoFramePipeline::new` creates it, so video rendering fails even with motion blur disabled. Keep the bounds available inside `sample_texture`, or return them from the helper.

```suggestion
        let cropped_uv = cropped_sample_uv(uv, crop_bounds_uv);
        let texel_offset = 1.0 / uniforms.frame_size;
        let safe_min = crop_bounds_uv.xy + texel_offset;
        let safe_max = crop_bounds_uv.zw - texel_offset;

        let crop_size = crop_bounds_uv.zw - crop_bounds_uv.xy;
```

---

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

d3d_device: d3d_device.clone(),
staging_pool: staging_pool.clone(),
recycle: None,
recycle: Some(crop_pool),

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.

P1 Crop textures return too early

Returning cropped textures to TrimPool when Frame drops does not fully fix the in-flight overwrite. The Windows encoder callbacks return only frame.texture().clone(), dropping Frame before the encoder calls process_texture. The next capture can then reuse and overwrite the same surface before the encoder copies its pixels, producing overwritten frames in cropped recordings. Keep the frame's pool ownership alive until the encoder has copied the texture; a cloned texture handle does not keep Frame alive.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/scap-direct3d/src/lib.rs
Line: 778

Comment:
**Crop textures return too early**

Returning cropped textures to `TrimPool` when `Frame` drops does not fully fix the in-flight overwrite. The Windows encoder callbacks return only `frame.texture().clone()`, dropping `Frame` before the encoder calls `process_texture`. The next capture can then reuse and overwrite the same surface before the encoder copies its pixels, producing overwritten frames in cropped recordings. Keep the frame's pool ownership alive until the encoder has copied the texture; a cloned texture handle does not keep `Frame` alive.

---

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

Aatman Jain added 2 commits October 29, 2026 23:38
Refactoring for blur sampling moved crop clamping into cropped_sample_uv
but left sample_texture using safe_min/safe_max out of scope.
Hold the scap Direct3D frame until the encoder finishes processing the
texture so pooled crop surfaces are not recycled early.
@AatmanAJ

Copy link
Copy Markdown
Contributor Author

@greptile-apps please re-review. Addressed the open P1/P2 items in the latest push on this branch.

}
screen_capture::ScreenFrame::Scaled(scaled) => {
captured_keepalive = None;
scaled.texture.clone()

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.

P1 Windows builds fail

Both Windows encoder callbacks now read scaled.texture directly, but that field is private to the screen_capture::windows module. These sibling modules cannot access it, so Windows builds fail with a private-field error. The same issue appears in win_segmented.rs at line 559.

Use the public ScreenFrame::texture() accessor before matching, or add an accessor to ScaledScreenFrame.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/output_pipeline/win.rs
Line: 364

Comment:
**Windows builds fail**

Both Windows encoder callbacks now read `scaled.texture` directly, but that field is private to the `screen_capture::windows` module. These sibling modules cannot access it, so Windows builds fail with a private-field error. The same issue appears in `win_segmented.rs` at line 559.

Use the public `ScreenFrame::texture()` accessor before matching, or add an accessor to `ScaledScreenFrame`.

---

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

@AatmanAJ
AatmanAJ force-pushed the perf/render-shaders-overlay branch from 6a36f76 to a9a4198 Compare October 30, 2026 00:26
@AatmanAJ

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

Aatman Jain added 3 commits October 30, 2026 10:28
…rness

Hyprland 0.57 dropped hyprland.conf and only reads hyprland.lua, so the
harness's monitor mode, session export and waybar were ignored and the
output came up at vkms's preferred 1024x768. The screenshot scenario then
reported screenshot_size=1024x768 instead of yes. Write the same settings
as Lua and use the Lua form of the focus dispatcher.
…set bitrate

At 80 kbps the paravirtualised VideoToolbox HEVC encoder on the CI macOS
VMs occasionally left a blurred camera code cell at margin 0.17, below the
reader's 0.18 threshold. 138,240 bps is the Web export preset (0.08 bpp),
still below the Social preset the verify exports use.
@AatmanAJ

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

@AatmanAJ

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

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.

1 participant