Skip to content

Add experimental session replay capturing on Windows#1404

Merged
tustanivsky merged 63 commits into
mainfrom
feat/ue-replay-capture
Jun 3, 2026
Merged

Add experimental session replay capturing on Windows#1404
tustanivsky merged 63 commits into
mainfrom
feat/ue-replay-capture

Conversation

@tustanivsky

@tustanivsky tustanivsky commented May 26, 2026

Copy link
Copy Markdown
Collaborator

This PR adds an opt-in session replay feature for Windows allowing the SDK to continuously record the last several seconds of gameplay into a MP4 file and attach it to crash reports.

Example crash event with Session Replay attached (Windows).

Architecture

The feature is a four-stage pipeline (backbuffer capture → H.264 encode → fMP4 mux → rolling rotation), each stage living on its own thread so nothing blocks the game or render thread.

  • FSentryBackBufferCapture (render thread) hooks Slate's OnBackBufferReadyToPresent, throttles capture to the configured frame rate, and copies each captured frame into a BGRA8 texture that can be consumed by the encoder.

  • FSentryVideoEncoder (dedicated thread) submits those textures to the AVCodecs H.264 encoder, asynchronously drains compressed packets, converts the Annex-B bitstream to AVCC format, and groups encoded samples into fragments at keyframe boundaries.

  • FSentryFMP4Writer is a lightweight, stateless muxer that converts SPS/PPS data and AVCC samples into ISO-BMFF structures, producing the ftyp + moov initialization segment once and a moof + mdat pair for each fragment.

  • FSentrySessionReplayRecorder acts as the orchestrator. It owns the capture hook and encoder, maintains a rolling ring buffer of completed fragments, and runs a rotation thread that periodically assembles the initialization segment and fragment ring into a complete recording before replacing the on-disk file.

Engine Dependencies

Session Replay depends on Unreal Engine’s experimental AVCodecs plugin family (available from UE 5.2 onward). Usage is gated in two layers:

  • Build-time: The feature is only compiled and linked when the consuming project has the AVCodecsCore plugin enabled and AttachSessionReplay set. Build.cs inspects the project descriptor, and only then defines USE_SENTRY_SESSION_REPLAY. If either condition is missing, the Session Replay sources are excluded entirely and no encoder/capture code is built for that platform/configuration.

  • Runtime: AttachSessionReplay is validated again at startup. If it is disabled, the recorder does not initialize, even if the module was compiled in.

Other Considerations

  • Backbuffer → BGRA8 conversion : NVENC’s D3D12 path only accepts BGRA8, while many projects render to 10-bit (PF_A2B10G10R10) or HDR backbuffers. Additionally, Slate does not create its backbuffer with the ShaderResource flag, so it cannot be sampled directly. The capture path therefore hardware-copies the backbuffer into a scratch texture we own (same format, correct usage flags), then runs AddDrawTexturePass (engine-provided) to convert scratch → BGRA8 pool texture. If the source is already BGRA8, this second step degenerates into a straight GPU copy; otherwise, conversion happens via the engine pixel shader.

  • Wall-clock based timing : Timing is based on FPlatformTime::Seconds() rather than frame count. Fragment duration and keyframe cadence are derived from real elapsed time instead of the render frame rate, since gameplay is inherently variable (hitches, alt-tab, throttling). Frame-based timing would cause drift, turning a 12s window into 20s+ under load. Sample durations are measured against wall-clock deltas and clamped (e.g., 2s max) to guard against stalls.

  • TFDT rebasing on rotation : Each fragment embeds decode timestamps. When the ring buffer evicts older fragments, remaining fragments may no longer start at t=0, which leads to incorrect duration reporting in players. During rotation, the first surviving fragment is rewritten so its timeline starts at t=0.

  • Write-on-rotate: Each snapshot is written to a temporary file and then renamed over the target. This ensures Sentry's backend only ever observes either the previous complete snapshot or the newly finished one - never a partially written (torn) file.

Known Limitations

  • Currently supported only on Windows with NVIDIA GPUs. Supporting other desktop platforms and GPU vendors may require additional work.
  • The video fragment being recorded at the time of a crash (typically around 0.5 seconds, depending on the recording configuration) is not included in the final clip.
  • Encoder input buffers are allocated based on the first captured frame. If the game resolution changes during a recording session, the resulting video may contain cropped or black-filled frames.
  • Audio capture is not currently supported; recordings contain video only.
  • Recordings may include any content visible on screen (for example, player names, chat messages, or other user-generated content). The current implementation does not respect the PII setting.

