Skip to content

Add a OneSideEveryOtherFrame reflection probe update mode - #148

Draft
JuhDuh26 wants to merge 1 commit into
carbonengine:mainfrom
JuhDuh26:reflection-probe-every-other-frame
Draft

JuhDuh26 wants to merge 1 commit into
carbonengine:mainfrom
JuhDuh26:reflection-probe-every-other-frame

Conversation

@JuhDuh26

Copy link
Copy Markdown

Summary

Adds a fourth reflection probe update rate, ReflectionProbeRenderFrequency.OneSideEveryOtherFrame. It does what OneSidePerFrame does (render one cube face, then pre-filter, generate mips and run the main filter over the whole cube), but only on even frames. On odd frames the render driver prepares no reflection faces, so nothing is rendered or filtered and the last filtered reflection stays bound.

  • A full cube refresh takes 12 frames instead of 6. At 160 fps that's 75 ms instead of 37 ms.
  • The first pass after the probe is created (or its resources are rebuilt) still renders all six faces, as before.
  • Which frames are skipped comes from Tr2Renderer::GetCurrentFrameCounter() parity, not from a toggle, so the answer is the same however many times a frame asks.
  • The enum value is appended, so OneSidePerFrame (0) and AllSidesPerFrame (1) keep their values.

Why

On a CPU-bound EVE Frontier client (about 5.4 ms CPU against about 3.1 ms GPU per frame), Tracy showed the reflection pass at about 0.37 ms of main-thread time per frame with OneSidePerFrame. That was one of the larger single costs left after other camera-movement fixes. Reflections on ships and structures change slowly, so updating them at half the rate isn't noticeable in normal play.

Changes

File Change
trinity/Tr2ReflectionProbe.h New enum value ONE_SIDE_EVERY_OTHER_FRAME; new ShouldRenderThisFrame() const.
trinity/Tr2ReflectionProbe.cpp ShouldRenderThisFrame(): true unless the mode is every-other-frame, the first full pass is done and the frame counter is odd.
trinity/Tr2ReflectionProbe_Blue.cpp Exposes OneSideEveryOtherFrame in the chooser.
trinity/Eve/EveSpaceSceneRenderDriver.cpp PrepareReflectionFaces returns no faces when the probe says to skip this frame, so RenderReflectionFaces does nothing.

GetStartFace/GetEndFace and FinishFaces don't change: on the frames that do render, the new mode behaves exactly like OneSidePerFrame.

Measurements

Measured in a live EVE Frontier client. It was a local trinitydev DX12 build of this change (written against the pre-#137 code, where the gate sat in EveSpaceScene::RenderReflectionPass). Reflections were on High, the ship was in space, and there was no input. The mode was switched at runtime through scene.reflectionProbe.renderFrequency, alternating five 4-second windows of each:

Mode ms per frame fps faces rendered per frame
OneSidePerFrame 6.51 (6.44–6.57) 153.6 1.00
OneSideEveryOtherFrame 6.18 (6.11–6.25) 161.7 0.50

That saves 0.33 ms a frame (+5% fps). It's more than half of the 0.37 ms main-thread zone, presumably because the filter's GPU work and submission go away on those frames too. The face count was read from probe.currentFrame once per frame, which confirms exactly every other frame renders. The screenshots looked normal.

After #137 landed I rebased onto it and moved the gate to EveSpaceSceneRenderDriver::PrepareReflectionFaces, because #137 removed EveSpaceScene::RenderReflectionPass and the driver now owns the faces. The rebased version builds locally (trinitydev, DX12). It hasn't been measured in the client: the client's Perforce stream doesn't have #137 yet.

How it's used

The game picks the mode from Python (packages/trinity/sceneRenderJobSpace.py in the EVE Frontier stream, Perforce CL 3685716). It uses OneSideEveryOtherFrame for Low, Medium and High reflection quality and keeps AllSidesPerFrame for Ultra. That code falls back to OneSidePerFrame when the running Trinity doesn't have the new value, so the two changes can land in either order.

Testing

🤖 Generated with Claude Code

OneSidePerFrame renders one cube face and re-runs the full pre-filter,
mip generation and main filter on every frame. On a CPU-bound client
the reflection pass measured about 0.37 ms of main-thread time per
frame.

OneSideEveryOtherFrame does the same work on even frames only (by
Tr2Renderer::GetCurrentFrameCounter parity, so it gives one answer per
frame however often it is asked). On odd frames the render driver
prepares no reflection faces, so no face is rendered or filtered and
the last filtered reflection stays bound. A full cube update takes 12
frames instead of 6. The first pass after creation still renders all
six faces.

The new enum value is appended, so existing values and saved settings
keep their meaning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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