ImGuiOverlays: Polish component debug windows

This commit is contained in:
Stenzek committed 2026-11-13 13:12:31 +10:00
1 parent b0ee1ff8f1
commit 7fd32ba2fe
9 files changed
+1193 -751

No files matched your search

+1
View File
@@ -6,6 +6,7 @@
// To generate: "<your emoji>".encode("utf-8") in Python 3.
// List to use: https://www.freecodecamp.org/news/all-emojis-emoji-list-for-copy-and-paste/
#define ICON_EMOJI_MIDDLE_DOT "\xc2\xb7"
#define ICON_EMOJI_WARNING "\xe2\x9a\xa0"
#define ICON_EMOJI_CHECKMARK_BUTTON "\xe2\x9c\x85"
#define ICON_EMOJI_CROSS_MARK_BUTTON "\xe2\x9d\x8c"
+266 -279
View File
@@ -8,6 +8,7 @@
#include "dma.h"
#include "fullscreenui.h"
#include "host.h"
#include "imgui_overlays.h"
#include "interrupt_controller.h"
#include "mdec.h"
#include "settings.h"
@@ -35,6 +36,7 @@
#include "common/xorshift_prng.h"
#include "IconsEmoji.h"
#include "IconsFontAwesome.h"
#include "fmt/format.h"
#include "imgui.h"
@@ -502,7 +504,6 @@ struct CDROMState
std::map<u32, std::pair<u32, std::string>> file_map;
bool file_map_created = false;
bool show_current_file = false;
};
} // namespace
@@ -584,7 +585,6 @@ void CDROM::Shutdown()
{
s_state.file_map.clear();
s_state.file_map_created = false;
s_state.show_current_file = false;
s_state.drive_event.Deactivate();
s_state.async_interrupt_event.Deactivate();
@@ -1035,8 +1035,8 @@ bool CDROM::InsertMedia(std::unique_ptr<CDImage>& media, DiscRegion region, std:
if (s_state.drive_state != DriveState::ShellOpening && !(s_state.manual_lid_control & MANUAL_LID_CONTROL_OPEN))
StartMotor();
if (s_state.show_current_file)
CreateFileMap();
s_state.file_map.clear();
s_state.file_map_created = false;
return true;
}
@@ -1054,8 +1054,8 @@ std::unique_ptr<CDImage> CDROM::RemoveMedia(bool for_disc_swap)
INFO_LOG("Removing CD...");
std::unique_ptr<CDImage> image = CDROMAsyncReader::RemoveMedia();
if (s_state.show_current_file)
CreateFileMap();
s_state.file_map.clear();
s_state.file_map_created = false;
s_state.last_sector_header_valid = false;
@@ -4235,6 +4235,12 @@ void CDROM::CheckForSectorBufferReadComplete()
void CDROM::CreateFileMap()
{
if (!System::IsValid() || !ImGuiManager::IsCDROMDebugWindowVisible(g_settings.debug_window_visibility) ||
s_state.file_map_created)
{
return;
}
s_state.file_map.clear();
s_state.file_map_created = true;
@@ -4302,301 +4308,282 @@ const std::string* CDROM::LookupFileMap(u32 lba, u32* start_lba, u32* end_lba)
void CDROM::DrawDebugWindow(float scale)
{
static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f};
static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f};
if (!s_state.file_map_created)
Host::RunOnCoreThread(static_cast<void (*)()>(&CDROM::CreateFileMap));
// draw voice states
if (ImGui::CollapsingHeader("Media", ImGuiTreeNodeFlags_DefaultOpen))
const float left = ImGui::GetCursorPosX();
const float button_width =
ImGui::CalcTextSize(ICON_FA_COPY " Copy State").x + ImGui::GetStyle().FramePadding.x * 2.0f;
ImGui::SetCursorPosX(left + ImGui::GetContentRegionAvail().x - button_width);
const bool copy_state = ImGui::Button(ICON_FA_COPY " Copy State");
if (copy_state)
ImGui::LogToClipboard();
ImGui::SameLine(left);
ImGui::PushFont(nullptr, std::ceil(18.0f * scale), 0.0f);
ImGui::AlignTextToFramePadding();
const float drive_speed =
(s_state.last_sector_read_ticks > 0) ?
((static_cast<float>(System::GetTicksPerSecond()) / static_cast<float>(SINGLE_SPEED_SECTORS_PER_SECOND)) /
static_cast<float>(s_state.last_sector_read_ticks)) :
0.0f;
if (!CDROMAsyncReader::HasMedia())
ImGui::TextUnformatted("No disc");
else if ((s_state.drive_state == DriveState::Reading || s_state.drive_state == DriveState::Playing) &&
drive_speed > 0.0f)
ImGui::Text("%s at %.2fx", s_drive_state_names[static_cast<u8>(s_state.drive_state)], drive_speed);
else
ImGui::TextUnformatted(s_drive_state_names[static_cast<u8>(s_state.drive_state)]);
ImGui::PopFont();
ImGui::Spacing();
constexpr ImGuiTableFlags table_flags =
ImGuiTableFlags_RowBg | ImGuiTableFlags_PadOuterX | ImGuiTableFlags_SizingStretchSame;
static constexpr auto label = [](const char* text) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImVec4 color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
color.w *= 0.7f;
ImGui::TextColored(color, "%s", text);
ImGui::TableNextColumn();
};
static constexpr auto flag = [](const char* text, bool value) {
label(text);
ImGui::TextUnformatted(value ? "Yes" : "No");
};
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10.0f * scale, ImGui::GetStyle().CellPadding.y));
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImVec4(1.0f, 1.0f, 1.0f, 0.035f));
if (ImGui::BeginTable("layout", 2, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_BordersInnerV))
{
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::SeparatorText("Media");
if (CDROMAsyncReader::HasMedia())
{
const CDImage* const media = CDROMAsyncReader::GetMedia();
const CDImage::Position disc_position = CDImage::Position::FromLBA(s_state.current_lba);
const float start_y = ImGui::GetCursorPosY();
if (media->HasSubImages())
const CDImage::Index* const index = media->GetIndexForDiscPosition(s_state.current_lba);
if (ImGui::BeginTable("media", 2, table_flags))
{
ImGui::Text("Filename: %s [Subimage %u of %u] [%u buffered sectors]", media->GetPath().c_str(),
media->GetCurrentSubImage() + 1u, media->GetSubImageCount(),
CDROMAsyncReader::GetBufferedSectorCount());
}
else
{
ImGui::Text("Filename: %s [%u buffered sectors]", media->GetPath().c_str(),
CDROMAsyncReader::GetBufferedSectorCount());
}
ImGui::Text("Disc Position: MSF[%02u:%02u:%02u] LBA[%u]", disc_position.minute, disc_position.second,
disc_position.frame, disc_position.ToLBA());
const CDImage::Index* index = media->GetIndexForDiscPosition(s_state.current_lba);
if (!index || index->track_number > media->GetTrackCount())
{
ImGui::Text("Track Position: Lead-out");
}
else
{
const CDImage::Position track_position = CDImage::Position::FromLBA(
s_state.current_lba - media->GetTrackStartPosition(Truncate8(index->track_number)));
ImGui::Text("Track Position: Number[%u] MSF[%02u:%02u:%02u] LBA[%u]", index->track_number,
track_position.minute, track_position.second, track_position.frame, track_position.ToLBA());
}
ImGui::Text("Last Sector: %02X:%02X:%02X (Mode %u)", s_state.last_sector_header.minute,
s_state.last_sector_header.second, s_state.last_sector_header.frame,
s_state.last_sector_header.sector_mode);
if (s_state.show_current_file)
{
if (index && index->track_number == 1)
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.7f);
label("Image");
ImGui::TextWrapped("%s", media->GetPath().c_str());
if (media->HasSubImages())
{
if (!s_state.file_map_created)
label("Subimage");
ImGui::Text("%u / %u", media->GetCurrentSubImage() + 1u, media->GetSubImageCount());
}
label("Buffered sectors");
ImGui::Text("%u", CDROMAsyncReader::GetBufferedSectorCount());
label("Disc position");
ImGui::Text("%02u:%02u:%02u (LBA %u)", disc_position.minute, disc_position.second, disc_position.frame,
disc_position.ToLBA());
label("Track position");
if (!index || index->track_number > media->GetTrackCount())
{
ImGui::TextUnformatted("Lead-out");
}
else
{
const CDImage::Position track_position = CDImage::Position::FromLBA(
s_state.current_lba - media->GetTrackStartPosition(Truncate8(index->track_number)));
ImGui::TextWrapped("%u " ICON_EMOJI_MIDDLE_DOT " %02u:%02u:%02u (LBA %u)", index->track_number,
track_position.minute, track_position.second, track_position.frame,
track_position.ToLBA());
}
label("Last sector");
ImGui::Text("%02X:%02X:%02X (Mode %u)", s_state.last_sector_header.minute, s_state.last_sector_header.second,
s_state.last_sector_header.frame, s_state.last_sector_header.sector_mode);
label("Current file");
if (!s_state.file_map_created)
{
ImGui::TextUnformatted("Loading...");
}
else if (index && index->track_number == 1)
{
u32 start_lba, end_lba;
const u32 track_lba = s_state.current_lba - media->GetTrackStartPosition(1);
const std::string* const file = LookupFileMap(track_lba, &start_lba, &end_lba);
ImGui::TextWrapped("%s", file ? file->c_str() : "Unknown");
if (file)
{
Host::RunOnCoreThread(static_cast<void (*)()>(&CDROM::CreateFileMap));
return;
}
u32 current_file_start_lba, current_file_end_lba;
const u32 track_lba = s_state.current_lba - media->GetTrackStartPosition(Truncate8(index->track_number));
const std::string* current_file = LookupFileMap(track_lba, &current_file_start_lba, &current_file_end_lba);
if (current_file)
{
static constexpr auto readable_size = [](u32 val) {
// based on
// https://stackoverflow.com/questions/1449805/how-to-format-a-number-using-comma-as-thousands-separator-in-c
// don't want to use locale...
TinyString ret;
TinyString temp;
temp.append_format("{}", val);
u32 commas = 2u - (temp.length() % 3u);
for (const char* p = temp.c_str(); *p != 0u; p++)
{
ret.append(*p);
if (commas == 1)
ret.append(',');
commas = (commas + 1) % 3;
}
DebugAssert(!ret.empty());
ret.erase(-1);
return ret;
};
ImGui::Text(
"Current File: %s (%s of %s bytes)", current_file->c_str(),
readable_size((track_lba - current_file_start_lba) * CDImage::DATA_SECTOR_SIZE).c_str(),
readable_size((current_file_end_lba - current_file_start_lba + 1) * CDImage::DATA_SECTOR_SIZE).c_str());
}
else
{
ImGui::Text("Current File: <Unknown>");
label("File position");
const TinyString position = TinyString::from_format(
"{} / {} bytes", fmt::group_digits((track_lba - start_lba) * CDImage::DATA_SECTOR_SIZE),
fmt::group_digits((end_lba - start_lba + 1) * CDImage::DATA_SECTOR_SIZE));
ImGui::TextWrapped("%s", position.c_str());
}
}
else
{
ImGui::Text("Current File: <Non-Data Track>");
ImGui::TextUnformatted("Non-data track");
}
label("File map");
ImGui::Text("%u entries", static_cast<u32>(s_state.file_map.size()));
ImGui::EndTable();
}
}
else
{
ImGui::TextUnformatted("No disc inserted");
}
ImGui::SameLine();
ImGui::Text("[%u files on disc]", static_cast<u32>(s_state.file_map.size()));
ImGui::SeparatorText("Command / Interrupts");
if (ImGui::BeginTable("command", 2, table_flags))
{
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.7f);
label("Command");
if (HasPendingCommand())
{
ImGui::TextWrapped("%s (0x%02X, %d ticks)", GetCommandName(s_state.command), static_cast<u8>(s_state.command),
s_state.command_event.IsActive() ? s_state.command_event.GetTicksUntilNextExecution() : 0);
}
else
{
const float end_y = ImGui::GetCursorPosY();
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 140.0f * scale);
ImGui::SetCursorPosY(start_y);
if (ImGui::Button("Show Current File"))
s_state.show_current_file = true;
ImGui::SetCursorPosY(end_y);
ImGui::TextUnformatted("None");
}
label("Last read speed");
if (drive_speed > 0.0f)
ImGui::Text("%.2fx", drive_speed);
else
ImGui::TextUnformatted("None");
label("Drive ticks remaining");
ImGui::Text("%d", s_state.drive_event.IsActive() ? s_state.drive_event.GetTicksUntilNextExecution() : 0);
label("Read speedup");
if (g_settings.cdrom_read_speedup == 1)
{
ImGui::TextUnformatted("Disabled");
}
else
{
if (!IsDriveIdle() && !CanUseReadSpeedup())
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Blocked");
else
ImGui::TextUnformatted("Enabled");
}
label("Interrupt enable");
ImGui::Text("0x%02X", s_state.interrupt_enable_register);
label("Interrupt flag");
ImGui::Text("0x%02X", s_state.interrupt_flag_register);
label("Pending interrupt");
if (HasPendingAsyncInterrupt())
ImGui::Text("0x%02X", s_state.pending_async_interrupt);
else
ImGui::TextUnformatted("None");
ImGui::EndTable();
}
else
ImGui::SeparatorText("Status");
if (ImGui::BeginTable("status", 2, table_flags))
{
ImGui::Text("No media inserted.");
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.7f);
flag("ADPBUSY", s_state.status.ADPBUSY);
flag("PRMEMPTY", s_state.status.PRMEMPTY);
flag("PRMWRDY", s_state.status.PRMWRDY);
flag("RSLRRDY", s_state.status.RSLRRDY);
flag("DRQSTS", s_state.status.DRQSTS);
flag("BUSYSTS", s_state.status.BUSYSTS);
ImGui::EndTable();
}
ImGui::TableSetColumnIndex(1);
ImGui::SeparatorText("Secondary status");
if (ImGui::BeginTable("secondary", 2, table_flags))
{
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.7f);
flag("Error", s_state.secondary_status.error);
flag("Motor on", s_state.secondary_status.motor_on);
flag("Seek error", s_state.secondary_status.seek_error);
flag("ID error", s_state.secondary_status.id_error);
flag("Shell open", s_state.secondary_status.shell_open);
if (s_state.manual_lid_control & MANUAL_LID_CONTROL_ENABLED)
{
label("Manual lid");
ImGui::TextUnformatted((s_state.manual_lid_control & MANUAL_LID_CONTROL_OPEN) ? "Open" : "Closed");
}
flag("Reading", s_state.secondary_status.reading);
flag("Seeking", s_state.secondary_status.seeking);
flag("Playing CDDA", s_state.secondary_status.playing_cdda);
ImGui::EndTable();
}
ImGui::SeparatorText("Mode");
if (ImGui::BeginTable("mode", 2, table_flags))
{
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.7f);
flag("CDDA", s_state.mode.cdda);
flag("Auto pause", s_state.mode.auto_pause);
flag("Report audio", s_state.mode.report_audio);
flag("XA filter", s_state.mode.xa_filter);
label("XA filter file / channel");
ImGui::Text("%u / %u", s_state.xa_filter_file_number, s_state.xa_filter_channel_number);
flag("Ignore bit", s_state.mode.ignore_bit);
flag("Raw sectors", s_state.mode.read_raw_sector);
flag("XA enable", s_state.mode.xa_enable);
flag("Double speed", s_state.mode.double_speed);
ImGui::EndTable();
}
ImGui::SeparatorText("CD Audio");
if (ImGui::BeginTable("audio", 2, table_flags))
{
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.7f);
label("Playing");
if (s_state.drive_state == DriveState::Reading && s_state.mode.xa_enable)
{
ImGui::TextUnformatted("XA-ADPCM");
label("XA file / channel");
ImGui::Text("%u / %u", s_state.xa_current_file_number, s_state.xa_current_channel_number);
label("Format");
ImGui::TextWrapped("%s, %s, %u Hz", s_state.xa_current_codinginfo.IsStereo() ? "Stereo" : "Mono",
s_state.xa_current_codinginfo.Is8BitADPCM() ? "8-bit" : "4-bit",
s_state.xa_current_codinginfo.IsHalfSampleRate() ? 18900u : 37800u);
}
else if (s_state.drive_state == DriveState::Playing)
{
ImGui::Text("CDDA (track %X)", s_state.last_subq.track_number_bcd);
}
else
{
ImGui::TextUnformatted("Inactive");
}
flag("Muted", s_state.muted);
label("Audio FIFO");
ImGui::Text("%u frames", s_state.audio_fifo.GetSize());
ImGui::EndTable();
}
if (ImGui::BeginTable("volume", 3, table_flags))
{
ImGui::TableSetupColumn("");
ImGui::TableSetupColumn("Left output");
ImGui::TableSetupColumn("Right output");
ImGui::TableHeadersRow();
for (u32 input = 0; input < 2; input++)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(input == 0 ? "Left input" : "Right input");
for (u32 output = 0; output < 2; output++)
{
ImGui::TableNextColumn();
const u8 volume = s_state.cd_audio_volume_matrix[input][output];
ImGui::Text("%02X (%u%%)", volume, ZeroExtend32(volume) * 100 / 0x80);
}
}
ImGui::EndTable();
}
ImGui::EndTable();
}
if (ImGui::CollapsingHeader("Status/Mode", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Columns(3);
ImGui::Text("Status");
ImGui::NextColumn();
ImGui::Text("Secondary Status");
ImGui::NextColumn();
ImGui::Text("Mode Status");
ImGui::NextColumn();
ImGui::TextColored(s_state.status.ADPBUSY ? active_color : inactive_color, "ADPBUSY: %s",
s_state.status.ADPBUSY ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.error ? active_color : inactive_color, "Error: %s",
s_state.secondary_status.error ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.cdda ? active_color : inactive_color, "CDDA: %s", s_state.mode.cdda ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.status.PRMEMPTY ? active_color : inactive_color, "PRMEMPTY: %s",
s_state.status.PRMEMPTY ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.motor_on ? active_color : inactive_color, "Motor On: %s",
s_state.secondary_status.motor_on ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.auto_pause ? active_color : inactive_color, "Auto Pause: %s",
s_state.mode.auto_pause ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.status.PRMWRDY ? active_color : inactive_color, "PRMWRDY: %s",
s_state.status.PRMWRDY ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.seek_error ? active_color : inactive_color, "Seek Error: %s",
s_state.secondary_status.seek_error ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.report_audio ? active_color : inactive_color, "Report Audio: %s",
s_state.mode.report_audio ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.status.RSLRRDY ? active_color : inactive_color, "RSLRRDY: %s",
s_state.status.RSLRRDY ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.id_error ? active_color : inactive_color, "ID Error: %s",
s_state.secondary_status.id_error ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.xa_filter ? active_color : inactive_color, "XA Filter: %s (File %u Channel %u)",
s_state.mode.xa_filter ? "Yes" : "No", s_state.xa_filter_file_number,
s_state.xa_filter_channel_number);
ImGui::NextColumn();
ImGui::TextColored(s_state.status.DRQSTS ? active_color : inactive_color, "DRQSTS: %s",
s_state.status.DRQSTS ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.shell_open ? active_color : inactive_color, "Shell Open: %s",
s_state.secondary_status.shell_open ? "Yes" : "No");
if (s_state.manual_lid_control & MANUAL_LID_CONTROL_ENABLED)
{
const bool open = (s_state.manual_lid_control & MANUAL_LID_CONTROL_OPEN);
ImGui::SameLine();
ImGui::TextColored(open ? active_color : inactive_color, "Lid: %s", open ? "Open" : "Closed");
}
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.ignore_bit ? active_color : inactive_color, "Ignore Bit: %s",
s_state.mode.ignore_bit ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.status.BUSYSTS ? active_color : inactive_color, "BUSYSTS: %s",
s_state.status.BUSYSTS ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.reading ? active_color : inactive_color, "Reading: %s",
s_state.secondary_status.reading ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.read_raw_sector ? active_color : inactive_color, "Read Raw Sectors: %s",
s_state.mode.read_raw_sector ? "Yes" : "No");
ImGui::NextColumn();
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.seeking ? active_color : inactive_color, "Seeking: %s",
s_state.secondary_status.seeking ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.xa_enable ? active_color : inactive_color, "XA Enable: %s",
s_state.mode.xa_enable ? "Yes" : "No");
ImGui::NextColumn();
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.playing_cdda ? active_color : inactive_color, "Playing CDDA: %s",
s_state.secondary_status.playing_cdda ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.double_speed ? active_color : inactive_color, "Double Speed: %s",
s_state.mode.double_speed ? "Yes" : "No");
ImGui::NextColumn();
ImGui::Columns(1);
ImGui::NewLine();
ImGui::Columns(2);
ImGui::SetColumnWidth(0, std::ceil(scale * 500.0f));
if (HasPendingCommand())
{
ImGui::TextColored(active_color, "Command: %s (0x%02X) (%d ticks remaining)", GetCommandName(s_state.command),
static_cast<u8>(s_state.command),
s_state.command_event.IsActive() ? s_state.command_event.GetTicksUntilNextExecution() : 0);
}
else
{
ImGui::TextColored(inactive_color, "Command: None");
}
ImGui::NextColumn();
ImGui::Text("Interrupt Enable Register: 0x%02X", s_state.interrupt_enable_register);
ImGui::NextColumn();
const bool drive_idle = IsDriveIdle();
if (drive_idle)
{
ImGui::TextColored(inactive_color, "Drive: Idle");
}
else
{
ImGui::TextColored(active_color, "Drive: %s (%d ticks remaining)",
s_drive_state_names[static_cast<u8>(s_state.drive_state)],
s_state.drive_event.IsActive() ? s_state.drive_event.GetTicksUntilNextExecution() : 0);
}
ImGui::NextColumn();
ImGui::Text("Interrupt Flag Register: 0x%02X", s_state.interrupt_flag_register);
ImGui::NextColumn();
ImGui::TextColored(drive_idle ? inactive_color : active_color, "Effective Drive Speed: %.2fx",
(s_state.last_sector_read_ticks > 0) ? ((static_cast<float>(System::GetTicksPerSecond()) /
static_cast<float>(SINGLE_SPEED_SECTORS_PER_SECOND)) /
static_cast<float>(s_state.last_sector_read_ticks)) :
0.0f);
if (!drive_idle && g_settings.cdrom_read_speedup != 1 && !CanUseReadSpeedup())
{
ImGui::SameLine();
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), 200.0f));
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "SPEEDUP BLOCKED");
}
ImGui::NextColumn();
if (HasPendingAsyncInterrupt())
{
ImGui::SameLine();
ImGui::TextColored(inactive_color, "0x%02X pending", s_state.pending_async_interrupt);
}
ImGui::Columns(1);
ImGui::NewLine();
}
if (ImGui::CollapsingHeader("CD Audio", ImGuiTreeNodeFlags_DefaultOpen))
{
if (s_state.drive_state == DriveState::Reading && s_state.mode.xa_enable)
{
ImGui::TextColored(active_color, "Playing: XA-ADPCM (File %u | Channel %u | %s | %s | %s)",
s_state.xa_current_file_number, s_state.xa_current_channel_number,
s_state.xa_current_codinginfo.IsStereo() ? "Stereo" : "Mono",
s_state.xa_current_codinginfo.Is8BitADPCM() ? "8-bit" : "4-bit",
s_state.xa_current_codinginfo.IsHalfSampleRate() ? "18900hz" : "37800hz");
}
else if (s_state.drive_state == DriveState::Playing)
{
ImGui::TextColored(active_color, "Playing: CDDA (Track %x)", s_state.last_subq.track_number_bcd);
}
else
{
ImGui::TextColored(inactive_color, "Playing: Inactive");
}
ImGui::TextColored(s_state.muted ? inactive_color : active_color, "Muted: %s", s_state.muted ? "Yes" : "No");
ImGui::Text("Left Output: Left Channel=%02X (%u%%), Right Channel=%02X (%u%%)",
s_state.cd_audio_volume_matrix[0][0], ZeroExtend32(s_state.cd_audio_volume_matrix[0][0]) * 100 / 0x80,
s_state.cd_audio_volume_matrix[1][0], ZeroExtend32(s_state.cd_audio_volume_matrix[1][0]) * 100 / 0x80);
ImGui::Text("Right Output: Left Channel=%02X (%u%%), Right Channel=%02X (%u%%)",
s_state.cd_audio_volume_matrix[0][1], ZeroExtend32(s_state.cd_audio_volume_matrix[0][1]) * 100 / 0x80,
s_state.cd_audio_volume_matrix[1][1], ZeroExtend32(s_state.cd_audio_volume_matrix[1][1]) * 100 / 0x80);
ImGui::Text("Audio FIFO Size: %u frames", s_state.audio_fifo.GetSize());
}
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (copy_state)
ImGui::LogFinish();
}
+74 -60
View File
@@ -26,6 +26,9 @@
#include "fmt/format.h"
#include "IconsEmoji.h"
#include "IconsFontAwesome.h"
#include <array>
#include <memory>
#include <vector>
@@ -988,71 +991,82 @@ TickCount DMA::TransferDeviceToMemory(u32 address, u32 increment, u32 word_count
void DMA::DrawDebugStateWindow(float scale)
{
static constexpr std::array column_names = {"#", "Req", "Addr", "Direction", "Chopping", "Mode",
"Busy", "Enable", "Priority", "IRQ", "Flag"};
static constexpr std::array sync_mode_names = {"Manual", "Request", "LinkedList", "Reserved"};
static constexpr std::array column_names = {"Channel", "Request", "Address", "Direction",
"Step", "Chop DMA/CPU", "Mode", "State",
"Enabled", "Priority", "IRQ enable", "IRQ flag"};
static constexpr std::array sync_mode_names = {"Manual", "Request", "Linked list", "Reserved"};
ImGui::Columns(static_cast<int>(column_names.size()));
ImGui::SetColumnWidth(0, 80.0f * scale);
ImGui::SetColumnWidth(1, 50.0f * scale);
ImGui::SetColumnWidth(2, 80.0f * scale);
ImGui::SetColumnWidth(3, 110.0f * scale);
ImGui::SetColumnWidth(4, 100.0f * scale);
ImGui::SetColumnWidth(5, 80.0f * scale);
ImGui::SetColumnWidth(6, 80.0f * scale);
ImGui::SetColumnWidth(7, 80.0f * scale);
ImGui::SetColumnWidth(8, 60.0f * scale);
ImGui::SetColumnWidth(9, 80.0f * scale);
ImGui::SetColumnWidth(10, 80.0f * scale);
const float left = ImGui::GetCursorPosX();
const float button_width =
ImGui::CalcTextSize(ICON_FA_COPY " Copy State").x + ImGui::GetStyle().FramePadding.x * 2.0f;
ImGui::SetCursorPosX(left + ImGui::GetContentRegionAvail().x - button_width);
for (const char* title : column_names)
const bool copy_state = ImGui::Button(ICON_FA_COPY " Copy State");
if (copy_state)
ImGui::LogToClipboard();
ImGui::SameLine(left);
ImGui::AlignTextToFramePadding();
ImGui::Text("IRQ %s " ICON_EMOJI_MIDDLE_DOT " Master IRQ %s " ICON_EMOJI_MIDDLE_DOT " Bus error %s",
s_state.DICR.master_flag ? "asserted" : "clear", s_state.DICR.master_enable ? "enabled" : "disabled",
s_state.DICR.bus_error ? "asserted" : "clear");
ImGui::Spacing();
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(8.0f * scale, ImGui::GetStyle().CellPadding.y));
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImVec4(1.0f, 1.0f, 1.0f, 0.035f));
if (ImGui::BeginTable("channels", static_cast<int>(column_names.size()),
ImGuiTableFlags_RowBg | ImGuiTableFlags_PadOuterX | ImGuiTableFlags_SizingStretchProp))
{
ImGui::TextUnformatted(title);
ImGui::NextColumn();
for (const char* title : column_names)
ImGui::TableSetupColumn(title);
ImGui::TableHeadersRow();
for (u32 i = 0; i < NUM_CHANNELS; i++)
{
const ChannelState& cs = s_state.channels[i];
const Channel channel = static_cast<Channel>(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%u %s", i, s_channel_names[i]);
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.request ? "Asserted" : "Clear");
ImGui::TableNextColumn();
ImGui::Text("%08X", cs.base_address);
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.channel_control.copy_to_device ? "From RAM" : "To RAM");
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.channel_control.address_step_reverse ? "-4" : "+4");
ImGui::TableNextColumn();
if (cs.channel_control.chopping_enable)
{
ImGui::Text("%u / %u cycles", 1u << cs.channel_control.chopping_dma_window_size.GetValue(),
1u << cs.channel_control.chopping_cpu_window_size.GetValue());
}
else
{
ImGui::TextUnformatted("Disabled");
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(sync_mode_names[static_cast<u8>(cs.channel_control.sync_mode.GetValue())]);
ImGui::TableNextColumn();
ImGui::TextWrapped("%s%s", cs.channel_control.enable_busy ? "Busy" : "Idle",
cs.channel_control.start_trigger ? " (trigger)" : "");
ImGui::TableNextColumn();
ImGui::TextUnformatted(s_state.DPCR.GetMasterEnable(channel) ? "Enabled" : "Disabled");
ImGui::TableNextColumn();
ImGui::Text("%u", s_state.DPCR.GetPriority(channel));
ImGui::TableNextColumn();
ImGui::TextUnformatted(s_state.DICR.GetIRQEnabled(channel) ? "Enabled" : "Disabled");
ImGui::TableNextColumn();
ImGui::TextUnformatted(s_state.DICR.GetIRQFlag(channel) ? "Asserted" : "Clear");
}
ImGui::EndTable();
}
const ImVec4 active(1.0f, 1.0f, 1.0f, 1.0f);
const ImVec4 inactive(0.5f, 0.5f, 0.5f, 1.0f);
for (u32 i = 0; i < NUM_CHANNELS; i++)
{
const ChannelState& cs = s_state.channels[i];
ImGui::TextColored(cs.channel_control.enable_busy ? active : inactive, "%u[%s]", i, s_channel_names[i]);
ImGui::NextColumn();
ImGui::TextColored(cs.request ? active : inactive, cs.request ? "Yes" : "No");
ImGui::NextColumn();
ImGui::TextColored(cs.request ? active : inactive, "%08X", cs.base_address);
ImGui::NextColumn();
ImGui::Text("%s%s", cs.channel_control.copy_to_device ? "FromRAM" : "ToRAM",
cs.channel_control.address_step_reverse ? " Addr+" : " Addr-");
ImGui::NextColumn();
ImGui::TextColored(cs.channel_control.chopping_enable ? active : inactive, "%s/%u/%u",
cs.channel_control.chopping_enable ? "Yes" : "No",
cs.channel_control.chopping_cpu_window_size.GetValue(),
cs.channel_control.chopping_dma_window_size.GetValue());
ImGui::NextColumn();
ImGui::Text("%s", sync_mode_names[static_cast<u8>(cs.channel_control.sync_mode.GetValue())]);
ImGui::NextColumn();
ImGui::TextColored(cs.channel_control.enable_busy ? active : inactive, "%s%s",
cs.channel_control.enable_busy ? "Busy" : "Idle",
cs.channel_control.start_trigger ? " (Trigger)" : "");
ImGui::NextColumn();
ImGui::TextColored(s_state.DPCR.GetMasterEnable(static_cast<Channel>(i)) ? active : inactive,
s_state.DPCR.GetMasterEnable(static_cast<Channel>(i)) ? "Enabled" : "Disabled");
ImGui::NextColumn();
ImGui::TextColored(s_state.DPCR.GetMasterEnable(static_cast<Channel>(i)) ? active : inactive, "%u",
s_state.DPCR.GetPriority(static_cast<Channel>(i)));
ImGui::NextColumn();
ImGui::TextColored(s_state.DICR.GetIRQEnabled(static_cast<Channel>(i)) ? active : inactive,
s_state.DICR.GetIRQEnabled(static_cast<Channel>(i)) ? "Enabled" : "Disabled");
ImGui::NextColumn();
ImGui::TextColored(s_state.DICR.GetIRQFlag(static_cast<Channel>(i)) ? active : inactive,
s_state.DICR.GetIRQFlag(static_cast<Channel>(i)) ? "IRQ" : "");
ImGui::NextColumn();
}
ImGui::Columns(1);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (copy_state)
ImGui::LogFinish();
}
// Instantiate channel functions.
+227 -46
View File
@@ -13,6 +13,7 @@
#include "gpu_hw_texture_cache.h"
#include "gpu_sw_rasterizer.h"
#include "host.h"
#include "imgui_overlays.h"
#include "interrupt_controller.h"
#include "performance_counters.h"
#include "settings.h"
@@ -46,6 +47,7 @@
#include "common/string_util.h"
#include "IconsEmoji.h"
#include "IconsFontAwesome.h"
#include "fmt/format.h"
#include "imgui.h"
@@ -2678,7 +2680,10 @@ void GPU::UpdateDisplay(bool submit_frame)
submit_frame = (submit_frame && System::GetFramePresentationParameters(&frame));
GPUBackendUpdateDisplayCommand* cmd = GPUBackend::NewUpdateDisplayCommand();
cmd->gpu_busy_pct = g_settings.display_show_gpu_stats ? UpdateOrGetGPUBusyPct() : 0;
cmd->gpu_busy_pct =
(g_settings.display_show_gpu_stats || ImGuiManager::IsGPUDebugWindowVisible(g_settings.debug_window_visibility)) ?
UpdateOrGetGPUBusyPct() :
0;
if (!g_settings.gpu_show_vram) [[likely]]
{
FillUpdateDisplayCommand(cmd);
@@ -4127,58 +4132,234 @@ void GPU::HandleCopyRectangleVRAMToVRAMCommand()
void GPU::DrawDebugStateWindow(float scale)
{
if (ImGui::CollapsingHeader("GPU", ImGuiTreeNodeFlags_DefaultOpen))
{
static constexpr std::array<const char*, 5> state_strings = {
{"Idle", "Reading VRAM", "Writing VRAM", "Drawing Polyline"}};
static constexpr const CRTCState& cs = s_locals.crtc_state;
static constexpr std::array<const char*, 4> state_strings = {
{"Idle", "Reading VRAM", "Writing VRAM", "Drawing Polyline"}};
const char* clock = s_locals.console_is_pal ? (s_locals.GPUSTAT.pal_mode ? "PAL-on-PAL" : "NTSC-on-PAL") :
(s_locals.GPUSTAT.pal_mode ? "PAL-on-NTSC" : "NTSC-on-NTSC");
ImGui::Text("State: %s", state_strings[static_cast<u8>(s_locals.blitter_state)]);
ImGui::Text("Dither: %s", s_locals.GPUSTAT.dither_enable ? "Enabled" : "Disabled");
ImGui::Text("Draw To Displayed Field: %s", s_locals.GPUSTAT.draw_to_displayed_field ? "Enabled" : "Disabled");
ImGui::Text("Draw Set Mask Bit: %s", s_locals.GPUSTAT.set_mask_while_drawing ? "Yes" : "No");
ImGui::Text("Draw To Masked Pixels: %s", s_locals.GPUSTAT.check_mask_before_draw ? "Yes" : "No");
ImGui::Text("Reverse Flag: %s", s_locals.GPUSTAT.reverse_flag ? "Yes" : "No");
ImGui::Text("Texture Page Upper Bank: %s", s_locals.GPUSTAT.texture_page_y_base_2 ? "Yes" : "No");
ImGui::Text("PAL Mode: %s", s_locals.GPUSTAT.pal_mode ? "Yes" : "No");
ImGui::Text("Interrupt Request: %s", s_locals.GPUSTAT.interrupt_request ? "Yes" : "No");
ImGui::Text("DMA Request: %s", s_locals.GPUSTAT.dma_data_request ? "Yes" : "No");
const char* standard = s_locals.GPUSTAT.pal_mode ? "PAL" : "NTSC";
const char* scan_mode = s_locals.GPUSTAT.vertical_interlace ? (s_locals.GPUSTAT.pal_mode ? "576i" : "480i") :
(s_locals.GPUSTAT.pal_mode ? "288p" : "240p");
const float left = ImGui::GetCursorPosX();
const float button_width =
ImGui::CalcTextSize(ICON_FA_COPY " Copy State").x + ImGui::GetStyle().FramePadding.x * 2.0f;
ImGui::SetCursorPosX(left + ImGui::GetContentRegionAvail().x - button_width);
const bool copy_state = ImGui::Button(ICON_FA_COPY " Copy State");
if (copy_state)
ImGui::LogToClipboard();
ImGui::SameLine(left);
ImGui::PushFont(nullptr, std::ceil(18.0f * scale), 0.0f);
ImGui::AlignTextToFramePadding();
ImGui::TextWrapped("%s %s " ICON_EMOJI_MIDDLE_DOT " %u x %u " ICON_EMOJI_MIDDLE_DOT " %u-bit " ICON_EMOJI_MIDDLE_DOT
" %.3f Hz " ICON_EMOJI_MIDDLE_DOT " %u%% Usage",
standard, scan_mode, cs.display_width, cs.display_height,
s_locals.GPUSTAT.display_area_color_depth_24 ? 24 : 15, ComputeVerticalFrequency(),
s_locals.last_gpu_busy_pct);
ImGui::PopFont();
ImGui::Spacing();
constexpr ImGuiTableFlags table_flags =
ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_PadOuterX | ImGuiTableFlags_RowBg;
static constexpr auto label = [](const char* text) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImVec4 color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
color.w *= 0.7f;
ImGui::TextColored(color, "%s", text);
ImGui::TableNextColumn();
};
static constexpr auto flag = [](const char* text, bool value) {
label(text);
ImGui::TextUnformatted(value ? "Enabled" : "Disabled");
};
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10.0f * scale, ImGui::GetStyle().CellPadding.y));
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImVec4(1.0f, 1.0f, 1.0f, 0.035f));
if (!ImGui::BeginTable("layout", 2, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_BordersInnerV))
{
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (copy_state)
ImGui::LogFinish();
return;
}
if (ImGui::CollapsingHeader("CRTC", ImGuiTreeNodeFlags_DefaultOpen))
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::SeparatorText("Drawing");
if (ImGui::BeginTable("drawing", 2, table_flags))
{
const auto& cs = s_locals.crtc_state;
ImGui::Text("Clock: %s", (s_locals.console_is_pal ? (s_locals.GPUSTAT.pal_mode ? "PAL-on-PAL" : "NTSC-on-PAL") :
(s_locals.GPUSTAT.pal_mode ? "PAL-on-NTSC" : "NTSC-on-NTSC")));
ImGui::Text("Horizontal Frequency: %.3f KHz", ComputeHorizontalFrequency() / 1000.0f);
ImGui::Text("Vertical Frequency: %.3f Hz", ComputeVerticalFrequency());
ImGui::Text("Dot Clock Divider: %u", cs.dot_clock_divider);
ImGui::Text("Vertical Interlace: %s (%s field)", s_locals.GPUSTAT.vertical_interlace ? "Yes" : "No",
cs.interlaced_field ? "odd" : "even");
ImGui::Text("Current Scanline: %u (tick %u)", cs.current_scanline, cs.current_tick_in_scanline);
ImGui::Text("Display Disable: %s", s_locals.GPUSTAT.display_disable ? "Yes" : "No");
ImGui::Text("Displaying Odd Lines: %s", cs.active_line_lsb ? "Yes" : "No");
ImGui::Text("Color Depth: %u-bit", s_locals.GPUSTAT.display_area_color_depth_24 ? 24 : 15);
ImGui::Text("Start Offset in VRAM: (%u, %u)", cs.regs.X.GetValue(), cs.regs.Y.GetValue());
ImGui::Text("Display Total: %u (%u) horizontal, %u vertical", cs.horizontal_total,
cs.horizontal_total / cs.dot_clock_divider, cs.vertical_total);
ImGui::Text("Configured Display Range: %u-%u (%u-%u), %u-%u", cs.regs.X1.GetValue(), cs.regs.X2.GetValue(),
cs.regs.X1.GetValue() / cs.dot_clock_divider, cs.regs.X2.GetValue() / cs.dot_clock_divider,
cs.regs.Y1.GetValue(), cs.regs.Y2.GetValue());
ImGui::Text("Output Display Range: %u-%u (%u-%u), %u-%u", cs.horizontal_display_start, cs.horizontal_display_end,
cs.horizontal_display_start / cs.dot_clock_divider, cs.horizontal_display_end / cs.dot_clock_divider,
cs.vertical_display_start, cs.vertical_display_end);
ImGui::Text("Cropping: %s", Settings::GetDisplayCropModeName(g_settings.display_crop_mode));
ImGui::Text("Visible Display Range: %u-%u (%u-%u), %u-%u", cs.horizontal_visible_start, cs.horizontal_visible_end,
cs.horizontal_visible_start / cs.dot_clock_divider, cs.horizontal_visible_end / cs.dot_clock_divider,
cs.vertical_visible_start, cs.vertical_visible_end);
ImGui::Text("Display Resolution: %ux%u", cs.display_width, cs.display_height);
ImGui::Text("Display Origin: %u, %u", cs.display_origin_left, cs.display_origin_top);
ImGui::Text("Displayed/Visible VRAM Portion: %ux%u @ (%u, %u)", cs.display_vram_width, cs.display_vram_height,
cs.display_vram_left, cs.display_vram_top);
ImGui::Text("Padding: Left=%d, Top=%d, Right=%d, Bottom=%d", cs.display_origin_left, cs.display_origin_top,
label("Blitter state");
ImGui::TextUnformatted(state_strings[static_cast<u8>(s_locals.blitter_state)]);
label("Drawing area (inclusive)");
ImGui::Text("(%u, %u) - (%u, %u)", s_locals.drawing_area.left, s_locals.drawing_area.top,
s_locals.drawing_area.right, s_locals.drawing_area.bottom);
label("Drawing offset");
ImGui::Text("(%d, %d)", s_locals.drawing_offset.x, s_locals.drawing_offset.y);
label("Semi-transparency");
static constexpr std::array<const char*, 4> transparency_modes = {{"B/2 + F/2", "B + F", "B - F", "B + F/4"}};
ImGui::TextUnformatted(transparency_modes[static_cast<u8>(s_locals.draw_mode_reg.transparency_mode.GetValue())]);
flag("Dithering", s_locals.GPUSTAT.dither_enable);
flag("Draw to displayed field", s_locals.GPUSTAT.draw_to_displayed_field);
flag("Set mask bit", s_locals.GPUSTAT.set_mask_while_drawing);
flag("Check mask bit", s_locals.GPUSTAT.check_mask_before_draw);
flag("Reverse flag", s_locals.GPUSTAT.reverse_flag);
ImGui::EndTable();
}
ImGui::SeparatorText("Texture");
if (ImGui::BeginTable("texture", 2, table_flags))
{
label("Texture page");
ImGui::Text("(%u, %u)", s_locals.draw_mode_reg.GetTexturePageBaseX(), s_locals.draw_mode_reg.GetTexturePageBaseY());
flag("Texture page upper bank", s_locals.GPUSTAT.texture_page_y_base_2);
label("Texture format");
static constexpr std::array<const char*, 4> texture_modes = {
{"4-bit indexed", "8-bit indexed", "16-bit direct", "Reserved (16-bit)"}};
ImGui::TextUnformatted(texture_modes[static_cast<u8>(s_locals.draw_mode_reg.texture_mode.GetValue())]);
label("CLUT");
ImGui::Text("(%u, %u)", s_locals.texture_palette_reg.GetXBase(), s_locals.texture_palette_reg.GetYBase());
label("Window AND (X / Y)");
ImGui::Text("0x%02X / 0x%02X", s_locals.texture_window.and_x, s_locals.texture_window.and_y);
label("Window OR (X / Y)");
ImGui::Text("0x%02X / 0x%02X", s_locals.texture_window.or_x, s_locals.texture_window.or_y);
ImGui::EndTable();
}
ImGui::SeparatorText("Timing");
if (ImGui::BeginTable("timing", 2, table_flags))
{
label("Clock");
ImGui::TextUnformatted(clock);
flag("PAL mode", s_locals.GPUSTAT.pal_mode);
label("Horizontal frequency");
ImGui::Text("%.3f kHz", ComputeHorizontalFrequency() / 1000.0f);
label("Vertical frequency");
ImGui::Text("%.3f Hz", ComputeVerticalFrequency());
label("Dot clock divider");
ImGui::Text("%u", cs.dot_clock_divider);
label("Horizontal total");
ImGui::Text("%u ticks (%u dots)", cs.horizontal_total, cs.horizontal_total / cs.dot_clock_divider);
label("Vertical total");
ImGui::Text("%u lines", cs.vertical_total);
flag("Interlace", s_locals.GPUSTAT.vertical_interlace);
label("Current field");
ImGui::TextUnformatted(cs.interlaced_field ? "Odd" : "Even");
label("Current scanline");
ImGui::Text("%u (tick %u)", cs.current_scanline, cs.current_tick_in_scanline);
label("Displaying odd lines");
ImGui::TextUnformatted(cs.active_line_lsb ? "Yes" : "No");
ImGui::EndTable();
}
ImGui::TableSetColumnIndex(1);
ImGui::SeparatorText("Signals");
if (ImGui::BeginTable("signals", 2, table_flags))
{
label("Interrupt request");
ImGui::TextUnformatted(s_locals.GPUSTAT.interrupt_request ? "Asserted" : "Clear");
label("DMA request");
ImGui::TextUnformatted(s_locals.GPUSTAT.dma_data_request ? "Asserted" : "Clear");
ImGui::EndTable();
}
ImGui::SeparatorText("Display");
if (ImGui::BeginTable("display", 2, table_flags))
{
flag("Display output", !s_locals.GPUSTAT.display_disable);
label("Color depth");
ImGui::Text("%u-bit", s_locals.GPUSTAT.display_area_color_depth_24 ? 24 : 15);
label("VRAM start");
ImGui::Text("(%u, %u)", cs.regs.X.GetValue(), cs.regs.Y.GetValue());
label("Resolution");
ImGui::Text("%u x %u", cs.display_width, cs.display_height);
label("Cropping");
ImGui::TextUnformatted(Settings::GetDisplayCropModeName(g_settings.display_crop_mode));
label("Display origin");
ImGui::Text("(%u, %u)", cs.display_origin_left, cs.display_origin_top);
label("Visible VRAM portion");
ImGui::Text("%u x %u at (%u, %u)", cs.display_vram_width, cs.display_vram_height, cs.display_vram_left,
cs.display_vram_top);
label("Padding (L / T / R / B)");
ImGui::Text("%u / %u / %d / %d", cs.display_origin_left, cs.display_origin_top,
cs.display_width - cs.display_vram_width - cs.display_origin_left,
cs.display_height - cs.display_vram_height - cs.display_origin_top);
ImGui::EndTable();
}
const GSVector4i configured(cs.regs.X1.GetValue(), cs.regs.Y1.GetValue(), cs.regs.X2.GetValue(),
cs.regs.Y2.GetValue());
const GSVector4i output(cs.horizontal_display_start, cs.vertical_display_start, cs.horizontal_display_end,
cs.vertical_display_end);
const GSVector4i visible(cs.horizontal_visible_start, cs.vertical_visible_start, cs.horizontal_visible_end,
cs.vertical_visible_end);
ImGui::SeparatorText("Display ranges");
if (ImGui::BeginTable("ranges", 4, table_flags))
{
ImGui::TableSetupColumn("Range");
ImGui::TableSetupColumn("H ticks");
ImGui::TableSetupColumn("H dots");
ImGui::TableSetupColumn("V lines");
ImGui::TableHeadersRow();
static constexpr auto range_row = [](const char* text, const GSVector4i& rect) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(text);
ImGui::TableNextColumn();
ImGui::Text("%d-%d", rect.x, rect.z);
ImGui::TableNextColumn();
ImGui::Text("%u-%u", rect.x / cs.dot_clock_divider, rect.z / cs.dot_clock_divider);
ImGui::TableNextColumn();
ImGui::Text("%d-%d", rect.y, rect.w);
};
range_row("Configured", configured);
range_row("Output", output);
range_row("Visible", visible);
ImGui::EndTable();
}
if (copy_state)
ImGui::LogFinish();
{
ImGui::Spacing();
ImDrawList* const dl = ImGui::GetWindowDrawList();
const ImVec2 origin = ImGui::GetCursorScreenPos();
const ImVec2 size(ImGui::GetContentRegionAvail().x, 140.0f * scale);
dl->AddRectFilled(origin, origin + size, ImGui::GetColorU32(ImGuiCol_FrameBg));
static constexpr auto draw_range = [](ImDrawList* const dl, float scale, const ImVec2& origin, const ImVec2& size,
const GSVector4i& rect, ImU32 color, float thickness) {
const ImVec2 start(origin.x + size.x * std::clamp(static_cast<float>(rect.x) / cs.horizontal_total, 0.0f, 1.0f),
origin.y + size.y * std::clamp(static_cast<float>(rect.y) / cs.vertical_total, 0.0f, 1.0f));
const ImVec2 end(origin.x + size.x * std::clamp(static_cast<float>(rect.z) / cs.horizontal_total, 0.0f, 1.0f),
origin.y + size.y * std::clamp(static_cast<float>(rect.w) / cs.vertical_total, 0.0f, 1.0f));
dl->AddRect(start, end, color, 0.0f, thickness * scale);
};
static constexpr ImU32 configured_color = IM_COL32(234, 190, 100, 255);
static constexpr ImU32 output_color = IM_COL32(110, 175, 235, 255);
static constexpr ImU32 visible_color = IM_COL32(120, 215, 165, 255);
// Different outline widths keep coincident ranges visible.
draw_range(dl, scale, origin, size, output, output_color, 5.0f);
draw_range(dl, scale, origin, size, configured, configured_color, 3.0f);
draw_range(dl, scale, origin, size, visible, visible_color, 1.0f);
ImGui::Dummy(size);
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(configured_color), "Configured");
ImGui::SameLine();
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(output_color), "Output");
ImGui::SameLine();
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(visible_color), "Visible");
}
ImGui::EndTable();
ImGui::PopStyleColor();
ImGui::PopStyleVar();
}
GPUDump::Recorder* GPU::GetGPUDump()
+16 -6
View File
@@ -112,12 +112,12 @@ static void UpdateInputOverlay(void* buffer);
static constexpr size_t NUM_DEBUG_WINDOWS = 7;
static constexpr const std::array<DebugWindowInfo, NUM_DEBUG_WINDOWS> s_debug_window_info = {{
{"Freecam", "Free Camera", ":icons/applications-system.png", &GTE::DrawFreecamWindow, 510, 500},
{"SPU", "SPU State", ":icons/applications-system.png", &SPU::DrawDebugStateWindow, 820, 950},
{"CDROM", "CD-ROM State", ":icons/applications-system.png", &CDROM::DrawDebugWindow, 820, 555},
{"GPU", "GPU State", ":icons/applications-system.png", &GPU::DrawDebugStateWindow, 450, 550},
{"DMA", "DMA State", ":icons/applications-system.png", &DMA::DrawDebugStateWindow, 860, 180},
{"MDEC", "MDEC State", ":icons/applications-system.png", &MDEC::DrawDebugStateWindow, 300, 350},
{"Timers", "Timers State", ":icons/applications-system.png", &Timers::DrawDebugStateWindow, 800, 95},
{"SPU", "SPU State", ":icons/applications-system.png", &SPU::DrawDebugStateWindow, 1100, 745},
{"CDROM", "CD-ROM State", ":icons/applications-system.png", &CDROM::DrawDebugWindow, 1050, 650},
{"GPU", "GPU State", ":icons/applications-system.png", &GPU::DrawDebugStateWindow, 1000, 650},
{"DMA", "DMA State", ":icons/applications-system.png", &DMA::DrawDebugStateWindow, 1160, 215},
{"MDEC", "MDEC State", ":icons/applications-system.png", &MDEC::DrawDebugStateWindow, 900, 370},
{"Timers", "Timers State", ":icons/applications-system.png", &Timers::DrawDebugStateWindow, 800, 315},
}};
static std::array<ImGuiManager::AuxiliaryRenderWindowState, NUM_DEBUG_WINDOWS> s_debug_window_state = {};
@@ -149,11 +149,21 @@ void ImGuiManager::SaveDebugWindowVisibility(SettingsInterface& si, u8 debug_win
}
}
bool ImGuiManager::IsCDROMDebugWindowVisible(u8 debug_window_visibility)
{
return ((debug_window_visibility & (1u << 2)) != 0);
}
bool ImGuiManager::IsSPUDebugWindowVisible(u8 debug_window_visibility)
{
return ((debug_window_visibility & (1u << 1)) != 0);
}
bool ImGuiManager::IsGPUDebugWindowVisible(u8 debug_window_visibility)
{
return ((debug_window_visibility & (1u << 3)) != 0);
}
bool ImGuiManager::UpdateDebugWindowConfig(u8 debug_window_visibility)
{
bool was_changed = false;
+2
View File
@@ -19,7 +19,9 @@ void UpdateInputOverlay();
void RenderTextOverlays(const GPUBackend* gpu);
u8 LoadDebugWindowVisibility(const SettingsInterface& si);
void SaveDebugWindowVisibility(SettingsInterface& si, u8 debug_window_visibility);
bool IsCDROMDebugWindowVisible(u8 debug_window_visibility);
bool IsSPUDebugWindowVisible(u8 debug_window_visibility);
bool IsGPUDebugWindowVisible(u8 debug_window_visibility);
void RenderDebugWindows();
bool UpdateDebugWindowConfig(u8 debug_window_visibility);
void DestroyAllDebugWindows();
+125 -28
View File
@@ -16,9 +16,11 @@
#include "common/gsvector.h"
#include "common/log.h"
#include "IconsFontAwesome.h"
#include "imgui.h"
#include <array>
#include <cmath>
#include <memory>
LOG_CHANNEL(MDEC);
@@ -1254,37 +1256,132 @@ void MDEC::SetScaleMatrix(const u16* values)
void MDEC::DrawDebugStateWindow(float scale)
{
static constexpr std::array<const char*, 5> state_names = {
{"None", "Decoding Macroblock", "Writing Macroblock", "SetIqTab", "SetScale"}};
static constexpr std::array<const char*, 6> state_names = {{"Idle", "Decoding macroblock", "Writing macroblock",
"Loading quantization table", "Loading scale table",
"No command"}};
static constexpr std::array<const char*, 4> output_depths = {{"4-bit", "8-bit", "24-bit", "15-bit"}};
static constexpr std::array<const char*, 7> block_names = {{"Crblk", "Cbblk", "Y1", "Y2", "Y3", "Y4", "Output"}};
static constexpr std::array<const char*, 7> block_names = {{"Cr", "Cb", "Y1", "Y2", "Y3", "Y4", "Output"}};
#if defined(_DEBUG) || defined(_DEVEL)
ImGui::Text("Blocks Decoded: %u", s_state.total_blocks_decoded);
ImGui::Text("IDCT Fast Path: %u/%u (%.1f%%)", s_state.fast_idct_blocks, s_state.total_idct_blocks,
s_state.total_idct_blocks ? (static_cast<double>(s_state.fast_idct_blocks) * 100.0 /
static_cast<double>(s_state.total_idct_blocks)) :
0.0);
#endif
ImGui::Text("Data-In FIFO Size: %u (%u bytes)", s_state.data_in_fifo.GetSize(), s_state.data_in_fifo.GetSize() * 4);
ImGui::Text("Data-Out FIFO Size: %u (%u bytes)", s_state.data_out_fifo.GetSize(),
s_state.data_out_fifo.GetSize() * 4);
ImGui::Text("DMA Enable: %s%s", s_state.enable_dma_in ? "In " : "", s_state.enable_dma_out ? "Out" : "");
ImGui::Text("Current State: %s", state_names[static_cast<u8>(s_state.state)]);
ImGui::Text("Current Block: %s", block_names[s_state.current_block]);
ImGui::Text("Current Coefficient: %u", s_state.current_coefficient);
const float left = ImGui::GetCursorPosX();
const float button_width =
ImGui::CalcTextSize(ICON_FA_COPY " Copy State").x + ImGui::GetStyle().FramePadding.x * 2.0f;
ImGui::SetCursorPosX(left + ImGui::GetContentRegionAvail().x - button_width);
const bool copy_state = ImGui::Button(ICON_FA_COPY " Copy State");
if (copy_state)
ImGui::LogToClipboard();
ImGui::SameLine(left);
ImGui::PushFont(nullptr, std::ceil(18.0f * scale), 0.0f);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(state_names[static_cast<u8>(s_state.state)]);
ImGui::PopFont();
ImGui::Spacing();
if (ImGui::CollapsingHeader("Status", ImGuiTreeNodeFlags_DefaultOpen))
constexpr ImGuiTableFlags table_flags =
ImGuiTableFlags_RowBg | ImGuiTableFlags_PadOuterX | ImGuiTableFlags_SizingStretchSame;
static constexpr auto label = [](const char* text) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImVec4 color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
color.w *= 0.7f;
ImGui::TextColored(color, "%s", text);
ImGui::TableNextColumn();
};
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10.0f * scale, ImGui::GetStyle().CellPadding.y));
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImVec4(1.0f, 1.0f, 1.0f, 0.035f));
if (ImGui::BeginTable("layout", 2, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_BordersInnerV))
{
ImGui::Text("Data-Out FIFO Empty: %s", s_state.status.data_out_fifo_empty ? "Yes" : "No");
ImGui::Text("Data-In FIFO Full: %s", s_state.status.data_in_fifo_full ? "Yes" : "No");
ImGui::Text("Command Busy: %s", s_state.status.command_busy ? "Yes" : "No");
ImGui::Text("Data-In Request: %s", s_state.status.data_in_request ? "Yes" : "No");
ImGui::Text("Output Depth: %s", output_depths[static_cast<u8>(s_state.status.data_output_depth.GetValue())]);
ImGui::Text("Output Signed: %s", s_state.status.data_output_signed ? "Yes" : "No");
ImGui::Text("Output Bit 15: %u", ZeroExtend32(s_state.status.data_output_bit15.GetValue()));
ImGui::Text("Current Block: %u", ZeroExtend32(s_state.status.current_block.GetValue()));
ImGui::Text("Parameter Words Remaining: %d",
static_cast<s32>(SignExtend32(s_state.status.parameter_words_remaining.GetValue())));
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::SeparatorText("Decoder");
if (ImGui::BeginTable("decoder", 2, table_flags))
{
label("Block");
ImGui::TextUnformatted(block_names[s_state.current_block]);
label("Coefficient");
ImGui::Text("%u", s_state.current_coefficient);
label("Q scale");
ImGui::Text("%u", s_state.current_q_scale);
label("Parameter halfwords");
ImGui::Text("%u", s_state.remaining_halfwords);
label("Command busy");
ImGui::TextUnformatted(s_state.status.command_busy ? "Yes" : "No");
ImGui::EndTable();
}
ImGui::SeparatorText("FIFO");
if (ImGui::BeginTable("fifo", 2, table_flags))
{
label("Input");
ImGui::TextWrapped("%u / %u halfwords (%u bytes)", s_state.data_in_fifo.GetSize(),
s_state.data_in_fifo.GetCapacity(), s_state.data_in_fifo.GetSize() * 2);
label("Output");
ImGui::TextWrapped("%u / %u words (%u bytes)", s_state.data_out_fifo.GetSize(),
s_state.data_out_fifo.GetCapacity(), s_state.data_out_fifo.GetSize() * 4);
label("Input full");
ImGui::TextUnformatted(s_state.status.data_in_fifo_full ? "Yes" : "No");
label("Output empty");
ImGui::TextUnformatted(s_state.status.data_out_fifo_empty ? "Yes" : "No");
ImGui::EndTable();
}
#if defined(_DEBUG) || defined(_DEVEL)
ImGui::SeparatorText("Statistics");
if (ImGui::BeginTable("statistics", 2, table_flags))
{
label("Blocks decoded");
ImGui::Text("%u", s_state.total_blocks_decoded);
label("IDCT fast path");
ImGui::TextWrapped("%u / %u (%.1f%%)", s_state.fast_idct_blocks, s_state.total_idct_blocks,
s_state.total_idct_blocks ? (static_cast<double>(s_state.fast_idct_blocks) * 100.0 /
static_cast<double>(s_state.total_idct_blocks)) :
0.0);
ImGui::EndTable();
}
#endif
ImGui::TableSetColumnIndex(1);
ImGui::SeparatorText("Output");
if (ImGui::BeginTable("output", 2, table_flags))
{
label("Depth");
ImGui::TextUnformatted(output_depths[static_cast<u8>(s_state.status.data_output_depth.GetValue())]);
label("Signed");
ImGui::TextUnformatted(s_state.status.data_output_signed ? "Yes" : "No");
label("Bit 15");
ImGui::Text("%u", ZeroExtend32(s_state.status.data_output_bit15.GetValue()));
ImGui::EndTable();
}
ImGui::SeparatorText("DMA");
if (ImGui::BeginTable("dma", 2, table_flags))
{
label("Input");
ImGui::TextUnformatted(s_state.enable_dma_in ? "Enabled" : "Disabled");
label("Output");
ImGui::TextUnformatted(s_state.enable_dma_out ? "Enabled" : "Disabled");
label("Input request");
ImGui::TextUnformatted(s_state.status.data_in_request ? "Asserted" : "Clear");
label("Output request");
ImGui::TextUnformatted(s_state.status.data_out_request ? "Asserted" : "Clear");
ImGui::EndTable();
}
ImGui::SeparatorText("Status register");
if (ImGui::BeginTable("status", 2, table_flags))
{
label("Value");
ImGui::Text("%08X", s_state.status.bits);
label("Block");
ImGui::Text("%u", ZeroExtend32(s_state.status.current_block.GetValue()));
label("Parameter words - 1");
ImGui::Text("%u", ZeroExtend32(s_state.status.parameter_words_remaining.GetValue()));
ImGui::EndTable();
}
ImGui::EndTable();
}
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (copy_state)
ImGui::LogFinish();
}
+360 -281
View File
@@ -25,9 +25,11 @@
#include "common/path.h"
#include "IconsEmoji.h"
#include "IconsFontAwesome.h"
#include "fmt/format.h"
#include "imgui.h"
#include <cmath>
#include <memory>
LOG_CHANNEL(SPU);
@@ -2858,307 +2860,384 @@ static constexpr const char* GetReverbModeName(const SPU::ReverbRegisters& rever
void SPU::DrawDebugStateWindow(float scale)
{
static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f};
static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f};
ImVec4 inactive_color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
inactive_color.w *= 0.5f;
#ifdef SPU_ENABLE_VU_METER
const auto draw_vu_meter = [&scale](s16* peaks) {
const auto draw_vu_meter = [scale](s16* peaks) {
constexpr s32 num_sections = 12;
constexpr s32 amp_per_section = 32767 / num_sections;
const s32 lidx = peaks[0] / amp_per_section;
const s32 ridx = peaks[1] / amp_per_section;
const ImVec2 section_size = ImVec2(std::floor(scale * 8.0f), std::floor(scale * 4.0f));
const float divider_size = std::floor(scale * 1.0f);
ImVec2 left_start = ImGui::GetCursorScreenPos() + ImVec2(0.0f, std::floor(scale * 4.0f));
ImVec2 right_start = left_start + ImVec2(0.0f, section_size.y + divider_size);
for (s32 i = 0; i < num_sections; i++)
const float width = std::max(1.0f, std::min(ImGui::GetContentRegionAvail().x, 108.0f * scale));
const float gap = std::min(scale, width / (num_sections * 2));
const ImVec2 section_size((width - gap * (num_sections - 1)) / num_sections, 4.0f * scale);
const ImVec2 origin = ImGui::GetCursorScreenPos();
const float offset_y = std::max(0.0f, (ImGui::GetTextLineHeight() - section_size.y * 2.0f - gap) * 0.5f);
for (u32 channel = 0; channel < 2; channel++)
{
u32 left_color = IM_COL32(30, 30, 30, 255);
if (peaks[0] > 0 && lidx >= i)
const s32 level = peaks[channel] / amp_per_section;
ImVec2 pos(origin.x, origin.y + offset_y + channel * (section_size.y + gap));
for (s32 section = 0; section < num_sections; section++)
{
left_color = IM_COL32(255, 0, 0, 255);
left_color = (i <= 8) ? IM_COL32(255, 255, 0, 255) : left_color;
left_color = (i <= 6) ? IM_COL32(0, 255, 0, 255) : left_color;
const ImU32 color = (peaks[channel] > 0 && level >= section) ? (section <= 6 ? IM_COL32(0, 220, 80, 255) :
section <= 8 ? IM_COL32(240, 200, 60, 255) :
IM_COL32(240, 70, 60, 255)) :
IM_COL32(30, 30, 30, 255);
ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + section_size, color);
pos.x += section_size.x + gap;
}
u32 right_color = IM_COL32(30, 30, 30, 255);
if (peaks[1] > 0 && ridx >= i)
{
right_color = IM_COL32(255, 0, 0, 255);
right_color = (i <= 8) ? IM_COL32(255, 255, 0, 255) : right_color;
right_color = (i <= 6) ? IM_COL32(0, 255, 0, 255) : right_color;
}
ImGui::GetWindowDrawList()->AddRectFilled(left_start, left_start + section_size, left_color);
ImGui::GetWindowDrawList()->AddRectFilled(right_start, right_start + section_size, right_color);
left_start.x += section_size.x + divider_size;
right_start.x += section_size.x + divider_size;
peaks[channel] = 0;
}
peaks[0] = 0;
peaks[1] = 0;
ImGui::Dummy(ImVec2(width, ImGui::GetTextLineHeight()));
};
#endif
// status
if (ImGui::CollapsingHeader("Status", ImGuiTreeNodeFlags_DefaultOpen))
const ImVec2 content_pos = ImGui::GetCursorScreenPos();
const ImVec2 content_size = ImGui::GetContentRegionAvail();
const float content_height = std::max(1.0f, content_size.y - ImGui::GetFrameHeightWithSpacing());
const float button_width =
ImGui::CalcTextSize(ICON_FA_COPY " Copy State").x + ImGui::GetStyle().FramePadding.x * 2.0f;
// Submit the footer first so clipboard logging can capture both tabs on the same frame.
ImGui::SetCursorScreenPos(ImVec2(content_pos.x, content_pos.y + content_height + ImGui::GetStyle().ItemSpacing.y));
if (ImGui::Button("Key Off All Voices"))
{
static constexpr std::array<const char*, 4> transfer_modes = {
{"Transfer Stopped", "Manual Write", "DMA Write", "DMA Read"}};
const std::array<float, 6> offsets = {
{100.0f * scale, 200.0f * scale, 300.0f * scale, 420.0f * scale, 500.0f * scale, 600.0f * scale}};
ImGui::Text("Control: ");
ImGui::SameLine(offsets[0]);
ImGui::TextColored(s_state.SPUCNT.enable ? active_color : inactive_color, "SPU Enable");
ImGui::SameLine(offsets[1]);
ImGui::TextColored(s_state.SPUCNT.mute_n ? inactive_color : active_color, "Mute SPU");
ImGui::SameLine(offsets[2]);
ImGui::TextColored(s_state.SPUCNT.external_audio_enable ? active_color : inactive_color, "External Audio");
ImGui::SameLine(offsets[3]);
ImGui::TextColored(s_state.SPUCNT.ram_transfer_mode != RAMTransferMode::Stopped ? active_color : inactive_color,
"%s", transfer_modes[static_cast<u8>(s_state.SPUCNT.ram_transfer_mode.GetValue())]);
ImGui::Text("Status: ");
ImGui::SameLine(offsets[0]);
ImGui::TextColored(s_state.SPUSTAT.irq9_flag ? active_color : inactive_color, "IRQ9");
ImGui::SameLine(offsets[1]);
ImGui::TextColored(s_state.SPUSTAT.dma_request ? active_color : inactive_color, "DMA Request");
ImGui::SameLine(offsets[2]);
ImGui::TextColored(s_state.SPUSTAT.dma_read_request ? active_color : inactive_color, "DMA Read");
ImGui::SameLine(offsets[3]);
ImGui::TextColored(s_state.SPUSTAT.dma_write_request ? active_color : inactive_color, "DMA Write");
ImGui::SameLine(offsets[4]);
ImGui::TextColored(s_state.SPUSTAT.transfer_busy ? active_color : inactive_color, "Transfer Busy");
ImGui::SameLine(offsets[5]);
ImGui::TextColored(s_state.SPUSTAT.second_half_capture_buffer ? active_color : inactive_color,
"Second Capture Buffer");
ImGui::Text("Interrupt: ");
ImGui::SameLine(offsets[0]);
ImGui::TextColored(s_state.SPUCNT.irq9_enable ? active_color : inactive_color,
s_state.SPUCNT.irq9_enable ? "Enabled @ 0x%04X (actual 0x%08X)" :
"Disabled @ 0x%04X (actual 0x%08X)",
s_state.irq_address, (ZeroExtend32(s_state.irq_address) * 8) & RAM_MASK);
ImGui::Text("Volume: ");
ImGui::SameLine(offsets[0]);
ImGui::Text("Left: %.1f%%", ApplyVolumeF(100.0f, s_state.main_volume_left.current_level));
ImGui::SameLine(offsets[1]);
ImGui::Text("Right: %.1f%%", ApplyVolumeF(100.0f, s_state.main_volume_right.current_level));
#ifdef SPU_ENABLE_VU_METER
ImGui::SameLine(offsets[2]);
draw_vu_meter(s_state.output_peaks);
ImGui::NewLine();
#endif
ImGui::Text("CD Audio: ");
ImGui::SameLine(offsets[0]);
ImGui::TextColored(s_state.SPUCNT.cd_audio_enable ? active_color : inactive_color,
s_state.SPUCNT.cd_audio_enable ? "Enabled" : "Disabled");
ImGui::SameLine(offsets[1]);
ImGui::TextColored(s_state.SPUCNT.cd_audio_enable ? active_color : inactive_color, "Left Volume: %.1f%%",
ApplyVolumeF(100.0f, s_state.cd_audio_volume_left));
ImGui::SameLine(offsets[3]);
ImGui::TextColored(s_state.SPUCNT.cd_audio_enable ? active_color : inactive_color, "Right Volume: %.1f%%",
ApplyVolumeF(100.0f, s_state.cd_audio_volume_right));
#ifdef SPU_ENABLE_VU_METER
ImGui::SameLine(offsets[5]);
draw_vu_meter(s_state.cd_audio_peaks);
ImGui::NewLine();
#endif
ImGui::Text("Transfer FIFO: ");
ImGui::SameLine(offsets[0]);
ImGui::TextColored(s_state.transfer_event.IsActive() ? active_color : inactive_color, "%u halfwords (%u bytes)",
s_state.transfer_fifo.GetSize(), s_state.transfer_fifo.GetSize() * 2);
}
// draw voice states
if (ImGui::CollapsingHeader("Voice State", ImGuiTreeNodeFlags_DefaultOpen))
{
static constexpr std::array column_titles = {
"#", "StartAddr", "RepeatAddr", "CurAddr", "SampleIdx", "SampleRate",
"VolLeft", "VolRight", "ADSRPhase", "ADSRVol", "ADSRTicks",
#ifdef SPU_ENABLE_VU_METER
"VUMeter",
#endif
};
static constexpr std::array adsr_phases = {"Off", "Attack", "Decay", "Sustain", "Release"};
ImGui::Columns(static_cast<int>(column_titles.size()));
// headers
for (const char* column_title : column_titles)
for (Voice& voice : s_state.voices)
{
ImGui::TextUnformatted(column_title);
ImGui::NextColumn();
}
// states
for (u32 voice_index = 0; voice_index < NUM_VOICES; voice_index++)
{
const Voice& v = s_state.voices[voice_index];
ImVec4 color = v.IsOn() ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
ImGui::TextColored(color, "%u", ZeroExtend32(voice_index));
ImGui::NextColumn();
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_start_address));
ImGui::NextColumn();
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_repeat_address));
ImGui::NextColumn();
ImGui::TextColored(color, "%04X", ZeroExtend32(v.current_address));
ImGui::NextColumn();
if (IsVoiceNoiseEnabled(voice_index))
ImGui::TextColored(color, "NOISE");
else
ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.sample_index.GetValue()));
ImGui::NextColumn();
ImGui::TextColored(color, "%.2f", (float(v.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f);
ImGui::NextColumn();
ImGui::TextColored(color, "%.1f%%", ApplyVolumeF(100.0f, v.left_volume.current_level));
ImGui::NextColumn();
ImGui::TextColored(color, "%.1f%%", ApplyVolumeF(100.0f, v.right_volume.current_level));
ImGui::NextColumn();
ImGui::TextColored(color, "%s", adsr_phases[static_cast<u8>(v.adsr_phase)]);
ImGui::NextColumn();
ImGui::TextColored(color, "%.1f%%", ApplyVolumeF(100.0f, v.regs.adsr_volume));
ImGui::NextColumn();
ImGui::TextColored(color, "%d", v.adsr_envelope.counter);
ImGui::NextColumn();
#ifdef SPU_ENABLE_VU_METER
draw_vu_meter(s_state.voice_peaks[voice_index]);
ImGui::NextColumn();
#endif
}
ImGui::Columns(1);
}
if (ImGui::CollapsingHeader("Reverb", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::TextColored(s_state.SPUCNT.reverb_master_enable ? active_color : inactive_color, "Master Enable: %s",
s_state.SPUCNT.reverb_master_enable ? "Yes" : "No");
ImGui::Text("Voices Enabled: ");
for (u32 i = 0; i < NUM_VOICES; i++)
{
ImGui::SameLine(0.0f, 16.0f);
const bool active = IsVoiceReverbEnabled(i);
ImGui::TextColored(active ? active_color : inactive_color, "%u", i);
}
ImGui::TextColored(s_state.SPUCNT.cd_audio_reverb ? active_color : inactive_color, "CD Audio Enable: %s",
s_state.SPUCNT.cd_audio_reverb ? "Yes" : "No");
ImGui::TextColored(s_state.SPUCNT.external_audio_reverb ? active_color : inactive_color,
"External Audio Enable: %s", s_state.SPUCNT.external_audio_reverb ? "Yes" : "No");
ImGui::Text("Base Address: 0x%08X (%04X)", s_state.reverb_base_address, s_state.reverb_registers.mBASE);
ImGui::Text("Current Address: 0x%08X", s_state.reverb_current_address);
ImGui::Text("Current Amplitude: Input (%d, %d) Output (%d, %d)", s_state.last_reverb_input[0],
s_state.last_reverb_input[1], s_state.last_reverb_output[0], s_state.last_reverb_output[1]);
ImGui::Text("Current Mode: %s", GetReverbModeName(s_state.reverb_registers));
ImGui::Text("Output Volume: Left %.1f%% Right %.1f%%", ApplyVolumeF(100.0f, s_state.reverb_registers.vLOUT),
ApplyVolumeF(100.0f, s_state.reverb_registers.vROUT));
#ifdef SPU_ENABLE_VU_METER
ImGui::SameLine();
draw_vu_meter(s_state.reverb_peaks);
ImGui::NewLine();
#endif
ImGui::Text("Pitch Modulation: ");
for (u32 i = 1; i < NUM_VOICES; i++)
{
ImGui::SameLine(0.0f, 16.0f);
const bool active = IsPitchModulationEnabled(i);
ImGui::TextColored(active ? active_color : inactive_color, "%u", i);
voice.KeyOff();
voice.adsr_envelope.counter = 0;
voice.regs.adsr_volume = 0;
}
}
ImGui::SetCursorScreenPos(ImVec2(content_pos.x + content_size.x - button_width,
content_pos.y + content_height + ImGui::GetStyle().ItemSpacing.y));
const bool copy_state = ImGui::Button(ICON_FA_COPY " Copy State");
if (copy_state)
ImGui::LogToClipboard();
ImGui::SetCursorScreenPos(content_pos);
if (ImGui::CollapsingHeader("Reverb Environment"))
constexpr ImGuiTableFlags table_flags =
ImGuiTableFlags_RowBg | ImGuiTableFlags_PadOuterX | ImGuiTableFlags_SizingStretchSame;
const auto label = [](const char* text) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImVec4 color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
color.w *= 0.7f;
ImGui::TextColored(color, "%s", text);
ImGui::TableNextColumn();
};
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(6.0f * scale, ImGui::GetStyle().CellPadding.y));
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImVec4(1.0f, 1.0f, 1.0f, 0.035f));
if (ImGui::BeginChild("state", ImVec2(0.0f, content_height)))
{
ImGui::Columns(4);
ImGui::Text("[0] FB_SRC_A: 0x%04X", static_cast<u32>(s_state.reverb_registers.FB_SRC_A));
ImGui::NextColumn();
ImGui::Text("[1] FB_SRC_B: 0x%04X", static_cast<u32>(s_state.reverb_registers.FB_SRC_B));
ImGui::NextColumn();
ImGui::Text("[2] IIR_ALPHA: %d", static_cast<s32>(s_state.reverb_registers.IIR_ALPHA));
ImGui::NextColumn();
ImGui::Text("[3] ACC_COEF_A: %d", static_cast<s32>(s_state.reverb_registers.ACC_COEF_A));
ImGui::NextColumn();
ImGui::Text("[4] ACC_COEF_B: %d", static_cast<s32>(s_state.reverb_registers.ACC_COEF_B));
ImGui::NextColumn();
ImGui::Text("[5] ACC_COEF_C: %d", static_cast<s32>(s_state.reverb_registers.ACC_COEF_C));
ImGui::NextColumn();
ImGui::Text("[6] ACC_COEF_D: %d", static_cast<s32>(s_state.reverb_registers.ACC_COEF_D));
ImGui::NextColumn();
ImGui::Text("[7] IIR_COEF: %d", static_cast<s32>(s_state.reverb_registers.IIR_COEF));
ImGui::NextColumn();
ImGui::Text("[8] FB_ALPHA: %d", static_cast<s32>(s_state.reverb_registers.FB_ALPHA));
ImGui::NextColumn();
ImGui::Text("[9] FB_X: %d", static_cast<s32>(s_state.reverb_registers.FB_X));
ImGui::NextColumn();
ImGui::Text("[10] IIR_DEST_A[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.IIR_DEST_A[0]));
ImGui::NextColumn();
ImGui::Text("[11] IIR_DEST_A[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.IIR_DEST_A[1]));
ImGui::NextColumn();
ImGui::Text("[12] ACC_SRC_A[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.ACC_SRC_A[0]));
ImGui::NextColumn();
ImGui::Text("[13] ACC_SRC_A[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.ACC_SRC_A[1]));
ImGui::NextColumn();
ImGui::Text("[14] ACC_SRC_B[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.ACC_SRC_B[0]));
ImGui::NextColumn();
ImGui::Text("[15] ACC_SRC_B[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.ACC_SRC_B[1]));
ImGui::NextColumn();
ImGui::Text("[16] IIR_SRC_A[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.IIR_SRC_A[0]));
ImGui::NextColumn();
ImGui::Text("[17] IIR_SRC_A[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.IIR_SRC_A[1]));
ImGui::NextColumn();
ImGui::Text("[18] IIR_DEST_B[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.IIR_DEST_B[0]));
ImGui::NextColumn();
ImGui::Text("[19] IIR_DEST_B[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.IIR_DEST_B[1]));
ImGui::NextColumn();
ImGui::Text("[20] ACC_SRC_C[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.ACC_SRC_C[0]));
ImGui::NextColumn();
ImGui::Text("[21] ACC_SRC_C[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.ACC_SRC_C[1]));
ImGui::NextColumn();
ImGui::Text("[22] ACC_SRC_D[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.ACC_SRC_D[0]));
ImGui::NextColumn();
ImGui::Text("[23] ACC_SRC_D[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.ACC_SRC_D[1]));
ImGui::NextColumn();
ImGui::Text("[24] IIR_SRC_B[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.IIR_SRC_B[0]));
ImGui::NextColumn();
ImGui::Text("[25] IIR_SRC_B[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.IIR_SRC_B[1]));
ImGui::NextColumn();
ImGui::Text("[26] MIX_DEST_A[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.MIX_DEST_A[0]));
ImGui::NextColumn();
ImGui::Text("[27] MIX_DEST_A[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.MIX_DEST_A[1]));
ImGui::NextColumn();
ImGui::Text("[28] MIX_DEST_B[L]: 0x%04X", static_cast<u32>(s_state.reverb_registers.MIX_DEST_B[0]));
ImGui::NextColumn();
ImGui::Text("[29] MIX_DEST_B[R]: 0x%04X", static_cast<u32>(s_state.reverb_registers.MIX_DEST_B[1]));
ImGui::NextColumn();
ImGui::Text("[30] IN_COEF[L]: %d", static_cast<u32>(s_state.reverb_registers.IN_COEF[0]));
ImGui::NextColumn();
ImGui::Text("[31] IN_COEF[R]: %d", static_cast<u32>(s_state.reverb_registers.IN_COEF[1]));
ImGui::NextColumn();
ImGui::Columns(1);
}
if (ImGui::CollapsingHeader("Hacks", ImGuiTreeNodeFlags_DefaultOpen))
{
if (ImGui::Button("Key Off All Voices"))
if (ImGui::BeginTable("overview", 2, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_BordersInnerV))
{
for (u32 i = 0; i < NUM_VOICES; i++)
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
u32 active_voices = 0;
for (const Voice& voice : s_state.voices)
active_voices += BoolToUInt32(voice.IsOn());
ImGui::SeparatorText(
TinyString::from_format("Mixer ({} / {} voices active)", active_voices, static_cast<u32>(NUM_VOICES)).c_str());
if (ImGui::BeginTable("mixer",
#ifdef SPU_ENABLE_VU_METER
6,
#else
5,
#endif
table_flags))
{
s_state.voices[i].KeyOff();
s_state.voices[i].adsr_envelope.counter = 0;
s_state.voices[i].regs.adsr_volume = 0;
ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthStretch, 1.1f);
ImGui::TableSetupColumn("State", ImGuiTableColumnFlags_WidthStretch, 1.1f);
ImGui::TableSetupColumn("Left vol.", ImGuiTableColumnFlags_WidthStretch, 0.9f);
ImGui::TableSetupColumn("Right vol.", ImGuiTableColumnFlags_WidthStretch, 0.9f);
ImGui::TableSetupColumn("Reverb send", ImGuiTableColumnFlags_WidthStretch, 1.2f);
#ifdef SPU_ENABLE_VU_METER
ImGui::TableSetupColumn("Peaks L/R", ImGuiTableColumnFlags_WidthFixed, 108.0f * scale);
#endif
ImGui::TableHeadersRow();
const auto mix_row = [](const char* source, bool enabled, s16 volume_left, s16 volume_right, const char* send) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(source);
ImGui::TableNextColumn();
ImGui::TextUnformatted(enabled ? "Enabled" : "Disabled");
ImGui::TableNextColumn();
ImGui::Text("%.1f%%", ApplyVolumeF(100.0f, volume_left));
ImGui::TableNextColumn();
ImGui::Text("%.1f%%", ApplyVolumeF(100.0f, volume_right));
ImGui::TableNextColumn();
ImGui::TextUnformatted(send);
};
mix_row("Main", s_state.SPUCNT.enable, s_state.main_volume_left.current_level,
s_state.main_volume_right.current_level, "-");
#ifdef SPU_ENABLE_VU_METER
ImGui::TableNextColumn();
draw_vu_meter(s_state.output_peaks);
#endif
mix_row("CD", s_state.SPUCNT.cd_audio_enable, s_state.cd_audio_volume_left, s_state.cd_audio_volume_right,
s_state.SPUCNT.cd_audio_reverb ? "Yes" : "No");
#ifdef SPU_ENABLE_VU_METER
ImGui::TableNextColumn();
draw_vu_meter(s_state.cd_audio_peaks);
#endif
mix_row("External", s_state.SPUCNT.external_audio_enable, s_state.external_volume_left,
s_state.external_volume_right, s_state.SPUCNT.external_audio_reverb ? "Yes" : "No");
#ifdef SPU_ENABLE_VU_METER
ImGui::TableNextColumn();
ImGui::TextUnformatted("-");
#endif
mix_row("Reverb", s_state.SPUCNT.reverb_master_enable, s_state.reverb_registers.vLOUT,
s_state.reverb_registers.vROUT, "-");
#ifdef SPU_ENABLE_VU_METER
ImGui::TableNextColumn();
draw_vu_meter(s_state.reverb_peaks);
#endif
ImGui::EndTable();
}
if (ImGui::BeginTable("reverb_levels", 3, table_flags))
{
ImGui::TableSetupColumn("Reverb (raw)");
ImGui::TableSetupColumn("Left");
ImGui::TableSetupColumn("Right");
ImGui::TableHeadersRow();
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("Input");
ImGui::TableNextColumn();
ImGui::Text("%d", s_state.last_reverb_input[0]);
ImGui::TableNextColumn();
ImGui::Text("%d", s_state.last_reverb_input[1]);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("Output");
ImGui::TableNextColumn();
ImGui::Text("%d", s_state.last_reverb_output[0]);
ImGui::TableNextColumn();
ImGui::Text("%d", s_state.last_reverb_output[1]);
ImGui::EndTable();
}
ImGui::TableSetColumnIndex(1);
ImGui::SeparatorText("Transfer / IRQ");
if (ImGui::BeginTable("transfer", 2, table_flags))
{
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthStretch, 0.35f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.65f);
static constexpr std::array transfer_modes = {"Stopped", "Manual write", "DMA write", "DMA read"};
label("Transfer mode");
ImGui::TextUnformatted(transfer_modes[static_cast<u8>(s_state.SPUCNT.ram_transfer_mode.GetValue())]);
label("Transfer FIFO");
ImGui::Text("%u halfwords (%u bytes)", s_state.transfer_fifo.GetSize(), s_state.transfer_fifo.GetSize() * 2);
label("Interrupt");
ImGui::Text("%s @ %04X (0x%05X bytes)", s_state.SPUCNT.irq9_enable ? "Enabled" : "Disabled",
s_state.irq_address, (ZeroExtend32(s_state.irq_address) * 8) & RAM_MASK);
label("Capture buffer");
ImGui::TextUnformatted(s_state.SPUSTAT.second_half_capture_buffer ? "Second" : "First");
label("Muted");
ImGui::TextUnformatted(s_state.SPUCNT.mute_n ? "No" : "Yes");
ImGui::EndTable();
}
if (ImGui::BeginTable("signals", 5, table_flags))
{
ImGui::TableSetupColumn("IRQ9");
ImGui::TableSetupColumn("DMA");
ImGui::TableSetupColumn("DMA read");
ImGui::TableSetupColumn("DMA write");
ImGui::TableSetupColumn("Busy");
ImGui::TableHeadersRow();
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(s_state.SPUSTAT.irq9_flag ? "Asserted" : "Clear");
ImGui::TableNextColumn();
ImGui::TextUnformatted(s_state.SPUSTAT.dma_request ? "Asserted" : "Clear");
ImGui::TableNextColumn();
ImGui::TextUnformatted(s_state.SPUSTAT.dma_read_request ? "Asserted" : "Clear");
ImGui::TableNextColumn();
ImGui::TextUnformatted(s_state.SPUSTAT.dma_write_request ? "Asserted" : "Clear");
ImGui::TableNextColumn();
ImGui::TextUnformatted(s_state.SPUSTAT.transfer_busy ? "Yes" : "No");
ImGui::EndTable();
}
if (ImGui::BeginTable("reverb_state", 3, table_flags))
{
ImGui::TableSetupColumn("Reverb preset");
ImGui::TableSetupColumn("Base (byte / reg)");
ImGui::TableSetupColumn("Current (byte)");
ImGui::TableHeadersRow();
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(GetReverbModeName(s_state.reverb_registers));
ImGui::TableNextColumn();
ImGui::Text("0x%05X / %04X", s_state.reverb_base_address, s_state.reverb_registers.mBASE);
ImGui::TableNextColumn();
ImGui::Text("0x%05X", s_state.reverb_current_address);
ImGui::EndTable();
}
ImGui::EndTable();
}
if (ImGui::BeginTabBar("state_tabs"))
{
const bool voices_tab = ImGui::BeginTabItem("Voices");
// Clipboard logging includes both tabs, without retaining a separate snapshot.
if (voices_tab || copy_state)
{
static constexpr int num_columns =
#ifdef SPU_ENABLE_VU_METER
14;
#else
13;
#endif
static constexpr std::array adsr_phases = {"Off", "Attack", "Decay", "Sustain", "Release"};
const float table_height = std::max(1.0f, ImGui::GetContentRegionAvail().y - ImGui::GetStyle().ItemSpacing.y);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(6.0f * scale, 1.0f * scale));
if (ImGui::BeginTable("voices", num_columns, table_flags | ImGuiTableFlags_ScrollY, ImVec2(0.0f, table_height)))
{
ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 22.0f * scale);
ImGui::TableSetupColumn("Start", ImGuiTableColumnFlags_WidthStretch, 0.8f);
ImGui::TableSetupColumn("Repeat", ImGuiTableColumnFlags_WidthStretch, 0.8f);
ImGui::TableSetupColumn("Current", ImGuiTableColumnFlags_WidthStretch, 0.8f);
ImGui::TableSetupColumn("Sample", ImGuiTableColumnFlags_WidthStretch, 0.75f);
ImGui::TableSetupColumn("Rate (Hz)", ImGuiTableColumnFlags_WidthStretch, 1.25f);
ImGui::TableSetupColumn("Left vol.", ImGuiTableColumnFlags_WidthStretch, 1.0f);
ImGui::TableSetupColumn("Right vol.", ImGuiTableColumnFlags_WidthStretch, 1.0f);
ImGui::TableSetupColumn("ADSR", ImGuiTableColumnFlags_WidthStretch, 0.95f);
ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthStretch, 0.9f);
ImGui::TableSetupColumn("Ticks", ImGuiTableColumnFlags_WidthStretch, 1.15f);
ImGui::TableSetupColumn("Reverb", ImGuiTableColumnFlags_WidthStretch, 0.8f);
ImGui::TableSetupColumn("Pitch mod.", ImGuiTableColumnFlags_WidthStretch, 0.95f);
#ifdef SPU_ENABLE_VU_METER
ImGui::TableSetupColumn("Peaks L/R", ImGuiTableColumnFlags_WidthFixed, 108.0f * scale);
#endif
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
for (u32 i = 0; i < NUM_VOICES; i++)
{
const Voice& voice = s_state.voices[i];
ImGui::PushStyleColor(ImGuiCol_Text,
voice.IsOn() ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : inactive_color);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%u", i);
ImGui::TableNextColumn();
ImGui::Text("%04X", voice.regs.adpcm_start_address);
ImGui::TableNextColumn();
ImGui::Text("%04X", voice.regs.adpcm_repeat_address);
ImGui::TableNextColumn();
ImGui::Text("%04X", voice.current_address);
ImGui::TableNextColumn();
if (IsVoiceNoiseEnabled(i))
ImGui::TextUnformatted("Noise");
else
ImGui::Text("%u", ZeroExtend32(voice.counter.sample_index.GetValue()));
ImGui::TableNextColumn();
ImGui::Text("%.2f", (static_cast<float>(voice.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f);
ImGui::TableNextColumn();
ImGui::Text("%.1f%%", ApplyVolumeF(100.0f, voice.left_volume.current_level));
ImGui::TableNextColumn();
ImGui::Text("%.1f%%", ApplyVolumeF(100.0f, voice.right_volume.current_level));
ImGui::TableNextColumn();
ImGui::TextUnformatted(adsr_phases[static_cast<u8>(voice.adsr_phase)]);
ImGui::TableNextColumn();
ImGui::Text("%.1f%%", ApplyVolumeF(100.0f, voice.regs.adsr_volume));
ImGui::TableNextColumn();
ImGui::Text("%d", voice.adsr_envelope.counter);
ImGui::TableNextColumn();
ImGui::TextUnformatted(IsVoiceReverbEnabled(i) ? "Yes" : "-");
ImGui::TableNextColumn();
ImGui::TextUnformatted(IsPitchModulationEnabled(i) ? "Yes" : "-");
#ifdef SPU_ENABLE_VU_METER
ImGui::TableNextColumn();
draw_vu_meter(s_state.voice_peaks[i]);
#endif
ImGui::PopStyleColor();
}
ImGui::EndTable();
}
ImGui::PopStyleVar();
if (voices_tab)
ImGui::EndTabItem();
}
const bool reverb_tab = ImGui::BeginTabItem("Reverb Registers");
if (reverb_tab || copy_state)
{
const ReverbRegisters& regs = s_state.reverb_registers;
if (ImGui::BeginTable("reverb_layout", 2, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_BordersInnerV))
{
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::SeparatorText("Coefficients");
if (ImGui::BeginTable("coefficients", 3, table_flags))
{
ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 22.0f * scale);
ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthStretch, 1.0f);
ImGui::TableSetupColumn("Signed / Hex", ImGuiTableColumnFlags_WidthStretch, 1.5f);
ImGui::TableHeadersRow();
const auto coefficient = [&](u32 index, const char* name) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%u", index);
ImGui::TableNextColumn();
ImGui::TextUnformatted(name);
ImGui::TableNextColumn();
ImGui::Text("%d / 0x%04X", static_cast<s32>(static_cast<s16>(regs.rev[index])), regs.rev[index]);
};
static constexpr std::array names = {"IIR_ALPHA", "ACC_COEF_A", "ACC_COEF_B", "ACC_COEF_C",
"ACC_COEF_D", "IIR_COEF", "FB_ALPHA", "FB_X"};
for (u32 i = 0; i < names.size(); i++)
coefficient(i + 2, names[i]);
coefficient(30, "IN_COEF[L]");
coefficient(31, "IN_COEF[R]");
ImGui::EndTable();
}
ImGui::TableSetColumnIndex(1);
ImGui::SeparatorText("Offsets");
if (ImGui::BeginTable("offsets", 3, table_flags))
{
ImGui::TableSetupColumn("Register");
ImGui::TableSetupColumn("Left (hex)");
ImGui::TableSetupColumn("Right (hex)");
ImGui::TableHeadersRow();
for (u32 i = 0; i < 2; i++)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%u FB_SRC_%c", i, 'A' + i);
ImGui::TableNextColumn();
ImGui::Text("%04X", regs.rev[i]);
ImGui::TableNextColumn();
ImGui::TextUnformatted("-");
}
static constexpr std::array names = {"IIR_DEST_A", "ACC_SRC_A", "ACC_SRC_B", "IIR_SRC_A", "IIR_DEST_B",
"ACC_SRC_C", "ACC_SRC_D", "IIR_SRC_B", "MIX_DEST_A", "MIX_DEST_B"};
for (u32 i = 0; i < names.size(); i++)
{
const u32 index = 10 + i * 2;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%u/%u %s", index, index + 1, names[i]);
ImGui::TableNextColumn();
ImGui::Text("%04X", regs.rev[index]);
ImGui::TableNextColumn();
ImGui::Text("%04X", regs.rev[index + 1]);
}
ImGui::EndTable();
}
ImGui::EndTable();
}
if (reverb_tab)
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (copy_state)
ImGui::LogFinish();
}
+122 -51
View File
@@ -13,6 +13,7 @@
#include "common/bitfield.h"
#include "common/log.h"
#include "IconsFontAwesome.h"
#include "imgui.h"
#include <array>
@@ -689,62 +690,132 @@ void Timers::UpdateSysClkEvent()
void Timers::DrawDebugStateWindow(float scale)
{
static constexpr u32 NUM_COLUMNS = 10;
static constexpr std::array<const char*, NUM_COLUMNS> column_names = {
{"#", "Value", "Target", "Sync", "Reset", "IRQ", "IRQRepeat", "IRQToggle", "Clock Source", "Reached"}};
static constexpr std::array<const char*, 4> sync_mode_names = {
{"PauseOnGate", "ResetOnGate", "ResetAndRunOnGate", "FreeRunOnGate"}};
{"Pause on gate", "Reset on gate end", "Reset/run on gate start", "Free run on gate end"}};
static constexpr std::array<std::array<const char*, 4>, 3> clock_source_names = {
{{{"SysClk", "DotClk", "SysClk", "DotClk"}},
{{"SysClk", "HBlank", "SysClk", "HBlank"}},
{{"SysClk", "DotClk", "SysClk/8", "SysClk/8"}}}};
{{{"System", "Dot clock", "System", "Dot clock"}},
{{"System", "HBlank", "System", "HBlank"}},
{{"System", "System", "System / 8", "System / 8"}}}};
ImGui::Columns(NUM_COLUMNS);
ImGui::SetColumnWidth(0, 20.0f * scale);
ImGui::SetColumnWidth(1, 50.0f * scale);
ImGui::SetColumnWidth(2, 50.0f * scale);
ImGui::SetColumnWidth(3, 100.0f * scale);
ImGui::SetColumnWidth(4, 80.0f * scale);
ImGui::SetColumnWidth(5, 80.0f * scale);
ImGui::SetColumnWidth(6, 80.0f * scale);
ImGui::SetColumnWidth(7, 80.0f * scale);
ImGui::SetColumnWidth(8, 80.0f * scale);
ImGui::SetColumnWidth(9, 80.0f * scale);
const ImVec2 content_pos = ImGui::GetCursorScreenPos();
const ImVec2 content_size = ImGui::GetContentRegionAvail();
const float table_height = std::max(1.0f, content_size.y - ImGui::GetFrameHeightWithSpacing());
const float button_width =
ImGui::CalcTextSize(ICON_FA_COPY " Copy State").x + ImGui::GetStyle().FramePadding.x * 2.0f;
// Submit the footer first so clipboard logging can capture the table on the same frame.
ImGui::SetCursorScreenPos(ImVec2(content_pos.x + content_size.x - button_width,
content_pos.y + table_height + ImGui::GetStyle().ItemSpacing.y));
const bool copy_state = ImGui::Button(ICON_FA_COPY " Copy State");
if (copy_state)
ImGui::LogToClipboard();
ImGui::SetCursorScreenPos(content_pos);
for (const char* title : column_names)
const auto row = [](const char* text) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImVec4 color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
color.w *= 0.7f;
ImGui::TextColored(color, "%s", text);
};
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10.0f * scale, ImGui::GetStyle().CellPadding.y));
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImVec4(1.0f, 1.0f, 1.0f, 0.035f));
if (ImGui::BeginTable("timers", NUM_TIMERS + 1,
ImGuiTableFlags_RowBg | ImGuiTableFlags_PadOuterX | ImGuiTableFlags_SizingStretchSame |
ImGuiTableFlags_ScrollY,
ImVec2(0.0f, table_height)))
{
ImGui::TextUnformatted(title);
ImGui::NextColumn();
}
ImGui::TableSetupColumn("");
ImGui::TableSetupColumn("Timer 0");
ImGui::TableSetupColumn("Timer 1");
ImGui::TableSetupColumn("Timer 2");
ImGui::TableHeadersRow();
for (u32 i = 0; i < NUM_TIMERS; i++)
{
const CounterState& cs = s_state.counters[i];
ImGui::PushStyleColor(ImGuiCol_Text,
cs.counting_enabled ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
ImGui::Text("%u", i);
ImGui::NextColumn();
ImGui::Text("%u", cs.counter);
ImGui::NextColumn();
ImGui::Text("%u", cs.target);
ImGui::NextColumn();
ImGui::Text("%s",
cs.mode.sync_enable ? sync_mode_names[static_cast<u8>(cs.mode.sync_mode.GetValue())] : "Disabled");
ImGui::NextColumn();
ImGui::Text("%s", cs.mode.reset_at_target ? "@Target" : "@Overflow");
ImGui::NextColumn();
ImGui::Text("%s%s", cs.mode.irq_at_target ? "Target " : "", cs.mode.irq_on_overflow ? "Overflow" : "");
ImGui::NextColumn();
ImGui::Text("%s", cs.mode.irq_repeat ? "Yes" : "No");
ImGui::NextColumn();
ImGui::Text("%s", cs.mode.irq_pulse_n ? "Yes" : "No");
ImGui::NextColumn();
ImGui::Text("%s%s", clock_source_names[i][cs.mode.clock_source], cs.external_counting_enabled ? " (External)" : "");
ImGui::NextColumn();
ImGui::Text("%s%s", cs.mode.reached_target ? "Target " : "", cs.mode.reached_overflow ? "Overflow" : "");
ImGui::NextColumn();
ImGui::PopStyleColor();
row("Counting");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.counting_enabled ? "Enabled" : "Disabled");
}
row("Value");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::Text("%u", cs.counter);
}
row("Target");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::Text("%u", cs.target);
}
row("Clock");
for (u32 i = 0; i < NUM_TIMERS; i++)
{
const CounterState& cs = s_state.counters[i];
ImGui::TableNextColumn();
ImGui::TextUnformatted(clock_source_names[i][cs.mode.clock_source]);
}
row("Sync");
for (u32 i = 0; i < NUM_TIMERS; i++)
{
const CounterState& cs = s_state.counters[i];
ImGui::TableNextColumn();
if (!cs.mode.sync_enable)
ImGui::TextUnformatted("Disabled");
else if (i == 2)
ImGui::TextUnformatted(cs.counting_enabled ? "Free run" : "Stopped");
else
ImGui::TextWrapped("%s", sync_mode_names[static_cast<u8>(cs.mode.sync_mode.GetValue())]);
}
row("Gate");
for (u32 i = 0; i < NUM_TIMERS; i++)
{
const CounterState& cs = s_state.counters[i];
ImGui::TableNextColumn();
ImGui::TextUnformatted(i == 2 ? "None" : (cs.gate ? "Active" : "Inactive"));
}
row("Reset");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.mode.reset_at_target ? "Target" : "Overflow");
}
row("IRQ source");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.mode.irq_at_target ? (cs.mode.irq_on_overflow ? "Target + overflow" : "Target") :
(cs.mode.irq_on_overflow ? "Overflow" : "None"));
}
row("IRQ repeat");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.mode.irq_repeat ? "Enabled" : "Disabled");
}
row("IRQ mode");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.mode.irq_pulse_n ? "Toggle" : "Pulse");
}
row("IRQ request");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.mode.interrupt_request_n ? "Clear" : "Asserted");
}
row("Reached");
for (const CounterState& cs : s_state.counters)
{
ImGui::TableNextColumn();
ImGui::TextUnformatted(cs.mode.reached_target ? (cs.mode.reached_overflow ? "Target + overflow" : "Target") :
(cs.mode.reached_overflow ? "Overflow" : "None"));
}
ImGui::EndTable();
}
ImGui::Columns(1);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (copy_state)
ImGui::LogFinish();
}