Testing

To evaluate the performance impact introduced by Session Replay, the game binary was profiled twice: once with replay disabled to establish a baseline, and once with replay enabled to measure the overhead. Per-frame timings were captured using Unreal Engine’s CSV profiler.

The majority of the additional cost comes from the render-thread backbuffer capture hook. The game thread, RHI thread, and GPU are only minimally affected, since encoding and file rotation are performed on dedicated background threads.

Setup

  • Game: SentryTower demo (UE 5.7.4, Win64 Development build)
  • Machine: Windows 11, Intel i9-12900K, RTX 3080 (NVENC enabled)
  • Resolution: 4K (3840×2160) fullscreen exclusive via r.SetRes 3840x2160f to bypass Windows DPI-scaled working-area clamping. 4K represents the worst-case replay workload due to the larger backbuffer copy and encode cost, so the measured overhead bounds the impact at lower resolutions.
  • Replay configuration:
    • 30 FPS capture
    • 2000 kbps bitrate
    • 0.2 s fragment duration
    • 8 s rolling replay window
  • Methodology:
    • Three measurement passes per condition
    • First 60 frames of each pass discarded as warmup
    • One additional warmup pass per condition discarded to prime PSO/shader caches

Performance Impact

Metric Baseline Replay On Δ Δ %
Frame Time 5.56 ms 5.77 ms +0.20 ms +3.6%
Render Thread 5.56 ms 5.67 ms +0.11 ms +1.9%
Game Thread 1.72 ms 1.82 ms +0.10 ms +5.8%
GPU 4.96 ms 5.04 ms +0.08 ms +1.6%
RHI Thread 1.81 ms 1.82 ms +0.01 ms +0.5%

Replay adds approximately 0.20 ms per frame at 4K:

  • ~1.2% of a 16.7 ms frame budget (60 FPS)
  • ~2.4% of an 8.3 ms frame budget (120 FPS)

This is well below the threshold of noticeable overhead for typical game performance targets.

Documentation

Related items

Comment thread plugin-dev/Source/Sentry/Private/SessionReplay/SentryVideoEncoder.cpp Outdated
Comment thread plugin-dev/Source/Sentry/Private/Windows/WindowsSentrySubsystem.cpp

@jpnurmi jpnurmi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Woah, amazing stuff! 🥇

There's a lot in the MP4 writer and video encoder that a casual reader might not understand in detail, but I scrolled through nevertheless. 😅 I didn't notice anything Windows-specific in them, so would it be possible to enable this for other platforms in the future?

@tustanivsky

Copy link
Copy Markdown
Collaborator Author

@jpnurmi Thanks! And you're right - the muxer and most of the encoder are platform-agnostic so technically there's nothing stopping them from running elsewhere. For now I kept this Windows-only since it's the primary target for Unreal and NVENC is the only backend I could fully validate and benchmark.

The next step is hooking in the other AVCodecs backends (e.g. AMF for AMD GPUs and VideoToolbox for Mac) and seeing whether we can support replays across all desktop platforms.

@JoshuaMoelans JoshuaMoelans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM! a few open questions just to clarify some stuff, but nothing blocking :D

Comment thread plugin-dev/Source/Sentry/Private/Windows/WindowsSentrySubsystem.cpp

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a777cf2. Configure here.

Comment thread plugin-dev/Source/Sentry/Private/Windows/WindowsSentrySubsystem.cpp

@JoshuaMoelans JoshuaMoelans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for addressing the questions! 🚀🚀🚀

@tustanivsky tustanivsky merged commit 44bfdc8 into main Jun 3, 2026
317 of 325 checks passed
@tustanivsky tustanivsky deleted the feat/ue-replay-capture branch June 3, 2026 08:05
tustanivsky added a commit to getsentry/sentry-docs that referenced this pull request Jun 3, 2026
This PR adds a dedicated "Session Replay" docs page for Unreal SDK.

Related items:
- getsentry/sentry-unreal#1404
- getsentry/sentry-unreal#1386
- getsentry/sentry-xbox#136
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.

5 participants