diff --git a/CMakeLists.txt b/CMakeLists.txt index b61d260c..0b18a644 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ set(VIEW_FILES src/view/src/rocprofvis_multi_track_table.cpp src/view/src/rocprofvis_event_search.cpp src/view/src/rocprofvis_summary_view.cpp + src/view/src/rocprofvis_minimap.cpp src/view/src/widgets/rocprofvis_widget.cpp src/view/src/widgets/rocprofvis_debug_window.cpp src/view/src/widgets/rocprofvis_gui_helpers.cpp diff --git a/src/view/src/rocprofvis_appwindow.cpp b/src/view/src/rocprofvis_appwindow.cpp index b12c94c6..6309442f 100644 --- a/src/view/src/rocprofvis_appwindow.cpp +++ b/src/view/src/rocprofvis_appwindow.cpp @@ -618,6 +618,16 @@ AppWindow::RenderViewMenu(Project* project) trace_view_tab->SetHistogramVisibility(settings.show_histogram); } } + if(ImGui::MenuItem("Show Minimap", nullptr, &settings.show_minimap)) + { + for(const auto& tab : m_tab_container->GetTabs()) + { + auto trace_view_tab = + std::dynamic_pointer_cast(tab->m_widget); + if(trace_view_tab) + trace_view_tab->SetMinimapVisibility(settings.show_minimap); + } + } ImGui::MenuItem("Show Summary", nullptr, &settings.show_summary); ImGui::Separator(); diff --git a/src/view/src/rocprofvis_minimap.cpp b/src/view/src/rocprofvis_minimap.cpp new file mode 100644 index 00000000..82a8aad9 --- /dev/null +++ b/src/view/src/rocprofvis_minimap.cpp @@ -0,0 +1,257 @@ +// Copyright Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include "rocprofvis_minimap.h" +#include "imgui.h" +#include "rocprofvis_data_provider.h" +#include "rocprofvis_event_manager.h" +#include "rocprofvis_events.h" +#include "rocprofvis_font_manager.h" +#include "rocprofvis_timeline_view.h" +#include +#include +#include + +namespace RocProfVis +{ +namespace View +{ + +Minimap::Minimap(DataProvider& dp, TimelineView* tv) +: m_data_width(0) +, m_data_height(0) +, m_data_valid(false) +, m_raw_min_value(0.0) +, m_raw_max_value(0.0) +, m_data_provider(dp) +, m_timeline_view(tv) +{ + m_downsampled_data.resize(MINIMAP_SIZE * MINIMAP_SIZE, 0.0); +} +void +Minimap::UpdateData() +{ + const auto& map = m_data_provider.GetMiniMap(); + auto tracks = m_data_provider.GetTrackInfoList(); + if(map.empty() || tracks.empty()) return; + + size_t w = 0; + for(const auto& [id, val] : map) + { + const auto& [vec, valid] = val; + if(valid) w = std::max(w, vec.size()); + } + + if(w == 0) return; + + std::vector data; + data.reserve(tracks.size() * w); + + for(const auto* t : tracks) + { + auto it = map.find(t->id); + const std::vector* vec_ptr = nullptr; + + if(it != map.end()) + { + const auto& [vec, valid] = it->second; + if(valid) vec_ptr = &vec; + } + + if(vec_ptr) + { + data.insert(data.end(), vec_ptr->begin(), vec_ptr->end()); + data.resize(data.size() + (w - vec_ptr->size()), 0.0); + } + else + { + data.resize(data.size() + w, 0.0); + } + } + + SetData(data, w, tracks.size()); +} +void +Minimap::SetData(const std::vector& data, size_t width, size_t height) +{ + if(data.empty() || width == 0 || height == 0) + { + m_data_valid = false; + return; + } + BinData(data, width, height); + NormalizeRawData(); + m_data_valid = true; +} + +void +Minimap::BinData(const std::vector& input, size_t w, size_t h) +{ + if(input.size() < w * h) + { + m_data_valid = false; + return; + } + + m_data_width = std::min(w, MINIMAP_SIZE); + m_data_height = std::min(h, MINIMAP_SIZE); + m_downsampled_data.assign(m_data_width * m_data_height, 0.0); + + double sx = (double) w / m_data_width; + double sy = (double) h / m_data_height; + + for(size_t y = 0; y < m_data_height; ++y) + { + for(size_t x = 0; x < m_data_width; ++x) + { + double sum = 0.0; + int count = 0; + size_t sy0 = (size_t) (y * sy), sy1 = std::min((size_t) ((y + 1) * sy), h); + size_t sx0 = (size_t) (x * sx), sx1 = std::min((size_t) ((x + 1) * sx), w); + + for(size_t iy = sy0; iy < sy1; ++iy) + for(size_t ix = sx0; ix < sx1; ++ix) + { + sum += input[iy * w + ix]; + count++; + } + if(count > 0) m_downsampled_data[y * m_data_width + x] = sum / count; + } + } +} + +void +Minimap::NormalizeRawData() +{ + if(m_downsampled_data.empty()) return; + + m_raw_min_value = std::numeric_limits::max(); + m_raw_max_value = std::numeric_limits::lowest(); + for(double v : m_downsampled_data) + if(v != 0) + { + m_raw_min_value = std::min(m_raw_min_value, v); + m_raw_max_value = std::max(m_raw_max_value, v); + } + + double range = m_raw_max_value - m_raw_min_value; + if(range > 0) + for(double& v : m_downsampled_data) + if(v != 0) v = (v - m_raw_min_value) / range; +} + +ImU32 +Minimap::GetColor(double v) const +{ + if(v == 0) + return SettingsManager::GetInstance() + .GetUserSettings() + .display_settings.use_dark_mode + ? IM_COL32(0, 0, 0, 255) + : IM_COL32(255, 255, 255, 255); + return SettingsManager::GetInstance().GetMinimapColor( + std::sqrt(std::clamp(v, 0.0, 1.0))); +} + +void +Minimap::Render() +{ + if(!m_data_valid || m_downsampled_data.empty()) return; + + SettingsManager& sm = SettingsManager::GetInstance(); + ImGui::PushStyleColor(ImGuiCol_ChildBg, sm.GetColor(Colors::kBgPanel)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); + ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f); + + float pad = 8.0f, legend_w = 60.0f, + title_h = ImGui::GetTextLineHeightWithSpacing() + pad * 2; + ImVec2 avail = ImGui::GetContentRegionAvail(); + + if(ImGui::BeginChild("Minimap", avail, true)) + { + ImDrawList* dl = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetWindowPos(); + + ImGui::SetCursorPos(ImVec2(pad, pad)); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(sm.GetColor(Colors::kTextMain)), + "Mini Map"); + + ImVec2 map_pos(pos.x + pad, pos.y + title_h); + ImVec2 map_size(avail.x - legend_w - pad * 3, avail.y - title_h - pad * 2); + + if(map_size.x > 0 && map_size.y > 0) + { + float bw = map_size.x / m_data_width, bh = map_size.y / m_data_height; + for(size_t y = 0; y < m_data_height; ++y) + for(size_t x = 0; x < m_data_width; ++x) + if(double v = m_downsampled_data[y * m_data_width + x]) + dl->AddRectFilled( + ImVec2(map_pos.x + x * bw, map_pos.y + y * bh), + ImVec2(map_pos.x + x * bw + bw, map_pos.y + y * bh + bh), + GetColor(v)); + + if(m_timeline_view) + { + ViewCoords vc = m_timeline_view->GetViewCoords(); + double t0 = m_data_provider.GetStartTime(); + double tr = m_data_provider.GetEndTime() - t0; + + ImGui::SetCursorScreenPos(map_pos); + if(ImGui::InvisibleButton("Hit", map_size) && tr > 0) + { + ImVec2 m = ImGui::GetMousePos(); + double bin_t = tr / m_data_width; + size_t idx = std::clamp( + (size_t) ((m.x - map_pos.x) / map_size.x * m_data_width), + (size_t) 0, m_data_width - 1); + double v_min = t0 + idx * bin_t; + float th = vc.y + m_timeline_view->GetGraphSize().y; + EventManager::GetInstance()->AddEvent( + std::make_shared( + v_min, v_min + bin_t, ((m.y - map_pos.y) / map_size.y) * th, + false)); + } + } + } + ImGui::SetCursorPos(ImVec2(avail.x - legend_w - pad, title_h)); + RenderLegend(legend_w, avail.y - title_h - pad * 2); + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); +} + +void +Minimap::RenderLegend(float w, float h) +{ + if(h <= 0 || w <= 0) return; + SettingsManager& sm = SettingsManager::GetInstance(); + ImDrawList* dl = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + + float bar_w = 15.0f, txt_h = ImGui::CalcTextSize("1.0").y, gap = 4.0f; + float bar_h = h - (txt_h * 2) - (gap * 2), bar_x = pos.x + (w - bar_w) * 0.5f, + bar_y = pos.y + txt_h + gap; + + for(int i = 0; i < 100; ++i) + { + float t = i / 100.0f, y0 = bar_y + bar_h - (i * (bar_h / 100.0f)), + y1 = y0 - (bar_h / 100.0f); + dl->AddRectFilled(ImVec2(bar_x, y1), ImVec2(bar_x + bar_w, y0), + sm.GetMinimapColor(std::sqrt(t))); + } + dl->AddRect(ImVec2(bar_x, bar_y), ImVec2(bar_x + bar_w, bar_y + bar_h), + sm.GetColor(Colors::kBorderColor)); + + ImFont* font = sm.GetFontManager().GetFont(FontType::kSmall); + dl->AddText(font, font->FontSize, + ImVec2(bar_x + (bar_w - ImGui::CalcTextSize("1.0").x) * 0.5f, pos.y), + sm.GetColor(Colors::kTextMain), "1.0"); + dl->AddText(font, font->FontSize, + ImVec2(bar_x + (bar_w - ImGui::CalcTextSize("0.0").x) * 0.5f, + bar_y + bar_h + gap), + sm.GetColor(Colors::kTextMain), "0.0"); +} + +} // namespace View +} // namespace RocProfVis \ No newline at end of file diff --git a/src/view/src/rocprofvis_minimap.h b/src/view/src/rocprofvis_minimap.h new file mode 100644 index 00000000..924c848e --- /dev/null +++ b/src/view/src/rocprofvis_minimap.h @@ -0,0 +1,49 @@ +// Copyright Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "rocprofvis_settings_manager.h" +#include "widgets/rocprofvis_widget.h" +#include + +namespace RocProfVis +{ +namespace View +{ + +class TimelineView; +class DataProvider; + +class Minimap : public RocWidget +{ +public: + Minimap(DataProvider& data_provider, TimelineView* timeline_view); + ~Minimap() override = default; + void UpdateData(); + void Render() override; + + void SetData(const std::vector& data, size_t width, size_t height); + +private: + static constexpr size_t MINIMAP_SIZE = 500; + + void BinData(const std::vector& input_data, size_t input_width, + size_t input_height); + void NormalizeRawData(); + ImU32 GetColor(double normalized_value) const; + void RenderLegend(float width, float height); + + std::vector m_downsampled_data; + size_t m_data_width; + size_t m_data_height; + bool m_data_valid; + double m_raw_min_value; + double m_raw_max_value; + + DataProvider& m_data_provider; + TimelineView* m_timeline_view; +}; + +} // namespace View +} // namespace RocProfVis diff --git a/src/view/src/rocprofvis_settings_manager.cpp b/src/view/src/rocprofvis_settings_manager.cpp index aa6085af..058ecea7 100644 --- a/src/view/src/rocprofvis_settings_manager.cpp +++ b/src/view/src/rocprofvis_settings_manager.cpp @@ -130,6 +130,32 @@ const std::vector FLAME_COLORS = { IM_COL32(0, 204, 102, 204), IM_COL32(230, 159, 0, 204), IM_COL32(153, 153, 255, 204), IM_COL32(255, 153, 51, 204) }; + +const std::vector MINIMAP_COLOR_RAMP_DARK = { + IM_COL32(38, 40, 44, 255), + IM_COL32(60, 80, 120, 255), + IM_COL32(30, 0, 40, 255), + IM_COL32(60, 0, 80, 255), + IM_COL32(100, 0, 120, 255), + IM_COL32(140, 20, 40, 255), + IM_COL32(200, 50, 0, 255), + IM_COL32(240, 120, 0, 255), + IM_COL32(255, 180, 60, 255), + IM_COL32(255, 240, 180, 255) +}; + +const std::vector MINIMAP_COLOR_RAMP_LIGHT = { + IM_COL32(250, 245, 240, 255), + IM_COL32(180, 200, 220, 255), + IM_COL32(200, 180, 200, 255), + IM_COL32(150, 100, 180, 255), + IM_COL32(180, 60, 140, 255), + IM_COL32(220, 80, 80, 255), + IM_COL32(240, 120, 40, 255), + IM_COL32(255, 160, 60, 255), + IM_COL32(255, 200, 120, 255), + IM_COL32(255, 245, 200, 255) +}; inline constexpr const char* SETTINGS_FILE_NAME = "settings_application.json"; inline constexpr size_t RECENT_FILES_LIMIT = 5; inline constexpr float EVENT_LEVEL_HEIGHT = 40.0f; @@ -416,12 +442,48 @@ SettingsManager::GetColorWheel() return FLAME_COLORS; } +ImU32 +SettingsManager::GetMinimapColor(double normalized_value) const +{ + const std::vector& color_ramp = + m_usersettings.display_settings.use_dark_mode + ? MINIMAP_COLOR_RAMP_DARK + : MINIMAP_COLOR_RAMP_LIGHT; + + if(color_ramp.empty()) + { + return IM_COL32(128, 128, 128, 255); + } + + float t = std::clamp(static_cast(normalized_value), 0.0f, 1.0f); + float index_float = t * static_cast(color_ramp.size() - 1); + size_t index_low = static_cast(index_float); + size_t index_high = std::min(index_low + 1, color_ramp.size() - 1); + float fraction = index_float - static_cast(index_low); + + ImU32 color_low = color_ramp[index_low]; + ImU32 color_high = color_ramp[index_high]; + + float r_low = static_cast((color_low >> IM_COL32_R_SHIFT) & 0xFF); + float g_low = static_cast((color_low >> IM_COL32_G_SHIFT) & 0xFF); + float b_low = static_cast((color_low >> IM_COL32_B_SHIFT) & 0xFF); + float r_high = static_cast((color_high >> IM_COL32_R_SHIFT) & 0xFF); + float g_high = static_cast((color_high >> IM_COL32_G_SHIFT) & 0xFF); + float b_high = static_cast((color_high >> IM_COL32_B_SHIFT) & 0xFF); + + float r = r_low + (r_high - r_low) * fraction; + float g = g_low + (g_high - g_low) * fraction; + float b = b_low + (b_high - b_low) * fraction; + + return IM_COL32(static_cast(r), static_cast(g), static_cast(b), 255); +} + SettingsManager::SettingsManager() : m_color_store(nullptr) , m_usersettings_default( { DisplaySettings{ false, true, 6 }, UnitSettings{ TimeFormat::kTimecode } }) , m_usersettings(m_usersettings_default) -, m_appwindowsettings({ AppWindowSettings{ true, true, true, true, false } }) +, m_appwindowsettings({ AppWindowSettings{ true, true, true, true, false, true } }) , m_display_dpi(1.5f) , m_json_path(GetStandardConfigPath()) {} diff --git a/src/view/src/rocprofvis_settings_manager.h b/src/view/src/rocprofvis_settings_manager.h index ff7dbe0e..2f8ce10d 100644 --- a/src/view/src/rocprofvis_settings_manager.h +++ b/src/view/src/rocprofvis_settings_manager.h @@ -51,6 +51,7 @@ typedef struct AppWindowSettings bool show_sidebar; bool show_histogram; bool show_summary; + bool show_minimap; } AppWindowSettings; enum class Colors @@ -145,6 +146,7 @@ class SettingsManager // Styling ImU32 GetColor(Colors color) const; const std::vector& GetColorWheel(); + ImU32 GetMinimapColor(double normalized_value) const; /** * Returns the default ImGui style. */ diff --git a/src/view/src/rocprofvis_timeline_view.cpp b/src/view/src/rocprofvis_timeline_view.cpp index 079631c5..9cb610fa 100644 --- a/src/view/src/rocprofvis_timeline_view.cpp +++ b/src/view/src/rocprofvis_timeline_view.cpp @@ -146,6 +146,7 @@ TimelineView::TimelineView(DataProvider& dp, FlameTrackItem::CalculateMaxEventLabelWidth(); } + void TimelineView::RenderInteractiveUI() { @@ -1513,6 +1514,8 @@ TimelineView::RenderTraceView() RenderScrubber(screen_pos); + + if(!m_resize_activity && !m_stop_user_interaction) { // Funtion enables user interactions to be captured diff --git a/src/view/src/rocprofvis_trace_view.cpp b/src/view/src/rocprofvis_trace_view.cpp index 5456b6a3..b01c0c0c 100644 --- a/src/view/src/rocprofvis_trace_view.cpp +++ b/src/view/src/rocprofvis_trace_view.cpp @@ -9,6 +9,7 @@ #include "rocprofvis_appwindow.h" #include "rocprofvis_event_manager.h" #include "rocprofvis_event_search.h" +#include "rocprofvis_minimap.h" #include "rocprofvis_settings_manager.h" #include "rocprofvis_sidebar.h" #include "rocprofvis_summary_view.h" @@ -41,6 +42,7 @@ TraceView::TraceView() , m_settings_manager(SettingsManager::GetInstance()) , m_event_search(nullptr) , m_summary_view(nullptr) +, m_minimap(nullptr) { m_data_provider.SetTrackDataReadyCallback( [](uint64_t track_id, const std::string& trace_path, const data_req_info_t& req) { @@ -194,6 +196,11 @@ TraceView::Update() { m_summary_view->Update(); } + + if(m_minimap) + { + m_minimap->UpdateData(); + } } void @@ -224,6 +231,24 @@ TraceView::CreateView() m_analysis_item->m_visible = m_settings_manager.GetAppWindowSettings().show_details_panel; + // Create minimap widget + m_minimap = std::make_shared(m_data_provider, m_timeline_view.get()); + auto minimap_widget = std::make_shared( + [this]() { + if(m_minimap) + { + m_minimap->Render(); + } + }); + m_minimap_item = LayoutItem::CreateFromWidget(minimap_widget, 200, 200); + m_minimap_item->m_visible = m_settings_manager.GetAppWindowSettings().show_minimap; + + // Split sidebar vertically: sidebar (top) | minimap (bottom) + auto sidebar_vsplit = std::make_shared(m_sidebar_item, m_minimap_item); + sidebar_vsplit->SetSplit(0.75f); + sidebar_vsplit->SetMinBottomHeight(150); + auto sidebar_with_minimap = LayoutItem::CreateFromWidget(sidebar_vsplit); + LayoutItem m_histogram_item(0, 80); m_histogram_item.m_item = m_histogram_widget; m_histogram_item.m_visible = m_settings_manager.GetAppWindowSettings().show_histogram; @@ -244,7 +269,7 @@ TraceView::CreateView() trace_area->m_item = m_vertical_split_container; m_horizontal_split_container = - std::make_shared(m_sidebar_item, trace_area); + std::make_shared(sidebar_with_minimap, trace_area); m_horizontal_split_container->SetSplit(0.2f); m_horizontal_split_container->SetMinRightWidth(400); } @@ -256,6 +281,7 @@ TraceView::DestroyView() m_sidebar_item->m_item = nullptr; m_horizontal_split_container = nullptr; m_analysis_item->m_item = nullptr; + m_minimap = nullptr; m_view_created = false; } @@ -548,6 +574,15 @@ TraceView::SetHistogramVisibility(bool visibility) } } +void +TraceView::SetMinimapVisibility(bool visibility) +{ + if(m_minimap_item) + { + m_minimap_item->m_visible = visibility; + } +} + void TraceView::RenderToolbar() { diff --git a/src/view/src/rocprofvis_trace_view.h b/src/view/src/rocprofvis_trace_view.h index 07414d72..9f9117bb 100644 --- a/src/view/src/rocprofvis_trace_view.h +++ b/src/view/src/rocprofvis_trace_view.h @@ -26,6 +26,7 @@ class TraceView; class SettingsManager; class EventSearch; class SummaryView; +class Minimap; class SystemTraceProjectSettings : public ProjectSetting { @@ -68,6 +69,7 @@ class TraceView : public RootView void SetAnalysisViewVisibility(bool visibility); void SetSidebarViewVisibility(bool visibility); void SetHistogramVisibility(bool visibility); + void SetMinimapVisibility(bool visibility); private: void HandleHotKeys(); @@ -86,9 +88,11 @@ class TraceView : public RootView std::shared_ptr m_timeline_container; std::shared_ptr m_event_search; std::shared_ptr m_summary_view; + std::shared_ptr m_minimap; LayoutItem::Ptr m_sidebar_item; LayoutItem::Ptr m_analysis_item; + LayoutItem::Ptr m_minimap_item; DataProvider m_data_provider; bool m_view_created;