mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 09:00:28 +02:00
- Added eye_pass_plan.hpp: an eye keeps drawing in the render pass it has open across the frame's
GX copies (only the mono render performs them), skips passes a later clear of the whole color and
depth erases, and splits only for a clear of color alone or depth alone.
- render_stereo_eye follows that plan; the cockpit fallback is drawn inside the last pass instead of
a render pass of its own that loaded the eye back.
- The stencil is now cleared where the eye actually starts, so a cockpit mask drawn in an erased pass
can no longer punch holes in the HUD.
- [vr] single_pass_eyes (default on, live from F10) and debug.wiicompiled.eye_passes 0/1 on the Quest
switch back to one render pass per recorded pass for A/B timing.
- Each new plan shape is logged once ("Eye replay plan: ...").
- Tests: EyePassPlan cases in gx_fifo_tests; stereo_multiplayer_smoke and stereo_frame_worker_smoke
pass on D3D12.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
176 lines
6.0 KiB
C++
176 lines
6.0 KiB
C++
#include "gfx/eye_pass_plan.hpp"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <vector>
|
|
|
|
namespace aurora::gfx::eye_pass_plan {
|
|
namespace {
|
|
|
|
PassSummary efb(bool clearColor, bool clearDepth, bool hasCommands = true, bool postCopyClear = false) {
|
|
return PassSummary{
|
|
.efbTarget = true,
|
|
.clearColor = clearColor,
|
|
.clearDepth = clearDepth,
|
|
.postCopyClear = postCopyClear,
|
|
.hasCommands = hasCommands,
|
|
};
|
|
}
|
|
|
|
PassSummary offscreen() {
|
|
return PassSummary{.efbTarget = false, .clearColor = true, .clearDepth = true, .hasCommands = true};
|
|
}
|
|
|
|
Plan build(const std::vector<PassSummary>& passes, int32_t lastPass = -1, bool skipCopyClears = true) {
|
|
Plan plan;
|
|
eye_pass_plan::build(passes.data(), passes.size(), lastPass, skipCopyClears, plan);
|
|
return plan;
|
|
}
|
|
|
|
TEST(EyePassPlan, ContinuationsAfterCopiesShareOneRenderPass) {
|
|
const Plan plan = build({efb(true, true), efb(false, false), efb(false, false), efb(false, false)});
|
|
ASSERT_EQ(plan.steps.size(), 4u);
|
|
EXPECT_EQ(plan.efbPasses, 4u);
|
|
EXPECT_EQ(plan.renderPasses, 1u);
|
|
EXPECT_EQ(plan.splits, 0u);
|
|
EXPECT_TRUE(plan.steps[0].begin);
|
|
EXPECT_TRUE(plan.steps[0].clearColor);
|
|
EXPECT_TRUE(plan.steps[0].clearDepth);
|
|
EXPECT_TRUE(plan.steps[0].clearStencil);
|
|
for (size_t i = 1; i < plan.steps.size(); ++i) {
|
|
EXPECT_EQ(plan.steps[i].pass, i);
|
|
EXPECT_FALSE(plan.steps[i].begin);
|
|
}
|
|
}
|
|
|
|
TEST(EyePassPlan, FullClearErasesEverythingDrawnBeforeIt) {
|
|
// A copy that clears the whole EFB: the eye never shows what came before it.
|
|
const Plan plan = build({efb(true, true), efb(false, false), efb(true, true), efb(false, false)});
|
|
EXPECT_EQ(plan.dead, 2u);
|
|
EXPECT_EQ(plan.renderPasses, 1u);
|
|
ASSERT_EQ(plan.steps.size(), 2u);
|
|
EXPECT_EQ(plan.steps[0].pass, 2u);
|
|
EXPECT_TRUE(plan.steps[0].begin);
|
|
EXPECT_TRUE(plan.steps[0].clearColor);
|
|
EXPECT_TRUE(plan.steps[0].clearDepth);
|
|
// The stencil is cleared where the eye actually starts, so a cockpit mask drawn in a pass that
|
|
// gets erased can no longer punch holes in the HUD.
|
|
EXPECT_TRUE(plan.steps[0].clearStencil);
|
|
EXPECT_EQ(plan.steps[1].pass, 3u);
|
|
EXPECT_FALSE(plan.steps[1].begin);
|
|
}
|
|
|
|
TEST(EyePassPlan, ClearOfOnlyColorOrOnlyDepthSplits) {
|
|
const Plan plan = build({efb(true, true), efb(false, true), efb(false, false), efb(true, false)});
|
|
EXPECT_EQ(plan.dead, 0u);
|
|
EXPECT_EQ(plan.renderPasses, 3u);
|
|
EXPECT_EQ(plan.splits, 2u);
|
|
ASSERT_EQ(plan.steps.size(), 4u);
|
|
EXPECT_TRUE(plan.steps[1].begin);
|
|
EXPECT_FALSE(plan.steps[1].clearColor);
|
|
EXPECT_TRUE(plan.steps[1].clearDepth);
|
|
EXPECT_FALSE(plan.steps[1].clearStencil);
|
|
EXPECT_FALSE(plan.steps[2].begin);
|
|
EXPECT_TRUE(plan.steps[3].begin);
|
|
EXPECT_TRUE(plan.steps[3].clearColor);
|
|
EXPECT_FALSE(plan.steps[3].clearDepth);
|
|
EXPECT_FALSE(plan.steps[3].clearStencil);
|
|
}
|
|
|
|
TEST(EyePassPlan, DisplayCopyResetFollowsTheCopyClearSwitch) {
|
|
// The continuation after GXCopyDisp holds only that copy's EFB reset.
|
|
const std::vector passes{efb(true, true), efb(true, true, false, true)};
|
|
const Plan skipped = build(passes, -1, true);
|
|
EXPECT_EQ(skipped.dead, 0u);
|
|
EXPECT_EQ(skipped.empty, 1u);
|
|
ASSERT_EQ(skipped.steps.size(), 1u);
|
|
EXPECT_EQ(skipped.steps[0].pass, 0u);
|
|
|
|
// Replayed raw, the reset erases the frame, as the legacy replay's black eye shows.
|
|
const Plan raw = build(passes, -1, false);
|
|
EXPECT_EQ(raw.dead, 1u);
|
|
ASSERT_EQ(raw.steps.size(), 1u);
|
|
EXPECT_EQ(raw.steps[0].pass, 1u);
|
|
EXPECT_TRUE(raw.steps[0].clearColor);
|
|
EXPECT_TRUE(raw.steps[0].clearDepth);
|
|
}
|
|
|
|
TEST(EyePassPlan, OffscreenPassesNeitherSplitNorEraseTheEye) {
|
|
const Plan plan = build({efb(true, true), offscreen(), efb(false, false)});
|
|
EXPECT_EQ(plan.efbPasses, 2u);
|
|
EXPECT_EQ(plan.dead, 0u);
|
|
EXPECT_EQ(plan.renderPasses, 1u);
|
|
ASSERT_EQ(plan.steps.size(), 2u);
|
|
EXPECT_EQ(plan.steps[0].pass, 0u);
|
|
EXPECT_EQ(plan.steps[1].pass, 2u);
|
|
EXPECT_FALSE(plan.steps[1].begin);
|
|
}
|
|
|
|
TEST(EyePassPlan, StopsAtTheLastReplayedPass) {
|
|
const std::vector passes{efb(true, true), efb(false, false), efb(true, true)};
|
|
const Plan cut = build(passes, 1);
|
|
EXPECT_EQ(cut.efbPasses, 2u);
|
|
EXPECT_EQ(cut.dead, 0u);
|
|
ASSERT_EQ(cut.steps.size(), 2u);
|
|
EXPECT_EQ(cut.steps[1].pass, 1u);
|
|
|
|
const Plan whole = build(passes, -1);
|
|
EXPECT_EQ(whole.efbPasses, 3u);
|
|
EXPECT_EQ(whole.dead, 2u);
|
|
ASSERT_EQ(whole.steps.size(), 1u);
|
|
EXPECT_EQ(whole.steps[0].pass, 2u);
|
|
|
|
const Plan beyond = build(passes, 10);
|
|
EXPECT_EQ(beyond.efbPasses, 3u);
|
|
}
|
|
|
|
TEST(EyePassPlan, SkipsEmptyPassesButKeepsClears) {
|
|
const Plan plan = build({efb(true, true, false), efb(false, false, false), efb(false, false)});
|
|
EXPECT_EQ(plan.empty, 1u);
|
|
EXPECT_EQ(plan.renderPasses, 1u);
|
|
ASSERT_EQ(plan.steps.size(), 2u);
|
|
EXPECT_EQ(plan.steps[0].pass, 0u);
|
|
EXPECT_TRUE(plan.steps[0].clearColor);
|
|
EXPECT_EQ(plan.steps[1].pass, 2u);
|
|
EXPECT_FALSE(plan.steps[1].begin);
|
|
}
|
|
|
|
TEST(EyePassPlan, AResumedFrameStartsByLoadingTheEye) {
|
|
const Plan plan = build({efb(false, false), efb(false, false)});
|
|
EXPECT_EQ(plan.renderPasses, 1u);
|
|
ASSERT_EQ(plan.steps.size(), 2u);
|
|
EXPECT_TRUE(plan.steps[0].begin);
|
|
EXPECT_FALSE(plan.steps[0].clearColor);
|
|
EXPECT_FALSE(plan.steps[0].clearDepth);
|
|
EXPECT_TRUE(plan.steps[0].clearStencil);
|
|
EXPECT_FALSE(plan.steps[1].begin);
|
|
}
|
|
|
|
TEST(EyePassPlan, NothingToReplay) {
|
|
const Plan none = build({offscreen()});
|
|
EXPECT_TRUE(none.steps.empty());
|
|
EXPECT_EQ(none.efbPasses, 0u);
|
|
EXPECT_EQ(none.renderPasses, 0u);
|
|
EXPECT_EQ(none.splits, 0u);
|
|
|
|
const Plan empty = build({});
|
|
EXPECT_TRUE(empty.steps.empty());
|
|
}
|
|
|
|
TEST(EyePassPlan, RebuildingReusesThePlan) {
|
|
Plan plan;
|
|
const std::vector first{efb(true, true), efb(false, true), efb(true, true), efb(false, false)};
|
|
eye_pass_plan::build(first.data(), first.size(), -1, true, plan);
|
|
const std::vector second{efb(true, true), efb(false, false)};
|
|
eye_pass_plan::build(second.data(), second.size(), -1, true, plan);
|
|
EXPECT_EQ(plan.efbPasses, 2u);
|
|
EXPECT_EQ(plan.dead, 0u);
|
|
EXPECT_EQ(plan.empty, 0u);
|
|
EXPECT_EQ(plan.renderPasses, 1u);
|
|
EXPECT_EQ(plan.splits, 0u);
|
|
EXPECT_EQ(plan.steps.size(), 2u);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace aurora::gfx::eye_pass_plan
|