mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 01:00:14 +02:00
Implement more detailed diagnostics for OpenXR stages
This commit is contained in:
1 parent
c4c7078afc
commit
787dbcf7d2
8 files changed
+212
-34
No files matched your search
@@ -414,22 +414,49 @@ When it is on, `console.log` receives lines tagged `[runtime] [xr-diag]`
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `Hz`, `cycles` | Display rate from the predicted display period; compositor cycles (xrWaitFrame/xrEndFrame pairs, repeats included). |
|
||||
| `predicted-rate`, `cycles` | Reciprocal of the runtime's predicted display period, **not necessarily physical headset refresh rate**; compositor cycles (xrWaitFrame/xrEndFrame pairs, repeats included). |
|
||||
| `skipped-slots` | Display slots the predicted display time jumped over: the runtime throttled or dropped frames. |
|
||||
| `late` | Frames whose xrEndFrame came after their predicted display time (needs `XR_KHR_win32_convert_performance_counter_time` or `XR_KHR_convert_timespec_time`). |
|
||||
| `layers new/repeat/empty` | Cycles ending with a newly rendered layer, the retained layer again, or no layer at all (black). |
|
||||
| `discarded`, `layer-rejected` | Retained layers dropped by a session or reference-space change; rendered layers not submitted (invalid pose or views, failed release). |
|
||||
| `wait-frame`, `open`, `end-call` | Time blocked in xrWaitFrame, from xrBeginFrame to xrEndFrame, and inside xrEndFrame. |
|
||||
| `end-margin`, `end-gap` | Predicted display time minus the xrEndFrame time; interval between xrEndFrame calls. |
|
||||
| `pickup`, `render` | Stereo packet published until Aurora's frame worker takes it (without interpolation this includes waiting for the next 60 Hz game frame); taken until the eye copy is submitted. |
|
||||
| `pickup`, `render` | Stereo packet published until Aurora's frame worker takes it (without interpolation this includes waiting for the next 60 Hz game frame); taken until the pacing thread observes the submission result. These are CPU wall times for completed submissions, **not GPU timestamps**; canceled packets are measured separately below. |
|
||||
| `acquire`, `release` | Swapchain image acquire+wait and release. |
|
||||
| `keepalive` | Retained-layer repeats while Aurora was still encoding past the 50 ms keep-alive. |
|
||||
| `packet-unused`, `packet-rejected`, `submit-failed` | Packets no game frame took within 50 ms; packets Aurora took but rendered mono (content tag or transform check); failed stereo copies. |
|
||||
| `packet-unused`, `packet-rejected`, `submit-failed` | Packets not picked up before cancellation; packets picked up but not encoded by the bridge before cancellation (the precise rejection cause is not known); failed stereo copies. |
|
||||
| `interp-skip` | Cycles the VR interpolation rate cap chose not to render. |
|
||||
| `frames immersive/screen` | Cycles per presentation mode; `not-rendered` counts cycles without views to render. |
|
||||
| `no-orientation`, `no-position` | Cycles whose head orientation or position was not valid. |
|
||||
| `suppressed` | Event lines dropped by the rate limit. |
|
||||
|
||||
- **Stage timings.** A separate `[xr-diag] stages ms` line accompanies each nonempty
|
||||
window, independently of the event rate limit. Each field is `median/worst` in ms;
|
||||
`@cycle=N,t=Ts` identifies the worst call's diagnostic cycle and completion time
|
||||
since logging/session reset. The main summary also includes the last `cycle` and `t`.
|
||||
Cycle 0 is before the first wait; work between cycles belongs to the previous cycle.
|
||||
These are wall times, including time the OS did not schedule the thread. Nested
|
||||
measurements (notably `sync-actions` within `input-sync`) must not be added together.
|
||||
|
||||
| Stage | What it isolates |
|
||||
| --- | --- |
|
||||
| `poll-events`, `begin-call`, `locate-views` | Event polling, the xrBeginFrame call itself, and xrLocateViews. |
|
||||
| `input-sync`, `sync-actions` | Complete input update and its xrSyncActions call. |
|
||||
| `publish`, `withdraw` | Packet construction/publication and withdrawal, including mutex waits. |
|
||||
| `set-targets` | D3D12 bridge target registration, including its mutex wait. |
|
||||
| `submission-wait` | Actual time waiting for a render result, including timeout paths; compare against the requested 50 ms. |
|
||||
| `cancel` | Bridge cancellation attempt, whether it succeeds or fails. |
|
||||
| `cancel-age` | Publication to cancellation, including packets never picked up. |
|
||||
| `cancel-pickup`, `cancel-after-pickup` | Publication to pickup and pickup to cancellation for consumed, canceled packets. |
|
||||
|
||||
For a blackout report, enable logging before entering a race, reproduce the blackout,
|
||||
and export the logs immediately afterward. Include the approximate time and whether
|
||||
both eyes and the desktop mirror went black. Check `frames immersive` is nonzero for
|
||||
an immersive-race capture. A large stage maximum identifies where the pacing thread
|
||||
spent time, but cannot distinguish API blocking from OS scheduling without a system
|
||||
trace. No empty layers does not rule out black image contents or compositor/display
|
||||
problems. This instrumentation does not change frame pacing or inspect image pixels.
|
||||
|
||||
- **Event lines.** At most 8 per second; the rest are counted in `suppressed`. They report late
|
||||
frames, skipped display slots, stalls (more than 2.5 display periods, and at least 25 ms, between
|
||||
xrEndFrame calls), empty frames and their reason, discarded retained layers, rejected layers,
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
// the settings overlay and the headless tests can use it in any build.
|
||||
namespace mkw::vr::diagnostics {
|
||||
|
||||
// Pacing-thread wall time, including scheduling delays. Stages may nest.
|
||||
enum class Stage : uint8_t {
|
||||
PollEvents, BeginCall, LocateViews, InputSync, SyncActions, Publish,
|
||||
Withdraw, SubmissionWait, Cancel, SetTargets, Count
|
||||
};
|
||||
|
||||
enum class EmptyFrameReason : uint8_t {
|
||||
NoRetainedLayer,
|
||||
ShouldRenderOff,
|
||||
@@ -105,7 +111,8 @@ public:
|
||||
void OnInterpolationSkip();
|
||||
void OnPacketPublished(int64_t now_ns);
|
||||
// consumed_ns is when Aurora took the packet, or 0 if it never did.
|
||||
void OnPacketCanceled(int64_t consumed_ns);
|
||||
void OnPacketCanceled(int64_t now_ns, int64_t consumed_ns);
|
||||
void OnStage(Stage stage, int64_t ns, int64_t now_ns);
|
||||
void OnKeepaliveRepeat();
|
||||
void OnSubmission(int64_t now_ns, int64_t consumed_ns, bool success);
|
||||
|
||||
@@ -168,6 +175,18 @@ private:
|
||||
uint32_t events_ = 0;
|
||||
uint32_t suppressed_ = 0;
|
||||
|
||||
struct StageSamples {
|
||||
Samples samples;
|
||||
int64_t worst_ns = -1;
|
||||
int64_t worst_at_ns = 0;
|
||||
uint64_t cycle = 0;
|
||||
};
|
||||
std::array<StageSamples, static_cast<size_t>(Stage::Count)> stages_{};
|
||||
int64_t session_start_ns_ = 0;
|
||||
uint64_t cycle_sequence_ = 0;
|
||||
Samples cancel_age_ms_;
|
||||
Samples cancel_pickup_ms_;
|
||||
Samples cancel_after_pickup_ms_;
|
||||
Samples wait_frame_ms_;
|
||||
Samples open_ms_;
|
||||
Samples margin_ms_;
|
||||
@@ -184,6 +203,7 @@ inline std::atomic_bool g_enabled{false};
|
||||
inline std::atomic_int64_t g_packet_consumed_ns{0};
|
||||
|
||||
int64_t NowNs() noexcept;
|
||||
void OnStage(Stage stage, int64_t ns);
|
||||
void OnWaitFrame(int64_t wait_ns, int64_t display_time, int64_t display_period);
|
||||
void OnBeginFrame();
|
||||
void OnEndFrame(int64_t submit_ns, int64_t call_ns);
|
||||
@@ -233,6 +253,30 @@ private:
|
||||
int64_t start_ns_;
|
||||
};
|
||||
|
||||
// Only use on the XR pacing thread. A diagnostic failure must not interrupt
|
||||
// frame ownership or change the instrumented call's return value.
|
||||
class ScopedStage {
|
||||
public:
|
||||
explicit ScopedStage(Stage stage) noexcept : stage_(stage) {}
|
||||
~ScopedStage() noexcept {
|
||||
const int64_t ns = timer_.ElapsedNs();
|
||||
if (ns >= 0 && Enabled()) {
|
||||
try { detail::OnStage(stage_, ns); } catch (...) {}
|
||||
}
|
||||
}
|
||||
ScopedStage(const ScopedStage&) = delete;
|
||||
ScopedStage& operator=(const ScopedStage&) = delete;
|
||||
private:
|
||||
Stage stage_;
|
||||
Stopwatch timer_;
|
||||
};
|
||||
|
||||
template <typename Call>
|
||||
decltype(auto) Measure(Stage stage, Call&& call) {
|
||||
const ScopedStage timer(stage);
|
||||
return call();
|
||||
}
|
||||
|
||||
inline void OnWaitFrame(const Stopwatch& wait, int64_t display_time, int64_t display_period) {
|
||||
if (const int64_t ns = wait.ElapsedNs(); ns >= 0 && Enabled()) detail::OnWaitFrame(ns, display_time, display_period);
|
||||
}
|
||||
|
||||
@@ -316,7 +316,9 @@ public:
|
||||
submission_success_ = false;
|
||||
submission_unsafe_ = false;
|
||||
}
|
||||
if (!aurora_d3d12_set_stereo_targets(frame.xr_frame.serial, targets.data(), target_count)) {
|
||||
if (!diagnostics::Measure(diagnostics::Stage::SetTargets, [&] {
|
||||
return aurora_d3d12_set_stereo_targets(frame.xr_frame.serial, targets.data(), target_count);
|
||||
})) {
|
||||
{
|
||||
std::lock_guard lock(submission_mutex_);
|
||||
awaiting_token_ = 0;
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
namespace mkw::vr::diagnostics {
|
||||
namespace {
|
||||
|
||||
constexpr std::array<const char*, static_cast<size_t>(Stage::Count)> kStageNames{
|
||||
"poll-events", "begin-call", "locate-views", "input-sync", "sync-actions",
|
||||
"publish", "withdraw", "submission-wait", "cancel", "set-targets"};
|
||||
|
||||
constexpr double kNanosecondsPerMillisecond = 1'000'000.0;
|
||||
|
||||
double Milliseconds(int64_t nanoseconds) noexcept {
|
||||
@@ -123,6 +127,7 @@ void FrameDiagnostics::Reset(int64_t now_ns) {
|
||||
Sink sink = std::move(sink_);
|
||||
*this = FrameDiagnostics(std::move(sink));
|
||||
window_start_ns_ = now_ns;
|
||||
session_start_ns_ = now_ns;
|
||||
session_info_requested_ = true;
|
||||
}
|
||||
|
||||
@@ -136,6 +141,7 @@ void FrameDiagnostics::OnWaitFrame(int64_t now_ns, int64_t wait_ns, int64_t disp
|
||||
window_start_ns_ = now_ns;
|
||||
}
|
||||
++cycles_;
|
||||
++cycle_sequence_;
|
||||
wait_frame_ms_.Add(Milliseconds(wait_ns));
|
||||
if (display_period > 0) {
|
||||
display_period_ = display_period;
|
||||
@@ -280,14 +286,31 @@ void FrameDiagnostics::OnPacketPublished(int64_t now_ns) {
|
||||
published_ns_ = now_ns;
|
||||
}
|
||||
|
||||
void FrameDiagnostics::OnPacketCanceled(int64_t consumed_ns) {
|
||||
void FrameDiagnostics::OnStage(Stage stage, int64_t ns, int64_t now_ns) {
|
||||
if (ns < 0 || static_cast<size_t>(stage) >= stages_.size()) return;
|
||||
auto& sample = stages_[static_cast<size_t>(stage)];
|
||||
sample.samples.Add(Milliseconds(ns));
|
||||
if (ns > sample.worst_ns) {
|
||||
sample.worst_ns = ns;
|
||||
sample.worst_at_ns = now_ns;
|
||||
sample.cycle = cycle_sequence_;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameDiagnostics::OnPacketCanceled(int64_t now_ns, int64_t consumed_ns) {
|
||||
if (published_ns_ != 0 && now_ns >= published_ns_) {
|
||||
cancel_age_ms_.Add(Milliseconds(now_ns - published_ns_));
|
||||
if (consumed_ns >= published_ns_ && consumed_ns <= now_ns) {
|
||||
cancel_pickup_ms_.Add(Milliseconds(consumed_ns - published_ns_));
|
||||
cancel_after_pickup_ms_.Add(Milliseconds(now_ns - consumed_ns));
|
||||
}
|
||||
}
|
||||
if (consumed_ns == 0 || consumed_ns < published_ns_) {
|
||||
++packet_unused_;
|
||||
Event("stereo packet withdrawn: no game frame picked it up within 50 ms");
|
||||
Event("stereo packet withdrawn: no game frame picked it up before cancellation");
|
||||
} else {
|
||||
++packet_rejected_;
|
||||
Event("stereo packet canceled: Aurora picked it up but rendered that frame without it "
|
||||
"(content tag or transform check)");
|
||||
Event("stereo packet canceled: Aurora picked it up but the bridge had not encoded it before cancellation");
|
||||
}
|
||||
published_ns_ = 0;
|
||||
}
|
||||
@@ -355,8 +378,13 @@ void FrameDiagnostics::ClearWindow(int64_t now_ns) {
|
||||
keepalive_ = packet_unused_ = packet_rejected_ = submit_failed_ = interp_skip_ = 0;
|
||||
immersive_ = screen_ = not_rendered_ = no_orientation_ = no_position_ = 0;
|
||||
events_ = suppressed_ = 0;
|
||||
for (auto& stage : stages_) {
|
||||
stage.samples.Clear();
|
||||
stage.worst_ns = -1;
|
||||
}
|
||||
for (Samples* samples : {&wait_frame_ms_, &open_ms_, &margin_ms_, &end_gap_ms_, &end_call_ms_,
|
||||
&acquire_ms_, &release_ms_, &game_wait_ms_, &encode_ms_}) {
|
||||
&acquire_ms_, &release_ms_, &game_wait_ms_, &encode_ms_,
|
||||
&cancel_age_ms_, &cancel_pickup_ms_, &cancel_after_pickup_ms_}) {
|
||||
samples->Clear();
|
||||
}
|
||||
}
|
||||
@@ -365,7 +393,7 @@ void FrameDiagnostics::EmitSummary(int64_t now_ns) {
|
||||
std::string text;
|
||||
AppendFormat(text, "%.2fs", Milliseconds(now_ns - window_start_ns_) / 1000.0);
|
||||
if (display_period_ > 0) {
|
||||
AppendFormat(text, " %.1fHz", 1.0e9 / static_cast<double>(display_period_));
|
||||
AppendFormat(text, " predicted-rate=%.1fHz", 1.0e9 / static_cast<double>(display_period_));
|
||||
}
|
||||
AppendFormat(text, " cycles=%u skipped-slots=%u late=%u", cycles_, skipped_slots_, late_);
|
||||
AppendFormat(text, " | layers new=%u repeat=%u empty=%u discarded=%u layer-rejected=%u", layers_new_,
|
||||
@@ -384,8 +412,26 @@ void FrameDiagnostics::EmitSummary(int64_t now_ns) {
|
||||
keepalive_, packet_unused_, packet_rejected_, submit_failed_, interp_skip_);
|
||||
AppendFormat(text, " | frames immersive=%u screen=%u not-rendered=%u no-orientation=%u no-position=%u",
|
||||
immersive_, screen_, not_rendered_, no_orientation_, no_position_);
|
||||
AppendFormat(text, " | suppressed=%u", suppressed_);
|
||||
AppendFormat(text, " | suppressed=%u cycle=%llu t=%.3fs", suppressed_,
|
||||
static_cast<unsigned long long>(cycle_sequence_),
|
||||
static_cast<double>(now_ns - session_start_ns_) / 1.0e9);
|
||||
Info(text);
|
||||
// Skipped-slot event spam must not suppress evidence of a blocking call.
|
||||
std::string stages = "stages ms";
|
||||
for (size_t i = 0; i < stages_.size(); ++i) {
|
||||
auto& sample = stages_[i];
|
||||
AppendStat(stages, kStageNames[i], sample.samples.values, false);
|
||||
if (sample.worst_ns >= 0) {
|
||||
AppendFormat(stages, "@cycle=%llu,t=%.3fs",
|
||||
static_cast<unsigned long long>(sample.cycle),
|
||||
static_cast<double>(sample.worst_at_ns - session_start_ns_) / 1.0e9);
|
||||
}
|
||||
}
|
||||
AppendStat(stages, "cancel-age", cancel_age_ms_.values, false);
|
||||
AppendStat(stages, "cancel-pickup", cancel_pickup_ms_.values, false);
|
||||
AppendStat(stages, "cancel-after-pickup", cancel_after_pickup_ms_.values, false);
|
||||
if (!cancel_age_ms_.values.empty() || std::any_of(stages_.begin(), stages_.end(),
|
||||
[](const auto& stage) { return stage.worst_ns >= 0; })) Info(stages);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -473,6 +519,10 @@ void OnWaitFrame(int64_t wait_ns, int64_t display_time, int64_t display_period)
|
||||
collector.OnWaitFrame(NowNs(), wait_ns, display_time, display_period, ConvertDisplayTime(display_time));
|
||||
}
|
||||
|
||||
void OnStage(Stage stage, int64_t ns) {
|
||||
Collector().OnStage(stage, ns, NowNs());
|
||||
}
|
||||
|
||||
void OnBeginFrame() {
|
||||
Collector().OnBeginFrame(NowNs());
|
||||
}
|
||||
@@ -524,7 +574,7 @@ void OnPacketPublished() {
|
||||
}
|
||||
|
||||
void OnPacketCanceled() {
|
||||
Collector().OnPacketCanceled(g_packet_consumed_ns.load(std::memory_order_relaxed));
|
||||
Collector().OnPacketCanceled(NowNs(), g_packet_consumed_ns.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
void OnKeepaliveRepeat() {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endif
|
||||
|
||||
#include "vr/openxr_input.h"
|
||||
#include "vr/openxr_diagnostics.h"
|
||||
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
#include <SDL3/SDL_joystick.h>
|
||||
@@ -518,7 +519,9 @@ void OpenXRInput::Sync(XrTime predicted_display_time, const OpenXRPointerScreen&
|
||||
XrActionsSyncInfo sync{XR_TYPE_ACTIONS_SYNC_INFO};
|
||||
sync.countActiveActionSets = 1;
|
||||
sync.activeActionSets = &active;
|
||||
const XrResult result = xrSyncActions(m_runtime->Session(), &sync);
|
||||
const XrResult result = diagnostics::Measure(diagnostics::Stage::SyncActions, [&] {
|
||||
return xrSyncActions(m_runtime->Session(), &sync);
|
||||
});
|
||||
m_runtime->ObserveResult(result);
|
||||
if (XR_FAILED(result)) {
|
||||
if (!m_logged_sync_failure) {
|
||||
|
||||
@@ -736,7 +736,9 @@ private:
|
||||
gx_registered = RegisterGxThread();
|
||||
}
|
||||
#endif
|
||||
const OpenXREventStatus events = runtime_->PollEvents();
|
||||
const OpenXREventStatus events = diagnostics::Measure(diagnostics::Stage::PollEvents, [&] {
|
||||
return runtime_->PollEvents();
|
||||
});
|
||||
const bool session_active = runtime_->IsSessionRunning();
|
||||
MkwVRPolicySetSessionActive(session_active);
|
||||
const uint64_t session_run_serial = runtime_->SessionRunSerial();
|
||||
@@ -859,6 +861,7 @@ private:
|
||||
ServiceRecenterRequest();
|
||||
UpdateVirtualScreenPose(frame);
|
||||
if (input_ != nullptr) {
|
||||
const diagnostics::ScopedStage input_timer(diagnostics::Stage::InputSync);
|
||||
// After the screen is placed, so the pointer aims at this
|
||||
// frame's screen rather than the previous one's.
|
||||
input_->Sync(frame.xr_frame.predicted_display_time, PointerScreen(frame, policy, immersive),
|
||||
@@ -876,7 +879,9 @@ private:
|
||||
if (aurora_get_stereo_frame_interpolation() &&
|
||||
!interpolation_pacing_.ShouldRender(frame.xr_frame.predicted_display_time, interpolation_target)) {
|
||||
diagnostics::OnInterpolationSkip();
|
||||
if (!backend_->TryCancelPendingFrame(frame) || !backend_->FinishFrame(frame, false)) {
|
||||
if (!diagnostics::Measure(diagnostics::Stage::Cancel, [&] {
|
||||
return backend_->TryCancelPendingFrame(frame);
|
||||
}) || !backend_->FinishFrame(frame, false)) {
|
||||
SetError(backend_->LastError());
|
||||
fatal = true;
|
||||
}
|
||||
@@ -884,6 +889,7 @@ private:
|
||||
}
|
||||
|
||||
{
|
||||
const diagnostics::ScopedStage publish_timer(diagnostics::Stage::Publish);
|
||||
std::lock_guard lock(published_mutex_);
|
||||
// First person renders at life-size scale, third person at the
|
||||
// configured diorama scale. Head translation and IPD are the
|
||||
@@ -904,14 +910,18 @@ private:
|
||||
submission == OpenXRSubmissionStatus::Timeout) {
|
||||
// Fresh rendering wakes us immediately. A 50 ms keep-alive
|
||||
// protects stalls without issuing eager repeats during GPU work.
|
||||
submission = backend_->WaitForSubmission(frame, 50);
|
||||
submission = diagnostics::Measure(diagnostics::Stage::SubmissionWait, [&] {
|
||||
return backend_->WaitForSubmission(frame, 50);
|
||||
});
|
||||
if (submission == OpenXRSubmissionStatus::Timeout) {
|
||||
// A pause, minimized window, or guest stall may leave no GX
|
||||
// frame to consume this packet. Withdraw it, then cancel the
|
||||
// matching bridge target only if Encode has not taken ownership.
|
||||
if (std::chrono::steady_clock::now() >= cancel_after) {
|
||||
WithdrawPublishedFrame();
|
||||
canceled_before_encode = backend_->TryCancelPendingFrame(frame);
|
||||
diagnostics::Measure(diagnostics::Stage::Withdraw, [&] { WithdrawPublishedFrame(); });
|
||||
canceled_before_encode = diagnostics::Measure(diagnostics::Stage::Cancel, [&] {
|
||||
return backend_->TryCancelPendingFrame(frame);
|
||||
});
|
||||
if (canceled_before_encode) {
|
||||
diagnostics::OnPacketCanceled();
|
||||
break;
|
||||
@@ -925,7 +935,7 @@ private:
|
||||
}
|
||||
}
|
||||
}
|
||||
WithdrawPublishedFrame();
|
||||
diagnostics::Measure(diagnostics::Stage::Withdraw, [&] { WithdrawPublishedFrame(); });
|
||||
if (stop_.load(std::memory_order_acquire)) {
|
||||
// Aurora has been drained by Shutdown(); backend shutdown below
|
||||
// cancels its pending target, then either safely releases the
|
||||
@@ -1035,6 +1045,7 @@ private:
|
||||
ServiceRecenterRequest();
|
||||
UpdateVirtualScreenPose(packet);
|
||||
if (input_ != nullptr) {
|
||||
const diagnostics::ScopedStage input_timer(diagnostics::Stage::InputSync);
|
||||
input_->Sync(packet.xr_frame.predicted_display_time, PointerScreen(packet, policy, immersive),
|
||||
SettingsPanelScreen(packet, policy, immersive));
|
||||
}
|
||||
@@ -1043,6 +1054,7 @@ private:
|
||||
return KeepAlive();
|
||||
}
|
||||
{
|
||||
const diagnostics::ScopedStage publish_timer(diagnostics::Stage::Publish);
|
||||
std::lock_guard lock(published_mutex_);
|
||||
BuildPublishedFrame(packet, immersive, policy.EffectiveUnitsPerMeter(), policy.content_tag);
|
||||
diagnostics::OnPacketPublished();
|
||||
@@ -1056,11 +1068,15 @@ private:
|
||||
bool canceled_before_encode = false;
|
||||
const auto cancel_after = std::chrono::steady_clock::now() + std::chrono::milliseconds(50);
|
||||
while (!stop_.load(std::memory_order_acquire) && submission == OpenXRSubmissionStatus::Timeout) {
|
||||
submission = backend_->WaitForSubmission(packet, 50);
|
||||
submission = diagnostics::Measure(diagnostics::Stage::SubmissionWait, [&] {
|
||||
return backend_->WaitForSubmission(packet, 50);
|
||||
});
|
||||
if (submission == OpenXRSubmissionStatus::Timeout) {
|
||||
if (std::chrono::steady_clock::now() >= cancel_after) {
|
||||
WithdrawPublishedFrame();
|
||||
canceled_before_encode = backend_->TryCancelPendingPacket(packet);
|
||||
diagnostics::Measure(diagnostics::Stage::Withdraw, [&] { WithdrawPublishedFrame(); });
|
||||
canceled_before_encode = diagnostics::Measure(diagnostics::Stage::Cancel, [&] {
|
||||
return backend_->TryCancelPendingPacket(packet);
|
||||
});
|
||||
if (canceled_before_encode) {
|
||||
diagnostics::OnPacketCanceled();
|
||||
break;
|
||||
@@ -1072,7 +1088,7 @@ private:
|
||||
}
|
||||
}
|
||||
}
|
||||
WithdrawPublishedFrame();
|
||||
diagnostics::Measure(diagnostics::Stage::Withdraw, [&] { WithdrawPublishedFrame(); });
|
||||
if (stop_.load(std::memory_order_acquire) || canceled_before_encode ||
|
||||
submission == OpenXRSubmissionStatus::ShuttingDown) {
|
||||
return true;
|
||||
|
||||
@@ -723,7 +723,9 @@ bool OpenXRRuntime::BeginFrame(const OpenXRFrame& frame) {
|
||||
}
|
||||
|
||||
XrFrameBeginInfo begin_info{XR_TYPE_FRAME_BEGIN_INFO};
|
||||
const XrResult result = xrBeginFrame(m_session, &begin_info);
|
||||
const XrResult result = diagnostics::Measure(diagnostics::Stage::BeginCall, [&] {
|
||||
return xrBeginFrame(m_session, &begin_info);
|
||||
});
|
||||
if (XR_FAILED(result)) {
|
||||
m_frame_phase = FramePhase::Idle;
|
||||
m_active_frame_serial = 0;
|
||||
@@ -769,9 +771,11 @@ bool OpenXRRuntime::LocateViewsForFrame(OpenXRFrame& frame) {
|
||||
locate_info.space = m_app_space;
|
||||
XrViewState view_state{XR_TYPE_VIEW_STATE};
|
||||
uint32_t view_count = 0;
|
||||
if (!Check(xrLocateViews(m_session, &locate_info, &view_state,
|
||||
kOpenXREyeCount, &view_count, frame.views.data()),
|
||||
"xrLocateViews")) {
|
||||
const XrResult locate_result = diagnostics::Measure(diagnostics::Stage::LocateViews, [&] {
|
||||
return xrLocateViews(m_session, &locate_info, &view_state,
|
||||
kOpenXREyeCount, &view_count, frame.views.data());
|
||||
});
|
||||
if (!Check(locate_result, "xrLocateViews")) {
|
||||
return false;
|
||||
}
|
||||
if (view_count != kOpenXREyeCount) {
|
||||
|
||||
@@ -171,25 +171,53 @@ void TestPacketAccounting() {
|
||||
diagnostics.OnSubmission(now + 18 * kMs, now + 15 * kMs, true);
|
||||
|
||||
diagnostics.OnPacketPublished(now + 20 * kMs);
|
||||
diagnostics.OnPacketCanceled(0);
|
||||
Require(capture.Count("no game frame picked it up within 50 ms") == 1);
|
||||
diagnostics.OnPacketCanceled(now + 75 * kMs, 0);
|
||||
Require(capture.Count("no game frame picked it up before cancellation") == 1);
|
||||
|
||||
diagnostics.OnPacketPublished(now + 80 * kMs);
|
||||
diagnostics.OnPacketCanceled(now + 90 * kMs);
|
||||
Require(capture.Count("Aurora picked it up but rendered that frame without it") == 1);
|
||||
diagnostics.OnPacketCanceled(now + 280 * kMs, now + 90 * kMs);
|
||||
Require(capture.Count("Aurora picked it up but the bridge had not encoded it") == 1);
|
||||
|
||||
diagnostics.OnPacketPublished(now + 200 * kMs);
|
||||
diagnostics.OnSubmission(now + 210 * kMs, now + 205 * kMs, false);
|
||||
diagnostics.OnPacketPublished(now + 400 * kMs);
|
||||
diagnostics.OnSubmission(now + 410 * kMs, now + 405 * kMs, false);
|
||||
Require(capture.Count("stereo submission failed") == 1);
|
||||
|
||||
diagnostics.Tick(now + 1'500 * kMs);
|
||||
const std::string& summary = capture.Summary();
|
||||
Require(summary.find("packet-unused=1 packet-rejected=1 submit-failed=1") != std::string::npos);
|
||||
Require(capture.Count("cancel-age=200.0/200.0") == 1);
|
||||
Require(capture.Count("cancel-pickup=10.0/10.0") == 1);
|
||||
Require(capture.Count("cancel-after-pickup=190.0/190.0") == 1);
|
||||
// Two submissions: pickup 15 and 5 ms, render 3 and 5 ms.
|
||||
Require(summary.find("pickup=15.0/15.0") != std::string::npos);
|
||||
Require(summary.find("render=5.0/5.0") != std::string::npos);
|
||||
}
|
||||
|
||||
void TestStageTimingSurvivesEventFlood() {
|
||||
Capture capture;
|
||||
FrameDiagnostics diagnostics(capture.Sink());
|
||||
const int64_t start = 10'000 * kMs;
|
||||
diagnostics.Reset(start);
|
||||
diagnostics.OnWaitFrame(start, 0, 1, kPeriod72Hz, 0);
|
||||
for (int i = 0; i < 20; ++i) diagnostics.OnEmptyFrame(EmptyFrameReason::NoRetainedLayer);
|
||||
diagnostics.OnStage(Stage::SyncActions, 2 * kMs, start + 2 * kMs);
|
||||
diagnostics.OnStage(Stage::SyncActions, 180 * kMs, start + 500 * kMs);
|
||||
diagnostics.OnStage(Stage::SyncActions, 3 * kMs, start + 600 * kMs);
|
||||
diagnostics.OnStage(Stage::Cancel, -1, start);
|
||||
diagnostics.Tick(start + 1'000 * kMs);
|
||||
Require(capture.Count("sync-actions=3.0/180.0@cycle=1,t=0.500s") == 1);
|
||||
Require(capture.Count("cancel=-") == 1);
|
||||
Require(capture.Summary().find("suppressed=12") != std::string::npos);
|
||||
diagnostics.OnStage(Stage::Cancel, 4 * kMs, start + 1'100 * kMs);
|
||||
diagnostics.Tick(start + 2'000 * kMs);
|
||||
Require(capture.Count("sync-actions=-") == 1);
|
||||
Require(capture.Count("cancel=4.0/4.0@cycle=1,t=1.100s") == 1);
|
||||
diagnostics.Reset(start + 3'000 * kMs);
|
||||
diagnostics.OnStage(Stage::Cancel, kMs, start + 3'010 * kMs);
|
||||
diagnostics.Tick(start + 4'000 * kMs);
|
||||
Require(capture.Count("cancel=1.0/1.0@cycle=0,t=0.010s") == 1);
|
||||
}
|
||||
|
||||
void TestViewGeometry() {
|
||||
Capture capture;
|
||||
FrameDiagnostics diagnostics(capture.Sink());
|
||||
@@ -249,6 +277,9 @@ void TestGlobalSwitchAndConfig() {
|
||||
const Stopwatch stopwatch;
|
||||
Require(stopwatch.ElapsedNs() == -1);
|
||||
}
|
||||
int calls = 0;
|
||||
Require(Measure(Stage::Cancel, [&] { ++calls; return 42; }) == 42);
|
||||
Require(calls == 1);
|
||||
OnEmptyFrame(EmptyFrameReason::NoRetainedLayer);
|
||||
Require(lines.empty());
|
||||
|
||||
@@ -284,6 +315,7 @@ int main() {
|
||||
TestEventsAreRateLimited();
|
||||
TestTrackingEdgesAndRejections();
|
||||
TestPacketAccounting();
|
||||
TestStageTimingSurvivesEventFlood();
|
||||
TestViewGeometry();
|
||||
TestResetStartsANewSession();
|
||||
TestGlobalSwitchAndConfig();
|
||||
|
||||
Reference in new issue
Block a user