mirror of
https://github.com/thejerf/baijiSteamVR.git
synced 2026-10-23 02:02:54 +02:00
350 lines
9.3 KiB
C++
350 lines
9.3 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "VideoCommon/OpenXROpcodeReplay.h"
|
|
|
|
#include <cmath>
|
|
#include <mutex>
|
|
#include <vector>
|
|
|
|
#include "VideoCommon/VideoConfig.h"
|
|
|
|
namespace VideoCommon::OpenXROpcodeReplay
|
|
{
|
|
namespace
|
|
{
|
|
struct ReplayClearState
|
|
{
|
|
bool color_enable = false;
|
|
bool alpha_enable = false;
|
|
bool z_enable = false;
|
|
PixelFormat pixel_format = PixelFormat::RGBA6_Z24;
|
|
u32 color = 0;
|
|
u32 clear_z_value = 0;
|
|
bool valid = false;
|
|
};
|
|
|
|
struct ReplayState
|
|
{
|
|
std::mutex mutex;
|
|
std::vector<u8> capture_preprocess;
|
|
std::vector<u8> capture_main;
|
|
std::vector<u8> replay_preprocess;
|
|
std::vector<u8> replay_main;
|
|
u64 replay_frame_index = 0;
|
|
u64 next_frame_index = 0;
|
|
bool capture_enabled = false;
|
|
bool replaying = false;
|
|
bool replay_frame_active = false;
|
|
bool replay_log_frame_active = false;
|
|
bool pending_immediate_swap = false;
|
|
bool pending_immediate_swap_is_frame_boundary = false;
|
|
u32 pending_xfb_addr = 0;
|
|
u32 pending_fb_width = 0;
|
|
u32 pending_fb_stride = 0;
|
|
u32 pending_fb_height = 0;
|
|
u32 replay_xfb_count = 0;
|
|
ReplayClearState clear_states[2];
|
|
};
|
|
|
|
ReplayState s_state;
|
|
|
|
bool IsConfiguredUnlocked()
|
|
{
|
|
return g_ActiveConfig.stereo_mode == StereoMode::OpenXR &&
|
|
g_ActiveConfig.vr_opcode_replay_mode != OpenXROpcodeReplayMode::Off;
|
|
}
|
|
|
|
void ClearUnlocked(ReplayState& state)
|
|
{
|
|
state.capture_preprocess.clear();
|
|
state.capture_main.clear();
|
|
state.replay_preprocess.clear();
|
|
state.replay_main.clear();
|
|
state.capture_enabled = false;
|
|
state.replaying = false;
|
|
state.replay_frame_active = false;
|
|
state.replay_log_frame_active = false;
|
|
state.pending_immediate_swap = false;
|
|
state.pending_immediate_swap_is_frame_boundary = false;
|
|
state.pending_xfb_addr = 0;
|
|
state.pending_fb_width = 0;
|
|
state.pending_fb_stride = 0;
|
|
state.pending_fb_height = 0;
|
|
state.replay_xfb_count = 0;
|
|
state.clear_states[0] = {};
|
|
state.clear_states[1] = {};
|
|
}
|
|
} // namespace
|
|
|
|
bool IsCaptureEnabled()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
s_state.capture_enabled = IsConfiguredUnlocked();
|
|
return s_state.capture_enabled;
|
|
}
|
|
|
|
bool IsReplaying()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
return s_state.replaying;
|
|
}
|
|
|
|
bool IsReplayFrameActive()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
return s_state.replay_frame_active;
|
|
}
|
|
|
|
bool IsReplayLogFrameActive()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
return s_state.replay_log_frame_active;
|
|
}
|
|
|
|
void EnableCaptureForNextFrame()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
s_state.capture_enabled = IsConfiguredUnlocked();
|
|
if (s_state.capture_enabled && !s_state.replaying)
|
|
s_state.replay_log_frame_active = true;
|
|
}
|
|
|
|
void CaptureCommand(bool is_preprocess, const u8* data, u32 size)
|
|
{
|
|
if (data == nullptr || size == 0)
|
|
return;
|
|
|
|
std::scoped_lock lock(s_state.mutex);
|
|
s_state.capture_enabled = IsConfiguredUnlocked();
|
|
if (!s_state.capture_enabled || s_state.replaying || !s_state.replay_log_frame_active)
|
|
return;
|
|
|
|
auto& stream = is_preprocess ? s_state.capture_preprocess : s_state.capture_main;
|
|
stream.insert(stream.end(), data, data + size);
|
|
}
|
|
|
|
void NotifyFrameBoundary()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
|
|
if (s_state.replaying)
|
|
return;
|
|
|
|
s_state.capture_enabled = IsConfiguredUnlocked();
|
|
if (!s_state.capture_enabled)
|
|
{
|
|
ClearUnlocked(s_state);
|
|
return;
|
|
}
|
|
|
|
if (s_state.capture_preprocess.empty() && s_state.capture_main.empty())
|
|
return;
|
|
|
|
s_state.replay_preprocess = std::move(s_state.capture_preprocess);
|
|
s_state.replay_main = std::move(s_state.capture_main);
|
|
s_state.capture_preprocess.clear();
|
|
s_state.capture_main.clear();
|
|
s_state.replay_frame_index = s_state.next_frame_index++;
|
|
s_state.replay_log_frame_active = false;
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
ClearUnlocked(s_state);
|
|
}
|
|
|
|
bool HasReplayData()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
return !s_state.replay_preprocess.empty() || !s_state.replay_main.empty();
|
|
}
|
|
|
|
int GetReplayCount(double display_period_ms)
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
|
|
s_state.capture_enabled = IsConfiguredUnlocked();
|
|
if (!s_state.capture_enabled || s_state.replaying ||
|
|
(s_state.replay_preprocess.empty() && s_state.replay_main.empty()))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
constexpr double target_period_ms = 1000.0 / 90.0;
|
|
constexpr double tolerance_ms = 0.8;
|
|
if (!(display_period_ms > 0.0 &&
|
|
std::abs(display_period_ms - target_period_ms) <= tolerance_ms))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
switch (g_ActiveConfig.vr_opcode_replay_mode)
|
|
{
|
|
case OpenXROpcodeReplayMode::Replay60To90:
|
|
return (s_state.replay_frame_index % 2) == 1 ? 1 : 0;
|
|
case OpenXROpcodeReplayMode::Replay30To90:
|
|
return 2;
|
|
case OpenXROpcodeReplayMode::Off:
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool BeginReplayIteration()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
if (s_state.replaying || (s_state.replay_preprocess.empty() && s_state.replay_main.empty()))
|
|
return false;
|
|
|
|
s_state.replaying = true;
|
|
s_state.capture_enabled = IsConfiguredUnlocked();
|
|
s_state.replay_frame_active = true;
|
|
s_state.replay_log_frame_active = false;
|
|
s_state.pending_immediate_swap = false;
|
|
s_state.pending_immediate_swap_is_frame_boundary = false;
|
|
s_state.pending_xfb_addr = 0;
|
|
s_state.pending_fb_width = 0;
|
|
s_state.pending_fb_stride = 0;
|
|
s_state.pending_fb_height = 0;
|
|
s_state.replay_xfb_count = 0;
|
|
return true;
|
|
}
|
|
|
|
void EndReplayIteration()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
s_state.replaying = false;
|
|
s_state.replay_frame_active = false;
|
|
s_state.replay_log_frame_active = false;
|
|
s_state.pending_immediate_swap = false;
|
|
s_state.pending_immediate_swap_is_frame_boundary = false;
|
|
s_state.pending_xfb_addr = 0;
|
|
s_state.pending_fb_width = 0;
|
|
s_state.pending_fb_stride = 0;
|
|
s_state.pending_fb_height = 0;
|
|
s_state.replay_xfb_count = 0;
|
|
}
|
|
|
|
void DiscardReplayFrame()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
s_state.replay_preprocess.clear();
|
|
s_state.replay_main.clear();
|
|
s_state.pending_immediate_swap = false;
|
|
s_state.pending_immediate_swap_is_frame_boundary = false;
|
|
s_state.pending_xfb_addr = 0;
|
|
s_state.pending_fb_width = 0;
|
|
s_state.pending_fb_stride = 0;
|
|
s_state.pending_fb_height = 0;
|
|
s_state.replay_xfb_count = 0;
|
|
}
|
|
|
|
void ApplyReplayClearState(bool frame_just_rendered, bool* color_enable, bool* alpha_enable,
|
|
bool* z_enable, PixelFormat* pixel_format, u32* color,
|
|
u32* clear_z_value)
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
s_state.capture_enabled = IsConfiguredUnlocked();
|
|
if (!s_state.capture_enabled || color_enable == nullptr || alpha_enable == nullptr ||
|
|
z_enable == nullptr || pixel_format == nullptr || color == nullptr ||
|
|
clear_z_value == nullptr)
|
|
return;
|
|
|
|
ReplayClearState current = {
|
|
.color_enable = *color_enable,
|
|
.alpha_enable = *alpha_enable,
|
|
.z_enable = *z_enable,
|
|
.pixel_format = *pixel_format,
|
|
.color = *color,
|
|
.clear_z_value = *clear_z_value,
|
|
.valid = true,
|
|
};
|
|
|
|
if (s_state.replay_frame_active && frame_just_rendered)
|
|
{
|
|
s_state.clear_states[0] = current;
|
|
return;
|
|
}
|
|
|
|
if (!s_state.replay_frame_active && !frame_just_rendered)
|
|
{
|
|
s_state.clear_states[1] = current;
|
|
return;
|
|
}
|
|
|
|
const ReplayClearState& selected =
|
|
!s_state.replay_frame_active && frame_just_rendered ? s_state.clear_states[0] :
|
|
s_state.clear_states[1];
|
|
if (!selected.valid)
|
|
return;
|
|
|
|
*color_enable = selected.color_enable;
|
|
*alpha_enable = selected.alpha_enable;
|
|
*z_enable = selected.z_enable;
|
|
*pixel_format = selected.pixel_format;
|
|
*color = selected.color;
|
|
*clear_z_value = selected.clear_z_value;
|
|
}
|
|
|
|
void RecordReplayImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height,
|
|
bool frame_just_rendered)
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
if (!s_state.replaying)
|
|
return;
|
|
|
|
s_state.replay_xfb_count++;
|
|
|
|
if (s_state.pending_immediate_swap && (s_state.pending_immediate_swap_is_frame_boundary ||
|
|
!frame_just_rendered))
|
|
{
|
|
return;
|
|
}
|
|
|
|
s_state.pending_immediate_swap = true;
|
|
s_state.pending_immediate_swap_is_frame_boundary = frame_just_rendered;
|
|
s_state.pending_xfb_addr = xfb_addr;
|
|
s_state.pending_fb_width = fb_width;
|
|
s_state.pending_fb_stride = fb_stride;
|
|
s_state.pending_fb_height = fb_height;
|
|
}
|
|
|
|
bool ConsumeReplayImmediateSwap(u32* xfb_addr, u32* fb_width, u32* fb_stride, u32* fb_height)
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
if (!s_state.replaying || !s_state.pending_immediate_swap)
|
|
return false;
|
|
|
|
if (xfb_addr)
|
|
*xfb_addr = s_state.pending_xfb_addr;
|
|
if (fb_width)
|
|
*fb_width = s_state.pending_fb_width;
|
|
if (fb_stride)
|
|
*fb_stride = s_state.pending_fb_stride;
|
|
if (fb_height)
|
|
*fb_height = s_state.pending_fb_height;
|
|
|
|
s_state.pending_immediate_swap = false;
|
|
s_state.pending_immediate_swap_is_frame_boundary = false;
|
|
s_state.pending_xfb_addr = 0;
|
|
s_state.pending_fb_width = 0;
|
|
s_state.pending_fb_stride = 0;
|
|
s_state.pending_fb_height = 0;
|
|
return true;
|
|
}
|
|
|
|
u32 GetReplayXFBCount()
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
return s_state.replay_xfb_count;
|
|
}
|
|
|
|
std::span<const u8> GetReplayCommands(bool is_preprocess)
|
|
{
|
|
std::scoped_lock lock(s_state.mutex);
|
|
const auto& stream = is_preprocess ? s_state.replay_preprocess : s_state.replay_main;
|
|
return std::span<const u8>(stream.data(), stream.size());
|
|
}
|
|
} // namespace VideoCommon::OpenXROpcodeReplay
|