diff --git a/BUILD.md b/BUILD.md index 9eae65f..1a5c4c5 100644 --- a/BUILD.md +++ b/BUILD.md @@ -36,8 +36,9 @@ This can be installed once Python is installed, as follows: Run the python pre_build.py script in the build folder from a command prompt. If no command line options are provided, the defaults will be used (Qt 6.7.0 and Visual Studio 2022) Some useful options of the pre_build.py script: -* --vs : generate the solution files for a specific Visual Studio version. For example, to target Visual Studio 2017, add --vs 2017 to the command. -* --qt : full path to the folder from where you would like the Qt binaries to be retrieved. By default, CMake would try to auto-detect Qt on the system. +* --vs \: generate the solution files for a specific Visual Studio version. For example, to target Visual Studio 2017, add --vs 2017 to the command. +* --qt \: specify the version of QT to be used with the script (default: 6.7.0). +* --qt-root \: full path to the folder from where you would like the Qt binaries to be retrieved. By default, CMake would try to auto-detect Qt on the system. Once the script has finished, in the case of Visual Studio 2019, a sub-folder called 'vs2019' will be created containing the necessary build files. Go into the 'vs2019' folder (build/win/vs2019) and double click on the RMV.sln file and build the 64-bit Debug and Release builds. diff --git a/source/frontend/models/compare/memory_leak_finder_model.cpp b/source/frontend/models/compare/memory_leak_finder_model.cpp index 9e620dc..7d93682 100644 --- a/source/frontend/models/compare/memory_leak_finder_model.cpp +++ b/source/frontend/models/compare/memory_leak_finder_model.cpp @@ -250,16 +250,31 @@ namespace rmv UpdateLabels(); } + void MemoryLeakFinderModel::FilterByMipLevelChanged(int min_value, int max_value) + { + const uint64_t scaled_min = rmv_util::CalculateThresholdFromStepValue(min_value, rmv::kMipSliderRange - 1); + const uint64_t scaled_max = rmv_util::CalculateThresholdFromStepValue(max_value, rmv::kMipSliderRange - 1); + + proxy_model_->SetMipLevelFilter(scaled_min, scaled_max); + proxy_model_->invalidate(); + + UpdateLabels(); + } + void MemoryLeakFinderModel::UpdatePreferredHeapList(const QString& preferred_heap_filter) { proxy_model_->SetPreferredHeapFilter(preferred_heap_filter); proxy_model_->invalidate(); + + UpdateLabels(); } void MemoryLeakFinderModel::UpdateResourceUsageList(const QString& resource_usage_filter) { proxy_model_->SetResourceUsageFilter(resource_usage_filter); proxy_model_->invalidate(); + + UpdateLabels(); } MemoryLeakFinderProxyModel* MemoryLeakFinderModel::GetResourceProxyModel() const diff --git a/source/frontend/models/compare/memory_leak_finder_model.h b/source/frontend/models/compare/memory_leak_finder_model.h index 17245ee..cb5eb39 100644 --- a/source/frontend/models/compare/memory_leak_finder_model.h +++ b/source/frontend/models/compare/memory_leak_finder_model.h @@ -84,6 +84,12 @@ namespace rmv /// @param [in] max_value Maximum value of slider span. void FilterBySizeChanged(int min_value, int max_value); + /// @brief Handle what happens when user changes the 'filter by mip level' slider. + /// + /// @param [in] min_value Minimum value of slider span. + /// @param [in] max_value Maximum value of slider span. + void FilterByMipLevelChanged(int min_value, int max_value); + /// @brief Update the list of heaps selected. This is set up from the preferred heap combo box. /// /// @param [in] preferred_heap_filter The regular expression string of selected heaps. diff --git a/source/frontend/models/proxy_models/resource_proxy_model.cpp b/source/frontend/models/proxy_models/resource_proxy_model.cpp index b23cc6a..d6f1ed0 100644 --- a/source/frontend/models/proxy_models/resource_proxy_model.cpp +++ b/source/frontend/models/proxy_models/resource_proxy_model.cpp @@ -34,13 +34,16 @@ namespace rmv setSourceModel(model); SetFilterKeyColumns({kResourceColumnName, kResourceColumnVirtualAddress, + kResourceColumnUsage, + kResourceColumnDimension, + kResourceColumnMipLevel, + kResourceColumnFormat, kResourceColumnSize, kResourceColumnMappedInvisible, kResourceColumnMappedLocal, kResourceColumnMappedHost, kResourceColumnMappedNone, - kResourceColumnPreferredHeap, - kResourceColumnUsage}); + kResourceColumnPreferredHeap}); view->setModel(this); return model; @@ -63,6 +66,11 @@ namespace rmv return false; } + if (FilterMipLevelSlider(source_row, kResourceColumnMipLevel, source_parent) == false) + { + return false; + } + // Apply the range-based searching to the virtual address and resource size. // Only fail if the search string is outside the address range. bool found_range = false; @@ -161,6 +169,48 @@ namespace rmv } return left_data < right_data; } + else if ((left.column() == kResourceColumnDimension && right.column() == kResourceColumnDimension)) + { + const QList left_data = left.data(Qt::UserRole).toList(); + const QList right_data = right.data(Qt::UserRole).toList(); + if (left_data == right_data) + { + return SortIdentical(left, right); + } + + qlonglong left_pixels = 1; + qlonglong right_pixels = 1; + for (const QVariant& data : left_data) + { + left_pixels *= data.toInt(); + } + for (const QVariant& data : right_data) + { + right_pixels *= data.toInt(); + } + + return left_pixels < right_pixels; + } + else if ((left.column() == kResourceColumnMipLevel && right.column() == kResourceColumnMipLevel)) + { + const int left_data = left.data(Qt::UserRole).toInt(); + const int right_data = right.data(Qt::UserRole).toInt(); + if (left_data == right_data) + { + return SortIdentical(left, right); + } + return left_data < right_data; + } + else if ((left.column() == kResourceColumnFormat && right.column() == kResourceColumnFormat)) + { + const QString left_data = left.data(Qt::UserRole).toString(); + const QString right_data = right.data(Qt::UserRole).toString(); + if (left_data == right_data) + { + return SortIdentical(left, right); + } + return left_data < right_data; + } else if ((left.column() == kResourceColumnSize && right.column() == kResourceColumnSize)) { const qulonglong left_data = left.data(Qt::UserRole).toULongLong(); diff --git a/source/frontend/models/proxy_models/table_proxy_model.cpp b/source/frontend/models/proxy_models/table_proxy_model.cpp index 1ac012a..743b10d 100644 --- a/source/frontend/models/proxy_models/table_proxy_model.cpp +++ b/source/frontend/models/proxy_models/table_proxy_model.cpp @@ -13,6 +13,8 @@ namespace rmv : QSortFilterProxyModel(parent) , min_size_(0) , max_size_(UINT64_MAX) + , min_mip_level_(1) + , max_mip_level_(UINT64_MAX) { } @@ -42,6 +44,12 @@ namespace rmv max_size_ = max; } + void TableProxyModel::SetMipLevelFilter(uint64_t min, uint64_t max) + { + min_mip_level_ = min; + max_mip_level_ = max; + } + uint64_t TableProxyModel::GetIndexValue(const QModelIndex& index) const { const QString data = sourceModel()->data(index).toString(); @@ -62,6 +70,19 @@ namespace rmv return out; } + QString TableProxyModel::GetDataAsStr(int row, int column) + { + QString out("null"); + + const QModelIndex model_index = index(row, column, QModelIndex()); + + if (model_index.isValid() == true) + { + out = data(model_index, Qt::DisplayRole).toString(); + } + return out; + } + QModelIndex TableProxyModel::FindModelIndex(qulonglong lookup, int column) const { QModelIndex out_model_index; @@ -95,6 +116,14 @@ namespace rmv return !(size < min_size_ || size > max_size_); } + bool TableProxyModel::FilterMipLevelSlider(int row, int column, const QModelIndex& source_parent) const + { + const QModelIndex& mip_filter_index = sourceModel()->index(row, column, source_parent); + const uint32_t mip_level = mip_filter_index.data(Qt::UserRole).toUInt(); + + return mip_level == 0 || !(mip_level < min_mip_level_ || mip_level > max_mip_level_); + } + bool TableProxyModel::FilterSearchString(int row, const QModelIndex& source_parent) const { if (column_filters_.empty() == false && search_filter_.isEmpty() == false) diff --git a/source/frontend/models/proxy_models/table_proxy_model.h b/source/frontend/models/proxy_models/table_proxy_model.h index 872a7f8..2b7b6b5 100644 --- a/source/frontend/models/proxy_models/table_proxy_model.h +++ b/source/frontend/models/proxy_models/table_proxy_model.h @@ -44,6 +44,12 @@ namespace rmv /// @param [in] max The maximum size. void SetSizeFilter(uint64_t min, uint64_t max); + /// @brief Specify range to use as mip level filter. + /// + /// @param [in] min The minimum mip level. + /// @param [in] max The maximum mip level. + void SetMipLevelFilter(uint64_t min, uint64_t max); + /// @brief Get content from proxy model. /// /// @param [in] row The row where the data is located. @@ -52,6 +58,14 @@ namespace rmv /// @return The contents at row, column. qulonglong GetData(int row, int column); + /// @brief Get content from proxy model. + /// + /// @param [in] row The row where the data is located. + /// @param [in] column The column where the data is located. + /// + /// @return The string contents at row, column. + QString GetDataAsStr(int row, int column); + /// @brief Find a model index corresponding to the passed in data. /// /// @param [in] lookup The value to find. @@ -90,6 +104,15 @@ namespace rmv /// @return true if the table item at row,column is to be shown, false if not. bool FilterSizeSlider(int row, int column, const QModelIndex& source_parent) const; + /// @brief Filter the mip level slider. + /// + /// @param [in] row The row to apply the mip level filter to. + /// @param [in] column The column to apply the mip level filter to. + /// @param [in] source_parent The parent model index in the source model. + /// + /// @return true if the table item at row,column is to be shown, false if not. + bool FilterMipLevelSlider(int row, int column, const QModelIndex& source_parent) const; + /// @brief Filter the search string. /// /// @param [in] row The row to apply the size filter to. @@ -109,6 +132,8 @@ namespace rmv QString search_filter_; ///< The current search string. uint64_t min_size_; ///< The minimum size of the size filter. uint64_t max_size_; ///< The maximum size of the size filter. + uint64_t min_mip_level_; ///< The minimum size of the mip level filter. + uint64_t max_mip_level_; ///< The maximum size of the mip level filter. }; } // namespace rmv diff --git a/source/frontend/models/resource_item_model.cpp b/source/frontend/models/resource_item_model.cpp index 9a8a5e1..90fbefc 100644 --- a/source/frontend/models/resource_item_model.cpp +++ b/source/frontend/models/resource_item_model.cpp @@ -53,6 +53,9 @@ namespace rmv resource_table->SetColumnWidthEms(kResourceColumnCompareId, 8); resource_table->SetColumnWidthEms(kResourceColumnName, 20); resource_table->SetColumnWidthEms(kResourceColumnVirtualAddress, 11); + resource_table->SetColumnWidthEms(kResourceColumnDimension, 11); + resource_table->SetColumnWidthEms(kResourceColumnMipLevel, 6); + resource_table->SetColumnWidthEms(kResourceColumnFormat, 8); resource_table->SetColumnWidthEms(kResourceColumnSize, 8); resource_table->SetColumnWidthEms(kResourceColumnPreferredHeap, 11); resource_table->SetColumnWidthEms(kResourceColumnMappedInvisible, 13); @@ -134,6 +137,18 @@ namespace rmv return cache_[row].resource_name; case kResourceColumnVirtualAddress: return rmv::string_util::LocalizedValueAddress(RmtResourceGetVirtualAddress(resource)); + case kResourceColumnDimension: + return resource->resource_type == kRmtResourceTypeImage + ? QString::asprintf("%dx%dx%d", resource->image.dimension_x, resource->image.dimension_y, resource->image.dimension_z) + : QString("-"); + case kResourceColumnMipLevel: + return resource->resource_type == kRmtResourceTypeImage + ? QString::asprintf("%d", resource->image.mip_levels) + : QString("-"); + case kResourceColumnFormat: + return resource->resource_type == kRmtResourceTypeImage + ? QString(RmtGetFormatNameFromFormat(resource->image.format.format)) + : QString("-"); case kResourceColumnSize: return rmv::string_util::LocalizedValueMemory(resource->size_in_bytes, false, false); case kResourceColumnMappedInvisible: @@ -179,6 +194,18 @@ namespace rmv return QVariant::fromValue(resource->identifier); case kResourceColumnVirtualAddress: return QVariant::fromValue(RmtResourceGetVirtualAddress(resource)); + case kResourceColumnDimension: + return resource->resource_type == kRmtResourceTypeImage + ? QList({resource->image.dimension_x, resource->image.dimension_y, resource->image.dimension_z}) + : QList({0, 0, 0}); + case kResourceColumnMipLevel: + return resource->resource_type == kRmtResourceTypeImage + ? resource->image.mip_levels + : 0; + case kResourceColumnFormat: + return resource->resource_type == kRmtResourceTypeImage + ? QString(RmtGetFormatNameFromFormat(resource->image.format.format)) + : QString("-"); case kResourceColumnSize: return QVariant::fromValue(resource->size_in_bytes); case kResourceColumnMappedInvisible: @@ -207,6 +234,18 @@ namespace rmv { case kResourceColumnName: return cache_[row].resource_name; + case kResourceColumnDimension: + return resource->resource_type == kRmtResourceTypeImage + ? QString::asprintf("%dx%dx%d", resource->image.dimension_x, resource->image.dimension_y, resource->image.dimension_z) + : QString("-"); + case kResourceColumnMipLevel: + return resource->resource_type == kRmtResourceTypeImage + ? QString::asprintf("%d", resource->image.mip_levels) + : QString("-"); + case kResourceColumnFormat: + return resource->resource_type == kRmtResourceTypeImage + ? QString(RmtGetFormatNameFromFormat(resource->image.format.format)) + : QString("-"); case kResourceColumnSize: return rmv::string_util::LocalizedValueBytes(resource->size_in_bytes); case kResourceColumnMappedInvisible: @@ -243,6 +282,12 @@ namespace rmv return "Name"; case kResourceColumnVirtualAddress: return "Virtual address"; + case kResourceColumnDimension: + return "Dimension"; + case kResourceColumnMipLevel: + return "Mip Level"; + case kResourceColumnFormat: + return "Format"; case kResourceColumnSize: return "Size"; case kResourceColumnPreferredHeap: diff --git a/source/frontend/models/resource_item_model.h b/source/frontend/models/resource_item_model.h index e5bf51e..a5a776c 100644 --- a/source/frontend/models/resource_item_model.h +++ b/source/frontend/models/resource_item_model.h @@ -26,13 +26,16 @@ namespace rmv kResourceColumnCompareId, kResourceColumnName, kResourceColumnVirtualAddress, + kResourceColumnUsage, + kResourceColumnDimension, + kResourceColumnMipLevel, + kResourceColumnFormat, kResourceColumnSize, kResourceColumnPreferredHeap, kResourceColumnMappedInvisible, kResourceColumnMappedLocal, kResourceColumnMappedHost, kResourceColumnMappedNone, - kResourceColumnUsage, // Hidden, these columns are used as proxies for sorting by other columns. kResourceColumnAllocationIdInternal, diff --git a/source/frontend/models/snapshot/resource_list_model.cpp b/source/frontend/models/snapshot/resource_list_model.cpp index 44a853c..00cb70f 100644 --- a/source/frontend/models/snapshot/resource_list_model.cpp +++ b/source/frontend/models/snapshot/resource_list_model.cpp @@ -134,6 +134,17 @@ namespace rmv UpdateBottomLabels(); } + void ResourceListModel::FilterByMipLevelChanged(int min_value, int max_value) + { + const uint64_t scaled_min = rmv_util::CalculateThresholdFromStepValue(min_value, rmv::kMipSliderRange - 1); + const uint64_t scaled_max = rmv_util::CalculateThresholdFromStepValue(max_value, rmv::kMipSliderRange - 1); + + proxy_model_->SetMipLevelFilter(scaled_min, scaled_max); + proxy_model_->invalidate(); + + UpdateBottomLabels(); + } + ResourceProxyModel* ResourceListModel::GetResourceProxyModel() const { return proxy_model_; diff --git a/source/frontend/models/snapshot/resource_list_model.h b/source/frontend/models/snapshot/resource_list_model.h index 4760d02..85e2deb 100644 --- a/source/frontend/models/snapshot/resource_list_model.h +++ b/source/frontend/models/snapshot/resource_list_model.h @@ -57,6 +57,12 @@ namespace rmv /// @param [in] max_value Maximum value of slider span. void FilterBySizeChanged(int min_value, int max_value); + /// @brief Handle what happens when the mip level filter changes. + /// + /// @param [in] min_value Minimum value of slider span. + /// @param [in] max_value Maximum value of slider span. + void FilterByMipLevelChanged(int min_value, int max_value); + /// @brief Read the dataset and update model. void Update(); diff --git a/source/frontend/settings/rmv_settings.cpp b/source/frontend/settings/rmv_settings.cpp index 248d80c..c2b4ab4 100644 --- a/source/frontend/settings/rmv_settings.cpp +++ b/source/frontend/settings/rmv_settings.cpp @@ -206,6 +206,7 @@ namespace rmv default_settings_[kSettingLastFileOpenLocation] = {"LastFileOpenLocation", ""}; default_settings_[kSettingGeneralCheckForUpdatesOnStartup] = {"CheckForUpdatesOnStartup", "False"}; default_settings_[kSettingGeneralTimeUnits] = {"TimeUnits", rmv::text::kSettingsUnitsSeconds}; + default_settings_[kSettingGeneralByteUnits] = {"ByteUnits", rmv::text::kSettingsByteUnitsDefault}; default_settings_[kSettingGeneralDriverOverridesAllowNotifications] = {"DriverOverridesAllowNotifications", "True"}; default_settings_[kSettingThemesAndColorsPalette] = {"ColorPalette", @@ -362,6 +363,11 @@ namespace rmv } } + QString RMVSettings::GetByteUnits() const + { + return active_settings_[kSettingGeneralByteUnits].value; + } + int RMVSettings::GetWindowWidth() const { return GetIntValue(kSettingMainWindowWidth); @@ -420,6 +426,19 @@ namespace rmv SaveSettings(); } + void RMVSettings::SetByteUnits(const QString& units) + { + if (units == rmv::text::kSettingsByteUnitsBinary || units == rmv::text::kSettingsByteUnitsDecimal) + { + AddPotentialSetting(default_settings_[kSettingGeneralByteUnits].name, units); + } + else + { + AddPotentialSetting(default_settings_[kSettingGeneralByteUnits].name, rmv::text::kSettingsByteUnitsDefault); + } + SaveSettings(); + } + void RMVSettings::SetLastFileOpenLocation(const QString& last_file_open_location) { AddPotentialSetting("LastFileOpenLocation", last_file_open_location); diff --git a/source/frontend/settings/rmv_settings.h b/source/frontend/settings/rmv_settings.h index 94f71f3..23317a0 100644 --- a/source/frontend/settings/rmv_settings.h +++ b/source/frontend/settings/rmv_settings.h @@ -97,6 +97,8 @@ enum RMVSettingID kSettingThemesAndColorsCommitTypePlaced, kSettingThemesAndColorsCommitTypeVirtual, + kSettingGeneralByteUnits, + kSettingCount, }; @@ -220,6 +222,11 @@ namespace rmv /// @return A TimeUnitType value. TimeUnitType GetUnits() const; + /// @brief Get byte units from the settings. + /// + /// @return The string value for Byte Unit. + QString GetByteUnits() const; + /// @brief Get last file open location from the settings. /// /// @return Path to last opened file dir. @@ -264,6 +271,11 @@ namespace rmv /// @param [in] units The new value of the timing units. void SetUnits(const TimeUnitType units); + /// @brief Set the byte units in the settings. + /// + /// @param [in] units The new value of the byte units. + void SetByteUnits(const QString& units); + /// @brief Get the value of kSettingGeneralCheckForUpdatesOnStartup in the settings. /// /// @return The value of kSettingGeneralCheckForUpdatesOnStartup. diff --git a/source/frontend/util/constants.h b/source/frontend/util/constants.h index b062c57..966e0bd 100644 --- a/source/frontend/util/constants.h +++ b/source/frontend/util/constants.h @@ -55,6 +55,7 @@ namespace rmv static const int kHeapComboBoxWidth = 140; static const int kResourceComboBoxWidth = 140; static const int kSizeSliderRange = 25; + static const int kMipSliderRange = 16; // Control window sizes. static const int kDesktopMargin = 25; @@ -137,6 +138,11 @@ namespace rmv static const QString kSettingsUnitsMinutes = "Minutes"; static const QString kSettingsUnitsHours = "Hours"; + // Byte units. + static const QString kSettingsByteUnitsDefault = "Default"; + static const QString kSettingsByteUnitsBinary = "Binary"; + static const QString kSettingsByteUnitsDecimal = "Decimal"; + // Help file locations for trace and RMV. static const QString kTraceHelpFile = "/help/rdp/index.html"; static const QString kRmvHelpFile = "/help/rmv/index.html"; diff --git a/source/frontend/util/rmv_util.cpp b/source/frontend/util/rmv_util.cpp index a282856..3249ff2 100644 --- a/source/frontend/util/rmv_util.cpp +++ b/source/frontend/util/rmv_util.cpp @@ -185,6 +185,16 @@ uint64_t rmv_util::CalculateSizeThresholdFromStepValue(const uint32_t step_value return pow(2, step_value + kThresholdStepOffset); } +uint64_t rmv_util::CalculateThresholdFromStepValue(const uint32_t step_value, const uint32_t max_steps) +{ + if (step_value >= max_steps) + { + return UINT64_MAX; + } + + return step_value; +} + QString rmv_util::GetVirtualAllocationName(const RmtVirtualAllocation* virtual_allocation) { QString allocation_name; diff --git a/source/frontend/util/rmv_util.h b/source/frontend/util/rmv_util.h index cd7eed0..c3f8bcd 100644 --- a/source/frontend/util/rmv_util.h +++ b/source/frontend/util/rmv_util.h @@ -84,6 +84,14 @@ namespace rmv_util /// @return The calculated value. uint64_t CalculateSizeThresholdFromStepValue(const uint32_t step_value, const uint32_t max_steps); + /// @brief Calculate the mapped value given a step value. + /// + /// @param [in] step_value An unmapped whole number used to calculate a mapped threshold value. + /// @param [in] max_steps The maximum number of steps in the range. + /// + /// @return The calculated value. + uint64_t CalculateThresholdFromStepValue(const uint32_t step_value, const uint32_t max_steps); + /// @brief Retrieves the name of a virtual allocation or a string containing the base address in hexadecimal form. /// /// @param [in] virtual_allocation A pointer to the virtual allocation object. diff --git a/source/frontend/util/string_util.cpp b/source/frontend/util/string_util.cpp index 9fa2cf3..b9a26f2 100644 --- a/source/frontend/util/string_util.cpp +++ b/source/frontend/util/string_util.cpp @@ -12,6 +12,8 @@ #include "qt_common/utils/qt_util.h" +#include "settings/rmv_settings.h" + QString rmv::string_util::ToUpperCase(const QString& string) { QString out; @@ -69,8 +71,19 @@ QString rmv::string_util::LocalizedValuePrecise(double value) return str; } -QString rmv::string_util::LocalizedValueMemory(const double value, const bool base_10, const bool use_round, const bool include_decimal) +QString rmv::string_util::LocalizedValueMemory(const double value, const bool in_base_10, const bool use_round, const bool include_decimal) { + QString ByteUnits = RMVSettings::Get().GetByteUnits(); + bool base_10 = in_base_10; + if (ByteUnits == rmv::text::kSettingsByteUnitsBinary) + { + base_10 = false; + } + else if (ByteUnits == rmv::text::kSettingsByteUnitsDecimal) + { + base_10 = true; + } + double multiple; if (base_10) { @@ -175,3 +188,36 @@ QString rmv::string_util::GetMemoryRangeString(const uint64_t min_memory_size, c return range_string; } + +QString rmv::string_util::GetValueRangeString(const uint64_t min, const uint64_t max) +{ + const static char kHyphen[] = " - "; + const static char kInfinity[] = "\xE2\x88\x9E"; + QString range_string; + + // Append string for range start. + if (min == UINT64_MAX) + { + range_string += kInfinity; + } + else + { + QString value = string_util::LocalizedValue(min); + range_string += value; + } + + range_string += kHyphen; + + // Append string for range end. + if (max == UINT64_MAX) + { + range_string += kInfinity; + } + else + { + QString value = string_util::LocalizedValue(max); + range_string += value; + } + + return range_string; +} diff --git a/source/frontend/util/string_util.h b/source/frontend/util/string_util.h index 0b7a847..a0d0cea 100644 --- a/source/frontend/util/string_util.h +++ b/source/frontend/util/string_util.h @@ -88,6 +88,13 @@ namespace rmv /// @return The localized string. QString GetMemoryRangeString(const uint64_t min_memory_size, const uint64_t max_memory_size); + /// @brief Builds a range string for two int values. + /// + /// @param [in] min The lower value of the range. + /// @param [in] max The upper value of the range. + /// + /// @return The localized string. + QString GetValueRangeString(const uint64_t min, const uint64_t max); } // namespace string_util } // namespace rmv diff --git a/source/frontend/util/widget_util.cpp b/source/frontend/util/widget_util.cpp index 33e68bd..25f961f 100644 --- a/source/frontend/util/widget_util.cpp +++ b/source/frontend/util/widget_util.cpp @@ -21,8 +21,18 @@ namespace rmv slider_widget->setFixedWidth(rmv::kDoubleSliderWidth); slider_widget->setFixedHeight(rmv::kDoubleSliderHeight); slider_widget->setCursor(Qt::PointingHandCursor); - slider_widget->setMinimum(0); - slider_widget->setMaximum(kSizeSliderRange - 1); + switch (slider_widget->SliderType()) + { + case ESliderType::Size: + slider_widget->setMinimum(0); + slider_widget->setMaximum(kSizeSliderRange - 1); + break; + case ESliderType::MipLevel: + default: + slider_widget->setMinimum(1); + slider_widget->setMaximum(kMipSliderRange - 1); + break; + } slider_widget->Init(); } diff --git a/source/frontend/views/compare/memory_leak_finder_pane.cpp b/source/frontend/views/compare/memory_leak_finder_pane.cpp index 23bc581..17c5ed8 100644 --- a/source/frontend/views/compare/memory_leak_finder_pane.cpp +++ b/source/frontend/views/compare/memory_leak_finder_pane.cpp @@ -63,6 +63,7 @@ MemoryLeakFinderPane::MemoryLeakFinderPane(QWidget* parent) rmv::widget_util::InitCommonFilteringComponents(ui_->search_box_, ui_->size_slider_); rmv::widget_util::InitRangeSlider(ui_->size_slider_); + rmv::widget_util::InitRangeSlider(ui_->mip_slider_); ui_->base_allocations_checkbox_->Initialize(false, rmv::RMVSettings::Get().GetColorSnapshotViewed(), Qt::black); ui_->both_allocations_checkbox_->Initialize( @@ -72,6 +73,7 @@ MemoryLeakFinderPane::MemoryLeakFinderPane(QWidget* parent) CompareFilterChanged(); connect(ui_->size_slider_, &DoubleSliderWidget::SpanChanged, this, &MemoryLeakFinderPane::FilterBySizeSliderChanged); + connect(ui_->mip_slider_, &DoubleSliderWidget::SpanChanged, this, &MemoryLeakFinderPane::FilterByMipLevelSliderChanged); connect(ui_->search_box_, &QLineEdit::textChanged, this, &MemoryLeakFinderPane::SearchBoxChanged); connect(ui_->resource_table_view_, &QTableView::doubleClicked, this, &MemoryLeakFinderPane::TableDoubleClicked); connect(ui_->both_allocations_checkbox_, &RMVColoredCheckbox::Clicked, this, &MemoryLeakFinderPane::CompareFilterChanged); @@ -170,6 +172,8 @@ void MemoryLeakFinderPane::Reset() ui_->size_slider_->SetLowerValue(0); ui_->size_slider_->SetUpperValue(ui_->size_slider_->maximum()); + ui_->mip_slider_->SetLowerValue(ui_->mip_slider_->minimum()); + ui_->mip_slider_->SetUpperValue(ui_->mip_slider_->maximum()); ui_->search_box_->setText(""); } @@ -191,6 +195,12 @@ void MemoryLeakFinderPane::FilterBySizeSliderChanged(int min_value, int max_valu SetMaximumResourceTableHeight(); } +void MemoryLeakFinderPane::FilterByMipLevelSliderChanged(int min_value, int max_value) +{ + model_->FilterByMipLevelChanged(min_value, max_value); + SetMaximumResourceTableHeight(); +} + void MemoryLeakFinderPane::CompareFilterChanged() { rmv::SnapshotCompareId filter = GetCompareIdFilter(); diff --git a/source/frontend/views/compare/memory_leak_finder_pane.h b/source/frontend/views/compare/memory_leak_finder_pane.h index c5ff104..c84227f 100644 --- a/source/frontend/views/compare/memory_leak_finder_pane.h +++ b/source/frontend/views/compare/memory_leak_finder_pane.h @@ -59,6 +59,12 @@ private slots: /// @param [in] max_value Maximum value of slider span. void FilterBySizeSliderChanged(int min_value, int max_value); + /// @brief Slot to handle what happens when the 'filter by mip level' slider changes. + /// + /// @param [in] min_value Minimum value of slider span. + /// @param [in] max_value Maximum value of slider span. + void FilterByMipLevelSliderChanged(int min_value, int max_value); + /// @brief Checkboxes on the top were clicked. void CompareFilterChanged(); diff --git a/source/frontend/views/compare/memory_leak_finder_pane.ui b/source/frontend/views/compare/memory_leak_finder_pane.ui index 79b3307..8d54165 100644 --- a/source/frontend/views/compare/memory_leak_finder_pane.ui +++ b/source/frontend/views/compare/memory_leak_finder_pane.ui @@ -351,6 +351,29 @@ + + + + + 0 + 0 + + + + Filter by mip level: + + + + + + + Qt::Horizontal + + + ESliderType::MipLevel + + + diff --git a/source/frontend/views/custom_widgets/rmv_range_slider.cpp b/source/frontend/views/custom_widgets/rmv_range_slider.cpp index 8f3d98c..cf7ae64 100644 --- a/source/frontend/views/custom_widgets/rmv_range_slider.cpp +++ b/source/frontend/views/custom_widgets/rmv_range_slider.cpp @@ -16,6 +16,7 @@ RmvRangeSlider::RmvRangeSlider(QWidget* parent) : DoubleSliderWidget(parent) , range_value_label_(nullptr) + , slider_type_(ESliderType::Size) { } @@ -37,10 +38,23 @@ void RmvRangeSlider::Init() range_value_label_ = new RmvFixedWidthLabel(container); range_value_label_->setObjectName("range_value_label_"); - // Build a formatted string with the maximum expected width. - // The label will reserve this much horizontal space in the layout so that the slider to the left isn't affected when the value string changes length. - QString widest_range_string = - rmv::string_util::LocalizedValueMemory(999, false, false, false) + " - " + rmv::string_util::LocalizedValueMemory(999, false, false, false); + QString widest_range_string; + + switch (slider_type_) + { + case ESliderType::Size: + // Build a formatted string with the maximum expected width. + // The label will reserve this much horizontal space in the layout so that the slider to the left isn't affected when the value string changes length. + widest_range_string = + rmv::string_util::LocalizedValueMemory(999, false, false, false) + " - " + rmv::string_util::LocalizedValueMemory(999, false, false, false); + break; + case ESliderType::MipLevel: + default: + widest_range_string = rmv::string_util::GetValueRangeString(999, 999); + SetHandleMovementMode(kNoCrossing); + break; + } + range_value_label_->SetWidestTextString(widest_range_string); layout->addWidget(range_value_label_); parentWidget()->layout()->replaceWidget(this, container); @@ -52,12 +66,38 @@ void RmvRangeSlider::Init() connect(this, &DoubleSliderWidget::SpanChanged, this, &RmvRangeSlider::UpdateValues); } +ESliderType RmvRangeSlider::SliderType() const +{ + return slider_type_; +} + +void RmvRangeSlider::setSliderType(ESliderType slider_type) +{ + slider_type_ = slider_type; +} + + void RmvRangeSlider::UpdateValues(const int min_value, const int max_value) { if (range_value_label_ != nullptr) { - const uint64_t lower_range = rmv_util::CalculateSizeThresholdFromStepValue(min_value, rmv::kSizeSliderRange - 1); - const uint64_t upper_range = rmv_util::CalculateSizeThresholdFromStepValue(max_value, rmv::kSizeSliderRange - 1); - range_value_label_->setText(rmv::string_util::GetMemoryRangeString(lower_range, upper_range)); + switch (slider_type_) + { + case ESliderType::Size: + { + const uint64_t lower_range = rmv_util::CalculateSizeThresholdFromStepValue(min_value, rmv::kSizeSliderRange - 1); + const uint64_t upper_range = rmv_util::CalculateSizeThresholdFromStepValue(max_value, rmv::kSizeSliderRange - 1); + range_value_label_->setText(rmv::string_util::GetMemoryRangeString(lower_range, upper_range)); + break; + } + case ESliderType::MipLevel: + default: + { + const uint64_t lower_range = rmv_util::CalculateThresholdFromStepValue(min_value, rmv::kMipSliderRange - 1); + const uint64_t upper_range = rmv_util::CalculateThresholdFromStepValue(max_value, rmv::kMipSliderRange - 1); + range_value_label_->setText(rmv::string_util::GetValueRangeString(lower_range, upper_range)); + break; + } + } } } diff --git a/source/frontend/views/custom_widgets/rmv_range_slider.h b/source/frontend/views/custom_widgets/rmv_range_slider.h index 555e149..7d4a3fd 100644 --- a/source/frontend/views/custom_widgets/rmv_range_slider.h +++ b/source/frontend/views/custom_widgets/rmv_range_slider.h @@ -14,9 +14,17 @@ #include "views/custom_widgets/rmv_fixed_width_label.h" +enum class ESliderType : int8_t +{ + Size = 0, + MipLevel, + Num +}; + /// @brief Range slider that extends the double slider widget by adding a range value label. class RmvRangeSlider : public DoubleSliderWidget { + Q_PROPERTY(ESliderType SliderType READ SliderType WRITE setSliderType) public: /// @brief Constructor /// @@ -29,6 +37,14 @@ class RmvRangeSlider : public DoubleSliderWidget /// @brief Intializes the range slider and adds the range value label. void Init(); + /// @brief Get rmv range slider type. + ESliderType SliderType() const; + + /// @brief Set rmv range slider type. + /// + /// @param [in] slider_type The type of this slider widget. + void setSliderType(ESliderType slider_type); + private slots: /// @brief Slot to update the range value label when the slide is adjusted. /// @@ -38,5 +54,6 @@ private slots: private: RmvFixedWidthLabel* range_value_label_; ///< A pointer to the label widget that displays the range values. + ESliderType slider_type_; }; #endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_RANGE_SLIDER_H_ diff --git a/source/frontend/views/settings/settings_pane.cpp b/source/frontend/views/settings/settings_pane.cpp index cbfeef8..01a70e9 100644 --- a/source/frontend/views/settings/settings_pane.cpp +++ b/source/frontend/views/settings/settings_pane.cpp @@ -16,6 +16,12 @@ using namespace driver_overrides; +static const QVector ByteUnits { + rmv::text::kSettingsByteUnitsDefault, + rmv::text::kSettingsByteUnitsBinary, + rmv::text::kSettingsByteUnitsDecimal +}; + SettingsPane::SettingsPane(QWidget* parent) : BasePane(parent) , ui_(new Ui::SettingsPane) @@ -53,6 +59,16 @@ SettingsPane::SettingsPane(QWidget* parent) ui_->units_combo_push_button_->SetSelectedRow(0); connect(ui_->units_combo_push_button_, &ArrowIconComboBox::SelectionChanged, this, &SettingsPane::TimeUnitsChanged); + // Populate the bytes combo box. + rmv::widget_util::InitSingleSelectComboBox(parent, ui_->byte_units_combo_push_button_, rmv::text::kSettingsByteUnitsDefault, false); + ui_->byte_units_combo_push_button_->ClearItems(); + for (const QString& unit : ByteUnits) + { + ui_->byte_units_combo_push_button_->AddItem(unit); + } + ui_->byte_units_combo_push_button_->SetSelectedRow(0); + connect(ui_->byte_units_combo_push_button_, &ArrowIconComboBox::SelectionChanged, this, &SettingsPane::ByteUnitsChanged); + connect(ui_->check_for_updates_on_startup_checkbox_, &CheckBoxWidget::stateChanged, this, &SettingsPane::CheckForUpdatesOnStartupStateChanged); } @@ -68,6 +84,10 @@ void SettingsPane::showEvent(QShowEvent* event) int units = rmv::RMVSettings::Get().GetUnits(); UpdateTimeComboBox(units); + // Update the byte unit combo box push button text. + QString byte_units = rmv::RMVSettings::Get().GetByteUnits(); + UpdateByteComboBox(byte_units); + QWidget::showEvent(event); } @@ -81,6 +101,20 @@ void SettingsPane::UpdateTimeComboBox(int units) ui_->units_combo_push_button_->SetSelectedRow(units); } +void SettingsPane::UpdateByteComboBox(const QString& units) +{ + int index = 0; + for (; index < ByteUnits.size(); ++index) + { + if (ByteUnits[index] == units) + { + break; + } + } + + ui_->byte_units_combo_push_button_->SetSelectedRow(index); +} + void SettingsPane::TimeUnitsChanged() { int index = ui_->units_combo_push_button_->CurrentRow(); @@ -94,6 +128,13 @@ void SettingsPane::TimeUnitsChanged() rmv::RMVSettings::Get().SaveSettings(); } +void SettingsPane::ByteUnitsChanged() +{ + int index = ui_->byte_units_combo_push_button_->CurrentRow(); + + rmv::RMVSettings::Get().SetByteUnits(ByteUnits[index]); +} + void SettingsPane::SwitchTimeUnits() { int units = rmv::RMVSettings::Get().GetUnits(); diff --git a/source/frontend/views/settings/settings_pane.h b/source/frontend/views/settings/settings_pane.h index 23c0964..0f5761e 100644 --- a/source/frontend/views/settings/settings_pane.h +++ b/source/frontend/views/settings/settings_pane.h @@ -48,6 +48,9 @@ public slots: /// @brief Slot to handle what happens when the time units combo box has changed. void TimeUnitsChanged(); + /// @brief Slot to handle what happens when the byte units combo box has changed. + void ByteUnitsChanged(); + private: /// @brief Update the time unit combo box. /// @@ -57,6 +60,11 @@ public slots: /// @param [in] units The time units. void UpdateTimeComboBox(int units); + /// @brief Update the byte unit combo box. + /// + /// @param [in] units The byte units. + void UpdateByteComboBox(const QString& units); + Ui::SettingsPane* ui_; ///< Pointer to the Qt UI design. }; diff --git a/source/frontend/views/settings/settings_pane.ui b/source/frontend/views/settings/settings_pane.ui index 1f62131..83fb6b4 100644 --- a/source/frontend/views/settings/settings_pane.ui +++ b/source/frontend/views/settings/settings_pane.ui @@ -253,6 +253,125 @@ + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Byte units + + + false + + + 0 + + + + + + + + 0 + 0 + + + + In tables, show units of byte in: + + + 0 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 2 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Bytes + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + diff --git a/source/frontend/views/snapshot/resource_list_pane.cpp b/source/frontend/views/snapshot/resource_list_pane.cpp index 4ae171b..aaefb35 100644 --- a/source/frontend/views/snapshot/resource_list_pane.cpp +++ b/source/frontend/views/snapshot/resource_list_pane.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include "qt_common/custom_widgets/double_slider_widget.h" @@ -64,12 +66,16 @@ ResourceListPane::ResourceListPane(QWidget* parent) rmv::widget_util::InitCommonFilteringComponents(ui_->search_box_, ui_->size_slider_); rmv::widget_util::InitRangeSlider(ui_->size_slider_); + rmv::widget_util::InitRangeSlider(ui_->mip_slider_); connect(ui_->size_slider_, &DoubleSliderWidget::SpanChanged, this, &ResourceListPane::FilterBySizeSliderChanged); + connect(ui_->mip_slider_, &DoubleSliderWidget::SpanChanged, this, &ResourceListPane::FilterByMipLevelSliderChanged); connect(ui_->search_box_, &QLineEdit::textChanged, this, &ResourceListPane::SearchBoxChanged); connect(ui_->resource_table_view_, &QTableView::clicked, this, &ResourceListPane::TableClicked); connect(ui_->resource_table_view_, &QTableView::doubleClicked, this, &ResourceListPane::TableDoubleClicked); - ui_->dump_resources_button_->hide(); + connect(ui_->dump_resources_button_, &QPushButton::clicked, this, &ResourceListPane::DumpResources); + + // ui_->dump_resources_button_->hide(); // Set up a connection between the timeline being sorted and making sure the selected event is visible. connect(model_->GetResourceProxyModel(), &rmv::ResourceProxyModel::layoutChanged, this, &ResourceListPane::ScrollToSelectedResource); @@ -111,6 +117,7 @@ void ResourceListPane::Refresh() QString resource_filter_string = resource_usage_combo_box_model_->GetFilterString(ui_->resource_usage_combo_box_); model_->UpdateResourceUsageList(resource_filter_string); FilterBySizeSliderChanged(ui_->size_slider_->LowerValue(), ui_->size_slider_->UpperValue()); + FilterByMipLevelSliderChanged(ui_->mip_slider_->LowerValue(), ui_->mip_slider_->UpperValue()); } void ResourceListPane::OnTraceClose() @@ -127,6 +134,8 @@ void ResourceListPane::Reset() ui_->size_slider_->SetLowerValue(0); ui_->size_slider_->SetUpperValue(ui_->size_slider_->maximum()); + ui_->mip_slider_->SetLowerValue(ui_->mip_slider_->minimum()); + ui_->mip_slider_->SetUpperValue(ui_->mip_slider_->maximum()); ui_->search_box_->setText(""); carousel_->ClearData(); @@ -206,6 +215,12 @@ void ResourceListPane::FilterBySizeSliderChanged(int min_value, int max_value) SetMaximumResourceTableHeight(); } +void ResourceListPane::FilterByMipLevelSliderChanged(int min_value, int max_value) +{ + model_->FilterByMipLevelChanged(min_value, max_value); + SetMaximumResourceTableHeight(); +} + void ResourceListPane::HeapChanged(bool checked) { // Rebuild the table depending on what the state of the combo box items is. @@ -268,6 +283,37 @@ void ResourceListPane::ScrollToSelectedResource() } } +void ResourceListPane::DumpResources() +{ + QString FilePath = QFileDialog::getSaveFileName(this, "Dump Resources", QDir::cleanPath(QDir::homePath() + "/rmv_resources.csv"), "CSV files | *.csv"); + QFile File(FilePath); + if (!File.open(QIODevice::WriteOnly | QIODevice::Text)) + return; + + ui_->dump_resources_button_->setEnabled(false); + QTextStream Stream(&File); + auto proxy_model_ = model_->GetResourceProxyModel(); + + for (int32_t j = rmv::kResourceColumnName; j <= rmv::kResourceColumnMappedNone; j++) + { + Stream << proxy_model_->headerData(j, Qt::Horizontal).toString() << ","; + } + Stream << "\n"; + + for (int32_t i = 0; i < proxy_model_->rowCount(); i++) + { + for (int32_t j = rmv::kResourceColumnName; j <= rmv::kResourceColumnMappedNone; j++) + { + Stream << proxy_model_->GetDataAsStr(i, j) << ","; + } + Stream << "\n"; + } + + File.close(); + + ui_->dump_resources_button_->setEnabled(true); +} + void ResourceListPane::SelectResourceInTable() { if (selected_resource_identifier_ != 0) diff --git a/source/frontend/views/snapshot/resource_list_pane.h b/source/frontend/views/snapshot/resource_list_pane.h index d7f7f53..5fedd29 100644 --- a/source/frontend/views/snapshot/resource_list_pane.h +++ b/source/frontend/views/snapshot/resource_list_pane.h @@ -67,6 +67,12 @@ private slots: /// @param [in] max_value Maximum value of slider span. void FilterBySizeSliderChanged(int min_value, int max_value); + /// @brief Slot to handle what happens when the 'filter by mip level' slider changes. + /// + /// @param [in] min_value Minimum value of slider span. + /// @param [in] max_value Maximum value of slider span. + void FilterByMipLevelSliderChanged(int min_value, int max_value); + /// @brief Handle what happens when a checkbox in the heap dropdown is checked or unchecked. /// /// @param [in] checked Whether the checkbox is checked or unchecked. @@ -101,6 +107,9 @@ private slots: /// Make sure the selected item (if there is one) is visible. void ScrollToSelectedResource(); + /// @brief Handle what happens when user click the dump resources button. + void DumpResources(); + private: /// @brief Refresh what's visible on the UI. void Refresh(); diff --git a/source/frontend/views/snapshot/resource_list_pane.ui b/source/frontend/views/snapshot/resource_list_pane.ui index 5763ce3..009538f 100644 --- a/source/frontend/views/snapshot/resource_list_pane.ui +++ b/source/frontend/views/snapshot/resource_list_pane.ui @@ -132,6 +132,29 @@ + + + + + 0 + 0 + + + + Filter by mip level: + + + + + + + Qt::Horizontal + + + ESliderType::MipLevel + + +