diff --git a/hw/xbox/mcpx/apu/apu.c b/hw/xbox/mcpx/apu/apu.c index bdb6d6db54..265c4485cf 100644 --- a/hw/xbox/mcpx/apu/apu.c +++ b/hw/xbox/mcpx/apu/apu.c @@ -102,30 +102,97 @@ static const MemoryRegionOps mcpx_apu_mmio_ops = { .write = mcpx_apu_write, }; +static void throttle_update_debug(MCPXAPUState *d, int64_t now_us) +{ + if (d->throttle.deviation.last_us > 0) { + int64_t dev = (now_us - d->throttle.deviation.last_us) - EP_FRAME_US; + if (d->throttle.deviation.count == 0) { + d->throttle.deviation.min_us = d->throttle.deviation.max_us = dev; + } else { + d->throttle.deviation.min_us = MIN(dev, d->throttle.deviation.min_us); + d->throttle.deviation.max_us = MAX(dev, d->throttle.deviation.max_us); + } + d->throttle.deviation.sum_us += dev; + d->throttle.deviation.count++; + } + d->throttle.deviation.last_us = now_us; +} + +static void throttle_publish_debug(MCPXAPUState *d) +{ + int pacing_sum = d->throttle.pacing.backoff + d->throttle.pacing.ok + + d->throttle.pacing.speedup; + if (d->throttle.deviation.count == 0 || pacing_sum == 0) { + return; + } + g_dbg.throttle.pacing.backoff = (float)d->throttle.pacing.backoff / pacing_sum; + g_dbg.throttle.pacing.ok = (float)d->throttle.pacing.ok / pacing_sum; + g_dbg.throttle.pacing.speedup = (float)d->throttle.pacing.speedup / pacing_sum; + g_dbg.throttle.deviation.min_us = d->throttle.deviation.min_us; + g_dbg.throttle.deviation.avg_us = d->throttle.deviation.sum_us / d->throttle.deviation.count; + g_dbg.throttle.deviation.max_us = d->throttle.deviation.max_us; + if (d->throttle.queued_bytes_count > 0) { + g_dbg.throttle.latency.min_ms = d->throttle.queued_bytes_min / (float)MONITOR_BYTES_PER_MS; + g_dbg.throttle.latency.avg_ms = d->throttle.queued_bytes_sum / + (d->throttle.queued_bytes_count * (float)MONITOR_BYTES_PER_MS); + g_dbg.throttle.latency.max_ms = d->throttle.queued_bytes_max / (float)MONITOR_BYTES_PER_MS; + g_dbg.throttle.latency.low_ms = d->monitor.queued_bytes_low / (float)MONITOR_BYTES_PER_MS; + g_dbg.throttle.latency.high_ms = d->monitor.queued_bytes_high / (float)MONITOR_BYTES_PER_MS; + } + d->throttle.pacing.backoff = d->throttle.pacing.ok = d->throttle.pacing.speedup = 0; + d->throttle.deviation.min_us = d->throttle.deviation.max_us = d->throttle.deviation.sum_us = 0; + d->throttle.deviation.count = 0; + d->throttle.queued_bytes_min = d->throttle.queued_bytes_max = 0; + d->throttle.queued_bytes_sum = 0; + d->throttle.queued_bytes_count = 0; +} + +static void throttle_record_queue(MCPXAPUState *d, int queued_bytes) +{ + if (d->throttle.queued_bytes_count == 0) { + d->throttle.queued_bytes_min = d->throttle.queued_bytes_max = queued_bytes; + } else { + d->throttle.queued_bytes_min = MIN(queued_bytes, d->throttle.queued_bytes_min); + d->throttle.queued_bytes_max = MAX(queued_bytes, d->throttle.queued_bytes_max); + } + d->throttle.queued_bytes_sum += queued_bytes; + d->throttle.queued_bytes_count++; + if (queued_bytes >= d->monitor.queued_bytes_high) { + d->throttle.pacing.backoff++; + } else if (queued_bytes <= d->monitor.queued_bytes_low) { + d->throttle.pacing.speedup++; + } else { + d->throttle.pacing.ok++; + } +} + static void throttle(MCPXAPUState *d) { if (d->ep_frame_div % 8) { return; } - const int64_t ep_frame_us = 5333; /* 256/48000 sec (~5.33ms) */ int64_t start_us = qemu_clock_get_us(QEMU_CLOCK_REALTIME); + throttle_update_debug(d, start_us); int queued_bytes = -1; if (d->monitor.stream) { - while (!d->pause_requested) { - queued_bytes = SDL_GetAudioStreamQueued(d->monitor.stream); - if (queued_bytes >= d->monitor.queued_bytes_high) { - qemu_cond_timedwait(&d->cond, &d->lock, ep_frame_us / 1000); - } else { + queued_bytes = SDL_GetAudioStreamQueued(d->monitor.stream); + if (queued_bytes >= 0) { + throttle_record_queue(d, queued_bytes); + } + while (!d->pause_requested && queued_bytes >= d->monitor.queued_bytes_high) { + qemu_cond_timedwait(&d->cond, &d->lock, EP_FRAME_US / 1000); + if (d->pause_requested) { break; } + queued_bytes = SDL_GetAudioStreamQueued(d->monitor.stream); } } if (queued_bytes < 0 || queued_bytes > d->monitor.queued_bytes_low) { if (d->next_frame_time_us == 0 || - start_us - d->next_frame_time_us > ep_frame_us) { + start_us - d->next_frame_time_us > EP_FRAME_US) { d->next_frame_time_us = start_us; } while (!d->pause_requested) { @@ -137,7 +204,7 @@ static void throttle(MCPXAPUState *d) break; } } - d->next_frame_time_us += ep_frame_us; + d->next_frame_time_us += EP_FRAME_US; } d->sleep_acc_us += qemu_clock_get_us(QEMU_CLOCK_REALTIME) - start_us; @@ -161,6 +228,7 @@ static void se_frame(MCPXAPUState *d) */ g_dbg.utilization = 1.0 - d->sleep_acc_us / (elapsed_ms * 1000.0); g_dbg.frames_processed = (int)(d->frame_count * 1000.0 / elapsed_ms + 0.5); + throttle_publish_debug(d); d->frame_count_time_ms = now_ms; d->frame_count = 0; diff --git a/hw/xbox/mcpx/apu/apu_debug.h b/hw/xbox/mcpx/apu/apu_debug.h index 65a00cfa81..d3dabb8491 100644 --- a/hw/xbox/mcpx/apu/apu_debug.h +++ b/hw/xbox/mcpx/apu/apu_debug.h @@ -76,6 +76,18 @@ struct McpxApuDebug struct McpxApuDebugDsp gp, ep; int frames_processed; float utilization; + struct { + struct { + float backoff, ok, speedup; + } pacing; + struct { + int64_t min_us, avg_us, max_us; + } deviation; + struct { + float min_ms, avg_ms, max_ms; + float low_ms, high_ms; + } latency; + } throttle; bool gp_realtime, ep_realtime; }; diff --git a/hw/xbox/mcpx/apu/apu_int.h b/hw/xbox/mcpx/apu/apu_int.h index 7a247d10c9..1e81605590 100644 --- a/hw/xbox/mcpx/apu/apu_int.h +++ b/hw/xbox/mcpx/apu/apu_int.h @@ -101,6 +101,20 @@ typedef struct MCPXAPUState { int64_t frame_count_time_ms; int64_t next_frame_time_us; + struct { + struct { + int backoff, ok, speedup; + } pacing; + struct { + int64_t last_us; + int64_t min_us, max_us, sum_us; + int count; + } deviation; + int queued_bytes_min, queued_bytes_max; + int64_t queued_bytes_sum; + int queued_bytes_count; + } throttle; + struct { McpxApuDebugMonitorPoint point; int16_t frame_buf[256][2]; // 1 EP frame (0x400 bytes) diff --git a/hw/xbox/mcpx/apu/apu_regs.h b/hw/xbox/mcpx/apu/apu_regs.h index 5b63612147..2deee37f69 100644 --- a/hw/xbox/mcpx/apu/apu_regs.h +++ b/hw/xbox/mcpx/apu/apu_regs.h @@ -360,4 +360,7 @@ enum MCPX_HW_NOTIFIER { // clang-format on +#define EP_FRAME_US 5333 /* 256/48000 sec (~5.33ms) */ +#define MONITOR_BYTES_PER_MS 192 /* 48000 Hz * 2ch * 2 bytes / 1000 */ + #endif diff --git a/ui/xui/debug.cc b/ui/xui/debug.cc index 52f3fa82b7..6c98ede711 100644 --- a/ui/xui/debug.cc +++ b/ui/xui/debug.cc @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // +#include #include "debug.hh" #include "common.hh" #include "misc.hh" @@ -228,6 +229,72 @@ void DebugApuWindow::Draw() if (color) ImGui::PopStyleColor(); ImGui::Text("Frames: %04d", dbg->frames_processed); + if (ImGui::TreeNode("Throttle")) { + ImGui::Text("Deviation: %" PRId64 "/%" PRId64 "/%" PRId64 " us", + dbg->throttle.deviation.min_us, + dbg->throttle.deviation.avg_us, + dbg->throttle.deviation.max_us); + ImGui::Text("Latency: %.1f/%.1f/%.1f ms", + dbg->throttle.latency.min_ms, + dbg->throttle.latency.avg_ms, + dbg->throttle.latency.max_ms); + float high = dbg->throttle.latency.high_ms; + if (high > 0) { + float scale = fmax(high, dbg->throttle.latency.max_ms); + float h = ImGui::GetFrameHeight() * 0.25f; + + ImGui::Text("Queue/Pacing:"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + float w = ImGui::GetContentRegionAvail().x; + float low_x = pos.x + w * (dbg->throttle.latency.low_ms / scale); + float high_x = pos.x + w * (high / scale); + float min_x = pos.x + w * (dbg->throttle.latency.min_ms / scale); + float avg_x = pos.x + w * (dbg->throttle.latency.avg_ms / scale); + float max_x = pos.x + w * (dbg->throttle.latency.max_ms / scale); + const ImU32 col_bg = IM_COL32(40, 40, 40, 255); + const ImU32 col_band = IM_COL32(60, 120, 60, 255); + const ImU32 col_avg = IM_COL32(100, 220, 100, 255); + const ImU32 col_low = IM_COL32(255, 255, 0, 180); + const ImU32 col_high = IM_COL32(255, 80, 80, 180); + + ImDrawList *dl = ImGui::GetWindowDrawList(); + dl->AddRectFilled(pos, ImVec2(pos.x + w, pos.y + h), col_bg); + dl->AddRectFilled(ImVec2(min_x, pos.y), ImVec2(max_x, pos.y + h), + col_band); + dl->AddLine(ImVec2(low_x, pos.y), ImVec2(low_x, pos.y + h), + col_low, 1.0f); + dl->AddLine(ImVec2(avg_x, pos.y), ImVec2(avg_x, pos.y + h), + col_avg, 2.0f); + dl->AddLine(ImVec2(high_x, pos.y), ImVec2(high_x, pos.y + h), + col_high, 1.0f); + ImGui::Dummy(ImVec2(w, h)); + + pos.y += h * 1.25; + + const ImU32 col_speedup = col_low; + const ImU32 col_ok = col_band; + const ImU32 col_backoff = col_high; + float sp = dbg->throttle.pacing.speedup; + float ok = dbg->throttle.pacing.ok; + float bo = dbg->throttle.pacing.backoff; + float sp_x = pos.x + w * sp; + float ok_x = sp_x + w * ok; + dl->AddRectFilled(pos, ImVec2(pos.x + w, pos.y + h), col_bg); + if (sp > 0) { + dl->AddRectFilled(pos, ImVec2(sp_x, pos.y + h), col_speedup); + } + if (ok > 0) { + dl->AddRectFilled(ImVec2(sp_x, pos.y), ImVec2(ok_x, pos.y + h), + col_ok); + } + if (bo > 0) { + dl->AddRectFilled(ImVec2(ok_x, pos.y), ImVec2(pos.x + w, pos.y + h), + col_backoff); + } + ImGui::Dummy(ImVec2(w, h)); + } + ImGui::TreePop(); + } ImGui::Text("VP: %4d us", dbg->vp.total_worker_time_us); if (ImGui::TreeNode("VP Workers")) { ImGui::Text(" W: # us");