Added Support for Multiplayer Player 1 Immersive View

This commit is contained in:
iChris4 committed 2026-09-16 17:06:03 +02:00
1 parent 9e37bb6447
commit 3e8bdcfd7b
22 files changed
+1013 -38

No files matched your search

+8
View File
@@ -356,6 +356,14 @@ target_include_directories(mkw_vr_player_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR
target_compile_features(mkw_vr_player_tests PRIVATE cxx_std_17)
add_test(NAME mkw_vr_player_tests COMMAND mkw_vr_player_tests)
add_executable(mkw_vr_policy_tests tests/vr_policy_tests.cpp src/vr/mkw_vr_policy.cpp)
target_include_directories(mkw_vr_policy_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
target_compile_features(mkw_vr_policy_tests PRIVATE cxx_std_17)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
target_compile_options(mkw_vr_policy_tests PRIVATE -ffast-math)
endif()
add_test(NAME mkw_vr_policy_tests COMMAND mkw_vr_policy_tests)
if(MKW_ENABLE_OPENXR AND MKW_PLATFORM_WINDOWS)
add_executable(mkw_openxr_replay_tests
tests/openxr_d3d12_replay_tests.cpp src/vr/openxr_d3d12.cpp)
+3 -2
View File
@@ -147,8 +147,9 @@ extern "C" void GX__CopyTex_8016fd74(uint32_t da, uint32_t c) {
// EFB exactly once, matching Dolphin's ConvertEFBRectangle path.
GXSetTexCopySrc(rawSrcLeft, rawSrcTop, rawSrcWidth, rawSrcHeight);
// EFB copies stay GPU-only except probe-sized ones (e.g. the 4x4 lens-flare depth probe),
// which Aurora reads back asynchronously via efb_ram::schedule and land in guest RAM a frame
// later. RISK: copies above the probe threshold, or on the offscreen list, are not
// which Aurora reads back asynchronously and publishes during the next copy to that buffer.
// GPU callbacks retain pixels in host memory so a scene restart cannot receive a late write
// into a freed/reused allocation. RISK: copies above the probe threshold, or on the offscreen list, are not
// auto-downloaded, so guest reads see stale RAM; call aurora_flush_efb_copies_to_ram if a
// copy needs reading back.
GXCopyTex(GuestToHostPtr(da), (GXBool)c);
+5 -2
View File
@@ -615,8 +615,11 @@ void VI_HLE_PresentFrame(bool presentedXfb, bool paceToRetrace) {
// asynchronous worker may ask for an XR packet after the guest has already
// begun the next frame, so immersive replay is accepted only when both
// tags match.
const uint64_t vrContentTag = mkw::vr::MkwVRPolicyGetSnapshot().content_tag;
aurora_end_frame_tagged(vrContentTag);
const auto vrPolicy = mkw::vr::MkwVRPolicyGetSnapshot();
aurora_set_stereo_local_player_count(
vrPolicy.presentation == mkw::vr::VRPresentationMode::ImmersiveRace
? vrPolicy.scene.local_player_count : 1);
aurora_end_frame_tagged(vrPolicy.content_tag);
if (paceThisFrame) {
PaceToRetraceBoundary(paceDeadline);
std::lock_guard<std::mutex> lock(g_viMutex);
+3 -1
View File
@@ -168,7 +168,9 @@ extern "C" void MkwVRObserveTranslatedFunctionEntry(uint32_t address,
case kRaceCameraUpdate: {
const uint32_t camera_address = context != nullptr ? context->gpr[3] : 0;
ObserveCamera(frame, camera_address);
PublishObservedCamera(frame, camera_address);
if (camera_address == FirstCamera(frame)) {
PublishObservedCamera(frame, camera_address);
}
break;
}
case kScnMgrRaceDraw: {
+10 -3
View File
@@ -86,12 +86,13 @@ VRPresentationMode SelectPresentation(const PolicyState& state) noexcept {
return VRPresentationMode::Desktop;
}
// A virtual screen is the fail-safe for menus, split-screen, and any
// A virtual screen is the fail-safe for menus and any
// incomplete instrumentation. It preserves the unmodified render path.
if ((state.available_bindings & kMkwVRRequiredImmersiveBindings) !=
kMkwVRRequiredImmersiveBindings ||
!state.config.immersive_races || state.scene.mode != VRSceneMode::Race ||
state.scene.local_player_count != 1 || !IsFiniteCamera(state.camera) ||
(state.scene.local_player_count < 1 || state.scene.local_player_count > 4) ||
!IsFiniteCamera(state.camera) ||
!ObservationsAreCoherent(state.scene, state.camera)) {
return VRPresentationMode::VirtualScreen;
}
@@ -111,7 +112,8 @@ VRPresentationMode SelectStablePresentation(const PolicyState& state) noexcept {
if ((state.available_bindings & kMkwVRRequiredImmersiveBindings) !=
kMkwVRRequiredImmersiveBindings ||
!state.config.immersive_races || state.scene.mode != VRSceneMode::Race ||
state.scene.local_player_count != 1 || !IsFiniteCamera(state.camera)) {
(state.scene.local_player_count < 1 || state.scene.local_player_count > 4) ||
!IsFiniteCamera(state.camera)) {
return VRPresentationMode::VirtualScreen;
}
@@ -211,6 +213,11 @@ void MkwVRPolicySetAvailableBindings(uint32_t bindings) noexcept {
void MkwVRPolicyPublishScene(const MkwVRSceneObservation& scene) noexcept {
std::lock_guard<std::mutex> lock(g_policy_mutex);
// A packet for a different split layout must not consume retained content,
// even when both layouts use immersive presentation.
if (scene.local_player_count != g_policy.scene.local_player_count) {
AdvanceSafetyGeneration(g_policy);
}
ApplyPolicyMutation([&] {
if (scene.mode != VRSceneMode::Race || g_policy.scene.mode != VRSceneMode::Race) {
// Never carry a camera sample across a menu/replay-to-race transition.
+79
View File
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "vr/mkw_vr_policy.h"
#include <cstring>
#include <iostream>
using namespace mkw::vr;
int main() {
int failures = 0;
const auto check = [&](bool condition, const char* message) {
if (!condition) {
std::cerr << message << '\n';
++failures;
}
};
MkwVRPolicyReset();
MkwVRPolicyConfig config{};
config.enabled = true;
MkwVRPolicyConfigure(config);
MkwVRPolicySetSessionActive(true);
MkwVRPolicySetAvailableBindings(kMkwVRRequiredImmersiveBindings);
MkwVRSceneObservation scene{VRSceneMode::Race, 1, 10};
MkwVRCameraObservation camera{};
camera.view_from_world = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0};
camera.guest_camera_address = 0x81000000;
camera.guest_frame_index = 10;
camera.valid = true;
MkwVRPolicyPublishScene(scene);
MkwVRPolicyPublishRaceCamera(camera);
uint64_t previous_tag = 0;
for (uint32_t players : {1u, 2u, 3u, 4u, 2u, 1u}) {
scene.local_player_count = players;
MkwVRPolicyPublishScene(scene);
const auto snapshot = MkwVRPolicyGetSnapshot();
check(snapshot.presentation == VRPresentationMode::ImmersiveRace, "1-4 players should be immersive");
check(snapshot.content_tag != previous_tag, "layout changes must invalidate retained packets");
previous_tag = snapshot.content_tag;
MkwVRPolicyPublishScene(scene);
MkwVRPolicyPublishRaceCamera(camera);
check(MkwVRPolicyGetSnapshot().content_tag == previous_tag, "steady frames must keep the same tag");
}
camera.guest_frame_index = 11;
MkwVRPolicyPublishRaceCamera(camera);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::ImmersiveRace,
"adjacent camera publication is coherent");
camera.guest_frame_index = 12;
MkwVRPolicyPublishRaceCamera(camera);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::VirtualScreen, "stale camera fails safe");
camera.guest_frame_index = 10;
MkwVRPolicyPublishRaceCamera(camera);
for (uint32_t players : {0u, 5u, UINT32_MAX}) {
scene.local_player_count = players;
MkwVRPolicyPublishScene(scene);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::VirtualScreen, "invalid counts fail safe");
}
scene.local_player_count = 4;
MkwVRPolicyPublishScene(scene);
const uint32_t nan_bits = 0x7fc00000;
std::memcpy(&camera.view_from_world[0], &nan_bits, sizeof(nan_bits));
MkwVRPolicyPublishRaceCamera(camera);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::VirtualScreen, "NaN camera fails under fast math");
camera.view_from_world[0] = 1;
MkwVRPolicyPublishRaceCamera(camera);
MkwVRPolicySetAvailableBindings(MkwVRBindingRaceCamera);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::VirtualScreen, "partial hooks fail safe");
MkwVRPolicySetAvailableBindings(kMkwVRRequiredImmersiveBindings);
scene.mode = VRSceneMode::FrontEnd;
MkwVRPolicyPublishScene(scene);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::VirtualScreen, "menus use virtual screen");
scene.mode = VRSceneMode::Race;
MkwVRPolicyPublishScene(scene);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::VirtualScreen, "race entry needs fresh camera");
MkwVRPolicyPublishRaceCamera(camera);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::ImmersiveRace, "race resumes with fresh camera");
MkwVRPolicySetSessionActive(false);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::Desktop, "inactive session uses desktop");
return failures == 0 ? 0 : 1;
}