diff --git a/dep/imgui/include/IconsEmoji.h b/dep/imgui/include/IconsEmoji.h index 763f0402d..cbc3bf996 100644 --- a/dep/imgui/include/IconsEmoji.h +++ b/dep/imgui/include/IconsEmoji.h @@ -6,6 +6,7 @@ // To generate: "".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" diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index f891c2678..2b97dd3cd 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -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> 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& 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 CDROM::RemoveMedia(bool for_disc_swap) INFO_LOG("Removing CD..."); std::unique_ptr 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(&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(System::GetTicksPerSecond()) / static_cast(SINGLE_SPEED_SECTORS_PER_SECOND)) / + static_cast(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(s_state.drive_state)], drive_speed); + else + ImGui::TextUnformatted(s_drive_state_names[static_cast(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(&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, ¤t_file_start_lba, ¤t_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: "); + 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: "); + ImGui::TextUnformatted("Non-data track"); } + label("File map"); + ImGui::Text("%u entries", static_cast(s_state.file_map.size())); + ImGui::EndTable(); + } + } + else + { + ImGui::TextUnformatted("No disc inserted"); + } - ImGui::SameLine(); - ImGui::Text("[%u files on disc]", static_cast(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(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(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(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(System::GetTicksPerSecond()) / - static_cast(SINGLE_SPEED_SECTORS_PER_SECOND)) / - static_cast(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(); } diff --git a/src/core/dma.cpp b/src/core/dma.cpp index a9d3369b4..fc6149d0a 100644 --- a/src/core/dma.cpp +++ b/src/core/dma.cpp @@ -26,6 +26,9 @@ #include "fmt/format.h" +#include "IconsEmoji.h" +#include "IconsFontAwesome.h" + #include #include #include @@ -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(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(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(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(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(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(i)) ? active : inactive, - s_state.DPCR.GetMasterEnable(static_cast(i)) ? "Enabled" : "Disabled"); - ImGui::NextColumn(); - ImGui::TextColored(s_state.DPCR.GetMasterEnable(static_cast(i)) ? active : inactive, "%u", - s_state.DPCR.GetPriority(static_cast(i))); - ImGui::NextColumn(); - ImGui::TextColored(s_state.DICR.GetIRQEnabled(static_cast(i)) ? active : inactive, - s_state.DICR.GetIRQEnabled(static_cast(i)) ? "Enabled" : "Disabled"); - ImGui::NextColumn(); - ImGui::TextColored(s_state.DICR.GetIRQFlag(static_cast(i)) ? active : inactive, - s_state.DICR.GetIRQFlag(static_cast(i)) ? "IRQ" : ""); - ImGui::NextColumn(); - } - - ImGui::Columns(1); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + if (copy_state) + ImGui::LogFinish(); } // Instantiate channel functions. diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index dea1e9b87..f924b124a 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -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 state_strings = { - {"Idle", "Reading VRAM", "Writing VRAM", "Drawing Polyline"}}; + static constexpr const CRTCState& cs = s_locals.crtc_state; + static constexpr std::array 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(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(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 transparency_modes = {{"B/2 + F/2", "B + F", "B - F", "B + F/4"}}; + ImGui::TextUnformatted(transparency_modes[static_cast(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 texture_modes = { + {"4-bit indexed", "8-bit indexed", "16-bit direct", "Reserved (16-bit)"}}; + ImGui::TextUnformatted(texture_modes[static_cast(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(rect.x) / cs.horizontal_total, 0.0f, 1.0f), + origin.y + size.y * std::clamp(static_cast(rect.y) / cs.vertical_total, 0.0f, 1.0f)); + const ImVec2 end(origin.x + size.x * std::clamp(static_cast(rect.z) / cs.horizontal_total, 0.0f, 1.0f), + origin.y + size.y * std::clamp(static_cast(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() diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index a013b5701..aed80aa55 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -112,12 +112,12 @@ static void UpdateInputOverlay(void* buffer); static constexpr size_t NUM_DEBUG_WINDOWS = 7; static constexpr const std::array s_debug_window_info = {{ {"Freecam", "Free Camera", ":icons/applications-system.png", >E::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 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; diff --git a/src/core/imgui_overlays.h b/src/core/imgui_overlays.h index 0f36bc382..7e3e2e523 100644 --- a/src/core/imgui_overlays.h +++ b/src/core/imgui_overlays.h @@ -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(); diff --git a/src/core/mdec.cpp b/src/core/mdec.cpp index a22ebf42a..5887e900d 100644 --- a/src/core/mdec.cpp +++ b/src/core/mdec.cpp @@ -16,9 +16,11 @@ #include "common/gsvector.h" #include "common/log.h" +#include "IconsFontAwesome.h" #include "imgui.h" #include +#include #include LOG_CHANNEL(MDEC); @@ -1254,37 +1256,132 @@ void MDEC::SetScaleMatrix(const u16* values) void MDEC::DrawDebugStateWindow(float scale) { - static constexpr std::array state_names = { - {"None", "Decoding Macroblock", "Writing Macroblock", "SetIqTab", "SetScale"}}; + static constexpr std::array state_names = {{"Idle", "Decoding macroblock", "Writing macroblock", + "Loading quantization table", "Loading scale table", + "No command"}}; static constexpr std::array output_depths = {{"4-bit", "8-bit", "24-bit", "15-bit"}}; - static constexpr std::array block_names = {{"Crblk", "Cbblk", "Y1", "Y2", "Y3", "Y4", "Output"}}; + static constexpr std::array 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(s_state.fast_idct_blocks) * 100.0 / - static_cast(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(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(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(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(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(s_state.fast_idct_blocks) * 100.0 / + static_cast(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(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(); } diff --git a/src/core/spu.cpp b/src/core/spu.cpp index d21132d5b..ecc802739 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -25,9 +25,11 @@ #include "common/path.h" #include "IconsEmoji.h" +#include "IconsFontAwesome.h" #include "fmt/format.h" #include "imgui.h" +#include #include 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 transfer_modes = { - {"Transfer Stopped", "Manual Write", "DMA Write", "DMA Read"}}; - const std::array 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(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(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(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(s_state.reverb_registers.FB_SRC_A)); - ImGui::NextColumn(); - ImGui::Text("[1] FB_SRC_B: 0x%04X", static_cast(s_state.reverb_registers.FB_SRC_B)); - ImGui::NextColumn(); - ImGui::Text("[2] IIR_ALPHA: %d", static_cast(s_state.reverb_registers.IIR_ALPHA)); - ImGui::NextColumn(); - ImGui::Text("[3] ACC_COEF_A: %d", static_cast(s_state.reverb_registers.ACC_COEF_A)); - ImGui::NextColumn(); - ImGui::Text("[4] ACC_COEF_B: %d", static_cast(s_state.reverb_registers.ACC_COEF_B)); - ImGui::NextColumn(); - ImGui::Text("[5] ACC_COEF_C: %d", static_cast(s_state.reverb_registers.ACC_COEF_C)); - ImGui::NextColumn(); - ImGui::Text("[6] ACC_COEF_D: %d", static_cast(s_state.reverb_registers.ACC_COEF_D)); - ImGui::NextColumn(); - ImGui::Text("[7] IIR_COEF: %d", static_cast(s_state.reverb_registers.IIR_COEF)); - ImGui::NextColumn(); - ImGui::Text("[8] FB_ALPHA: %d", static_cast(s_state.reverb_registers.FB_ALPHA)); - ImGui::NextColumn(); - ImGui::Text("[9] FB_X: %d", static_cast(s_state.reverb_registers.FB_X)); - ImGui::NextColumn(); - ImGui::Text("[10] IIR_DEST_A[L]: 0x%04X", static_cast(s_state.reverb_registers.IIR_DEST_A[0])); - ImGui::NextColumn(); - ImGui::Text("[11] IIR_DEST_A[R]: 0x%04X", static_cast(s_state.reverb_registers.IIR_DEST_A[1])); - ImGui::NextColumn(); - ImGui::Text("[12] ACC_SRC_A[L]: 0x%04X", static_cast(s_state.reverb_registers.ACC_SRC_A[0])); - ImGui::NextColumn(); - ImGui::Text("[13] ACC_SRC_A[R]: 0x%04X", static_cast(s_state.reverb_registers.ACC_SRC_A[1])); - ImGui::NextColumn(); - ImGui::Text("[14] ACC_SRC_B[L]: 0x%04X", static_cast(s_state.reverb_registers.ACC_SRC_B[0])); - ImGui::NextColumn(); - ImGui::Text("[15] ACC_SRC_B[R]: 0x%04X", static_cast(s_state.reverb_registers.ACC_SRC_B[1])); - ImGui::NextColumn(); - ImGui::Text("[16] IIR_SRC_A[L]: 0x%04X", static_cast(s_state.reverb_registers.IIR_SRC_A[0])); - ImGui::NextColumn(); - ImGui::Text("[17] IIR_SRC_A[R]: 0x%04X", static_cast(s_state.reverb_registers.IIR_SRC_A[1])); - ImGui::NextColumn(); - ImGui::Text("[18] IIR_DEST_B[L]: 0x%04X", static_cast(s_state.reverb_registers.IIR_DEST_B[0])); - ImGui::NextColumn(); - ImGui::Text("[19] IIR_DEST_B[R]: 0x%04X", static_cast(s_state.reverb_registers.IIR_DEST_B[1])); - ImGui::NextColumn(); - ImGui::Text("[20] ACC_SRC_C[L]: 0x%04X", static_cast(s_state.reverb_registers.ACC_SRC_C[0])); - ImGui::NextColumn(); - ImGui::Text("[21] ACC_SRC_C[R]: 0x%04X", static_cast(s_state.reverb_registers.ACC_SRC_C[1])); - ImGui::NextColumn(); - ImGui::Text("[22] ACC_SRC_D[L]: 0x%04X", static_cast(s_state.reverb_registers.ACC_SRC_D[0])); - ImGui::NextColumn(); - ImGui::Text("[23] ACC_SRC_D[R]: 0x%04X", static_cast(s_state.reverb_registers.ACC_SRC_D[1])); - ImGui::NextColumn(); - ImGui::Text("[24] IIR_SRC_B[L]: 0x%04X", static_cast(s_state.reverb_registers.IIR_SRC_B[0])); - ImGui::NextColumn(); - ImGui::Text("[25] IIR_SRC_B[R]: 0x%04X", static_cast(s_state.reverb_registers.IIR_SRC_B[1])); - ImGui::NextColumn(); - ImGui::Text("[26] MIX_DEST_A[L]: 0x%04X", static_cast(s_state.reverb_registers.MIX_DEST_A[0])); - ImGui::NextColumn(); - ImGui::Text("[27] MIX_DEST_A[R]: 0x%04X", static_cast(s_state.reverb_registers.MIX_DEST_A[1])); - ImGui::NextColumn(); - ImGui::Text("[28] MIX_DEST_B[L]: 0x%04X", static_cast(s_state.reverb_registers.MIX_DEST_B[0])); - ImGui::NextColumn(); - ImGui::Text("[29] MIX_DEST_B[R]: 0x%04X", static_cast(s_state.reverb_registers.MIX_DEST_B[1])); - ImGui::NextColumn(); - ImGui::Text("[30] IN_COEF[L]: %d", static_cast(s_state.reverb_registers.IN_COEF[0])); - ImGui::NextColumn(); - ImGui::Text("[31] IN_COEF[R]: %d", static_cast(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(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(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(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(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(static_cast(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(); } diff --git a/src/core/timers.cpp b/src/core/timers.cpp index 15206dbda..4fc7467ba 100644 --- a/src/core/timers.cpp +++ b/src/core/timers.cpp @@ -13,6 +13,7 @@ #include "common/bitfield.h" #include "common/log.h" +#include "IconsFontAwesome.h" #include "imgui.h" #include @@ -689,62 +690,132 @@ void Timers::UpdateSysClkEvent() void Timers::DrawDebugStateWindow(float scale) { - static constexpr u32 NUM_COLUMNS = 10; - static constexpr std::array column_names = { - {"#", "Value", "Target", "Sync", "Reset", "IRQ", "IRQRepeat", "IRQToggle", "Clock Source", "Reached"}}; static constexpr std::array 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, 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(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(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(); }