Files
xemu/ui/xui/debug.cc
coldhex 0427ae8cfc mcpx: Implement APU multipass voice processing
Voice Processor (VP) multipass feature allows configuring lists of voices
that are first mixed (in order) into a designated mixbin which is then used
as a sample source when processing voices with multipass flag set to true
in NV_PAVS_VOICE_CFG_FMT. Setting correct voice order in lists is the
responsibility of the game/application and in practice is handled by the
DirectSound library. The multipass mixbin is hardcoded to 31 in
DirectSound, but hardware would allow other bins.

This implementation also adds additional info to audio debug UI to see what
the source and destination voices involved are. The info is only shown
when DSP processing is off, i.e. "VP Only" (MON_VP) is selected. This is
because storing the voice numbers requires additional digging which is
required for MON_VP anyway and therefore is free. The multipass feature
itself works fine with DSP (i.e. GP and EP) enabled, only the additional
debug info is not shown.
2025-02-15 15:01:29 -07:00

403 lines
15 KiB
C++

//
// xemu User Interface
//
// Copyright (C) 2020-2022 Matt Borgerson
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "debug.hh"
#include "common.hh"
#include "misc.hh"
#include "font-manager.hh"
#include "viewport-manager.hh"
#define MAX_VOICES 256
DebugApuWindow::DebugApuWindow() : m_is_open(false)
{
}
void DebugApuWindow::Draw()
{
if (!m_is_open)
return;
ImGui::SetNextWindowContentSize(ImVec2(600.0f*g_viewport_mgr.m_scale, 0.0f));
if (!ImGui::Begin("Audio Debug", &m_is_open,
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::End();
return;
}
const struct McpxApuDebug *dbg = mcpx_apu_get_debug_info();
ImGui::Columns(2, "", false);
ImGui::SetColumnWidth(0, 360*g_viewport_mgr.m_scale);
int now = SDL_GetTicks() % 1000;
float t = now/1000.0f;
float freq = 1;
float v = fabs(sin(M_PI*t*freq));
float c_active = mix(0.4, 0.97, v);
float c_inactive = 0.2f;
int voice_monitor = -1;
int voice_info = -1;
int voice_mute = -1;
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2*g_viewport_mgr.m_scale, 2*g_viewport_mgr.m_scale));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4*g_viewport_mgr.m_scale, 4*g_viewport_mgr.m_scale));
for (int i = 0; i < MAX_VOICES; i++) {
if (i % 16) {
ImGui::SameLine();
}
float c, s, h;
h = 0.6;
if (dbg->vp.v[i].active) {
if (dbg->vp.v[i].paused) {
c = c_inactive;
s = 0.4;
} else {
c = c_active;
s = 0.7;
}
if (mcpx_apu_debug_is_muted(i)) {
h = 1.0;
}
} else {
c = c_inactive;
s = 0;
}
ImGui::PushID(i);
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(h, s, c));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(h, s, 0.8));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(h, 0.8f, 1.0));
char buf[12];
snprintf(buf, sizeof(buf), "%02x", i);
ImGui::Button(buf);
if (/*dbg->vp.v[i].active &&*/ ImGui::IsItemHovered()) {
voice_monitor = i;
voice_info = i;
}
if (ImGui::IsItemClicked(1)) {
voice_mute = i;
}
ImGui::PopStyleColor(3);
ImGui::PopID();
}
ImGui::PopStyleVar(3);
ImGui::PopFont();
if (voice_info >= 0) {
const struct McpxApuDebugVoice *voice = &dbg->vp.v[voice_info];
ImGui::BeginTooltip();
bool is_paused = voice->paused;
ImGui::Text("Voice 0x%x/%d %s", voice_info, voice_info, is_paused ? "(Paused)" : "");
ImGui::SameLine();
ImGui::Text(voice->stereo ? "Stereo" : "Mono");
ImGui::Separator();
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
const char *noyes[2] = { "NO", "YES" };
ImGui::Text("Stream: %-3s Loop: %-3s Persist: %-3s Multipass: %-3s "
"Linked: %-3s",
noyes[voice->stream], noyes[voice->loop],
noyes[voice->persist], noyes[voice->multipass],
noyes[voice->linked]);
const char *cs[4] = { "1 byte", "2 bytes", "ADPCM", "4 bytes" };
const char *ss[4] = {
"Unsigned 8b PCM",
"Signed 16b PCM",
"Signed 24b PCM",
"Signed 32b PCM"
};
assert(voice->container_size < 4);
assert(voice->sample_size < 4);
const char *spb_or_bin_label = "Samples per Block";
unsigned int spb_or_bin = voice->samples_per_block;
if (voice->multipass) {
spb_or_bin_label = "Multipass Bin";
spb_or_bin = voice->multipass_bin;
}
ImGui::Text("Container Size: %s, Sample Size: %s, %s: %d",
cs[voice->container_size], ss[voice->sample_size],
spb_or_bin_label, spb_or_bin);
ImGui::Text("Rate: %f (%d Hz)", voice->rate, (int)(48000.0/voice->rate));
ImGui::Text("EBO=%d CBO=%d LBO=%d BA=%x",
voice->ebo, voice->cbo, voice->lbo, voice->ba);
ImGui::Text("Mix: ");
for (int i = 0; i < 8; i++) {
if (i == 4) ImGui::Text(" ");
ImGui::SameLine();
char buf[64];
if (voice->vol[i] == 0xFFF) {
snprintf(buf, sizeof(buf),
"Bin %2d (MUTE) ", voice->bin[i]);
} else {
snprintf(buf, sizeof(buf),
"Bin %2d (-%.3f) ", voice->bin[i],
(float)((voice->vol[i] >> 6) & 0x3f) +
(float)((voice->vol[i] >> 0) & 0x3f) / 64.0);
}
ImGui::Text("%-17s", buf);
}
int mon = mcpx_apu_debug_get_monitor();
if (mon == MCPX_APU_DEBUG_MON_VP) {
if (voice->multipass_dst_voice != 0xFFFF) {
ImGui::Text("Multipass Dest Voice: 0x%02x",
voice->multipass_dst_voice);
}
if (voice->multipass) {
ImGui::Text("Multipass Src Voices:");
int n = 0;
for (int i = 0; i < MAX_VOICES; i++) {
if (dbg->vp.v[i].multipass_dst_voice == voice_info) {
if (n > 0 && ((n & 7) == 0)) {
ImGui::Text(" ");
}
ImGui::SameLine();
ImGui::Text("0x%02x", i);
n++;
}
}
}
}
ImGui::PopFont();
ImGui::EndTooltip();
}
if (voice_monitor >= 0) {
mcpx_apu_debug_isolate_voice(voice_monitor);
} else {
mcpx_apu_debug_clear_isolations();
}
if (voice_mute >= 0) {
mcpx_apu_debug_toggle_mute(voice_mute);
}
ImGui::NextColumn();
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
ImGui::Text("Frames: %04d", dbg->frames_processed);
ImGui::Text("GP Cycles: %04d", dbg->gp.cycles);
ImGui::Text("EP Cycles: %04d", dbg->ep.cycles);
bool color = (dbg->utilization > 0.9);
if (color) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
ImGui::Text("Utilization: %.2f%%", (dbg->utilization*100));
if (color) ImGui::PopStyleColor();
ImGui::PopFont();
static int mon = 0;
mon = mcpx_apu_debug_get_monitor();
if (ImGui::Combo("Monitor", &mon, "AC97\0VP Only\0GP Only\0EP Only\0GP/EP if enabled\0")) {
mcpx_apu_debug_set_monitor(mon);
}
static bool gp_realtime;
gp_realtime = dbg->gp_realtime;
if (ImGui::Checkbox("GP Realtime\n", &gp_realtime)) {
mcpx_apu_debug_set_gp_realtime_enabled(gp_realtime);
}
static bool ep_realtime;
ep_realtime = dbg->ep_realtime;
if (ImGui::Checkbox("EP Realtime\n", &ep_realtime)) {
mcpx_apu_debug_set_ep_realtime_enabled(ep_realtime);
}
ImGui::Columns(1);
ImGui::End();
}
// Utility structure for realtime plot
struct ScrollingBuffer {
int MaxSize;
int Offset;
ImVector<ImVec2> Data;
ScrollingBuffer() {
MaxSize = 2000;
Offset = 0;
Data.reserve(MaxSize);
}
void AddPoint(float x, float y) {
if (Data.size() < MaxSize)
Data.push_back(ImVec2(x,y));
else {
Data[Offset] = ImVec2(x,y);
Offset = (Offset + 1) % MaxSize;
}
}
void Erase() {
if (Data.size() > 0) {
Data.shrink(0);
Offset = 0;
}
}
};
DebugVideoWindow::DebugVideoWindow()
{
m_is_open = false;
m_transparent = false;
m_position_restored = false;
m_resize_init_complete = false;
m_prev_scale = g_viewport_mgr.m_scale;
}
void DebugVideoWindow::Draw()
{
if (!m_is_open)
return;
if (!m_position_restored) {
ImGui::SetNextWindowPos(ImVec2(g_config.display.debug.video.x_pos,
g_config.display.debug.video.y_pos),
ImGuiCond_Once, ImVec2(0, 0));
m_transparent = g_config.display.debug.video.transparency;
m_position_restored = true;
}
float alpha = m_transparent ? 0.2 : 1.0;
PushWindowTransparencySettings(m_transparent, 0.2);
if (!m_resize_init_complete || (g_viewport_mgr.m_scale != m_prev_scale)) {
ImGui::SetNextWindowSize(ImVec2(
g_config.display.debug.video.x_winsize * g_viewport_mgr.m_scale,
g_config.display.debug.video.y_winsize * g_viewport_mgr.m_scale));
m_resize_init_complete = true;
}
m_prev_scale = g_viewport_mgr.m_scale;
if (ImGui::Begin("Video Debug", &m_is_open)) {
double x_start, x_end;
static ImPlotAxisFlags rt_axis = ImPlotAxisFlags_NoTickLabels;
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(5,5));
ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f);
static ScrollingBuffer fps;
static float t = 0;
if (runstate_is_running()) {
t += ImGui::GetIO().DeltaTime;
fps.AddPoint(t, g_nv2a_stats.increment_fps);
}
x_start = t - 10.0;
x_end = t;
float plot_width = 0.5 * (ImGui::GetWindowSize().x -
2 * ImGui::GetStyle().WindowPadding.x -
ImGui::GetStyle().ItemSpacing.x);
ImGui::SetNextWindowBgAlpha(alpha);
if (ImPlot::BeginPlot("##ScrollingFPS", ImVec2(plot_width,75*g_viewport_mgr.m_scale))) {
ImPlot::SetupAxes(NULL, NULL, rt_axis, rt_axis | ImPlotAxisFlags_Lock);
ImPlot::SetupAxesLimits(x_start, x_end, 0, 65, ImPlotCond_Always);
if (fps.Data.size() > 0) {
ImPlot::PlotShaded("##fps", &fps.Data[0].x, &fps.Data[0].y, fps.Data.size(), 0, 0, fps.Offset, 2 * sizeof(float));
ImPlot::PlotLine("##fps", &fps.Data[0].x, &fps.Data[0].y, fps.Data.size(), 0, fps.Offset, 2 * sizeof(float));
}
ImPlot::Annotation(x_start, 65, ImPlot::GetLastItemColor(), ImVec2(0,0), true, "FPS: %d", g_nv2a_stats.increment_fps);
ImPlot::EndPlot();
}
ImGui::SameLine();
x_end = g_nv2a_stats.frame_count;
x_start = x_end - NV2A_PROF_NUM_FRAMES;
ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(1));
ImGui::SetNextWindowBgAlpha(alpha);
if (ImPlot::BeginPlot("##ScrollingMSPF", ImVec2(plot_width,75*g_viewport_mgr.m_scale))) {
ImPlot::SetupAxes(NULL, NULL, rt_axis, rt_axis | ImPlotAxisFlags_Lock);
ImPlot::SetupAxesLimits(x_start, x_end, 0, 100, ImPlotCond_Always);
ImPlot::PlotShaded("##mspf", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 0, 1, x_start, 0, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
ImPlot::PlotLine("##mspf", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 1, x_start, 0, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
ImPlot::Annotation(x_start, 100, ImPlot::GetLastItemColor(), ImVec2(0,0), true, "MSPF: %d", g_nv2a_stats.frame_history[(g_nv2a_stats.frame_ptr - 1) % NV2A_PROF_NUM_FRAMES].mspf);
ImPlot::EndPlot();
}
ImPlot::PopStyleColor();
ImGui::SetNextItemOpen(g_config.display.debug.video.advanced_tree_state,
ImGuiCond_Once);
g_config.display.debug.video.advanced_tree_state =
ImGui::TreeNode("Advanced");
if (g_config.display.debug.video.advanced_tree_state) {
ImGui::SetNextWindowBgAlpha(alpha);
if (ImPlot::BeginPlot("##ScrollingDraws", ImVec2(-1,-1))) {
ImPlot::SetupAxes(NULL, NULL, ImPlotAxisFlags_None, ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisScale(ImAxis_Y1, ImPlotScale_Log10);
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 1500);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, NV2A_PROF_NUM_FRAMES);
ImGui::PushID(0);
ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(0));
ImPlot::PushStyleColor(ImPlotCol_Fill, ImPlot::GetColormapColor(0));
ImPlot::PlotLine("MSPF", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 1, 0, 0, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
ImPlot::PopStyleColor(2);
ImGui::PopID();
for (int i = 0; i < NV2A_PROF__COUNT; i++) {
ImGui::PushID(i+1);
char title[64];
snprintf(title, sizeof(title), "%s: %d",
nv2a_profile_get_counter_name(i),
nv2a_profile_get_counter_value(i));
ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(i+1));
ImPlot::PushStyleColor(ImPlotCol_Fill, ImPlot::GetColormapColor(i+1));
ImPlot::PlotLine(title, &g_nv2a_stats.frame_history[0].counters[i], NV2A_PROF_NUM_FRAMES, 1, 0, 0, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
ImPlot::PopStyleColor(2);
ImGui::PopID();
}
ImPlot::EndPlot();
}
ImGui::TreePop();
}
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(2)) {
m_transparent = !m_transparent;
}
ImPlot::PopStyleVar(2);
ImVec2 debug_window_pos = ImGui::GetWindowPos();
g_config.display.debug.video.x_pos = debug_window_pos.x;
g_config.display.debug.video.y_pos = debug_window_pos.y;
ImVec2 debug_window_size = ImGui::GetWindowSize();
g_config.display.debug.video.x_winsize =
debug_window_size.x / g_viewport_mgr.m_scale;
g_config.display.debug.video.y_winsize =
debug_window_size.y / g_viewport_mgr.m_scale;
g_config.display.debug.video.transparency = m_transparent;
}
ImGui::End();
ImGui::PopStyleColor(5);
}
DebugApuWindow apu_window;
DebugVideoWindow video_window;