diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..49672f4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,46 @@ +BasedOnStyle: Google +IndentWidth: 4 +UseTab: Never +ColumnLimit: 160 +Language: Cpp +AccessModifierOffset: -4 +BreakBeforeBraces: Custom +BraceWrapping: + + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +ConstructorInitializerAllOnOneLineOrOnePerLine : false +BreakConstructorInitializers: BeforeComma +DerivePointerAlignment: false +IndentCaseLabels: false +NamespaceIndentation: All +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignTrailingComments: true +AlignOperands: true +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: false +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +BinPackArguments: false +BinPackParameters: false +ExperimentalAutoDetectBinPacking: false +AllowAllParametersOfDeclarationOnNextLine: false \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..7500d9c --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,52 @@ +Checks: bugprone-*,clang-analyzer-*,clang-diagnostic-*,google-*,misc-*,modernize-*,-modernize-use-trailing-return-type,-modernize-concat-nested-namespaces,performance-*,readability-*,-modernize-use-auto +WarningsAsErrors: bugprone-*,clang-analyzer-*,clang-diagnostic-*,google-*,misc-*,modernize-*,performance-*,readability-* +FormatStyle: file +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.ClassConstantCase + value: CamelCase + - key: readability-identifier-naming.ClassConstantPrefix + value: k + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantPrefix + value: k + - key: readability-identifier-naming.FunctionCase + value: CamelCase + - key: readability-identifier-naming.GlobalConstantCase + value: CamelCase + - key: readability-identifier-naming.GlobalConstantPrefix + value: k + - key: readability-identifier-naming.GlobalConstantPointerCase + value: CamelCase + - key: readability-identifier-naming.GlobalConstantPointerPrefix + value: k + - key: readability-identifier-naming.MethodCase + value: CamelCase + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.PrivateMemberCase + value: lower_case + - key: readability-identifier-naming.PrivateMemberSuffix + value: _ + - key: readability-identifier-naming.PublicMemberCase + value: lower_case + - key: readability-identifier-naming.StaticConstantCase + value: CamelCase + - key: readability-identifier-naming.StaticConstantPrefix + value: k + - key: readability-identifier-naming.TemplateParameterCase + value: CamelCase + - key: readability-identifier-naming.TypeAliasCase + value: CamelCase + - key: readability-identifier-naming.TypedefCase + value: CamelCase + - key: readability-identifier-naming.UnionCase + value: CamelCase + - key: readability-identifier-naming.VariableCase + value: lower_case diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..277d9f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*~ +*.pyc +*.orig +__pycache__/ +build/linux +build/mac +build/win +documentation/build +documentation/source/_build +external +.vscode +*.aps diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ab705cd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,117 @@ +cmake_minimum_required(VERSION 3.11) + +## Specify the top level name of the project - this will define the solution name for Visual Studio +project(RMV) + +## For RMV we only care about the Debug and Release configuration types +set(CMAKE_CONFIGURATION_TYPES Debug Release) + +## Determine build suffixes based on configuration, bitness and internal status +## These values will be inherited by all child projects +set(ADT_PLATFORM_POSTFIX "-x86") +IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ADT_PLATFORM_POSTFIX "-x64") +ENDIF() + +# As default for RMV, include the debug & internal status in filename - but not the platform bitness +set (CMAKE_DEBUG_POSTFIX -d${ADT_INTERNAL_POSTFIX}) +set (CMAKE_RELEASE_POSTFIX ${ADT_INTERNAL_POSTFIX}) + +IF(WIN32) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/../release${ADT_INTERNAL_POSTFIX}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/../debug${ADT_INTERNAL_POSTFIX}) +ELSE(WIN32) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/../../release${ADT_INTERNAL_POSTFIX}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/../../debug${ADT_INTERNAL_POSTFIX}) +ENDIF(WIN32) + +# Add for CentOS compiler warning +add_definitions(-DJSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + +include_directories("${PROJECT_SOURCE_DIR}/external/qt_common/") +include_directories("${PROJECT_SOURCE_DIR}/external/") + +# Global compiler options +IF(WIN32) + add_compile_options(/W4 /WX /MP) + # disable warning C4201: nonstandard extension used: nameless struct/union + add_compile_options(/wd4201) + # this warning is caused by the QT header files - use pragma to disable at source + # disable warning C4127: conditional expression is constant + add_compile_options(/wd4127) + # bump the stack size + add_link_options(/STACK:16777216) +ELSEIF(UNIX) + # Use -Wno-missing-field-initializers for CentOS compiler warning + add_compile_options(-std=c++11 -D_LINUX -Wall -Wextra -Werror -Wno-missing-field-initializers) + # Use _DEBUG on Unix for Debug Builds (defined automatically on Windows) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") +ENDIF(WIN32) + +IF(WIN32) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") +ENDIF(WIN32) + +# Macro to build source groups to match directory structure +MACRO(SOURCE_GROUP_BY_FOLDER target) + SET(SOURCE_GROUP_DELIMITER "/") + SET(last_dir "") + SET(files "") + FOREACH(file ${SOURCES}) + GET_FILENAME_COMPONENT(dir "${file}" PATH) + IF (NOT "${dir}" STREQUAL "${last_dir}") + IF (files) + SOURCE_GROUP("${last_dir}" FILES ${files}) + ENDIF (files) + SET(files "") + ENDIF (NOT "${dir}" STREQUAL "${last_dir}") + SET(files ${files} ${file}) + SET(last_dir "${dir}") + ENDFOREACH(file) + IF (files) + SOURCE_GROUP("${last_dir}" FILES ${files}) + ENDIF (files) +ENDMACRO(SOURCE_GROUP_BY_FOLDER) + +add_subdirectory(external/qt_common/custom_widgets QtCommon/custom_widgets) +add_subdirectory(external/qt_common/utils QtCommon/utils) +add_subdirectory(source/parser parser) +add_subdirectory(source/backend backend) +add_subdirectory(source/frontend frontend) + +# Group external dependency targets into folder +IF(WIN32) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set_target_properties(QtCustomWidgets + QtUtils + PROPERTIES + FOLDER Dependencies +) +ELSEIF(APPLE) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set_target_properties(QtCustomWidgets + QtUtils + PROPERTIES + FOLDER Dependencies +) +ENDIF() + +IF(WIN32) + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT RadeonMemoryVisualizer) +ENDIF(WIN32) + +## Copy Documentation and Samples to output directory +add_custom_target(Documentation ALL) +add_custom_command(TARGET Documentation POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "copying documentation to output directory" + COMMAND ${CMAKE_COMMAND} -E make_directory $/docs + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/documentation/License.htm $/docs/. + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/README.md $/. + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/Release_Notes.txt $/. + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/NOTICES.txt $/. + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/License.txt $/. + COMMAND ${CMAKE_COMMAND} -E echo "copying samples to output directory" + COMMAND ${CMAKE_COMMAND} -E make_directory $/samples + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/samples/sampleTrace.rmv $/samples/. +) diff --git a/Known_Issues.md b/Known_Issues.md deleted file mode 100644 index db5fe2c..0000000 --- a/Known_Issues.md +++ /dev/null @@ -1,11 +0,0 @@ -# Known Issues - -1. - 1. More than a single active device at a time within a single application is not supported. In this case, only the first device will be traced. - 2. More than a single Vulkan/DX12 process at a time is not supported. Only the first started application will be traced. -2. In the Snapshots|Resource details pane, Physical memory mapped events may be shown before virtual allocate events. -3. Some of the Pane navigation shortcuts may conflict with the keyboard shortcuts used by the Radeon Settings (such as ALT-R). It is recommended to remap the Radeon settings so they don't conflict. -4. Some UI elements do not rescale properly when the OS's DPI scale settings are dynamically changed, or when dragging RMV between two monitors with different DPI scales. Close and re-open RMV to view at proper sizes. -5. Running multiple instances of the Radeon Developer Panel is not supported. -6. Sparse texture are not fully supported. -7. When tracing an application that uses a launcher, or an application that creates multiple devices, it is possible that more than one trace file will be written to disk. In the case of the launcher, adding the launcher's executable name to the Blocked applications list in the Radeon Developer Panel should prevent multiple trace files. Restarting the Radeon Developer Panel may be required before attempting to trace again. diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..674a939 --- /dev/null +++ b/License.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2020 Advanced Micro Devices, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/NOTICES.txt b/NOTICES.txt new file mode 100644 index 0000000..e6ef2b1 --- /dev/null +++ b/NOTICES.txt @@ -0,0 +1,439 @@ +Notices and Licenses file +_________________________ + +driftyco-ionicons v-u (MIT) +Copyright (c) 2014 Drifty (http://drifty.com/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +neosmart-pevents v-u (MIT) +Copyright (C) 2011 - 2015 by NeoSmart Technologies + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +nlohmann-json v3.2.0 (MIT) +Copyright (c) 2013-2018 Niels Lohmann + +MIT License +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Qt v5.12.6 (LGPL v3) +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. +As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. + +“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + +An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + +A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. + +The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + +The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or +b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. +3. Object Code Incorporating Material from Library Header Files. +The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + +a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. +b) Accompany the object code with a copy of the GNU GPL and this license document. +4. Combined Works. +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. +b) Accompany the Combined Work with a copy of the GNU GPL and this license document. +c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. +d) Do one of the following: +0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. +1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. +e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) +5. Combined Libraries. +You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. +b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. +6. Revised Versions of the GNU Lesser General Public License. +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + +----------------------------------------------- + +metrohash v-u (Apache 2.0) + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +LZ4 Library v-u (BSD2) +Copyright (c) 2011-2016, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +MPack v-u (MIT) + +Copyright (c) 2015-2018 Nicholas Fraser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +tencent-rapidjson v1.1.0 v-u (MIT) +Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. + +If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. +If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. +A copy of the MIT License is included in this file. + +Other dependencies and licenses: + +Open Source Software Licensed Under the BSD License: +-------------------------------------------------------------------- + +The msinttypes r29 +Copyright (c) 2006-2013 Alexander Chemeris +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Open Source Software Licensed Under the JSON License: +-------------------------------------------------------------------- + +json.org +Copyright (c) 2002 JSON.org +All Rights Reserved. + +JSON_checker +Copyright (c) 2002 JSON.org +All Rights Reserved. + + +Terms of the JSON License: +--------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Terms of the MIT License: +-------------------------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 16fc7cf..f9dbd31 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,70 @@ The Radeon Memory Visualizer (RMV) is a software tool that will allow users to a ### Windows® 10 only (version 1903 or higher recommended) * DirectX12 * Vulkan + +## Build instructions + +### Download Source code + +Clone the project radeon_memory_visualizer from github.com + +git clone https://github.com/GPUOpen-Tools/radeon_memory_visualizer.git + +### Building on Windows ### +As a preliminary step, make sure that you have the following installed on your system: +* CMake 3.11 or above. +* Python 3.7 or above. +* Qt® 5 or above (5.12.6 is the default and recommended). +* Visual Studio® 2015 or above (2017 is the default). + +Qt should be installed to the default location (C:\Qt\Qt5.12.6). The Qt 5.12.6 install package can be found at https://download.qt.io/archive/qt/5.12/5.12.6/. +Make sure to select msvc2017 64-bit (under Qt 5.12.6 option) during Qt installation. Note that the expected installation will produce a C:\Qt\Qt5.12.6\5.12.6 folder. +A reboot is required after Qt is installed. +Cmake can be downloaded from (https://cmake.org/download/). +Python (V3.x) can be downloaded from (https://www.python.org/). + +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 5.12.6 and Visual Studio 2017) + +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 2019, add --vs 2019 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. + +Once the script has finished, in the case of Visual Studio 2017, a sub-folder called 'vs2017' will be created containing the necessary build files. +Go into the 'vs2017' folder (build/win/vs2017) and double click on the RMV.sln file and build the 64-bit Debug and Release builds. +The Release and Debug builds of RMV will be available in the build/release and build/debug folders. + +## Support ## +For support, please visit the RMV repository github page: https://github.com/GPUOpen-Tools/radeon_memory_visualizer + +## License ## +Radeon Memory Visualizer is licensed under the MIT license. See the License.txt file for complete license information. + +## Copyright information ## +Please see NOTICES.txt for third party license information. + +## DISCLAIMER ## +The information contained herein is for informational purposes only, and is subject to change without notice. While every +precaution has been taken in the preparation of this document, it may contain technical inaccuracies, omissions and typographical +errors, and AMD is under no obligation to update or otherwise correct this information. Advanced Micro Devices, Inc. makes no +representations or warranties with respect to the accuracy or completeness of the contents of this document, and assumes no +liability of any kind, including the implied warranties of noninfringement, merchantability or fitness for particular purposes, with +respect to the operation or use of AMD hardware, software or other products described herein. No license, including implied or +arising by estoppel, to any intellectual property rights is granted by this document. Terms and limitations applicable to the purchase +or use of AMD’s products are as set forth in a signed agreement between the parties or in AMD's Standard Terms and Conditions +of Sale. + +AMD, the AMD Arrow logo, Radeon, Ryzen, RDNA and combinations thereof are trademarks of Advanced Micro Devices, Inc. Other product names used in +this publication are for identification purposes only and may be trademarks of their respective companies. + +Visual Studio, DirectX and Windows are registered trademarks of Microsoft Corporation in the US and other jurisdictions. + +Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc. + +Python is a registered trademark of the PSF. The Python logos (in several variants) are use trademarks of the PSF as well. + +CMake is a registered trademark of Kitware, Inc. + +Qt and the Qt logo are registered trademarks of the Qt Company Ltd and/or its subsidiaries worldwide. + + +© 2020 Advanced Micro Devices, Inc. All rights reserved. diff --git a/build/dependency_map.py b/build/dependency_map.py new file mode 100644 index 0000000..ae292bf --- /dev/null +++ b/build/dependency_map.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +# Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. + +import sys + +# prevent generation of .pyc file +sys.dont_write_bytecode = True + +####### Git Dependencies ####### + +# To allow for future updates where we may have cloned the project somewhere other than gerrit, store the root of +# the repo in a variable. In future, we can automatically calculate this based on the git config +gitRoot = "https://github.com/GPUOpen-Tools/" + +# Define a set of dependencies that exist as separate git projects. +# each git dependency has a desired directory where it will be cloned - along with a commit to checkout +gitMapping = { + gitRoot + "QtCommon" : ["../external/qt_common", "v3.6.0"], + gitRoot + "UpdateCheckAPI" : ["../external/update_check_api", "v2.0.0"], +} + diff --git a/build/fetch_dependencies.py b/build/fetch_dependencies.py new file mode 100644 index 0000000..25b5892 --- /dev/null +++ b/build/fetch_dependencies.py @@ -0,0 +1,117 @@ +#! /usr/bin/python +# Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +# +# Script to fetch all external git and/or downloable dependencies needed to build the project +# +# fetch_dependencies.py (--internal) +# +# If --internal is specified, then any additional dependencies required for internal builds will also +# be checked out. +# +# Each git repo will be updated to the commit specified in the "gitMapping" table. + +import os +import subprocess +import sys +import zipfile +import tarfile +import platform +import argparse + +# Check for the python 3.x name and import it as the 2.x name +try: + import urllib.request as urllib +# if that failed, then try the 2.x name +except ImportError: + import urllib + +# to allow the script to be run from anywhere - not just the cwd - store the absolute path to the script file +scriptRoot = os.path.dirname(os.path.realpath(__file__)) + +# also store the basename of the file +scriptName = os.path.basename(__file__) + +# Print a message to the console with appropriate pre-amble +def logPrint(message): + print ("\n" + scriptName + ": " + message) + sys.stdout.flush() + +# add script root to support import of URL and git maps +sys.path.append(scriptRoot) +from dependency_map import gitMapping + +# Clone or update a git repo +def updateGitDependencies(gitMapping, update): + for gitRepo in gitMapping: + # add script directory to path + tmppath = os.path.join(scriptRoot, gitMapping[gitRepo][0]) + + # clean up path, collapsing any ../ and converting / to \ for Windows + path = os.path.normpath(tmppath) + + # required commit + reqdCommit = gitMapping[gitRepo][1] + + doCheckout = False + if not os.path.isdir(path): + # directory doesn't exist - clone from git + logPrint("Directory %s does not exist, using 'git clone' to get latest from %s" % (path, gitRepo)) + p = subprocess.Popen((["git", "clone", gitRepo ,path]), stderr=subprocess.STDOUT) + p.wait() + if(p.returncode == 0): + doCheckout = True + else: + logPrint("git clone failed with return code: %d" % p.returncode) + return False + elif update == True: + # directory exists and update requested - get latest from git + logPrint("Directory %s exists, using 'git fetch --tags' to get latest from %s" % (path, gitRepo)) + p = subprocess.Popen((["git", "fetch", "--tags"]), cwd=path, stderr=subprocess.STDOUT) + p.wait() + if(p.returncode == 0): + doCheckout = True + else: + logPrint("git fetch failed with return code: %d" % p.returncode) + return False + else: + # Directory exists and update not requested + logPrint("Git Dependency %s found and not updated" % gitRepo) + + if doCheckout == True: + logPrint("Checking out required commit: %s" % reqdCommit) + p = subprocess.Popen((["git", "checkout", reqdCommit]), cwd=path, stderr=subprocess.STDOUT) + p.wait() + if(p.returncode != 0): + logPrint("git checkout failed with return code: %d" % p.returncode) + return False + logPrint("Ensuring any branch is on the head using git pull --ff-only origin %s" % reqdCommit) + p = subprocess.Popen((["git", "pull", "--ff-only", "origin", reqdCommit]), cwd=path, stderr=subprocess.STDOUT) + p.wait() + if(p.returncode != 0): + logPrint("git merge failed with return code: %d" % p.returncode) + return False + + return True + +# Main body of update functionality +def doFetchDependencies(update, internal): + # Print git version being used + gitCmd = ["git", "--version"] + gitOutput = subprocess.check_output(gitCmd, stderr=subprocess.STDOUT) + logPrint("%s" % gitOutput) + + # Update all git dependencies + if updateGitDependencies(gitMapping, update): + return True + else: + return False + +if __name__ == '__main__': + # fetch_dependencies.py executed as a script + + # parse the command line arguments + parser = argparse.ArgumentParser(description="A script that fetches all the necessary build dependencies for the project") + parser.add_argument("--internal", action="store_true", help="fetch dependencies required for internal builds of the tool (only used within AMD") + args = parser.parse_args() + + doFetchDependencies(True, args.internal) diff --git a/build/pre_build.py b/build/pre_build.py new file mode 100644 index 0000000..958b249 --- /dev/null +++ b/build/pre_build.py @@ -0,0 +1,344 @@ +#! /usr/bin/python +# Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +# +# Script to perform all necessary pre build steps. This includes: +# +# - Fetching all dependencies +# - Creating output directories +# - Calling CMake with appropriate parameters to generate the build files +# +import os +import sys +import argparse +import shutil +import subprocess +import distutils.spawn +import platform +import time + +# Remember the start time +startTime = time.time() + +# Enable/Disable options supported by this project +support32BitBuild = False + +# prevent fetch_dependency script from leaving a compiled .pyc file in the script directory +sys.dont_write_bytecode = True + +import fetch_dependencies + +# to allow the script to be run from anywhere - not just the cwd - store the absolute path to the script file +scriptRoot = os.path.dirname(os.path.realpath(__file__)) + +# also store the basename of the file +scriptName = os.path.basename(__file__) + +if sys.platform == "win32": + outputRoot = os.path.join(scriptRoot, "win") +elif sys.platform == "darwin": + outputRoot = os.path.join(scriptRoot, "mac") +else: + outputRoot = os.path.join(scriptRoot, "linux") + +# parse the command line arguments +parser = argparse.ArgumentParser(description="A script that generates all the necessary build dependencies for a project") +if sys.platform == "win32": + parser.add_argument("--vs", default="2017", choices=["2017", "2019"], help="specify the version of Visual Studio to be used with this script (default: 2017)") + parser.add_argument("--toolchain", default="2017", choices=["2017", "2019"], help="specify the compiler toolchain to be used with this script (default: 2017)") + parser.add_argument("--qt-root", default="C:\\Qt", help="specify the root directory for locating QT on this system (default: C:\\Qt\\)") +elif sys.platform == "darwin": + parser.add_argument("--xcode", action="store_true", help="specify Xcode should be used as generator for CMake") + parser.add_argument("--no-bundle", action="store_true", help="specify macOS application should be built as standard executable instead of app bundle") + parser.add_argument("--qt-root", default="~/Qt", help="specify the root directory for locating QT on this system (default: ~/Qt) ") +else: + parser.add_argument("--qt-root", default="~/Qt", help="specify the root directory for locating QT on this system (default: ~/Qt) ") +parser.add_argument("--qt", default="5.12.6", help="specify the version of QT to be used with the script (default: 5.12.6)" ) +parser.add_argument("--clean", action="store_true", help="delete any directories created by this script") +parser.add_argument("--no-qt", action="store_true", help="build a headless version (not applicable for all products)") +parser.add_argument("--internal", action="store_true", help="configure internal builds of the tool (only used within AMD") +parser.add_argument("--disable-break", action="store_true", help="disable RGP_DEBUG_BREAK asserts in debug builds") +parser.add_argument("--update", action="store_true", help="Force fetch_dependencies script to update all dependencies") +parser.add_argument("--output", default=outputRoot, help="specify the output location for generated cmake and build output files (default = OS specific subdirectory of location of PreBuild.py script)") +parser.add_argument("--build", action="store_true", help="build all supported configurations on completion of prebuild step") +parser.add_argument("--build-jobs", default="4", help="number of simultaneous jobs to run during a build (default = 4)") +if support32BitBuild: + parser.add_argument("--platform", default="x64", choices=["x64", "x86"], help="specify the platform (32 or 64 bit)") +args = parser.parse_args() + +# Define the build configurations that will be generated +configs = ["debug", "release"] + +# Generate the appropriate suffix to append for internal builds +internalSuffix = "" +if args.internal: + internalSuffix = "-Internal" + +## Define some simple utility functions used lower down in the script + +# Print a message to the console with appropriate pre-amble +def logPrint(message): + print ("\n" + scriptName + ": " + message) + +# Print an error message to the console with appropriate pre-amble then exit +def logErrorAndExit(message): + print ("\nERROR: " + scriptName + ": " + message) + sys.stdout.flush() + sys.exit(-1) + +# Remove a directory and all subdirectories - printing relevant status +def rmdirPrint(dir): + logPrint ("Removing directory - " + dir) + if os.path.exists(dir): + try: + shutil.rmtree(dir) + except Exception as e: + logErrorAndExit ("Failed to delete directory - " + dir + ": " + str(e)) + else: + logPrint (" " + dir + " doesn't exist!") + +# Make a directory if it doesn't exist - print information +def mkdirPrint(dir): + if not os.path.exists(dir): + logPrint ("Creating Directory: " + dir) + os.makedirs(dir) + +# check that the default output directory exists +mkdirPrint(args.output) + +# Define the output directory for CMake generated files +cmakeOutputDir = None + +if sys.platform == "win32": + cmakeOutputDir = os.path.join(args.output, "vs" + args.toolchain) +else: + cmakeOutputDir = os.path.join(args.output, "make") + +if support32BitBuild: + # Always add platform to output folder for 32 bit builds + if args.platform == "x86": + cmakeOutputDir += args.platform + +if sys.platform == "win32": + if args.internal: + cmakeOutputDir += internalSuffix + +# Clean all files generated by this script or the build process +if (args.clean): + logPrint ("Cleaning build ...\n") + # delete the CMake output directory + rmdirPrint(cmakeOutputDir) + # delete the build output directories + for config in configs: + if args.internal: + config += internalSuffix + dir = os.path.join(args.output, config) + rmdirPrint(dir) + sys.exit(0) + +# Call fetch_dependencies script +logPrint ("Fetching project dependencies ...\n") +if (fetch_dependencies.doFetchDependencies(args.update, args.internal) == False): + logErrorAndExit("Unable to retrieve dependencies") + +# Create the CMake output directory +mkdirPrint(cmakeOutputDir) + +# locate the relevant QT libraries +# generate the platform specific portion of the QT path name +if sys.platform == "win32": + qtLeaf = "msvc" + args.toolchain + "_64" +elif sys.platform == "darwin": + qtLeaf = "clang_64" +else: + qtLeaf = "gcc_64" + +# Generate the full path to QT, converting path to OS specific form +# Look for Qt path in specified Qt root directory +# Example: +# --qt-root=C:\\Qt +# --qt=5.9.6 +# Look first for C:\\Qt\\Qt5.9.6\\5.9.6 +# (if not found..) +# Look next for C:\\Qt\\5.9.6 +# +# If neither of those can be found AND we are using the default +# qt-root dir (i.e. the user did not specify --qt-root), then also +# go up one directory from qt-root and check both permutations +# again. This allows the default Qt install path on Linux to be +# found without needing to specify a qt-root + +qtExpandedRoot = os.path.expanduser(args.qt_root) + +qtPathNotFoundError = "Unable to find Qt root dir. Use --qt-root to specify\n Locations checked:" + +qtPath = os.path.normpath(qtExpandedRoot + "/" + "Qt" + args.qt + "/" + args.qt) +if not os.path.exists(qtPath): + qtPathNotFoundError = qtPathNotFoundError + "\n " + qtPath + qtPath = os.path.normpath(qtExpandedRoot + "/" + args.qt) + if not os.path.exists(qtPath): + qtPathNotFoundError = qtPathNotFoundError + "\n " + qtPath + # if there is no user-specified qt-root, then check additional locations + # used by the various Qt installers + if args.qt_root == parser.get_default('qt_root'): + qtPath = os.path.normpath(qtExpandedRoot + "/../" + "Qt" + args.qt + "/" + args.qt) + if not os.path.exists(qtPath): + qtPathNotFoundError = qtPathNotFoundError + "\n " + qtPath + qtPath = os.path.normpath(qtExpandedRoot + "/../" + args.qt) + if not os.path.exists(qtPath): + qtPathNotFoundError = qtPathNotFoundError + "\n " + qtPath + logErrorAndExit(qtPathNotFoundError) + else: + logErrorAndExit(qtPathNotFoundError) + +qtPath = os.path.normpath(qtPath + "/" + qtLeaf) + +if not os.path.exists(qtPath) and not args.no_qt: + logErrorAndExit ("QT Path does not exist - " + qtPath) + +# Specify the type of Build files to generate +qtGenerator = None +if sys.platform == "win32": + if args.vs == "2019": + qtGenerator="Visual Studio 16 2019" + else: + qtGenerator="Visual Studio 15 2017" + if not support32BitBuild or args.platform != "x86": + qtGenerator = qtGenerator + " Win64" + +elif sys.platform == "darwin": + if args.xcode: + qtGenerator="Xcode" + else: + qtGenerator="Unix Makefiles" +else: + qtGenerator="Unix Makefiles" + +# Common code related to generating a build configuration +def generateConfig(config): + if (config != ""): + cmakeDir = os.path.join(cmakeOutputDir, config + internalSuffix) + mkdirPrint(cmakeDir) + else: + cmakeDir = cmakeOutputDir + + cmakelistPath = os.path.join(scriptRoot, os.path.normpath("..")) + + releaseOutputDir = os.path.join(args.output, "release" + internalSuffix) + debugOutputDir = os.path.join(args.output, "debug" + internalSuffix) + + if args.no_qt: + cmakeArgs = ["cmake", cmakelistPath, "-DHEADLESS=TRUE"] + else: + cmakeArgs = ["cmake", cmakelistPath, "-DCMAKE_PREFIX_PATH=" + qtPath, "-G", qtGenerator] + + if sys.platform == "win32": + if args.vs == "2019": + if not support32BitBuild or args.platform != "x86": + cmakeArgs.extend(["-A" + "x64"]) + + if args.toolchain == "2017": + cmakeArgs.extend(["-Tv141"]) + + if args.internal: + cmakeArgs.extend(["-DINTERNAL_BUILD:BOOL=TRUE"]) + + if args.disable_break: + cmakeArgs.extend(["-DDISABLE_RGP_DEBUG_BREAK:BOOL=TRUE"]) + + cmakeArgs.extend(["-DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=" + releaseOutputDir]) + cmakeArgs.extend(["-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE=" + releaseOutputDir]) + cmakeArgs.extend(["-DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=" + debugOutputDir]) + cmakeArgs.extend(["-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG=" + debugOutputDir]) + + if sys.platform != "win32": + if "RELEASE" in config.upper(): + cmakeArgs.extend(["-DCMAKE_BUILD_TYPE=Release"]) + elif "DEBUG" in config.upper(): + cmakeArgs.extend(["-DCMAKE_BUILD_TYPE=Debug"]) + else: + logErrorAndExit("unknown configuration: " + config) + + if sys.platform == "darwin": + cmakeArgs.extend(["-DNO_APP_BUNDLE=" + str(args.no_bundle)]) + + if not distutils.spawn.find_executable(cmakeArgs[0]): + logErrorAndExit("cmake not found") + + p = subprocess.Popen(cmakeArgs, cwd=cmakeDir, stderr=subprocess.STDOUT) + p.wait() + sys.stdout.flush() + if(p.returncode != 0): + logErrorAndExit("cmake failed with %d" % p.returncode) + +logPrint("\nGenerating build files ...\n") +if sys.platform == "win32": + # On Windows always generates both Debug and Release configurations in a single solution file + generateConfig("") +else: + # For Linux & Mac - generate both Release and Debug configurations + for config in configs: + generateConfig(config) + +# Optionally, the user can choose to build all configurations on conclusion of the prebuild job +if (args.build): + for config in configs: + logPrint( "\nBuilding " + config + " configuration\n") + if sys.platform == "win32": + # Define the path to the Visual Studio directory that contains the VsDevCmd.bat file + vsDevPath = os.path.normpath ("C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/Common7/Tools/") + vsDevCommand = "VsDevCmd.bat" + msBuildCommand = "msbuild /nodeReuse:false /m:" + args.build_jobs + " /t:Build /p:Configuration=" + config + " /verbosity:minimal " + + # search in cmakeOutputDir for a Visual Studio solution file - we assume there is only 1 solution file in this directory + + solutionFile = None + for file in os.listdir(cmakeOutputDir): + if file.endswith('.sln'): + solutionFile = file + + if solutionFile == None: + logErrorAndExit("Unable to find solution file in location: " + cmakeOutputDir) + + # Specify the solution to be used for the Windows build + solution = os.path.normpath(os.path.join(cmakeOutputDir, solutionFile)) + + # Build it + # All 3 of these commands need to be called like this to ensure the environment propagates through to the final call to msbuild + if os.system("c:" + " & cd " + vsDevPath + " & " + vsDevCommand + " & " + msBuildCommand + solution) != 0: + logErrorAndExit(config + " build failed for " + solution) + + # Build the documentation + documentationProject = os.path.join(cmakeOutputDir, "Documentation.vcxproj") + if os.system("c:" + "& cd " + vsDevPath + " & " + vsDevCommand + " & " + msBuildCommand + documentationProject) != 0: + logErrorAndExit(config + " build failed for " + documentationProject) + + else: + # linux & mac use the same commands + + # generate the path to the config specific makefile + makeDir = os.path.join(cmakeOutputDir, config + internalSuffix) + + makeArgs = ["make", "-j" + args.build_jobs, "-C", makeDir] + + # Build it + logPrint ("Building configuration: " + config) + p = subprocess.Popen(makeArgs, stderr=subprocess.STDOUT) + p.wait() + sys.stdout.flush() + if(p.returncode != 0): + logErrorAndExit("make failed with %d" % p.returncode) + + makeArgs.extend(["Documentation"]) + + # Build the documentation + logPrint ("Building documentation for configuration: " + config) + p = subprocess.Popen(makeArgs, stderr=subprocess.STDOUT) + p.wait() + sys.stdout.flush() + if(p.returncode != 0): + logErrorAndExit("make Documentation failed with %d" % p.returncode) + + +minutes, seconds = divmod(time.time() - startTime, 60) +logPrint("Successfully completed in {0:.0f} minutes, {1:.1f} seconds".format(minutes,seconds)) +sys.exit(0) diff --git a/build/qt.conf b/build/qt.conf new file mode 100644 index 0000000..e9318be --- /dev/null +++ b/build/qt.conf @@ -0,0 +1,2 @@ +[Paths] +Prefix = qt diff --git a/documentation/License.htm b/documentation/License.htm new file mode 100644 index 0000000..9243f16 --- /dev/null +++ b/documentation/License.htm @@ -0,0 +1,132 @@ + + + + + + + + + + + +
+ +

MIT +License

+ +

 

+ +

Copyright +(c) 2017-2020 Advanced Micro Devices, Inc.

+ +

 

+ +

Permission +is hereby granted, free of charge, to any person obtaining a copy

+ +

of +this software and associated documentation files (the "Software"), to +deal

+ +

in +the Software without restriction, including without limitation the rights

+ +

to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell

+ +

copies +of the Software, and to permit persons to whom the Software is

+ +

furnished +to do so, subject to the following conditions:

+ +

 

+ +

The +above copyright notice and this permission notice shall be included in

+ +

all +copies or substantial portions of the Software.

+ +

 

+ +

THE +SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR

+ +

IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+ +

FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+ +

AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+ +

LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+ +

OUT +OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

+ +

THE +SOFTWARE.

+ +

 

+ +
+ + + + diff --git a/samples/sampleTrace.rmv b/samples/sampleTrace.rmv new file mode 100644 index 0000000..406b05a Binary files /dev/null and b/samples/sampleTrace.rmv differ diff --git a/source/assets/amd_logo.svg b/source/assets/amd_logo.svg new file mode 100644 index 0000000..b21be68 --- /dev/null +++ b/source/assets/amd_logo.svg @@ -0,0 +1,126 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/assets/browse_back_disabled.svg b/source/assets/browse_back_disabled.svg new file mode 100644 index 0000000..7a1d716 --- /dev/null +++ b/source/assets/browse_back_disabled.svg @@ -0,0 +1,72 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/source/assets/browse_back_normal.svg b/source/assets/browse_back_normal.svg new file mode 100644 index 0000000..25dc5b8 --- /dev/null +++ b/source/assets/browse_back_normal.svg @@ -0,0 +1,72 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/source/assets/browse_back_pressed.svg b/source/assets/browse_back_pressed.svg new file mode 100644 index 0000000..962c59f --- /dev/null +++ b/source/assets/browse_back_pressed.svg @@ -0,0 +1,72 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/source/assets/browse_fwd_disabled.svg b/source/assets/browse_fwd_disabled.svg new file mode 100644 index 0000000..877ea00 --- /dev/null +++ b/source/assets/browse_fwd_disabled.svg @@ -0,0 +1,72 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/source/assets/browse_fwd_normal.svg b/source/assets/browse_fwd_normal.svg new file mode 100644 index 0000000..4c25990 --- /dev/null +++ b/source/assets/browse_fwd_normal.svg @@ -0,0 +1,72 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/source/assets/browse_fwd_pressed.svg b/source/assets/browse_fwd_pressed.svg new file mode 100644 index 0000000..6c3092d --- /dev/null +++ b/source/assets/browse_fwd_pressed.svg @@ -0,0 +1,72 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/source/assets/rmv_icon_32x32.png b/source/assets/rmv_icon_32x32.png new file mode 100644 index 0000000..e061cde Binary files /dev/null and b/source/assets/rmv_icon_32x32.png differ diff --git a/source/assets/rmv_icon_48x48.png b/source/assets/rmv_icon_48x48.png new file mode 100644 index 0000000..6fea34f Binary files /dev/null and b/source/assets/rmv_icon_48x48.png differ diff --git a/source/assets/stop_128x128.png b/source/assets/stop_128x128.png new file mode 100644 index 0000000..e5fd494 Binary files /dev/null and b/source/assets/stop_128x128.png differ diff --git a/source/assets/third_party/ionicons/LICENSE b/source/assets/third_party/ionicons/LICENSE new file mode 100644 index 0000000..18ab118 --- /dev/null +++ b/source/assets/third_party/ionicons/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Drifty (http://drifty.com/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/source/assets/third_party/ionicons/link.png b/source/assets/third_party/ionicons/link.png new file mode 100644 index 0000000..de24d24 Binary files /dev/null and b/source/assets/third_party/ionicons/link.png differ diff --git a/source/assets/third_party/ionicons/search_icon.png b/source/assets/third_party/ionicons/search_icon.png new file mode 100644 index 0000000..90d13d5 Binary files /dev/null and b/source/assets/third_party/ionicons/search_icon.png differ diff --git a/source/assets/third_party/ionicons/warning.svg b/source/assets/third_party/ionicons/warning.svg new file mode 100644 index 0000000..693460a --- /dev/null +++ b/source/assets/third_party/ionicons/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/assets/zoom_in.svg b/source/assets/zoom_in.svg new file mode 100644 index 0000000..502f4c6 --- /dev/null +++ b/source/assets/zoom_in.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/source/assets/zoom_in_disabled.svg b/source/assets/zoom_in_disabled.svg new file mode 100644 index 0000000..e0a872d --- /dev/null +++ b/source/assets/zoom_in_disabled.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/source/assets/zoom_out.svg b/source/assets/zoom_out.svg new file mode 100644 index 0000000..0e58684 --- /dev/null +++ b/source/assets/zoom_out.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/source/assets/zoom_out_disabled.svg b/source/assets/zoom_out_disabled.svg new file mode 100644 index 0000000..f26f555 --- /dev/null +++ b/source/assets/zoom_out_disabled.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/source/assets/zoom_reset.svg b/source/assets/zoom_reset.svg new file mode 100644 index 0000000..f56df1d --- /dev/null +++ b/source/assets/zoom_reset.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/source/assets/zoom_reset_disabled.svg b/source/assets/zoom_reset_disabled.svg new file mode 100644 index 0000000..7d39619 --- /dev/null +++ b/source/assets/zoom_reset_disabled.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/source/assets/zoom_to_selection.svg b/source/assets/zoom_to_selection.svg new file mode 100644 index 0000000..55944dd --- /dev/null +++ b/source/assets/zoom_to_selection.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/source/assets/zoom_to_selection_disabled.svg b/source/assets/zoom_to_selection_disabled.svg new file mode 100644 index 0000000..c6f83f0 --- /dev/null +++ b/source/assets/zoom_to_selection_disabled.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/source/backend/CMakeLists.txt b/source/backend/CMakeLists.txt new file mode 100644 index 0000000..90a640e --- /dev/null +++ b/source/backend/CMakeLists.txt @@ -0,0 +1,81 @@ +cmake_minimum_required(VERSION 3.5.1) +project(RmvBackend) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +include_directories(AFTER ../backend ../parser) + +IF(UNIX) + # Remove warnings for Linux in backend + add_compile_options(-std=c++11 -D_LINUX -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-sign-compare -Wno-uninitialized) +ENDIF(UNIX) + +# List of all source files. It may be possible to have the build process call cmake to update the makefiles +# only when this file has changed (ie source files have been added or removed) + +set( SOURCES + "rmt_adapter_info.cpp" + "rmt_adapter_info.h" + "rmt_atomic.cpp" + "rmt_atomic.h" + "rmt_configuration.h" + "rmt_data_profile.h" + "rmt_data_set.cpp" + "rmt_data_set.h" + "rmt_data_set.h" + "rmt_data_snapshot.cpp" + "rmt_data_snapshot.h" + "rmt_data_timeline.cpp" + "rmt_data_timeline.h" + "rmt_job_system.cpp" + "rmt_job_system.h" + "rmt_linear_buffer.cpp" + "rmt_linear_buffer.h" + "rmt_mutex.cpp" + "rmt_mutex.h" + "rmt_page_table.cpp" + "rmt_page_table.h" + "rmt_physical_allocation_list.cpp" + "rmt_physical_allocation_list.h" + "rmt_pool.cpp" + "rmt_pool.h" + "rmt_process_map.cpp" + "rmt_process_map.h" + "rmt_process_start_info.h" + "rmt_resource_history.cpp" + "rmt_resource_history.h" + "rmt_resource_list.cpp" + "rmt_resource_list.h" + "rmt_segment_info.h" + "rmt_thread.cpp" + "rmt_thread.h" + "rmt_thread_event.cpp" + "rmt_thread_event.h" + "rmt_virtual_allocation_list.cpp" + "rmt_virtual_allocation_list.h" + "rmt_warnings.cpp" + "rmt_warnings.h" +) + +set( LINUX_SOURCES + "../third_party/pevents/pevents.cpp" + "../third_party/pevents/pevents.h" +) + +# specify output library name +IF(WIN32) + add_library(${PROJECT_NAME} ${SOURCES}) +ELSEIF(UNIX) + add_library(${PROJECT_NAME} ${SOURCES} ${LINUX_SOURCES}) +ENDIF(WIN32) + +set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) + +IF(WIN32) +# Create Visual Studio filters so that the source files in the project match the directory structure +foreach(source IN LISTS SOURCES) + get_filename_component(source_path "${source}" PATH) + string(REPLACE "/" "\\" source_path_msvc "${source_path}") + source_group("${source_path_msvc}" FILES "${source}") +endforeach() +ENDIF(WIN32) + diff --git a/source/backend/rmt_adapter_info.cpp b/source/backend/rmt_adapter_info.cpp new file mode 100644 index 0000000..62ed999 --- /dev/null +++ b/source/backend/rmt_adapter_info.cpp @@ -0,0 +1,38 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief The adapter information captured for the target process. +//============================================================================= + +#include "rmt_adapter_info.h" +#include "rmt_assert.h" + +const char* RmtAdapterInfoGetVideoMemoryType(const RmtAdapterInfo* adapter_info) +{ + RMT_ASSERT(adapter_info); + + switch (adapter_info->memory_type) + { + case kRmtAdapterInfoMemoryTypeUnknown: + return "Unknown"; + case kRmtAdapterInfoMemoryTypeDdR2: + return "DDR2"; + case kRmtAdapterInfoMemoryTypeDdR3: + return "DDR3"; + case kRmtAdapterInfoMemoryTypeDdR4: + return "DDR4"; + case kRmtAdapterInfoMemoryTypeGddR5: + return "GDDR5"; + case kRmtAdapterInfoMemoryTypeGddR6: + return "GDDR6"; + case kRmtAdapterInfoMemoryTypeHbm: + return "HBM"; + case kRmtAdapterInfoMemoryTypeHbm2: + return "HBM2"; + case kRmtAdapterInfoMemoryTypeHbm3: + return "HBM3"; + + default: + return ""; + } +} diff --git a/source/backend/rmt_adapter_info.h b/source/backend/rmt_adapter_info.h new file mode 100644 index 0000000..2f39a05 --- /dev/null +++ b/source/backend/rmt_adapter_info.h @@ -0,0 +1,61 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief The adapter information captured for the target process. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_ADAPTER_INFO_H_ +#define RMV_BACKEND_RMT_ADAPTER_INFO_H_ + +#include +#include +#include +#include + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// An enumeration of memory types that can be used with an adapter. +typedef enum RmtAdapterInfoMemoryType +{ + kRmtAdapterInfoMemoryTypeUnknown = 0, ///< Memory is unknown. + kRmtAdapterInfoMemoryTypeDdR2 = 1, ///< + kRmtAdapterInfoMemoryTypeDdR3 = 2, ///< + kRmtAdapterInfoMemoryTypeDdR4 = 3, ///< . + kRmtAdapterInfoMemoryTypeGddR5 = 4, ///< Graphics DDR 5. + kRmtAdapterInfoMemoryTypeGddR6 = 5, ///< Graphics DDR 6. + kRmtAdapterInfoMemoryTypeHbm = 6, ///< First version of High-Bandwidth Memory. + kRmtAdapterInfoMemoryTypeHbm2 = 7, ///< Second version of High-Bandwidth Memory. + kRmtAdapterInfoMemoryTypeHbm3 = 8 ///< Third version of High-Bandwidth Memory. +} RmtAdapterInfoMemoryType; + +/// A structure encapsulating the information about the adapter. +typedef struct RmtAdapterInfo +{ + char name[RMT_MAX_ADAPTER_NAME_LENGTH]; ///< The name of the adapter as a NULL terminated string. + uint32_t pcie_family_id; ///< The PCIe family ID of the adapater. + uint32_t pcie_revision_id; ///< The PCIe revision ID of the adapter. + uint32_t device_id; ///< The PCIe device ID of the adapter. + uint32_t minimum_engine_clock; ///< The minimum engine clock (in MHz). + uint32_t maximum_engine_clock; ///< The maximum engine clock (in MHz). + RmtAdapterInfoMemoryType memory_type; ///< The memory type. + uint32_t memory_operations_per_clock; ///< The number of memory operations that can be performed per clock. + uint32_t memory_bus_width; ///< The width of the memory bus (in bits). + uint32_t memory_bandwidth; ///< Bandwidth of the memory system (in MB/s). + uint32_t minimum_memory_clock; ///< The minimum memory clock (in MHz). + uint32_t maximum_memory_clock; ///< The maximum memory clock (in MHz). +} RmtAdapterInfo; + +/// Get the chip type (in string) of the video memory. +/// +/// @param [in] adapter_info A pointer to a RmtAdapterInfo structure. +/// +/// @returns +/// Pointer to the memory chip type string. +const char* RmtAdapterInfoGetVideoMemoryType(const RmtAdapterInfo* adapter_info); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_ADAPTER_INFO_H_ diff --git a/source/backend/rmt_atomic.cpp b/source/backend/rmt_atomic.cpp new file mode 100644 index 0000000..179e08a --- /dev/null +++ b/source/backend/rmt_atomic.cpp @@ -0,0 +1,84 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of atomic helper functions. +//============================================================================= + +#include +#include "rmt_atomic.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include + +uint64_t RmtThreadAtomicRead(volatile uint64_t* address) +{ + return InterlockedOr(address, 0); +} + +uint64_t RmtThreadAtomicWrite(volatile uint64_t* address, uint64_t value) +{ + return InterlockedExchange(address, value); +} + +uint64_t RmtThreadAtomicOr(volatile uint64_t* address, uint64_t value) +{ + return InterlockedOr(address, value); +} + +int32_t RmtThreadAtomicAdd(volatile int32_t* address, int32_t value) +{ + RMT_STATIC_ASSERT(sizeof(LONG) == sizeof(int32_t)); + return InterlockedAdd((volatile LONG*)address, (LONG)value); +} + +int64_t RmtThreadAtomicAdd64(volatile int64_t* address, int64_t value) +{ + RMT_STATIC_ASSERT(sizeof(LONG64) == sizeof(uint64_t)); + return InterlockedAdd64((volatile LONG64*)address, (LONG64)value); +} + +int64_t RmtThreadAtomicMax64(volatile int64_t* address, int64_t value) +{ + RMT_STATIC_ASSERT(sizeof(LONG64) == sizeof(uint64_t)); + + int64_t previous_value = *address; + while (previous_value < value && InterlockedCompareExchange64(address, value, previous_value) != previous_value) + { + ; + } + + return value; +} + +#else +// Linux-specific atomic functions. Uses the extensions provided by gcc +// The memory order parameter. Set to "sequentially consistent". See +static const int memorder = __ATOMIC_SEQ_CST; +uint64_t RmtThreadAtomicRead(volatile uint64_t* address) +{ + return __atomic_or_fetch(address, 0, memorder); +} + +uint64_t RmtThreadAtomicWrite(volatile uint64_t* address, uint64_t value) +{ + return __atomic_exchange_n(address, value, memorder); +} + +uint64_t RmtThreadAtomicOr(volatile uint64_t* address, uint64_t value) +{ + return __atomic_or_fetch(address, value, memorder); +} + +int32_t RmtThreadAtomicAdd(volatile int32_t* address, int32_t value) +{ + return __atomic_add_fetch(address, value, memorder); +} + +int64_t RmtThreadAtomicAdd64(volatile int64_t* address, int64_t value) +{ + return __atomic_add_fetch(address, value, memorder); +} + +#endif // #ifdef _WIN32 diff --git a/source/backend/rmt_atomic.h b/source/backend/rmt_atomic.h new file mode 100644 index 0000000..4e418b5 --- /dev/null +++ b/source/backend/rmt_atomic.h @@ -0,0 +1,76 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Definition of platform-abstracted atomic functions. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_ATOMIC_H_ +#define RMV_BACKEND_RMT_ATOMIC_H_ + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// Atomically read a 64bit value from a specified address. +/// +/// @param [in] address The address of the 64bit value to read. +/// +/// @returns +/// The 64bit value at the specified address. +/// +uint64_t RmtThreadAtomicRead(volatile uint64_t* address); + +/// Atomically write a 64bit value from a specified address. +/// +/// @param [in] address The address of the 64bit value to write. +/// @param [in] value The 64bit value to write to address. +/// +/// @returns +/// The 64bit value that was written to the specified address. +/// +uint64_t RmtThreadAtomicWrite(volatile uint64_t* address, uint64_t value); + +/// Atomically OR a 64bit value into the 64bit value at a specified address. +/// +/// @param [in] address The address of the 64bit value to OR with. +/// @param [in] value The 64bit value to OR with the value at address. +/// +/// @returns +/// The 64bit value that resulted from the OR operation to the specified address. +/// +uint64_t RmtThreadAtomicOr(volatile uint64_t* address, uint64_t value); + +/// Atomically add a 32bit value into the 32bit value at a specified address. +/// +/// @param [in] address The address of the 32bit value to add with. +/// @param [in] value The 32bit value to add to the value at address. +/// +/// @returns +/// The 32bit value that resulted from adding value with the value at address. +/// +int32_t RmtThreadAtomicAdd(volatile int32_t* address, int32_t value); + +/// Atomically add a 64bit value into the 64bit value at a specified address. +/// +/// @param [in] address The address of the 64bit value to add with. +/// @param [in] value The 64bit value to add to the value at address. +/// +/// @returns +/// The 64bit value that resulted from adding value with the value at address. +/// +int64_t RmtThreadAtomicAdd64(volatile int64_t* address, int64_t value); + +/// Atomically max a 64bit value into the 64bit value at a specified address. +/// +/// @param [in] address The address of the 64bit value to max with. +/// @param [in] value The 64bit value to max against the value at address. +/// +/// @returns +/// The 64bit value that resulted from adding value with the value at address. +/// +int64_t RmtThreadAtomicMax64(volatile int64_t* address, int64_t value); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_ATOMIC_H_ diff --git a/source/backend/rmt_configuration.h b/source/backend/rmt_configuration.h new file mode 100644 index 0000000..de72c01 --- /dev/null +++ b/source/backend/rmt_configuration.h @@ -0,0 +1,34 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Global configuration values for the RMT backend. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_CONFIGURATION_H_ +#define RMV_BACKEND_RMT_CONFIGURATION_H_ + +/// The maximum length of a name. +#define RMT_MAXIMUM_NAME_LENGTH (1024) + +/// The maximum number of segments per process. +#define RMT_MAXIMUM_SEGMENTS (8) + +/// The maximum number of entries for a 64GiB memory map at 4KiB page size. +#define RMT_PAGE_TABLE_MAX_SIZE (16777216) + +/// The maximum number of processes that a single RMT file will contain. +#define RMT_MAXIMUM_PROCESS_COUNT (1024) + +/// The maximum number of snapshot points a single RMT file will contain. +#define RMT_MAXIMUM_SNAPSHOT_POINTS (1024) + +/// The maximum nubmer of file path. +#define RMT_MAXIMUM_FILE_PATH (8192) + +/// The maximum number of resource history events that can be analyzed. +#define RMT_MAXIMUM_RESOURCE_HISTORY_EVENTS (32768) + +/// The maximum number of physical addresses that can be associated with a single resource history. +#define RMT_MAXIMUM_RESOURCE_PHYSICAL_ADDRESSES (32768) + +#endif // #ifndef RMV_BACKEND_RMT_CONFIGURATION_H_ diff --git a/source/backend/rmt_data_profile.h b/source/backend/rmt_data_profile.h new file mode 100644 index 0000000..8770f61 --- /dev/null +++ b/source/backend/rmt_data_profile.h @@ -0,0 +1,37 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Structures for profiling the RMT data for future parsing. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_DATA_PROFILE_H_ +#define RMV_BACKEND_RMT_DATA_PROFILE_H_ + +#include +#include "rmt_configuration.h" +#include + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A structure encapsulating a profile of an RMT file. +typedef struct RmtDataProfile +{ + int32_t process_count; ///< The number of processes seen in the RMT streams. + int32_t stream_count; ///< The number of RMT streams. + int32_t snapshot_count; ///< The number of snapshots in the RMT streams. + int32_t snapshot_name_count; ///< The number of bytes required to store all snapshot names. + int32_t current_virtual_allocation_count; + int32_t max_virtual_allocation_count; ///< The maximum number of allocations seen concurrently. + int32_t current_resource_count; + int32_t max_concurrent_resources; ///< The maximum number of resources seen concurrently. + int32_t total_resource_count; ///< The total number of resources seent at any time. + int32_t total_virtual_allocation_count; ///< The total number of virtual allocations seen at any time. + +} RmtDataProfile; + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_DATA_PROFILE_H_ diff --git a/source/backend/rmt_data_set.cpp b/source/backend/rmt_data_set.cpp new file mode 100644 index 0000000..c75d917 --- /dev/null +++ b/source/backend/rmt_data_set.cpp @@ -0,0 +1,1798 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of functions for working with a data set. +//============================================================================= + +#include "rmt_data_set.h" +#include "rmt_data_timeline.h" +#include +#include +#include // for memcpy() +#include // for malloc() / free() +#include "rmt_linear_buffer.h" +#include "rmt_data_snapshot.h" +#include "rmt_resource_history.h" +#include +#include +#include + +// Define this on to print the tokens to console. + +#ifndef _WIN32 +#include "linux/safe_crt.h" +#include // for offsetof macro. +#else +#define WIN32_LEAN_AND_MEAN +#include +#ifdef PRINT_TOKENS +#include +#endif // #ifdef PRINT_TOKENS +#endif + +// this buffer is used by the parsers to read chunks of data into RAM for processing. The larger this +// buffer the better the parsing performance, but the larger the memory footprint. +// +// NOTE: If we new the total stream count ahead of time, we could divide this more intelligently. +// From very quick tests you probably don't want to go less than 128KB per stream. +static uint8_t s_file_read_buffer[16 * 1024 * 1024]; + +// create a stream for the RMT chunk +static RmtErrorCode ParseRmtDataChunk(RmtDataSet* data_set, RmtFileChunkHeader* file_chunk) +{ + RMT_ASSERT(data_set); + RMT_ASSERT(file_chunk); + + // read the RmtFileChunkRmtData from the file. + RmtFileChunkRmtData data_chunk; + const size_t read_size = fread(&data_chunk, 1, sizeof(RmtFileChunkRmtData), (FILE*)data_set->file_handle); + RMT_ASSERT(read_size == sizeof(RmtFileChunkRmtData)); + RMT_RETURN_ON_ERROR(read_size == sizeof(RmtFileChunkRmtData), RMT_ERROR_MALFORMED_DATA); + + const int32_t offset = ftell((FILE*)data_set->file_handle); + const int32_t size = file_chunk->size_in_bytes - (sizeof(RmtFileChunkRmtData) + sizeof(RmtFileChunkHeader)); + + // ignore 0 sized chunks. + if (size == 0) + { + return RMT_OK; + } + + // create an RMT parser for this stream with a file handle and offset. + RmtParser* parser = &data_set->streams[data_set->stream_count]; + const RmtErrorCode error_code = RmtParserInitialize(parser, + (FILE*)data_set->file_handle, + offset, + size, + s_file_read_buffer + (data_set->stream_count * (sizeof(s_file_read_buffer) / RMT_MAXIMUM_STREAMS)), + (sizeof(s_file_read_buffer) / RMT_MAXIMUM_STREAMS), + file_chunk->version_major, + file_chunk->version_minor, + data_set->stream_count, + data_chunk.process_id, + data_chunk.thread_id); + + // set the target process. + if (data_chunk.process_id != 0 && data_set->target_process_id == 0) + { + data_set->target_process_id = data_chunk.process_id; + } + + RMT_UNUSED(error_code); + // read for next allocation. + data_set->stream_count++; + + return RMT_OK; +} + +// handle setting up segment info chunks. +static RmtErrorCode ParseSegmentInfoChunk(RmtDataSet* data_set, RmtFileChunkHeader* current_file_chunk) +{ + RMT_UNUSED(current_file_chunk); + + RMT_ASSERT((data_set->segment_info_count + 1) < RMT_MAXIMUM_SEGMENTS); + RMT_RETURN_ON_ERROR((data_set->segment_info_count + 1) < RMT_MAXIMUM_SEGMENTS, RMT_ERROR_INVALID_SIZE); + + // Read the RmtSegmentInfo from the file. + RmtFileChunkSegmentInfo segment_info_chunk; + const size_t read_size = fread(&segment_info_chunk, 1, sizeof(RmtFileChunkSegmentInfo), (FILE*)data_set->file_handle); + RMT_ASSERT(read_size == sizeof(RmtFileChunkSegmentInfo)); + RMT_RETURN_ON_ERROR(read_size == sizeof(RmtFileChunkSegmentInfo), RMT_ERROR_MALFORMED_DATA); + + // Fill out the segment info. + RmtSegmentInfo* next_segment_info = &data_set->segment_info[data_set->segment_info_count++]; + next_segment_info->base_address = segment_info_chunk.base_address; + next_segment_info->size = segment_info_chunk.size_in_bytes; + next_segment_info->heap_type = (RmtHeapType)segment_info_chunk.heap_type; + next_segment_info->index = segment_info_chunk.memory_index; + return RMT_OK; +} + +// handle setting up process start info. +static RmtErrorCode ParseProcessStartInfo(RmtDataSet* data_set, RmtFileChunkHeader* current_file_chunk) +{ + RMT_UNUSED(current_file_chunk); + + RMT_ASSERT((data_set->process_start_info_count + 1) < RMT_MAXIMUM_PROCESS_COUNT); + RMT_RETURN_ON_ERROR((data_set->process_start_info_count + 1) < RMT_MAXIMUM_PROCESS_COUNT, RMT_ERROR_INVALID_SIZE); + + RmtProcessStartInfo* next_process_start_info = &data_set->process_start_info[data_set->process_start_info_count++]; + next_process_start_info->process_id = 0; + next_process_start_info->physical_memory_allocated = 0; + return RMT_OK; +} + +// handle reading in any static adapter info. +static RmtErrorCode ParseAdapterInfoChunk(RmtDataSet* data_set, RmtFileChunkHeader* current_file_chunk) +{ + RMT_UNUSED(current_file_chunk); + + RmtFileChunkAdapterInfo adapter_info_chunk; + const size_t read_size = fread(&adapter_info_chunk, 1, sizeof(RmtFileChunkAdapterInfo), (FILE*)data_set->file_handle); + RMT_ASSERT(read_size == sizeof(RmtFileChunkAdapterInfo)); + RMT_RETURN_ON_ERROR(read_size == sizeof(RmtFileChunkAdapterInfo), RMT_ERROR_MALFORMED_DATA); + + // these should always match. + RMT_STATIC_ASSERT(sizeof(adapter_info_chunk.name) == sizeof(data_set->adapter_info.name)); + + // fill out adapter info. + memcpy(data_set->adapter_info.name, adapter_info_chunk.name, RMT_MAX_ADAPTER_NAME_LENGTH); + data_set->adapter_info.pcie_family_id = adapter_info_chunk.pcie_family_id; + data_set->adapter_info.pcie_revision_id = adapter_info_chunk.pcie_revision_id; + data_set->adapter_info.device_id = adapter_info_chunk.device_id; + data_set->adapter_info.minimum_engine_clock = adapter_info_chunk.minimum_engine_clock; + data_set->adapter_info.maximum_engine_clock = adapter_info_chunk.maximum_engine_clock; + data_set->adapter_info.memory_type = (RmtAdapterInfoMemoryType)adapter_info_chunk.memory_type; + data_set->adapter_info.memory_operations_per_clock = adapter_info_chunk.memory_operations_per_clock; + data_set->adapter_info.memory_bus_width = adapter_info_chunk.memory_bus_width; + data_set->adapter_info.memory_bandwidth = adapter_info_chunk.memory_bandwidth; + data_set->adapter_info.minimum_memory_clock = adapter_info_chunk.minimum_memory_clock; + data_set->adapter_info.maximum_memory_clock = adapter_info_chunk.maximum_memory_clock; + return RMT_OK; +} + +// handle reading a snapshot. +static RmtErrorCode ParseSnapshotInfoChunk(RmtDataSet* data_set, RmtFileChunkHeader* current_file_chunk) +{ + RMT_UNUSED(current_file_chunk); + + RmtFileChunkSnapshotInfo snapshot_info_chunk; + + const size_t file_offset = ftell((FILE*)data_set->file_handle); + size_t read_size = fread(&snapshot_info_chunk, 1, sizeof(RmtFileChunkSnapshotInfo), (FILE*)data_set->file_handle); + RMT_ASSERT(read_size == sizeof(RmtFileChunkSnapshotInfo)); + RMT_RETURN_ON_ERROR(read_size == sizeof(RmtFileChunkSnapshotInfo), RMT_ERROR_MALFORMED_DATA); + + // Allocate some buffer in the snapshot names. + const int32_t snapshot_index = data_set->snapshot_count; + if (snapshot_index >= RMT_MAXIMUM_SNAPSHOT_POINTS) + { + return RMT_ERROR_OUT_OF_MEMORY; + } + + // ignore snapshots of 0-length name, these are deleted snapshots. + if (snapshot_info_chunk.name_length_in_bytes == 0) + { + return RMT_OK; + } + + // read the name into the snapshot point. + const size_t capped_name_length = RMT_MINIMUM(RMT_MAXIMUM_NAME_LENGTH, snapshot_info_chunk.name_length_in_bytes); + void* name_buffer = (void*)&data_set->snapshots[snapshot_index].name; + read_size = fread(name_buffer, 1, capped_name_length, (FILE*)data_set->file_handle); + RMT_ASSERT(read_size == capped_name_length); + RMT_RETURN_ON_ERROR(read_size == capped_name_length, RMT_ERROR_MALFORMED_DATA); + + // set the time. + data_set->snapshots[snapshot_index].timestamp = snapshot_info_chunk.snapshot_time; + data_set->snapshots[snapshot_index].file_offset = file_offset; + data_set->snapshot_count++; + return RMT_OK; +} + +// helper function to parse the chunks of the RMT file into the data set. +static RmtErrorCode ParseChunks(RmtDataSet* data_set) +{ + data_set->stream_count = 0; + data_set->segment_info_count = 0; + data_set->process_start_info_count = 0; + + RmtFileParser rmt_file_parser; + RmtErrorCode error_code = RmtFileParserCreateFromHandle(&rmt_file_parser, (FILE*)data_set->file_handle); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // Check if file is supported + error_code = RmtFileParserIsFileSupported(&rmt_file_parser.header); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // process all the chunks in the rmt file. + RmtFileChunkHeader* current_file_chunk = NULL; + while (RmtFileParserParseNextChunk(&rmt_file_parser, ¤t_file_chunk) == RMT_OK) + { + // ensure that the chunk is valid to read. + RMT_ASSERT(current_file_chunk); + if (current_file_chunk == nullptr) + { + break; + } + + if ((size_t)rmt_file_parser.next_chunk_offset > data_set->file_size_in_bytes) + { + return RMT_ERROR_MALFORMED_DATA; + } + + if (((size_t)current_file_chunk->size_in_bytes < sizeof(RmtFileChunkHeader)) || + ((size_t)current_file_chunk->size_in_bytes > data_set->file_size_in_bytes)) + { + return RMT_ERROR_MALFORMED_DATA; + } + + // depending on the type of chunk, handle pre-processing it. + switch (current_file_chunk->chunk_identifier.chunk_type) + { + case kRmtFileChunkTypeRmtData: + error_code = ParseRmtDataChunk(data_set, current_file_chunk); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + + case kRmtFileChunkTypeSegmentInfo: + error_code = ParseSegmentInfoChunk(data_set, current_file_chunk); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + + case kRmtFileChunkTypeProcessStart: + error_code = ParseProcessStartInfo(data_set, current_file_chunk); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + + case kRmtFileChunkTypeAdapterInfo: + error_code = ParseAdapterInfoChunk(data_set, current_file_chunk); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + + case kRmtFileChunkTypeSnapshotInfo: + error_code = ParseSnapshotInfoChunk(data_set, current_file_chunk); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + + default: + break; + } + } + + // initialize the token heap for k-way merging. + error_code = RmtStreamMergerInitialize(&data_set->stream_merger, data_set->streams, data_set->stream_count); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // rebase any snapshot times to be relative to the minimum timestamp. + for (int32_t current_snapshot_index = 0; current_snapshot_index < data_set->snapshot_count; ++current_snapshot_index) + { + data_set->snapshots[current_snapshot_index].timestamp -= data_set->stream_merger.minimum_start_timestamp; + } + + return RMT_OK; +} + +static void BuildDataProfileParseUserdata(RmtDataSet* data_set, const RmtToken* current_token) +{ + RMT_ASSERT(current_token->type == kRmtTokenTypeUserdata); + + const RmtTokenUserdata* userdata = (const RmtTokenUserdata*)¤t_token->userdata_token; + + if (userdata->userdata_type != kRmtUserdataTypeSnapshot) + { + return; // we only care about snapshots. + } + + data_set->data_profile.snapshot_count++; + data_set->data_profile.snapshot_name_count += userdata->size_in_bytes + 1; // +1 for /0 +} + +static void BuildDataProfileParseProcessEvent(RmtDataSet* data_set, const RmtToken* current_token) +{ + RMT_ASSERT(current_token->type == kRmtTokenTypeProcessEvent); + + const RmtTokenProcessEvent* process_event = (const RmtTokenProcessEvent*)¤t_token->process_event_token; + + if (process_event->event_type != kRmtProcessEventTypeStart) + { + return; // we only care about process start. + } + + // Add to the process map. + RmtProcessMapAddProcess(&data_set->process_map, process_event->common.process_id); + data_set->data_profile.process_count++; +} + +static void BuildDataProfileParseVirtualFree(RmtDataSet* data_set, const RmtToken* current_token) +{ + RMT_ASSERT(current_token->type == kRmtTokenTypeVirtualFree); + + data_set->data_profile.current_virtual_allocation_count--; +} + +static void BuildDataProfileParseVirtualAllocate(RmtDataSet* data_set, const RmtToken* current_token) +{ + RMT_ASSERT(current_token->type == kRmtTokenTypeVirtualAllocate); + + data_set->data_profile.current_virtual_allocation_count++; + data_set->data_profile.total_virtual_allocation_count++; + data_set->data_profile.max_virtual_allocation_count = + RMT_MAXIMUM(data_set->data_profile.max_virtual_allocation_count, data_set->data_profile.current_virtual_allocation_count); +} + +static void BuildDataProfileParseResourceCreate(RmtDataSet* data_set, const RmtToken* current_token) +{ + RMT_ASSERT(current_token->type == kRmtTokenTypeResourceCreate); + + data_set->data_profile.current_resource_count++; + data_set->data_profile.total_resource_count++; + data_set->data_profile.max_concurrent_resources = + RMT_MAXIMUM(data_set->data_profile.max_concurrent_resources, data_set->data_profile.current_resource_count); + + // Add one to the allocation count if the resource being created is a shareable image, since we might need to create a dummy allocation token + // if we don't see one in the token stream. + if ((current_token->resource_create_token.resource_type == kRmtResourceTypeImage) && + ((current_token->resource_create_token.image.create_flags & kRmtImageCreationFlagShareable) == kRmtImageCreationFlagShareable)) + { + data_set->data_profile.current_virtual_allocation_count++; + data_set->data_profile.total_virtual_allocation_count++; + data_set->data_profile.max_virtual_allocation_count = + RMT_MAXIMUM(data_set->data_profile.max_virtual_allocation_count, data_set->data_profile.current_virtual_allocation_count); + } +} + +static void BuildDataProfileParseResourceDestroy(RmtDataSet* data_set, const RmtToken* current_token) +{ + RMT_ASSERT(current_token->type == kRmtTokenTypeResourceDestroy); + + data_set->data_profile.current_resource_count--; +} + +// Build a data profile which can be used by all subsequent parsing. +static RmtErrorCode BuildDataProfile(RmtDataSet* data_set) +{ + // get the stream count from the loader, and initialize all the counters + data_set->data_profile.stream_count = data_set->stream_count; + data_set->data_profile.process_count = data_set->process_start_info_count; + data_set->data_profile.snapshot_count = 0; + data_set->data_profile.snapshot_name_count = 0; + data_set->maximum_timestamp = 0; + + // Push processes into the process map. + for (int32_t current_process_start_index = 0; current_process_start_index < data_set->process_start_info_count; ++current_process_start_index) + { + RmtProcessMapAddProcess(&data_set->process_map, data_set->process_start_info[current_process_start_index].process_id); + } + + // if the heap has something there, then add it. + while (!RmtStreamMergerIsEmpty(&data_set->stream_merger)) + { + // grab the next token from the heap. + RmtToken current_token; + const RmtErrorCode error_code = RmtStreamMergerAdvance(&data_set->stream_merger, ¤t_token); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + data_set->maximum_timestamp = RMT_MAXIMUM(data_set->maximum_timestamp, current_token.common.timestamp); + + // process the token. + switch (current_token.type) + { + case kRmtTokenTypeUserdata: + BuildDataProfileParseUserdata(data_set, ¤t_token); + break; + + case kRmtTokenTypeProcessEvent: + BuildDataProfileParseProcessEvent(data_set, ¤t_token); + break; + + case kRmtTokenTypeVirtualFree: + BuildDataProfileParseVirtualFree(data_set, ¤t_token); + break; + + case kRmtTokenTypeVirtualAllocate: + BuildDataProfileParseVirtualAllocate(data_set, ¤t_token); + break; + + case kRmtTokenTypeResourceCreate: + BuildDataProfileParseResourceCreate(data_set, ¤t_token); + break; + + case kRmtTokenTypeResourceDestroy: + BuildDataProfileParseResourceDestroy(data_set, ¤t_token); + break; + + default: + break; + } + } + + data_set->cpu_frequency = data_set->streams[0].cpu_frequency; + + // Create an allocator for the token heap to use for generating unique resource IDs + size_t size_required = (data_set->data_profile.total_resource_count * sizeof(ResourceIdMapNode)) + sizeof(ResourceIdMapAllocator); + + void* data = calloc(size_required, 1); + RMT_ASSERT(data != nullptr); + data_set->p_resource_id_map_allocator = static_cast(data); + data_set->p_resource_id_map_allocator->allocation_base = (void*)(static_cast(data) + sizeof(ResourceIdMapAllocator)); + data_set->p_resource_id_map_allocator->allocation_size = size_required - sizeof(ResourceIdMapAllocator); + data_set->p_resource_id_map_allocator->resource_count = 0; + data_set->stream_merger.allocator = data_set->p_resource_id_map_allocator; + return RMT_OK; +} + +// Helper function call the correct allocation function. +static void* PerformAllocation(RmtDataSet* data_set, size_t size_in_bytes, size_t alignment) +{ + if (data_set->allocate_func == nullptr) + { + return malloc(size_in_bytes); + } + + return (data_set->allocate_func)(size_in_bytes, alignment); +} + +// Helper functo call the correct free function. +static void PerformFree(RmtDataSet* data_set, void* pointer) +{ + if (data_set->free_func == nullptr) + { + return free(pointer); + } + + return (data_set->free_func)(pointer); +} + +// Allocate memory for a snapshot. +static RmtErrorCode AllocateMemoryForSnapshot(RmtDataSet* data_set, RmtDataSnapshot* out_snapshot) +{ + // Set a pointer to parent data set. + out_snapshot->data_set = data_set; + + // Initialize the virtual allocation list. + const size_t virtual_allocation_buffer_size = + RmtVirtualAllocationListGetBufferSize(data_set->data_profile.total_virtual_allocation_count, data_set->data_profile.max_concurrent_resources + 200); + if (virtual_allocation_buffer_size > 0) + { + out_snapshot->virtual_allocation_buffer = PerformAllocation(data_set, virtual_allocation_buffer_size, sizeof(uint32_t)); + RMT_ASSERT(out_snapshot->virtual_allocation_buffer); + RMT_RETURN_ON_ERROR(out_snapshot->virtual_allocation_buffer, RMT_ERROR_OUT_OF_MEMORY); + const RmtErrorCode error_code = RmtVirtualAllocationListInitialize(&out_snapshot->virtual_allocation_list, + out_snapshot->virtual_allocation_buffer, + virtual_allocation_buffer_size, + data_set->data_profile.max_virtual_allocation_count, + data_set->data_profile.max_concurrent_resources + 200, + data_set->data_profile.total_virtual_allocation_count); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + + // create the resource list. + const size_t resource_list_buffer_size = RmtResourceListGetBufferSize(data_set->data_profile.max_concurrent_resources + 200); + if (resource_list_buffer_size > 0) + { + out_snapshot->resource_list_buffer = PerformAllocation(data_set, resource_list_buffer_size, sizeof(uint32_t)); + RMT_ASSERT(out_snapshot->resource_list_buffer); + RMT_RETURN_ON_ERROR(out_snapshot->resource_list_buffer, RMT_ERROR_OUT_OF_MEMORY); + const RmtErrorCode error_code = RmtResourceListInitialize(&out_snapshot->resource_list, + out_snapshot->resource_list_buffer, + resource_list_buffer_size, + &out_snapshot->virtual_allocation_list, + data_set->data_profile.max_concurrent_resources + 200); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + + // initialize the region stack + out_snapshot->region_stack_buffer = NULL; + out_snapshot->region_stack_count = 0; + + return RMT_OK; +} + +// consume next RMT token for snapshot generation. +static RmtErrorCode ProcessTokenForSnapshot(RmtDataSet* data_set, RmtToken* current_token, RmtDataSnapshot* out_snapshot) +{ + RmtErrorCode error_code = RMT_OK; + + RMT_UNUSED(data_set); + + switch (current_token->type) + { + case kRmtTokenTypeVirtualFree: + { +#ifdef PRINT_TOKENS + char buf[128]; + sprintf_s(buf, "%lld - VIRTUAL_FREE - 0x%010llx\n", current_token->common.timestamp, current_token->virtual_free_token.virtual_address); + OutputDebugString(buf); +#endif + + error_code = RmtVirtualAllocationListRemoveAllocation(&out_snapshot->virtual_allocation_list, current_token->virtual_free_token.virtual_address); + RMT_ASSERT(error_code == RMT_OK); + //RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + break; + + case kRmtTokenTypePageTableUpdate: + { +#ifdef PRINT_TOKENS + char buf[128]; + sprintf_s(buf, + "%lld - PAGE_TABLE_UPDATE - [0x%010llx..0x%010llx] => [0x%010llx..0x%010llx] (%lld * %lld) %d %s\n", + current_token->common.timestamp, + current_token->page_table_update_token.virtual_address, + current_token->page_table_update_token.virtual_address + + (current_token->page_table_update_token.size_in_pages * rmtGetPageSize(current_token->page_table_update_token.page_size)), + current_token->page_table_update_token.physical_address, + current_token->page_table_update_token.physical_address + + (current_token->page_table_update_token.size_in_pages * rmtGetPageSize(current_token->page_table_update_token.page_size)), + current_token->page_table_update_token.size_in_pages, + rmtGetPageSize(current_token->page_table_update_token.page_size), + current_token->page_table_update_token.update_type, + current_token->page_table_update_token.is_unmapping ? "UM" : "M"); + OutputDebugString(buf); +#endif + + if (!current_token->page_table_update_token.is_unmapping) + { + const uint64_t size_in_bytes = + current_token->page_table_update_token.size_in_pages * RmtGetPageSize(current_token->page_table_update_token.page_size); + RmtProcessMapAddCommittedMemoryForProcessId(&out_snapshot->process_map, current_token->common.process_id, size_in_bytes); + } + else + { + const uint64_t size_in_bytes = + current_token->page_table_update_token.size_in_pages * RmtGetPageSize(current_token->page_table_update_token.page_size); + RmtProcessMapRemoveCommittedMemoryForProcessId(&out_snapshot->process_map, current_token->common.process_id, size_in_bytes); + } + + // Filter is done in the page table such that we only build it for target PID. + error_code = RmtPageTableUpdateMemoryMappings(&out_snapshot->page_table, + current_token->page_table_update_token.virtual_address, + current_token->page_table_update_token.physical_address, + current_token->page_table_update_token.size_in_pages, + current_token->page_table_update_token.page_size, + current_token->page_table_update_token.is_unmapping, + current_token->page_table_update_token.update_type, + current_token->common.process_id); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + break; + + case kRmtTokenTypeUserdata: + { + if (current_token->userdata_token.userdata_type == kRmtUserdataTypeName) + { + RmtResource* found_resource = NULL; + error_code = RmtResourceListGetResourceByResourceId( + &out_snapshot->resource_list, current_token->userdata_token.resource_identifer, (const RmtResource**)&found_resource); + if (error_code == RMT_OK) + { + memcpy(found_resource->name, + current_token->userdata_token.payload, + RMT_MINIMUM(current_token->userdata_token.size_in_bytes, RMT_MAXIMUM_NAME_LENGTH)); + } + } + +#ifdef PRINT_TOKENS +#endif + } + break; + + case kRmtTokenTypeMisc: + { +#ifdef PRINT_TOKENS + char buf[128]; + sprintf_s(buf, "%lld - MISC - %s\n", current_token->common.timestamp, rmtGetMiscTypeNameFromMiscType(current_token->misc_token.type)); + OutputDebugString(buf); +#endif + } + break; + + case kRmtTokenTypeResourceReference: + { + error_code = RmtVirtualAllocationListAddResourceReference(&out_snapshot->virtual_allocation_list, + current_token->common.timestamp, + current_token->resource_reference.virtual_address, + current_token->resource_reference.residency_update_type, + current_token->resource_reference.queue); + //RMT_ASSERT(error_code == RMT_OK); + //RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + break; + + case kRmtTokenTypeResourceBind: + { +#ifdef PRINT_TOKENS + char buf[128]; + sprintf_s(buf, + "%lld - RESOURCE_BIND - %lld 0x%010llx %lld\n", + current_token->common.timestamp, + current_token->resource_bind_token.resource_identifier, + current_token->resource_bind_token.virtual_address, + current_token->resource_bind_token.size_in_bytes); + OutputDebugString(buf); +#endif + + error_code = RmtResourceListAddResourceBind(&out_snapshot->resource_list, ¤t_token->resource_bind_token); + + if (error_code == RMT_ERROR_SHARED_ALLOCATION_NOT_FOUND) + { + // This is not a true error, it just means that we encountered a shareable resource without the matching virtual + // alloc token. This is an expected case as that allocation is owned outside the target process, so we'll add + // the allocation to the list so future resource tokens can find it. + static const RmtHeapType kDummyHeapPref[4] = {kRmtHeapTypeInvisible, kRmtHeapTypeInvisible, kRmtHeapTypeInvisible, kRmtHeapTypeInvisible}; + error_code = RmtVirtualAllocationListAddAllocation(&out_snapshot->virtual_allocation_list, + current_token->common.timestamp, + current_token->resource_bind_token.virtual_address, + (int32_t)(current_token->resource_bind_token.size_in_bytes >> 12), + kDummyHeapPref, + RmtOwnerType::kRmtOwnerTypeClientDriver); + } + else if (error_code == RMT_ERROR_RESOURCE_ALREADY_BOUND) + { + // duplicate the command allocator resource we have at this resource ID this is because command allocators are + // bound to multiple chunks of virtual address space simultaneously. + const RmtResource* matching_resource = NULL; + error_code = RmtResourceListGetResourceByResourceId( + &out_snapshot->resource_list, current_token->resource_bind_token.resource_identifier, &matching_resource); + if (error_code == RMT_OK) + { + // form the token. + RmtTokenResourceCreate resource_create_token; + resource_create_token.resource_identifier = matching_resource->identifier; + resource_create_token.owner_type = matching_resource->owner_type; + resource_create_token.commit_type = matching_resource->commit_type; + resource_create_token.resource_type = kRmtResourceTypeCommandAllocator; + memcpy(&resource_create_token.common, ¤t_token->common, sizeof(RmtTokenCommon)); + memcpy(&resource_create_token.command_allocator, &matching_resource->command_allocator, sizeof(RmtResourceDescriptionCommandAllocator)); + + // Create the resource. + error_code = RmtResourceListAddResourceCreate(&out_snapshot->resource_list, &resource_create_token); + RMT_ASSERT(error_code == RMT_OK); + + if (!(current_token->resource_bind_token.is_system_memory && current_token->resource_bind_token.virtual_address == 0)) + { + error_code = RmtResourceListAddResourceBind(&out_snapshot->resource_list, ¤t_token->resource_bind_token); + RMT_ASSERT(error_code == RMT_OK); + } + } + } + + RMT_ASSERT(error_code == RMT_OK); + //RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + break; + + case kRmtTokenTypeProcessEvent: + { + if (current_token->process_event_token.event_type != kRmtProcessEventTypeStart) + { + break; // we only care about process start. + } + + // Add to the process map. + RmtProcessMapAddProcess(&data_set->process_map, current_token->common.process_id); + } + break; + + case kRmtTokenTypePageReference: + { + } + break; + + case kRmtTokenTypeCpuMap: + { +#ifdef PRINT_TOKENS + char buf[128]; + sprintf_s(buf, "%lld - CPU_MAP - 0x%010llx\n", current_token->common.timestamp, current_token->cpu_map_token.virtual_address); + OutputDebugString(buf); +#endif + + if (current_token->cpu_map_token.is_unmap) + { + error_code = RmtVirtualAllocationListAddCpuUnmap( + &out_snapshot->virtual_allocation_list, current_token->common.timestamp, current_token->cpu_map_token.virtual_address); + } + else + { + error_code = RmtVirtualAllocationListAddCpuMap( + &out_snapshot->virtual_allocation_list, current_token->common.timestamp, current_token->cpu_map_token.virtual_address); + } + + RMT_ASSERT(error_code == RMT_OK); + //RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + break; + + case kRmtTokenTypeVirtualAllocate: + { +#ifdef PRINT_TOKENS + char buf[128]; + sprintf_s(buf, + "%lld - VIRTUAL_ALLOCATE - 0x%010llx %lld\n", + current_token->common.timestamp, + current_token->virtual_allocate_token.virtual_address, + current_token->virtual_allocate_token.size_in_bytes); + OutputDebugString(buf); +#endif + + error_code = RmtVirtualAllocationListAddAllocation(&out_snapshot->virtual_allocation_list, + current_token->common.timestamp, + current_token->virtual_allocate_token.virtual_address, + (int32_t)(current_token->virtual_allocate_token.size_in_bytes >> 12), + current_token->virtual_allocate_token.preference, + current_token->virtual_allocate_token.owner_type); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + break; + + case kRmtTokenTypeResourceCreate: + { +#ifdef PRINT_TOKENS + char buf[128]; + sprintf_s(buf, + "%lld - RESOURCE_CREATE - %lld %d", + current_token->common.timestamp, + current_token->resource_create_token.resource_identifier, + current_token->resource_create_token.resource_type); + OutputDebugString(buf); +#endif + + error_code = RmtResourceListAddResourceCreate(&out_snapshot->resource_list, ¤t_token->resource_create_token); + RMT_ASSERT(error_code == RMT_OK); + +#ifdef PRINT_TOKENS + if (error_code != RMT_OK) + { + OutputDebugString(" - FAILED\n"); + } + else + { + OutputDebugString("\n"); + } +#endif + //RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + break; + + case kRmtTokenTypeResourceDestroy: + { +#ifdef PRINT_TOKENS + char buf[128]; + sprintf_s(buf, + "%lld - RESOURCE_DESTROY - %lld", + current_token->resource_destroy_token.common.timestamp, + current_token->resource_destroy_token.resource_identifier); + OutputDebugString(buf); +#endif + error_code = RmtResourceListAddResourceDestroy(&out_snapshot->resource_list, ¤t_token->resource_destroy_token); + //RMT_ASSERT(error_code == RMT_OK); + +#ifdef PRINT_TOKENS + if (error_code != RMT_OK) + { + OutputDebugString(" - FAILED\n"); + } + else + { + OutputDebugString("\n"); + } +#endif + //RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + break; + + default: + break; + } + + return RMT_OK; +} + +// helper function that mirrors the .bak file to the original. +static void CommitTemporaryFileEdits(RmtDataSet* data_set, bool remove_temporary) +{ + RMT_ASSERT(data_set); + if (data_set->read_only) + { + return; + } +#ifdef _WIN32 + // On Windows, filesystem metadata updates are atomic. Therefore, if we + // rename the temporary file we created during initialize to the original, + // we should gaurntee we always safely have a valid RMT file. + if (data_set->file_handle != nullptr) + { + fflush((FILE*)data_set->file_handle); + fclose((FILE*)data_set->file_handle); + data_set->file_handle = NULL; + } + + if (remove_temporary) + { + bool success = MoveFileEx(data_set->temporary_file_path, data_set->file_path, MOVEFILE_REPLACE_EXISTING); + RMT_ASSERT(success); + } + else + { + // for a mirror without remove, we need to recopy the temp + bool success = MoveFileEx(data_set->temporary_file_path, data_set->file_path, MOVEFILE_REPLACE_EXISTING); + RMT_ASSERT(success); + success = CopyFile(data_set->file_path, data_set->temporary_file_path, FALSE); + RMT_ASSERT(success); + + data_set->file_handle = NULL; + errno_t error_no = fopen_s((FILE**)&data_set->file_handle, data_set->temporary_file_path, "rb+"); + RMT_ASSERT(data_set->file_handle); + RMT_ASSERT(error_no == 0); + } +#else + RMT_UNUSED(remove_temporary); + // If we want this on Linux implement it here. +#endif +} + +// Is the 'path' file read only +static bool IsFileReadOnly(const char* path) +{ +#ifdef _WIN32 + DWORD file_attributes = GetFileAttributes(path); + return ((file_attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY); +#else + RMT_UNUSED(path); + // Linux implementation + return false; +#endif +} + +// initialize the data set by reading the header chunks, and setting up the streams. +RmtErrorCode RmtDataSetInitialize(const char* path, RmtDataSet* data_set) +{ + RMT_ASSERT(path); + RMT_ASSERT(data_set); + RMT_RETURN_ON_ERROR(path, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + + // copy the path + const size_t path_length = strlen(path); + memcpy(data_set->file_path, path, RMT_MINIMUM(RMT_MAXIMUM_FILE_PATH, path_length)); + memcpy(data_set->temporary_file_path, path, RMT_MINIMUM(RMT_MAXIMUM_FILE_PATH, path_length)); + + data_set->file_handle = NULL; + data_set->read_only = false; + errno_t error_no; + + if (IsFileReadOnly(path)) + { + data_set->read_only = true; + } + else + { + // copy the entire input file to a temporary. +#ifdef _WIN32 + strcat_s(data_set->temporary_file_path, ".bak"); + CopyFile(data_set->file_path, data_set->temporary_file_path, FALSE); +#else + // If we want to implement similar safety for linux. Then we should copy the file here. + // instead we just copy the file name into the temporary path, and operate directly on + // the raw file. +#endif + // Load the data from the RMT file. + error_no = fopen_s((FILE**)&data_set->file_handle, data_set->temporary_file_path, "rb+"); + if ((data_set->file_handle == nullptr) || error_no != 0) + { + data_set->read_only = true; + } + } + + if (data_set->read_only) + { + // file is read-only so just read the original rmv trace file + error_no = fopen_s((FILE**)&data_set->file_handle, data_set->file_path, "rb"); + if ((data_set->file_handle == nullptr) || error_no != 0) + { + return RMT_ERROR_FILE_NOT_OPEN; + } + } + + // get the size of the file. + const size_t current_stream_offset = ftell((FILE*)data_set->file_handle); + fseek((FILE*)data_set->file_handle, 0L, SEEK_END); + data_set->file_size_in_bytes = ftell((FILE*)data_set->file_handle); + fseek((FILE*)data_set->file_handle, (int32_t)current_stream_offset, SEEK_SET); + if (data_set->file_size_in_bytes == 0U) + { + fclose((FILE*)data_set->file_handle); + data_set->file_handle = NULL; + return RMT_ERROR_FILE_NOT_OPEN; + } + + // check that the file is larger enough at least for the RMT file header. + if (data_set->file_size_in_bytes < sizeof(RmtFileHeader)) + { + return RMT_ERROR_FILE_NOT_OPEN; + } + + // parse all the chunk headers from the file. + RmtErrorCode error_code = ParseChunks(data_set); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // construct the data profile for subsequent data parsing. + error_code = BuildDataProfile(data_set); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + return RMT_OK; +} + +// destroy the data set. +RmtErrorCode RmtDataSetDestroy(RmtDataSet* data_set) +{ + // flush writes and close the handle. + fflush((FILE*)data_set->file_handle); + fclose((FILE*)data_set->file_handle); + data_set->file_handle = NULL; + + CommitTemporaryFileEdits(data_set, true); + + data_set->file_handle = NULL; + return RMT_OK; +} + +// get the number of series from the timeline type +static int32_t GetSeriesCountFromTimelineType(const RmtDataSet* data_set, RmtDataTimelineType timeline_type) +{ + // NOTE: We always have 2 additional buckets, one for unallocated physical pages + // and one for physical pages belonging to any other process. + const int32_t additional_buckets_for_non_process_grouping_mode = 2; + + switch (timeline_type) + { + case kRmtDataTimelineTypeProcess: + return data_set->process_map.process_count; + + case kRmtDataTimelineTypePageSize: + return kRmtPageSizeReserved0 + additional_buckets_for_non_process_grouping_mode; + + case kRmtDataTimelineTypeCommitted: + return kRmtHeapTypeCount; + + case kRmtDataTimelineTypeResourceUsageCount: + return kRmtResourceUsageTypeCount; // NOTE: should be top 7..8..9 with 1 bucket for "other" use a bitfield to test inclusion in top 8. + + case kRmtDataTimelineTypeResourceUsageVirtualSize: + return kRmtResourceUsageTypeCount; + + case kRmtDataTimelineTypePaging: + return 1; // NOTE: Could be per driver model, could also be done per heap. + + case kRmtDataTimelineTypeVirtualMemory: + return kRmtHeapTypeCount; + + case kRmtDataTimelineTypeResourceNonPreferred: + return kRmtResourceUsageTypeCount; + + default: + return 0; + } +} + +// calculate the grouping value for a collection +static int32_t UpdateSeriesValuesFromCurrentSnapshot(const RmtDataSnapshot* current_snapshot, + RmtDataTimelineType timeline_type, + int32_t last_value_index, + RmtDataTimeline* out_timeline) +{ + // calculate the index within the level-0 series for the value + const int32_t value_index = RmtDataSetGetSeriesIndexForTimestamp(current_snapshot->data_set, current_snapshot->timestamp); + + // Smeer from lastValueIndex until valueIndex if its >1 step away. + if (last_value_index > 0) + { + for (int32_t current_value_index = last_value_index + 1; current_value_index < value_index; ++current_value_index) + { + for (int32_t current_series_index = 0; current_series_index < out_timeline->series_count; ++current_series_index) + { + const uint64_t value = out_timeline->series[current_series_index].levels[0].values[last_value_index]; + out_timeline->series[current_series_index].levels[0].values[current_value_index] = value; + } + } + } + + // handle the values for this timeline type. + switch (timeline_type) + { + case kRmtDataTimelineTypeProcess: + { + for (int32_t current_process_index = 0; current_process_index < current_snapshot->process_map.process_count; ++current_process_index) + { + const uint64_t comitted_memory_for_process = current_snapshot->process_map.process_committed_memory[current_process_index]; + out_timeline->series[current_process_index].levels[0].values[value_index] = comitted_memory_for_process; + } + } + break; + + case kRmtDataTimelineTypeCommitted: + { + for (int32_t current_heap_type_index = 0; current_heap_type_index < kRmtHeapTypeCount; ++current_heap_type_index) + { + const uint64_t heap_type_count = current_snapshot->page_table.mapped_per_heap[current_heap_type_index]; + out_timeline->series[current_heap_type_index].levels[0].values[value_index] = heap_type_count; + } + } + break; + + case kRmtDataTimelineTypeResourceUsageCount: + { + for (int32_t current_resource_index = 0; current_resource_index < kRmtResourceUsageTypeCount; ++current_resource_index) + { + const int32_t resource_count_for_usage_type = current_snapshot->resource_list.resource_usage_count[current_resource_index]; + + // Write this to the correct slot in the series. + if (current_resource_index == kRmtResourceUsageTypeHeap) + { + out_timeline->series[current_resource_index].levels[0].values[value_index] = 0; + } + else + { + out_timeline->series[current_resource_index].levels[0].values[value_index] = resource_count_for_usage_type; + } + } + } + break; + + case kRmtDataTimelineTypeResourceUsageVirtualSize: + { + for (int32_t current_resource_index = 0; current_resource_index < kRmtResourceUsageTypeCount; ++current_resource_index) + { + const uint64_t resource_size_for_usage_type = current_snapshot->resource_list.resource_usage_size[current_resource_index]; + + // Write this to the correct slot in the series. + if (current_resource_index == kRmtResourceUsageTypeHeap) + { + out_timeline->series[current_resource_index].levels[0].values[value_index] = 0; + } + else + { + out_timeline->series[current_resource_index].levels[0].values[value_index] = resource_size_for_usage_type; + } + } + } + break; + + case kRmtDataTimelineTypeVirtualMemory: + { + for (int32_t current_heap_type_index = 0; current_heap_type_index < kRmtHeapTypeCount; ++current_heap_type_index) + { + const uint64_t heap_type_count = current_snapshot->virtual_allocation_list.allocations_per_preferred_heap[current_heap_type_index]; + out_timeline->series[current_heap_type_index].levels[0].values[value_index] = heap_type_count; + } + } + break; + + default: + break; + } + + // Sum the values in each series, and take the max against maximumValueInAllSeries + uint64_t total_for_all_series = 0; + for (int32_t current_series_index = 0; current_series_index < out_timeline->series_count; ++current_series_index) + { + total_for_all_series += out_timeline->series[current_series_index].levels[0].values[value_index]; + } + + // track the max. + out_timeline->maximum_value_in_all_series = RMT_MAXIMUM(out_timeline->maximum_value_in_all_series, total_for_all_series); + + return value_index; +} + +// Allocate memory for the stuff we counted in the RmtDataProfile. +static RmtErrorCode TimelineGeneratorAllocateMemory(RmtDataSet* data_set, RmtDataTimelineType timeline_type, RmtDataTimeline* out_timeline) +{ + out_timeline->series_count = GetSeriesCountFromTimelineType(data_set, timeline_type); + RMT_ASSERT(out_timeline->series_count < RMT_MAXIMUM_TIMELINE_DATA_SERIES); + + const int32_t values_per_top_level_series = RmtDataSetGetSeriesIndexForTimestamp(data_set, data_set->maximum_timestamp) + 1; + const uint64_t buffer_size = values_per_top_level_series * sizeof(uint64_t); + const size_t series_memory_buffer_size = buffer_size * out_timeline->series_count; + out_timeline->series_memory_buffer = (int32_t*)PerformAllocation(data_set, series_memory_buffer_size, sizeof(uint64_t)); + RMT_ASSERT(out_timeline->series_memory_buffer); + RMT_RETURN_ON_ERROR(out_timeline->series_memory_buffer, RMT_ERROR_OUT_OF_MEMORY); + + // zero the entire buffer. + memset(out_timeline->series_memory_buffer, 0, series_memory_buffer_size); + + uint64_t current_series_memory_buffer_start_offset = 0; + for (int32_t current_series_index = 0; current_series_index < out_timeline->series_count; ++current_series_index) + { + // Work out what we needed and increment it. + const uintptr_t buffer_address = (uintptr_t)out_timeline->series_memory_buffer + current_series_memory_buffer_start_offset; + out_timeline->series[current_series_index].levels[0].values = (uint64_t*)buffer_address; + out_timeline->series[current_series_index].level_count = 1; + out_timeline->series[current_series_index].levels[0].value_count = values_per_top_level_series; + + // Move the buffer along to the next process. + current_series_memory_buffer_start_offset += buffer_size; + } + + return RMT_OK; +} + +// Load the data into the structures we have allocated. +static RmtErrorCode TimelineGeneratorParseData(RmtDataSet* data_set, RmtDataTimelineType timeline_type, RmtDataTimeline* out_timeline) +{ + RMT_ASSERT(data_set); + + // Allocate temporary snapshot. + RmtDataSnapshot* temp_snapshot = (RmtDataSnapshot*)PerformAllocation(data_set, sizeof(RmtDataSnapshot), alignof(RmtDataSnapshot)); + RMT_ASSERT(temp_snapshot); + RMT_RETURN_ON_ERROR(temp_snapshot, RMT_ERROR_OUT_OF_MEMORY); + RmtErrorCode error_code = AllocateMemoryForSnapshot(data_set, temp_snapshot); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // initialize this. + temp_snapshot->maximum_physical_memory_in_bytes = 0; + + // initialize the page table. + error_code = RmtPageTableInitialize(&temp_snapshot->page_table, data_set->segment_info, data_set->segment_info_count, data_set->target_process_id); + RMT_ASSERT(error_code == RMT_OK); + + // initialize the process map. + error_code = RmtProcessMapInitialize(&temp_snapshot->process_map); + RMT_ASSERT(error_code == RMT_OK); + + // Special case: + // for timeline type of process, we have to first fill the 0th value of level 0 + // of each series with the total amount of committed memory from the process start + // information. + if (timeline_type == kRmtDataTimelineTypeProcess) + { + for (int32_t current_process_start_index = 0; current_process_start_index < data_set->process_start_info_count; ++current_process_start_index) + { + int32_t series_index = -1; + RmtProcessMapGetIndexFromProcessId(&data_set->process_map, data_set->process_start_info[current_process_start_index].process_id, &series_index); + RMT_ASSERT(series_index >= 0); + + const uint64_t value = data_set->process_start_info[current_process_start_index].physical_memory_allocated; + + // Write the value for the process start to 0th value of 0th level each series. + out_timeline->series[current_process_start_index].levels[0].values[0] = value; + } + } + + RmtStreamMergerReset(&data_set->stream_merger); + + // if the heap has something there, then add it. + int32_t last_value_index = -1; + while (!RmtStreamMergerIsEmpty(&data_set->stream_merger)) + { + // grab the next token from the heap. + RmtToken current_token; + error_code = RmtStreamMergerAdvance(&data_set->stream_merger, ¤t_token); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // Update the temporary snapshot with the RMT token. + error_code = ProcessTokenForSnapshot(data_set, ¤t_token, temp_snapshot); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // set the timestamp for the current snapshot + temp_snapshot->timestamp = current_token.common.timestamp; + + // Generate whatever series values we need for current timeline type from the snapshot. + last_value_index = UpdateSeriesValuesFromCurrentSnapshot(temp_snapshot, timeline_type, last_value_index, out_timeline); + } + + // clean up temporary structures we allocated to construct the timeline. + RmtDataSnapshotDestroy(temp_snapshot); + PerformFree(data_set, temp_snapshot); + + return RMT_OK; +} + +// calculate mip-maps for all levels of all series +static RmtErrorCode TimelineGeneratorCalculateSeriesLevels(RmtDataTimeline* out_timeline) +{ + RMT_UNUSED(out_timeline); + + return RMT_OK; +} + +// function to generate a timeline. +RmtErrorCode RmtDataSetGenerateTimeline(RmtDataSet* data_set, RmtDataTimelineType timeline_type, RmtDataTimeline* out_timeline) +{ + RMT_ASSERT(data_set); + RMT_ASSERT(out_timeline); + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_timeline, RMT_ERROR_INVALID_POINTER); + + // points at the parent dataset, which has lots of shared data. + out_timeline->data_set = data_set; + out_timeline->max_timestamp = data_set->maximum_timestamp; + out_timeline->timeline_type = timeline_type; + out_timeline->maximum_value_in_all_series = 0; // this will be calculated as we populate the data/generate mipmaps. + + // Allocate the memory we care about for the timeline. + TimelineGeneratorAllocateMemory(data_set, timeline_type, out_timeline); + + // Do the parsing for generating a timeline. + TimelineGeneratorParseData(data_set, timeline_type, out_timeline); + + // Generate mip-map data. + TimelineGeneratorCalculateSeriesLevels(out_timeline); + + return RMT_OK; +} + +// a pass to convert solitary heaps in an allocation into buffers. +static RmtErrorCode SnapshotGeneratorConvertHeapsToBuffers(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot); + + for (int32_t current_resource_index = 0; current_resource_index < snapshot->resource_list.resource_count; ++current_resource_index) + { + RmtResource* current_resource = &snapshot->resource_list.resources[current_resource_index]; + RmtVirtualAllocation* current_virtual_allocation = (RmtVirtualAllocation*)current_resource->bound_allocation; + + if (current_virtual_allocation == nullptr) + { + continue; + } + + // we're only interested in heaps which are the only resource inside an allocation. + if (current_virtual_allocation->resource_count > 1 || current_resource->resource_type != kRmtResourceTypeHeap) + { + continue; + } + + // NOTE: read things out into temporaries as heap and buffer structures are unioned. + const size_t heap_size_in_bytes = current_resource->heap.size; + current_resource->buffer.create_flags = 0; + current_resource->buffer.usage_flags = 0; + current_resource->buffer.size_in_bytes = heap_size_in_bytes; + current_resource->resource_type = kRmtResourceTypeBuffer; + } + + return RMT_OK; +} + +static int32_t ResourceComparator(const void* a, const void* b) +{ + const RmtResource** resource_pointer_a = (const RmtResource**)a; + const RmtResource** resource_pointer_b = (const RmtResource**)b; + const RmtResource* resource_a = *resource_pointer_a; + const RmtResource* resource_b = *resource_pointer_b; + return (resource_a->address > resource_b->address) ? 1 : -1; +} + +// add a list of pointers to resources to each allocation. +static RmtErrorCode SnapshotGeneratorAddResourcePointers(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot); + + // set up the pointer addresses for each allocation. + int32_t current_resource_connectivity_index = 0; + for (int32_t current_virtual_allocation_index = 0; current_virtual_allocation_index < snapshot->virtual_allocation_list.allocation_count; + ++current_virtual_allocation_index) + { + RmtVirtualAllocation* current_virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[current_virtual_allocation_index]; + + if ((current_virtual_allocation->flags & kRmtAllocationDetailIsDead) == kRmtAllocationDetailIsDead) + { + continue; + } + + current_virtual_allocation->resources = &snapshot->virtual_allocation_list.resource_connectivity[current_resource_connectivity_index]; + + // move the index along by the number of resources inside this allocation. + current_resource_connectivity_index += current_virtual_allocation->resource_count; + } + + // iterate over every resource and add pointers to the allocations. + for (int32_t current_resource_index = 0; current_resource_index < snapshot->resource_list.resource_count; ++current_resource_index) + { + RmtResource* current_resource = &snapshot->resource_list.resources[current_resource_index]; + RmtVirtualAllocation* current_virtual_allocation = (RmtVirtualAllocation*)current_resource->bound_allocation; + + if (current_virtual_allocation == nullptr) + { + continue; + } + + // if the bound allocation is marked as dead then we don't want to bother patching up + // its pointers. This is also an indication that we may have a dangling resource. We know + // that the boundAllocation will be invalid after snapshotGeneratorCompactVirtualAllocations + // has completed anyway, so we can easily clear them now. + if ((current_virtual_allocation->flags & kRmtAllocationDetailIsDead) == kRmtAllocationDetailIsDead) + { + current_resource->flags |= kRmtResourceFlagDangling; + current_resource->bound_allocation = NULL; + continue; + } + + RMT_ASSERT(current_virtual_allocation->base_address <= current_resource->address); + + // add the pointer + RmtResource** next_resource_pointer = ¤t_virtual_allocation->resources[current_virtual_allocation->next_resource_index++]; + *next_resource_pointer = current_resource; + } + + // sort the resources into baseAddress order. This will allow subsequent algorithms to + // operate more efficiently as they can make assumptions about the order of the resources + // within a virtual allocation. + for (int32_t current_virtual_allocation_index = 0; current_virtual_allocation_index < snapshot->virtual_allocation_list.allocation_count; + ++current_virtual_allocation_index) + { + RmtVirtualAllocation* current_virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[current_virtual_allocation_index]; + + if ((current_virtual_allocation->flags & kRmtAllocationDetailIsDead) == kRmtAllocationDetailIsDead) + { + continue; + } + + qsort(current_virtual_allocation->resources, current_virtual_allocation->resource_count, sizeof(RmtResource*), ResourceComparator); + } + + return RMT_OK; +} + +// add unbound resources to the virtual allocation, there should be one of these for every gap in the VA +// address space. +static RmtErrorCode SnapshotGeneratorAddUnboundResources(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot); + + // scan the VA range and look for holes in the address space where no resource is bound. + int32_t unbound_region_index = 0; + for (int32_t current_virtual_allocation_index = 0; current_virtual_allocation_index < snapshot->virtual_allocation_list.allocation_count; + ++current_virtual_allocation_index) + { + RmtVirtualAllocation* current_virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[current_virtual_allocation_index]; + + // set the pointer to array of unbound regions to next in sequence. + current_virtual_allocation->unbound_memory_regions = &snapshot->virtual_allocation_list.unbound_memory_regions[unbound_region_index]; + current_virtual_allocation->unbound_memory_region_count = 0; + + // not interested in soon to be compacted out allocations. + if ((current_virtual_allocation->flags & kRmtAllocationDetailIsDead) == kRmtAllocationDetailIsDead) + { + continue; + } + + // scan the list of resources looking for holes. + RmtGpuAddress last_resource_end = current_virtual_allocation->base_address; + for (int32_t current_resource_index = 0; current_resource_index < current_virtual_allocation->resource_count; ++current_resource_index) + { + const RmtResource* current_resource = current_virtual_allocation->resources[current_resource_index]; + + // look for holes in the VA range. + if (current_resource->address > last_resource_end) + { + const uint64_t delta = current_resource->address - last_resource_end; + + // Create the unbound region. + RmtMemoryRegion* next_region = &snapshot->virtual_allocation_list.unbound_memory_regions[unbound_region_index++]; + next_region->offset = last_resource_end - current_virtual_allocation->base_address; + next_region->size = delta; + current_virtual_allocation->unbound_memory_region_count++; + } + + last_resource_end = current_resource->address + current_resource->size_in_bytes; + } + + // check the ending region as a special case. + const RmtGpuAddress end_address = current_virtual_allocation->base_address + RmtVirtualAllocationGetSizeInBytes(current_virtual_allocation); + if (end_address > last_resource_end) + { + const uint64_t delta = end_address - last_resource_end; + + // Create the unbound region. + RmtMemoryRegion* next_region = &snapshot->virtual_allocation_list.unbound_memory_regions[unbound_region_index++]; + next_region->offset = last_resource_end - current_virtual_allocation->base_address; + next_region->size = delta; + current_virtual_allocation->unbound_memory_region_count++; + } + } + + return RMT_OK; +} + +// compact virtual allocations, removing dead ones. +static RmtErrorCode SnapshotGeneratorCompactVirtualAllocations(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot); + + RmtVirtualAllocationListCompact(&snapshot->virtual_allocation_list, true); + + return RMT_OK; +} + +// calculate summary data for snapshot +static RmtErrorCode SnapshotGeneratorCalculateSummary(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot); + + snapshot->minimum_virtual_address = UINT64_MAX; + snapshot->maximum_virtual_address = 0; + snapshot->minimum_allocation_timestamp = UINT64_MAX; + snapshot->maximum_allocation_timestamp = 0; + + for (int32_t current_virtual_allocation_index = 0; current_virtual_allocation_index < snapshot->virtual_allocation_list.allocation_count; + ++current_virtual_allocation_index) + { + RmtVirtualAllocation* current_virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[current_virtual_allocation_index]; + + snapshot->minimum_virtual_address = RMT_MINIMUM(snapshot->minimum_virtual_address, current_virtual_allocation->base_address); + snapshot->maximum_virtual_address = RMT_MAXIMUM( + snapshot->maximum_virtual_address, current_virtual_allocation->base_address + RmtVirtualAllocationGetSizeInBytes(current_virtual_allocation)); + snapshot->minimum_allocation_timestamp = RMT_MINIMUM(snapshot->minimum_allocation_timestamp, current_virtual_allocation->timestamp); + snapshot->maximum_allocation_timestamp = RMT_MAXIMUM(snapshot->maximum_allocation_timestamp, current_virtual_allocation->timestamp); + } + + if (snapshot->minimum_virtual_address == UINT64_MAX) + { + snapshot->minimum_virtual_address = 0; + } + + if (snapshot->minimum_allocation_timestamp == UINT64_MAX) + { + snapshot->minimum_allocation_timestamp = 0; + } + + snapshot->minimum_resource_size_in_bytes = RmtDataSnapshotGetSmallestResourceSize(snapshot); + snapshot->maximum_resource_size_in_bytes = RmtDataSnapshotGetLargestResourceSize(snapshot); + + return RMT_OK; +} + +// calculate approximate commit type for each resource. +static RmtErrorCode SnapshotGeneratorCalculateCommitType(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot); + + for (int32_t current_virtual_allocation_index = 0; current_virtual_allocation_index < snapshot->virtual_allocation_list.allocation_count; + ++current_virtual_allocation_index) + { + RmtVirtualAllocation* current_virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[current_virtual_allocation_index]; + + // walk every resources and update the commit type flag. + for (int32_t current_resource_index = 0; current_resource_index < current_virtual_allocation->resource_count; ++current_resource_index) + { + RmtResource* current_resource = current_virtual_allocation->resources[current_resource_index]; + + if (current_resource->commit_type != kRmtCommitTypeVirtual) + { + current_resource->commit_type = (current_virtual_allocation->non_heap_resource_count <= 1) ? kRmtCommitTypeCommitted : kRmtCommitTypePlaced; + } + } + } + + return RMT_OK; +} + +// Allocate the region stack used to calculate the total resource memory in an allocation. +static RmtErrorCode SnapshotGeneratorAllocateRegionStack(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot); + + // find the allocation with the largest number of resources + int32_t max_resource_count = 0; + for (int32_t current_virtual_allocation_index = 0; current_virtual_allocation_index < snapshot->virtual_allocation_list.allocation_count; + ++current_virtual_allocation_index) + { + RmtVirtualAllocation* current_virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[current_virtual_allocation_index]; + int32_t current_resource_count = current_virtual_allocation->resource_count; + if (current_resource_count > max_resource_count) + { + max_resource_count = current_resource_count; + } + } + + // allocate the memory and keep track of the max size + snapshot->region_stack_count = max_resource_count; + snapshot->region_stack_buffer = + (RmtMemoryRegion*)PerformAllocation(snapshot->data_set, sizeof(RmtMemoryRegion) * max_resource_count, sizeof(RmtMemoryRegion)); + + return RMT_OK; +} + +static RmtErrorCode SnapshotGeneratorCalculateSnapshotPointSummary(RmtDataSnapshot* snapshot, RmtSnapshotPoint* out_snapshot_point) +{ + RMT_ASSERT(snapshot); + RMT_ASSERT(out_snapshot_point); + + out_snapshot_point->virtual_allocations = snapshot->virtual_allocation_list.allocation_count; + out_snapshot_point->resource_count = snapshot->resource_list.resource_count; + out_snapshot_point->total_virtual_memory = RmtVirtualAllocationListGetTotalSizeInBytes(&snapshot->virtual_allocation_list); + out_snapshot_point->bound_virtual_memory = RmtVirtualAllocationListGetBoundTotalSizeInBytes(snapshot, &snapshot->virtual_allocation_list); + out_snapshot_point->unbound_virtual_memory = RmtVirtualAllocationListGetUnboundTotalSizeInBytes(snapshot, &snapshot->virtual_allocation_list); + + RmtSegmentStatus heap_status[kRmtHeapTypeCount]; + for (int32_t current_heap_type_index = 0; current_heap_type_index < kRmtHeapTypeCount; ++current_heap_type_index) + { + RmtDataSnapshotGetSegmentStatus(snapshot, (RmtHeapType)current_heap_type_index, &heap_status[current_heap_type_index]); + out_snapshot_point->committed_memory[current_heap_type_index] = heap_status[current_heap_type_index].total_physical_mapped_by_process; + } + + return RMT_OK; +} + +// function to generate a snapshot. +RmtErrorCode RmtDataSetGenerateSnapshot(RmtDataSet* data_set, RmtSnapshotPoint* snapshot_point, RmtDataSnapshot* out_snapshot) +{ + RMT_ASSERT(data_set); + RMT_ASSERT(out_snapshot); + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_snapshot, RMT_ERROR_INVALID_POINTER); + + out_snapshot->snapshot_point = snapshot_point; + + // set up the snapshot. + memcpy(out_snapshot->name, snapshot_point->name, RMT_MINIMUM(strlen(snapshot_point->name), sizeof(out_snapshot->name))); + out_snapshot->timestamp = snapshot_point->timestamp; + RmtErrorCode error_code = AllocateMemoryForSnapshot(data_set, out_snapshot); + + // initialize this. + out_snapshot->maximum_physical_memory_in_bytes = 0; + + // initialize the page table. + error_code = RmtPageTableInitialize( + &out_snapshot->page_table, out_snapshot->data_set->segment_info, out_snapshot->data_set->segment_info_count, out_snapshot->data_set->target_process_id); + RMT_ASSERT(error_code == RMT_OK); + + // Reset the RMT stream parsers ready to load the data. + RmtStreamMergerReset(&data_set->stream_merger); + + // process all the tokens + while (!RmtStreamMergerIsEmpty(&data_set->stream_merger)) + { + // grab the next token from the heap. + RmtToken current_token; + error_code = RmtStreamMergerAdvance(&data_set->stream_merger, ¤t_token); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // we only want to create the snapshot using events up until a specific moment in time. + if (current_token.common.timestamp > snapshot_point->timestamp) + { + break; + } + + // handle the token. + error_code = ProcessTokenForSnapshot(data_set, ¤t_token, out_snapshot); + RMT_ASSERT(error_code == RMT_OK); + } + + SnapshotGeneratorConvertHeapsToBuffers(out_snapshot); + SnapshotGeneratorAddResourcePointers(out_snapshot); + SnapshotGeneratorCompactVirtualAllocations(out_snapshot); + SnapshotGeneratorAddUnboundResources(out_snapshot); + SnapshotGeneratorCalculateSummary(out_snapshot); + SnapshotGeneratorCalculateCommitType(out_snapshot); + SnapshotGeneratorAllocateRegionStack(out_snapshot); + SnapshotGeneratorCalculateSnapshotPointSummary(out_snapshot, snapshot_point); + return RMT_OK; +} + +// get the segment info for a physical address +RmtErrorCode RmtDataSetGetSegmentForPhysicalAddress(const RmtDataSet* data_set, RmtGpuAddress physical_address, const RmtSegmentInfo** out_segment_info) +{ + RMT_ASSERT(data_set); + RMT_ASSERT(out_segment_info); + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_segment_info, RMT_ERROR_INVALID_POINTER); + + for (int32_t current_segment_info_index = 0; current_segment_info_index < data_set->segment_info_count; ++current_segment_info_index) + { + const RmtSegmentInfo* current_segment_info = &data_set->segment_info[current_segment_info_index]; + if (current_segment_info->base_address <= physical_address && physical_address <= (current_segment_info->base_address + current_segment_info->size)) + { + *out_segment_info = current_segment_info; + return RMT_OK; + } + } + + return RMT_ERROR_NO_ALLOCATION_FOUND; +} + +// Get the time corresponding to the given number of cpu clock cycles +RmtErrorCode RmtDataSetGetCpuClockTimestamp(const RmtDataSet* data_set, uint64_t clk, double* out_cpu_timestamp) +{ + RMT_ASSERT(data_set); + RMT_ASSERT(out_cpu_timestamp); + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_cpu_timestamp, RMT_ERROR_INVALID_POINTER); + + const uint64_t cpu_clock_frequency_in_mhz = data_set->cpu_frequency / 1000000; + + if (cpu_clock_frequency_in_mhz == 0) + { + return RMT_ERROR_TIMESTAMP_OUT_OF_BOUNDS; + } + + *out_cpu_timestamp = clk * 1000.0; + *out_cpu_timestamp /= cpu_clock_frequency_in_mhz; + return RMT_OK; +} + +// Get whether the CPU clock timestamp is valid +RmtErrorCode RmtDataSetGetCpuClockTimestampValid(const RmtDataSet* data_set) +{ + RMT_ASSERT(data_set); + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + + const uint64_t cpu_clock_frequency_in_mhz = data_set->cpu_frequency / 1000000; + + if (cpu_clock_frequency_in_mhz == 0) + { + return RMT_ERROR_TIMESTAMP_OUT_OF_BOUNDS; + } + return RMT_OK; +} + +// guts of adding a snapshot with file ops. +static RmtErrorCode AddSnapshot(RmtDataSet* data_set, const char* name, uint64_t timestamp, RmtSnapshotPoint** out_snapshot_point) +{ + // add it to the snapshot list in the dataset. + const int32_t snapshot_index = data_set->snapshot_count++; + if (snapshot_index >= RMT_MAXIMUM_SNAPSHOT_POINTS) + { + *out_snapshot_point = NULL; + return RMT_ERROR_OUT_OF_MEMORY; + } + + const int32_t name_length = (int32_t)RMT_MINIMUM(strlen(name), RMT_MAXIMUM_NAME_LENGTH); + data_set->snapshots[snapshot_index].timestamp = timestamp; + memset(data_set->snapshots[snapshot_index].name, 0, sizeof(data_set->snapshots[snapshot_index].name)); + memcpy(data_set->snapshots[snapshot_index].name, name, name_length); + data_set->snapshots[snapshot_index].cached_snapshot = NULL; + data_set->snapshots[snapshot_index].virtual_allocations = 0; + data_set->snapshots[snapshot_index].resource_count = 0; + data_set->snapshots[snapshot_index].total_virtual_memory = 0; + data_set->snapshots[snapshot_index].bound_virtual_memory = 0; + data_set->snapshots[snapshot_index].unbound_virtual_memory = 0; + for (int32_t current_heap_type_index = 0; current_heap_type_index < kRmtHeapTypeCount; ++current_heap_type_index) + { + data_set->snapshots[snapshot_index].committed_memory[current_heap_type_index] = 0; + } + + if (!data_set->read_only) + { + // jump to the end of the file, and write a new snapshot out. + fseek((FILE*)data_set->file_handle, 0L, SEEK_END); + + // add the header. + RmtFileChunkHeader chunk_header; + chunk_header.chunk_identifier.chunk_type = kRmtFileChunkTypeSnapshotInfo; + chunk_header.chunk_identifier.chunk_index = 0; + chunk_header.chunk_identifier.reserved = 0; + chunk_header.version_major = 1; + chunk_header.version_minor = 0; + chunk_header.padding = 0; + chunk_header.size_in_bytes = sizeof(RmtFileChunkHeader) + sizeof(RmtFileChunkSnapshotInfo) + name_length; + + size_t write_size = fwrite(&chunk_header, 1, sizeof(RmtFileChunkHeader), (FILE*)data_set->file_handle); + RMT_ASSERT(write_size == sizeof(RmtFileChunkHeader)); + + // add the snapshot payload + RmtFileChunkSnapshotInfo snapshot_info_chunk; + snapshot_info_chunk.name_length_in_bytes = name_length; + snapshot_info_chunk.snapshot_time = timestamp + data_set->stream_merger.minimum_start_timestamp; // add the minimum so rebase on load works. + data_set->snapshots[snapshot_index].file_offset = ftell((FILE*)data_set->file_handle); // get offset before write to payload + write_size = fwrite(&snapshot_info_chunk, 1, sizeof(RmtFileChunkSnapshotInfo), (FILE*)data_set->file_handle); + RMT_ASSERT(write_size == sizeof(RmtFileChunkSnapshotInfo)); + + // write the name + write_size = fwrite(name, 1, name_length, (FILE*)data_set->file_handle); + RMT_ASSERT(write_size == name_length); + } + + // write the pointer back. + *out_snapshot_point = &data_set->snapshots[snapshot_index]; + return RMT_OK; +} + +// add a new snapshot to the end of the file. +RmtErrorCode RmtDataSetAddSnapshot(RmtDataSet* data_set, const char* name, uint64_t timestamp, RmtSnapshotPoint** out_snapshot_point) +{ + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(name, RMT_ERROR_INVALID_POINTER); + + const RmtErrorCode error_code = AddSnapshot(data_set, name, timestamp, out_snapshot_point); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + CommitTemporaryFileEdits(data_set, false); + return RMT_OK; +} + +// guts of removing a snapshot without destroying the cached object, lets this code be shared with rename. +static void RemoveSnapshot(RmtDataSet* data_set, const int32_t snapshot_index) +{ + if (!data_set->read_only) + { + // set the length to 0 in the file. + const uint64_t offset_to_snapshot_chunk = data_set->snapshots[snapshot_index].file_offset; // offset to snapshot info chunk. + fseek((FILE*)data_set->file_handle, (int32_t)(offset_to_snapshot_chunk + offsetof(RmtFileChunkSnapshotInfo, name_length_in_bytes)), SEEK_SET); + const uint32_t value = 0; + fwrite(&value, 1, sizeof(uint32_t), (FILE*)data_set->file_handle); + } + + // remove the snapshot from the list of snapshot points in the dataset. + const int32_t last_snapshot_index = data_set->snapshot_count - 1; + memcpy(&data_set->snapshots[snapshot_index], &data_set->snapshots[last_snapshot_index], sizeof(RmtSnapshotPoint)); + data_set->snapshot_count--; +} + +// remove a snapshot from the data set. +RmtErrorCode RmtDataSetRemoveSnapshot(RmtDataSet* data_set, const int32_t snapshot_index) +{ + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(snapshot_index < data_set->snapshot_count, RMT_ERROR_INDEX_OUT_OF_RANGE); + + RmtSnapshotPoint* snapshot_point = &data_set->snapshots[snapshot_index]; + RmtDataSnapshotDestroy(snapshot_point->cached_snapshot); + + RemoveSnapshot(data_set, snapshot_index); + + CommitTemporaryFileEdits(data_set, false); + + return RMT_OK; +} + +// rename a snapshot in the data set. +RmtErrorCode RmtDataSetRenameSnapshot(RmtDataSet* data_set, const int32_t snapshot_index, const char* name) +{ + RMT_RETURN_ON_ERROR(data_set, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(name, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(snapshot_index < data_set->snapshot_count, RMT_ERROR_INDEX_OUT_OF_RANGE); + + const uint64_t timestamp = data_set->snapshots[snapshot_index].timestamp; + + // add it to the end. + RmtSnapshotPoint* snapshot_point = NULL; + RmtErrorCode error_code = AddSnapshot(data_set, name, timestamp, &snapshot_point); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // copy over the summary stuff from the previous one, and the pointer to the cached dataset. + snapshot_point->cached_snapshot = data_set->snapshots[snapshot_index].cached_snapshot; + snapshot_point->virtual_allocations = data_set->snapshots[snapshot_index].virtual_allocations; + snapshot_point->resource_count = data_set->snapshots[snapshot_index].resource_count; + snapshot_point->total_virtual_memory = data_set->snapshots[snapshot_index].total_virtual_memory; + snapshot_point->bound_virtual_memory = data_set->snapshots[snapshot_index].bound_virtual_memory; + snapshot_point->unbound_virtual_memory = data_set->snapshots[snapshot_index].unbound_virtual_memory; + for (int32_t current_heap_type_index = 0; current_heap_type_index < kRmtHeapTypeCount; ++current_heap_type_index) + { + snapshot_point->committed_memory[current_heap_type_index] = data_set->snapshots[snapshot_index].committed_memory[current_heap_type_index]; + } + + // remove it also, has the side effect of copying the new thing we just made back to the original location :D + RemoveSnapshot(data_set, snapshot_index); + + CommitTemporaryFileEdits(data_set, false); + + return RMT_OK; +} + +int32_t RmtDataSetGetSeriesIndexForTimestamp(RmtDataSet* data_set, uint64_t timestamp) +{ + RMT_UNUSED(data_set); + + return (int32_t)(timestamp / 3000); +} diff --git a/source/backend/rmt_data_set.h b/source/backend/rmt_data_set.h new file mode 100644 index 0000000..48f3d3d --- /dev/null +++ b/source/backend/rmt_data_set.h @@ -0,0 +1,250 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Structures and functions for working with a data set. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_DATA_SET_H_ +#define RMV_BACKEND_RMT_DATA_SET_H_ + +#include +#include +#include "rmt_adapter_info.h" +#include "rmt_configuration.h" +#include "rmt_segment_info.h" +#include "rmt_process_start_info.h" +#include "rmt_process_map.h" +#include "rmt_data_profile.h" +#include "rmt_data_timeline.h" +#include "rmt_virtual_allocation_list.h" +#include "rmt_physical_allocation_list.h" +#include +#include +#include + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtDataSnapshot RmtDataSnapshot; +typedef struct RmtResource RmtResource; +typedef struct RmtResourceHistory RmtResourceHistory; + +/// Callback function prototype for allocating memory. +typedef void* (*RmtDataSetAllocationFunc)(size_t size_in_bytes, size_t alignment); + +/// Callback function prototype for freeing memory. +typedef void (*RmtDataSetFreeFunc)(void* buffer); + +/// A structure encapsulating a single snapshot point. +typedef struct RmtSnapshotPoint +{ + char name[RMT_MAXIMUM_NAME_LENGTH]; ///< The name of the snapshot. + uint64_t timestamp; ///< The point at which the snapshot was taken. + uint64_t file_offset; ///< The file offset for snapshot management. + RmtDataSnapshot* cached_snapshot; ///< A pointer to a RmtDataSnapshot that has been created for this snapshot point. + int32_t virtual_allocations; + int32_t resource_count; + uint64_t total_virtual_memory; + uint64_t bound_virtual_memory; + uint64_t unbound_virtual_memory; + uint64_t committed_memory[kRmtHeapTypeCount]; +} RmtSnapshotPoint; + +/// A structure encapsulating a single RMT dataset. +typedef struct RmtDataSet +{ + char file_path[RMT_MAXIMUM_FILE_PATH]; ///< The file path to the file being worked with. + char temporary_file_path[RMT_MAXIMUM_FILE_PATH]; ///< The file path to the safe temporary file being worked with. + void* file_handle; ///< The handle to the RMT file (operates on the temporary). + size_t file_size_in_bytes; ///< The size of the file pointed to by fileHandle in bytes. + bool read_only; ///< Whether the dataset is loaded as read-only + + RmtDataSetAllocationFunc allocate_func; ///< Allocate memory function pointer. + RmtDataSetFreeFunc free_func; ///< Free memory function pointer. + + RmtParser streams[RMT_MAXIMUM_STREAMS]; ///< An RmtParser structure for each stream in the file. + int32_t stream_count; ///< The number of RMT streams in the file. + RmtStreamMerger stream_merger; ///< Token heap. + + RmtAdapterInfo adapter_info; ///< The adapter info. + + RmtSegmentInfo segment_info[RMT_MAXIMUM_SEGMENTS]; ///< An array of segment information. + int32_t segment_info_count; ///< The number of segments. + + RmtProcessStartInfo process_start_info[RMT_MAXIMUM_PROCESS_COUNT]; ///< An array of process start information. + int32_t process_start_info_count; ///< The number of RmtProcessStartInfo structures in processStartInfo. + RmtProcessMap process_map; ///< A map of processes seen in the RMT file. + + RmtSnapshotPoint snapshots[RMT_MAXIMUM_SNAPSHOT_POINTS]; ///< An array of all snapshots in the data set. + int32_t snapshot_count; ///< The number of snapshots used. + + RmtDataProfile data_profile; ///< The data profile which is populated in the 1st pass of the parser. + + uint64_t maximum_timestamp; ///< The maximum timestamp seen in this data set. + uint32_t cpu_frequency; ///< The CPU frequency (in clock ticks per second) of the machine where the RMT data was captured. + uint64_t target_process_id; ///< The target process ID that was traced. + + RmtVirtualAllocationList virtual_allocation_list; ///< Temporary virtual allocation list. + RmtPhysicalAllocationList physical_allocation_list; ///< Temporary physical allocation list. + + ResourceIdMapAllocator* p_resource_id_map_allocator; ///< Allocator buffer/struct used to do lookup of unique resource ID. + +} RmtDataSet; + +/// Initialize the RMT data set from a file path. +/// +/// In order to avoid accidental corruption of the file being opened. The RMT backend +/// will make a temporary copy of the file with which to work. Modifications are done +/// to this copy, and then calls to rmtDataSetDestroy will commit those +/// edits back to the original by way of a file rename. +/// +/// The reason for this implementation choice is because changes to the file system +/// metadata are atomic, where as changes to the contents of the file are not. This +/// means if a crash of the application (or wider system) where to happen during a +/// change to the file, then the RMV file may be rendered corrupted and unusable. If +/// we instead copy the file on load, and work in the temporary copy, and then, using +/// only metadata edits (i.e.: rename and delete), place that back where the orignial +/// file was. Then even if the system were to crash, the integrity of the original +/// RMV file is always preserved. +/// +/// @param [in] path A pointer to a string containing the path to the RMT file that we would like to load to initialize the data set. +/// @param [in] data_set A pointer to a RmtDataSet structure that will contain the data set. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +RmtErrorCode RmtDataSetInitialize(const char* path, RmtDataSet* data_set); + +/// Destroy the data set. +/// +/// @param [in] data_set A pointer to a RmtDataSet structure that will contain the data set. +/// +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +RmtErrorCode RmtDataSetDestroy(RmtDataSet* data_set); + +/// Generate a timeline from the data set. +/// +/// @param [in] data_set A pointer to a RmtDataSet structure to used to generate the timeline. +/// @param [in] timeline_type The type of timeline to generate. +/// @param [out] out_timeline The address of a RmtDataTimeline structure to populate. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +/// @retval +/// RMT_ERROR_OUT_OF_MEMORY The operation failed due as memory could not be allocated to create the timeline. +RmtErrorCode RmtDataSetGenerateTimeline(RmtDataSet* data_set, RmtDataTimelineType timeline_type, RmtDataTimeline* out_timeline); + +/// Genereate a snapshot from a data set at a specific time. +/// +/// @param [in] data_set A pointer to a RmtDataSet structure to used to generate the timeline. +/// @param [in] timestamp The timestamp to generate the snapshot for. +/// @param [in] name The name of the snapshot. +/// @param [out] out_snapshot The address of a RmtDataSnapshot structure to populate. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +/// @retval +/// RMT_ERROR_OUT_OF_MEMORY The operation failed due as memory could not be allocated to create the snapshot. +RmtErrorCode RmtDataSetGenerateSnapshot(RmtDataSet* data_set, RmtSnapshotPoint* snapshot_point, RmtDataSnapshot* out_snapshot); + +/// Find a segment from a physical address. +/// +/// @param [in] data_set A pointer to a RmtDataSet structure. +/// @param [in] physical_address The physical address to find the parent segment for. +/// @param [out] out_segment_info The address of a pointer to a RmtSegmentInfo structure. This will be set to NULL if the segment wasn't found. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +/// @retval +/// RMT_ERROR_NO_ALLOCATION_FOUND The operation failed due to physicalAddress not being present in the segment info. +RmtErrorCode RmtDataSetGetSegmentForPhysicalAddress(const RmtDataSet* data_set, RmtGpuAddress physical_address, const RmtSegmentInfo** out_segment_info); + +/// Get the time corresponding to the given number of cpu clock cycles +/// +/// @param [in] data_set A pointer to a RmtDataSet structure. +/// @param [in] clk The number of clock cycles +/// @param [out] out_cpu_timestamp The time, in nanoseconds, corresponding to the number of clock cycles +/// +/// @returns +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +/// @retval +/// RMT_ERROR_TIMESTAMP_OUT_OF_BOUNDS The operation failed due to the CPU clock being invalid. +RmtErrorCode RmtDataSetGetCpuClockTimestamp(const RmtDataSet* data_set, uint64_t clk, double* out_cpu_timestamp); + +/// Get whether the CPU clock timestamp is valid +/// +/// @param [in] data_set A pointer to a RmtDataSet structure. +/// +/// @returns +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +/// @retval +/// RMT_ERROR_TIMESTAMP_OUT_OF_BOUNDS The operation failed due to the CPU clock being invalid. +RmtErrorCode RmtDataSetGetCpuClockTimestampValid(const RmtDataSet* data_set); + +/// Add a new snapshot to the file. +/// +/// @param [in] data_set A pointer to a RmtDataSet structure. +/// @param [in] name The name of the snapshot. +/// @param [in] timestamp The time at which the snapshot was created. +/// @param [out] out_snapshot_point The address of a pointer to a RmtSnapshotPoint structure. +/// +/// @returns +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +RmtErrorCode RmtDataSetAddSnapshot(RmtDataSet* data_set, const char* name, uint64_t timestamp, RmtSnapshotPoint** out_snapshot_point); + +/// Remove a new snapshot to the file. +/// +/// Using this function may change the order of snapshot points in data_set. If you +/// have code that is relying on this order by use of index, then you should make sure you update +/// those indices after a call to this function. +/// +/// @param [in] data_set A pointer to a RmtDataSet structure. +/// @param [in] snapshot_index The index of the snapshot to delete. +/// +/// @returns +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +RmtErrorCode RmtDataSetRemoveSnapshot(RmtDataSet* data_set, const int32_t snapshot_index); + +/// Rename an existing snapshot in the file. +/// +/// @param [in] data_set A pointer to a RmtDataSet structure. +/// @param [in] snapshot_index The index of the snapshot to rename. +/// @param [in] name The name of the snapshot. +/// +/// @returns +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to data_set being set to NULL. +RmtErrorCode RmtDataSetRenameSnapshot(RmtDataSet* data_set, const int32_t snapshot_index, const char* name); + +/// Get the index in level-0 of a series for a specified timestamp. +/// +/// @param [in] data_set A pointer to a RmtDataSet structure. +/// @param [in] timestamp The timestamp to convert. +/// +/// @returns +/// The series index. +int32_t RmtDataSetGetSeriesIndexForTimestamp(RmtDataSet* data_set, uint64_t timestamp); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_DATA_SET_H_ diff --git a/source/backend/rmt_data_snapshot.cpp b/source/backend/rmt_data_snapshot.cpp new file mode 100644 index 0000000..20ec121 --- /dev/null +++ b/source/backend/rmt_data_snapshot.cpp @@ -0,0 +1,516 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Functions working on a snapshot. +//============================================================================= + +#include "rmt_data_snapshot.h" +#include "rmt_resource_history.h" +#include "rmt_data_set.h" +#include +#include +#include "rmt_address_helper.h" +#include // for free() + +#ifndef _WIN32 +#include "linux/safe_crt.h" +#endif + +static void DumpResourceList(FILE* file, RmtResource** resources, int32_t resource_count) +{ + fprintf(file, "\t\t\t\"Resources\" : [\n"); + + for (int32_t current_resource_index = 0; current_resource_index < resource_count; ++current_resource_index) + { + const RmtResource* current_resource = resources[current_resource_index]; + + fprintf(file, "\t\t\t\t\"Resource %llu\" : {\n", (unsigned long long)current_resource->identifier); + fprintf(file, "\t\t\t\t\tCreated : %llu,\n", (unsigned long long)current_resource->create_time); + fprintf(file, "\t\t\t\t\tBind : %llu,\n", (unsigned long long)current_resource->bind_time); + fprintf(file, "\t\t\t\t\tAddress : \"0x%010llx\",\n", (unsigned long long)current_resource->address); + fprintf(file, "\t\t\t\t\tSize (Bytes) : %lld,\n", (unsigned long long)current_resource->size_in_bytes); + fprintf(file, "\t\t\t\t\tType : \"%s\"\n", RmtGetResourceTypeNameFromResourceType(current_resource->resource_type)); + fprintf(file, "\t\t\t\t}"); + + if (current_resource_index < (resource_count - 1)) + { + fprintf(file, ",\n"); + } + else + { + fprintf(file, "\n"); + } + } + fprintf(file, "\t\t\t]\n"); +} + +static void DumpAllocationList(FILE* file, const RmtVirtualAllocationList* allocation_list) +{ + for (int32_t current_allocation_index = 0; current_allocation_index < allocation_list->allocation_count; ++current_allocation_index) + { + const RmtVirtualAllocation* allocation_details = &allocation_list->allocation_details[current_allocation_index]; + + fprintf(file, "\t\"Allocation %d\" : {\n", allocation_details->guid); + fprintf(file, "\t\t \"Address\" : \"0x%010llx\",\n", (unsigned long long)allocation_details->base_address); + fprintf(file, "\t\t \"Size\" : %lld,\n", (unsigned long long)RmtGetAllocationSizeInBytes(allocation_details->size_in_4kb_page, kRmtPageSize4Kb)); + fprintf(file, "\t\t \"Created\" : %lld,\n", (unsigned long long)allocation_details->timestamp); + fprintf(file, "\t\t \"Last CPU map\" : %lld,\n", (unsigned long long)allocation_details->last_cpu_map); + fprintf(file, "\t\t \"Last CPU unmap\" : %lld,\n", (unsigned long long)allocation_details->last_cpu_un_map); + fprintf(file, "\t\t \"Last residency update\" : %lld,\n", (unsigned long long)allocation_details->last_residency_update); + fprintf(file, "\t\t \"Map count\" : %d,\n", allocation_details->map_count); + fprintf(file, "\t\t \"Unbound regions\" : %d,\n", allocation_details->unbound_memory_region_count); + fprintf(file, "\t\t \"Resource count\" : %d,\n", allocation_details->resource_count); + + if (allocation_details->resource_count > 0) + { + DumpResourceList(file, allocation_details->resources, allocation_details->resource_count); + } + + fprintf(file, "\t}"); + + if (current_allocation_index < (allocation_list->allocation_count - 1)) + { + fprintf(file, ",\n"); + } + else + { + fprintf(file, "\n"); + } + } +} + +// dump the state. +RmtErrorCode RmtDataSnapshotDumpJsonToFile(const RmtDataSnapshot* snapshot, const char* filename) +{ + FILE* file = NULL; + errno_t error_no = fopen_s(&file, filename, "wb"); + if ((file == nullptr) || error_no != 0) + { + return RMT_ERROR_FILE_NOT_OPEN; + } + + DumpAllocationList(file, &snapshot->virtual_allocation_list); + + fflush(file); + fclose(file); + return RMT_OK; +} + +// do the first pass over the RMT data, figure out the resource-based events, and +// virtual memory-based events, and also build a list of physical address ranges +// that the resource interacts with during its life. This list will be used in the +// 2nd pass of the algorithm. +static RmtErrorCode ProcessTokensIntoResourceHistory(RmtDataSet* data_set, const RmtResource* resource, RmtResourceHistory* out_resource_history) +{ + // Reset the RMT stream parsers ready to load the data. + RmtStreamMergerReset(&data_set->stream_merger); + + while (!RmtStreamMergerIsEmpty(&data_set->stream_merger)) + { + // grab the next token from the heap. + RmtToken current_token; + RmtErrorCode error_code = RmtStreamMergerAdvance(&data_set->stream_merger, ¤t_token); + RMT_ASSERT(error_code == RMT_OK); + + // interested in tokens that directly reference resources. + switch (current_token.type) + { + case kRmtTokenTypeResourceCreate: + if (current_token.resource_create_token.resource_identifier != resource->identifier) + { + break; + } + + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventResourceCreated, current_token.common.thread_id, current_token.common.timestamp, false); + break; + + case kRmtTokenTypeResourceDestroy: + if (current_token.resource_destroy_token.resource_identifier != resource->identifier) + { + break; + } + + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventResourceDestroyed, current_token.common.thread_id, current_token.common.timestamp, false); + break; + + case kRmtTokenTypeResourceBind: + if (current_token.resource_bind_token.resource_identifier != resource->identifier) + { + break; + } + + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventResourceBound, current_token.common.thread_id, current_token.common.timestamp, false); + break; + + case kRmtTokenTypeVirtualAllocate: + { + const RmtGpuAddress address_of_last_byte_allocation = + (current_token.virtual_allocate_token.virtual_address + current_token.virtual_allocate_token.size_in_bytes) - 1; + if (!RmtResourceOverlapsVirtualAddressRange(resource, current_token.virtual_allocate_token.virtual_address, address_of_last_byte_allocation)) + { + break; + } + + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventVirtualMemoryAllocated, current_token.common.thread_id, current_token.common.timestamp, false); + } + break; + + case kRmtTokenTypeResourceReference: + + if (out_resource_history->base_allocation == nullptr) + { + break; + } + + // NOTE: PAL can only make resident/evict a full virtual allocation on CPU, not just a single resource. + if (current_token.cpu_map_token.virtual_address != out_resource_history->base_allocation->base_address) + { + break; + } + + if (current_token.resource_reference.residency_update_type == kRmtResidencyUpdateTypeAdd) + { + RmtResourceHistoryAddEvent(out_resource_history, + kRmtResourceHistoryEventVirtualMemoryMakeResident, + current_token.common.thread_id, + current_token.common.timestamp, + false); + } + else + { + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventVirtualMemoryEvict, current_token.common.thread_id, current_token.common.timestamp, false); + } + break; + + case kRmtTokenTypeCpuMap: + + if (out_resource_history->base_allocation == nullptr) + { + break; + } + + // NOTE: PAL can only map/unmap a full virtual allocation on CPU, not just a resource. + if (current_token.cpu_map_token.virtual_address != out_resource_history->base_allocation->base_address) + { + break; + } + + if (current_token.cpu_map_token.is_unmap) + { + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventVirtualMemoryUnmapped, current_token.common.thread_id, current_token.common.timestamp, false); + } + else + { + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventVirtualMemoryMapped, current_token.common.thread_id, current_token.common.timestamp, false); + } + break; + + case kRmtTokenTypeVirtualFree: + { + if (out_resource_history->base_allocation == nullptr) + { + break; + } + + if (!RmtResourceOverlapsVirtualAddressRange( + resource, current_token.virtual_free_token.virtual_address, (current_token.virtual_free_token.virtual_address + 1))) + { + break; + } + + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventVirtualMemoryFree, current_token.common.thread_id, current_token.common.timestamp, false); + } + break; + + case kRmtTokenTypePageTableUpdate: + { + if (out_resource_history->base_allocation == nullptr) + { + break; + } + + // check for overlap between the resource VA range and this change to the PA mappings. + const uint64_t size_in_bytes = + RmtGetAllocationSizeInBytes(current_token.page_table_update_token.size_in_pages, current_token.page_table_update_token.page_size); + + if (!RmtAllocationsOverlap(current_token.page_table_update_token.virtual_address, + size_in_bytes, + out_resource_history->resource->address, + out_resource_history->resource->size_in_bytes)) + { + break; + } + + if (current_token.page_table_update_token.is_unmapping) + { + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventPhysicalUnmap, current_token.common.thread_id, current_token.common.timestamp, true); + } + else + { + if (current_token.page_table_update_token.physical_address == 0) + { + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventPhysicalMapToHost, current_token.common.thread_id, current_token.common.timestamp, true); + } + else + { + RmtResourceHistoryAddEvent( + out_resource_history, kRmtResourceHistoryEventPhysicalMapToLocal, current_token.common.thread_id, current_token.common.timestamp, true); + } + } + } + break; + + default: + break; + } + } + + return RMT_OK; +} + +// Helper functo call the correct free function. +static void PerformFree(RmtDataSet* data_set, void* pointer) +{ + if (data_set->free_func == nullptr) + { + return free(pointer); + } + + return (data_set->free_func)(pointer); +} + +RmtErrorCode RmtDataSnapshotGenerateResourceHistory(RmtDataSnapshot* snapshot, const RmtResource* resource, RmtResourceHistory* out_resource_history) +{ + RMT_ASSERT(snapshot); + RMT_ASSERT(resource); + RMT_ASSERT(out_resource_history); + RMT_RETURN_ON_ERROR(snapshot, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(resource, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_resource_history, RMT_ERROR_INVALID_POINTER); + + // stash the pointer to the resource and the underlaying VA. + out_resource_history->resource = resource; + out_resource_history->base_allocation = resource->bound_allocation; + out_resource_history->event_count = 0; + + RmtErrorCode error_code = RMT_OK; + + error_code = ProcessTokensIntoResourceHistory(snapshot->data_set, resource, out_resource_history); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + return RMT_OK; +} + +RmtErrorCode RmtDataSnapshotDestroy(RmtDataSnapshot* snapshot) +{ + RMT_RETURN_ON_ERROR(snapshot, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(snapshot->data_set, RMT_ERROR_MALFORMED_DATA); + + // free the memory allocated for the snapshot. + PerformFree(snapshot->data_set, snapshot->virtual_allocation_buffer); + PerformFree(snapshot->data_set, snapshot->resource_list_buffer); + PerformFree(snapshot->data_set, snapshot->region_stack_buffer); + + return RMT_OK; +} + +uint64_t RmtDataSnapshotGetLargestResourceSize(const RmtDataSnapshot* snapshot) +{ + RMT_RETURN_ON_ERROR(snapshot, 0); + + uint64_t latest_resource_size_in_bytes = 0; + + for (int32_t current_resource_index = 0; current_resource_index < snapshot->resource_list.resource_count; ++current_resource_index) + { + const RmtResource* current_resource = &snapshot->resource_list.resources[current_resource_index]; + latest_resource_size_in_bytes = RMT_MAXIMUM(latest_resource_size_in_bytes, current_resource->size_in_bytes); + } + + return latest_resource_size_in_bytes; +} + +/// Get the smallest resource size (in bytes) seen in a snapshot. +uint64_t RmtDataSnapshotGetSmallestResourceSize(const RmtDataSnapshot* snapshot) +{ + RMT_RETURN_ON_ERROR(snapshot, 0); + + uint64_t smallest_resource_size_in_bytes = UINT64_MAX; + + for (int32_t current_resource_index = 0; current_resource_index < snapshot->resource_list.resource_count; ++current_resource_index) + { + const RmtResource* current_resource = &snapshot->resource_list.resources[current_resource_index]; + smallest_resource_size_in_bytes = RMT_MINIMUM(smallest_resource_size_in_bytes, current_resource->size_in_bytes); + } + + if (smallest_resource_size_in_bytes == UINT64_MAX) + { + return 0; + } + + return smallest_resource_size_in_bytes; +} + +RmtErrorCode RmtDataSnapshotGetSegmentStatus(const RmtDataSnapshot* snapshot, RmtHeapType heap_type, RmtSegmentStatus* out_segment_status) +{ + RMT_RETURN_ON_ERROR(snapshot, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_segment_status, RMT_ERROR_INVALID_POINTER); + + out_segment_status->heap_type = heap_type; + + // all this stuff is flagged. + switch (heap_type) + { + case kRmtHeapTypeInvisible: + out_segment_status->flags |= kRmtSegmentStatusFlagVram; + out_segment_status->flags |= kRmtSegmentStatusFlagGpuVisible; + out_segment_status->flags |= kRmtSegmentStatusFlagGpuCached; + break; + case kRmtHeapTypeLocal: + out_segment_status->flags |= kRmtSegmentStatusFlagVram; + out_segment_status->flags |= kRmtSegmentStatusFlagGpuVisible; + out_segment_status->flags |= kRmtSegmentStatusFlagGpuCached; + out_segment_status->flags |= kRmtSegmentStatusFlagCpuVisible; + break; + case kRmtHeapTypeSystem: + out_segment_status->flags |= kRmtSegmentStatusFlagHost; + out_segment_status->flags |= kRmtSegmentStatusFlagGpuVisible; + out_segment_status->flags |= kRmtSegmentStatusFlagGpuCached; + out_segment_status->flags |= kRmtSegmentStatusFlagCpuVisible; + out_segment_status->flags |= kRmtSegmentStatusFlagCpuCached; + break; + + default: + break; + } + + out_segment_status->total_physical_size = snapshot->data_set->segment_info[heap_type].size; + out_segment_status->total_bound_virtual_memory = 0; + + // calculate data for the segment info fields. + uint64_t max_virtual_allocation_size = 0; + uint64_t min_virtual_allocation_size = UINT64_MAX; + uint64_t total_virtual_memory_requested = 0; + uint64_t total_physical_mapped_by_process = snapshot->page_table.mapped_per_heap[heap_type]; + ; + uint64_t allocation_count = 0; + + // set the resource committed memory values. + for (int32_t current_resource_usage_index = 0; current_resource_usage_index < kRmtResourceUsageTypeCount; ++current_resource_usage_index) + { + out_segment_status->physical_bytes_per_resource_usage[current_resource_usage_index] = 0; + } + + for (int32_t current_virtual_allocation_index = 0; current_virtual_allocation_index < snapshot->virtual_allocation_list.allocation_count; + ++current_virtual_allocation_index) + { + const RmtVirtualAllocation* current_virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[current_virtual_allocation_index]; + + const uint64_t size_in_bytes = RmtGetAllocationSizeInBytes(current_virtual_allocation->size_in_4kb_page, kRmtPageSize4Kb); + + if (current_virtual_allocation->heap_preferences[0] == heap_type) + { + total_virtual_memory_requested += size_in_bytes; + max_virtual_allocation_size = RMT_MAXIMUM(max_virtual_allocation_size, size_in_bytes); + min_virtual_allocation_size = RMT_MINIMUM(min_virtual_allocation_size, size_in_bytes); + allocation_count++; + } + + // walk each resource in the allocation and work out what heap each resource is in. + for (int32_t current_resource_index = 0; current_resource_index < current_virtual_allocation->resource_count; ++current_resource_index) + { + const RmtResource* current_resource = current_virtual_allocation->resources[current_resource_index]; + + if (current_resource->resource_type == kRmtResourceTypeHeap) + { + continue; + } + + // process the resource + const RmtResourceUsageType current_resource_usage = RmtResourceGetUsageType(current_resource); + + if (current_virtual_allocation->heap_preferences[0] == heap_type) + { + out_segment_status->total_bound_virtual_memory += current_resource->size_in_bytes; + } + + // caculate the histogram of where each resource has its memory committed. + uint64_t resource_histogram[kRmtResourceBackingStorageCount] = {0}; + RmtResourceGetBackingStorageHistogram(snapshot, current_resource, resource_histogram); + out_segment_status->physical_bytes_per_resource_usage[current_resource_usage] += resource_histogram[heap_type]; + } + } + + if (min_virtual_allocation_size == UINT64_MAX) + { + min_virtual_allocation_size = 0; + } + + // fill out the structure fields. + out_segment_status->total_virtual_memory_requested = total_virtual_memory_requested; + out_segment_status->total_physical_mapped_by_process = total_physical_mapped_by_process; + out_segment_status->total_physical_mapped_by_other_processes = 0; + out_segment_status->max_allocation_size = max_virtual_allocation_size; + out_segment_status->min_allocation_size = min_virtual_allocation_size; + if (allocation_count > 0) + { + out_segment_status->mean_allocation_size = total_virtual_memory_requested / allocation_count; + } + else + { + out_segment_status->mean_allocation_size = 0; + } + + return RMT_OK; +} + +// calculate the seg. status. +RmtSegmentSubscriptionStatus RmtSegmentStatusGetOversubscribed(const RmtSegmentStatus* segment_status) +{ + const uint64_t close_limit = (uint64_t)((double)segment_status->total_physical_size * 0.8); + if (segment_status->total_virtual_memory_requested > segment_status->total_physical_size) + { + return kRmtSegmentSubscriptionStatusOverLimit; + } + if (segment_status->total_virtual_memory_requested > close_limit) + { + return kRmtSegmentSubscriptionStatusCloseToLimit; + } + + return kRmtSegmentSubscriptionStatusUnderLimit; +} + +// get the heap type for a physical address. +RmtHeapType RmtDataSnapshotGetSegmentForAddress(const RmtDataSnapshot* snapshot, RmtGpuAddress gpu_address) +{ + RMT_RETURN_ON_ERROR(snapshot, kRmtHeapTypeUnknown); + + // special case for system memory. + if (gpu_address == 0) + { + return kRmtHeapTypeSystem; + } + + for (int32_t current_segment_index = 0; current_segment_index < snapshot->data_set->segment_info_count; ++current_segment_index) + { + const RmtGpuAddress start_address = snapshot->data_set->segment_info->base_address; + const RmtGpuAddress end_address = + snapshot->data_set->segment_info[current_segment_index].base_address + snapshot->data_set->segment_info[current_segment_index].size; + + if (start_address <= gpu_address && gpu_address < end_address) + { + return snapshot->data_set->segment_info[current_segment_index].heap_type; + } + } + + return kRmtHeapTypeUnknown; +} diff --git a/source/backend/rmt_data_snapshot.h b/source/backend/rmt_data_snapshot.h new file mode 100644 index 0000000..68229ff --- /dev/null +++ b/source/backend/rmt_data_snapshot.h @@ -0,0 +1,181 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Structures and functions for working with a snapshot. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_DATA_SNAPSHOT_H_ +#define RMV_BACKEND_RMT_DATA_SNAPSHOT_H_ + +#include +#include "rmt_resource_list.h" +#include "rmt_page_table.h" +#include "rmt_virtual_allocation_list.h" +#include "rmt_physical_allocation_list.h" +#include "rmt_configuration.h" +#include "rmt_process_map.h" + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtDataSet RmtDataSet; +typedef struct RmtResourceHistory RmtResourceHistory; +typedef struct RmtSnapshotPoint RmtSnapshotPoint; + +/// An enumeration of all segment status flags. +typedef enum RmtSegmentStatusFlags +{ + kRmtSegmentStatusFlagVram = (1 << 0), + kRmtSegmentStatusFlagHost = (1 << 1), + kRmtSegmentStatusFlagCpuCached = (1 << 2), + kRmtSegmentStatusFlagCpuVisible = (1 << 3), + kRmtSegmentStatusFlagGpuCached = (1 << 4), + kRmtSegmentStatusFlagGpuVisible = (1 << 5) +} RmtSegmentStatusFlags; + +/// An enumeration of all subscription +typedef enum RmtSegmentSubscriptionStatus +{ + kRmtSegmentSubscriptionStatusOverLimit = 0, ///< The segment is over-subscribed. + kRmtSegmentSubscriptionStatusUnderLimit = 1, ///< The segment is under the advised limit. + kRmtSegmentSubscriptionStatusCloseToLimit = 2, ///< The segment is close to the limit. +} RmtSegmentSubscriptionStatus; + +/// A structure encapsulating the status of a heap. +typedef struct RmtSegmentStatus +{ + RmtHeapType heap_type; ///< The type of the heap. + uint32_t flags; ///< The flags for the segment status. + uint64_t total_physical_size; ///< The total size (in bytes) of physical memory. + uint64_t total_virtual_memory_requested; ///< The total size (in bytes) of virtual memory that was requested from this segment. + uint64_t total_bound_virtual_memory; ///< The total size (in bytes) of virtual memory that was requested from this segment and then bound. + uint64_t total_physical_mapped_by_process; ///< The total size (in bytes) of the physical memory mapped by the target process. + uint64_t total_physical_mapped_by_other_processes; ///< The total size (in bytes) of the physical memory mapped by other processes. + uint64_t peak_bandwidth_in_bytes_per_second; ///< The peak bandwidth (in bytes per second) that the RAM in the segment is capable of. + uint64_t mean_allocation_size; ///< The mean allcation size (in bytes) of all allocations in this segment. + uint64_t max_allocation_size; ///< The max allocation size (in bytes) of all allocations in this segment. + uint64_t min_allocation_size; ///< The min allocation size (in bytes) of all allocations in this segment. + + uint64_t physical_bytes_per_resource_usage[kRmtResourceUsageTypeCount]; ///< The amount of physical memory (in bytes) of each resource usage type. +} RmtSegmentStatus; + +/// Get the status of a specific segment. +/// +/// @param [in] segment_status A pointer to a RmtSegmentStatus structure. +/// +/// @returns +/// The segment subscription status value. +RmtSegmentSubscriptionStatus RmtSegmentStatusGetOversubscribed(const RmtSegmentStatus* segment_status); + +/// A structure encapsulating a single snapshot at a specific point in time. +typedef struct RmtDataSnapshot +{ + char name[RMT_MAXIMUM_NAME_LENGTH]; ///< The name of the snapshot. + uint64_t timestamp; ///< The timestamp at the point where the snapshot was taken. + RmtDataSet* data_set; ///< A pointer to a RmtDataSet structure from which the RmtDataSnapshot was generated. + RmtSnapshotPoint* snapshot_point; ///< The snapshot point the snapshot was generated from. + + // Summary data for the snapshot. + RmtGpuAddress minimum_virtual_address; ///< The minimum virtual address that has been encountered in this snapshot. + RmtGpuAddress maximum_virtual_address; ///< The maximum virtual address that has been encountered in this snapshot. + uint64_t minimum_allocation_timestamp; ///< The minimum timestamp seen for allocations. + uint64_t maximum_allocation_timestamp; ///< The maximum timestamp seen for allocations. + uint64_t minimum_resource_size_in_bytes; ///< The minimum resource size (in bytes) in this snapshot. + uint64_t maximum_resource_size_in_bytes; ///< The maximum resource size (in bytes) in this snapshot. + uint64_t maximum_physical_memory_in_bytes; ///< The maximum amount of physical memory (in bytes). + + RmtVirtualAllocationList virtual_allocation_list; ///< A list of all virtual allocations. + RmtResourceList resource_list; ///< A list of all resources. + RmtPageTable page_table; ///< The page table at the point the snapshot was taken. + RmtProcessMap process_map; ///< A map of processes seen. + + void* virtual_allocation_buffer; ///< A pointer to the buffer allocated for the virtual allocation list. + void* resource_list_buffer; ///< A pointer to the buffer allocated for the resource list. + + RmtMemoryRegion* region_stack_buffer; + int32_t region_stack_count; + +} RmtDataSnapshot; + +/// Destroy a snapshot. +/// +/// @param [in] snapshot The snapshot to destroy. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to snapshot being set to NULL. +/// @retval +/// RMT_ERROR_MALFORMED_DATA The operation failed due to the snapshot data set being NULL. +RmtErrorCode RmtDataSnapshotDestroy(RmtDataSnapshot* snapshot); + +/// Debugging function. +RmtErrorCode RmtSnapshotDumpStateToConsole(const RmtDataSnapshot* snapshot); + +/// Generate a resource history for a specific resource from a snapshot. +/// +/// @param [in] snapshot The snapshot containing the resource. +/// @param [in] resource The resource to retrieve the history from. +/// @param [out] out_resource_history Pointer to an RmtResourceHistory structure to fill. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to snapshot , resource or +/// out_resource_history being set to NULL. +RmtErrorCode RmtDataSnapshotGenerateResourceHistory(RmtDataSnapshot* snapshot, const RmtResource* resource, RmtResourceHistory* out_resource_history); + +/// Get the largest resource size (in bytes) seen in a snapshot. +/// +/// @param [in] snapshot The snapshot to retrieve the largest resource size from. +/// +/// @returns +/// The largest resource size seen in a snapshot. +uint64_t RmtDataSnapshotGetLargestResourceSize(const RmtDataSnapshot* snapshot); + +/// Get the smallest resource size (in bytes) seen in a snapshot. +/// +/// @param [in] snapshot The snapshot to retrieve the smallest resource size from. +/// +/// @returns +/// The smallest resource size seen in a snapshot. +uint64_t RmtDataSnapshotGetSmallestResourceSize(const RmtDataSnapshot* snapshot); + +/// Get the segment status for a specific heap type. +/// +/// @param [in] snapshot The snapshot to retrieve the status from. +/// @param [in] heap_type The type of heap to get the status for. +/// @param [out] out_segment_status A pointer to a RmtSegmentStatus structure to fill. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because snapshot being set to NULL. +RmtErrorCode RmtDataSnapshotGetSegmentStatus(const RmtDataSnapshot* snapshot, RmtHeapType heap_type, RmtSegmentStatus* out_segment_status); + +/// Get the segment that an address is in. +/// +/// @param [in] snapshot The snapshot to retrieve the status from. +/// @param [in] gpu_address The address to calculate the segment for. +/// +/// @returns +/// The heap type where the physical address resides. +RmtHeapType RmtDataSnapshotGetSegmentForAddress(const RmtDataSnapshot* snapshot, RmtGpuAddress gpu_address); + +/// Dump a snapshot as a JSON payload to a file. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] filename A pointer to the json file name to use. +/// +/// @returns +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_FILE_NOT_OPEN The operation failed due to filename being invalid. +/// +RmtErrorCode RmtDataSnapshotDumpJsonToFile(const RmtDataSnapshot* snapshot, const char* filename); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_DATA_SNAPSHOT_H_ diff --git a/source/backend/rmt_data_timeline.cpp b/source/backend/rmt_data_timeline.cpp new file mode 100644 index 0000000..1683568 --- /dev/null +++ b/source/backend/rmt_data_timeline.cpp @@ -0,0 +1,190 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation of the data timeline functions. +//============================================================================= + +#include "rmt_data_timeline.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include +#include "rmt_job_system.h" +#include +#include // for memcpy() +#include // for malloc(), free() + +// Helper function to call the correct free function. +static void PerformFree(RmtDataSet* data_set, void* pointer) +{ + if (data_set->free_func == nullptr) + { + return free(pointer); + } + + return (data_set->free_func)(pointer); +} + +RmtErrorCode RmtDataTimelineDestroy(RmtDataTimeline* timeline) +{ + RMT_RETURN_ON_ERROR(timeline, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(timeline->data_set, RMT_ERROR_MALFORMED_DATA); + + PerformFree(timeline->data_set, timeline->series_memory_buffer); + return RMT_OK; +} + +// input structure to the histogram job. +typedef struct HistogramJobInput +{ + RmtDataTimelineHistogram* out_timeline_histogram; // histogram being generated. + const RmtDataTimeline* timeline; // timeline being processed to form histogram. + uint64_t start_timestamp; // the start timestamp of the histogram. + uint64_t end_timestamp; // the end timestmap of the histogram. + int64_t bucket_width_in_cycles; // the width of each bucket in RMT cycles. + int64_t bucket_count; // the number of buckets in the histogram. +} HistogramJobInput; + +// check the input parameters are good. +static bool ValidateInputParameters(HistogramJobInput* input_parameters) +{ + if (input_parameters == NULL) + { + return false; + } + + if (input_parameters->timeline == NULL) + { + return false; + } + + if (input_parameters->out_timeline_histogram == NULL) + { + return false; + } + + return true; +} + +// Job function to create histogram data for a single bucket from RMT mip-mapped data series in timeline. +static void CreateHistogramJob(int32_t thread_id, int32_t index, void* input) +{ + RMT_UNUSED(thread_id); + + // Get hold of the job input and validate it. + HistogramJobInput* input_parameters = (HistogramJobInput*)input; + const bool is_input_valid = ValidateInputParameters(input_parameters); + RMT_ASSERT(is_input_valid == true); + if (!is_input_valid) + { + return; + } + + const uint64_t start_timestamp = input_parameters->start_timestamp + (input_parameters->bucket_width_in_cycles * index); + const uint64_t end_timestamp = start_timestamp + input_parameters->bucket_width_in_cycles; + RMT_UNUSED(end_timestamp); + const int32_t value_index = RmtDataSetGetSeriesIndexForTimestamp(NULL, start_timestamp); + const int32_t level_index = 0; + + for (int32_t current_series_index = 0; current_series_index < input_parameters->timeline->series_count; ++current_series_index) + { + const uint64_t current_value = input_parameters->timeline->series[current_series_index].levels[level_index].values[value_index]; + + const int32_t bucket_index = RmtDataTimelineHistogramGetIndex(input_parameters->out_timeline_histogram, index, current_series_index); + input_parameters->out_timeline_histogram->bucket_data[bucket_index] = current_value; + } +} + +// create the historgram. +RmtErrorCode RmtDataTimelineCreateHistogram(const RmtDataTimeline* timeline, + RmtJobQueue* job_queue, + int32_t bucket_count, + uint64_t bucket_width_in_rmt_cycles, + uint64_t start_timestamp, + uint64_t end_timestamp, + RmtDataTimelineHistogram* out_timeline_histogram) +{ + // Validate inputs are okay. + RMT_ASSERT_MESSAGE(timeline, "Parameter timeline is NULL."); + RMT_ASSERT_MESSAGE(job_queue, "Parameter jobQueue is NULL."); + RMT_ASSERT_MESSAGE(bucket_width_in_rmt_cycles > 0, "Parameter bucketWidthInCycles must be larger than 0 cycles."); + RMT_ASSERT_MESSAGE(out_timeline_histogram, "Parameter outTimelineHistogram is NULL."); + + // Handle generating error codes in release builds. + RMT_RETURN_ON_ERROR(timeline, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(job_queue, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(bucket_width_in_rmt_cycles > 0, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR(out_timeline_histogram, RMT_ERROR_INVALID_POINTER); + + // This check is added to see that the time intervals provided by the input timestamp arguments + // need to be larger than the time intervals provided by bucket arguments.. + const int64_t cycle_delta = (end_timestamp - start_timestamp) - (bucket_count * bucket_width_in_rmt_cycles); + RMT_ASSERT_MESSAGE(cycle_delta >= 0, "The time delta is not correct."); + RMT_RETURN_ON_ERROR(cycle_delta >= 0, RMT_ERROR_INVALID_SIZE); + + // Fill out the initial fields of the data set. + out_timeline_histogram->timeline = timeline; + out_timeline_histogram->bucket_width_in_cycles = bucket_width_in_rmt_cycles; + out_timeline_histogram->bucket_count = bucket_count; + + // Allocate memory for the data and prepare it. + out_timeline_histogram->bucket_group_count = timeline->series_count; + const int32_t size_in_bytes = timeline->series_count * out_timeline_histogram->bucket_count * sizeof(uint64_t); + out_timeline_histogram->bucket_data = (uint64_t*)malloc(size_in_bytes); + RMT_ASSERT_MESSAGE(out_timeline_histogram->bucket_data, "Failed to allocate timeline histogram."); + RMT_RETURN_ON_ERROR(out_timeline_histogram->bucket_data, RMT_ERROR_OUT_OF_MEMORY); + memset(out_timeline_histogram->bucket_data, 0, size_in_bytes); + + // Setup the inputs to our job in the scratch memory. + HistogramJobInput* input_parameters = (HistogramJobInput*)out_timeline_histogram->scratch_buffer; + RMT_STATIC_ASSERT(sizeof(HistogramJobInput) <= sizeof(out_timeline_histogram->scratch_buffer)); + input_parameters->bucket_count = out_timeline_histogram->bucket_count; + input_parameters->bucket_width_in_cycles = out_timeline_histogram->bucket_width_in_cycles; + input_parameters->end_timestamp = end_timestamp; + input_parameters->start_timestamp = start_timestamp; + input_parameters->out_timeline_histogram = out_timeline_histogram; + input_parameters->timeline = out_timeline_histogram->timeline; + + // Kick the jobs off to the worker threads. + // NOTE: This can be done as one per bucket (or range of buckets). + RmtJobHandle job_handle = 0; + const RmtErrorCode error_code = RmtJobQueueAddMultiple(job_queue, CreateHistogramJob, input_parameters, 0, bucket_count, &job_handle); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // Wait for job to complete. + RmtJobQueueWaitForCompletion(job_queue, job_handle); + + return RMT_OK; +} + +// destroy the histogram. +RmtErrorCode RmtDataTimelineHistogramDestroy(RmtDataTimelineHistogram* timeline_histogram) +{ + RMT_RETURN_ON_ERROR(timeline_histogram, RMT_ERROR_INVALID_POINTER); + + free(timeline_histogram->bucket_data); + + timeline_histogram->timeline = NULL; + timeline_histogram->bucket_data = NULL; + timeline_histogram->bucket_width_in_cycles = 0; + timeline_histogram->bucket_count = 0; + timeline_histogram->bucket_group_count = 0; + return RMT_OK; +} + +// get index from a bucket address. +int32_t RmtDataTimelineHistogramGetIndex(RmtDataTimelineHistogram* timeline_histogram, int32_t bucket_index, int32_t bucket_group_index) +{ + RMT_ASSERT_MESSAGE(timeline_histogram, "Parameter timelineHistogram is NULL."); + RMT_ASSERT_MESSAGE(bucket_index < timeline_histogram->bucket_count, "bucketIndex is out of range, should be in range [0..bucketCount-1]."); + const int32_t index = (bucket_index * timeline_histogram->bucket_group_count) + bucket_group_index; + return index; +} + +// get the value from the bucket address. +int64_t RmtDataTimelineHistogramGetValue(RmtDataTimelineHistogram* timeline_histogram, int32_t bucket_index, int32_t bucket_group_index) +{ + RMT_ASSERT_MESSAGE(timeline_histogram, "Parameter timelineHistogram is NULL."); + const int32_t index = RmtDataTimelineHistogramGetIndex(timeline_histogram, bucket_index, bucket_group_index); + return timeline_histogram->bucket_data[index]; +} diff --git a/source/backend/rmt_data_timeline.h b/source/backend/rmt_data_timeline.h new file mode 100644 index 0000000..e25758f --- /dev/null +++ b/source/backend/rmt_data_timeline.h @@ -0,0 +1,167 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Data structures and functions to operate on a timeline. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_DATA_TIMELINE_H_ +#define RMV_BACKEND_RMT_DATA_TIMELINE_H_ + +#include +#include +#include "rmt_configuration.h" +#include "rmt_segment_info.h" +#include "rmt_process_map.h" +#include "rmt_process_start_info.h" +#include +#include "rmt_linear_buffer.h" + +/// Scratch memory for the histogram calculation. +#define RMT_WODS_SCRATCH_SIZE (256) + +/// The number of mip-map levels we want to support in the timeline. +#define RMT_MAXIMUM_TIMELINE_SERIES_LEVELS (20) + +/// The maximum number of data series. +#define RMT_MAXIMUM_TIMELINE_DATA_SERIES (20) + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtDataSnapshot RmtDataSnapshot; +typedef struct RmtJobQueue RmtJobQueue; +typedef struct RmtDataSet RmtDataSet; + +/// An enumeration of all timeline types that can be created. Specifies how to group the histogram into buckets. +typedef enum RmtDataTimelineType +{ + kRmtDataTimelineTypeProcess = 0, ///< By which process owns it. + kRmtDataTimelineTypePageSize = 1, ///< Of page size used for the mapping. + kRmtDataTimelineTypeCommitted = 2, ///< Of if the virtual address space is mapped to physical backing or not. + kRmtDataTimelineTypeResourceUsageCount = 3, ///< Of resources where each bucket contains the number of that resource usage type. + kRmtDataTimelineTypeResourceUsageVirtualSize = 4, ///< Of resources where each bucket contains the size of that resource usage type in vm bytes. + kRmtDataTimelineTypePaging = 5, ///< Where each bucket is how much paging is happening (in bytes) for each physical heap type. + kRmtDataTimelineTypeVirtualMemory = 6, ///< Where each bucket is how much virtual memory map is allocated for each preferred heap type. + kRmtDataTimelineTypeResourceNonPreferred = 7, ///< Where each bucket is a resource usage type which is not its preferred heap. + + // add above this. + kRmtDataTimelineTypeCount +} RmtDataTimelineType; + +/// A structure encapsulating a single level in a series. +typedef struct RmtDataTimelineSeriesLevel +{ + uint64_t* values; ///< Pointer to an array of int32_t values at this level in the series map. + int32_t value_count; ///< The number of elements in the array pointed to by values. +} RmtDataTimelineSeriesLevel; + +/// A structure encapsulating a single series. +/// +/// A series is compromised of multiple levels of data. Each level is 50% the +/// size of the previous level, and is generated by taking the maximum value +/// seen in every two buckets of the previous level. +typedef struct RmtDataTimelineSeries +{ + RmtDataTimelineSeriesLevel levels[RMT_MAXIMUM_TIMELINE_SERIES_LEVELS]; ///< An array of RmtDataTimelineSeriesLevel structures. + int32_t level_count; ///< The number of elements used in levels. +} RmtDataTimelineSeries; + +/// A structure encapsulating a timeline of a single RMT trace. +typedef struct RmtDataTimeline +{ + RmtDataSet* data_set; ///< The RmtDataSet which this timeline was generated from. + uint64_t max_timestamp; ///< The maximum timestamp seen in the data set. + + // The data for the currently selected timeline mode. + RmtDataTimelineSeries series[RMT_MAXIMUM_TIMELINE_DATA_SERIES]; ///< An array of series of data. + int32_t series_count; ///< The number of elements used in series. + int32_t* series_memory_buffer; ///< The size of the series memory buffer. This is subdivided into the series and levels. + uint64_t maximum_value_in_all_series; ///< The maxim value seen at any one time in all series. + RmtDataTimelineType timeline_type; ///< The type of timeline. +} RmtDataTimeline; + +/// Destroy the timeline (and free underlaying memory allocated for this). +/// +/// @param [in] timeline A pointer to a RmtDataTimeline structure. +/// +/// @retval +/// RMT_OK The operation completed succesfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because timeline was NULL. +/// @retval +/// RMT_ERROR_MALFORMED_DATA The operation failed because timeline was not correctly initialized. +RmtErrorCode RmtDataTimelineDestroy(RmtDataTimeline* timeline); + +/// A view of the timeline data according to certain view parameters. +typedef struct RmtDataTimelineHistogram +{ + const RmtDataTimeline* timeline; ///< A pointer to the RmtDataTimeline that was used to generate the histogram. + uint64_t* bucket_data; ///< A pointer to the memory allocated to contain the data. + uint64_t bucket_width_in_cycles; ///< The width of each bucket in cycles. + int32_t bucket_count; ///< The number of buckets. + int32_t bucket_group_count; ///< The number of groups that are inside each bucket. + uint8_t scratch_buffer[RMT_WODS_SCRATCH_SIZE]; ///< Scratch buffer used during calculations. +} RmtDataTimelineHistogram; + +/// Create a RmtDataTimelineHistogram. +/// +/// All parameters that are pointers do not transfer "ownership" of the +/// structures thereto pointed at. This means the client code is responsible +/// for ensuring that these objects are alive for for the duration of this +/// function's execution. +/// +/// @param [in] timeline A pointer to a RmtDataTimeline structure to generate the RmtDataTimelineHistogram structure from. +/// @param [in] job_queue The job queue responsible for allocating work to worker threads. +/// @param [in] bucket_count The number of buckets the new data set should contain. +/// @param [in] bucket_width_in_rmt_cycles The width of each bucket, expressed in GPU cycles. +/// @param [in] start_timestamp The starting timestamp. +/// @param [in] end_timestamp The ending timestamp. +/// @param [out] out_timeline_histogram A pointer to a RmtDataTimelineHistogram structure to initialize. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because timeline was NULL. +RmtErrorCode RmtDataTimelineCreateHistogram(const RmtDataTimeline* timeline, + RmtJobQueue* job_queue, + int32_t bucket_count, + uint64_t bucket_width_in_rmt_cycles, + uint64_t start_timestamp, + uint64_t end_timestamp, + RmtDataTimelineHistogram* out_timeline_histogram); + +/// Destroy a RmtDataTimelineHistogram. +/// +/// @param [in,out] timeline_histogram A pointer to a RmtDataTimelineHistogram structure to destroy. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because timelineHistogram was NULL. +RmtErrorCode RmtDataTimelineHistogramDestroy(RmtDataTimelineHistogram* timeline_histogram); + +/// Get the value at a specific bucket. +/// +/// @param [in] timeline_histogram A pointer to a RmtDataTimelineHistogram structure. +/// @param [in] bucket_index The index of the bucket to access. +/// @param [in] bucket_group_index The index of the item in the bucket. Depends on the grouping mode. +/// +/// @retval +/// The value of the item at the specified location in the data set. +int64_t RmtDataTimelineHistogramGetValue(RmtDataTimelineHistogram* timeline_histogram, int32_t bucket_index, int32_t bucket_group_index); + +/// Get the index at a specific bucket. +/// +/// @param [in] timeline_histogram A pointer to a RmtDataTimelineHistogram structure. +/// @param [in] bucket_index The index of the bucket to access. +/// @param [in] bucket_group_index The index of the item in the bucket. Depends on the grouping mode. +/// +/// @retval +/// The index of the item at the specified location in the data set. +int32_t RmtDataTimelineHistogramGetIndex(RmtDataTimelineHistogram* timeline_histogram, int32_t bucket_index, int32_t bucket_group_index); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_DATA_TIMELINE_H_ diff --git a/source/backend/rmt_job_system.cpp b/source/backend/rmt_job_system.cpp new file mode 100644 index 0000000..e32b4d7 --- /dev/null +++ b/source/backend/rmt_job_system.cpp @@ -0,0 +1,241 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation for a job system to run work on multiple threads. +//============================================================================= + +#include "rmt_job_system.h" +#include "rmt_platform.h" +#include +#include // for memset() + +// flag to signal worker thread should terminate +#define WORKER_THREAD_FLAGS_TERMINATE (1 << 0) + +// job system main function +static uint32_t RMT_THREAD_FUNC JobSystemThreadFunc(void* input_data) +{ + RMT_ASSERT_MESSAGE(input_data, "No thread id address passed to thread func."); + + // read the thread input structure + RmtJobQueueWorkerThreadInput* thread_input = (RmtJobQueueWorkerThreadInput*)input_data; + + if (thread_input == NULL) + { + return 0; + } + + // run until the thread terminate signal is set + while (true) + { + // sleep the thread until work is available + RmtThreadEventWait(&thread_input->job_queue->signal); + + // check if we should quit + const uint64_t flags = RmtThreadAtomicRead((volatile uint64_t*)&thread_input->flags); + + if ((flags & WORKER_THREAD_FLAGS_TERMINATE) == WORKER_THREAD_FLAGS_TERMINATE) + { + break; + } + + // acquire the mutex. + const RmtErrorCode error_code = RmtMutexLock(&thread_input->job_queue->queue_mutex); + RMT_ASSERT(error_code == RMT_OK); + + // if there is no work then reset the signal and release the mutex + if (thread_input->job_queue->queue_size == 0) + { + RmtThreadEventReset(&thread_input->job_queue->signal); + RmtMutexUnlock(&thread_input->job_queue->queue_mutex); + } + else + { + // there is some work in the queue grab the job from the head + const int32_t job_index = thread_input->job_queue->queue_head_index; + + RmtJobQueueJob* current_job = &thread_input->job_queue->queue_items[job_index]; + const int32_t index = current_job->run_count++; + const int32_t adjusted_index = current_job->base_index + index; + void* job_input = current_job->input; + RmtJobFunction func = current_job->function; + volatile int64_t* addr = ¤t_job->completed_count; + + // if we ran the last instance, then remove it from the queue + if (current_job->run_count == current_job->count) + { + thread_input->job_queue->queue_head_index = (thread_input->job_queue->queue_head_index + 1) % RMT_MAXIMUM_JOB_COUNT; + thread_input->job_queue->queue_size--; + } + + // release queue access + RmtMutexUnlock(&thread_input->job_queue->queue_mutex); + + // run the job + (*func)(thread_input->thread_id, adjusted_index, job_input); + + // signal that it is done. + RmtThreadAtomicAdd64(addr, 1); + } + } + + return 0; +} + +// initialize the job queue +RmtErrorCode RmtJobQueueInitialize(RmtJobQueue* job_queue, int32_t worker_thread_count) +{ + RMT_ASSERT_MESSAGE(job_queue, "Parameter jobQueue is NULL."); + RMT_RETURN_ON_ERROR(job_queue, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(worker_thread_count > 0 && worker_thread_count <= RMT_MAXIMUM_WORKER_THREADS, RMT_ERROR_INDEX_OUT_OF_RANGE); + + // stash anything we need in the structure + job_queue->worker_thread_count = worker_thread_count; + job_queue->handle_next = 0; + + // clear the queue contents + memset(job_queue->queue_items, 0, sizeof(job_queue->queue_items)); + + // create the event to signal when queue has work + RmtErrorCode error_code = RMT_OK; + error_code = RmtThreadEventCreate(&job_queue->signal, false, true, ""); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // create the mutex for altering the job queue + error_code = RmtMutexCreate(&job_queue->queue_mutex, "RMT Job Queue Mutex"); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // set up the queue + job_queue->queue_tail_index = 0; + job_queue->queue_head_index = 0; + job_queue->queue_size = 0; + + // create our worker threads. + for (int32_t current_worker_thread_index = 0; current_worker_thread_index < worker_thread_count; ++current_worker_thread_index) + { + // set up the inputs + RmtJobQueueWorkerThreadInput* input = &job_queue->worker_thread_inputs[current_worker_thread_index]; + input->thread_id = current_worker_thread_index; + input->job_queue = job_queue; + input->flags = 0; + + // create the thread + error_code = RmtThreadCreate(&job_queue->worker_threads[current_worker_thread_index], JobSystemThreadFunc, input); + + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + } + + return RMT_OK; +} + +// shutdown the job queue +RmtErrorCode RmtJobQueueShutdown(RmtJobQueue* job_queue) +{ + RMT_ASSERT_MESSAGE(job_queue, "Parameter jobQueue is NULL."); + RMT_RETURN_ON_ERROR(job_queue, RMT_ERROR_INVALID_POINTER); + + // singal to terminate all the threads + for (int32_t current_worker_thread_index = 0; current_worker_thread_index < job_queue->worker_thread_count; ++current_worker_thread_index) + { + RmtJobQueueWorkerThreadInput* input = &job_queue->worker_thread_inputs[current_worker_thread_index]; + RmtThreadAtomicOr((volatile uint64_t*)&input->flags, WORKER_THREAD_FLAGS_TERMINATE); + } + + // signal all threads that there is work + RmtThreadEventSignal(&job_queue->signal); + + // wait for all the threads to finish + for (int32_t current_worker_thread_index = 0; current_worker_thread_index < job_queue->worker_thread_count; ++current_worker_thread_index) + { + const RmtErrorCode error_code = RmtThreadWaitForExit(&job_queue->worker_threads[current_worker_thread_index]); + RMT_ASSERT(error_code == RMT_OK); + } + + RmtThreadEventDestroy(&job_queue->signal); + RmtMutexDestroy(&job_queue->queue_mutex); + + return RMT_OK; +} + +// add a single job the queue +RmtErrorCode RmtJobQueueAddSingle(RmtJobQueue* job_queue, RmtJobFunction func, void* input, RmtJobHandle* out_handle) +{ + return RmtJobQueueAddMultiple(job_queue, func, input, 0, 1, out_handle); +} + +// add a job to the queue +RmtErrorCode RmtJobQueueAddMultiple(RmtJobQueue* job_queue, RmtJobFunction func, void* input, int32_t base_index, int32_t count, RmtJobHandle* out_handle) +{ + RMT_ASSERT_MESSAGE(job_queue, "Parameter jobQueue is NULL."); + RMT_ASSERT_MESSAGE(func, "Parameter func is NULL."); + RMT_RETURN_ON_ERROR(job_queue, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(func, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(count, RMT_ERROR_INVALID_SIZE); + + // add work to the queue. + const RmtErrorCode error_code = RmtMutexLock(&job_queue->queue_mutex); + RMT_ASSERT(error_code == RMT_OK); + + // check the size of the queue + if (job_queue->queue_size == RMT_MAXIMUM_JOB_COUNT) + { + RmtMutexUnlock(&job_queue->queue_mutex); + return RMT_ERROR_OUT_OF_MEMORY; + } + + // work out the handle + const RmtJobHandle job_handle = job_queue->handle_next++; + + // block until this item in the queue is available + RmtJobQueueWaitForCompletion(job_queue, job_handle); + + // work out the location in the queue and write the data + const int32_t job_index = job_queue->queue_tail_index; + job_queue->queue_items[job_index].function = func; + job_queue->queue_items[job_index].input = input; + job_queue->queue_items[job_index].base_index = base_index; + job_queue->queue_items[job_index].count = count; + job_queue->queue_items[job_index].run_count = 0; + job_queue->queue_items[job_index].handle = job_handle; + job_queue->queue_items[job_index].completed_count = 0; + + // advance the tail and size + job_queue->queue_tail_index = (job_queue->queue_tail_index + 1) % RMT_MAXIMUM_JOB_COUNT; + job_queue->queue_size++; + + RmtMutexUnlock(&job_queue->queue_mutex); + + // signal all threads that there is work to do + RmtThreadEventSignal(&job_queue->signal); + + // write the handle back if the pointer is not NULL + if (out_handle != nullptr) + { + *out_handle = job_handle; + } + + return RMT_OK; +} + +// check if a job has completed +RmtErrorCode RmtJobQueueWaitForCompletion(RmtJobQueue* job_queue, RmtJobHandle handle) +{ + RMT_ASSERT_MESSAGE(job_queue, "Parameter jobQueue is NULL."); + RMT_RETURN_ON_ERROR(job_queue, RMT_ERROR_INVALID_POINTER); + + do + { + const uint64_t completed_jobs = RmtThreadAtomicRead((volatile uint64_t*)&job_queue->queue_items[handle % RMT_MAXIMUM_JOB_COUNT].completed_count); + + if ((int32_t)completed_jobs == job_queue->queue_items[handle % RMT_MAXIMUM_JOB_COUNT].count) + { + break; + } + + // sleep for 1ms to reduce spin-lock. + RmtSleep(1); + + } while (true); + + return RMT_OK; +} diff --git a/source/backend/rmt_job_system.h b/source/backend/rmt_job_system.h new file mode 100644 index 0000000..2a0f9fe --- /dev/null +++ b/source/backend/rmt_job_system.h @@ -0,0 +1,173 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief A job system to run work on multiple threads. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_JOB_SYSTEM_H_ +#define RMV_BACKEND_RMT_JOB_SYSTEM_H_ + +#include +#include "rmt_atomic.h" +#include "rmt_mutex.h" +#include "rmt_thread_event.h" +#include "rmt_thread.h" + +/// The maximum number of worker threads that RmtJobQueue supports. +#define RMT_MAXIMUM_WORKER_THREADS (24) + +/// The maximum number of jobs that can be queued up. +#define RMT_MAXIMUM_JOB_COUNT (1024) + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtJobQueue RmtJobQueue; + +/// The prototype for a job function. +/// +/// This is the form that all functions should take that are used with the job +/// system. When using RmtJobQueueAddSingle or +/// RmtJobQueueAddMultiple your function will be called on a +/// worker thread asynchronously from the main thread. +/// +/// @param [in] thread_id The unique identifier of the worker thread running this function. +/// @param [in] index The index of the job, only applicable when using RmtJobQueueAddMultiple. +/// @param [in] input The user data provided to your call to RmtJobQueueAddSingle or RmtJobQueueAddMultiple. +/// +typedef void (*RmtJobFunction)(int32_t thread_id, int32_t index, void* input); + +/// Input structures for the job queue worker threads. +typedef struct RmtJobQueueWorkerThreadInput +{ + RmtJobQueue* job_queue; ///< Pointer to the job queue that owns the thread. + uint64_t flags; ///< Flags to control execution of worker thread. + uint32_t thread_id; ///< The id assigned to this worker thread. +} RmtJobQueueWorkerThreadInput; + +/// A type to represent a handle to a job. +/// +/// Handles can be optionally retrieved from RmtJobQueueAddSingle +/// or RmtJobQueueAddMultiple. They can then be passed to +/// RmtJobQueueIsCompleted to check if a job has completed. +/// +typedef uint64_t RmtJobHandle; + +/// A structure encapsulating a single job and it's input. +typedef struct RmtJobQueueJob +{ + RmtJobFunction function; + void* input; + RmtJobHandle handle; + int32_t run_count; + int32_t base_index; + int32_t count; + int64_t completed_count; +} RmtJobQueueJob; + +/// A structure encapsulating the state of the job system. +typedef struct RmtJobQueue +{ + RmtThread worker_threads[RMT_MAXIMUM_WORKER_THREADS]; ///< An array of threads where the jobs may run. + RmtJobQueueWorkerThreadInput worker_thread_inputs[RMT_MAXIMUM_WORKER_THREADS]; ///< A queue of inputs to the jobs. + int32_t worker_thread_count; ///< The total number of worker threads in use. + RmtThreadEvent signal; ///< Signal when there is work in the global queue. + + RmtJobQueueJob queue_items[RMT_MAXIMUM_JOB_COUNT]; ///< A queue of jobs. + int32_t queue_head_index; ///< The head index of the queue. + int32_t queue_tail_index; ///< The tail index of the queue. + int32_t queue_size; ///< The current size of the queue. + RmtMutex queue_mutex; ///< A mutex controlling queue access. + + RmtJobHandle handle_next; ///< The next handle to use. +} RmtJobQueue; + +/// Initialize the job queue for the specified number of threads. +/// +/// @param [in,out] job_queue A pointer to the RmtJobSystem structure to initialize. +/// @param [in] worker_thread_count The number of worker threads to create. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because jobQueue was NULL. +/// @retval +/// RMT_ERROR_INDEX_OUT_OF_RANGE The operation failed because workerThreadCount was not in range [0..RMT_MAXIMUM_WORKER_THREADS]. +/// +RmtErrorCode RmtJobQueueInitialize(RmtJobQueue* job_queue, int32_t worker_thread_count); + +/// Shutdown the job queue. +/// +/// @param [in,out] job_queue A pointer to the RmtJobSystem structure to shutdown. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because jobQueue was NULL. +/// +RmtErrorCode RmtJobQueueShutdown(RmtJobQueue* job_queue); + +/// Add a single job to the queue. +/// +/// @param [in,out] job_queue A pointer to the RmtJobSystem structure. +/// @param [in] func A pointer to the function containing your job. +/// @param [in] input A pointer to some user data to pass to func. See RmtJobFunction. +/// @param [out] out_handle An optional pointer to a RmtJobHandle. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because jobQueue was NULL. +/// @retval +/// RMT_ERROR_OUT_OF_MEMORY The operation failed because there was no space left in the queue. +/// +RmtErrorCode RmtJobQueueAddSingle(RmtJobQueue* job_queue, RmtJobFunction func, void* input, RmtJobHandle* out_handle); + +/// Add multiple copies of the same job to the queue. +/// +/// This is similar to a parallel for. The func provided will be +/// called count times. With the index being passed to +/// func being calculated as i + baseIndex. This is +/// a much more space-efficient way of adding multiple copies of the same job +/// to the queue. It takes a single queue slot, rather than n. +/// +/// Conceptually you can think of it as the equivalent to an async. version of a +/// for loop, see the following pseduo code: +/// +/// for(int32_t index=baseIndex; index<(baseIndex+count); ++index) { +/// func(thread_id, index, input); +/// } +/// +/// @param [in,out] job_queue A pointer to the RmtJobSystem structure. +/// @param [in] func A pointer to the function containing your job. +/// @param [in] input A pointer to some user data to pass to func. See RmtJobFunction. +/// @param [in] base_index The start index of the job. +/// @param [in] count The total number of copies of the job to execute. +/// @param [out] out_handle An optional pointer to a RmtJobHandle. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because jobQueue was NULL. +/// @retval +/// RMT_ERROR_OUT_OF_MEMORY The operation failed because there was no space left in the queue. +/// +RmtErrorCode RmtJobQueueAddMultiple(RmtJobQueue* job_queue, RmtJobFunction func, void* input, int32_t base_index, int32_t count, RmtJobHandle* out_handle); + +/// Wait for a job handle to complete. +/// +/// @param [in] job_queue A pointer to the RmtJobSystem structure. +/// @param [in] handle A handle to the job that was added using RmtJobQueueAddSingle or RmtJobQueueAddMultiple. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because jobQueue was NULL. +/// +RmtErrorCode RmtJobQueueWaitForCompletion(RmtJobQueue* job_queue, RmtJobHandle handle); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_JOB_SYSTEM_H_ diff --git a/source/backend/rmt_linear_buffer.cpp b/source/backend/rmt_linear_buffer.cpp new file mode 100644 index 0000000..5daf6c7 --- /dev/null +++ b/source/backend/rmt_linear_buffer.cpp @@ -0,0 +1,38 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation of the linear allocator helper functions. +//============================================================================= + +#include "rmt_linear_buffer.h" +#include + +RmtErrorCode RmtAllocLinearBufferInitialize(RmtAllocLinearBuffer* linear_buffer, void* buffer, size_t buffer_size) +{ + RMT_RETURN_ON_ERROR(linear_buffer && buffer, RMT_ERROR_INVALID_POINTER); + + linear_buffer->buffer_base = buffer; + linear_buffer->buffer_size = buffer_size; + linear_buffer->offset = 0; + return RMT_OK; +} + +void* RmtAllocLinearBufferAllocate(RmtAllocLinearBuffer* linear_buffer, size_t size) +{ + RMT_ASSERT_MESSAGE(linear_buffer, "Parameter linearBuffer is NULL."); + RMT_RETURN_ON_ERROR(linear_buffer, NULL); + // check there is enough space in the buffer for the allocation. + RMT_RETURN_ON_ERROR((linear_buffer->offset + size) <= linear_buffer->buffer_size, NULL); + + const uintptr_t address = (uintptr_t)linear_buffer->buffer_base + linear_buffer->offset; + linear_buffer->offset += size; + return (void*)address; +} + +void* RmtAllocLinearBufferGetBaseAddress(RmtAllocLinearBuffer* linear_buffer) +{ + RMT_ASSERT_MESSAGE(linear_buffer, "Parameter linearBuffer is NULL."); + RMT_RETURN_ON_ERROR(linear_buffer, NULL); + + return linear_buffer->buffer_base; +} \ No newline at end of file diff --git a/source/backend/rmt_linear_buffer.h b/source/backend/rmt_linear_buffer.h new file mode 100644 index 0000000..7c96e31 --- /dev/null +++ b/source/backend/rmt_linear_buffer.h @@ -0,0 +1,58 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief A linear allocator structure and helper functions. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_LINEAR_BUFFER_H_ +#define RMV_BACKEND_RMT_LINEAR_BUFFER_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A structure encapsulating state for a linear allocator. +typedef struct RmtAllocLinearBuffer +{ + void* buffer_base; ///< A pointer to the buffer. + size_t buffer_size; ///< The total size of bufferBase in bytes. + size_t offset; ///< The current offset into bufferBase in bytes. +} RmtAllocLinearBuffer; + +/// Initialize the linear allocator. +/// +/// @param [in,out] linear_buffer A pointer to a RmtAllocLinearBuffer structure. +/// @param [in] buffer A pointer to a memory buffer to manage allocations for. +/// @param [in] buffer_size The size (in bytes) of the buffer pointed to by buffer. +/// +/// @retval +/// RMT_OK The operation completed succesfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because either linearBuffer or buffer was NULL. +/// +RmtErrorCode RmtAllocLinearBufferInitialize(RmtAllocLinearBuffer* linear_buffer, void* buffer, size_t buffer_size); + +/// Allocate the next free block from the pool. +/// +/// @param [in,out] linear_buffer A pointer to a RmtAllocLinearBuffer structure. +/// @param [in] size The size (in bytes) of the allocation. +/// +/// @returns +/// A pointer to the next allocated block. If the buffer is empty then NULL is returned. +/// +void* RmtAllocLinearBufferAllocate(RmtAllocLinearBuffer* linear_buffer, size_t size); + +/// Get the base address of the linear buffer. +/// +/// @param [in] linear_buffer A pointer to a RmtAllocLinearBuffer structure. +/// +/// @returns +/// A pointer to the linear buffer base address. If linear_buffer is NULL then NULL is returned. +void* RmtAllocLinearBufferGetBaseAddress(RmtAllocLinearBuffer* linear_buffer); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_LINEAR_BUFFER_H_ diff --git a/source/backend/rmt_mutex.cpp b/source/backend/rmt_mutex.cpp new file mode 100644 index 0000000..fc6eda5 --- /dev/null +++ b/source/backend/rmt_mutex.cpp @@ -0,0 +1,103 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementations of the mutex abstraction. +//============================================================================= + +#ifndef _WIN32 +#include +#endif // #ifndef _WIN32 + +#include +#include "rmt_mutex.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif // #ifdef _WIN32 + +// structure for a mutex +typedef struct RmtMutexInternal +{ +#ifdef _WIN32 + HANDLE handle; +#else + std::mutex* mutex; // pointer to mutex object + std::mutex buffer; // buffer to store the mutex object (using placement new) +#endif // #ifdef _WIN32 +} RmtMutexInternal; + +// create a mutex +RmtErrorCode RmtMutexCreate(RmtMutex* mutex, const char* name) +{ + RMT_ASSERT_MESSAGE(mutex, "Parameter mutex is NULL."); + RMT_RETURN_ON_ERROR(mutex, RMT_ERROR_INVALID_POINTER); + + RmtMutexInternal* mutex_internal = (RmtMutexInternal*)mutex; + + // if this fails it means the external opaque structure isn't large enough for an internal mutex. + RMT_STATIC_ASSERT(sizeof(RmtMutex) >= sizeof(RmtMutexInternal)); + +#ifdef _WIN32 + /* create the mutex for win32 */ + mutex_internal->handle = CreateMutexA(NULL, FALSE, name); + + RMT_RETURN_ON_ERROR(mutex_internal->handle != NULL, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#else + RMT_UNUSED(name); + mutex_internal->mutex = new (&mutex_internal->buffer) std::mutex; +#endif // #ifdef _WIN32 + + return RMT_OK; +} + +RmtErrorCode RmtMutexLock(RmtMutex* mutex) +{ + RMT_ASSERT_MESSAGE(mutex, "Parameter mutex is NULL."); + RMT_RETURN_ON_ERROR(mutex, RMT_ERROR_INVALID_POINTER); + + RmtMutexInternal* mutex_internal = (RmtMutexInternal*)mutex; + +#ifdef _WIN32 + const DWORD error_code = WaitForSingleObject(mutex_internal->handle, INFINITE); + RMT_RETURN_ON_ERROR(error_code == WAIT_OBJECT_0, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#else + mutex_internal->mutex->lock(); +#endif // #ifdef _WIN32 + + return RMT_OK; +} + +RmtErrorCode RmtMutexUnlock(RmtMutex* mutex) +{ + RMT_ASSERT_MESSAGE(mutex, "Parameter mutex is NULL."); + RMT_RETURN_ON_ERROR(mutex, RMT_ERROR_INVALID_POINTER); + + RmtMutexInternal* mutex_internal = (RmtMutexInternal*)mutex; + +#ifdef _WIN32 + BOOL error_code = ReleaseMutex(mutex_internal->handle); + RMT_RETURN_ON_ERROR(error_code, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#else + mutex_internal->mutex->unlock(); +#endif // #ifdef _WIN32 + + return RMT_OK; +} + +RmtErrorCode RmtMutexDestroy(RmtMutex* mutex) +{ + RMT_ASSERT_MESSAGE(mutex, "Parameter mutex is NULL."); + RMT_RETURN_ON_ERROR(mutex, RMT_ERROR_INVALID_POINTER); + + RmtMutexInternal* mutex_internal = (RmtMutexInternal*)mutex; + +#ifdef _WIN32 + BOOL error_code = CloseHandle(mutex_internal->handle); + RMT_RETURN_ON_ERROR(error_code, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#else + mutex_internal->mutex->~mutex(); +#endif // #ifdef _WIN32 + + return RMT_OK; +} diff --git a/source/backend/rmt_mutex.h b/source/backend/rmt_mutex.h new file mode 100644 index 0000000..9b83516 --- /dev/null +++ b/source/backend/rmt_mutex.h @@ -0,0 +1,81 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief +//============================================================================= + +#ifndef RMV_BACKEND_RMT_MUTEX_H_ +#define RMV_BACKEND_RMT_MUTEX_H_ + +#include + +/// Define the size of a mutex handle structure (in DWORDS). +#define RMT_MUTEX_SIZE (24) + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A handle for a mutex. +typedef struct RmtMutex +{ + uint32_t data[RMT_MUTEX_SIZE]; +} RmtMutex; + +/// Create a new mutex. +/// +/// @param [in] mutex A pointer to a RmtMutex structure. +/// @param [in] name The name of the mutex. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter mutex was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtMutexCreate(RmtMutex* mutex, const char* name); + +/// Lock (acquire) the mutex. +/// +/// @param [in] mutex A pointer to a RmtMutex structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter threadEvent was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtMutexLock(RmtMutex* mutex); + +/// Unlock (release) the mutex. +/// +/// @param [in] mutex A pointer to a RmtMutex structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter threadEvent was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtMutexUnlock(RmtMutex* mutex); + +/// Destroy the mutex. Free up any resources the mutex was using +/// +/// @param [in] mutex A pointer to a RmtMutex structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter threadEvent was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtMutexDestroy(RmtMutex* mutex); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_MUTEX_H diff --git a/source/backend/rmt_page_table.cpp b/source/backend/rmt_page_table.cpp new file mode 100644 index 0000000..e8a0b08 --- /dev/null +++ b/source/backend/rmt_page_table.cpp @@ -0,0 +1,382 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation of the page table helper functions. +//============================================================================= + +#include "rmt_page_table.h" +#include "rmt_address_helper.h" +#include +#include "rmt_virtual_allocation_list.h" +#include "rmt_resource_list.h" +#include // for memcpy + +// helper function to decompose an address +static void DecomposeAddress(RmtGpuAddress virtual_address, + int32_t* out_level0_radix, + int32_t* out_level1_radix, + int32_t* out_level2_radix, + int32_t* out_level3_radix) +{ + RMT_ASSERT((virtual_address >> 48) == 0); + + // Decompose the virtual address into 4 radixes for looking into the trie. The format of the + // address is 10:10:8:8. First of all we have to calculate a VA page offset as the trie + // structure deals at 4KiB page granularity. + const uint64_t virtual_page_offset = virtual_address >> 12; + const int32_t level0_radix = (virtual_page_offset >> 26) & 0x3ff; + const int32_t level1_radix = (virtual_page_offset >> 16) & 0x3ff; + const int32_t level2_radix = (virtual_page_offset >> 8) & 0xff; + const int32_t level3_radix = (virtual_page_offset >> 0) & 0xff; + + // check the radix never goes out of range, this will stomp memory. + RMT_ASSERT(level0_radix < RMT_PAGE_DIRECTORY_LEVEL_0_SIZE); + RMT_ASSERT(level1_radix < RMT_PAGE_DIRECTORY_LEVEL_1_SIZE); + RMT_ASSERT(level2_radix < RMT_PAGE_DIRECTORY_LEVEL_2_SIZE); + RMT_ASSERT(level3_radix < RMT_PAGE_DIRECTORY_LEVEL_3_SIZE); + + *out_level0_radix = level0_radix; + *out_level1_radix = level1_radix; + *out_level2_radix = level2_radix; + *out_level3_radix = level3_radix; +} + +// helper function to work out the physical heap from an physical address. +static RmtHeapType GetHeapTypeFromAddress(RmtPageTable* page_table, RmtGpuAddress physical_address) +{ + // Calculate the heap that we are updating. + RmtHeapType current_segment = kRmtHeapTypeUnknown; + if (physical_address == 0) + { + current_segment = kRmtHeapTypeSystem; + } + else + { + for (int32_t current_segment_index = 0; current_segment_index <= kRmtHeapTypeInvisible; ++current_segment_index) + { + const RmtGpuAddress end_address = + page_table->segment_info[current_segment_index].base_address + page_table->segment_info[current_segment_index].size; + if (page_table->segment_info[current_segment_index].base_address <= physical_address && physical_address < end_address) + { + current_segment = (RmtHeapType)current_segment_index; + break; + } + } + } + + return current_segment; +} + +// update mapping for a single 4KB page. +static void UpdateMappingForSingle4KPage(RmtPageTable* page_table, + int32_t level0_radix, + int32_t level1_radix, + int32_t level2_radix, + int32_t level3_radix, + RmtGpuAddress physical_address, + bool is_unmapping) +{ + // The first three nodes have a similar idea that they are implementing. If we didn't + // already have a level 1 node for this radix, then we can allocate one now. When we + // first allocate a node, we should set all pointers to the next page directory level to NULL. + RmtPageDirectoryLevel1* level1 = page_table->level0[level0_radix]; + if (level1 == nullptr) + { + level1 = (RmtPageDirectoryLevel1*)RmtPoolAllocate(&page_table->level1_allocator); + RMT_ASSERT(level1); + for (int32_t current_page_directory_level_index = 0; current_page_directory_level_index < RMT_PAGE_DIRECTORY_LEVEL_1_SIZE; + ++current_page_directory_level_index) + { + level1->page_directory[current_page_directory_level_index] = NULL; + } + page_table->level0[level0_radix] = level1; + } + + RmtPageDirectoryLevel2* level2 = level1->page_directory[level1_radix]; + if (level2 == nullptr) + { + level2 = (RmtPageDirectoryLevel2*)RmtPoolAllocate(&page_table->level2_allocator); + RMT_ASSERT(level2); + for (int32_t current_page_directory_level_index = 0; current_page_directory_level_index < RMT_PAGE_DIRECTORY_LEVEL_2_SIZE; + ++current_page_directory_level_index) + { + level2->page_directory[current_page_directory_level_index] = NULL; + } + level1->page_directory[level1_radix] = level2; + } + + RmtPageDirectoryLevel3* level3 = level2->page_directory[level2_radix]; + if (level3 == nullptr) + { + level3 = (RmtPageDirectoryLevel3*)RmtPoolAllocate(&page_table->level3_allocator); + RMT_ASSERT(level3); + for (int32_t current_page_directory_level_index = 0; current_page_directory_level_index < RMT_MAXIMUM_PAGE_TABLE_LEAF_SIZE; + ++current_page_directory_level_index) + { + level3->physical_addresses[current_page_directory_level_index] = (uint8_t)NULL; + } + for (size_t current_bit_field_byte = 0; current_bit_field_byte < sizeof(level3->is_mapped); ++current_bit_field_byte) + { + level3->is_mapped[current_bit_field_byte] = 0; + } + level2->page_directory[level2_radix] = level3; + } + + // Update specific slot in the level3 node. + const int32_t byte_offset = level3_radix / 8; + const int32_t bit_offset = level3_radix % 8; + const uint8_t mask = (1 << bit_offset); + + // remove from mapped totals if it was previously mapped. + bool was_previously_mapped = ((level3->is_mapped[byte_offset] & mask) == mask); + if (was_previously_mapped) + { + const RmtGpuAddress previous_physical_address = ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 0] << 40) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 1] << 32) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 2] << 24) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 3] << 16) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 4] << 8) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 5] << 0); + + const RmtHeapType previous_physical_heap_type = GetHeapTypeFromAddress(page_table, previous_physical_address); + RMT_ASSERT(previous_physical_heap_type != kRmtHeapTypeUnknown); + + if (page_table->mapped_per_heap[previous_physical_heap_type] > RmtGetPageSize(kRmtPageSize4Kb)) + { + page_table->mapped_per_heap[previous_physical_heap_type] -= RmtGetPageSize(kRmtPageSize4Kb); + } + else + { + page_table->mapped_per_heap[previous_physical_heap_type] = 0; + } + } + + // Store the physical address. + if (!is_unmapping) + { + // TOOD: Check if it was previously mapped, and if so, find out which segment it was in. + level3->physical_addresses[(level3_radix * 6) + 0] = ((physical_address >> 40) & 0xff); + level3->physical_addresses[(level3_radix * 6) + 1] = ((physical_address >> 32) & 0xff); + level3->physical_addresses[(level3_radix * 6) + 2] = ((physical_address >> 24) & 0xff); + level3->physical_addresses[(level3_radix * 6) + 3] = ((physical_address >> 16) & 0xff); + level3->physical_addresses[(level3_radix * 6) + 4] = ((physical_address >> 8) & 0xff); + level3->physical_addresses[(level3_radix * 6) + 5] = ((physical_address >> 0) & 0xff); + level3->is_mapped[byte_offset] |= mask; + + const RmtHeapType current_physical_heap_type = GetHeapTypeFromAddress(page_table, physical_address); + page_table->mapped_per_heap[current_physical_heap_type] += RmtGetPageSize(kRmtPageSize4Kb); + } + else + { + level3->physical_addresses[(level3_radix * 6) + 0] = 0; + level3->physical_addresses[(level3_radix * 6) + 1] = 0; + level3->physical_addresses[(level3_radix * 6) + 2] = 0; + level3->physical_addresses[(level3_radix * 6) + 3] = 0; + level3->physical_addresses[(level3_radix * 6) + 4] = 0; + level3->physical_addresses[(level3_radix * 6) + 5] = 0; + level3->is_mapped[byte_offset] &= ~mask; + } +} + +// Initialize the page table. +RmtErrorCode RmtPageTableInitialize(RmtPageTable* page_table, const RmtSegmentInfo* segment_info, int32_t segment_info_count, uint64_t target_process_id) +{ + RMT_ASSERT(page_table); + RMT_RETURN_ON_ERROR(page_table, RMT_ERROR_INVALID_POINTER); + + // copy the segment info over. + memcpy(page_table->segment_info, segment_info, sizeof(RmtSegmentInfo) * segment_info_count); + page_table->segment_info_count = segment_info_count; + page_table->target_process_id = target_process_id; + + // Initialize the level 1 node pointers to be NULL to denote an empty page table. + for (int32_t current_level0_node_index = 0; current_level0_node_index < RMT_PAGE_DIRECTORY_LEVEL_0_SIZE; ++current_level0_node_index) + { + page_table->level0[current_level0_node_index] = NULL; + } + + // Initialize the allocators for level 1, 2 and 3 nodes. + RmtErrorCode error_code = RMT_OK; + error_code = RmtPoolInitialize(&page_table->level1_allocator, page_table->level1_nodes, sizeof(page_table->level1_nodes), sizeof(RmtPageDirectoryLevel1)); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + error_code = RmtPoolInitialize(&page_table->level2_allocator, page_table->level2_nodes, sizeof(page_table->level2_nodes), sizeof(RmtPageDirectoryLevel2)); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + error_code = RmtPoolInitialize(&page_table->level3_allocator, page_table->level3_nodes, sizeof(page_table->level3_nodes), sizeof(RmtPageDirectoryLevel3)); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // clear per-heap byte tracking. + for (int32_t current_heap_index = 0; current_heap_index < kRmtHeapTypeCount; ++current_heap_index) + { + page_table->mapped_per_heap[current_heap_index] = 0; + } + + return RMT_OK; +} + +// Map virtual memory. +RmtErrorCode RmtPageTableUpdateMemoryMappings(RmtPageTable* page_table, + RmtGpuAddress virtual_address, + RmtGpuAddress physical_address, + size_t size_in_pages, + RmtPageSize page_size, + bool is_unmapping, + RmtPageTableUpdateType update_type, + uint64_t process_id) +{ + RMT_UNUSED(process_id); + + RMT_ASSERT(page_table); + RMT_RETURN_ON_ERROR(page_table, RMT_ERROR_INVALID_POINTER); + + // For now, we ignore anything that's not a regular update. + if (update_type != kRmtPageTableUpdateTypeUpdate) + { + return RMT_OK; + } + + // For a regular mapping operation these must be valid. + RMT_ASSERT(size_in_pages > 0); + + // NOTE: process filtering, driver doesn't seem to be producing >1 process currently? + if (process_id <= 1) + { + return RMT_OK; + } + + // Calculate the number of 4KiB pages we require. + const uint64_t page_size_4kib = RmtGetPageSize(kRmtPageSize4Kb); + const uint64_t size_of_page = RmtGetPageSize(page_size); + const uint64_t size_in_bytes = size_in_pages * size_of_page; + const int32_t size_in_4k_pages = (int32_t)(size_in_bytes / page_size_4kib); + RMT_ASSERT(size_in_4k_pages * 4 * 1024 == size_in_bytes); // make sure no precision lost in division (4KB should always be a factor of other page sizes). + + // Update each page's mapping in the page table. + RmtGpuAddress current_virtual_address = virtual_address; + RmtGpuAddress current_physical_address = physical_address; + for (int32_t current_page_index = 0; current_page_index < size_in_4k_pages; ++current_page_index) + { + int32_t level0_radix; + int32_t level1_radix; + int32_t level2_radix; + int32_t level3_radix = 0; + DecomposeAddress(current_virtual_address, &level0_radix, &level1_radix, &level2_radix, &level3_radix); + + UpdateMappingForSingle4KPage(page_table, level0_radix, level1_radix, level2_radix, level3_radix, current_physical_address, is_unmapping); + + current_virtual_address += page_size_4kib; + if (!is_unmapping && (current_physical_address == 0U)) + { + current_physical_address = 0; // this means mapped in system RAM. + } + else + { + current_physical_address += page_size_4kib; + } + } + + return RMT_OK; +} + +// Get the physical address for a specified virtual address. +RmtErrorCode RmtPageTableGetPhysicalAddressForVirtualAddress(const RmtPageTable* page_table, RmtGpuAddress virtual_address, RmtGpuAddress* out_physical_address) +{ + RMT_RETURN_ON_ERROR(page_table, RMT_ERROR_INVALID_POINTER); + + int32_t level0_radix; + int32_t level1_radix; + int32_t level2_radix; + int32_t level3_radix = 0; + DecomposeAddress(virtual_address, &level0_radix, &level1_radix, &level2_radix, &level3_radix); + + const RmtPageDirectoryLevel1* level1 = page_table->level0[level0_radix]; + RMT_RETURN_ON_ERROR(level1, RMT_ERROR_ADDRESS_NOT_MAPPED); + + const RmtPageDirectoryLevel2* level2 = level1->page_directory[level1_radix]; + RMT_RETURN_ON_ERROR(level2, RMT_ERROR_ADDRESS_NOT_MAPPED); + + const RmtPageDirectoryLevel3* level3 = level2->page_directory[level2_radix]; + RMT_RETURN_ON_ERROR(level3, RMT_ERROR_ADDRESS_NOT_MAPPED); + + // Each physical address value at level 3 of the page map trie is 36 bits in size. + const int32_t byte_offset = level3_radix / 8; + const int32_t bit_offset = level3_radix % 8; + const uint8_t mask = (1 << bit_offset); + if ((level3->is_mapped[byte_offset] & mask) != mask) + { + return RMT_ERROR_ADDRESS_NOT_MAPPED; + } + + // Look up the physical address value from the level 3 node. + if (out_physical_address != nullptr) + { + *out_physical_address = ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 0] << 40) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 1] << 32) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 2] << 24) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 3] << 16) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 4] << 8) | + ((RmtGpuAddress)level3->physical_addresses[(level3_radix * 6) + 5] << 0); + } + + return RMT_OK; +} + +// check if entire resource physically +bool RmtPageTableIsEntireResourcePhysicallyMapped(const RmtPageTable* page_table, const RmtResource* resource) +{ + RMT_RETURN_ON_ERROR(page_table, false); + RMT_RETURN_ON_ERROR(resource, false); + + const RmtGpuAddress end_virtual_address = resource->address + resource->size_in_bytes; + RmtGpuAddress current_virtual_address = resource->address; + + // no address, no size, or dangling should be ignored. + if (resource->address == 0 || resource->size_in_bytes == 0 || (resource->flags & kRmtResourceFlagDangling) == kRmtResourceFlagDangling) + { + return false; + } + + // walk through the resource, and check each page. + while (current_virtual_address < end_virtual_address) + { + RmtGpuAddress physical_address = 0; + const RmtErrorCode error_code = RmtPageTableGetPhysicalAddressForVirtualAddress(page_table, current_virtual_address, &physical_address); + if (error_code == RMT_ERROR_ADDRESS_NOT_MAPPED) + { + return false; + } + + current_virtual_address += RmtGetPageSize(kRmtPageSize4Kb); + } + + return true; +} + +// check if entire allocation is mapped. +bool RmtPageTableIsEntireVirtualAllocationPhysicallyMapped(const RmtPageTable* page_table, const RmtVirtualAllocation* virtual_allocation) +{ + RMT_RETURN_ON_ERROR(page_table, false); + RMT_RETURN_ON_ERROR(virtual_allocation, false); + + const RmtGpuAddress end_virtual_address = + virtual_allocation->base_address + RmtGetAllocationSizeInBytes(virtual_allocation->size_in_4kb_page, kRmtPageSize4Kb); + RmtGpuAddress current_virtual_address = virtual_allocation->base_address; + + // walk through the virtual resource, and check each page. + while (current_virtual_address < end_virtual_address) + { + RmtGpuAddress physical_address = 0; + const RmtErrorCode error_code = RmtPageTableGetPhysicalAddressForVirtualAddress(page_table, current_virtual_address, &physical_address); + if (error_code == RMT_ERROR_ADDRESS_NOT_MAPPED) + { + return false; + } + + // move ahead by the page mapping size. + current_virtual_address += RmtGetPageSize(kRmtPageSize4Kb); + } + + return true; +} diff --git a/source/backend/rmt_page_table.h b/source/backend/rmt_page_table.h new file mode 100644 index 0000000..6270c8c --- /dev/null +++ b/source/backend/rmt_page_table.h @@ -0,0 +1,189 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Definition of structures and functions for a multi-level page table. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_PAGE_TABLE_H_ +#define RMV_BACKEND_RMT_PAGE_TABLE_H_ + +#include +#include +#include "rmt_configuration.h" +#include "rmt_pool.h" +#include "rmt_segment_info.h" + +/// The number of entries in the multi-level page directory for level 0. Enough for 10-bit radix. +#define RMT_PAGE_DIRECTORY_LEVEL_0_SIZE (1024) + +/// The number of entries in the multi-level page directory for level 1. Enough for 10-bit radix. +#define RMT_PAGE_DIRECTORY_LEVEL_1_SIZE (1024) + +/// The number of entries in the multi-level page directory for level 2. Enough for 8-bit radix. +#define RMT_PAGE_DIRECTORY_LEVEL_2_SIZE (256) + +/// The number of entries in the multi-level page directory for level 3. Enough for 8-bit radix. +#define RMT_PAGE_DIRECTORY_LEVEL_3_SIZE (256) + +/// The size (in bytes) of a set of 256 x 48-bit physical offsets for the leaf node. +/// NOTE: This could be optimized down to 36-bit later. +#define RMT_MAXIMUM_PAGE_TABLE_LEAF_SIZE (RMT_PAGE_DIRECTORY_LEVEL_3_SIZE * 6) + +/// The maximum size the page table can encode in physical address space. +#define RMT_PAGE_TABLE_MAX_SIZE_OF_PHYSICAL_SPACE_IN_BYTES (16 * 1024 * 1024 * 1024) + +/// The number of level 0 nodes to keep space for. +#define RMT_PAGE_DIRECTORY_LEVEL_0_COUNT 1024 + +/// The number of level 1 nodes to keep space for. +#define RMT_PAGE_DIRECTORY_LEVEL_1_COUNT 1024 + +/// The number of level 2 nodes to keep space for. +#define RMT_PAGE_DIRECTORY_LEVEL_2_COUNT 4096 + +/// The number of level 3 nodes to keep space for. This value maps exactly to number of MB of VA space than can be mapped at once. +#define RMT_PAGE_DIRECTORY_LEVEL_3_COUNT (64 * 1024) + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtVirtualAllocation RmtVirtualAllocation; +typedef struct RmtResource RmtResource; + +/// A structure encapsulating the leaf node of a page table. +typedef struct RmtPageDirectoryLevel3 +{ + // NOTE: Moving to 36 bit offsets in here would save 25MB at 64GB. + + uint8_t physical_addresses[RMT_MAXIMUM_PAGE_TABLE_LEAF_SIZE]; ///< An array of bytes to store 256 x 48bit physical addresses. + uint8_t is_mapped[RMT_PAGE_DIRECTORY_LEVEL_3_SIZE / 8]; ///< A bit field indicating if the slot in physicalAddresses is used or not. +} RmtPageDirectoryLevel3; + +/// A structure to encapsulate a level 2 page directory structure. +typedef struct RmtPageDirectoryLevel2 +{ + RmtPageDirectoryLevel3* page_directory[RMT_PAGE_DIRECTORY_LEVEL_2_SIZE]; ///< An array of pointers to level 3 page directory structures. +} RmtPageDirectoryLevel2; + +/// A structure to encapsulate +typedef struct RmtPageDirectoryLevel1 +{ + RmtPageDirectoryLevel2* page_directory[RMT_PAGE_DIRECTORY_LEVEL_1_SIZE]; ///< An array of pointers to level 2 page directory structures. +} RmtPageDirectoryLevel1; + +/// A structure encapsulating a multi-level page table. +/// +/// This is implemented a trie data structure. The virtual address is decompossed into +/// a different size radix at each level of the tree. +/// +/// |XXXXXXXXXX|XXXXXXXXXX|XXXXXXXX|XXXXXXXX|XXXXXXXXXXXX| +/// |----------|----------|--------|--------|------------| +/// | | | | lower bits +/// Lvl.0 Lvl.1 Lvl.2 Lvl.3 +/// (10bit) (10bit) (8bit) (8bit) +/// +/// This means to traverse the tree we use the different parts of the virtual address +/// to index in to the array of pointers at the different tree levels. At the leaf of +/// the trie, we are storing a compacted array of 48bit physical address pointers. +/// +/// On top of this the RmtPageTable structure also implements a small +/// TLB-like cache, which keeps a small buffer of the most recently accessed Lvl. 3 +/// nodes from the trie. This means that iterating through virtual address space +/// will only result in a tree traversal every 1MB of VA range. +/// +typedef struct RmtPageTable +{ + RmtPageDirectoryLevel1* level0[RMT_PAGE_DIRECTORY_LEVEL_0_SIZE]; ///< An array of pointers to level 1 page directory structures. + + // Storage and allocators for level 2..3 structures and what not. + RmtPageDirectoryLevel1 level1_nodes[RMT_PAGE_DIRECTORY_LEVEL_1_COUNT]; ///< An array of RmtPageDirectoryLevel1 structures. + RmtPageDirectoryLevel2 level2_nodes[RMT_PAGE_DIRECTORY_LEVEL_2_COUNT]; ///< An array of RmtPageDirectoryLevel2 structures. + RmtPageDirectoryLevel3 level3_nodes[RMT_PAGE_DIRECTORY_LEVEL_3_COUNT]; ///< An array of RmtPageDirectoryLevel3 structures. + RmtPool level1_allocator; ///< A RmtPool allocator structure for level 1 nodes. + RmtPool level2_allocator; ///< A RmtPool allocator structure for level 2 nodes. + RmtPool level3_allocator; ///< A RmtPool allocator structure for level 3 nodes. + + uint64_t mapped_per_heap[kRmtHeapTypeCount]; ///< An array of uint64_t each containing the number of bytes per heap currently mapped. + RmtSegmentInfo segment_info[RMT_MAXIMUM_SEGMENTS]; ///< An array of segment information. + int32_t segment_info_count; ///< The number of segments. + + uint64_t target_process_id; ///< The process ID of the process we're tracing for UMD data. +} RmtPageTable; + +/// Initialize the page table. +/// +/// @param [in] page_table A pointer to a RmtPageTable structure to initialize. +/// @param [in] segment_info A pointer to an array of segment info structures. +/// @param [in] segment_info_count The number of segment info structures in the structures. +/// @param [in] target_process_id The target process being traced. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed as pageTable structure was NULL. +RmtErrorCode RmtPageTableInitialize(RmtPageTable* page_table, const RmtSegmentInfo* segment_info, int32_t segment_info_count, uint64_t target_process_id); + +/// Map some virtual memory to an underlaying physical range. +/// +/// If pageSize is set to RMT_PAGE_SIZE_UNAMPPED then this function will +/// interpret this as an unmapping operation and call rmtPageTableUnmap. +/// +/// @param [in] page_table A pointer to a RmtPageTable structure to update. +/// @param [in] virtual_address The virtual address of the page table mapping. +/// @param [in] physical_address The physical address that the virtual address is being mapped to. Only valid when pageSize is not RMT_PAGE_SIZE_UNMAPPED. +/// @param [in] size_in_pages The size of the the mapping specified in pages. The page size is determined by pageSize. +/// @param [in] page_size The size of each page being mapped. +/// @param [in] is_unmapping A boolean value indicating if the mapping update is an unmap operation. +/// @param [in] update_type The type of the update operation. +/// @param [in] process_id The process id. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed as pageTable structure was NULL. +/// @retval +/// RMT_ERROR_ADDRESS_ALREADY_MAPPED The operation failed because some memory in this range was already mapped. +RmtErrorCode RmtPageTableUpdateMemoryMappings(RmtPageTable* page_table, + RmtGpuAddress virtual_address, + RmtGpuAddress physical_address, + size_t size_in_pages, + RmtPageSize page_size, + bool is_unmapping, + RmtPageTableUpdateType update_type, + uint64_t process_id); + +/// Find the physical mapping for the specified virtual address. +/// +/// @param [in] page_table A pointer to a RmtPageTable structure to search. +/// @param [in] virtual_address The virtual address of the page table mapping. +/// @param [out] out_physical_address The physical address for the specified virtual address. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed as pageTable structure was NULL. +/// @retval +/// RMT_ERROR_ADDRESS_NOT_MAPPED The operation failed as virtualAddress was not mapped to a physical address. +RmtErrorCode RmtPageTableGetPhysicalAddressForVirtualAddress(const RmtPageTable* page_table, + RmtGpuAddress virtual_address, + RmtGpuAddress* out_physical_address); + +/// Check if a virtual allocation is completed backed by physical memory. +/// +/// @param [in] page_table A pointer to a RmtPageTable structure. +/// @param [in] virtual_allocation A pointer to the RmtVirtualAllocation structure to check the mapping of. +/// +bool RmtPageTableIsEntireVirtualAllocationPhysicallyMapped(const RmtPageTable* page_table, const RmtVirtualAllocation* virtual_allocation); + +/// Check if a resource is completed backed by physical memory. +/// +/// @param [in] page_table A pointer to a RmtPageTable structure. +/// @param [in] resource A pointer to the RmtResource structure to check the mapping of. +/// +bool RmtPageTableIsEntireResourcePhysicallyMapped(const RmtPageTable* page_table, const RmtResource* resource); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_PAGE_TABLE_H_ diff --git a/source/backend/rmt_physical_allocation_list.cpp b/source/backend/rmt_physical_allocation_list.cpp new file mode 100644 index 0000000..f6a0e08 --- /dev/null +++ b/source/backend/rmt_physical_allocation_list.cpp @@ -0,0 +1,201 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of the physical allocation list functions. +//============================================================================= + +#include "rmt_physical_allocation_list.h" +#include +#include // memcpy +#include // for sqrt + +// calculate the size of an allocation in bytes. +uint64_t RmtPhysicalAllocationGetSizeInBytes(const RmtPhysicalAllocation* physical_allocation) +{ + RMT_ASSERT(physical_allocation); + RMT_RETURN_ON_ERROR(physical_allocation, 0); + + return ((uint64_t)physical_allocation->size_in_4kb_page) << 12; +} + +// NOTE: likely that we are going to do more queries than insert/delete, may want to accelerate the lookup using a tree. + +static int32_t FindAllocationIndexForAddress(const RmtPhysicalAllocationList* physical_allocation_list, RmtGpuAddress address) +{ + for (int32_t current_allocation_index = 0; current_allocation_index < physical_allocation_list->allocation_count; ++current_allocation_index) + { + const RmtPhysicalAllocationInterval* current_allocation_interval = &physical_allocation_list->allocation_intervals[current_allocation_index]; + const int64_t size_in_bytes = ((uint64_t)current_allocation_interval->size_in_4kb_pages) << 12; + if (current_allocation_interval->base_address <= address && address < (current_allocation_interval->base_address + size_in_bytes)) + { + return current_allocation_index; + } + } + + return -1; +} + +// get the size of the buffer required for an allocation list, given a specific number of concurrent allocations. +size_t RmtPhysicalAllocationListGetBufferSize(int32_t maximum_concurrent_allocations) +{ + return maximum_concurrent_allocations * (sizeof(RmtPhysicalAllocationInterval) + sizeof(RmtPhysicalAllocation)); +} + +// initialize the data structure. +RmtErrorCode RmtPhysicalAllocationListInitialize(RmtPhysicalAllocationList* physical_allocation_list, + void* buffer, + size_t buffer_size, + int32_t maximum_concurrent_allocations) +{ + RMT_ASSERT(physical_allocation_list); + RMT_RETURN_ON_ERROR(physical_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(buffer, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(buffer_size, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR(RmtPhysicalAllocationListGetBufferSize(maximum_concurrent_allocations) <= buffer_size, RMT_ERROR_INVALID_SIZE); + + // dice up the buffer + physical_allocation_list->allocation_intervals = (RmtPhysicalAllocationInterval*)buffer; + physical_allocation_list->allocation_details = + (RmtPhysicalAllocation*)(((uintptr_t)buffer) + (maximum_concurrent_allocations * sizeof(RmtPhysicalAllocationInterval))); + physical_allocation_list->allocation_count = 0; + physical_allocation_list->next_allocation_guid = 0; + physical_allocation_list->maximum_concurrent_allocations = maximum_concurrent_allocations; + + return RMT_OK; +} + +// add an allocation to the list. +RmtErrorCode RmtPhysicalAllocationListAddAllocation(RmtPhysicalAllocationList* physical_allocation_list, + uint64_t timestamp, + RmtGpuAddress address, + int32_t size_in_4kb_pages, + RmtHeapType heap_type, + RmtProcessId process_id) +{ + RMT_ASSERT(physical_allocation_list); + RMT_RETURN_ON_ERROR(physical_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(size_in_4kb_pages, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR((address >> 12) + size_in_4kb_pages < RMT_PAGE_TABLE_MAX_SIZE, RMT_ERROR_INVALID_SIZE); + + const int32_t next_allocation_index = physical_allocation_list->allocation_count++; + + // fill out the allocation interval + RmtPhysicalAllocationInterval* allocation_interval = &physical_allocation_list->allocation_intervals[next_allocation_index]; + allocation_interval->base_address = address; + allocation_interval->size_in_4kb_pages = size_in_4kb_pages; + + // fill out the details. + RmtPhysicalAllocation* allocation_details = &physical_allocation_list->allocation_details[next_allocation_index]; + allocation_details->base_address = address; + allocation_details->size_in_4kb_page = size_in_4kb_pages; + allocation_details->guid = physical_allocation_list->next_allocation_guid++; + allocation_details->flags = 0; + allocation_details->timestamp = timestamp; + allocation_details->process_id = process_id; + allocation_details->heap_type = heap_type; + + return RMT_OK; +} + +// tradtional free of an allocation from the list. +RmtErrorCode RmtPhysicalAllocationListDiscardAllocation(RmtPhysicalAllocationList* physical_allocation_list, RmtGpuAddress address) +{ + RMT_ASSERT(physical_allocation_list); + RMT_RETURN_ON_ERROR(physical_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(physical_allocation_list->allocation_count, RMT_ERROR_NO_ALLOCATION_FOUND); + + // find the allocation index. + const int32_t index = FindAllocationIndexForAddress(physical_allocation_list, address); + if (index < 0) + { + return RMT_ERROR_NO_ALLOCATION_FOUND; + } + + const int32_t last_physica_allocation_index = physical_allocation_list->allocation_count - 1; + + // special case for deleting the last element of the list. + if (index == last_physica_allocation_index) + { + physical_allocation_list->allocation_count--; + return RMT_OK; + } + + // copy over. + RmtPhysicalAllocation* current_physical_allocation = &physical_allocation_list->allocation_details[index]; + const RmtPhysicalAllocation* last_physical_allocation = &physical_allocation_list->allocation_details[last_physica_allocation_index]; + memcpy(current_physical_allocation, last_physical_allocation, sizeof(RmtPhysicalAllocation)); + physical_allocation_list->allocation_count--; + + return RMT_OK; +} + +// transfer of memory to system. +RmtErrorCode RmtPhysicaAllocationListTransferAllocation(RmtPhysicalAllocationList* physical_allocation_list, RmtGpuAddress address) +{ + RMT_ASSERT(physical_allocation_list); + RMT_RETURN_ON_ERROR(physical_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(physical_allocation_list->allocation_count, RMT_ERROR_NO_ALLOCATION_FOUND); + + const int32_t index = FindAllocationIndexForAddress(physical_allocation_list, address); + if (index < 0) + { + return RMT_ERROR_NO_ALLOCATION_FOUND; + } + + RmtPhysicalAllocation* allocation = &physical_allocation_list->allocation_details[index]; + allocation->flags |= kRmtPhysicalAllocationFlagTransferred; + + return RMT_OK; +} + +// find an allocation for an address. +RmtErrorCode RmtPhysicalAllocationListGetAllocationForAddress(const RmtPhysicalAllocationList* physical_allocation_list, + RmtGpuAddress address, + const RmtPhysicalAllocation** out_allocation) +{ + RMT_ASSERT(physical_allocation_list); + RMT_ASSERT(out_allocation); + RMT_RETURN_ON_ERROR(physical_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_allocation, RMT_ERROR_INVALID_POINTER); + + const int32_t index = FindAllocationIndexForAddress(physical_allocation_list, address); + if (index < 0) + { + return RMT_ERROR_NO_ALLOCATION_FOUND; + } + + const RmtPhysicalAllocation* allocation = &physical_allocation_list->allocation_details[index]; + *out_allocation = allocation; + return RMT_OK; +} + +RmtErrorCode RmtPhysicalAllocationListGetAllocationAtIndex(const RmtPhysicalAllocationList* physical_allocation_list, + int32_t index, + const RmtPhysicalAllocation** out_allocation) +{ + RMT_ASSERT(physical_allocation_list); + RMT_ASSERT(out_allocation); + RMT_RETURN_ON_ERROR(physical_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_allocation, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(0 <= index && index < physical_allocation_list->allocation_count, RMT_ERROR_INDEX_OUT_OF_RANGE); + + const RmtPhysicalAllocation* allocation = &physical_allocation_list->allocation_details[index]; + *out_allocation = allocation; + return RMT_OK; +} + +uint64_t RmtPhysicalAllocationListGetTotalSizeInBytes(const RmtPhysicalAllocationList* physical_allocation_list) +{ + RMT_ASSERT(physical_allocation_list); + RMT_RETURN_ON_ERROR(physical_allocation_list, 0); + + uint64_t total_size_in_bytes = 0; + + for (int32_t current_allocation_index = 0; current_allocation_index < physical_allocation_list->allocation_count; ++current_allocation_index) + { + const RmtPhysicalAllocation* current_physical_allocation = &physical_allocation_list->allocation_details[current_allocation_index]; + total_size_in_bytes += RmtPhysicalAllocationGetSizeInBytes(current_physical_allocation); + } + + return total_size_in_bytes; +} diff --git a/source/backend/rmt_physical_allocation_list.h b/source/backend/rmt_physical_allocation_list.h new file mode 100644 index 0000000..09aca9f --- /dev/null +++ b/source/backend/rmt_physical_allocation_list.h @@ -0,0 +1,162 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Structures and functions for managing a physical allocation list. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_PHYSICAL_ALLOCATION_LIST_H_ +#define RMV_BACKEND_RMT_PHYSICAL_ALLOCATION_LIST_H_ + +#include +#include +#include "rmt_configuration.h" +#include "rmt_pool.h" +#include "rmt_format.h" + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A structure encapsulating critical allocation identifier information. +typedef struct RmtPhysicalAllocationInterval +{ + RmtGpuAddress base_address; ///< The base address of the physical allocation. + int32_t size_in_4kb_pages; ///< The size of the allocation in 4KiB pages. + int32_t padding; ///< Padding for the node. +} RmtPhysicalAllocationInterval; + +/// An enumeration of all flags for physical allocations. +typedef enum RmtPhysicaAllocationFlags +{ + kRmtPhysicalAllocationFlagTransferred = 1 << 0, ///< Indicates that the physical allocation has previously been transfered. +} RmtPhysicaAllocationFlags; + +/// A structure encapsulating extra details about a physical allocation. +typedef struct RmtPhysicalAllocation +{ + uint64_t base_address; ///< The base address of the physical allocation. + int32_t size_in_4kb_page; ///< The size of the physical allocation. + int32_t guid; ///< A GUID for the this physical allocation. + uint32_t flags; ///< A set of flags for the physical allocation. + + uint64_t timestamp; ///< The timestamp when the physical allocation was made. + RmtProcessId process_id; ///< The ID of the process which made this physical allocation. + RmtHeapType heap_type; ///< The type of heap the physical allocation resides in. +} RmtPhysicalAllocation; + +/// Get the size (in bytes) of a physical allocation. +/// +/// @param [in] physical_allocation A pointer to a RmtPhysicalAllocation structure. +/// +/// @returns +/// The size (in bytes) of the physicalAllocation. +uint64_t RmtPhysicalAllocationGetSizeInBytes(const RmtPhysicalAllocation* physical_allocation); + +/// A structure encapsulating a list of allocations. +typedef struct RmtPhysicalAllocationList +{ + RmtPhysicalAllocationInterval* allocation_intervals; ///< A buffer of allocation intervals. + RmtPhysicalAllocation* allocation_details; ///< A buffer of extra allocation details. + int32_t allocation_count; ///< The number of live allocations in the list. + int32_t next_allocation_guid; ///< The next allocation GUID to assign. + int32_t maximum_concurrent_allocations; ///< The maximum number of concurrent allocations. +} RmtPhysicalAllocationList; + +/// Calculate the size of the workling buffer required for a specific number of concurrent allocations. +/// +/// @param [in] maximum_concurrent_allocations The maximum number of concurrent allocations that can be in flight at any one time. +/// +/// @returns +/// The size of the physical allocation list buffer that is required to initialize a RmtPhysicalAllocationList structure using rmtPhysicalAllocationListInitialize. +size_t RmtPhysicalAllocationListGetBufferSize(int32_t maximum_concurrent_allocations); + +/// Initialize the physical allocation list. +/// +/// @param [in] physical_allocation_list A pointer to a RmtPhysicalAllocationList structure. +/// @param [in] buffer A pointer to a memory buffer to use for the list contents. +/// @param [in] buffer_size The size (in bytes) of the memory region pointed to by buffer. +/// @param [in] maximum_concurrent_allocations The maximum number of concurrent allocations that can be in flight at any one time. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// @RMT_ERROR_INVALID_POINTER The operation failed because physicalAllocationList or buffer was NULL. +/// @retval +/// @RMT_ERROR_INVALID_SIZE The operation failed because buffer was not large enough to contain the number of concurrent allocations specified. +RmtErrorCode RmtPhysicalAllocationListInitialize(RmtPhysicalAllocationList* physical_allocation_list, + void* buffer, + size_t buffer_size, + int32_t maximum_concurrent_allocations); + +/// Add a physical allocation to the list. +/// +/// @param [in] physical_allocation_list A pointer to a RmtPhysicalAllocationList structure. +/// @param [in] timestamp The time at which the allocation occurred. +/// @param [in] address The physical allocation address to add. +/// @param [in] size_in_4kb_pages The size of the allocation in 4KiB page chunks. +/// @param [in] heap_type The type of heap the physical address is in. +/// @param [in] process_id The process which allocated this physical memory. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// @RMT_ERROR_INVALID_POINTER The operation failed because physicalAllocationList was NULL. +RmtErrorCode RmtPhysicalAllocationListAddAllocation(RmtPhysicalAllocationList* physical_allocation_list, + uint64_t timestamp, + RmtGpuAddress address, + int32_t size_in_4kb_pages, + RmtHeapType heap_type, + RmtProcessId process_id); + +/// Remove a physical allocation from the list. +RmtErrorCode RmtPhysicalAllocationListDiscardAllocation(RmtPhysicalAllocationList* physical_allocation_list, RmtGpuAddress address); + +/// Find base address of a physical allocation from address. +/// +/// @param [in] physical_allocation_list A pointer to a RmtPhysicalAllocationList structure. +/// @param [in] address The physical allocation address to search for. +/// @param [out] out_allocation The address of a pointer which will be filled to point at an RmtPhysicalAllocation structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// @RMT_ERROR_INVALID_POINTER The operation failed because physicalAllocationList or outAllocation was NULL. +RmtErrorCode RmtPhysicalAllocationListGetAllocationForAddress(const RmtPhysicalAllocationList* physical_allocation_list, + RmtGpuAddress address, + const RmtPhysicalAllocation** out_allocation); + +/// Get a physical allocation at a specific index. +/// +/// @param [in] physical_allocation_list A pointer to a RmtPhysicalAllocationList structure. +/// @paran [in] index The index of the physical allocation to return. +/// @param [out] out_allocation The address of a pointer to a RmtPhysicalAllocationList structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because physicalAllocationList or outAllocation was NULL. +RmtErrorCode RmtPhysicalAllocationListGetAllocationAtIndex(const RmtPhysicalAllocationList* physical_allocation_list, + int32_t index, + const RmtPhysicalAllocation** out_allocation); + +/// Get the total size (in bytes) of the memory in a physical allocation list. +/// +/// @param [in] physical_allocation_list A pointer to a RmtPhysicalAllocationList structure. +/// +/// @returns +/// The size (in bytes) of all physical allocations contained in physicalAllocationList. +uint64_t RmtPhysicalAllocationListGetTotalSizeInBytes(const RmtPhysicalAllocationList* physical_allocation_list); + +/// Get the total size (in bytes) of the memory in a physical allocation list for a specific process. +/// +/// @param [in] physical_allocation_list A pointer to a RmtPhysicalAllocationList structure. +/// @param [in] process_id The 32bit process ID. +/// +/// @returns +/// The size (in bytes) of all physical allocations contained in physicalAllocationList. +uint64_t RmtPhysicalAllocationListGetTotalSizeInBytesForProcess(const RmtPhysicalAllocationList* physical_allocation_list, RmtProcessId process_id); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_PHYSICAL_ALLOCATION_LIST_H_ diff --git a/source/backend/rmt_pool.cpp b/source/backend/rmt_pool.cpp new file mode 100644 index 0000000..4e2e592 --- /dev/null +++ b/source/backend/rmt_pool.cpp @@ -0,0 +1,74 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of functions for a fixed-size pool memory allocator. +//============================================================================= + +#include "rmt_pool.h" +#include + +#define POOL_ALLOC_TRASH_DEALLOCATED_MEMORY +#ifdef POOL_ALLOC_TRASH_DEALLOCATED_MEMORY +#include // for memset() +#endif // #ifdef POOL_ALLOC_TRASH_DEALLOCATED_MEMORY + +// initialize the pool +RmtErrorCode RmtPoolInitialize(RmtPool* pool, void* buffer, size_t buffer_size, size_t block_size) +{ + // validate pool parameters + RMT_RETURN_ON_ERROR(pool, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(buffer, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR((buffer_size >= block_size) && (buffer_size % block_size) == 0, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR(block_size >= sizeof(uintptr_t), RMT_ERROR_INVALID_SIZE); + + // setup structure fields + pool->buffer = buffer; + pool->buffer_size = buffer_size; + pool->block_size = block_size; + pool->head = buffer; + pool->allocated = 0; + + // initialize the free list + const size_t block_count = (buffer_size / block_size) - 1; + + for (size_t block_index = 0; block_index < block_count; ++block_index) + { + void* curr_chunk = (void*)((uintptr_t)buffer + (block_index * block_size)); + *((uintptr_t*)curr_chunk) = (uintptr_t)curr_chunk + block_size; + } + + // write the terminal null + uintptr_t* terminal_null = (uintptr_t*)((uintptr_t)buffer + (block_count * block_size)); + *terminal_null = (uintptr_t)NULL; + return RMT_OK; +} + +// allocate a new block +void* RmtPoolAllocate(RmtPool* pool) +{ + RMT_RETURN_ON_ERROR(pool, NULL); + RMT_RETURN_ON_ERROR(pool->head, NULL); + + // deference the head pointer and store it back in head + void* current_ptr = pool->head; + pool->head = (void*)(*((uint8_t**)(pool->head))); + pool->allocated++; + return current_ptr; +} + +// free a block +void RmtPoolFree(RmtPool* pool, void* address) +{ + if ((pool == nullptr) || (address == nullptr)) + { + return; + } + +#ifdef POOL_ALLOC_TRASH_DEALLOCATED_MEMORY + memset(address, 0xcdcdcdcd, pool->block_size); +#endif // #ifdef POOL_ALLOC_TRASH_DEALLOCATED_MEMORY + + *((uintptr_t*)address) = (uintptr_t)pool->head; + pool->head = (void*)address; + pool->allocated--; +} diff --git a/source/backend/rmt_pool.h b/source/backend/rmt_pool.h new file mode 100644 index 0000000..404631a --- /dev/null +++ b/source/backend/rmt_pool.h @@ -0,0 +1,79 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Definition of structures and functions for a fixed-size pool +/// memory allocator. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_ALLOC_POOL_H_ +#define RMV_BACKEND_RMT_ALLOC_POOL_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A structure encapsulating state for a pool allocator. +/// +/// A pool allocate allocates blocks of a fixed size with a single, +/// contiguous buffer. No guarantee is made about the order that blocks +/// are not allocated within the buffer, they are not always allocated +/// contiguously. +/// +/// Allocating and freeing is a relatively cheap, fixed cost operation. +/// +/// The contents of freed memory blocks is not changed when blocks are +/// returned to the pool, other than the first sizeof(uintptr_t) bytes. +/// +/// NOTE: The minimum supported size of a block in this pool allocator is +/// the same as the sizeof(uintptr_t). This limitation is so the additional +/// memory overhead for the pool allocator is kept to a minimum as no +/// free block list is required. The memory for this is unioned with free +/// blocks in the memory buffer. +/// +typedef struct RmtPool +{ + void* head; + void* buffer; + size_t buffer_size; + size_t block_size; + size_t allocated; +} RmtPool; + +/// Initialize the pool allocator. +/// +/// @param [in,out] pool A pointer to a RmtPool structure. +/// @param [in] buffer A pointer to a memory buffer to manage allocations for. +/// @param [in] buffer_size The size (in bytes) of the buffer pointed to by buffer. +/// @param [in] block_size The size of a single block to allocate. Must be >= sizeof(uintptr_t). +/// +/// @retval +/// RMT_OK The operation completed succesfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because either pool or buffer was NULL. +/// @retval +/// RMT_ERROR_INVALID_SIZE The operation failed because chunkSize was not greater than or equal to sizeof(uintptr_t). +/// +RmtErrorCode RmtPoolInitialize(RmtPool* pool, void* buffer, size_t buffer_size, size_t block_size); + +/// Allocate the next free block from the pool. +/// +/// @param [in,out] pool A pointer to a RmtPool structure. +/// +/// @returns +/// A pointer to the next block. If the pool is empty then NULL is returned. +/// +void* RmtPoolAllocate(RmtPool* pool); + +/// Free a block that was previously allocated from the pool. +/// +/// @param [in,out] pool A pointer to a RmtPool structure. +/// @param [in] address The address of the block to free back to the pool. +/// +void RmtPoolFree(RmtPool* pool, void* address); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_ALLOC_POOL_H_ diff --git a/source/backend/rmt_process_map.cpp b/source/backend/rmt_process_map.cpp new file mode 100644 index 0000000..48b397a --- /dev/null +++ b/source/backend/rmt_process_map.cpp @@ -0,0 +1,115 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation of the process map helper functions. +//============================================================================= + +#include "rmt_process_map.h" +#include // for memset + +RmtErrorCode RmtProcessMapInitialize(RmtProcessMap* process_map) +{ + RMT_RETURN_ON_ERROR(process_map, RMT_ERROR_INVALID_POINTER); + + memset(process_map->process_committed_memory, 0, sizeof(process_map->process_committed_memory)); + process_map->process_count = 0; + return RMT_OK; +} + +RmtErrorCode RmtProcessMapAddProcess(RmtProcessMap* process_map, uint64_t process_id) +{ + RMT_RETURN_ON_ERROR(process_map, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR((process_map->process_count + 1) < RMT_MAXIMUM_PROCESS_COUNT, RMT_ERROR_OUT_OF_MEMORY); + + // if the process is already in the map we're done. + if (RmtProcessMapContainsProcessId(process_map, process_id)) + { + return RMT_OK; + } + + // set into the next slot if not. + process_map->process_identifiers[process_map->process_count++] = process_id; + return RMT_OK; +} + +bool RmtProcessMapContainsProcessId(const RmtProcessMap* process_map, uint64_t process_id) +{ + RMT_RETURN_ON_ERROR(process_map, false); + + for (int32_t current_process_index = 0; current_process_index < process_map->process_count; ++current_process_index) + { + if (process_map->process_identifiers[current_process_index] == process_id) + { + return true; + } + } + + return false; +} + +RmtErrorCode RmtProcessMapGetIndexFromProcessId(const RmtProcessMap* process_map, uint64_t process_id, int32_t* out_index) +{ + RMT_RETURN_ON_ERROR(process_map, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_index, RMT_ERROR_INVALID_POINTER); + + for (int32_t current_process_index = 0; current_process_index < process_map->process_count; ++current_process_index) + { + if (process_map->process_identifiers[current_process_index] == process_id) + { + *out_index = current_process_index; + return RMT_OK; + } + } + + *out_index = -1; + return RMT_ERROR_INDEX_OUT_OF_RANGE; +} + +uint64_t RmtProcessMapGetCommittedMemoryForProcessId(const RmtProcessMap* process_map, uint64_t process_id) +{ + RMT_RETURN_ON_ERROR(process_map, 0); + + int32_t index = -1; + const RmtErrorCode error_code = RmtProcessMapGetIndexFromProcessId(process_map, process_id, &index); + if (error_code == RMT_OK) + { + return process_map->process_committed_memory[index]; + } + + return 0; +} + +RmtErrorCode RmtProcessMapAddCommittedMemoryForProcessId(RmtProcessMap* process_map, uint64_t process_id, uint64_t size_in_bytes) +{ + RMT_RETURN_ON_ERROR(process_map, RMT_ERROR_INVALID_POINTER); + + int32_t index = -1; + const RmtErrorCode error_code = RmtProcessMapGetIndexFromProcessId(process_map, process_id, &index); + if (error_code == RMT_OK) + { + process_map->process_committed_memory[index] += size_in_bytes; + } + + return RMT_OK; +} + +RmtErrorCode RmtProcessMapRemoveCommittedMemoryForProcessId(RmtProcessMap* process_map, uint64_t process_id, uint64_t size_in_bytes) +{ + RMT_RETURN_ON_ERROR(process_map, RMT_ERROR_INVALID_POINTER); + + int32_t index = -1; + const RmtErrorCode error_code = RmtProcessMapGetIndexFromProcessId(process_map, process_id, &index); + if (error_code == RMT_OK) + { + if (size_in_bytes < process_map->process_committed_memory[index]) + { + process_map->process_committed_memory[index] -= size_in_bytes; + } + else + { + process_map->process_committed_memory[index] = 0; + } + } + + return RMT_OK; +} diff --git a/source/backend/rmt_process_map.h b/source/backend/rmt_process_map.h new file mode 100644 index 0000000..cb5a62e --- /dev/null +++ b/source/backend/rmt_process_map.h @@ -0,0 +1,105 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Data structures and functions to help construct a map of proc IDs +//============================================================================= + +#ifndef RMV_BACKEND_RMT_PROCESS_MAP_H_ +#define RMV_BACKEND_RMT_PROCESS_MAP_H_ + +#include +#include "rmt_configuration.h" + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A structure encapsulating a set of process IDs. +typedef struct RmtProcessMap +{ + uint64_t process_identifiers[RMT_MAXIMUM_PROCESS_COUNT]; ///< The 32bit process identifier. + uint64_t process_committed_memory[RMT_MAXIMUM_PROCESS_COUNT]; ///< The amount of committed memory (in bytes) per process. + int32_t process_count; ///< The number of processes. +} RmtProcessMap; + +/// Initialize the process map. +/// +/// @param [in] process_map A pointer to a RmtProcessMap structure that will contain the process map. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to process_map being set to NULL. +RmtErrorCode RmtProcessMapInitialize(RmtProcessMap* process_map); + +/// Add a process ID to the map. +/// +/// @param [in] process_map A pointer to a RmtProcessMap structure that contains the process map. +/// @param [in] process_id The process ID to add. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to process_map being set to NULL. +/// @retval +/// RMT_ERROR_OUT_OF_MEMORY The operation failed due to the number of processes in the map being greater than RMT_MAXIMUM_PROCESS_COUNT. +RmtErrorCode RmtProcessMapAddProcess(RmtProcessMap* process_map, uint64_t process_id); + +/// Get the amount of committed memory (in bytes) for a specified process ID. +/// +/// @param [in] process_map A pointer to a RmtProcessMap structure that contains the process map. +/// @param [in] process_id The specified process ID. +/// +/// @return +/// The amount of committed memory, in bytes. +uint64_t RmtProcessMapGetCommittedMemoryForProcessId(const RmtProcessMap* process_map, uint64_t process_id); + +/// Add some memory (in bytes) for a specified process ID. +/// +/// @param [in] process_map A pointer to a RmtProcessMap structure that contains the process map. +/// @param [in] process_id The specified process ID. +/// @param [in] size_in_bytes The amount of memory to add. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to process_map being set to NULL. +RmtErrorCode RmtProcessMapAddCommittedMemoryForProcessId(RmtProcessMap* process_map, uint64_t process_id, uint64_t size_in_bytes); + +/// Remove some memory (in bytes) from a specified process ID. +/// +/// @param [in] process_map A pointer to a RmtProcessMap structure that contains the process map. +/// @param [in] process_id The specified process ID. +/// @param [in] size_in_bytes The amount of memory to remove. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to process_map being set to NULL. +RmtErrorCode RmtProcessMapRemoveCommittedMemoryForProcessId(RmtProcessMap* process_map, uint64_t process_id, uint64_t size_in_bytes); + +/// Query the process map to see if it contains the specified process ID. +/// +/// @param [in] process_map A pointer to a RmtProcessMap structure that contains the process map. +/// @param [in] process_id The specified process ID. +/// +/// @returns +/// true if process ID is in the map, false if not. +bool RmtProcessMapContainsProcessId(const RmtProcessMap* process_map, uint64_t process_id); + +/// Get the index of a process from a process ID. +/// +/// @param [in] process_map A pointer to a RmtProcessMap structure that contains the process map. +/// @param [in] process_id The specified process ID. +/// @param [out] out_index Pointer to an int32_t to receive the index. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed due to process_map or out_index being set to NULL. +RmtErrorCode RmtProcessMapGetIndexFromProcessId(const RmtProcessMap* process_map, uint64_t process_id, int32_t* out_index); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_PROCESS_MAP_H_ diff --git a/source/backend/rmt_process_start_info.h b/source/backend/rmt_process_start_info.h new file mode 100644 index 0000000..01f32ef --- /dev/null +++ b/source/backend/rmt_process_start_info.h @@ -0,0 +1,27 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Definition of the process start info structures and helper functions. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_PROCESS_START_INFO_H_ +#define RMV_BACKEND_RMT_PROCESS_START_INFO_H_ + +#include +#include + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A structure encapsulating the process start info. +typedef struct RmtProcessStartInfo +{ + uint64_t process_id; ///< The process ID. + uint64_t physical_memory_allocated; ///< The amount of physical memory allocated (in bytes). +} RmtProcessStartInfo; + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_PROCESS_START_INFO_H_ diff --git a/source/backend/rmt_resource_history.cpp b/source/backend/rmt_resource_history.cpp new file mode 100644 index 0000000..ea3a2a4 --- /dev/null +++ b/source/backend/rmt_resource_history.cpp @@ -0,0 +1,60 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of functions operating on a resource history structure. +//============================================================================= + +#include +#include + +// add an event to resource history log +RmtErrorCode RmtResourceHistoryAddEvent(RmtResourceHistory* resource_history, + RmtResourceHistoryEventType event_type, + uint64_t thread_id, + uint64_t timestamp, + bool compact) +{ + RMT_ASSERT(resource_history); + RMT_RETURN_ON_ERROR(resource_history, RMT_ERROR_INVALID_POINTER); + + if (resource_history->event_count >= RMT_MAXIMUM_RESOURCE_HISTORY_EVENTS) + { + return RMT_ERROR_OUT_OF_MEMORY; + } + + // if the events need compacting, ignore this event if it's indentical to the previous event + bool duplicate = false; + if (compact) + { + int32_t last_event_index = resource_history->event_count - 1; + if (last_event_index >= 0) + { + // if the last event is the same as the current event, it's a duplicate + RmtResourceHistoryEvent* last_event = &resource_history->events[last_event_index]; + if (last_event->event_type == event_type && last_event->timestamp == timestamp && last_event->thread_id == thread_id) + { + duplicate = true; + } + } + } + + if (!duplicate) + { + RmtResourceHistoryEvent* next_event = &resource_history->events[resource_history->event_count]; + next_event->event_type = event_type; + next_event->timestamp = timestamp; + next_event->thread_id = thread_id; + resource_history->event_count++; + } + + return RMT_OK; +} + +// destroy resource history +RmtErrorCode RmtResourceHistoryDestroy(RmtResourceHistory* resource_history) +{ + RMT_ASSERT(resource_history); + RMT_RETURN_ON_ERROR(resource_history, RMT_ERROR_INVALID_POINTER); + + return RMT_OK; +} diff --git a/source/backend/rmt_resource_history.h b/source/backend/rmt_resource_history.h new file mode 100644 index 0000000..df6cab2 --- /dev/null +++ b/source/backend/rmt_resource_history.h @@ -0,0 +1,98 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Structures and functions for working with a resource history. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_RESOURCE_HISTORY_H_ +#define RMV_BACKEND_RMT_RESOURCE_HISTORY_H_ + +#include +#include +#include + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtResource RmtResource; +typedef struct RmtVirtualAllocation RmtVirtualAllocation; + +/// An enumeration of all resource history event types. +typedef enum RmtResourceHistoryEventType +{ + kRmtResourceHistoryEventSnapshotTaken = -1, ///< A snapshot was taken + kRmtResourceHistoryEventResourceCreated = 0, ///< A resource was created. + kRmtResourceHistoryEventResourceDestroyed = 1, ///< A resource was destroyed. + kRmtResourceHistoryEventResourceBound = 2, ///< A resource was bound to a virtual address range. + kRmtResourceHistoryEventVirtualMemoryAllocated = 3, ///< The virtual memory backing the resource was allocated. + kRmtResourceHistoryEventVirtualMemoryFree = 4, ///< The virtual memory backing the resource was freed. + kRmtResourceHistoryEventVirtualMemoryMapped = 5, ///< The virtual memory backing the resource was CPU mapped. + kRmtResourceHistoryEventVirtualMemoryUnmapped = 6, ///< The virtual memory backing the resource was CPU unammped. + kRmtResourceHistoryEventVirtualMemoryMakeResident = 7, ///< The virtual memory backing the resource was requested to be made resident. + kRmtResourceHistoryEventVirtualMemoryEvict = 8, ///< The virtual memory backing the resource was requested to be evicted. + kRmtResourceHistoryEventBackingMemoryPaged = 9, ///< Some or all of the backing memory was paged from one memory type to another. + kRmtResourceHistoryEventPhysicalMapToLocal = 10, ///< Some or all of the physical memory backing this resource was mapped. + kRmtResourceHistoryEventPhysicalUnmap = 11, ///< Some or all of the physical memory backing this resource was unmapped. + kRmtResourceHistoryEventPhysicalMapToHost = 12, ///< Some or all of the physical memory backing this resource was paged to local. + +} RmtResourceHistoryEventType; + +/// A structure encapsulating a single event in the resource history. +typedef struct RmtResourceHistoryEvent +{ + uint64_t timestamp; ///< The time at which the event occurred. + uint64_t thread_id; ///< The CPU thread on which the event occurred. + RmtResourceHistoryEventType event_type; ///< The type of resource history event that occurred. + uint32_t padding; ///< Padding for the event. +} RmtResourceHistoryEvent; + +/// A structure encapsulating an address range. +typedef struct RmtResourceAddressRange +{ + RmtGpuAddress address; ///< The address of the address range. + uint64_t size_in_bytes; ///< The size of the range in bytes. +} RmtResourceAddressRange; + +/// A structure encapsulating the history of a resource. +typedef struct RmtResourceHistory +{ + const RmtResource* resource; ///< The resource the history pertains to. + RmtResourceHistoryEvent events[RMT_MAXIMUM_RESOURCE_HISTORY_EVENTS]; ///< A pointer to an array of RmtResourceHistoryEvent structures. + int32_t event_count; ///< The number of RmtResourceHistoryEvent structures pointed to by events. + const RmtVirtualAllocation* base_allocation; ///< A pointer to a RmtVirtualAllocation structure that underpins resource. + +} RmtResourceHistory; + +/// Add a new event to the resource history. +/// +/// @param [in] resource_history A pointer to a RmtResourceHistory structure to add the event to. +/// @param [in] event_type The type of event that occurred. +/// @param [in] thread_id The CPU thread ID where the event occurred. +/// @param [in] timestamp The time at which the event occurred. +/// @param [in] compact If true, ignore identical sequential events. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because resourceHistory was NULL. +RmtErrorCode RmtResourceHistoryAddEvent(RmtResourceHistory* resource_history, + RmtResourceHistoryEventType event_type, + uint64_t thread_id, + uint64_t timestamp, + bool compact); + +/// Destroy the resource history data. +/// +/// @param [in] resource_history A pointer to a RmtResourceHistory structure to destroy. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because resourceHistory was NULL. +RmtErrorCode RmtResourceHistoryDestroy(RmtResourceHistory* resource_history); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_RESOURCE_HISTORY_H_ diff --git a/source/backend/rmt_resource_list.cpp b/source/backend/rmt_resource_list.cpp new file mode 100644 index 0000000..7464b27 --- /dev/null +++ b/source/backend/rmt_resource_list.cpp @@ -0,0 +1,853 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of the resource list functions. +//============================================================================= + +#include "rmt_resource_list.h" +#include "rmt_virtual_allocation_list.h" +#include +#include "rmt_page_table.h" +#include "rmt_data_snapshot.h" +#include "rmt_address_helper.h" +#include // for memcpy() +#include + +#ifndef _WIN32 +#include "linux/safe_crt.h" +#endif + +RmtResourceUsageType RmtResourceGetUsageType(const RmtResource* resource) +{ + RMT_ASSERT(resource); + RMT_RETURN_ON_ERROR(resource, kRmtResourceUsageTypeUnknown); + + switch (resource->resource_type) + { + case kRmtResourceTypeBuffer: + if (resource->buffer.usage_flags == kRmtBufferUsageFlagVertexBuffer) + { + return kRmtResourceUsageTypeVertexBuffer; + } + + if (resource->buffer.usage_flags == kRmtBufferUsageFlagIndexBuffer) + { + return kRmtResourceUsageTypeIndexBuffer; + } + + return kRmtResourceUsageTypeBuffer; + break; + + case kRmtResourceTypeImage: + if ((resource->image.usage_flags & kRmtImageUsageFlagsColorTarget) == kRmtImageUsageFlagsColorTarget) + { + return kRmtResourceUsageTypeRenderTarget; + } + else if ((resource->image.usage_flags & kRmtImageUsageFlagsDepthStencil) == kRmtImageUsageFlagsDepthStencil) + { + return kRmtResourceUsageTypeDepthStencil; + } + else + { + return kRmtResourceUsageTypeTexture; + } + break; + + case kRmtResourceTypePipeline: + return kRmtResourceUsageTypeShaderPipeline; + + case kRmtResourceTypeCommandAllocator: + return kRmtResourceUsageTypeCommandBuffer; + + case kRmtResourceTypeHeap: + return kRmtResourceUsageTypeHeap; + + case kRmtResourceTypeDescriptorHeap: + case kRmtResourceTypeDescriptorPool: + return kRmtResourceUsageTypeDescriptors; + + case kRmtResourceTypeGpuEvent: + return kRmtResourceUsageTypeGpuEvent; + + case kRmtResourceTypeBorderColorPalette: + case kRmtResourceTypeTimestamp: + case kRmtResourceTypeMiscInternal: + + case kRmtResourceTypePerfExperiment: + case kRmtResourceTypeMotionEstimator: + case kRmtResourceTypeVideoDecoder: + case kRmtResourceTypeVideoEncoder: + case kRmtResourceTypeQueryHeap: + case kRmtResourceTypeIndirectCmdGenerator: + return kRmtResourceUsageTypeInternal; + + default: + break; + } + + return kRmtResourceUsageTypeUnknown; +} + +uint64_t RmtResourceGetOffsetFromBoundAllocation(const RmtResource* resource) +{ + RMT_ASSERT(resource); + RMT_RETURN_ON_ERROR(resource, 0); + + if (resource->bound_allocation != nullptr) + { + const RmtGpuAddress allocation_base_address = resource->bound_allocation->base_address; + const RmtGpuAddress resource_base_address = resource->address; + return resource_base_address - allocation_base_address; + } + + return 0; +} + +RmtGpuAddress RmtResourceGetVirtualAddress(const RmtResource* resource) +{ + RMT_ASSERT(resource); + RMT_RETURN_ON_ERROR(resource, 0); + + return resource->address; +} + +bool RmtResourceOverlapsVirtualAddressRange(const RmtResource* resource, RmtGpuAddress address_start, RmtGpuAddress address_end) +{ + RMT_RETURN_ON_ERROR(resource, false); + + if (resource->bound_allocation == nullptr) + { + return false; + } + + // Case 1: if the resource starts after the end of the range then it can't overlap + // + // |--virtual address range--| + // |--resource--| + if (resource->address > address_end) + { + return false; + } + + // Case 2: if the resource ends before the start of the range then it can't overlap + // + // |--resource--| + // |--virtual address range--| + const RmtGpuAddress next_byte_after_resource = resource->address + resource->size_in_bytes; + if (next_byte_after_resource < address_start) + { + return false; + } + + // anything else must overlap. + return true; +} + +RmtErrorCode RmtResourceGetBackingStorageHistogram(const RmtDataSnapshot* snapshot, const RmtResource* resource, uint64_t* out_bytes_per_backing_storage_type) +{ + RMT_RETURN_ON_ERROR(resource, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_bytes_per_backing_storage_type, RMT_ERROR_INVALID_POINTER) + + const uint64_t size_of_minimum_page = RmtGetPageSize(kRmtPageSize4Kb); + + // stride through the resource in 4KB pages and figure out the mapping of each. + RmtGpuAddress current_virtual_address = resource->address; + const RmtGpuAddress end_virtual_address = resource->address + resource->size_in_bytes; + + // add all the resource into unmapped category initially. + out_bytes_per_backing_storage_type[kRmtResourceBackingStorageUnmapped] = resource->size_in_bytes; + + while (current_virtual_address < end_virtual_address) + { + // Handle edge case where last page isn't 4KB in size. + const uint64_t size = RMT_MINIMUM(end_virtual_address - current_virtual_address, size_of_minimum_page); + + // get the physical address + RmtGpuAddress physical_address = 0; + const RmtErrorCode error_code = RmtPageTableGetPhysicalAddressForVirtualAddress(&snapshot->page_table, current_virtual_address, &physical_address); + + if (error_code == RMT_OK) + { + // remove bytes from unmapped count. + if (size <= out_bytes_per_backing_storage_type[kRmtResourceBackingStorageUnmapped]) + { + out_bytes_per_backing_storage_type[kRmtResourceBackingStorageUnmapped] -= size; + } + + if (physical_address == 0) + { + out_bytes_per_backing_storage_type[kRmtHeapTypeSystem] += size; + } + else + { + const RmtHeapType segment_type = RmtDataSnapshotGetSegmentForAddress(snapshot, physical_address); + if (segment_type != kRmtHeapTypeUnknown) + { + out_bytes_per_backing_storage_type[segment_type] += size; + } + } + } + + current_virtual_address += size; + } + + return RMT_OK; +} + +bool RmtResourceIsCompletelyInPreferredHeap(const RmtDataSnapshot* snapshot, const RmtResource* resource) +{ + RMT_RETURN_ON_ERROR(resource, false); + RMT_RETURN_ON_ERROR(resource->bound_allocation, false); + + const uint64_t size_of_minimum_page = RmtGetPageSize(kRmtPageSize4Kb); + const RmtHeapType preferred_heap = resource->bound_allocation->heap_preferences[0]; + + // stride through the resource in 4KB pages and figure out the mapping of each. + RmtGpuAddress current_virtual_address = resource->address; + const RmtGpuAddress end_virtual_address = resource->address + resource->size_in_bytes; + while (current_virtual_address < end_virtual_address) + { + // Handle edge case where last page isn't 4KB in size. + const uint64_t size = RMT_MINIMUM(end_virtual_address - current_virtual_address, size_of_minimum_page); + + // get the physical address + RmtGpuAddress physical_address = 0; + const RmtErrorCode error_code = RmtPageTableGetPhysicalAddressForVirtualAddress(&snapshot->page_table, current_virtual_address, &physical_address); + if (error_code != RMT_OK) + { + return false; + } + + if (physical_address == 0) + { + if (preferred_heap != kRmtHeapTypeSystem) + { + return false; + } + } + else + { + const RmtHeapType segment_type = RmtDataSnapshotGetSegmentForAddress(snapshot, physical_address); + if (segment_type != preferred_heap) + { + return false; + } + } + + current_virtual_address += size; + } + + return true; +} + +const char* RmtResourceGetHeapTypeName(const RmtResource* resource) +{ + RMT_ASSERT(resource); + if (resource->bound_allocation != nullptr) + { + return RmtGetHeapTypeNameFromHeapType(resource->bound_allocation->heap_preferences[0]); + } + if ((resource->flags & kRmtResourceFlagDangling) == kRmtResourceFlagDangling) + { + return "Orphaned"; + } + + return "-"; +} + +bool RmtResourceGetName(const RmtResource* resource, int32_t buffer_size, char** out_resource_name) +{ + RMT_ASSERT(resource); + RMT_ASSERT(out_resource_name); + RMT_ASSERT(*out_resource_name); + + if (buffer_size < RMT_MAXIMUM_NAME_LENGTH) + { + return false; + } + + if (resource->name[0] != '\0') + { + strcpy_s(*out_resource_name, buffer_size, resource->name); + } + else + { + sprintf_s(*out_resource_name, buffer_size, "Resource %llu", resource->identifier); + } + + return true; +} + +RmtHeapType RmtResourceGetActualHeap(const RmtDataSnapshot* snapshot, const RmtResource* resource) +{ + RMT_RETURN_ON_ERROR(snapshot, kRmtHeapTypeUnknown); + RMT_RETURN_ON_ERROR(resource, kRmtHeapTypeUnknown); + + if (resource->bound_allocation != nullptr) + { + return resource->bound_allocation->heap_preferences[0]; + } + + return kRmtHeapTypeUnknown; +} + +int32_t RmtResourceGetAliasCount(const RmtResource* resource) +{ + RMT_RETURN_ON_ERROR(resource, 0); + RMT_RETURN_ON_ERROR(resource->bound_allocation, 0); + RMT_RETURN_ON_ERROR(resource->resource_type != kRmtResourceTypeHeap, 0); + + const RmtVirtualAllocation* virtual_allocation = resource->bound_allocation; + const RmtGpuAddress resource_start_address = resource->address; + const RmtGpuAddress resource_end_address = resource->address + resource->size_in_bytes; + + int32_t total_resource_alias_count = 0; + for (int32_t current_resource_index = 0; current_resource_index < virtual_allocation->resource_count; ++current_resource_index) + { + const RmtResource* current_resource = virtual_allocation->resources[current_resource_index]; + + if (current_resource == resource) + { + continue; + } + + // special handle for heaps. + if (current_resource->resource_type == kRmtResourceTypeHeap) + { + continue; + } + + // get the start and end address + const RmtGpuAddress current_resource_start = current_resource->address; + const RmtGpuAddress current_resource_end = current_resource->address + current_resource->size_in_bytes; + if (resource_start_address >= current_resource_end) + { + continue; + } + if (resource_end_address <= current_resource_start) + { + continue; + } + + total_resource_alias_count++; + } + + return total_resource_alias_count; +} + +// Helper function to improve tree balance by hashing the handles. +static uint64_t GenerateResourceHandle(uint64_t handle) +{ + // NOTE: this function is vestigial now, as the hashing is done in rmt_token_heap + // as part of the process to de-duplicate reused driver handles. + return handle; +} + +// helper function to find smallest value in a branch +static RmtResourceIdNode* GetSmallestNode(RmtResourceIdNode* node) +{ + while ((node != nullptr) && (node->left != nullptr)) + { + node = node->left; + } + return node; +} + +// recursive function to find a node by ID +static RmtResourceIdNode* FindResourceNode(RmtResourceIdNode* root, RmtResourceIdentifier resource_identifier) +{ + if (root == nullptr) + { + return NULL; + } + + if (root->identifer == resource_identifier) + { + return root; + } + + if (resource_identifier < root->identifer) + { + return FindResourceNode(root->left, resource_identifier); + } + + return FindResourceNode(root->right, resource_identifier); +} + +// recursive function to insert a node +static RmtResourceIdNode* InsertNode(RmtResourceList* resource_list, RmtResourceIdNode* node, RmtResourceIdentifier resource_identifier, RmtResource* resource) +{ + if (node == nullptr) + { + // create a new node + RmtResourceIdNode* new_node = (RmtResourceIdNode*)RmtPoolAllocate(&resource_list->resource_id_node_pool); + new_node->identifer = resource_identifier; + new_node->resource = resource; + new_node->left = NULL; + new_node->right = NULL; + + // store pointer to the node ID so we can update it on delete. + resource->id_node = new_node; + return new_node; + } + + if (resource_identifier < node->identifer) + { + node->left = InsertNode(resource_list, node->left, resource_identifier, resource); + } + else if (resource_identifier > node->identifer) + { + node->right = InsertNode(resource_list, node->right, resource_identifier, resource); + } + else + { + RMT_ASSERT_FAIL("WTF"); + } + + return node; +} + +// recursive function to delete a node +static RmtResourceIdNode* DeleteNode(RmtResourceList* resource_list, RmtResourceIdNode* node, RmtResourceIdentifier resource_identifier) +{ + if (node == nullptr) + { + return node; + } + + if (resource_identifier < node->identifer) + { + node->left = DeleteNode(resource_list, node->left, resource_identifier); + } + else if (resource_identifier > node->identifer) + { + node->right = DeleteNode(resource_list, node->right, resource_identifier); + } + else + { + if (node->left == nullptr) + { + RmtResourceIdNode* child = node->right; + node->identifer = 0; + node->left = NULL; + node->right = NULL; + RmtPoolFree(&resource_list->resource_id_node_pool, node); + return child; + } + if (node->right == nullptr) + { + RmtResourceIdNode* child = node->left; + node->identifer = 0; + node->left = NULL; + node->right = NULL; + RmtPoolFree(&resource_list->resource_id_node_pool, node); + return child; + } + + RmtResourceIdNode* smallest_child = GetSmallestNode(node->right); + node->identifer = smallest_child->identifer; + + // update payload pointers. + RMT_ASSERT(node->resource); + node->resource = smallest_child->resource; + RMT_ASSERT(node->resource->id_node); + node->resource->id_node = node; + + // now delete the node we just moved. + node->right = DeleteNode(resource_list, node->right, smallest_child->identifer); + } + + return node; +} + +// search the acceleration structure for a resource. +static RmtResource* FindResourceById(const RmtResourceList* resource_list, RmtResourceIdentifier resource_identifer) +{ + RmtResourceIdNode* found_node = FindResourceNode(resource_list->root, resource_identifer); + if (found_node == nullptr) + { + return NULL; + } + + return found_node->resource; +} + +// add a resource to the acceleration structure. +static RmtErrorCode AddResourceToTree(RmtResourceList* resource_list, RmtResourceIdentifier resource_identifier, RmtResource* resource) +{ + const size_t pool_count = resource_list->resource_id_node_pool.allocated; + resource_list->root = InsertNode(resource_list, resource_list->root, resource_identifier, resource); + RMT_ASSERT(resource_list->resource_id_node_pool.allocated == pool_count + 1); + return RMT_OK; +} + +// destroy a resource from the acceleration structure. +static RmtErrorCode RemoveResourceFromTree(RmtResourceList* resource_list, RmtResourceIdentifier resource_identifer) +{ + const size_t pool_count = resource_list->resource_id_node_pool.allocated; + resource_list->root = DeleteNode(resource_list, resource_list->root, resource_identifer); + RMT_ASSERT(resource_list->resource_id_node_pool.allocated == pool_count - 1); + return RMT_OK; +} + +// destroy a resource +static RmtErrorCode DestroyResource(RmtResourceList* resource_list, RmtResource* resource) +{ + if (resource_list->resource_count == 0) + { + return RMT_OK; + } + + resource_list->resource_usage_count[RmtResourceGetUsageType(resource)]--; + RMT_ASSERT(resource_list->resource_usage_count[RmtResourceGetUsageType(resource)] >= 0); + + // don't count shareable resources + bool is_shareable = (resource->resource_type == kRmtResourceTypeImage) && + ((resource->image.create_flags & kRmtImageCreationFlagShareable) == kRmtImageCreationFlagShareable); + + if (!is_shareable) + { + RmtResourceUsageType resource_type = RmtResourceGetUsageType(resource); + RMT_ASSERT(resource_list->resource_usage_size[resource_type] >= resource->size_in_bytes); + resource_list->resource_usage_size[RmtResourceGetUsageType(resource)] -= resource->size_in_bytes; + } + + const int64_t hashed_identifier = GenerateResourceHandle(resource->identifier); + + // get a pointer to the last resource, if its the one we're trying to delete then adjust the count. + RmtResource* tail_resource = &resource_list->resources[resource_list->resource_count - 1]; + + const RmtErrorCode error_code = RemoveResourceFromTree(resource_list, hashed_identifier); + RMT_ASSERT(error_code == RMT_OK); + + // copy the tail into the target. + if (tail_resource != resource) + { + memcpy(resource, tail_resource, sizeof(RmtResource)); + tail_resource->id_node->resource = resource; // update acceleration structure pointer to this resource's new home. + } + + resource_list->resource_count--; + return RMT_OK; +} + +size_t RmtResourceListGetBufferSize(int32_t maximum_concurrent_resources) +{ + return maximum_concurrent_resources * (sizeof(RmtResource) + sizeof(RmtResourceIdNode)); +} + +RmtErrorCode RmtResourceListInitialize(RmtResourceList* resource_list, + void* buffer, + size_t buffer_size, + const RmtVirtualAllocationList* virtual_allocation_list, + int32_t maximum_concurrent_resources) +{ + RMT_ASSERT(resource_list); + RMT_RETURN_ON_ERROR(resource_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(buffer, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(buffer_size, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR(RmtResourceListGetBufferSize(maximum_concurrent_resources) <= buffer_size, RMT_ERROR_INVALID_SIZE); + + // initialize the resource storage + resource_list->resources = (RmtResource*)buffer; + resource_list->resource_count = 0; + resource_list->virtual_allocation_list = virtual_allocation_list; + resource_list->maximum_concurrent_resources = maximum_concurrent_resources; + + // initialize the acceleration structure. + const uintptr_t resource_node_buffer = ((uintptr_t)buffer) + (maximum_concurrent_resources * sizeof(RmtResource)); + resource_list->resource_id_nodes = (RmtResourceIdNode*)resource_node_buffer; + const size_t resource_id_nodes_size = maximum_concurrent_resources * sizeof(RmtResourceIdNode); + const RmtErrorCode error_code = + RmtPoolInitialize(&resource_list->resource_id_node_pool, resource_list->resource_id_nodes, resource_id_nodes_size, sizeof(RmtResourceIdNode)); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + resource_list->root = NULL; + + memset(resource_list->resource_usage_count, 0, sizeof(resource_list->resource_usage_count)); + memset(resource_list->resource_usage_size, 0, sizeof(resource_list->resource_usage_size)); + + return RMT_OK; +} + +RmtErrorCode RmtResourceListAddResourceCreate(RmtResourceList* resource_list, const RmtTokenResourceCreate* resource_create) +{ + RMT_ASSERT(resource_list); + RMT_ASSERT(resource_create); + RMT_RETURN_ON_ERROR(resource_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(resource_create, RMT_ERROR_INVALID_POINTER); + + // Resource ID should be a thing. + RMT_ASSERT(resource_create->resource_identifier); + + const uint64_t hashed_identifier = GenerateResourceHandle(resource_create->resource_identifier); + + // Check if a resource with this ID already exists insert an implicit unbind. + RmtResource* resource = FindResourceById(resource_list, hashed_identifier); + if (resource != nullptr) + { + // remove it + if ((resource->bound_allocation != nullptr) && resource->resource_type != kRmtResourceTypeHeap) + { + if (resource->bound_allocation->resource_count > 0) + { + ((RmtVirtualAllocation*)resource->bound_allocation)->resource_count--; + ((RmtVirtualAllocation*)resource->bound_allocation)->non_heap_resource_count--; + } + } + + DestroyResource(resource_list, resource); + } + + // Make sure we can allocate this resource. + RMT_ASSERT(resource_list->resource_count + 1 <= resource_list->maximum_concurrent_resources); + RMT_RETURN_ON_ERROR((resource_list->resource_count + 1) <= resource_list->maximum_concurrent_resources, RMT_ERROR_OUT_OF_MEMORY); + + // fill out the stuff we know. + RmtResource* new_resource = &resource_list->resources[resource_list->resource_count++]; + new_resource->name[0] = '\0'; + new_resource->identifier = resource_create->resource_identifier; + new_resource->create_time = resource_create->common.timestamp; + new_resource->flags = 0; + new_resource->commit_type = resource_create->commit_type; + new_resource->resource_type = resource_create->resource_type; + new_resource->owner_type = resource_create->owner_type; + + switch (resource_create->resource_type) + { + case kRmtResourceTypeImage: + memcpy(&new_resource->image, &resource_create->image, sizeof(RmtResourceDescriptionImage)); + break; + + case kRmtResourceTypeBuffer: + memcpy(&new_resource->buffer, &resource_create->buffer, sizeof(RmtResourceDescriptionBuffer)); + break; + + case kRmtResourceTypeGpuEvent: + memcpy(&new_resource->gpu_event, &resource_create->gpu_event, sizeof(RmtResourceDescriptionGpuEvent)); + break; + + case kRmtResourceTypeBorderColorPalette: + memcpy(&new_resource->border_color_palette, &resource_create->border_color_palette, sizeof(RmtResourceDescriptionBorderColorPalette)); + break; + + case kRmtResourceTypePerfExperiment: + memcpy(&new_resource->perf_experiment, &resource_create->perf_experiment, sizeof(RmtResourceDescriptionPerfExperiment)); + break; + + case kRmtResourceTypeQueryHeap: + memcpy(&new_resource->query_heap, &resource_create->query_heap, sizeof(RmtResourceDescriptionQueryHeap)); + break; + + case kRmtResourceTypeVideoDecoder: + memcpy(&new_resource->video_decoder, &resource_create->video_decoder, sizeof(RmtResourceDescriptionVideoDecoder)); + break; + + case kRmtResourceTypeVideoEncoder: + memcpy(&new_resource->video_encoder, &resource_create->video_encoder, sizeof(RmtResourceDescriptionVideoEncoder)); + break; + + case kRmtResourceTypeHeap: + memcpy(&new_resource->heap, &resource_create->heap, sizeof(RmtResourceDescriptionHeap)); + break; + + case kRmtResourceTypePipeline: + memcpy(&new_resource->pipeline, &resource_create->pipeline, sizeof(RmtResourceDescriptionPipeline)); + break; + + case kRmtResourceTypeDescriptorHeap: + memcpy(&new_resource->descriptor_heap, &resource_create->descriptor_heap, sizeof(RmtResourceDescriptionDescriptorHeap)); + break; + + case kRmtResourceTypeDescriptorPool: + memcpy(&new_resource->descriptor_pool, &resource_create->descriptor_pool, sizeof(RmtResourceDescriptionDescriptorPool)); + break; + + case kRmtResourceTypeCommandAllocator: + memcpy(&new_resource->command_allocator, &resource_create->command_allocator, sizeof(RmtResourceDescriptionCommandAllocator)); + break; + + case kRmtResourceTypeMiscInternal: + memcpy(&new_resource->misc_internal, &resource_create->misc_internal, sizeof(RmtResourceDescriptionMiscInternal)); + break; + + case kRmtResourceTypeIndirectCmdGenerator: + case kRmtResourceTypeMotionEstimator: + case kRmtResourceTypeTimestamp: + // NOTE: no data associated with these types, if this changes in future we'll need to copy it here. + break; + + default: + // shouldn't reach here. + RMT_ASSERT(0); + break; + } + + // clear this ready for patch during bind. + new_resource->bind_time = 0; + new_resource->address = 0; + new_resource->size_in_bytes = 0; + new_resource->bound_allocation = NULL; + + // insert node into the acceleration structure. + AddResourceToTree(resource_list, hashed_identifier, new_resource); + + resource_list->resource_usage_count[RmtResourceGetUsageType(new_resource)]++; + return RMT_OK; +} + +// calculate commit type for the resource. +static void UpdateCommitType(RmtResourceList* resource_list, RmtResource* resource) +{ + RMT_ASSERT(resource); + RMT_ASSERT(resource->bound_allocation); + + // NOTE: Casting const away here is intentional, as this structure is internal to backend and + // const is designed for the external facing API. + RmtVirtualAllocation* current_virtual_allocation = (RmtVirtualAllocation*)resource->bound_allocation; + + // PRT stuff is for sure virtual. + if (resource->resource_type == kRmtResourceTypeImage) + { + if ((resource->image.create_flags & kRmtImageCreationFlagPrt) == kRmtImageCreationFlagPrt) + { + const RmtHeapType previous_heap_type = current_virtual_allocation->heap_preferences[0]; + RMT_ASSERT(previous_heap_type != kRmtHeapTypeNone); + + // mark everything as virtual and heap none, we shouldn't get allocations which contain virtual and non-virtual stuff. + resource->commit_type = kRmtCommitTypeVirtual; + current_virtual_allocation->heap_preferences[0] = kRmtHeapTypeNone; + current_virtual_allocation->heap_preferences[1] = kRmtHeapTypeNone; + current_virtual_allocation->heap_preferences[2] = kRmtHeapTypeNone; + current_virtual_allocation->heap_preferences[3] = kRmtHeapTypeNone; + + // move the allocation's bytes into the NONE heap from wherever they came from. + const uint64_t size_in_bytes = RmtGetAllocationSizeInBytes(current_virtual_allocation->size_in_4kb_page, kRmtPageSize4Kb); + ((RmtVirtualAllocationList*)(resource_list->virtual_allocation_list))->allocations_per_preferred_heap[previous_heap_type] -= size_in_bytes; + ((RmtVirtualAllocationList*)(resource_list->virtual_allocation_list))->allocations_per_preferred_heap[kRmtHeapTypeNone] += size_in_bytes; + } + } + + // NOTE: A more accurate commit type for non-vritual resources will be calculated in deferred pass. See + // snapshotGeneratorCalculateCommitType in rmt_data_set.cpp. +} + +RmtErrorCode RmtResourceListAddResourceBind(RmtResourceList* resource_list, const RmtTokenResourceBind* resource_bind) +{ + RMT_ASSERT(resource_list); + RMT_RETURN_ON_ERROR(resource_list, RMT_ERROR_INVALID_POINTER); + + const uint64_t handle = GenerateResourceHandle(resource_bind->resource_identifier); + + RmtResource* resource = FindResourceById(resource_list, handle); + RMT_RETURN_ON_ERROR(resource, RMT_ERROR_NO_RESOURCE_FOUND); + + // NOTE: We have multiple binds per resource for command buffer allocators, + // This is because they grow in size to accomodate the allocators needs. GPU events + // are often inlined into command buffers, so these are also affected by extension. + // for now we are only handling the command allocator case. + if (resource->bound_allocation != nullptr) + { + switch (resource->resource_type) + { + case kRmtResourceTypeCommandAllocator: + return RMT_ERROR_RESOURCE_ALREADY_BOUND; + + case kRmtResourceTypeGpuEvent: + return RMT_OK; + + default: + // Should never reach this point, handle it just in case. + RMT_ASSERT(false); + return RMT_OK; + } + } + + // bind the allocation to the resource. + resource->bind_time = resource_bind->common.timestamp; + resource->address = resource_bind->virtual_address; + resource->size_in_bytes = resource_bind->size_in_bytes; + + // find the bound allocation + const RmtErrorCode error_code = + RmtVirtualAllocationListGetAllocationForAddress(resource_list->virtual_allocation_list, resource_bind->virtual_address, &resource->bound_allocation); + + // look for externally shared resources. + if ((error_code == RMT_ERROR_NO_ALLOCATION_FOUND) && (resource->resource_type == kRmtResourceTypeImage) && + ((resource->image.create_flags & kRmtImageCreationFlagShareable) == kRmtImageCreationFlagShareable)) + { + // It is expected that we won't see a virtual allocate token for some shareable resources, as that memory is owned outside + // the target process. This error code will result in a dummy allocation being added to the list, so future resource calls + // looking for it will be able to "find" the allocation. + return RMT_ERROR_SHARED_ALLOCATION_NOT_FOUND; + } + + // only external shared can fail to find the allocation. + RMT_ASSERT(error_code == RMT_OK); + + // Count the resources on each allocation. We fill in pointers later + // when we do the fix up pass in snapshot generation. + if (resource->bound_allocation != nullptr) + { + RMT_ASSERT(resource->bound_allocation->base_address <= resource->address); + + // count resources and non-heap resources. + if (resource->resource_type != kRmtResourceTypeHeap) + { + ((RmtVirtualAllocation*)resource->bound_allocation)->non_heap_resource_count++; + } + ((RmtVirtualAllocation*)resource->bound_allocation)->resource_count++; + + // update the commit type of the allocation + UpdateCommitType(resource_list, resource); + } + + // track the bytes bound + resource_list->resource_usage_size[RmtResourceGetUsageType(resource)] += resource->size_in_bytes; + + return RMT_OK; +} + +RmtErrorCode RmtResourceListAddResourceDestroy(RmtResourceList* resource_list, const RmtTokenResourceDestroy* resource_destroy) +{ + RMT_ASSERT(resource_list); + RMT_RETURN_ON_ERROR(resource_list, RMT_ERROR_INVALID_POINTER); + + const uint64_t handle = GenerateResourceHandle(resource_destroy->resource_identifier); + RmtResource* resource = FindResourceById(resource_list, handle); + RMT_RETURN_ON_ERROR(resource, RMT_ERROR_NO_RESOURCE_FOUND); + + // remove the resource from the parent allocation. + if (resource->bound_allocation != nullptr) + { + ((RmtVirtualAllocation*)resource->bound_allocation)->resource_count--; + + if (resource->resource_type != kRmtResourceTypeHeap) + { + ((RmtVirtualAllocation*)resource->bound_allocation)->non_heap_resource_count--; + } + } + + // call destroy on it. + const RmtErrorCode error_code = DestroyResource(resource_list, resource); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + return RMT_OK; +} + +RmtErrorCode RmtResourceListGetResourceByResourceId(const RmtResourceList* resource_list, + RmtResourceIdentifier resource_identifier, + const RmtResource** out_resource) +{ + RMT_ASSERT(resource_list); + RMT_ASSERT(out_resource); + RMT_RETURN_ON_ERROR(resource_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_resource, RMT_ERROR_INVALID_POINTER); + + const uint64_t handle = GenerateResourceHandle(resource_identifier); + + RmtResource* resource = FindResourceById(resource_list, handle); + RMT_RETURN_ON_ERROR(resource, RMT_ERROR_NO_RESOURCE_FOUND); + *out_resource = resource; + return RMT_OK; +} diff --git a/source/backend/rmt_resource_list.h b/source/backend/rmt_resource_list.h new file mode 100644 index 0000000..253edac --- /dev/null +++ b/source/backend/rmt_resource_list.h @@ -0,0 +1,303 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Allocation list. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_RESOURCE_LIST_H_ +#define RMV_BACKEND_RMT_RESOURCE_LIST_H_ + +#include +#include +#include "rmt_configuration.h" +#include "rmt_pool.h" +#include "rmt_format.h" + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtVirtualAllocationList RmtVirtualAllocationList; +typedef struct RmtVirtualAllocation RmtVirtualAllocation; +typedef struct RmtResourceIdNode RmtResourceIdNode; +typedef struct RmtDataSnapshot RmtDataSnapshot; + +/// An enumeration of the different types of a backing store that can sit behind a resource. +typedef enum RmtResourceBackingStorage +{ + kRmtResourceBackingStorageUnmapped = kRmtHeapTypeCount, ///< Unmapped histogram entry. + kRmtResourceBackingStorageUnknown = (kRmtHeapTypeCount + 1), ///< Unknown histogram entry. + + // Add above this. + kRmtResourceBackingStorageCount +} RmtResourceBackingStorage; + +/// An enumeration of all bits for the flags field of RmtResource. +typedef enum RmtResourceFlagBits +{ + kRmtResourceFlagDangling = (1 << 0), ///< The resource was left dangling by freeing the underlaying virtual allocation without destroying the resource. +} RmtResourceFlagBits; + +/// An enumeration of asbtract usage types. +typedef enum RmtResourceUsageType +{ + kRmtResourceUsageTypeUnknown, ///< The resource usage type is unknown. + kRmtResourceUsageTypeDepthStencil, ///< The resource is a depth/stencil buffer. + kRmtResourceUsageTypeRenderTarget, ///< The resource is a color target. + kRmtResourceUsageTypeTexture, ///< The resource is a texture. + kRmtResourceUsageTypeVertexBuffer, ///< The resource is a vertex buffer. + kRmtResourceUsageTypeIndexBuffer, ///< The resource is an index buffer. + kRmtResourceUsageTypeUav, ///< The resource is an unordered access view. + kRmtResourceUsageTypeShaderPipeline, ///< The resource is a shader pipeline. + kRmtResourceUsageTypeCommandBuffer, ///< The resource is a command buffer. + kRmtResourceUsageTypeHeap, ///< The resource is a memory heap. + kRmtResourceUsageTypeDescriptors, ///< The resource is a descroptor heap/pool. + kRmtResourceUsageTypeBuffer, ///< The resource is a buffer. + kRmtResourceUsageTypeGpuEvent, ///< The resource is a GPU event. + kRmtResourceUsageTypeFree, ///< An additional type to represent memory that is allocated, but not bound to something. + kRmtResourceUsageTypeInternal, + + // Add above this. + kRmtResourceUsageTypeCount +} RmtResourceUsageType; + +/// A structure encapsulating a single resource. +typedef struct RmtResource +{ + char name[RMT_MAXIMUM_NAME_LENGTH]; ///< The name of the resource. + RmtResourceIdentifier identifier; ///< A GUID for the this resource. + uint64_t create_time; ///< The time the resource was created. + uint64_t bind_time; ///< The time the resource was last bound to a virtual address range. + uint64_t address; ///< The virtual address of the resource. + uint64_t size_in_bytes; ///< The total size of the resource. + const RmtVirtualAllocation* + bound_allocation; ///< An pointers to a RmtAllocation structure containing the virtual address allocation containing this resource. This is set to NULL if the resource isn't bound to a virtual address. + uint32_t flags; ///< Flags on the resource. + RmtCommitType commit_type; ///< The commit type of the resource. + RmtResourceType resource_type; ///< The type of the resource. + RmtOwnerType owner_type; ///< The owner of the resource. + + union + { + RmtResourceDescriptionImage image; ///< Valid when resourceType is kRmtResourceTypeImage. + RmtResourceDescriptionBuffer buffer; ///< Valid when resourceType is kRmtResourceTypeBuffer. + RmtResourceDescriptionGpuEvent gpu_event; ///< Valid when resourceType is kRmtResourceTypeGpuEvent. + RmtResourceDescriptionBorderColorPalette + border_color_palette; ///< Valid when resourceType is kRmtResourceTypeBorderColorPalette. + RmtResourceDescriptionPerfExperiment perf_experiment; ///< Valid when resourceType is kRmtResourceTypePerfExperiment. + RmtResourceDescriptionQueryHeap query_heap; ///< Valid when resourceType is kRmtResourceTypeQueryHeap. + RmtResourceDescriptionPipeline pipeline; ///< Valid when resourceType is kRmtResourceTypePipeline. + RmtResourceDescriptionVideoDecoder video_decoder; ///< Valid when resourceType is kRmtResourceTypeVideoDecoder. + RmtResourceDescriptionVideoEncoder video_encoder; ///< Valid when resourceType is kRmtResourceTypeVideoEncoder. + RmtResourceDescriptionHeap heap; ///< Valid when resourceType is kRmtResourceTypeHeap. + RmtResourceDescriptionDescriptorHeap descriptor_heap; ///< Valid when resourceType is kRmtResourceTypeDescriptorHeap. + RmtResourceDescriptionDescriptorPool descriptor_pool; ///< Valid when resourceType is kRmtResourceTypeDescriptorPool. + RmtResourceDescriptionCommandAllocator + command_allocator; ///< Valid when resourceType is RMT_RESOURCE_TYPE_COMMAND_ALLOCATOR. + RmtResourceDescriptionMiscInternal misc_internal; ///< Valid when resourceType is RMT_RESOURCE_TYPE_MISC_INTERNAL. + }; + + RmtResourceIdNode* id_node; ///< A pointer to the RmtResourceIdNode structure in the tree, used to quickly locate this resource by ID. +} RmtResource; + +/// Get the resource usage type from the resource. +/// +/// @param [in] resource A pointer to a RmtResource structure. +/// +/// @returns +/// The usage type of the resource. +RmtResourceUsageType RmtResourceGetUsageType(const RmtResource* resource); + +/// Calculate the offset (in bytes) from the start of the base allocation that the resource is bound to. +/// +/// @param [in] resource A pointer to a RmtResource structure. +/// +/// @returns +/// The offset from the bound allocation. +uint64_t RmtResourceGetOffsetFromBoundAllocation(const RmtResource* resource); + +/// Get the base virtual address for the resource. +/// +/// @param [in] resource A pointer to a RmtResource structure. +/// +/// @returns +/// The virtual address of this resource. +RmtGpuAddress RmtResourceGetVirtualAddress(const RmtResource* resource); + +/// Check if a virtual allocation contains all or part of a resource. +/// +/// @param [in] resource A pointer to a RmtResource structure. +/// @param [in] address_start The start of the virtual address range. +/// @param [in] address_end The end of the virtual address range. +/// +/// @returns +/// True if any of the specified virtual address range overlaps any of the virtual address used by the resource. +bool RmtResourceOverlapsVirtualAddressRange(const RmtResource* resource, RmtGpuAddress address_start, RmtGpuAddress address_end); + +/// Check if a resource is completely in the preferred heap. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure that contains the page table to check. +/// @param [in] resource A pointer to a RmtResource structure. +/// +/// @returns +/// True if the resource is completely in its preferred heap. +bool RmtResourceIsCompletelyInPreferredHeap(const RmtDataSnapshot* snapshot, const RmtResource* resource); + +/// Calculate a histogram demonstrating the number of bytes of memory in each backing store type. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure that contains the page table to check. +/// @param [in] resource A pointer to a RmtResource structure. +/// @param [out] out_bytes_per_backing_storage_type The number of bytes the resource has in each backing storage type. +/// +RmtErrorCode RmtResourceGetBackingStorageHistogram(const RmtDataSnapshot* snapshot, const RmtResource* resource, uint64_t* out_bytes_per_backing_storage_type); + +/// Calculate the number of resource that alias the memory underpinning this resource. +/// +/// @param [in] resource A pointer to a RmtResource structure. +/// +/// @returns +/// THe number of resources that alias this one. +int32_t RmtResourceGetAliasCount(const RmtResource* resource); + +/// A structure for fast searching by resource ID. +typedef struct RmtResourceIdNode +{ + RmtResourceIdentifier identifer; ///< The guid to search on. + RmtResource* resource; ///< A pointer to a RmtResource structure containing the resource payload. + RmtResourceIdNode* left; ///< A pointer to a RmtResourceNodeId structure that is the left child of this node. + RmtResourceIdNode* right; ///< A pointer to a RmtResourceNodeId structure that is the right child of this node. +} RmtResourceIdNode; + +/// A structure encapsulating a list of allocations. +typedef struct RmtResourceList +{ + // Data structure for fast lookups based on resource GUID. + RmtResourceIdNode* root; ///< The root node of the acceleration structure. + RmtResourceIdNode* resource_id_nodes; ///< A pointer to an array of RmtResourceIdNode structures for the search acceleration structure. + RmtPool resource_id_node_pool; ///< The pool allocator for the memory buffer pointed to be resourceIdNodes. + + // Storage for resources. + RmtResource* resources; ///< A buffer of extra allocation details. + int32_t resource_count; ///< The number of live allocations in the list. + int32_t maximum_concurrent_resources; ///< The maximum number of resources that can be in flight at once. + const RmtVirtualAllocationList* virtual_allocation_list; ///< The virtual allocation to query for bindings. + + int32_t resource_usage_count[kRmtResourceUsageTypeCount]; ///< The number of each resource usage currently in the list. + uint64_t resource_usage_size[kRmtResourceUsageTypeCount]; + +} RmtResourceList; + +/// Calculate how many bytes of memory we need for resource list buffers. +/// +/// @param [in] maximum_concurrent_resources The maximum number of resources that can be in flight at once. +/// +/// @returns +/// The buffer size needed, in bytes. +size_t RmtResourceListGetBufferSize(int32_t maximum_concurrent_resources); + +/// Initialize the resource list. +/// +/// @param [in] resource_list A pointer to a RmtResourceList structure. +/// @param [in] buffer A pointer to a buffer containing the raw resource data. +/// @param [in] buffer_size The buffer size, in bytes. +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] maximum_concurrent_resources The maximum number of resources that can be in flight at once. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because resource_list or buffer being set to NULL. +/// @retval +/// RMT_ERROR_INVALID_SIZE The operation failed because buffer_size is invalid. +RmtErrorCode RmtResourceListInitialize(RmtResourceList* resource_list, + void* buffer, + size_t buffer_size, + const RmtVirtualAllocationList* virtual_allocation_list, + int32_t maximum_concurrent_resources); + +/// Add a resource create to the list. +/// +/// @param [in] resource_list A pointer to a RmtResourceList structure. +/// @param [in] resource_create A pointer to a RmtTokenResourceCreate structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because resource_list or resource_create being set to NULL. +RmtErrorCode RmtResourceListAddResourceCreate(RmtResourceList* resource_list, const RmtTokenResourceCreate* resource_create); + +/// Add a resource bind to the list. +/// +/// @param [in] resource_list A pointer to a RmtResourceList structure. +/// @param [in] resource_bind A pointer to a RmtTokenResourceBind structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because resource_list being set to NULL. +/// @retval +/// RMT_ERROR_NO_RESOURCE_FOUND The operation failed because the resource in resource_bind can't be found. +/// @retval +/// RMT_ERROR_RESOURCE_ALREADY_BOUND The operation failed because the resource in resource_bind is already bound. +RmtErrorCode RmtResourceListAddResourceBind(RmtResourceList* resource_list, const RmtTokenResourceBind* resource_bind); + +/// Add a resource destroy to the list. +/// +/// @param [in] resource_list A pointer to a RmtResourceList structure. +/// @param [in] resource_destroy A pointer to a RmtTokenResourceDestroy structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because resource_list being set to NULL. +/// @retval +/// RMT_ERROR_NO_RESOURCE_FOUND The operation failed because the resource in resource_destroy can't be found. +RmtErrorCode RmtResourceListAddResourceDestroy(RmtResourceList* resource_list, const RmtTokenResourceDestroy* resource_destroy); + +/// Find resource in the resource list from resource ID. +/// +/// @param [in] resource_list A pointer to a RmtResourceList structure. +/// @param [in] resource_identifier The identifier of the resource to find. +/// @param [out] out_resource A pointer to a pointer to a RmtResource structure to receive the resource. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because resource_list or out_resource being set to NULL. +/// @retval +/// RMT_ERROR_NO_RESOURCE_FOUND The operation failed because the resource can't be found. +RmtErrorCode RmtResourceListGetResourceByResourceId(const RmtResourceList* resource_list, + RmtResourceIdentifier resource_identifier, + const RmtResource** out_resource); + +/// Get the heap name for the resource passed in. +/// +/// @param [in] resource A pointer to a RmtResource structure. +/// +/// @returns +/// Pointer to a string containing the heap name. +const char* RmtResourceGetHeapTypeName(const RmtResource* resource); + +/// Get the resource name from the resource +/// +/// @param [in] resource A pointer to a RmtResource structure. +/// @param [in] buffer_size The size of the buffer to receive the resource name, in bytes. +/// @param [in] out_resource_name A pointer to a buffer to receive the resource name. +/// +/// @returns +/// true if the resource name was successfully written to the output buffer, false if not. +bool RmtResourceGetName(const RmtResource* resource, int32_t buffer_size, char** out_resource_name); + +/// Get the actual physical heap from the resource. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] resource A pointer to a RmtResource structure. +/// +/// @returns +/// The heap type ID as an RmtHeapType type. +RmtHeapType RmtResourceGetActualHeap(const RmtDataSnapshot* snapshot, const RmtResource* resource); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_RESOURCE_LIST_H_ diff --git a/source/backend/rmt_segment_info.h b/source/backend/rmt_segment_info.h new file mode 100644 index 0000000..6e8169b --- /dev/null +++ b/source/backend/rmt_segment_info.h @@ -0,0 +1,30 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief The segment information captured for the target process. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_SEGMENT_INFO_H_ +#define RMV_BACKEND_RMT_SEGMENT_INFO_H_ + +#include +#include +#include + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A structure encapsulating the information about a segment. +typedef struct RmtSegmentInfo +{ + RmtGpuAddress base_address; ///< The base address of the segment. + uint64_t size; ///< The size of the segment (in bytes). + RmtHeapType heap_type; ///< The heap type where the segment resides. + int32_t index; ///< The index of the segment. +} RmtSegmentInfo; + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_SEGMENT_INFO_H_ diff --git a/source/backend/rmt_thread.cpp b/source/backend/rmt_thread.cpp new file mode 100644 index 0000000..214595c --- /dev/null +++ b/source/backend/rmt_thread.cpp @@ -0,0 +1,74 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of platform-specific thread. +//============================================================================= + +#ifndef _WIN32 +#include +#endif // #ifndef _WIN32 + +#include +#include +#include "rmt_thread.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif /* #ifdef _WIN32 */ + +/* structure for a thread. */ +typedef struct RmtThreadInternal +{ +#ifdef _WIN32 + HANDLE handle; // handle to the thread + uint32_t thread_id; // the thread ID +#else + std::thread* thread; // pointer to the thread object + std::thread buffer; // buffer to store the thread object (using placement new) +#endif // #ifdef _WIN32 +} RmtThreadInternal; + +/* create a thread */ +RmtErrorCode RmtThreadCreate(RmtThread* thread, RmtThreadFunc thread_func, void* input_data) +{ + RMT_ASSERT_MESSAGE(thread, "Parameter thread is NULL."); + RMT_ASSERT_MESSAGE(thread, "Parameter threadFunc is NULL."); + RMT_RETURN_ON_ERROR(thread, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(thread_func, RMT_ERROR_INVALID_POINTER); + + // if this fails it means the external opaque structure isn't large enough for an internal thread. + RMT_STATIC_ASSERT(sizeof(RmtThread) >= sizeof(RmtThreadInternal)); + + // conver the pointer to the internal representation + RmtThreadInternal* thread_internal = (RmtThreadInternal*)thread; + +#ifdef _WIN32 + thread_internal->handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)thread_func, input_data, 0, (LPDWORD)&thread_internal->thread_id); + RMT_RETURN_ON_ERROR(thread_internal->handle != NULL, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#else + thread_internal->thread = new (&thread_internal->buffer) std::thread(thread_func, input_data); +#endif // #ifdef _WIN32 + + return RMT_OK; +} + +/* wait for the thread to exit */ +RmtErrorCode RmtThreadWaitForExit(RmtThread* thread) +{ + RMT_ASSERT_MESSAGE(thread, "Parameter thread is NULL."); + RMT_RETURN_ON_ERROR(thread, RMT_ERROR_INVALID_POINTER); + + // convert the pointer to the internal representation + RmtThreadInternal* thread_internal = (RmtThreadInternal*)thread; + + // wait for the thread to exit +#ifdef _WIN32 + WaitForSingleObject(thread_internal->handle, INFINITE); +#else + thread_internal->thread->join(); + thread_internal->thread->~thread(); +#endif // #ifdef _WIN32 + + return RMT_OK; +} diff --git a/source/backend/rmt_thread.h b/source/backend/rmt_thread.h new file mode 100644 index 0000000..3ccd8ca --- /dev/null +++ b/source/backend/rmt_thread.h @@ -0,0 +1,64 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Abstraction of a thread. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_THREAD_H_ +#define RMV_BACKEND_RMT_THREAD_H_ + +#include + +/// Define the size of the thread handle structure (in DWORDS). +#define RMT_THREAD_SIZE (24) + +#ifdef _WIN32 +/// The calling convention used by thread functions on Windows platform. +#define RMT_THREAD_FUNC __stdcall +#else +#define RMT_THREAD_FUNC +#endif // #ifdef _WIN32 + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A handle for a thread. +typedef struct RmtThread +{ + uint32_t data[RMT_THREAD_SIZE]; +} RmtThread; + +/// A type for the thread function. +typedef uint32_t(RMT_THREAD_FUNC* RmtThreadFunc)(void* input_data); + +/// Create a new thread. +/// +/// @param [in,out] thread A pointer to a RmtThread structure to contain a handle to the thread. +/// @param [in] thread_func The function to execute as the thread's main function. +/// @param [in] input_data Data to pass to the threads inputData parameter. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter thread or threadFunc was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtThreadCreate(RmtThread* thread, RmtThreadFunc thread_func, void* input_data); + +/// Wait for the thread to exit. +/// +/// @param [in] thread A pointer to a RmtThread structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter thread was NULL. +/// +RmtErrorCode RmtThreadWaitForExit(RmtThread* thread); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_THREAD_H_ diff --git a/source/backend/rmt_thread_event.cpp b/source/backend/rmt_thread_event.cpp new file mode 100644 index 0000000..fbdff3b --- /dev/null +++ b/source/backend/rmt_thread_event.cpp @@ -0,0 +1,127 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief +//============================================================================= + +#include +#include "rmt_thread_event.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include "../third_party/pevents/pevents.h" +#endif // #ifdef _WIN32 + +// structure for thread event +typedef struct RmtThreadEventInternal +{ +#ifdef _WIN32 + HANDLE handle; +#else + neosmart::neosmart_event_t handle; +#endif // #ifdef _WIN32 +} RmtThreadEventInternal; + +// create a thread event +RmtErrorCode RmtThreadEventCreate(RmtThreadEvent* thread_event, bool initial_value, bool manual_reset, const char* name) +{ + RMT_ASSERT_MESSAGE(thread_event, "Parameter threadEvent is NULL."); + RMT_RETURN_ON_ERROR(thread_event, RMT_ERROR_INVALID_POINTER); + + // convert the pointer to the internal representation + RmtThreadEventInternal* thread_event_internal = (RmtThreadEventInternal*)thread_event; + + // if this fails it means the external opaque structure isn't large enough for an internal thread event. + RMT_STATIC_ASSERT(sizeof(RmtThreadEvent) >= sizeof(RmtThreadEventInternal)); + +#ifdef _WIN32 + thread_event_internal->handle = CreateEventA(NULL, manual_reset, initial_value, name); +#else + RMT_UNUSED(name); + thread_event_internal->handle = neosmart::CreateEvent(manual_reset, initial_value); +#endif // #ifdef _WIN32 + + RMT_RETURN_ON_ERROR(thread_event_internal->handle != NULL, RMT_ERROR_PLATFORM_FUNCTION_FAILED); + return RMT_OK; +} + +// signal a thread event +RmtErrorCode RmtThreadEventSignal(RmtThreadEvent* thread_event) +{ + RMT_ASSERT_MESSAGE(thread_event, "Parameter threadEvent is NULL."); + RMT_RETURN_ON_ERROR(thread_event, RMT_ERROR_INVALID_POINTER); + + // convert the pointer to the internal representation + RmtThreadEventInternal* thread_event_internal = (RmtThreadEventInternal*)thread_event; + +#ifdef _WIN32 + // signal the event + BOOL result = SetEvent(thread_event_internal->handle); +#else + int result = neosmart::SetEvent(thread_event_internal->handle); +#endif // #ifdef _WIN32 + + RMT_RETURN_ON_ERROR(result, RMT_ERROR_PLATFORM_FUNCTION_FAILED); + return RMT_OK; +} + +/* wait for a thread event */ +RmtErrorCode RmtThreadEventWait(RmtThreadEvent* thread_event) +{ + RMT_ASSERT_MESSAGE(thread_event, "Parameter threadEvent is NULL."); + RMT_RETURN_ON_ERROR(thread_event, RMT_ERROR_INVALID_POINTER); + + // convert the pointer to the internal representation + RmtThreadEventInternal* thread_event_internal = (RmtThreadEventInternal*)thread_event; + +#ifdef _WIN32 + const uint32_t result = WaitForSingleObject(thread_event_internal->handle, INFINITE); + RMT_RETURN_ON_ERROR(result == WAIT_OBJECT_0, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#else + int32_t result = neosmart::WaitForEvent(thread_event_internal->handle); + RMT_RETURN_ON_ERROR(result == 0, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#endif // #ifdef _WIN32 + + return RMT_OK; +} + +/* reset a thread event */ +RmtErrorCode RmtThreadEventReset(RmtThreadEvent* thread_event) +{ + RMT_ASSERT_MESSAGE(thread_event, "Parameter threadEvent is NULL."); + RMT_RETURN_ON_ERROR(thread_event, RMT_ERROR_INVALID_POINTER); + + // convert the pointer to the internal representation + RmtThreadEventInternal* thread_event_internal = (RmtThreadEventInternal*)thread_event; + +#ifdef _WIN32 + const uint32_t result = ResetEvent(thread_event_internal->handle); + RMT_RETURN_ON_ERROR(result == WAIT_OBJECT_0, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#else + int result = neosmart::ResetEvent(thread_event_internal->handle); + + RMT_RETURN_ON_ERROR(result == 0, RMT_ERROR_PLATFORM_FUNCTION_FAILED); +#endif // #ifdef _WIN32 + + return RMT_OK; +} + +RmtErrorCode RmtThreadEventDestroy(RmtThreadEvent* thread_event) +{ + RMT_ASSERT_MESSAGE(thread_event, "Parameter threadEvent is NULL."); + RMT_RETURN_ON_ERROR(thread_event, RMT_ERROR_INVALID_POINTER); + + // convert the pointer to the internal representation + RmtThreadEventInternal* thread_event_internal = (RmtThreadEventInternal*)thread_event; + +#ifdef _WIN32 + BOOL error_code = CloseHandle(thread_event_internal->handle); +#else + int error_code = neosmart::DestroyEvent(thread_event_internal->handle); +#endif // #ifdef _WIN32 + + RMT_RETURN_ON_ERROR(error_code, RMT_ERROR_PLATFORM_FUNCTION_FAILED); + return RMT_OK; +} diff --git a/source/backend/rmt_thread_event.h b/source/backend/rmt_thread_event.h new file mode 100644 index 0000000..fd2086f --- /dev/null +++ b/source/backend/rmt_thread_event.h @@ -0,0 +1,96 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief +//============================================================================= + +#ifndef RMV_BACKEND_RMT_THREAD_EVENT_H_ +#define RMV_BACKEND_RMT_THREAD_EVENT_H_ + +#include + +/// Define the size of a thread event handle structure (in DWORDS). +#define RMT_THREAD_EVENT_SIZE (4) + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// A handle for a thread event. +typedef struct RmtThreadEvent +{ + uint32_t data[RMT_THREAD_EVENT_SIZE]; +} RmtThreadEvent; + +/// Create a new thread event. +/// +/// @param [in] thread_event A pointer to a RmtThreadEvent structure. +/// @param [in] initial_value Boolean value indicating what the set the thread event to. +/// @param [in] manual_reset Boolean value indicating if the thread event should require manually reset. +/// @param [in] name The name of the thread event. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter threadEvent was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtThreadEventCreate(RmtThreadEvent* thread_event, bool initial_value, bool manual_reset, const char* name); + +/// Signal a thread event. +/// +/// @param [in] thread_event A pointer to a RmtThreadEvent structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter threadEvent was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtThreadEventSignal(RmtThreadEvent* thread_event); + +/// Wait for a thread event to be signalled. +/// +/// @param [in] thread_event A pointer to a RmtThreadEvent structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter threadEvent was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtThreadEventWait(RmtThreadEvent* thread_event); + +/// Wait for a thread event to be reset. +/// +/// @param [in] thread_event A pointer to a RmtThreadEvent structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter threadEvent was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtThreadEventReset(RmtThreadEvent* thread_event); + +/// Destroy a thread event. +/// +/// @param [in] thread_event A pointer to a RmtThreadEvent structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The parameter threadEvent was NULL. +/// @retval +/// RMT_ERROR_PLATFORM_FUNCTION_FAILED A platform-specific function failed. +/// +RmtErrorCode RmtThreadEventDestroy(RmtThreadEvent* thread_event); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_THREAD_EVENT_H_ diff --git a/source/backend/rmt_virtual_allocation_list.cpp b/source/backend/rmt_virtual_allocation_list.cpp new file mode 100644 index 0000000..a12de16 --- /dev/null +++ b/source/backend/rmt_virtual_allocation_list.cpp @@ -0,0 +1,757 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of the virtual allocation list functions. +//============================================================================= + +#include + +#include "rmt_virtual_allocation_list.h" +#include "rmt_resource_list.h" +#include "rmt_page_table.h" +#include "rmt_address_helper.h" +#include "rmt_data_snapshot.h" +#include +#include // memcpy + +// Helper function to improve tree balance by hashing the handles. +static RmtGpuAddress HashGpuAddress(RmtGpuAddress address) +{ +#if 0 + int32_t low = (int32_t)address; + int32_t high = (int32_t)(address >> 32); + return (int32_t)((0x1337c0dedeadbeef * low + 0x12ed89f849021acd * high + 0x293fff346aba878e) >> 32); +#else + return address; +#endif +} + +// helper function to find smallest value in a branch +static RmtVirtualAllocationInterval* GetSmallestNode(RmtVirtualAllocationInterval* node) +{ + while ((node != nullptr) && (node->left != nullptr)) + { + node = node->left; + } + return node; +} + +// recursive function to find a node by ID +static RmtVirtualAllocationInterval* FindAllocationNode(RmtVirtualAllocationInterval* root, RmtGpuAddress gpu_address) +{ + if (root == nullptr) + { + return NULL; + } + + if (root->base_address <= gpu_address && gpu_address < (root->base_address + (RmtGetPageSize(kRmtPageSize4Kb) * root->size_in_4kb_pages))) + { + return root; + } + + if (gpu_address < root->base_address) + { + return FindAllocationNode(root->left, gpu_address); + } + + return FindAllocationNode(root->right, gpu_address); +} + +// recursive function to insert a node +static RmtVirtualAllocationInterval* InsertNode(RmtVirtualAllocationList* virtual_allocation_list, + RmtVirtualAllocationInterval* node, + RmtGpuAddress gpu_address, + int32_t size_in_pages, + RmtVirtualAllocation* allocation) +{ + if (node == nullptr) + { + // create a new node + RmtVirtualAllocationInterval* new_node = (RmtVirtualAllocationInterval*)RmtPoolAllocate(&virtual_allocation_list->allocation_interval_pool); + new_node->base_address = gpu_address; + new_node->size_in_4kb_pages = size_in_pages; + new_node->dead = 0; + new_node->allocation = allocation; + new_node->left = NULL; + new_node->right = NULL; + return new_node; + } + + if (gpu_address < node->base_address) + { + node->left = InsertNode(virtual_allocation_list, node->left, gpu_address, size_in_pages, allocation); + } + else if (gpu_address >= node->base_address) + { + node->right = InsertNode(virtual_allocation_list, node->right, gpu_address, size_in_pages, allocation); + } + else + { + RMT_ASSERT_FAIL("WTF"); + } + + return node; +} + +// recursive function to delete a node +static RmtVirtualAllocationInterval* DeleteNode(RmtVirtualAllocationList* virtual_allocation_list, + RmtVirtualAllocationInterval* node, + RmtGpuAddress gpu_address) +{ + if (node == nullptr) + { + return node; + } + + if (gpu_address < node->base_address) + { + node->left = DeleteNode(virtual_allocation_list, node->left, gpu_address); + } + else if (gpu_address > node->base_address) + { + node->right = DeleteNode(virtual_allocation_list, node->right, gpu_address); + } + else + { + if (node->left == nullptr) + { + RmtVirtualAllocationInterval* child = node->right; + node->base_address = 0; + node->size_in_4kb_pages = 0; + node->left = NULL; + node->right = NULL; + node->dead = 0; + RmtPoolFree(&virtual_allocation_list->allocation_interval_pool, node); + return child; + } + if (node->right == nullptr) + { + RmtVirtualAllocationInterval* child = node->left; + node->base_address = 0; + node->size_in_4kb_pages = 0; + node->dead = 0; + node->left = NULL; + node->right = NULL; + RmtPoolFree(&virtual_allocation_list->allocation_interval_pool, node); + return child; + } + + RmtVirtualAllocationInterval* smallest_child = GetSmallestNode(node->right); + node->base_address = smallest_child->base_address; + node->size_in_4kb_pages = smallest_child->size_in_4kb_pages; + node->dead = smallest_child->dead; + + // update payload pointers. + RMT_ASSERT(node->allocation); + node->allocation = smallest_child->allocation; + + // now delete the node we just moved. + node->right = DeleteNode(virtual_allocation_list, node->right, smallest_child->base_address); + } + + return node; +} + +// search the acceleration structure for a resource. +static RmtVirtualAllocationInterval* FindAllocationIntervalByAddress(const RmtVirtualAllocationList* virtual_allocation_list, RmtGpuAddress gpu_address) +{ + RmtVirtualAllocationInterval* found_node = FindAllocationNode(virtual_allocation_list->root, gpu_address); + if (found_node == nullptr) + { + return NULL; + } + + return found_node; +} + +// add a resource to the acceleration structure. +static RmtErrorCode AddAllocationToTree(RmtVirtualAllocationList* virtual_allocation_list, + RmtGpuAddress gpu_address, + int32_t size_in_4kb_pages, + RmtVirtualAllocation* virtual_allocation) +{ + const size_t pool_count = virtual_allocation_list->allocation_interval_pool.allocated; + virtual_allocation_list->root = InsertNode(virtual_allocation_list, virtual_allocation_list->root, gpu_address, size_in_4kb_pages, virtual_allocation); + RMT_ASSERT(virtual_allocation_list->allocation_interval_pool.allocated == pool_count + 1); + return RMT_OK; +} + +// destroy a resource from the acceleration structure. +static RmtErrorCode RemoveAllocationFromTree(RmtVirtualAllocationList* virtual_allocation_list, RmtGpuAddress gpu_address) +{ + const size_t pool_count = virtual_allocation_list->allocation_interval_pool.allocated; + virtual_allocation_list->root = DeleteNode(virtual_allocation_list, virtual_allocation_list->root, gpu_address); + RMT_ASSERT(virtual_allocation_list->allocation_interval_pool.allocated == pool_count - 1); + return RMT_OK; +} + +uint64_t RmtVirtualAllocationGetSizeInBytes(const RmtVirtualAllocation* virtual_allocation) +{ + RMT_RETURN_ON_ERROR(virtual_allocation, 0); + + return ((uint64_t)virtual_allocation->size_in_4kb_page) << 12; +} + +uint64_t RmtVirtualAllocationGetLargestResourceSize(const RmtVirtualAllocation* virtual_allocation) +{ + RMT_RETURN_ON_ERROR(virtual_allocation, 0); + RMT_RETURN_ON_ERROR(virtual_allocation->resource_count, 0); + + uint64_t maximum_resource_size = 0; + for (int32_t current_resource_index = 0; current_resource_index < virtual_allocation->resource_count; ++current_resource_index) + { + maximum_resource_size = RMT_MAXIMUM(maximum_resource_size, (uint64_t)virtual_allocation->resources[current_resource_index]->size_in_bytes); + } + + return maximum_resource_size; +} + +uint64_t RmtVirtualAllocationGetTotalResourceMemoryInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation) +{ + RMT_ASSERT(snapshot); + RMT_ASSERT(virtual_allocation); + RMT_RETURN_ON_ERROR(virtual_allocation, 0); + RMT_RETURN_ON_ERROR(virtual_allocation->resource_count, 0); + RMT_ASSERT(virtual_allocation->resource_count <= snapshot->region_stack_count); + RMT_RETURN_ON_ERROR(virtual_allocation->resource_count <= snapshot->region_stack_count, 0); + + RmtMemoryRegion* region_stack = snapshot->region_stack_buffer; + int32_t current_region_stack_top = 0; + + uint64_t total_resource_size = 0; + for (int32_t current_resource_index = 0; current_resource_index < virtual_allocation->resource_count; ++current_resource_index) + { + const RmtResource* current_resource = virtual_allocation->resources[current_resource_index]; + const size_t current_resource_offset = current_resource->address - virtual_allocation->base_address; + + if (current_region_stack_top == 0) + { + region_stack[current_region_stack_top].offset = current_resource_offset; + region_stack[current_region_stack_top].size = current_resource->size_in_bytes; + current_region_stack_top++; + } + else + { + RmtMemoryRegion* top_region = ®ion_stack[current_region_stack_top - 1]; + if ((top_region->offset + top_region->size) <= current_resource_offset) + { + region_stack[current_region_stack_top].offset = current_resource_offset; + region_stack[current_region_stack_top].size = current_resource->size_in_bytes; + current_region_stack_top++; + continue; + } + + // merge ranges. + const size_t new_size = (current_resource_offset + current_resource->size_in_bytes) - region_stack[current_region_stack_top - 1].offset; + region_stack[current_region_stack_top - 1].offset = current_resource_offset; + region_stack[current_region_stack_top - 1].size = new_size; + } + } + + for (int32_t current_stack_index = 0; current_stack_index < current_region_stack_top; ++current_stack_index) + { + total_resource_size += region_stack[current_stack_index].size; + } + + RMT_ASSERT(RmtVirtualAllocationGetSizeInBytes(virtual_allocation) >= total_resource_size); + return total_resource_size; +} + +uint64_t RmtVirtualAllocationGetTotalUnboundSpaceInAllocation(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation) +{ + RMT_ASSERT(virtual_allocation); + RMT_RETURN_ON_ERROR(virtual_allocation, 0); + + const uint64_t total_resource_memory = RmtVirtualAllocationGetTotalResourceMemoryInBytes(snapshot, virtual_allocation); + + RMT_ASSERT(total_resource_memory <= RmtVirtualAllocationGetSizeInBytes(virtual_allocation)); + return RmtVirtualAllocationGetSizeInBytes(virtual_allocation) - total_resource_memory; +} + +uint64_t RmtVirtualAllocationGetAverageResourceSizeInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation) +{ + RMT_ASSERT(virtual_allocation); + if (virtual_allocation == NULL || virtual_allocation->resource_count == 0) + { + return 0; + } + + const uint64_t total_resource_size = RmtVirtualAllocationGetTotalResourceMemoryInBytes(snapshot, virtual_allocation); + return total_resource_size / virtual_allocation->resource_count; +} + +uint64_t RmtVirtualAllocationGetResourceStandardDeviationInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation) +{ + RMT_ASSERT(virtual_allocation); + if (virtual_allocation == NULL || virtual_allocation->resource_count == 0) + { + return 0; + } + + uint64_t variance = 0; + int64_t avg_resource_size = RmtVirtualAllocationGetAverageResourceSizeInBytes(snapshot, virtual_allocation); + for (int32_t current_resource_index = 0; current_resource_index < virtual_allocation->resource_count; ++current_resource_index) + { + const int64_t diff = virtual_allocation->resources[current_resource_index]->size_in_bytes - avg_resource_size; + variance = diff * diff; + } + variance /= virtual_allocation->resource_count; + + return (uint64_t)sqrt((double)variance); +} + +float RmtVirtualAllocationGetFragmentationQuotient(const RmtVirtualAllocation* virtual_allocation) +{ + RMT_ASSERT(virtual_allocation); + + if (virtual_allocation == NULL || virtual_allocation->resource_count == 0) + { + return 0.0F; + } + + int32_t gaps_in_virtual_memory = 0; + uint64_t last_address = virtual_allocation->base_address; + + for (int32_t current_resource_index = 0; current_resource_index < virtual_allocation->resource_count; ++current_resource_index) + { + const RmtResource* current_resource = virtual_allocation->resources[current_resource_index]; + if (current_resource->address != last_address) + { + gaps_in_virtual_memory++; + } + + // advance the last address to the next byte in the allocation. + last_address = current_resource->bind_time + current_resource->size_in_bytes; + } + + return (float)gaps_in_virtual_memory; +} + +size_t RmtVirtualAllocationListGetBufferSize(int32_t total_allocations, int32_t max_concurrent_resources) +{ + return (total_allocations * (sizeof(RmtVirtualAllocationInterval) + sizeof(RmtVirtualAllocation))) + (max_concurrent_resources * sizeof(uintptr_t)) + + ((total_allocations + max_concurrent_resources) * sizeof(RmtMemoryRegion)); +} + +RmtErrorCode RmtVirtualAllocationListInitialize(RmtVirtualAllocationList* virtual_allocation_list, + void* buffer, + size_t buffer_size, + int32_t maximum_concurrent_allocations, + int32_t maximum_concurrent_resources, + int32_t total_allocations) +{ + RMT_ASSERT(virtual_allocation_list); + RMT_RETURN_ON_ERROR(virtual_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(buffer, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(buffer_size, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR(RmtVirtualAllocationListGetBufferSize(total_allocations, maximum_concurrent_resources) <= buffer_size, RMT_ERROR_INVALID_SIZE); + + const size_t interval_size_in_bytes = (total_allocations * sizeof(RmtVirtualAllocationInterval)); + + // dice up the buffer + virtual_allocation_list->allocation_intervals = (RmtVirtualAllocationInterval*)buffer; + virtual_allocation_list->allocation_details = (RmtVirtualAllocation*)(((uintptr_t)buffer) + interval_size_in_bytes); + virtual_allocation_list->allocation_count = 0; + virtual_allocation_list->next_allocation_guid = 0; + virtual_allocation_list->maximum_concurrent_allocations = maximum_concurrent_allocations; + virtual_allocation_list->total_allocations = total_allocations; + virtual_allocation_list->total_allocated_bytes = 0; + const size_t allocation_details_size = total_allocations * (sizeof(RmtVirtualAllocationInterval) + sizeof(RmtVirtualAllocation)); + virtual_allocation_list->resource_connectivity = (RmtResource**)((uintptr_t)buffer + allocation_details_size); + virtual_allocation_list->resource_connectivity_count = maximum_concurrent_resources; + const size_t resource_connectivity_size = maximum_concurrent_resources * sizeof(RmtResource*); + virtual_allocation_list->unbound_memory_regions = (RmtMemoryRegion*)((uintptr_t)buffer + allocation_details_size + resource_connectivity_size); + virtual_allocation_list->unbound_memory_region_count = maximum_concurrent_resources + 1; + + // initialize interval pool. + RmtPoolInitialize(&virtual_allocation_list->allocation_interval_pool, + virtual_allocation_list->allocation_intervals, + interval_size_in_bytes, + sizeof(RmtVirtualAllocationInterval)); + virtual_allocation_list->root = NULL; + + memset(virtual_allocation_list->allocations_per_preferred_heap, 0, sizeof(virtual_allocation_list->allocations_per_preferred_heap)); + return RMT_OK; +} + +RmtErrorCode RmtVirtualAllocationListAddAllocation(RmtVirtualAllocationList* virtual_allocation_list, + uint64_t timestamp, + RmtGpuAddress address, + int32_t size_in_4kb_pages, + const RmtHeapType preferences[4], + RmtOwnerType owner) +{ + RMT_ASSERT(virtual_allocation_list); + RMT_RETURN_ON_ERROR(virtual_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(size_in_4kb_pages, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR((address >> 12) + size_in_4kb_pages < RMT_PAGE_TABLE_MAX_SIZE, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR(virtual_allocation_list->allocation_count < virtual_allocation_list->total_allocations, RMT_ERROR_OUT_OF_MEMORY); + + // check if this region overlaps an existing region + const RmtVirtualAllocation* found_allocation = NULL; + const RmtErrorCode error_code = RmtVirtualAllocationListGetAllocationForAddress(virtual_allocation_list, address, &found_allocation); + if (error_code == RMT_OK) + { + RMT_ASSERT((found_allocation->flags & kRmtAllocationDetailIsDead) != kRmtAllocationDetailIsDead); + } + RMT_RETURN_ON_ERROR(error_code != RMT_OK, error_code); + + const int32_t next_allocation_index = virtual_allocation_list->allocation_count++; + + // fill out the details. + RmtVirtualAllocation* allocation_details = &virtual_allocation_list->allocation_details[next_allocation_index]; + allocation_details->base_address = address; + allocation_details->size_in_4kb_page = size_in_4kb_pages; + allocation_details->guid = virtual_allocation_list->next_allocation_guid++; + allocation_details->flags = 0; + allocation_details->timestamp = timestamp; + allocation_details->last_residency_update = 0; + allocation_details->last_cpu_map = 0; + allocation_details->last_cpu_un_map = 0; + allocation_details->add_count = 0; + allocation_details->remove_count = 0; + allocation_details->non_heap_resource_count = 0; + allocation_details->map_count = 0; + allocation_details->owner = owner; + allocation_details->commit_type = 0; + allocation_details->resource_count = 0; + allocation_details->next_resource_index = 0; + + for (int32_t current_heap_preference_index = 0; current_heap_preference_index < 4; ++current_heap_preference_index) + { + allocation_details->heap_preferences[current_heap_preference_index] = preferences[current_heap_preference_index]; + } + + // fill out the allocation interval + const RmtGpuAddress hashed_address = HashGpuAddress(address); + AddAllocationToTree(virtual_allocation_list, hashed_address, size_in_4kb_pages, allocation_details); + + const uint64_t size_in_bytes = (size_in_4kb_pages << 12); + virtual_allocation_list->total_allocated_bytes += size_in_bytes; + virtual_allocation_list->allocations_per_preferred_heap[allocation_details->heap_preferences[0]] += size_in_bytes; + return RMT_OK; +} + +RmtErrorCode RmtVirtualAllocationListRemoveAllocation(RmtVirtualAllocationList* virtual_allocation_list, RmtGpuAddress address) +{ + RMT_ASSERT(virtual_allocation_list); + RMT_RETURN_ON_ERROR(virtual_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(virtual_allocation_list->allocation_count, RMT_ERROR_NO_ALLOCATION_FOUND); + + const RmtGpuAddress hashed_address = HashGpuAddress(address); + RmtVirtualAllocationInterval* current_allocation_interval = FindAllocationIntervalByAddress(virtual_allocation_list, hashed_address); + if (current_allocation_interval == nullptr) + { + return RMT_ERROR_NO_ALLOCATION_FOUND; + } + + // mark the allocation as dead, the allocation will then be removed later on + // when the resource pointers are set on the allocation. Removal of allocations + // is deferred in this way, as moving the virtual allocation structures + // on-demand means we'd have to dive off and fix up the boundAllocation pointers + // on the resources. As we don't set these until the end of the parsing process + // this is undesireable. Additionally, this has the benefit that we can + // potentially detect "dangling" resources, i.e.: resources which are not + // destroyed that are still bound to a free'd range in the virtual address map. + // See the rmtVirtualAllocationListCompact for more info. + current_allocation_interval->dead = 1; + current_allocation_interval->allocation->flags |= kRmtAllocationDetailIsDead; + + const uint64_t size_in_bytes = current_allocation_interval->allocation->size_in_4kb_page << 12; + virtual_allocation_list->total_allocated_bytes -= size_in_bytes; + virtual_allocation_list->allocations_per_preferred_heap[current_allocation_interval->allocation->heap_preferences[0]] -= size_in_bytes; + + // remove efrom the tree + RemoveAllocationFromTree(virtual_allocation_list, hashed_address); + + return RMT_OK; +} + +RmtErrorCode RmtVirtualAllocationListAddResourceReference(RmtVirtualAllocationList* virtual_allocation_list, + uint64_t timestamp, + RmtGpuAddress address, + RmtResidencyUpdateType update_type, + RmtQueue queue) +{ + RMT_UNUSED(queue); + + RMT_ASSERT(virtual_allocation_list); + RMT_RETURN_ON_ERROR(virtual_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(virtual_allocation_list->allocation_count, RMT_ERROR_NO_ALLOCATION_FOUND); + + // find the allocation index. + const RmtGpuAddress hashed_address = HashGpuAddress(address); + RmtVirtualAllocationInterval* interval = FindAllocationIntervalByAddress(virtual_allocation_list, hashed_address); + if (interval == nullptr) + { + return RMT_ERROR_NO_ALLOCATION_FOUND; + } + + // store the residency update on the details structure. + RmtVirtualAllocation* current_details = interval->allocation; + RMT_ASSERT(current_details); + + if (update_type == kRmtResidencyUpdateTypeAdd) + { + current_details->flags |= kRmtAllocationDetailHasBeenMadeResident; + current_details->flags |= kRmtAllocationDetailIsMadeResident; + current_details->add_count++; + } + else if (update_type == kRmtResidencyUpdateTypeRemove) + { + current_details->flags |= kRmtAllocationDetailHasBeenEvicted; + current_details->flags &= ~kRmtAllocationDetailIsMadeResident; + current_details->remove_count++; + } + + current_details->last_residency_update = timestamp; + + return RMT_OK; +} + +RmtErrorCode RmtVirtualAllocationListAddCpuMap(RmtVirtualAllocationList* virtual_allocation_list, uint64_t timestamp, RmtGpuAddress address) +{ + RMT_ASSERT(virtual_allocation_list); + RMT_RETURN_ON_ERROR(virtual_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(virtual_allocation_list->allocation_count, RMT_ERROR_NO_ALLOCATION_FOUND); + + // find the allocation index. + const RmtGpuAddress hashed_address = HashGpuAddress(address); + RmtVirtualAllocationInterval* interval = FindAllocationIntervalByAddress(virtual_allocation_list, hashed_address); + if (interval == nullptr) + { + return RMT_ERROR_NO_ALLOCATION_FOUND; + } + + // store the residency update on the details structure. + RmtVirtualAllocation* current_details = interval->allocation; + current_details->flags |= kRmtAllocationDetailIsCpuMapped; + current_details->flags |= kRmtAllocationDetailHasBeenCpuMapped; + current_details->last_cpu_map = timestamp; + current_details->map_count++; + + return RMT_OK; +} + +RmtErrorCode RmtVirtualAllocationListAddCpuUnmap(RmtVirtualAllocationList* virtual_allocation_list, uint64_t timestamp, RmtGpuAddress address) +{ + RMT_ASSERT(virtual_allocation_list); + RMT_RETURN_ON_ERROR(virtual_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(virtual_allocation_list->allocation_count, RMT_ERROR_NO_ALLOCATION_FOUND); + + // find the allocation index. + const RmtGpuAddress hashed_address = HashGpuAddress(address); + RmtVirtualAllocationInterval* interval = FindAllocationIntervalByAddress(virtual_allocation_list, hashed_address); + if (interval == nullptr) + { + return RMT_ERROR_NO_ALLOCATION_FOUND; + } + + // store the residency update on the details structure. + RmtVirtualAllocation* current_details = interval->allocation; + current_details->flags &= ~kRmtAllocationDetailIsCpuMapped; + current_details->last_cpu_un_map = timestamp; + current_details->map_count--; + + return RMT_OK; +} + +RmtErrorCode RmtVirtualAllocationListGetAllocationForAddress(const RmtVirtualAllocationList* virtual_allocation_list, + RmtGpuAddress address, + const RmtVirtualAllocation** out_allocation) +{ + RMT_ASSERT(virtual_allocation_list); + RMT_ASSERT(out_allocation); + RMT_RETURN_ON_ERROR(virtual_allocation_list, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_allocation, RMT_ERROR_INVALID_POINTER); + + // find the allocation interval. + const RmtGpuAddress hashed_address = HashGpuAddress(address); + RmtVirtualAllocationInterval* interval = FindAllocationIntervalByAddress(virtual_allocation_list, hashed_address); + if (interval == nullptr) + { + return RMT_ERROR_NO_ALLOCATION_FOUND; + } + + // store the residency update on the details structure. + RmtVirtualAllocation* current_details = interval->allocation; + *out_allocation = current_details; + return RMT_OK; +} + +uint64_t RmtVirtualAllocationListGetTotalSizeInBytes(const RmtVirtualAllocationList* virtual_allocation_list) +{ + RMT_ASSERT(virtual_allocation_list); + RMT_RETURN_ON_ERROR(virtual_allocation_list, 0); + + uint64_t total_size_in_bytes = 0; + + for (int32_t current_allocation_index = 0; current_allocation_index < virtual_allocation_list->allocation_count; ++current_allocation_index) + { + const RmtVirtualAllocation* current_virtual_allocation = &virtual_allocation_list->allocation_details[current_allocation_index]; + total_size_in_bytes += RmtVirtualAllocationGetSizeInBytes(current_virtual_allocation); + } + + return total_size_in_bytes; +} + +uint64_t RmtVirtualAllocationListGetBoundTotalSizeInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocationList* virtual_allocation_list) +{ + uint64_t total_bound_size = 0; + + for (int32_t current_allocation_index = 0; current_allocation_index < virtual_allocation_list->allocation_count; ++current_allocation_index) + { + const RmtVirtualAllocation* current_virtual_allocation = &virtual_allocation_list->allocation_details[current_allocation_index]; + total_bound_size += RmtVirtualAllocationGetTotalResourceMemoryInBytes(snapshot, current_virtual_allocation); + } + + return total_bound_size; +} + +uint64_t RmtVirtualAllocationListGetUnboundTotalSizeInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocationList* virtual_allocation_list) +{ + uint64_t total_unbound_size = 0; + + for (int32_t current_allocation_index = 0; current_allocation_index < virtual_allocation_list->allocation_count; ++current_allocation_index) + { + const RmtVirtualAllocation* current_virtual_allocation = &virtual_allocation_list->allocation_details[current_allocation_index]; + total_unbound_size += RmtVirtualAllocationGetTotalUnboundSpaceInAllocation(snapshot, current_virtual_allocation); + } + + return total_unbound_size; +} + +static void DropDeadAllocations(RmtVirtualAllocationList* virtual_allocation_list) +{ + RMT_ASSERT(virtual_allocation_list); + + // drop any allocations from the end of the list, until we spot the first non-dead allocation + for (int32_t current_virtual_allocation_index = virtual_allocation_list->allocation_count - 1; current_virtual_allocation_index >= 0; + --current_virtual_allocation_index) + { + RmtVirtualAllocation* current_virtual_allocation = &virtual_allocation_list->allocation_details[current_virtual_allocation_index]; + if ((current_virtual_allocation->flags & kRmtAllocationDetailIsDead) != kRmtAllocationDetailIsDead) + { + break; + } + virtual_allocation_list->allocation_count--; + } +} + +RmtErrorCode RmtVirtualAllocationListCompact(RmtVirtualAllocationList* virtual_allocation_list, bool fixup_resources) +{ + RMT_ASSERT(virtual_allocation_list); + + DropDeadAllocations(virtual_allocation_list); + + // now perform compaction. + for (int32_t current_virtual_allocation_index = 0; current_virtual_allocation_index < virtual_allocation_list->allocation_count; + ++current_virtual_allocation_index) + { + RmtVirtualAllocation* current_virtual_allocation = &virtual_allocation_list->allocation_details[current_virtual_allocation_index]; + + // if the allocation is alive, then we can leave it be. + if ((current_virtual_allocation->flags & kRmtAllocationDetailIsDead) != kRmtAllocationDetailIsDead) + { + continue; + } + + // copy the allocation from the end of the list into this slot and then fix up + // the boundAllocation pointers on each resource to point at the new location. + DropDeadAllocations(virtual_allocation_list); + const int32_t last_virtual_allocation_index = virtual_allocation_list->allocation_count - 1; + + // special case for deleting the last element of the list. + if (current_virtual_allocation_index == last_virtual_allocation_index) + { + virtual_allocation_list->allocation_count--; + continue; + } + + // otherwise do a full copy and fix it all up. + const RmtVirtualAllocation* last_virtual_allocation = &virtual_allocation_list->allocation_details[last_virtual_allocation_index]; + + // get the original guid before copying so it can be fixed up + int32_t guid = current_virtual_allocation->guid; + memcpy(current_virtual_allocation, last_virtual_allocation, sizeof(RmtVirtualAllocation)); + + // fix up guid + current_virtual_allocation->guid = guid; + + RMT_ASSERT((current_virtual_allocation->flags & kRmtAllocationDetailIsDead) != kRmtAllocationDetailIsDead); + + if (fixup_resources) + { + for (int32_t current_resource_index = 0; current_resource_index < current_virtual_allocation->resource_count; ++current_resource_index) + { + RmtResource* current_resource = current_virtual_allocation->resources[current_resource_index]; + current_resource->bound_allocation = current_virtual_allocation; + } + } + + virtual_allocation_list->allocation_count--; + } + + return RMT_OK; +} + +RmtErrorCode RmtVirtualAllocationGetBackingStorageHistogram(const RmtDataSnapshot* snapshot, + const RmtVirtualAllocation* virtual_allocation, + uint64_t* out_bytes_per_backing_storage_type, + uint64_t* out_histogram_total) +{ + RMT_RETURN_ON_ERROR(virtual_allocation, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_bytes_per_backing_storage_type, RMT_ERROR_INVALID_POINTER) + RMT_RETURN_ON_ERROR(out_histogram_total, RMT_ERROR_INVALID_POINTER); + + const uint64_t size_of_minimum_page = RmtGetPageSize(kRmtPageSize4Kb); + + const uint64_t size_in_bytes = RmtGetAllocationSizeInBytes(virtual_allocation->size_in_4kb_page, kRmtPageSize4Kb); + + // stride through the resource in 4KB pages and figure out the mapping of each. + RmtGpuAddress current_virtual_address = virtual_allocation->base_address; + const RmtGpuAddress end_virtual_address = virtual_allocation->base_address + size_in_bytes; + + // add all the resource into unmapped category initially. + out_bytes_per_backing_storage_type[kRmtHeapTypeInvisible] = 0; + out_bytes_per_backing_storage_type[kRmtHeapTypeLocal] = 0; + out_bytes_per_backing_storage_type[kRmtHeapTypeSystem] = 0; + out_bytes_per_backing_storage_type[kRmtResourceBackingStorageUnmapped] = size_in_bytes; + *out_histogram_total = size_in_bytes; + + while (current_virtual_address < end_virtual_address) + { + // Handle edge case where last page isn't 4KB in size. + const uint64_t size = RMT_MINIMUM(end_virtual_address - current_virtual_address, size_of_minimum_page); + + // get the physical address + RmtGpuAddress physical_address = 0; + const RmtErrorCode error_code = RmtPageTableGetPhysicalAddressForVirtualAddress(&snapshot->page_table, current_virtual_address, &physical_address); + + if (error_code == RMT_OK) + { + // remove bytes from unmapped count. + if (size <= out_bytes_per_backing_storage_type[kRmtResourceBackingStorageUnmapped]) + { + out_bytes_per_backing_storage_type[kRmtResourceBackingStorageUnmapped] -= size; + } + + if (physical_address == 0) + { + out_bytes_per_backing_storage_type[kRmtHeapTypeSystem] += size; + } + else + { + const RmtHeapType segment_type = RmtDataSnapshotGetSegmentForAddress(snapshot, physical_address); + if (segment_type != kRmtHeapTypeUnknown) + { + out_bytes_per_backing_storage_type[segment_type] += size; + } + } + } + + current_virtual_address += size; + } + + return RMT_OK; +} diff --git a/source/backend/rmt_virtual_allocation_list.h b/source/backend/rmt_virtual_allocation_list.h new file mode 100644 index 0000000..586bc7a --- /dev/null +++ b/source/backend/rmt_virtual_allocation_list.h @@ -0,0 +1,351 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Structures and functions for managing a virtual allocation list. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_VIRTUAL_ALLOCATION_LIST_H_ +#define RMV_BACKEND_RMT_VIRTUAL_ALLOCATION_LIST_H_ + +#include +#include +#include "rmt_configuration.h" +#include "rmt_pool.h" +#include "rmt_format.h" + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtResource RmtResource; +typedef struct RmtDataSnapshot RmtDataSnapshot; + +/// A structure encapsulating a region of memory that is unbound. +typedef struct RmtMemoryRegion +{ + uint64_t offset; ///< The offset in bytes from the start of the parent RmtVirtualAllocation. + uint64_t size; ///< The size (in bytes) of the unbound memory region. +} RmtMemoryRegion; + +/// An enumeration of flags for allocation details. +typedef enum RmtAllocationDetailFlagBits +{ + kRmtAllocationDetailIsCpuMapped = (1 << 0), ///< The allocation is currently mapped for CPU access. + kRmtAllocationDetailHasBeenCpuMapped = (1 << 1), ///< The allocation has been mapped for CPU access. + kRmtAllocationDetailIsMadeResident = (1 << 2), ///< The allocation is currently requested to be made resident. + kRmtAllocationDetailHasBeenMadeResident = (1 << 3), ///< The allocation has been requested to be made resident. + kRmtAllocationDetailHasBeenEvicted = (1 << 4), ///< The allocation has been requested to be evicted. + kRmtAllocationDetailIsDead = (1 << 5), ///< The allocation has been freed later on and is waiting for deferred compaction. +} RmtAllocationDetailFlagBits; + +/// A structure encapsulating extra details about an allocation. +typedef struct RmtVirtualAllocation +{ + uint64_t base_address; ///< The base address of the allocation. + int32_t size_in_4kb_page; ///< The size of the allocation. + int32_t guid; ///< A GUID for the this allocation. + uint32_t flags; ///< A set of flags for the alllcation. + uint64_t timestamp; ///< The timestamp when the allocation was made. + uint64_t last_residency_update; ///< The timestamp when the last residency update was made. + uint64_t last_cpu_map; ///< THe timestamp when the last CPU map operation occurred. + uint64_t last_cpu_un_map; ///< The timestamp when the last CPU unmap operation occurred. + int32_t add_count; ///< The number of times a residency update add was requested for this allocation. + int32_t remove_count; ///< The number of times a residency update remove was requeted for this allocation. + int32_t map_count; ///< The current number of times the address is CPU mapped. + int32_t resource_count; ///< The number of resources bound to this allocation. + int32_t non_heap_resource_count; ///< The number of resources bound to this allocation which are not heaps. + RmtHeapType heap_preferences[4]; ///< The heap preferences in order. + RmtOwnerType owner; ///< The owner of the allocation. + uint32_t commit_type; ///< A bit field of all commit types used by resources inside this allocation. + RmtResource** resources; ///< The address of an array of points to RmtResource structures. + int32_t next_resource_index; ///< The index of the new resource. + RmtMemoryRegion* + unbound_memory_regions; ///< An array of RmtUnboundMemoryRegion structures representing the unbound memory inside this virtual allocation. + int32_t unbound_memory_region_count; ///< The number of RmtUnboundMemoryRegion structures inside unboundMemoryRegions. +} RmtVirtualAllocation; + +/// Get the size (in bytes) of a virtual allocation. +/// +/// @param [in] virtual_allocation A pointer to a RmtVirtualAllocation structure. +/// +/// @returns +/// The size (in bytes) of the virtualAllocation. +uint64_t RmtVirtualAllocationGetSizeInBytes(const RmtVirtualAllocation* virtual_allocation); + +/// Get the size (in bytes) of the largest resource bound to a virtual allocation. +/// +/// @param [in] virtual_allocation A pointer to a RmtVirtualAllocation structure. +/// +/// @returns +/// The size (in bytes) of the largest resource bound to virtualAllocation. +uint64_t RmtVirtualAllocationGetLargestResourceSize(const RmtVirtualAllocation* virtual_allocation); + +/// Get the total amount of memory used for resourced within a virtual allocation. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] virtual_allocation A pointer to a RmtVirtualAllocation structure. +/// +/// @returns +/// The size (in bytes) of the space inside virtualAllocation bound to resources. +uint64_t RmtVirtualAllocationGetTotalResourceMemoryInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation); + +/// Get the amount of memory not used for any resources. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] virtual_allocation A pointer to a RmtVirtualAllocation structure. +/// +/// @returns +/// The size (in bytes) of the free space inside virtualAllocation. +uint64_t RmtVirtualAllocationGetTotalUnboundSpaceInAllocation(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation); + +/// Get the average resource size inside a virtual allocation. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] virtual_allocation A pointer to a RmtVirtualAllocation structure. +/// +/// @returns +/// The average size (in bytes) of a resource inside virtualAllocation. +uint64_t RmtVirtualAllocationGetAverageResourceSizeInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation); + +/// Get the standard deviation for the resources in side a virtual allocation. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] virtual_allocation A pointer to a RmtVirtualAllocation structure. +/// +/// @returns +/// The standard deviation (in bytes) for the resources inside virtualAllocation. +uint64_t RmtVirtualAllocationGetResourceStandardDeviationInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation); + +/// Get the fragmentation quotient for a virtual allocation. +/// +/// A fragmentation quotient is a score in the range [0..1] which tells you how fragmented a +/// virtual allocation is. +/// +/// @param [in] virtual_allocation A pointer to a RmtVirtualAllocation structure. +/// +/// @returns +/// The fragmentation quotient for virtualAllocation. +float RmtVirtualAllocationGetFragmentationQuotient(const RmtVirtualAllocation* virtual_allocation); + +/// Get a histogram of bytes backing a virtual allocation. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] virtual_allocation A pointer to a RmtVirtualAllocation structure. +/// @param [out] out_bytes_per_backing_storage_type A pointer an array of uint64_t that will contain histogram values (in bytes). +/// @param [out] out_histogram_total A pointer to a uint64_t value where the size will be written. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because snapshot or outAllocation was NULL. +RmtErrorCode RmtVirtualAllocationGetBackingStorageHistogram(const RmtDataSnapshot* snapshot, + const RmtVirtualAllocation* virtual_allocation, + uint64_t* out_bytes_per_backing_storage_type, + uint64_t* out_histogram_total); + +/// A structure encapsulating critical allocation identifier information. +typedef struct RmtVirtualAllocationInterval +{ + RmtGpuAddress base_address; ///< The base address of the allocation. + int32_t size_in_4kb_pages; ///< The size of the allocation in 4KiB page + int32_t dead; ///< Set to true if its dead. + RmtVirtualAllocation* allocation; ///< A pointer to a RmtVirtualAllocation structure containing the resource payload. + RmtVirtualAllocationInterval* left; ///< A pointer to a RmtVirtualAllocationNode structure that is the left child of this node. + RmtVirtualAllocationInterval* right; ///< A pointer to a RmtVirtualAllocationNode structure that is the right child of this node. +} RmtVirtualAllocationInterval; + +/// A structure encapsulating a list of allocations. +typedef struct RmtVirtualAllocationList +{ + // data structures for lookups. + RmtVirtualAllocationInterval* root; ///< The root node of the acceleration structure. + RmtVirtualAllocationInterval* allocation_intervals; ///< A buffer of allocation intervals. + RmtPool allocation_interval_pool; ///< A pool allocator for the memory buffer pointed to by allocationIntervals. + + // storage for allocations. + RmtVirtualAllocation* allocation_details; ///< A buffer of extra allocation details. + int32_t allocation_count; ///< The number of live allocations in the list. + int32_t next_allocation_guid; ///< The next allocation GUID to assign. + int32_t maximum_concurrent_allocations; ///< The maximum number of concurrent allocations. + int32_t total_allocations; ///< The total number of allocations. + uint64_t total_allocated_bytes; ///< The total number of bytes allocated. + uint64_t allocations_per_preferred_heap[kRmtHeapTypeCount]; ///< The number of bytes for each preferred heap type. + RmtResource** resource_connectivity; ///< An array of pointers to resources, sorted by the resource's base address. + int32_t resource_connectivity_count; ///< The number of resoure pointers in the buffer pointed to by resourceConnectivity. + RmtMemoryRegion* + unbound_memory_regions; ///< An array of RmtUnboundMemoryRegion structures representing all unbound memory regions for all allocations. + int32_t unbound_memory_region_count; ///< The number of RmtUnboundMemoryRegion structures inside unboundMemoryRegions. + +} RmtVirtualAllocationList; + +/// Calculate the size of the workling buffer required for a specific number of concurrent allocations. +/// +/// @param [in] total_allocations The maximum number of concurrent allocations that can be in flight at any one time. +/// @param [in] max_concurrent_resources The maximum number of concurrent resources that can be in flight at any one time. +/// +/// @returns +/// The size of the virtual allocation list buffer that is required to initialize a RmtVirtualAllocationList structure using rmtVirtualAllocationListInitialize. +size_t RmtVirtualAllocationListGetBufferSize(int32_t total_allocations, int32_t max_concurrent_resources); + +/// Initialize the allocation list. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] buffer A pointer to a buffer containing the raw resource data. +/// @param [in] buffer_size The buffer size, in bytes. +/// @param [in] maximum_concurrent_allocations The maximum number of allocations that can be in flight at once. +/// @param [in] maximum_concurrent_resources The maximum number of resources that can be in flight at once. +/// @param [in] total_resources The maximum number of resources that can be in flight at once. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because virtual_allocation_list or buffer being set to NULL. +/// @retval +/// RMT_ERROR_INVALID_SIZE The operation failed because buffer_size is invalid. +RmtErrorCode RmtVirtualAllocationListInitialize(RmtVirtualAllocationList* virtual_allocation_list, + void* buffer, + size_t buffer_size, + int32_t maximum_concurrent_allocations, + int32_t maximum_concurrent_resources, + int32_t total_allocations); + +/// Add an allocation to the list. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] timestamp The timestamp for when the allocation happened. +/// @param [in] address The address of the allocation. +/// @param [in] size_in_4kb_pages The size of the allocation. +/// @param [in] preferences An array of preferred heaps for the allocation. +/// @param [in] owner The owner of the allocation. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because virtual_allocation_list being set to NULL. +/// @retval +/// RMT_ERROR_INVALID_SIZE The operation failed because size_in_4kb_pages is invalid. +RmtErrorCode RmtVirtualAllocationListAddAllocation(RmtVirtualAllocationList* virtual_allocation_list, + uint64_t timestamp, + RmtGpuAddress address, + int32_t size_in_4kb_pages, + const RmtHeapType preferences[4], + RmtOwnerType owner); + +/// Remove an allocation from the list. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] address The address of the allocation. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because virtual_allocation_list being set to NULL. +/// @retval +/// RMT_ERROR_NO_ALLOCATION_FOUND The operation failed because the allocation at address wasn't found. +RmtErrorCode RmtVirtualAllocationListRemoveAllocation(RmtVirtualAllocationList* virtual_allocation_list, RmtGpuAddress address); + +/// Add a residency update to a specific address. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] timestamp The timestamp for when the allocation happened. +/// @param [in] address The address of the allocation. +/// @param [in] update_type The address of the allocation. +/// @param [in] queue The address of the allocation. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because virtual_allocation_list being set to NULL. +/// @retval +/// RMT_ERROR_NO_ALLOCATION_FOUND The operation failed because the allocation at address wasn't found. +RmtErrorCode RmtVirtualAllocationListAddResourceReference(RmtVirtualAllocationList* virtual_allocation_list, + uint64_t timestamp, + RmtGpuAddress address, + RmtResidencyUpdateType update_type, + RmtQueue queue); + +/// Add a CPU map to a specific address. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] timestamp The timestamp for when the allocation happened. +/// @param [in] address The address of the allocation. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because virtual_allocation_list being set to NULL. +/// @retval +/// RMT_ERROR_NO_ALLOCATION_FOUND The operation failed because the allocation at address wasn't found. +RmtErrorCode RmtVirtualAllocationListAddCpuMap(RmtVirtualAllocationList* virtual_allocation_list, uint64_t timestamp, RmtGpuAddress address); + +/// Add a CPU unmap to a specific address. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] timestamp The timestamp for when the allocation happened. +/// @param [in] address The address of the allocation. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because virtual_allocation_list being set to NULL. +/// @retval +/// RMT_ERROR_NO_ALLOCATION_FOUND The operation failed because the allocation at address wasn't found. +RmtErrorCode RmtVirtualAllocationListAddCpuUnmap(RmtVirtualAllocationList* virtual_allocation_list, uint64_t timestamp, RmtGpuAddress address); + +/// Perform compaction on the virtual allocation list. This will remove any allocations +/// that are marked as dead and fix up anay resources that point at them. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] fixup_resources Set to true if you want the compaction to attempt to change the bound pointers on resources. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because virtual_allocation_list was NULL. +RmtErrorCode RmtVirtualAllocationListCompact(RmtVirtualAllocationList* virtual_allocation_list, bool fixup_resources); + +/// Find base address of allocation from address. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// @param [in] address The address of the allocation. +/// @param [out] out_allocation A pointer to a pointer to a RmtVirtualAllocation structure to receive the allocation. + +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because virtual_allocation_list or out_allocation was NULL. +RmtErrorCode RmtVirtualAllocationListGetAllocationForAddress(const RmtVirtualAllocationList* virtual_allocation_list, + RmtGpuAddress address, + const RmtVirtualAllocation** out_allocation); + +/// Get the total size (in bytes) of the memory in a virtual allocation list. +/// +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// +/// @returns +/// The size (in bytes) of all virtual allocations contained in virtual_allocation_list. +uint64_t RmtVirtualAllocationListGetTotalSizeInBytes(const RmtVirtualAllocationList* virtual_allocation_list); + +/// Get the size (in bytes) of the memory in a virtual allocation list that is bound to resources. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// +/// @returns +/// The size (in bytes) of the memory in a virtual_allocation_list that is bound to resources. +uint64_t RmtVirtualAllocationListGetBoundTotalSizeInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocationList* virtual_allocation_list); + +/// Get the size (in bytes) of the memory in a virtual allocation list that is not bound to resources. +/// +/// @param [in] snapshot A pointer to a RmtDataSnapshot structure. +/// @param [in] virtual_allocation_list A pointer to a RmtVirtualAllocationList structure. +/// +/// @returns +/// The size (in bytes) of the memory in a virtual_allocation_list that is not bound to resources. +uint64_t RmtVirtualAllocationListGetUnboundTotalSizeInBytes(const RmtDataSnapshot* snapshot, const RmtVirtualAllocationList* virtual_allocation_list); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_VIRTUAL_ALLOCATION_LIST_H_ diff --git a/source/backend/rmt_warnings.cpp b/source/backend/rmt_warnings.cpp new file mode 100644 index 0000000..e69de29 diff --git a/source/backend/rmt_warnings.h b/source/backend/rmt_warnings.h new file mode 100644 index 0000000..dd98fad --- /dev/null +++ b/source/backend/rmt_warnings.h @@ -0,0 +1,34 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Definition of structures and functions for RMT warnings. +//============================================================================= + +#ifndef RMV_BACKEND_RMT_WARNINGS_H_ +#define RMV_BACKEND_RMT_WARNINGS_H_ + +#include +#include + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// An enumeration of all warnings. +typedef enum RmtWarningType +{ + kRmtWarningTypeVirtualAllocationHighDedicatedResourceCount = 0, + kRmtWarningTypeVirtualAllocationHighFragmentationAllocation = 1, + kRmtWarningTypeHeapOversubscribed = 2, + kRmtWarningTypeResourceCriticalInNonlocal = 3, + kRmtWarningTypeResourceInNonPreferredHeap = 4, + kRmtWarningTypeResourceOrphaned = 5, + + // add above this + kRmtWarningTypeCount +} RmtWarningType; + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_BACKEND_RMT_WARNINGS_H_ diff --git a/source/frontend/CMakeLists.txt b/source/frontend/CMakeLists.txt new file mode 100644 index 0000000..40d3bbc --- /dev/null +++ b/source/frontend/CMakeLists.txt @@ -0,0 +1,454 @@ +cmake_minimum_required(VERSION 3.11) + +project(RadeonMemoryVisualizer) + +# switch on the autogenerators needed for Qt. Includes the MetaObject Compiler (MOC), +# the UI compiler (UIC) and the resource compiler (RCC) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +# Add UpdateCheckAPI +add_subdirectory (${PROJECT_SOURCE_DIR}/../../external/update_check_api ${CMAKE_CURRENT_BINARY_DIR}/update_check_api) +include_directories(${UPDATECHECKAPI_INC_DIRS}) + +IF (WIN32) + # get the location of the Visual Studio redist libraries - but don't create an install rule to install them + # the libs will be copied manually as a post build step + set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) + include(InstallRequiredSystemLibraries) +ENDIF(WIN32) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +include_directories(AFTER ../backend ../parser ../frontend) +IF(UNIX) + include_directories(AFTER ../backend/Linux) +ENDIF(UNIX) + +find_package(Qt5 COMPONENTS Core Gui Widgets Svg REQUIRED) + +IF(UNIX) + find_package(Threads) +ENDIF(UNIX) + +# List of all source files. It may be possible to have the build process call cmake to update the makefiles +# only when this file has changed (ie source files have been added or removed) + +set( INTERNAL_SOURCES ) + +set( SOURCES + "main.cpp" + "views/colorizer.cpp" + "views/colorizer.h" + "views/colorizer_base.cpp" + "views/colorizer_base.h" + "views/timeline_colorizer.cpp" + "views/timeline_colorizer.h" + "views/debug_window.cpp" + "views/debug_window.h" + "views/debug_window.ui" + "views/main_window.cpp" + "views/main_window.h" + "views/main_window.ui" + "views/pane_manager.cpp" + "views/pane_manager.h" + "views/base_pane.cpp" + "views/base_pane.h" + "views/keyboard_zoom_shortcuts.cpp" + "views/keyboard_zoom_shortcuts.h" + "views/navigation_manager.cpp" + "views/navigation_manager.h" + "views/start/welcome_pane.cpp" + "views/start/welcome_pane.h" + "views/start/welcome_pane.ui" + "views/start/recent_traces_pane.cpp" + "views/start/recent_traces_pane.h" + "views/start/recent_traces_pane.ui" + "views/start/about_pane.cpp" + "views/start/about_pane.h" + "views/start/about_pane.ui" + "views/custom_widgets/rmv_allocation_bar.cpp" + "views/custom_widgets/rmv_allocation_bar.h" + "views/custom_widgets/rmv_camera_snapshot_widget.cpp" + "views/custom_widgets/rmv_camera_snapshot_widget.h" + "views/custom_widgets/rmv_carousel.cpp" + "views/custom_widgets/rmv_carousel.h" + "views/custom_widgets/rmv_carousel_allocation_sizes.cpp" + "views/custom_widgets/rmv_carousel_allocation_sizes.h" + "views/custom_widgets/rmv_carousel_item.cpp" + "views/custom_widgets/rmv_carousel_item.h" + "views/custom_widgets/rmv_carousel_memory_footprint.cpp" + "views/custom_widgets/rmv_carousel_memory_footprint.h" + "views/custom_widgets/rmv_carousel_memory_types.cpp" + "views/custom_widgets/rmv_carousel_memory_types.h" + "views/custom_widgets/rmv_carousel_nav_button.cpp" + "views/custom_widgets/rmv_carousel_nav_button.h" + "views/custom_widgets/rmv_carousel_resource_types.cpp" + "views/custom_widgets/rmv_carousel_resource_types.h" + "views/custom_widgets/rmv_clickable_table_view.cpp" + "views/custom_widgets/rmv_clickable_table_view.h" + "views/custom_widgets/rmv_colored_checkbox.cpp" + "views/custom_widgets/rmv_colored_checkbox.h" + "views/custom_widgets/rmv_color_picker_widget.cpp" + "views/custom_widgets/rmv_color_picker_widget.h" + "views/custom_widgets/rmv_delta_display.cpp" + "views/custom_widgets/rmv_delta_display.h" + "views/custom_widgets/rmv_delta_display_widget.cpp" + "views/custom_widgets/rmv_delta_display_widget.h" + "views/custom_widgets/rmv_heap_overview_memory_bar.cpp" + "views/custom_widgets/rmv_heap_overview_memory_bar.h" + "views/custom_widgets/rmv_resource_details.cpp" + "views/custom_widgets/rmv_resource_details.h" + "views/custom_widgets/rmv_resource_timeline.cpp" + "views/custom_widgets/rmv_resource_timeline.h" + "views/custom_widgets/rmv_scaled_donut_widget.cpp" + "views/custom_widgets/rmv_scaled_donut_widget.h" + "views/custom_widgets/rmv_snapshot_marker.cpp" + "views/custom_widgets/rmv_snapshot_marker.h" + "views/custom_widgets/rmv_snapshot_table_view.cpp" + "views/custom_widgets/rmv_snapshot_table_view.h" + "views/custom_widgets/rmv_snapshot_timeline.cpp" + "views/custom_widgets/rmv_snapshot_timeline.h" + "views/custom_widgets/rmv_timeline_graph.cpp" + "views/custom_widgets/rmv_timeline_graph.h" + "views/custom_widgets/rmv_timeline_tooltip.cpp" + "views/custom_widgets/rmv_timeline_tooltip.h" + "views/custom_widgets/rmv_tree_map_blocks.cpp" + "views/custom_widgets/rmv_tree_map_blocks.h" + "views/custom_widgets/rmv_tree_map_view.cpp" + "views/custom_widgets/rmv_tree_map_view.h" + "views/custom_widgets/themes_and_colors_item_button.cpp" + "views/custom_widgets/themes_and_colors_item_button.h" + "views/compare/compare_start_pane.cpp" + "views/compare/compare_start_pane.h" + "views/compare/compare_start_pane.ui" + "views/compare/memory_leak_finder_pane.h" + "views/compare/memory_leak_finder_pane.cpp" + "views/compare/memory_leak_finder_pane.ui" + "views/compare/snapshot_delta_pane.h" + "views/compare/snapshot_delta_pane.cpp" + "views/compare/snapshot_delta_pane.ui" + "views/delegates/rmv_compare_id_delegate.h" + "views/delegates/rmv_compare_id_delegate.cpp" + "views/delegates/rmv_resource_event_delegate.h" + "views/delegates/rmv_resource_event_delegate.cpp" + "views/settings/settings_pane.cpp" + "views/settings/settings_pane.h" + "views/settings/settings_pane.ui" + "views/settings/keyboard_shortcuts_pane.cpp" + "views/settings/keyboard_shortcuts_pane.h" + "views/settings/keyboard_shortcuts_pane.ui" + "views/settings/themes_and_colors_pane.cpp" + "views/settings/themes_and_colors_pane.h" + "views/settings/themes_and_colors_pane.ui" + "views/snapshot/allocation_explorer_pane.h" + "views/snapshot/allocation_explorer_pane.cpp" + "views/snapshot/allocation_explorer_pane.ui" + "views/snapshot/allocation_overview_pane.h" + "views/snapshot/allocation_overview_pane.cpp" + "views/snapshot/allocation_overview_pane.ui" + "views/snapshot/heap_overview_heap_layout.h" + "views/snapshot/heap_overview_heap_layout.cpp" + "views/snapshot/heap_overview_heap_layout.ui" + "views/snapshot/heap_overview_pane.h" + "views/snapshot/heap_overview_pane.cpp" + "views/snapshot/heap_overview_pane.ui" + "views/snapshot/resource_details_pane.h" + "views/snapshot/resource_details_pane.cpp" + "views/snapshot/resource_details_pane.ui" + "views/snapshot/resource_list_pane.h" + "views/snapshot/resource_list_pane.cpp" + "views/snapshot/resource_list_pane.ui" + "views/snapshot/resource_event_icons.h" + "views/snapshot/resource_event_icons.cpp" + "views/snapshot/resource_overview_pane.h" + "views/snapshot/resource_overview_pane.cpp" + "views/snapshot/resource_overview_pane.ui" + "views/snapshot/snapshot_start_pane.cpp" + "views/snapshot/snapshot_start_pane.h" + "views/snapshot/snapshot_start_pane.ui" + "views/timeline/device_configuration_pane.h" + "views/timeline/device_configuration_pane.cpp" + "views/timeline/device_configuration_pane.ui" + "views/timeline/keyboard_zoom_shortcuts_timeline.h" + "views/timeline/keyboard_zoom_shortcuts_timeline.cpp" + "views/timeline/timeline_pane.h" + "views/timeline/timeline_pane.cpp" + "views/timeline/timeline_pane.ui" + "util/constants.h" + "util/definitions.h" + "util/log_file_writer.cpp" + "util/log_file_writer.h" + "util/rmv_util.cpp" + "util/rmv_util.h" + "util/string_util.cpp" + "util/string_util.h" + "util/thread_controller.cpp" + "util/thread_controller.h" + "util/time_util.cpp" + "util/time_util.h" + "util/version.cpp" + "util/version.h" + "util/widget_util.cpp" + "util/widget_util.h" + "models/aliased_resource_model.cpp" + "models/aliased_resource_model.h" + "models/allocation_bar_model.cpp" + "models/allocation_bar_model.h" + "models/allocation_item_model.cpp" + "models/allocation_item_model.h" + "models/allocation_multi_bar_model.cpp" + "models/allocation_multi_bar_model.h" + "models/carousel_model.cpp" + "models/carousel_model.h" + "models/combo_box_model.cpp" + "models/combo_box_model.h" + "models/heap_combo_box_model.cpp" + "models/heap_combo_box_model.h" + "models/message_manager.cpp" + "models/message_manager.h" + "models/resource_item_model.cpp" + "models/resource_item_model.h" + "models/resource_sorter.cpp" + "models/resource_sorter.h" + "models/resource_usage_combo_box_model.cpp" + "models/resource_usage_combo_box_model.h" + "models/snapshot_manager.cpp" + "models/snapshot_manager.h" + "models/trace_manager.cpp" + "models/trace_manager.h" + "models/timeline/device_configuration_model.h" + "models/timeline/device_configuration_model.cpp" + "models/timeline/snapshot_item_model.h" + "models/timeline/snapshot_item_model.cpp" + "models/timeline/timeline_model.cpp" + "models/timeline/timeline_model.h" + "models/snapshot/allocation_explorer_model.h" + "models/snapshot/allocation_explorer_model.cpp" + "models/snapshot/allocation_overview_model.h" + "models/snapshot/allocation_overview_model.cpp" + "models/snapshot/resource_details_model.h" + "models/snapshot/resource_details_model.cpp" + "models/snapshot/resource_list_model.h" + "models/snapshot/resource_list_model.cpp" + "models/snapshot/resource_properties_model.h" + "models/snapshot/resource_properties_model.cpp" + "models/snapshot/resource_overview_model.h" + "models/snapshot/resource_overview_model.cpp" + "models/snapshot/resource_timeline_item_model.cpp" + "models/snapshot/resource_timeline_item_model.h" + "models/snapshot/heap_overview_heap_model.h" + "models/snapshot/heap_overview_heap_model.cpp" + "models/snapshot/memory_map_model.h" + "models/snapshot/memory_map_model.cpp" + "models/compare/allocation_delta_model.h" + "models/compare/allocation_delta_model.cpp" + "models/compare/memory_leak_finder_model.h" + "models/compare/memory_leak_finder_model.cpp" + "models/compare/snapshot_delta_model.h" + "models/compare/snapshot_delta_model.cpp" + "models/proxy_models/allocation_proxy_model.h" + "models/proxy_models/allocation_proxy_model.cpp" + "models/proxy_models/table_proxy_model.h" + "models/proxy_models/table_proxy_model.cpp" + "models/proxy_models/resource_details_proxy_model.h" + "models/proxy_models/resource_details_proxy_model.cpp" + "models/proxy_models/resource_proxy_model.h" + "models/proxy_models/resource_proxy_model.cpp" + "models/proxy_models/snapshot_timeline_proxy_model.h" + "models/proxy_models/snapshot_timeline_proxy_model.cpp" + "models/proxy_models/memory_leak_finder_proxy_model.h" + "models/proxy_models/memory_leak_finder_proxy_model.cpp" + "settings/rmv_geometry_settings.cpp" + "settings/rmv_geometry_settings.h" + "settings/rmv_settings.cpp" + "settings/rmv_settings.h" + "settings/settings_reader.cpp" + "settings/settings_reader.h" + "settings/settings_writer.cpp" + "settings/settings_writer.h" + "resources.qrc" + "stylesheet.qss" + ${UPDATECHECKAPI_SRC} + ${UPDATECHECKAPI_INC} + ${UPDATECHECKAPI_QT_SRC} + ${UPDATECHECKAPI_QT_INC} + ${UPDATECHECKAPI_QT_UI} +) + +set( WINDOWS_SOURCES + "windows/resource.h" + "windows/rmv.rc" + "windows/rmv_icon.ico" +) + +# Filter out the UI files and get the list of generated files +set(UI_FILES ${SOURCES}) +list(FILTER UI_FILES INCLUDE REGEX "\.ui$") +qt5_wrap_ui (GENERATED_UI ${UI_FILES}) +set(SOURCES ${SOURCES} ${GENERATED_UI}) + +# searching for library file + +# Specify output executable name. For windows, indicate this is a Windows application +# and not a console application +IF(WIN32) + add_executable(${PROJECT_NAME} WIN32 ${SOURCES} ${WINDOWS_SOURCES}) +ELSEIF(APPLE) + IF(NO_APP_BUNDLE) + add_executable(${PROJECT_NAME} ${SOURCES}) + ELSE() + set(MACOSX_BUNDLE_ICON_FILE rmv.icns) + set(MACOSX_ICON MacOSX/rmv.icns) + set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${MACOSX_ICON} ${SOURCES}) + set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_RPATH TRUE MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/MacOSX/MacOSXBundleInfo.plist.in) + ENDIF() +ELSEIF(UNIX) + add_executable(${PROJECT_NAME} ${SOURCES}) + # Setting RPATH here results in local build directory also being appended to RPATH + # RPATH set instead by calling chrpath command line command for cleaner solution. + # Correct cmake solution will likely require use of a separate make install + # for packaging + # Leaving these Commands here for future reference + # set_target_properties(${PROJECT_NAME} PROPERTIES + # BUILD_WITH_INSTALL_RPATH TRUE + # INSTALL_RPATH_USE_LINK_PATH TRUE + # INSTALL_RPATH "\$ORIGIN/qt/lib" + #) +ENDIF(WIN32) + + +IF (WIN32 OR APPLE) +SOURCE_GROUP_BY_FOLDER(${PROJECT_NAME}) +ENDIF() + +# CMAKE__POSTFIX isn't applied automatically to executable targets so apply manually +IF(CMAKE_DEBUG_POSTFIX) + set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) +ENDIF(CMAKE_DEBUG_POSTFIX) +IF(CMAKE_RELEASE_POSTFIX) + set_target_properties(${PROJECT_NAME} PROPERTIES RELEASE_POSTFIX ${CMAKE_RELEASE_POSTFIX}) +ENDIF(CMAKE_RELEASE_POSTFIX) + +# executable file library dependency list +IF(WIN32) + target_link_libraries(${PROJECT_NAME} RmvParser RmvBackend Qt5::Widgets QtCustomWidgets QtUtils wsock32 winmm ${UPDATECHECKAPI_LIBS}) +ELSEIF(UNIX) + target_link_libraries(${PROJECT_NAME} RmvBackend RmvParser Qt5::Widgets QtCustomWidgets QtUtils Threads::Threads ${UPDATECHECKAPI_LIBS}) +ENDIF() + +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "copying rtda to output directory" + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${RTDA_PATH} $ +) + +IF(APPLE) + IF (NOT NO_APP_BUNDLE) + # Run macdeployqt if it can be found + find_program(MACDEPLOYQT_EXECUTABLE NAMES macdeployqt) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + # macdeployqt expects to be passed the "${PROJECT_NAME}.app" directory rather than the final target + COMMAND ${MACDEPLOYQT_EXECUTABLE} $/../../ + ) + ENDIF() +ENDIF(APPLE) + +IF(WIN32) + ## Not using windeployqt currently as it copies more files than necessary. Leaving the command + ## here for future reference + # + # Run windeployqt if it can be found + # find_program(WINDEPLOYQT_EXECUTABLE NAMES windeployqt) + # add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + # COMMAND ${WINDEPLOYQT_EXECUTABLE} $ --no-translations --no-angle --no-opengl-sw --no-system-d3d-compiler --no-compiler-runtime --no-plugins + # ) + + # Copy the QT files to the output directory + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + # TBD - can windows use the same subdirectory layout as Linux (i.e. files in qt subfolder references via qt.conf)" + COMMAND ${CMAKE_COMMAND} -E make_directory $/imageformats + COMMAND ${CMAKE_COMMAND} -E make_directory $/platforms + COMMAND ${CMAKE_COMMAND} -E make_directory $/iconengines + COMMAND ${CMAKE_COMMAND} -E make_directory $/styles + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $/imageformats + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $/iconengines + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $/platforms + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $/styles + ) + + # Copy the VisualStudio redist files + # the list of all redist files is contained in the variable CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS. We only want to copy + # a subset of these so we extract the directory from the first entry and then manually copy the files we want + foreach(VSLIB ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}) + get_filename_component(VSREDISTDIR ${VSLIB} DIRECTORY) + message("Visual Studio redistributable files directory = ${VSREDISTDIR}") + break() + endforeach() + + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "copying Visual Studio redist files from ${VSREDISTDIR} to $" + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${VSREDISTDIR}/msvcp140.dll $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${VSREDISTDIR}/concrt140.dll $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${VSREDISTDIR}/vcruntime140.dll $ + ) + + # Add the Debug-Only custom commands. + # This functionality requires CMake 3.8 or higher. + add_custom_command( TARGET ${PROJECT_NAME} POST_BUILD + COMMAND "${_cmd_copy_updates_to_vs_wd}" + COMMAND "${_cmd_copy_updates_to_output_dir}" + COMMAND_EXPAND_LISTS + ) + +ENDIF(WIN32) + +IF(UNIX AND NOT APPLE) + # define some variables for the source and destination of the QT lib copy + set(QT_LIB_SRC "$") + set(QT_LIB_DST "$/qt/lib") + set(QT_PLATFORM_SRC "$") + set(QT_PLATFORM_DST "$/qt/plugins/platforms") + set(QT_IMAGEFORMATS_SRC "$") + set(QT_IMAGEFORMATS_DST "$/qt/plugins/imageformats") + set(QT_ICONENGINES_SRC "$") + set(QT_ICONENGINES_DST "$/qt/plugins/iconengines") + + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "copying QT libs from ${QT_LIB_SRC} to ${QT_LIB_DST}" + COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E make_directory $/qt/plugins/iconengines + COMMAND ${CMAKE_COMMAND} -E make_directory $/qt/plugins/imageformats + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libQt5Core.so.5 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libQt5Gui.so.5 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libQt5Svg.so.5 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libQt5Widgets.so.5 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libQt5XcbQpa.so.5 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libQt5DBus.so.5 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libicudata.so.56 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libicui18n.so.56 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_LIB_SRC}/libicuuc.so.56 ${QT_LIB_DST} + COMMAND ${CMAKE_COMMAND} -E echo "copying QT platform plugins from ${QT_PLATFORM_SRC} to ${QT_PLATFORM_DST}" + COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_PLATFORM_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_PLATFORM_SRC}/libqxcb.so ${QT_PLATFORM_DST} + COMMAND ${CMAKE_COMMAND} -E echo "copying QT imageformat plugins from ${QT_IMAGEFORMATS_SRC} to ${QT_IMAGEFORMATS_DST}" + COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_IMAGEFORMATS_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_IMAGEFORMATS_SRC}/libqsvg.so ${QT_IMAGEFORMATS_DST} + COMMAND ${CMAKE_COMMAND} -E echo "copying QT iconengine plugins from ${QT_ICONENGINES_SRC} to ${QT_ICONENGINES_DST}" + COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_ICONENGINES_DST} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QT_ICONENGINES_SRC}/libqsvgicon.so ${QT_ICONENGINES_DST} + COMMAND ${CMAKE_COMMAND} -E echo "copying qt.conf to $" + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/../../build/qt.conf $ + ) + # Call chrpath on system to override executable file RPATH. + find_program(CHRPATH_EXECUTABLE NAMES chrpath) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "trying chrpath" + COMMAND ${CHRPATH_EXECUTABLE} -r "\\$$ORIGIN/qt/lib" $ + ) + +ENDIF(UNIX AND NOT APPLE) diff --git a/source/frontend/MacOSX/MacOSXBundleInfo.plist.in b/source/frontend/MacOSX/MacOSXBundleInfo.plist.in new file mode 100644 index 0000000..34fd61d --- /dev/null +++ b/source/frontend/MacOSX/MacOSXBundleInfo.plist.in @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + com.AMD.RGP + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + RGP V1.0.XXXX + CFBundleName + RGP + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + 000000 + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright © 2017 AMD. All rights reserved + NSPrincipalClass + NsApplication + + diff --git a/source/frontend/MacOSX/rmv.icns b/source/frontend/MacOSX/rmv.icns new file mode 100644 index 0000000..d0167f9 Binary files /dev/null and b/source/frontend/MacOSX/rmv.icns differ diff --git a/source/frontend/main.cpp b/source/frontend/main.cpp new file mode 100644 index 0000000..887c59a --- /dev/null +++ b/source/frontend/main.cpp @@ -0,0 +1,123 @@ +//============================================================================= +/// Copyright (c) 2018 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Main entry point. +//============================================================================= + +#include +#include +#include +#include + +#include "rmt_print.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" + +#include "qt_common/utils/scaling_manager.h" + +#include "models/trace_manager.h" +#include "views/main_window.h" +#include "util/rmv_util.h" + +/// Handle printing from RMV backend. +/// \param pMsg Incoming message +void PrintCallback(const char* message) +{ + DebugWindow::DbgMsg(message); +} + +/// Detect RMV trace if any was specified as command line param +/// \return Empty string if no trace, and full path if valid RMV file +static QString GetTracePath() +{ + QString out = ""; + + if (QCoreApplication::arguments().count() > 1) + { + const QString potential_trace_path = QDir::toNativeSeparators(QCoreApplication::arguments().at(1)); + if (TraceManager::Get().TraceValidToLoad(potential_trace_path) == true) + { + out = potential_trace_path; + } + } + + return out; +} + +#if 0 +static RmtDataSet command_line_data_set; +static RmtDataSnapshot command_line_snapshot; +#endif + +/// Main entry point. +/// \param argc The number of arguments. +/// \param argv An array containing arguments. +int main(int argc, char* argv[]) +{ +#if 0 + // Test feature to dump RMV file to JSON. Need a bunch of error handling. + if (argc == 4) + { + RmtErrorCode error_code = rmtDataSetInitialize(argv[1], &command_line_data_set); + if (error_code != RMT_OK) + { + return 1; + } + + const uint64_t timestamp = strtoull(argv[2], NULL, 0); + RMT_UNUSED(timestamp); + error_code = rmtDataSetGenerateSnapshot(&command_line_data_set, command_line_data_set.maximumTimestamp, "snapshot 0", &command_line_snapshot); + if (error_code != RMT_OK) + { + return 2; + } + + error_code = rmtDataSnapshotDumpJsonToFile(&command_line_snapshot, argv[3]); + if (error_code != RMT_OK) + { + return 3; + } + + return 0; + } +#endif + + QApplication a(argc, argv); + + // Load application stylesheet + QFile style_sheet(rmv::resource::kStylesheet); + if (style_sheet.open(QFile::ReadOnly)) + { + a.setStyleSheet(style_sheet.readAll()); + } + + // set the default font size + QFont font; + font.setFamily(font.defaultFamily()); + font.setPointSize(8); + a.setFont(font); + + MainWindow::InitializeJobQueue(); + MainWindow* window = new (std::nothrow) MainWindow(); + int result = -1; + if (window != nullptr) + { + window->show(); + + TraceManager::Get().Initialize(window); + + // Scaling manager object registration + ScalingManager::Get().Initialize(window); + + if (!GetTracePath().isEmpty()) + { + window->LoadTrace(GetTracePath()); + } + + result = a.exec(); + delete window; + } + + return result; +} diff --git a/source/frontend/models/aliased_resource_model.cpp b/source/frontend/models/aliased_resource_model.cpp new file mode 100644 index 0000000..476a199 --- /dev/null +++ b/source/frontend/models/aliased_resource_model.cpp @@ -0,0 +1,107 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the aliased resource model. +//============================================================================= + +#include "models/aliased_resource_model.h" + +#include "rmt_resource_list.h" + +#include "util/rmv_util.h" + +namespace rmv +{ + AliasedResourceModel::AliasedResourceModel() + { + } + + AliasedResourceModel::~AliasedResourceModel() + { + } + + void AliasedResourceModel::Clear() + { + alias_data_.clear(); + } + + bool AliasedResourceModel::Generate(const RmtVirtualAllocation* allocation) + { + auto alias_iter = alias_data_.find(allocation); + if (alias_iter != alias_data_.end()) + { + return ((*alias_iter).second.num_rows > 1); + } + + int32_t resource_count = allocation->resource_count; + + // A list of the last resource added per row. + AliasData new_alias_info; + new_alias_info.resource_rows.resize(resource_count); + + std::vector last_resource_list; + last_resource_list.clear(); + last_resource_list.reserve(resource_count); + + for (int loop = 0; loop < resource_count; loop++) + { + const RmtResource* resource = allocation->resources[loop]; + + // Ignore heap resources. + if (resource->resource_type != kRmtResourceTypeHeap) + { + bool found = false; + int row_count = static_cast(last_resource_list.size()); + + // For all of the rows added so far, look at the last element and see if there's an + // overlap with that element and the one to be added. If there isn't, add it to this row. + // If not, try the next row. Continue until it can be added to an existing row or if not, + // add it to a new row. + // Assumes resources are in chronological order. + for (int i = 0; i < row_count; i++) + { + const RmtResource* last_resource = last_resource_list[i]; + if (resource->address >= (last_resource->address + last_resource->size_in_bytes)) + { + last_resource_list[i] = resource; + new_alias_info.resource_rows[loop] = i; + found = true; + break; + } + } + if (!found) + { + // Add a new row. + new_alias_info.resource_rows[loop] = row_count; + last_resource_list.push_back(resource); + } + } + } + + new_alias_info.num_rows = static_cast(last_resource_list.size()); + alias_data_.insert(std::make_pair(allocation, new_alias_info)); + + return (new_alias_info.num_rows > 1); + } + + int AliasedResourceModel::GetNumRows(const RmtVirtualAllocation* allocation) const + { + auto iter = alias_data_.find(allocation); + if (iter == alias_data_.end()) + { + return 1; + } + return (*iter).second.num_rows; + } + + int AliasedResourceModel::GetRowForResourceAtIndex(const RmtVirtualAllocation* allocation, int index) const + { + auto iter = alias_data_.find(allocation); + if (iter == alias_data_.end()) + { + return 0; + } + return (*iter).second.resource_rows[index]; + } +} // namespace rmv diff --git a/source/frontend/models/aliased_resource_model.h b/source/frontend/models/aliased_resource_model.h new file mode 100644 index 0000000..bef1e8f --- /dev/null +++ b/source/frontend/models/aliased_resource_model.h @@ -0,0 +1,70 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the aliased resource model. +//============================================================================= + +#ifndef RMV_MODELS_ALIASED_RESOURCE_MODEL_H_ +#define RMV_MODELS_ALIASED_RESOURCE_MODEL_H_ + +#include +#include +#include + +#include "rmt_resource_list.h" +#include "rmt_virtual_allocation_list.h" + +namespace rmv +{ + /// Class to generate and store the aliased resource data. This is generated when + /// a snapshot is created. It consists of a map lookup of allocation to aliased data. + /// If an allocation isn't in the map, then it isn't aliased. + class AliasedResourceModel + { + public: + /// Constructor. + AliasedResourceModel(); + + /// Destructor. + ~AliasedResourceModel(); + + /// Clear out the aliased data model. + void Clear(); + + /// Generate the aliased data. + /// Build a temporary 2D vector of data to determine if resources overlap. + /// From this data, have an array the same size as the number of resources and + /// for each resource, save its row for quick lookup. + /// \param allocation The allocation containing the resources. + /// \return true if this allocation contains aliased resources, false if not. + bool Generate(const RmtVirtualAllocation* allocation); + + /// Get the number of rows required for drawing the resources for the current allocation. + /// \param allocation The virtual allocation containing the resources. + /// \return The number of rows. + int GetNumRows(const RmtVirtualAllocation* allocation) const; + + /// Get the row that a resource is on. + /// \param allocation The virtual allocation containing the resources. + /// \param index The index of the resource in the rmt_resource_list structure. + /// \return The row. + int GetRowForResourceAtIndex(const RmtVirtualAllocation* allocation, int index) const; + + private: + /// Struct containing information for aliased resources. Consists of a vector indicating + /// which row a resource is to be drawn in (the index in the vector is the same as the index + /// in the resource list in the back end) and the total number of rows needed to show the + /// resources. + struct AliasData + { + std::vector resource_rows; ///< A lookup to get the row for a resource. + int num_rows = 0; ///< The number of rows required for the resources. + }; + + std::map alias_data_; ///< The alias data. + }; + +} // namespace rmv + +#endif // RMV_MODELS_ALIASED_RESOURCE_MODEL_H_ diff --git a/source/frontend/models/allocation_bar_model.cpp b/source/frontend/models/allocation_bar_model.cpp new file mode 100644 index 0000000..09910bf --- /dev/null +++ b/source/frontend/models/allocation_bar_model.cpp @@ -0,0 +1,317 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the allocation bar model base class. This model +/// holds any state information for derived models that use a graphical +/// representation of an allocation and is used for a single allocation bar, +/// as seen in the allocation explorer pane. These allocations are rendered +/// using RMVAllocationBar objects. +//============================================================================= + +#include "models/allocation_bar_model.h" + +#include "rmt_assert.h" +#include "rmt_print.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" + +#include "util/string_util.h" + +#include "models/snapshot/allocation_overview_model.h" +#include "models/trace_manager.h" + +namespace rmv +{ + AllocationBarModel::AllocationBarModel(uint32_t model_count, bool show_details) + : model_count_(model_count) + , show_details_(show_details) + , show_aliased_(false) + { + selection_state_ = new SelectionState[model_count]; + ClearSelectionState(); + } + + AllocationBarModel::~AllocationBarModel() + { + delete[] selection_state_; + } + + QString AllocationBarModel::GetTitleText(int32_t allocation_index, int32_t model_index) const + { + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + if (allocation == nullptr) + { + return QString(); + } + return GetTitleText(allocation); + } + + QString AllocationBarModel::GetTitleText(const RmtVirtualAllocation* allocation) const + { + return "Allocation: " + QString::number(allocation->base_address) + + " - Heap: " + QString(RmtGetHeapTypeNameFromHeapType(allocation->heap_preferences[0])); + } + + QString AllocationBarModel::GetDescriptionText(int32_t allocation_index, int32_t model_index) const + { + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + if (allocation == nullptr) + { + return QString(); + } + return GetDescriptionText(allocation); + } + + QString AllocationBarModel::GetDescriptionText(const RmtVirtualAllocation* allocation) const + { + return " (Size: " + rmv::string_util::LocalizedValueMemory(RmtVirtualAllocationGetSizeInBytes(allocation), false, false) + + " - Resources: " + QString::number(allocation->resource_count) + ")"; + } + + double AllocationBarModel::GetBytesPerPixel(int32_t allocation_index, int32_t model_index, int32_t width) const + { + RMT_ASSERT(width > 0); + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + RMT_ASSERT(allocation != nullptr); + if (allocation != nullptr) + { + uint64_t allocation_size = RmtVirtualAllocationGetSizeInBytes(allocation); + return static_cast(allocation_size) / static_cast(width); + } + + return 1.0; + } + + const RmtVirtualAllocation* AllocationBarModel::GetAllocation(int32_t scene_index, int32_t model_index) const + { + Q_UNUSED(scene_index); + return selection_state_[model_index].selected_allocation; + } + + bool AllocationBarModel::ShowDetails() const + { + return show_details_; + } + + int32_t AllocationBarModel::GetHoveredResourceForAllocation(int32_t allocation_index, int model_index) const + { + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + if (allocation == selection_state_[model_index].hovered_allocation && allocation != nullptr) + { + return selection_state_[model_index].hovered_resource; + } + return -1; + } + + int32_t AllocationBarModel::GetSelectedResourceForAllocation(int32_t allocation_index, int model_index) const + { + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + if (allocation == selection_state_[model_index].selected_allocation && allocation != nullptr) + { + return selection_state_[model_index].selected_resource; + } + return -1; + } + + void AllocationBarModel::SetHoveredResourceForAllocation(int32_t allocation_index, int32_t resource_index, int model_index) + { + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + if (allocation != nullptr) + { + selection_state_[model_index].hovered_allocation = allocation; + selection_state_[model_index].hovered_resource = resource_index; + } + } + + void AllocationBarModel::SetSelectedResourceForAllocation(int32_t allocation_index, int32_t resource_index, int model_index) + { + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + SetSelectedResourceForAllocation(allocation, resource_index, model_index); + } + + void AllocationBarModel::SetHoveredResourceForAllocation(int32_t allocation_index, + int32_t model_index, + int32_t width, + int32_t height, + const QPointF& mouse_pos) + { + qreal x_pos = mouse_pos.x(); + + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + if (allocation == nullptr) + { + return; + } + + const int resource_count = allocation->resource_count; + if (resource_count == 0) + { + return; + } + + SetHoveredResourceForAllocation(allocation_index, -1, model_index); + + // calculate which row the mouse is in. + qreal pixels_per_row = static_cast(height) / static_cast(GetNumRows(allocation)); + int row = mouse_pos.y() / pixels_per_row; + + // find which resource is highlighted, and set the hovered bit. + const double bytes_per_pixel = GetBytesPerPixel(allocation_index, model_index, width); + + for (int32_t current_resource_index = resource_count - 1; current_resource_index >= 0; --current_resource_index) + { + const RmtResource* current_resource = allocation->resources[current_resource_index]; + + if (current_resource->resource_type == kRmtResourceTypeHeap) + { + continue; + } + + if (GetRowForResourceAtIndex(allocation, current_resource_index) != row) + { + continue; + } + + const uint64_t offset_in_bytes = RmtResourceGetOffsetFromBoundAllocation(current_resource); + const uint64_t target_offset_start = offset_in_bytes / bytes_per_pixel; + const uint64_t target_offset_end = (offset_in_bytes + current_resource->size_in_bytes - 1) / bytes_per_pixel; + + if (target_offset_start <= x_pos && x_pos <= target_offset_end) + { + SetHoveredResourceForAllocation(allocation_index, current_resource_index, model_index); + break; + } + } + } + + bool AllocationBarModel::SetSelectedResourceForAllocation(const RmtVirtualAllocation* allocation, int32_t resource_index, int model_index) + { + if (allocation != nullptr) + { + selection_state_[model_index].selected_allocation = allocation; + selection_state_[model_index].selected_resource = resource_index; + + return TraceManager::Get().GetAliasModel().GetNumRows(allocation) > 1; + } + return false; + } + + void AllocationBarModel::SelectResource(int32_t allocation_index, int32_t model_index, int resource_index) + { + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + if (allocation == nullptr) + { + return; + } + + const int resource_count = allocation->resource_count; + if (resource_index >= 0 && resource_index < resource_count) + { + SetSelectedResourceForAllocation(allocation, resource_index, model_index); + } + } + + RmtResourceIdentifier AllocationBarModel::FindResourceIdentifier(int32_t allocation_index, int32_t model_index) const + { + const RmtVirtualAllocation* allocation = GetAllocation(allocation_index, model_index); + if (allocation != nullptr) + { + const int resource_count = allocation->resource_count; + + for (int i = 0; i < resource_count; i++) + { + if (GetHoveredResourceForAllocation(allocation_index, model_index) == i) + { + RmtResourceIdentifier resource_identifier = allocation->resources[i]->identifier; + return resource_identifier; + } + } + } + + return 0; + } + + void AllocationBarModel::ClearSelectionState() + { + for (uint32_t i = 0; i < model_count_; i++) + { + selection_state_[i] = {}; + } + } + + void AllocationBarModel::ClearSelectionState(int32_t model_index) + { + selection_state_[model_index] = {}; + } + + const RmtVirtualAllocation* AllocationBarModel::GetAllocationFromResourceID(RmtResourceIdentifier resource_identifier, int32_t model_index) + { + if (resource_identifier == 0) + { + return nullptr; + } + + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return nullptr; + } + + const RmtDataSnapshot* snapshot = trace_manager.GetOpenSnapshot(); + if (snapshot == nullptr) + { + return nullptr; + } + + const RmtResource* resource = NULL; + const RmtErrorCode error_code = RmtResourceListGetResourceByResourceId(&snapshot->resource_list, resource_identifier, &resource); + if (error_code != RMT_OK) + { + return nullptr; + } + RMT_ASSERT(resource); + + int32_t resource_count = snapshot->resource_list.resource_count; + const RmtVirtualAllocation* allocation = resource->bound_allocation; + if (allocation != nullptr) + { + for (int32_t current_resource_index = 0; current_resource_index < resource_count; current_resource_index++) + { + const RmtResource* current_resource = allocation->resources[current_resource_index]; + if (current_resource->identifier == resource_identifier) + { + SetSelectedResourceForAllocation(resource->bound_allocation, current_resource_index, model_index); + break; + } + } + } + + return resource->bound_allocation; + } + + void AllocationBarModel::ShowAliased(bool aliased) + { + show_aliased_ = aliased; + } + + int AllocationBarModel::GetNumRows(const RmtVirtualAllocation* allocation) const + { + if (!show_aliased_) + { + return 1; + } + + return TraceManager::Get().GetAliasModel().GetNumRows(allocation); + } + + int AllocationBarModel::GetRowForResourceAtIndex(const RmtVirtualAllocation* allocation, int index) const + { + if (!show_aliased_) + { + return 0; + } + + return TraceManager::Get().GetAliasModel().GetRowForResourceAtIndex(allocation, index); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/allocation_bar_model.h b/source/frontend/models/allocation_bar_model.h new file mode 100644 index 0000000..5621b3f --- /dev/null +++ b/source/frontend/models/allocation_bar_model.h @@ -0,0 +1,184 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the allocation bar model base class. This model holds +/// any state information for derived models that use a graphical +/// representation of an allocation and is used for a single allocation bar, +/// as seen in the allocation explorer pane. These allocations are rendered +/// using RMVAllocationBar objects. +//============================================================================= + +#ifndef RMV_MODELS_ALLOCATION_BAR_MODEL_H_ +#define RMV_MODELS_ALLOCATION_BAR_MODEL_H_ + +#include +#include +#include +#include + +#include "rmt_types.h" +#include "rmt_virtual_allocation_list.h" + +namespace rmv +{ + class AllocationBarModel + { + public: + /// Structure to describe the selection state of the allocation overview. Contains the allocations + /// and resources that the mouse is currently over or selected. + struct SelectionState + { + SelectionState() + : hovered_allocation(nullptr) + , selected_allocation(nullptr) + , hovered_resource(0) + , selected_resource(0) + { + } + + const RmtVirtualAllocation* hovered_allocation; ///< The allocation hovered over, or nullptr if nothing + const RmtVirtualAllocation* selected_allocation; ///< The allocation selected, or nullptr if nothing + int32_t hovered_resource; ///< The resource hovered over (or -1 if none hovered over) + int32_t selected_resource; ///< The resource selected (or -1 if none selected) + }; + + /// Constructor. + /// \param model_count The number of models used to represent the allocations. + /// \param show_details If true, show the stats for this allocation bar graph. + explicit AllocationBarModel(uint32_t model_count, bool show_details); + + /// Destructor. + virtual ~AllocationBarModel(); + + /// Get the title text. + /// \param allocation_index The index in the scene of the allocation to return. + /// \param model_index The index of the model referred to. + /// \return The title text. + QString GetTitleText(int32_t allocation_index, int32_t model_index) const; + + /// Get the description text. + /// \param allocation_index The index in the scene of the allocation to return. + /// \param model_index The index of the model referred to. + /// \return The description text. + QString GetDescriptionText(int32_t allocation_index, int32_t model_index) const; + + /// Get the number of bytes per pixel of an allocation. + /// \param allocation_index The index in the scene of the allocation to return. + /// \param model_index The index of the model referred to. + /// \param width The width of the UI element, in pixels. + /// \return The number of bytes per pixel. + virtual double GetBytesPerPixel(int32_t allocation_index, int32_t model_index, int32_t width) const; + + /// Get the allocation. In the allocation overview, each allocation is assigned an index + /// in the scene and they all reference the same model. The scene index will remain the same + /// but the model will return a different allocation depending on how the allocations are + /// sorted in the model. In the allocation explorer, there is one allocation at scene index 0. + /// \param allocation_index The index in the scene of the allocation to return. + /// \param model_index The index of the model referred to. + /// \return The allocation. + virtual const RmtVirtualAllocation* GetAllocation(int32_t allocation_index, int32_t model_index) const; + + /// Should the allocation details be shown with the graphics representation of the allocation + /// and its resources. + /// \return true if the allocation details should be shown, false if not. + bool ShowDetails() const; + + /// Get the hovered resource for a specified allocation. + /// \param allocation_index The index of the allocation the resource is in. + /// \param model_index The index of the model where the allocation can be found. + /// \return The hovered resource index. + int32_t GetHoveredResourceForAllocation(int32_t allocation_index, int model_index) const; + + /// Get the selected resource for a specified allocation. + /// \param allocation_index The index of the allocation the resource is in. + /// \param model_index The index of the model where the allocation can be found. + /// \return The selected resource index. + int32_t GetSelectedResourceForAllocation(int32_t allocation_index, int model_index) const; + + /// Set the hovered resource for a specified allocation. + /// \param allocation_index The index of the allocation the resource is in. + /// \param resource_index The index of the resource that is hovered over. + /// \param model_index The index of the model where the allocation can be found. + void SetHoveredResourceForAllocation(int32_t allocation_index, int32_t resource_index, int model_index); + + /// Set the hovered resource for a specified allocation. + /// \param allocation_index The index of the allocation the resource is in. + /// \param model_index The index of the model where the allocation can be found. + /// \param width The width of the UI element, in pixels. + /// \param height The height of the UI element, in pixels. + /// \param mouse_pos The position of the mouse within the UI element, in pixels. + void SetHoveredResourceForAllocation(int32_t allocation_index, int32_t model_index, int32_t width, int32_t height, const QPointF& mouse_pos); + + /// Set the selected resource for a specified allocation. + /// \param allocation_index The index of the allocation the resource is in. + /// \param resource_index The index of the resource that is selected. + /// \param model_index The index of the model where the allocation can be found. + void SetSelectedResourceForAllocation(int32_t allocation_index, int32_t resource_index, int model_index); + + /// Set the selected resource for a specified allocation. + /// \param allocation The allocation to search for the resource in. + /// \param resource_index The index of the resource that is selected. + /// \param model_index The index of the model where the allocation can be found. + /// \return true if the allocation contains aliased resources, false if not. + bool SetSelectedResourceForAllocation(const RmtVirtualAllocation* allocation, int32_t resource_index, int model_index); + + /// Select a resource. + /// \param allocation_index The index of the allocation the resource is in. + /// \param model_index The index of the model where the allocation can be found. + /// \param resource_index The index of the resource selected. + void SelectResource(int32_t allocation_index, int32_t model_index, int resource_index); + + /// Find a resource identifier for a hovered-over resource (if it exists). + /// \param allocation_index The index of the allocation the resource is in. + /// \param model_index The index of the model where the allocation can be found. + /// \return The identifier of the requested resource, or 0 if nothing is selectable. + RmtResourceIdentifier FindResourceIdentifier(int32_t allocation_index, int32_t model_index) const; + + /// Clear the selection state for all allocations. No resources are selected or hovered over. + void ClearSelectionState(); + + /// Clear the selection state. No resources are selected or hovered over. + /// \param model_index The index of the model whose state is to be cleared. + void ClearSelectionState(int32_t model_index); + + /// Get an allocation from a resource ID + /// \param resource_identifier The resource identifier + /// \param model_index The index of the model where the resource can be found. + /// \return the allocation the resource is contained in + const RmtVirtualAllocation* GetAllocationFromResourceID(RmtResourceIdentifier resource_identifier, int32_t model_index); + + /// Get the title text. + /// \param allocation The allocation to get the title text for. + /// \return The title text. + QString GetTitleText(const RmtVirtualAllocation* allocation) const; + + /// Get the description text. + /// \param allocation The allocation to get the description text for. + /// \return The description text. + QString GetDescriptionText(const RmtVirtualAllocation* allocation) const; + + /// Should the resources be displayed to show aliasing (ie stacked). + /// \param aliased Whether the resources should be shown aliased. + void ShowAliased(bool aliased); + + /// Get the number of rows needed to show the resources. + /// \param allocation The virtual allocation containing the resources. + /// \return The number of rows. + int GetNumRows(const RmtVirtualAllocation* allocation) const; + + /// Get the row that a resource is in. + /// \param allocation The virtual allocation containing the resources. + /// \index The resource index. + /// \return The row. + int GetRowForResourceAtIndex(const RmtVirtualAllocation* allocation, int index) const; + + private: + SelectionState* selection_state_; ///< The selected/hovered resource state information. + uint32_t model_count_; ///< The number of graphic models required. + bool show_details_; ///< Should the allocation details text be shown. + bool show_aliased_; ///< Should the resources be shown aliased (stacked). + }; +} // namespace rmv + +#endif // RMV_MODELS_ALLOCATION_BAR_MODEL_H_ diff --git a/source/frontend/models/allocation_item_model.cpp b/source/frontend/models/allocation_item_model.cpp new file mode 100644 index 0000000..82a9856 --- /dev/null +++ b/source/frontend/models/allocation_item_model.cpp @@ -0,0 +1,223 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for an allocation item model. Used for the allocation +/// list tables +//============================================================================= + +#include "models/allocation_item_model.h" + +#include "rmt_assert.h" +#include "rmt_print.h" +#include "rmt_util.h" + +#include "models/snapshot/allocation_explorer_model.h" +#include "models/trace_manager.h" +#include "util/string_util.h" + +namespace rmv +{ + AllocationItemModel::AllocationItemModel(QObject* parent) + : QAbstractItemModel(parent) + , num_rows_(0) + , num_columns_(0) + { + } + + AllocationItemModel::~AllocationItemModel() + { + } + + void AllocationItemModel::SetRowCount(int rows) + { + num_rows_ = rows; + cache_.clear(); + } + + void AllocationItemModel::SetColumnCount(int columns) + { + num_columns_ = columns; + } + + void AllocationItemModel::AddAllocation(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation) + { + uint64_t histogram_total = 0; + uint64_t histogram[kRmtResourceBackingStorageCount] = {0}; + RmtVirtualAllocationGetBackingStorageHistogram(snapshot, virtual_allocation, histogram, &histogram_total); + + DataCache cache; + cache.virtual_allocation = virtual_allocation; + cache.allocation_size = RmtVirtualAllocationGetSizeInBytes(virtual_allocation); + cache.bound_size = RmtVirtualAllocationGetTotalResourceMemoryInBytes(snapshot, virtual_allocation); + if (cache.allocation_size >= cache.bound_size) + { + cache.unbound_size = RmtVirtualAllocationGetTotalUnboundSpaceInAllocation(snapshot, virtual_allocation); + } + else + { + cache.unbound_size = 0; + } + cache.avg_resource_size = RmtVirtualAllocationGetAverageResourceSizeInBytes(snapshot, virtual_allocation); + cache.std_dev_resource_size = RmtVirtualAllocationGetResourceStandardDeviationInBytes(snapshot, virtual_allocation); + cache.local_bytes = histogram[kRmtHeapTypeLocal]; + cache.invisible_bytes = histogram[kRmtHeapTypeInvisible]; + cache.host_bytes = histogram[kRmtHeapTypeSystem]; + cache.unmapped_bytes = histogram[kRmtResourceBackingStorageUnmapped]; + + cache_.push_back(cache); + } + + QVariant AllocationItemModel::data(const QModelIndex& index, int role) const + { + if (!index.isValid()) + { + return QVariant(); + } + + int row = index.row(); + + const DataCache& cache = cache_[row]; + if (cache.virtual_allocation == nullptr) + { + return QVariant(); + } + + if (role == Qt::DisplayRole) + { + switch (index.column()) + { + case kVirtualAllocationColumnId: + return QString::number(cache.virtual_allocation->base_address); + case kVirtualAllocationColumnAllocationSize: + return rmv::string_util::LocalizedValueMemory(cache.allocation_size, false, false); + case kVirtualAllocationColumnBound: + return rmv::string_util::LocalizedValueMemory(cache.bound_size, false, false); + case kVirtualAllocationColumnUnbound: + return rmv::string_util::LocalizedValueMemory(cache.unbound_size, false, false); + case kVirtualAllocationColumnAverageResourceSize: + return rmv::string_util::LocalizedValueMemory(cache.avg_resource_size, false, false); + case kVirtualAllocationColumnResourceSizeStdDev: + return rmv::string_util::LocalizedValueMemory(cache.std_dev_resource_size, false, false); + case kVirtualAllocationColumnResourceCount: + return rmv::string_util::LocalizedValue(cache.virtual_allocation->resource_count); + case kVirtualAllocationColumnPreferredHeapName: + return RmtGetHeapTypeNameFromHeapType(cache.virtual_allocation->heap_preferences[0]); + case kVirtualAllocationColumnInvisiblePercentage: + return rmv::string_util::LocalizedValueMemory(cache.invisible_bytes, false, false); + case kVirtualAllocationColumnLocalPercentage: + return rmv::string_util::LocalizedValueMemory(cache.local_bytes, false, false); + case kVirtualAllocationColumnSystemPercentage: + return rmv::string_util::LocalizedValueMemory(cache.host_bytes, false, false); + case kVirtualAllocationColumnUnmappedPercentage: + return rmv::string_util::LocalizedValueMemory(cache.unmapped_bytes, false, false); + default: + break; + } + } + else if (role == Qt::UserRole) + { + switch (index.column()) + { + case kVirtualAllocationColumnId: + return QVariant::fromValue((qulonglong)cache.virtual_allocation); + case kVirtualAllocationColumnAllocationSize: + return QVariant::fromValue(cache.allocation_size); + case kVirtualAllocationColumnBound: + return QVariant::fromValue(cache.bound_size); + case kVirtualAllocationColumnUnbound: + return QVariant::fromValue(cache.unbound_size); + case kVirtualAllocationColumnAverageResourceSize: + return QVariant::fromValue(cache.avg_resource_size); + case kVirtualAllocationColumnResourceSizeStdDev: + return QVariant::fromValue(cache.std_dev_resource_size); + case kVirtualAllocationColumnResourceCount: + return QVariant::fromValue(cache.virtual_allocation->resource_count); + case kVirtualAllocationColumnInvisiblePercentage: + return QVariant::fromValue(cache.invisible_bytes); + case kVirtualAllocationColumnLocalPercentage: + return QVariant::fromValue(cache.local_bytes); + case kVirtualAllocationColumnSystemPercentage: + return QVariant::fromValue(cache.host_bytes); + case kVirtualAllocationColumnUnmappedPercentage: + return QVariant::fromValue(cache.unmapped_bytes); + + default: + break; + } + } + + return QVariant(); + } + + Qt::ItemFlags AllocationItemModel::flags(const QModelIndex& index) const + { + return QAbstractItemModel::flags(index); + } + + QVariant AllocationItemModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case kVirtualAllocationColumnId: + return "Allocation"; + case kVirtualAllocationColumnAllocationSize: + return "Allocation size"; + case kVirtualAllocationColumnBound: + return "Bound"; + case kVirtualAllocationColumnUnbound: + return "Unbound"; + case kVirtualAllocationColumnAverageResourceSize: + return "Avg. resource size"; + case kVirtualAllocationColumnResourceSizeStdDev: + return "Resource size std. dev."; + case kVirtualAllocationColumnResourceCount: + return "Resource count"; + case kVirtualAllocationColumnPreferredHeapName: + return "Preferred heap"; + case kVirtualAllocationColumnInvisiblePercentage: + return "Committed invisible"; + case kVirtualAllocationColumnLocalPercentage: + return "Committed local"; + case kVirtualAllocationColumnSystemPercentage: + return "Committed host"; + case kVirtualAllocationColumnUnmappedPercentage: + return "Unmapped"; + default: + break; + } + } + + return QAbstractItemModel::headerData(section, orientation, role); + } + + QModelIndex AllocationItemModel::index(int row, int column, const QModelIndex& parent) const + { + if (!hasIndex(row, column, parent)) + { + return QModelIndex(); + } + + return createIndex(row, column); + } + + QModelIndex AllocationItemModel::parent(const QModelIndex& index) const + { + Q_UNUSED(index); + return QModelIndex(); + } + + int AllocationItemModel::rowCount(const QModelIndex& parent) const + { + Q_UNUSED(parent); + return num_rows_; + } + + int AllocationItemModel::columnCount(const QModelIndex& parent) const + { + Q_UNUSED(parent); + return num_columns_; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/allocation_item_model.h b/source/frontend/models/allocation_item_model.h new file mode 100644 index 0000000..7a6b9b1 --- /dev/null +++ b/source/frontend/models/allocation_item_model.h @@ -0,0 +1,86 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for an allocation item model. Used for the allocation list +/// tables +//============================================================================= + +#ifndef RMV_MODELS_ALLOCATION_ITEM_MODEL_H_ +#define RMV_MODELS_ALLOCATION_ITEM_MODEL_H_ + +#include + +#include "rmt_data_snapshot.h" +#include "rmt_virtual_allocation_list.h" + +namespace rmv +{ + class AllocationItemModel : public QAbstractItemModel + { + public: + /// Constructor. + explicit AllocationItemModel(QObject* parent = nullptr); + + /// Destructor. + ~AllocationItemModel(); + + /// Set the number of rows in the table. + /// \param rows The number of rows required. + void SetRowCount(int rows); + + /// Set the number of columns in the table. + /// \param columns The number of columns required. + void SetColumnCount(int columns); + + /// Add an allocation to the table. + /// \param snapshot The snapshot where the allocation data is located. + /// \param virtual_allocation The allocation to add. + void AddAllocation(const RmtDataSnapshot* snapshot, const RmtVirtualAllocation* virtual_allocation); + + // QAbstractItemModel overrides. See Qt documentation for parameter and return values + QVariant data(const QModelIndex& index, int role) const Q_DECL_OVERRIDE; + Qt::ItemFlags flags(const QModelIndex& index) const Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column, const QModelIndex& parent) const Q_DECL_OVERRIDE; + QModelIndex parent(const QModelIndex& index) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + + private: + /// Data from the backend that needs caching for speed. + struct DataCache + { + DataCache() + : virtual_allocation(nullptr) + , allocation_size(0) + , bound_size(0) + , unbound_size(0) + , avg_resource_size(0) + , std_dev_resource_size(0) + , local_bytes(0) + , invisible_bytes(0) + , host_bytes(0) + , unmapped_bytes(0) + { + } + + const RmtVirtualAllocation* virtual_allocation; ///< The virtual allocation. + uint64_t allocation_size; ///< The allocation size. + uint64_t bound_size; ///< The size of bound memory. + uint64_t unbound_size; ///< The size of unbound memory. + uint64_t avg_resource_size; ///< The average resource size. + uint64_t std_dev_resource_size; ///< The standard deviation of the resource size. + double local_bytes; ///< Amount of local memory. + double invisible_bytes; ///< Amount of invisible memory. + double host_bytes; ///< Amount of host memory. + double unmapped_bytes; ///< Amount of unmapped memory. + }; + + int num_rows_; ///< The number of rows in the table. + int num_columns_; ///< The number of columns in the table. + std::vector cache_; ///< Cached data from the backend. + }; +} // namespace rmv + +#endif // RMV_MODELS_ALLOCATION_ITEM_MODEL_H_ diff --git a/source/frontend/models/allocation_multi_bar_model.cpp b/source/frontend/models/allocation_multi_bar_model.cpp new file mode 100644 index 0000000..c284efc --- /dev/null +++ b/source/frontend/models/allocation_multi_bar_model.cpp @@ -0,0 +1,248 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the allocation multi bar model class. This model +/// derives from the allocation bar base class and contains additional +/// support for displays with multiple allocations as seen in the allocation +/// overview pane. These allocation are rendered using +/// RMVAllocationBar objects. +//============================================================================= + +#include "models/allocation_multi_bar_model.h" + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" + +#include "models/snapshot/allocation_overview_model.h" +#include "models/trace_manager.h" +#include "util/string_util.h" + +namespace rmv +{ + // Compare functor used to sort allocations. + // Handles the compare functions for all the "sort by" modes used by the allocation overview pane. + // Also handles ascending and descending ordering. + // This has the advantage of being able to do the whole comparison within a single 'function call' + // rather than having several static comparison functions + class SortComparator + { + public: + /// Constructor + /// \param sortMode The sort mode required. + /// \param ascending If true, sort ascending, Otherwise descending. + SortComparator(AllocationOverviewModel::SortMode sort_mode, bool ascending) + : sort_mode_(sort_mode) + , ascending_(ascending) + { + } + + /// Overloaded () operator which does the actual comparisons. This gets called by std::sort. + /// \param block1 Pointer to first RmtVirtualAllocation to compare. + /// \param block2 Pointer to second RmtVirtualAllocation to compare. + bool operator()(const RmtVirtualAllocation* virtual_allocation_a, const RmtVirtualAllocation* virtual_allocation_b) const + { + uint64_t value_a = 0; + uint64_t value_b = 0; + + // decide which sort mode to use and calculate the comparison arguments + switch (sort_mode_) + { + case AllocationOverviewModel::kSortModeAllocationID: + value_a = virtual_allocation_a->guid; + value_b = virtual_allocation_b->guid; + break; + + case AllocationOverviewModel::kSortModeAllocationSize: + value_a = RmtVirtualAllocationGetSizeInBytes(virtual_allocation_a); + value_b = RmtVirtualAllocationGetSizeInBytes(virtual_allocation_b); + break; + + case AllocationOverviewModel::kSortModeAllocationAge: + value_a = virtual_allocation_a->timestamp; + value_b = virtual_allocation_b->timestamp; + break; + + case AllocationOverviewModel::kSortModeResourceCount: + value_a = virtual_allocation_a->resource_count; + value_b = virtual_allocation_b->resource_count; + break; + + case AllocationOverviewModel::kSortModeFragmentationScore: + value_a = RmtVirtualAllocationGetFragmentationQuotient(virtual_allocation_a); + value_b = RmtVirtualAllocationGetFragmentationQuotient(virtual_allocation_b); + break; + + default: + // Should not get here + RMT_ASSERT_MESSAGE(false, "Allocation overview pane: Invalid sort mode."); + break; + } + + // decide on whether ascending or descending sort is required and return the appropriate result + if (ascending_) + { + return value_a < value_b; + } + + return value_a > value_b; + } + + private: + AllocationOverviewModel::SortMode sort_mode_; ///< The sort mode to use for the comparison + bool ascending_; ///< If true, use ascending sort. Otherwise descending + }; + + MultiAllocationBarModel::MultiAllocationBarModel(uint32_t model_count) + : AllocationBarModel(model_count, true) + , largest_allocation_size_(0) + , normalize_allocations_(false) + , allocation_offset_(0) + { + } + + MultiAllocationBarModel::~MultiAllocationBarModel() + { + } + + double MultiAllocationBarModel::GetBytesPerPixel(int32_t scene_index, int32_t model_index, int32_t width) const + { + RMT_ASSERT(width > 0); + const RmtVirtualAllocation* allocation = GetAllocation(scene_index, model_index); + RMT_ASSERT(allocation != nullptr); + if (allocation != nullptr) + { + uint64_t allocation_size = RmtVirtualAllocationGetSizeInBytes(allocation); + if (normalize_allocations_) + { + return static_cast(allocation_size) / static_cast(width); + } + + return static_cast(largest_allocation_size_) / static_cast(width); + } + + return 1.0; + } + + const RmtVirtualAllocation* MultiAllocationBarModel::GetAllocation(int32_t scene_index, int32_t model_index) const + { + Q_UNUSED(model_index); + scene_index += allocation_offset_; + if (scene_index < static_cast(shown_allocation_list_.size())) + { + return shown_allocation_list_[scene_index]; + } + return nullptr; + } + + void MultiAllocationBarModel::ResetModelValues() + { + largest_allocation_size_ = 0; + shown_allocation_list_.clear(); + } + + size_t MultiAllocationBarModel::GetAllocationIndex(const RmtVirtualAllocation* allocation) const + { + for (size_t index = 0; index < shown_allocation_list_.size(); index++) + { + if (shown_allocation_list_[index] == allocation) + { + return index; + } + } + return UINT64_MAX; + } + + size_t MultiAllocationBarModel::GetViewableAllocationCount() const + { + return shown_allocation_list_.size(); + } + + void MultiAllocationBarModel::SetNormalizeAllocations(bool normalized) + { + normalize_allocations_ = normalized; + } + + void MultiAllocationBarModel::Sort(int sort_mode, bool ascending) + { + std::stable_sort( + shown_allocation_list_.begin(), shown_allocation_list_.end(), SortComparator(static_cast(sort_mode), ascending)); + } + + void MultiAllocationBarModel::ApplyAllocationFilters(const QString& filter_text, const bool* heap_array_flags, int sort_mode, bool ascending) + { + const TraceManager& trace_manager = TraceManager::Get(); + + if (trace_manager.DataSetValid()) + { + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + const RmtVirtualAllocationList* allocation_list = &open_snapshot->virtual_allocation_list; + int32_t allocation_count = allocation_list->allocation_count; + + if (allocation_count > 0) + { + for (int32_t i = 0; i < allocation_count; i++) + { + const RmtVirtualAllocation* virtual_allocation = &allocation_list->allocation_details[i]; + int heap_index = virtual_allocation->heap_preferences[0]; + bool allow = false; + + if (heap_array_flags[heap_index]) + { + QString description = GetTitleText(virtual_allocation) + GetDescriptionText(virtual_allocation); + + if (filter_text.isEmpty() == false) + { + if (description.contains(filter_text, Qt::CaseInsensitive)) + { + allow = true; + } + } + else + { + allow = true; + } + } + + if (allow) + { + shown_allocation_list_.push_back(virtual_allocation); + } + + uint64_t allocation_size = RmtVirtualAllocationGetSizeInBytes(virtual_allocation); + if (allocation_size > largest_allocation_size_) + { + largest_allocation_size_ = allocation_size; + } + } + } + Sort(sort_mode, ascending); + } + } + + size_t MultiAllocationBarModel::SelectResource(RmtResourceIdentifier resource_identifier, int32_t model_index) + { + const RmtVirtualAllocation* resource_allocation = GetAllocationFromResourceID(resource_identifier, model_index); + size_t index = GetAllocationIndex(resource_allocation); + + if (index != UINT64_MAX) + { + for (int32_t resource_index = 0; resource_index < resource_allocation->resource_count; resource_index++) + { + if (resource_allocation->resources[resource_index]->identifier == resource_identifier) + { + AllocationBarModel::SelectResource(static_cast(index - allocation_offset_), model_index, resource_index); + break; + } + } + } + return index; + } + + void MultiAllocationBarModel::SetAllocationOffset(int32_t scene_offset) + { + allocation_offset_ = scene_offset; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/allocation_multi_bar_model.h b/source/frontend/models/allocation_multi_bar_model.h new file mode 100644 index 0000000..69a77e8 --- /dev/null +++ b/source/frontend/models/allocation_multi_bar_model.h @@ -0,0 +1,101 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the allocation multi bar model class. This model +/// derives from the allocation bar base class and contains additional +/// support for displays with multiple allocations as seen in the allocation +/// overview pane. These allocation are rendered using +/// RMVAllocationBar objects. +//============================================================================= + +#ifndef RMV_MODELS_ALLOCATION_MULTI_BAR_MODEL_H_ +#define RMV_MODELS_ALLOCATION_MULTI_BAR_MODEL_H_ + +#include +#include +#include + +#include "rmt_types.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/allocation_bar_model.h" + +namespace rmv +{ + class MultiAllocationBarModel : public AllocationBarModel + { + public: + /// Constructor. + /// \param model_count The number of models used to represent the allocations. + explicit MultiAllocationBarModel(uint32_t model_count); + + /// Destructor. + virtual ~MultiAllocationBarModel(); + + /// Get the number of bytes per pixel of an allocation. + /// \param scene_index The index in the scene of the allocation to return. + /// \param model_index The index of the model referred to. + /// \param width The width of the UI element, in pixels. + /// \return The number of bytes per pixel. + virtual double GetBytesPerPixel(int32_t scene_index, int32_t model_index, int32_t width) const; + + /// Get the allocation. In the allocation overview, each allocation is assigned an index + /// in the scene and they all reference the same model. The scene index will remain the same + /// but the model will return a different allocation depending on how the allocations are + /// sorted in the model. In the allocation explorer, there is one allocation at scene index 0. + /// \param scene_index The index in the scene of the allocation to return. + /// \param model_index The index of the model referred to. + /// \return The allocation. + virtual const RmtVirtualAllocation* GetAllocation(int32_t scene_index, int32_t model_index) const; + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Get the number of viewable allocations. + /// \return The number of viewable allocations. + size_t GetViewableAllocationCount() const; + + /// Set whether the allocations should be normalized. + /// \param normalized true if the allocations should be normalized, false if not. + void SetNormalizeAllocations(bool normalized); + + /// Sort the allocations. + /// \param sort_mode The sort mode to sort by. + /// \param ascending Whether to use ascending or descending ordering. + void Sort(int sort_mode, bool ascending); + + /// Apply filters and rebuild the list of allocations. + /// \param filter_text The search text specified in the UI. + /// \param heap_array_flags An array of flags indicating if the corresponding heap + /// should be shown. + void ApplyAllocationFilters(const QString& filter_text, const bool* heap_array_flags, int sort_mode, bool ascending); + + /// Select a resource on this pane. This is usually called when selecting a resource + /// on a different pane to make sure the resource selection is propagated to all + /// interested panes. + /// \param resource_identifier the resource identifier of the resource to select. + /// \param model_index The index of the model referred to. This pane uses a single model + /// to represent all the allocations. + /// \return The index in the scene of the selected resource. + size_t SelectResource(RmtResourceIdentifier resource_identifier, int32_t model_index); + + /// Set the offset of the allocation in the scene. This is the index of the allocation at the + /// top of the visible area of the scene. + /// \param allocation_offset The new allocation offset. + void SetAllocationOffset(int32_t allocation_offset); + + private: + /// Get the allocation index from the allocation. + /// \param allocation The allocation whose index is to be found. + /// \return The allocation index or UINT64_MAX if the allocation can't be found. + size_t GetAllocationIndex(const RmtVirtualAllocation* allocation) const; + + std::vector shown_allocation_list_; ///< The list of shown allocations. + uint64_t largest_allocation_size_; ///< The largest allocation size. + bool normalize_allocations_; ///< Should the allocations be drawn normalized. + int32_t allocation_offset_; ///< The allocation offset in the visible area of the scene. + }; +} // namespace rmv + +#endif // RMV_MODELS_ALLOCATION_MULTI_BAR_MODEL_H_ diff --git a/source/frontend/models/carousel_model.cpp b/source/frontend/models/carousel_model.cpp new file mode 100644 index 0000000..3dcc6dc --- /dev/null +++ b/source/frontend/models/carousel_model.cpp @@ -0,0 +1,262 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the carousel model +//============================================================================= + +#include "models/carousel_model.h" + +#include + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/trace_manager.h" +#include "views/custom_widgets/rmv_carousel_item.h" + +// The sort comparator used when sorting the resources in descending order +static int ResourceSortComparator(const void* r1, const void* r2) +{ + int32_t a1 = reinterpret_cast(r1)->usage_amount; + int32_t a2 = reinterpret_cast(r2)->usage_amount; + return (abs(a2) - abs(a1)); +} + +CarouselModel::CarouselModel() +{ +} + +CarouselModel::~CarouselModel() +{ +} + +bool CarouselModel::GetCarouselData(RmtDataSnapshot* snapshot, RMVCarouselData& out_carousel_data) const +{ + bool success = false; + + if (TraceManager::Get().DataSetValid()) + { + if (snapshot != nullptr) + { + success = true; + + uint64_t total_size = 0; + + uint64_t consumed_per_type[kRmtHeapTypeCount] = {}; + uint64_t available_per_type[kRmtHeapTypeCount] = {}; + + const uint64_t total_available = RmtVirtualAllocationListGetTotalSizeInBytes(&snapshot->virtual_allocation_list); + const uint64_t allocated_and_used = RmtVirtualAllocationListGetBoundTotalSizeInBytes(snapshot, &snapshot->virtual_allocation_list); + const uint64_t allocated_and_unused = RmtVirtualAllocationListGetUnboundTotalSizeInBytes(snapshot, &snapshot->virtual_allocation_list); + + total_size = allocated_and_used + allocated_and_unused; + + RMT_ASSERT(total_size <= total_available); + + for (int heap = 0; heap < kRmtHeapTypeCount; heap++) + { + available_per_type[heap] = snapshot->data_set->segment_info[heap].size; + } + + // do a quick check to see if the output carousel data is blank + RMT_ASSERT(out_carousel_data.resource_types_data.usage_map[0].usage_amount == 0); + RMT_ASSERT(out_carousel_data.allocation_sizes_data.buckets[0] == 0); + RMT_ASSERT(out_carousel_data.allocation_sizes_data.num_allocations == 0); + RMT_ASSERT(out_carousel_data.memory_footprint_data.total_allocated_memory == 0); + RMT_ASSERT(out_carousel_data.memory_footprint_data.total_unused_memory == 0); + RMT_ASSERT(out_carousel_data.memory_footprint_data.max_memory == 0); + RMT_ASSERT(out_carousel_data.memory_types_data.preferred_heap[0].value == 0); + RMT_ASSERT(out_carousel_data.memory_types_data.preferred_heap[0].max == 0); + + for (int32_t i = 0; i < snapshot->virtual_allocation_list.allocation_count; i++) + { + const RmtVirtualAllocation* current_allocation = &snapshot->virtual_allocation_list.allocation_details[i]; + uint64_t allocation_size = static_cast(current_allocation->size_in_4kb_page) * 4096; + const int resource_count = current_allocation->resource_count; + for (int j = 0; j < resource_count; j++) + { + RmtResource* current_resource = current_allocation->resources[j]; + RmtResourceUsageType resource_type = RmtResourceGetUsageType(current_resource); + out_carousel_data.resource_types_data.usage_amount[resource_type]++; + } + if (current_allocation->heap_preferences[0] < kRmtHeapTypeCount) + { + consumed_per_type[current_allocation->heap_preferences[0]] += allocation_size; + } + + int bucket_index = GetAllocationBucketIndex(allocation_size); + out_carousel_data.allocation_sizes_data.buckets[bucket_index]++; + } + + // Find the resource with the maximum value and store it. Also copy the resource amounts into the map + // so they can be sorted + int32_t maximum = 0; + for (int32_t i = 0; i < kRmtResourceUsageTypeCount; i++) + { + out_carousel_data.resource_types_data.usage_map[i].usage_type = static_cast(i); + out_carousel_data.resource_types_data.usage_map[i].usage_amount = out_carousel_data.resource_types_data.usage_amount[i]; + int32_t current_value = out_carousel_data.resource_types_data.usage_map[i].usage_amount; + if (current_value > maximum) + { + maximum = current_value; + } + } + out_carousel_data.resource_types_data.usage_maximum = maximum; + + // sort the resources by amount + qsort((void*)out_carousel_data.resource_types_data.usage_map, kRmtResourceUsageTypeCount, sizeof(ResourceMapping), ResourceSortComparator); + + out_carousel_data.allocation_sizes_data.num_allocations = snapshot->virtual_allocation_list.allocation_count; + + RMVCarouselMemoryFootprintData& memory_footprint = out_carousel_data.memory_footprint_data; + memory_footprint.total_allocated_memory = allocated_and_used; + memory_footprint.total_unused_memory = allocated_and_unused; + memory_footprint.max_memory = total_available; + + RMVCarouselMemoryTypesData& memory_types_data = out_carousel_data.memory_types_data; + for (int i = 0; i < kRmtHeapTypeCount; i++) + { + RmtSegmentStatus segment_status; + RmtDataSnapshotGetSegmentStatus(snapshot, static_cast(i), &segment_status); + memory_types_data.name[i] = QString(RmtGetHeapTypeNameFromHeapType(static_cast(i))); + memory_types_data.preferred_heap[i].value = consumed_per_type[i]; + memory_types_data.preferred_heap[i].max = available_per_type[i]; + memory_types_data.preferred_heap[i].color = GetColorFromSubscription(segment_status); + memory_types_data.physical_heap[i].value = segment_status.total_physical_mapped_by_process; + memory_types_data.physical_heap[i].max = segment_status.total_physical_size; + memory_types_data.physical_heap[i].color = kDefaultCarouselBarColor; + } + } + } + + return success; +} + +QColor CarouselModel::GetColorFromSubscription(const RmtSegmentStatus& segment_status) const +{ + RmtSegmentSubscriptionStatus status = RmtSegmentStatusGetOversubscribed(&segment_status); + + switch (status) + { + case kRmtSegmentSubscriptionStatusOverLimit: + return rmv::kOverSubscribedColor; + + case kRmtSegmentSubscriptionStatusUnderLimit: + return rmv::kUnderSubscribedColor; + + case kRmtSegmentSubscriptionStatusCloseToLimit: + return rmv::kCloseToSubscribedColor; + + default: + break; + } + + return kDefaultCarouselBarColor; +} + +bool CarouselModel::CalcGlobalCarouselData(RmtDataSnapshot* base_snapshot, RmtDataSnapshot* diff_snapshot, RMVCarouselData& out_carousel_delta_data) +{ + if (TraceManager::Get().DataSetValid()) + { + if (base_snapshot != nullptr && diff_snapshot != nullptr) + { + // Update global data + RMVCarouselData base_carousel_data = {}; + bool base_success = GetCarouselData(base_snapshot, base_carousel_data); + + RMVCarouselData diff_carousel_data = {}; + bool diff_success = GetCarouselData(diff_snapshot, diff_carousel_data); + + if (base_success && diff_success) + { + // memory footprint delta + RMVCarouselMemoryFootprintData& memory_footprint = out_carousel_delta_data.memory_footprint_data; + memory_footprint.max_memory = + std::max(base_carousel_data.memory_footprint_data.max_memory, diff_carousel_data.memory_footprint_data.max_memory); + memory_footprint.total_allocated_memory = + diff_carousel_data.memory_footprint_data.total_allocated_memory - base_carousel_data.memory_footprint_data.total_allocated_memory; + memory_footprint.total_unused_memory = + diff_carousel_data.memory_footprint_data.total_unused_memory - base_carousel_data.memory_footprint_data.total_unused_memory; + + // resource deltas + int32_t maximum = 0; + for (int i = 0; i < kRmtResourceUsageTypeCount; i++) + { + out_carousel_delta_data.resource_types_data.usage_map[i].usage_type = static_cast(i); + int32_t diff = diff_carousel_data.resource_types_data.usage_amount[i] - base_carousel_data.resource_types_data.usage_amount[i]; + out_carousel_delta_data.resource_types_data.usage_map[i].usage_amount = diff; + + diff = abs(diff); + if (diff > maximum) + { + maximum = diff; + } + } + out_carousel_delta_data.resource_types_data.usage_maximum = maximum; + + // sort the resources by amount + qsort( + (void*)out_carousel_delta_data.resource_types_data.usage_map, kRmtResourceUsageTypeCount, sizeof(ResourceMapping), ResourceSortComparator); + + // heap deltas + RMVCarouselMemoryTypesData& memory_types_data = out_carousel_delta_data.memory_types_data; + for (int i = 0; i < kRmtHeapTypeCount; i++) + { + memory_types_data.preferred_heap[i].max = base_carousel_data.memory_types_data.preferred_heap[i].max; + memory_types_data.preferred_heap[i].value = + diff_carousel_data.memory_types_data.preferred_heap[i].value - base_carousel_data.memory_types_data.preferred_heap[i].value; + memory_types_data.physical_heap[i].max = base_carousel_data.memory_types_data.physical_heap[i].max; + memory_types_data.physical_heap[i].value = + diff_carousel_data.memory_types_data.physical_heap[i].value - base_carousel_data.memory_types_data.physical_heap[i].value; + + // add heap name. Both snapshots contain the same heap names so use the reference snapshot + memory_types_data.name[i] = base_carousel_data.memory_types_data.name[i]; + } + + // allocation deltas + out_carousel_delta_data.allocation_sizes_data.num_allocations = + std::max(diff_carousel_data.allocation_sizes_data.num_allocations, base_carousel_data.allocation_sizes_data.num_allocations); + + for (int i = 0; i < kNumAllocationSizeBuckets; i++) + { + out_carousel_delta_data.allocation_sizes_data.buckets[i] = + diff_carousel_data.allocation_sizes_data.buckets[i] - base_carousel_data.allocation_sizes_data.buckets[i]; + } + + return true; + } + } + } + + return false; +} + +bool CarouselModel::GetCarouselData(RMVCarouselData& out_carousel_data) const +{ + RmtDataSnapshot* open_snapshot = TraceManager::Get().GetOpenSnapshot(); + return GetCarouselData(open_snapshot, out_carousel_data); +} + +int CarouselModel::GetAllocationBucketIndex(uint64_t allocation_size) const +{ + // The bucket index is calculated by using the MSB as an index ie bit 31 + // would give an index of 31. The index is then shifted down to give + // indices for: <1MB (0), <2MB (1), <4MB (2) etc. + // Anything >1GB will go in the last bucket + int bucket_index = 0; + + allocation_size >>= 19; // shift down by 2^19 so anything <1MB is in bucket 0. + if (allocation_size > 0) + { + bucket_index = log2(allocation_size); + if (bucket_index >= kNumAllocationSizeBuckets) + { + bucket_index = kNumAllocationSizeBuckets - 1; + } + } + return bucket_index; +} diff --git a/source/frontend/models/carousel_model.h b/source/frontend/models/carousel_model.h new file mode 100644 index 0000000..d42d740 --- /dev/null +++ b/source/frontend/models/carousel_model.h @@ -0,0 +1,123 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the carousel model +//============================================================================= + +#ifndef RMV_MODELS_CAROUSEL_MODEL_H_ +#define RMV_MODELS_CAROUSEL_MODEL_H_ + +#include +#include +#include + +#include "rmt_data_snapshot.h" +#include "rmt_resource_list.h" + +/// The number of buckets for the allocation sizes carousel item. Currently +/// caters for less than 1MB, then in power of 2 increments up to greater than 1GB. +static const int kNumAllocationSizeBuckets = 12; + +/// Specific to the carousel, for the memory footprint component. +struct RMVCarouselMemoryFootprintData +{ + double total_allocated_memory; ///< Total allocated memory. + double total_unused_memory; ///< Total memory that was allocated but not used. + double max_memory; ///< Max memory. +}; + +/// Mapping of resource type to amount. +struct ResourceMapping +{ + RmtResourceUsageType usage_type; ///< the resource type. + int32_t usage_amount; ///< the amount of this type. +}; + +/// Specific to the carousel, for the resource types component. +struct RMVCarouselResourceTypesData +{ + int32_t usage_amount[kRmtResourceUsageTypeCount]; ///< How much was used for this usage (the raw data). + ResourceMapping usage_map[kRmtResourceUsageTypeCount]; ///< How much was used for this usage (the sort results). + int32_t usage_maximum; ///< The highest resource value. +}; + +/// Struct to describe the heap. +struct HeapData +{ + HeapData() + : value(0) + , max(0) + { + } + + int64_t value; ///< The value. + int64_t max; ///< Possible maximum. + QColor color; ///< The bar color. +}; + +/// Specific to the carousel, for the memory types component. +struct RMVCarouselMemoryTypesData +{ + HeapData preferred_heap[kRmtHeapTypeCount]; ///< Holds how much of each preferred heap memory type is used and max. + HeapData physical_heap[kRmtHeapTypeCount]; ///< Holds how much of each physical heap memory type is used and max. + QString name[kRmtHeapTypeCount]; ///< The name of each heap. +}; + +/// Specific to the carousel, for the allocation sizes component. +struct RMVCarouselAllocationSizesData +{ + int32_t num_allocations; ///< The total number of allocations in the current snapshot. + int32_t buckets[kNumAllocationSizeBuckets]; ///< The number of allocations in each bucket. +}; + +/// Hold all carousel data. +struct RMVCarouselData +{ + RMVCarouselMemoryFootprintData memory_footprint_data; ///< Data for mem footprint component. + RMVCarouselResourceTypesData resource_types_data; ///< Data for resource types component. + RMVCarouselMemoryTypesData memory_types_data; ///< Data for mem types component. + RMVCarouselAllocationSizesData allocation_sizes_data; ///< Data for allocation sizes component. +}; + +class CarouselModel +{ +public: + /// Constructor. + CarouselModel(); + + /// Destructor. + ~CarouselModel(); + + /// Parse the dataset for carousel data for the currently opened snapshot. + /// \param out_carousel_data The data needed for the carousel. + /// \return true if successful, false if not. + bool GetCarouselData(RMVCarouselData& out_carousel_data) const; + + /// Compute delta between carousels. + /// \param base_snapshot The first (base) snapshot. + /// \param diff_snapshot The second snapshot to compare against the first. + /// \param out_carousel_delta_data The delta of the carousel data. + /// \return true if successful, false if not. + bool CalcGlobalCarouselData(RmtDataSnapshot* base_snapshot, RmtDataSnapshot* diff_snapshot, RMVCarouselData& out_carousel_delta_data); + +private: + /// Parse the dataset for carousel data for a snapshot. Assumes that + /// out_carousel_data has been initialized to all 0's. + /// \param snapshot_id target snapshot. + /// \param out_carousel_data The data needed for the carousel. + /// \return true if successful, false if not. + bool GetCarouselData(RmtDataSnapshot* snapshot, RMVCarouselData& out_carousel_data) const; + + /// Calculate which allocation bucket this allocation will go into. + /// \param allocation_size The current allocation size to add. + /// \return the bucket index. + int GetAllocationBucketIndex(uint64_t allocation_size) const; + + /// Get the color based on the memory subscription. + /// \param segment_status The segment status. + /// \return The color to use. + QColor GetColorFromSubscription(const RmtSegmentStatus& segment_status) const; +}; + +#endif // RMV_MODELS_CAROUSEL_MODEL_H_ diff --git a/source/frontend/models/combo_box_model.cpp b/source/frontend/models/combo_box_model.cpp new file mode 100644 index 0000000..36e509c --- /dev/null +++ b/source/frontend/models/combo_box_model.cpp @@ -0,0 +1,76 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a model corresponding to a combo box. +/// This can be used for the ArrowIconComboBoxes when in the checkbox mode, +/// where it's possible to have more than 1 entry selected. +//============================================================================= + +#include "models/combo_box_model.h" + +namespace rmv +{ + ComboBoxModel::ComboBoxModel() + { + } + + ComboBoxModel::~ComboBoxModel() + { + } + + void ComboBoxModel::SetupExcludeIndexList(const std::set& indices) + { + excluded_list_.clear(); + + for (auto it = indices.begin(); it != indices.end(); ++it) + { + excluded_list_.push_back(*it); + } + } + + void ComboBoxModel::SetupState(const ArrowIconComboBox* combo_box, bool all_option) + { + // Skip the first entry in the list if it is "All" or something similar + int start_index = (!all_option) ? 0 : 1; + + // add all of the checked items to the list. This will be their index + // in the combo box + checked_items_list_.clear(); + for (int i = start_index; i < combo_box->RowCount(); i++) + { + if (combo_box->IsChecked(i) == true) + { + checked_items_list_.insert(i - start_index); + } + } + } + + bool ComboBoxModel::ItemInList(int item) const + { + int count = 0; + for (auto exclude_it = excluded_list_.begin(); exclude_it != excluded_list_.end(); ++exclude_it) + { + // if the item is in the excluded list, then item isn't valid + if (item == (*exclude_it)) + { + return false; + } + + // if the item is more than the current entry in the excluded list, adjust the item to take into + // account it's missing from the UI (it's index in the UI will now be 1 less than the enum value + // passed in + if (item > (*exclude_it)) + { + count++; + } + } + + auto it = checked_items_list_.find(item - count); + if (it != checked_items_list_.end()) + { + return true; + } + return false; + } +} // namespace rmv diff --git a/source/frontend/models/combo_box_model.h b/source/frontend/models/combo_box_model.h new file mode 100644 index 0000000..992eacf --- /dev/null +++ b/source/frontend/models/combo_box_model.h @@ -0,0 +1,55 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a model corresponding to a combo box. +/// This can be used for the ArrowIconComboBoxes when in the checkbox mode, +/// where it's possible to have more than 1 entry selected. +//============================================================================= + +#ifndef RMV_MODELS_COMBO_BOX_MODEL_H_ +#define RMV_MODELS_COMBO_BOX_MODEL_H_ + +#include +#include +#include + +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" + +namespace rmv +{ + class ComboBoxModel : public QObject + { + Q_OBJECT + + public: + /// Constructor. + ComboBoxModel(); + + /// Destructor. + ~ComboBoxModel(); + + /// Set up the list of excluded items. These are items from an enum list that are + /// not shown in the UI so the model also needs to know they are excluded. + /// \param indices This set of indices to exclude. + void SetupExcludeIndexList(const std::set& indices); + + /// Is an item in the list. + /// \param item The item to search for. + /// \return true if so, false otherwise. + bool ItemInList(int item) const; + + protected: + /// Check the state of the combo box and setup the internal state representation + /// of the ArrowIconComboBox. + /// \param combo_box Pointer to the combo box whose state is to be examined. + /// \param all_option True if the combo box contains an "All" option. + void SetupState(const ArrowIconComboBox* combo_box, bool all_option); + + private: + std::set checked_items_list_; ///< The list of checked items. + std::vector excluded_list_; ///< Ths list of excluded itenms. + }; +} // namespace rmv + +#endif // RMV_MODELS_COMBO_BOX_MODEL_H_ diff --git a/source/frontend/models/compare/allocation_delta_model.cpp b/source/frontend/models/compare/allocation_delta_model.cpp new file mode 100644 index 0000000..74c18af --- /dev/null +++ b/source/frontend/models/compare/allocation_delta_model.cpp @@ -0,0 +1,234 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for the Allocation delta model +//============================================================================= + +#include "models/compare/allocation_delta_model.h" + +#include "rmt_error.h" +#include "rmt_util.h" + +#include "models/trace_manager.h" +#include "util/string_util.h" + +// The number of allocation models needed. For this pane, there are 2 +// allocation graphics. +static const int32_t kNumAllocationModels = 2; + +namespace rmv +{ + AllocationDeltaModel::AllocationDeltaModel() + : ModelViewMapper(kAllocationDeltaCompareNumWidgets) + , base_index_(kSnapshotCompareBase) + , diff_index_(kSnapshotCompareDiff) + , base_snapshot_(nullptr) + , diff_snapshot_(nullptr) + , largest_allocation_size_(0) + { + allocation_bar_model_ = new AllocationBarModel(kNumAllocationModels, false); + } + + AllocationDeltaModel::~AllocationDeltaModel() + { + delete allocation_bar_model_; + } + + void AllocationDeltaModel::ResetModelValues() + { + SetModelData(kAllocationDeltaCompareBaseName, "-"); + SetModelData(kAllocationDeltaCompareBaseGraphicName, "-"); + SetModelData(kAllocationDeltaCompareDiffName, "-"); + SetModelData(kAllocationDeltaCompareDiffGraphicName, "-"); + + base_index_ = kSnapshotCompareBase; + diff_index_ = kSnapshotCompareDiff; + } + + bool AllocationDeltaModel::Update(int32_t base_allocation_index, int32_t diff_allocation_index) + { + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return false; + } + + base_snapshot_ = trace_manager.GetComparedSnapshot(base_index_); + diff_snapshot_ = trace_manager.GetComparedSnapshot(diff_index_); + + if (base_snapshot_ == nullptr || diff_snapshot_ == nullptr) + { + return false; + } + + SetModelData(kAllocationDeltaCompareBaseName, trace_manager.GetCompareSnapshotName(base_index_)); + SetModelData(kAllocationDeltaCompareBaseGraphicName, trace_manager.GetCompareSnapshotName(base_index_)); + SetModelData(kAllocationDeltaCompareDiffName, trace_manager.GetCompareSnapshotName(diff_index_)); + SetModelData(kAllocationDeltaCompareDiffGraphicName, trace_manager.GetCompareSnapshotName(diff_index_)); + + const RmtVirtualAllocation* base_allocation = &base_snapshot_->virtual_allocation_list.allocation_details[base_allocation_index]; + const RmtVirtualAllocation* diff_allocation = &diff_snapshot_->virtual_allocation_list.allocation_details[diff_allocation_index]; + + int64_t base_selected_size = RmtVirtualAllocationGetSizeInBytes(base_allocation); + int64_t diff_selected_size = RmtVirtualAllocationGetSizeInBytes(diff_allocation); + + largest_allocation_size_ = RMT_MAXIMUM(base_selected_size, diff_selected_size); + + return true; + } + + bool AllocationDeltaModel::UpdateAllocationDeltas(int32_t base_allocation_index, int32_t diff_allocation_index, QVector& out_allocation_data) + { + const RmtVirtualAllocation* base_virtual_allocation = &base_snapshot_->virtual_allocation_list.allocation_details[base_allocation_index]; + const RmtVirtualAllocation* diff_virtual_allocation = &diff_snapshot_->virtual_allocation_list.allocation_details[diff_allocation_index]; + + if (base_virtual_allocation != nullptr && diff_virtual_allocation != nullptr) + { + int64_t base_size = RmtVirtualAllocationGetSizeInBytes(base_virtual_allocation); + int64_t diff_size = RmtVirtualAllocationGetSizeInBytes(diff_virtual_allocation); + + int64_t base_bound = RmtVirtualAllocationGetTotalResourceMemoryInBytes(base_snapshot_, base_virtual_allocation); + int64_t diff_bound = RmtVirtualAllocationGetTotalResourceMemoryInBytes(diff_snapshot_, diff_virtual_allocation); + + int64_t base_unbound = RmtVirtualAllocationGetTotalUnboundSpaceInAllocation(base_snapshot_, base_virtual_allocation); + int64_t diff_unbound = RmtVirtualAllocationGetTotalUnboundSpaceInAllocation(diff_snapshot_, diff_virtual_allocation); + + int64_t base_average_resource_size = RmtVirtualAllocationGetAverageResourceSizeInBytes(base_snapshot_, base_virtual_allocation); + int64_t diff_average_resource_size = RmtVirtualAllocationGetAverageResourceSizeInBytes(diff_snapshot_, diff_virtual_allocation); + + int64_t base_resource_count = base_virtual_allocation->resource_count; + int64_t diff_resource_count = diff_virtual_allocation->resource_count; + + int64_t base_stanrdard_deviation = RmtVirtualAllocationGetResourceStandardDeviationInBytes(base_snapshot_, base_virtual_allocation); + int64_t diff_standard_deviation = RmtVirtualAllocationGetResourceStandardDeviationInBytes(diff_snapshot_, diff_virtual_allocation); + + out_allocation_data[kAllocationDeltaDataTypeAvailableSize].value_num = diff_size - base_size; + out_allocation_data[kAllocationDeltaDataTypeAllocatedAndUsed].value_num = diff_bound - base_bound; + out_allocation_data[kAllocationDeltaDataTypeAllocatedAndUnused].value_num = diff_unbound - base_unbound; + out_allocation_data[kAllocationDeltaDataTypeAverageAllocationSize].value_num = diff_average_resource_size - base_average_resource_size; + out_allocation_data[kAllocationDeltaDataTypeStandardDeviation].value_num = diff_standard_deviation - base_stanrdard_deviation; + out_allocation_data[kAllocationDeltaDataTypeAllocationCount].value_num = diff_resource_count - base_resource_count; + + return true; + } + return false; + } + + bool AllocationDeltaModel::SwapSnapshots() + { + int temp_index = diff_index_; + diff_index_ = base_index_; + base_index_ = temp_index; + + return true; + } + + RmtDataSnapshot* AllocationDeltaModel::GetSnapshotFromSnapshotIndex(int32_t snapshot_index) const + { + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return nullptr; + } + + RmtDataSnapshot* snapshot = nullptr; + if (snapshot_index == kSnapshotCompareBase) + { + snapshot = trace_manager.GetComparedSnapshot(base_index_); + } + else + { + snapshot = trace_manager.GetComparedSnapshot(diff_index_); + } + return snapshot; + } + + double AllocationDeltaModel::GetAllocationSizeRatio(int32_t allocation_index, int32_t model_index) const + { + const RmtDataSnapshot* snapshot = GetSnapshotFromSnapshotIndex(model_index); + if (snapshot == nullptr) + { + return 0.0; + } + + // Make sure allocation index is in range. + if (allocation_index < 0 || allocation_index >= snapshot->virtual_allocation_list.allocation_count) + { + return 0.0; + } + + const RmtVirtualAllocation* allocation = &snapshot->virtual_allocation_list.allocation_details[allocation_index]; + const uint64_t size_in_bytes = RmtVirtualAllocationGetSizeInBytes(allocation); + return (static_cast(size_in_bytes) / static_cast(largest_allocation_size_)); + } + + void AllocationDeltaModel::InitializeComboBox(int32_t snapshot_index, ArrowIconComboBox* combo_box) const + { + const RmtDataSnapshot* snapshot = GetSnapshotFromSnapshotIndex(snapshot_index); + if (snapshot == nullptr) + { + return; + } + + combo_box->ClearItems(); + + for (int32_t current_allocation_index = 0; current_allocation_index < snapshot->virtual_allocation_list.allocation_count; current_allocation_index++) + { + const RmtVirtualAllocation* allocation = &snapshot->virtual_allocation_list.allocation_details[current_allocation_index]; + + QString allocation_name = "Allocation " + QString::number(allocation->base_address) + " | " + + rmv::string_util::LocalizedValueMemory(RmtVirtualAllocationGetSizeInBytes(allocation), false, false); + + QListWidgetItem* item = new QListWidgetItem(allocation_name); + item->setData(Qt::UserRole, (qulonglong)allocation); + combo_box->AddItem(item); + } + + if (snapshot->virtual_allocation_list.allocation_count > 0) + { + combo_box->SetSelectedRow(0); + } + } + + void AllocationDeltaModel::SelectAllocation(int32_t snapshot_index, int32_t allocation_index) + { + const RmtDataSnapshot* snapshot = GetSnapshotFromSnapshotIndex(snapshot_index); + if (snapshot != nullptr) + { + RmtVirtualAllocation* allocation = nullptr; + + if (allocation_index < snapshot->virtual_allocation_list.allocation_count) + { + allocation = &snapshot->virtual_allocation_list.allocation_details[allocation_index]; + } + + if (allocation != allocation_bar_model_->GetAllocation(0, snapshot_index)) + { + allocation_bar_model_->SetSelectedResourceForAllocation(allocation, -1, snapshot_index); + } + } + } + + const RmtResource* AllocationDeltaModel::GetResource(int32_t snapshot_index, RmtResourceIdentifier resource_identifier) const + { + const RmtDataSnapshot* snapshot = GetSnapshotFromSnapshotIndex(snapshot_index); + if (snapshot == nullptr) + { + return nullptr; + } + + const RmtResource* resource = nullptr; + RmtErrorCode error_code = RmtResourceListGetResourceByResourceId(&snapshot->resource_list, resource_identifier, &resource); + if (error_code != RMT_OK) + { + return nullptr; + } + return resource; + } + + AllocationBarModel* AllocationDeltaModel::GetAllocationBarModel() const + { + return allocation_bar_model_; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/compare/allocation_delta_model.h b/source/frontend/models/compare/allocation_delta_model.h new file mode 100644 index 0000000..5a3346e --- /dev/null +++ b/source/frontend/models/compare/allocation_delta_model.h @@ -0,0 +1,122 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Allocation delta model +//============================================================================= + +#ifndef RMV_MODELS_COMPARE_ALLOCATION_DELTA_MODEL_H_ +#define RMV_MODELS_COMPARE_ALLOCATION_DELTA_MODEL_H_ + +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_types.h" + +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" +#include "qt_common/utils/model_view_mapper.h" + +#include "models/allocation_bar_model.h" +#include "views/custom_widgets/rmv_delta_display.h" + +namespace rmv +{ + /// Enum containing the allocation delta data indices. + enum AllocationDeltaDataType + { + kAllocationDeltaDataTypeAvailableSize, + kAllocationDeltaDataTypeAllocatedAndUsed, + kAllocationDeltaDataTypeAllocatedAndUnused, + kAllocationDeltaDataTypeAverageAllocationSize, + kAllocationDeltaDataTypeStandardDeviation, + kAllocationDeltaDataTypeAllocationCount, + + kAllocationDeltaDataTypeCount, + }; + + /// Enum containing the id's of UI elements needed by the model. + enum AllocationDeltaWidgets + { + kAllocationDeltaCompareBaseName, + kAllocationDeltaCompareBaseGraphicName, + kAllocationDeltaCompareDiffName, + kAllocationDeltaCompareDiffGraphicName, + + kAllocationDeltaCompareNumWidgets, + }; + + /// Container class that holds model data for a given pane. + class AllocationDeltaModel : public ModelViewMapper + { + public: + /// Constructor. + explicit AllocationDeltaModel(); + + /// Destructor. + virtual ~AllocationDeltaModel(); + + /// Update the model. + /// \param base_allocation_index The index of the base allocation. + /// \param diff_allocation_index The index of the diff allocation. + /// \return true if the model was updated successfully, false otherwise. + bool Update(int32_t base_allocation_index, int32_t diff_allocation_index); + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Update the allocation deltas. + /// \param base_allocation_index The index of the base allocation. + /// \param diff_allocation_index The index of the diff allocation. + /// \param out_allocation_data A structure where the allocation delta information is written to + /// by the model. + /// \return true if the data was written correctly, false otherwise. + bool UpdateAllocationDeltas(int32_t base_allocation_index, int32_t diff_allocation_index, QVector& out_allocation_data); + + /// Swap the snapshots. + /// \return true if the snapshots were swapped successfully, false otherwise. + bool SwapSnapshots(); + + /// Get the relative size as a ratio of this allocation compared to the largest allocation. In + /// this case, the largest allocation is the largest selected allocations from the base and diff snapshots. + /// As an example, if the current allocation is half the size of the largest allocation, the value returned + /// would be 0.5. + /// \param allocation_index The index of the allocation in the list of allocations in the snapshot. + /// \param model_index In this case, the index of the snapshot (base or diff). + /// \return The relative size as a percentage. + double GetAllocationSizeRatio(int32_t allocation_index, int32_t model_index) const; + + /// Initialize a combo box with allocation data from the model. + /// \param snapshot_index The index of the snapshot (base or diff). + /// \param combo_box The combo box to populate. + void InitializeComboBox(int32_t snapshot_index, ArrowIconComboBox* combo_box) const; + + /// Get an allocation from the model. + /// \param snapshot_index The index of the snapshot (base or diff). + /// \param allocation_index The index of the allocation in the list of allocations in the snapshot. + void SelectAllocation(int32_t snapshot_index, int32_t allocation_index); + + /// Get a resource from the model. + /// \param snapshot_index The index of the snapshot (base or diff). + /// \param resource_identifier The identifier of the resource to get. + /// \return The resource (or nullptr if resource not available). + const RmtResource* GetResource(int32_t snapshot_index, RmtResourceIdentifier resource_identifier) const; + + /// Get the model for the allocation bar. + /// \return The allocation bar model. + AllocationBarModel* GetAllocationBarModel() const; + + private: + /// Get the snapshot from the snapshot index. + /// \param snapshot_index The index of the snapshot (base or diff). + /// \return The snapshot (or nullptr if no snapshot available). + RmtDataSnapshot* GetSnapshotFromSnapshotIndex(int32_t snapshot_index) const; + + AllocationBarModel* allocation_bar_model_; ///< The model for the allocation bar graphs. + int base_index_; ///< The index of the base snapshot. + int diff_index_; ///< The index of the diff snapshot. + RmtDataSnapshot* base_snapshot_; ///< The base snapshot. + RmtDataSnapshot* diff_snapshot_; ///< The diff snapshot. + int64_t largest_allocation_size_; ///< the current largest allocation size. + }; +} // namespace rmv + +#endif // RMV_MODELS_COMPARE_ALLOCATION_DELTA_MODEL_H_ diff --git a/source/frontend/models/compare/memory_leak_finder_model.cpp b/source/frontend/models/compare/memory_leak_finder_model.cpp new file mode 100644 index 0000000..cc429f8 --- /dev/null +++ b/source/frontend/models/compare/memory_leak_finder_model.cpp @@ -0,0 +1,307 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for Memory Leak Finder pane +//============================================================================= + +#include "models/compare/memory_leak_finder_model.h" + +#include +#include +#include + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" +#include "rmt_resource_list.h" +#include "rmt_util.h" + +#include "models/proxy_models/memory_leak_finder_proxy_model.h" +#include "models/resource_item_model.h" +#include "models/snapshot_manager.h" +#include "models/trace_manager.h" +#include "util/string_util.h" + +static const QString kStatsText = "%1 resources, %2"; + +namespace rmv +{ + MemoryLeakFinderModel::MemoryLeakFinderModel() + : ModelViewMapper(kMemoryLeakFinderNumWidgets) + , table_model_(nullptr) + , proxy_model_(nullptr) + , resource_thresholds_{} + , stats_in_both_{} + , stats_in_base_only_{} + , stats_in_diff_only_{} + { + ResetStats(); + } + + MemoryLeakFinderModel::~MemoryLeakFinderModel() + { + delete table_model_; + } + + void MemoryLeakFinderModel::InitializeTableModel(ScaledTableView* table_view, uint num_rows, uint num_columns, uint32_t compare_id_filter) + { + if (proxy_model_ != nullptr) + { + delete proxy_model_; + proxy_model_ = nullptr; + } + + proxy_model_ = new MemoryLeakFinderProxyModel(compare_id_filter); + table_model_ = proxy_model_->InitializeResourceTableModels(table_view, num_rows, num_columns); + table_model_->Initialize(table_view, true); + } + + void MemoryLeakFinderModel::ResetModelValues() + { + table_model_->removeRows(0, table_model_->rowCount()); + table_model_->SetRowCount(0); + + memset(resource_thresholds_, 0, sizeof(uint64_t) * (kSizeSliderRange + 1)); + + SetModelData(kMemoryLeakFinderBaseStats, "-"); + SetModelData(kMemoryLeakFinderBothStats, "-"); + SetModelData(kMemoryLeakFinderDiffStats, "-"); + SetModelData(kMemoryLeakFinderTotalResources, "-"); + SetModelData(kMemoryLeakFinderTotalSize, "-"); + SetModelData(kMemoryLeakFinderBaseCheckbox, "-"); + SetModelData(kMemoryLeakFinderDiffCheckbox, "-"); + SetModelData(kMemoryLeakFinderBaseSnapshot, "-"); + SetModelData(kMemoryLeakFinderDiffSnapshot, "-"); + } + + void MemoryLeakFinderModel::Update(SnapshotCompareId compare_filter) + { + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return; + } + + const RmtDataSnapshot* base_snapshot = trace_manager.GetComparedSnapshot(kSnapshotCompareBase); + const RmtDataSnapshot* diff_snapshot = trace_manager.GetComparedSnapshot(kSnapshotCompareDiff); + + if (base_snapshot == nullptr || diff_snapshot == nullptr) + { + return; + } + + RmtSnapshotPoint* base_snapshot_point = base_snapshot->snapshot_point; + RmtSnapshotPoint* diff_snapshot_point = diff_snapshot->snapshot_point; + + if (base_snapshot_point == nullptr || diff_snapshot_point == nullptr) + { + return; + } + + const QString base_snapshot_name = QString(base_snapshot_point->name); + const QString diff_snapshot_name = QString(diff_snapshot_point->name); + + SetModelData(kMemoryLeakFinderBaseCheckbox, "Resources unique to snapshot " + base_snapshot_name); + SetModelData(kMemoryLeakFinderDiffCheckbox, "Resources unique to snapshot " + diff_snapshot_name); + SetModelData(kMemoryLeakFinderBaseSnapshot, base_snapshot_name); + SetModelData(kMemoryLeakFinderDiffSnapshot, diff_snapshot_name); + + // Set up the lists to contain worst-case scenario. + // Uses unordered set/map for fast insert/delete + std::unordered_map resources_in_base_only; + std::unordered_set resources_in_diff_only; + std::unordered_set resources_in_both; + resources_in_base_only.reserve(base_snapshot->resource_list.resource_count); + resources_in_diff_only.reserve(diff_snapshot->resource_list.resource_count); + resources_in_both.reserve(RMT_MAXIMUM(base_snapshot->resource_list.resource_count, diff_snapshot->resource_list.resource_count)); + + // Add the base resources to an unordered map, with the resource id as the key. + // If this needs speeding up further, look for which resource list is smallest (base or diff) and populate that instead + // of the base. + for (int32_t current_resource_index = 0; current_resource_index < base_snapshot->resource_list.resource_count; ++current_resource_index) + { + const RmtResource* current_resource = &base_snapshot->resource_list.resources[current_resource_index]; + resources_in_base_only.insert(std::make_pair(current_resource->identifier, current_resource)); + } + + // Add the diff resources. If the diff resource is in the base resource list (look it up via resource id), + // add it to the 'both' list and remove from base map, otherwise just add it to the compare (diff) + for (int32_t current_resource_index = 0; current_resource_index < diff_snapshot->resource_list.resource_count; ++current_resource_index) + { + const RmtResource* current_resource = &diff_snapshot->resource_list.resources[current_resource_index]; + + auto iter = resources_in_base_only.find(current_resource->identifier); + if (iter != resources_in_base_only.end()) + { + // found, so add to both and remove from base + resources_in_both.insert(current_resource); + resources_in_base_only.erase(iter); + } + else + { + // not found, just add to the diff list + resources_in_diff_only.insert(current_resource); + } + } + + size_t size = resources_in_base_only.size() + resources_in_diff_only.size() + resources_in_both.size(); + table_model_->SetRowCount(static_cast(size)); + + ResetStats(); + + int32_t row_index = 0; + for (auto iter = resources_in_both.begin(); iter != resources_in_both.end(); ++iter) + { + const RmtResource* resource = *iter; + table_model_->AddResource(base_snapshot, resource, kSnapshotCompareIdCommon); + stats_in_both_.num_resources++; + stats_in_both_.size += resource->size_in_bytes; + row_index++; + } + + for (auto iter = resources_in_base_only.begin(); iter != resources_in_base_only.end(); ++iter) + { + const RmtResource* resource = (*iter).second; + table_model_->AddResource(base_snapshot, resource, kSnapshotCompareIdOpen); + stats_in_base_only_.num_resources++; + stats_in_base_only_.size += resource->size_in_bytes; + row_index++; + } + + for (auto iter = resources_in_diff_only.begin(); iter != resources_in_diff_only.end(); ++iter) + { + const RmtResource* resource = *iter; + table_model_->AddResource(base_snapshot, resource, kSnapshotCompareIdCompared); + stats_in_diff_only_.num_resources++; + stats_in_diff_only_.size += resource->size_in_bytes; + row_index++; + } + + proxy_model_->UpdateCompareFilter(compare_filter); + proxy_model_->invalidate(); + + UpdateResourceThresholds(); + + UpdateLabels(); + } + + void MemoryLeakFinderModel::ResetStats() + { + stats_in_both_.num_resources = 0; + stats_in_both_.size = 0; + stats_in_base_only_.num_resources = 0; + stats_in_base_only_.size = 0; + stats_in_diff_only_.num_resources = 0; + stats_in_diff_only_.size = 0; + } + + void MemoryLeakFinderModel::UpdateResourceThresholds() + { + uint32_t row_count = table_model_->rowCount(); + if (row_count > 0) + { + std::vector resource_sizes; + resource_sizes.reserve(row_count); + + for (uint32_t loop = 0; loop < row_count; loop++) + { + QModelIndex model_index = table_model_->index(loop, kResourceColumnSize, QModelIndex()); + if (model_index.isValid() == true) + { + resource_sizes.push_back(table_model_->data(model_index, Qt::UserRole).toULongLong()); + } + } + TraceManager::Get().BuildResourceSizeThresholds(resource_sizes, resource_thresholds_); + } + } + + void MemoryLeakFinderModel::UpdateLabels() + { + SetModelData(kMemoryLeakFinderBaseStats, + kStatsText.arg(rmv::string_util::LocalizedValue(stats_in_base_only_.num_resources)) + .arg(rmv::string_util::LocalizedValueMemory(stats_in_base_only_.size, false, false))); + SetModelData(kMemoryLeakFinderBothStats, + kStatsText.arg(rmv::string_util::LocalizedValue(stats_in_both_.num_resources)) + .arg(rmv::string_util::LocalizedValueMemory(stats_in_both_.size, false, false))); + SetModelData(kMemoryLeakFinderDiffStats, + kStatsText.arg(rmv::string_util::LocalizedValue(stats_in_diff_only_.num_resources)) + .arg(rmv::string_util::LocalizedValueMemory(stats_in_diff_only_.size, false, false))); + + uint32_t row_count = proxy_model_->rowCount(); + uint64_t total_size = 0; + + for (uint32_t i = 0; i < row_count; i++) + { + const uint64_t size = proxy_model_->GetData(i, kResourceColumnSize); + total_size += size; + } + + SetModelData(kMemoryLeakFinderTotalResources, rmv::string_util::LocalizedValue(row_count)); + SetModelData(kMemoryLeakFinderTotalSize, rmv::string_util::LocalizedValueMemory(total_size, false, false)); + } + + void MemoryLeakFinderModel::SearchBoxChanged(const QString& filter) + { + proxy_model_->SetSearchFilter(filter); + proxy_model_->invalidate(); + + UpdateLabels(); + } + + void MemoryLeakFinderModel::FilterBySizeChanged(int min_value, int max_value) + { + const uint64_t scaled_min = resource_thresholds_[min_value]; + const uint64_t scaled_max = resource_thresholds_[max_value]; + + proxy_model_->SetSizeFilter(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(); + } + + void MemoryLeakFinderModel::UpdateResourceUsageList(const QString& resource_usage_filter) + { + proxy_model_->SetResourceUsageFilter(resource_usage_filter); + proxy_model_->invalidate(); + } + + MemoryLeakFinderProxyModel* MemoryLeakFinderModel::GetResourceProxyModel() const + { + return proxy_model_; + } + + RmtSnapshotPoint* MemoryLeakFinderModel::LoadSnapshot(const QModelIndex& index) + { + const int compare_id = GetResourceProxyModel()->GetData(index.row(), kResourceColumnCompareId); + RmtDataSnapshot* snapshot = nullptr; + const TraceManager& trace_manager = TraceManager::Get(); + + // Use base snapshot if open or common to both. + if ((compare_id & kSnapshotCompareIdOpen) != 0 || (compare_id & kSnapshotCompareIdCommon) != 0) + { + snapshot = trace_manager.GetComparedSnapshot(kSnapshotCompareBase); + } + else + { + snapshot = trace_manager.GetComparedSnapshot(kSnapshotCompareDiff); + } + + // Open the snapshot if not already opened as a single snapshot. + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + if (snapshot != open_snapshot) + { + RmtSnapshotPoint* snapshot_point = snapshot->snapshot_point; + SnapshotManager::Get().SetSelectedSnapshotPoint(snapshot_point); + return snapshot_point; + } + return nullptr; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/compare/memory_leak_finder_model.h b/source/frontend/models/compare/memory_leak_finder_model.h new file mode 100644 index 0000000..fb75afb --- /dev/null +++ b/source/frontend/models/compare/memory_leak_finder_model.h @@ -0,0 +1,120 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Memory Leak Finder pane +//============================================================================= + +#ifndef RMV_MODELS_COMPARE_MEMORY_LEAK_FINDER_MODEL_H_ +#define RMV_MODELS_COMPARE_MEMORY_LEAK_FINDER_MODEL_H_ + +#include "qt_common/custom_widgets/scaled_table_view.h" +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_resource_list.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/proxy_models/memory_leak_finder_proxy_model.h" +#include "models/resource_item_model.h" +#include "util/constants.h" + +namespace rmv +{ + /// Enum containing the id's of UI elements needed by the model. + enum MemoryLeakFinderWidgets + { + kMemoryLeakFinderBaseStats, + kMemoryLeakFinderBothStats, + kMemoryLeakFinderDiffStats, + kMemoryLeakFinderTotalResources, + kMemoryLeakFinderTotalSize, + kMemoryLeakFinderBaseCheckbox, + kMemoryLeakFinderDiffCheckbox, + kMemoryLeakFinderBaseSnapshot, + kMemoryLeakFinderDiffSnapshot, + + kMemoryLeakFinderNumWidgets, + }; + + /// Container class that holds model data for a given pane. + class MemoryLeakFinderModel : public ModelViewMapper + { + public: + /// Constructor. + explicit MemoryLeakFinderModel(); + + /// Destructor. + virtual ~MemoryLeakFinderModel(); + + /// Initialize the table model. + /// \param table_view The view to the table. + /// \param num_rows Total rows of the table. + /// \param num_columns Total columns of the table. + /// \param compare_id_filter starting filter. + void InitializeTableModel(ScaledTableView* table_view, uint num_rows, uint num_columns, uint32_t compare_id_filter); + + /// Update the model. + /// \param compare_filter The compare filter ID, to indicate which resources are to be displayed. + void Update(SnapshotCompareId compare_filter); + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Handle what happens when user changes the 'filter by size' slider. + /// \param filter The search text filter. + void SearchBoxChanged(const QString& filter); + + /// Handle what happens when the size filter changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void FilterBySizeChanged(int min_value, int max_value); + + /// Update the list of heaps selected. This is set up from the preferred heap combo box. + /// \param preferred_heap_filter The regular expression string of selected heaps. + void UpdatePreferredHeapList(const QString& preferred_heap_filter); + + /// Update the list of resources available. This is set up from the resource usage combo box. + /// \param resource_usage_filter The regular expression string of selected resource usage types. + void UpdateResourceUsageList(const QString& resource_usage_filter); + + /// Get the resource proxy model. Used to set up a connection between the table being sorted and the UI update. + /// \return the proxy model. + MemoryLeakFinderProxyModel* GetResourceProxyModel() const; + + /// Figure out which snapshot the selected table entry is from and load the snapshot + /// if it's not already loaded. It will be in memory already but just needs assigning + /// to be the snapshot that is visible in the snapshot tab. + /// \param index The model index for the entry selected in the memory leak resource table. + RmtSnapshotPoint* LoadSnapshot(const QModelIndex& index); + + private: + /// Update the resource size buckets. This is used by the double-slider to + /// group the resource sizes. Called whenever the table data changes. + void UpdateResourceThresholds(); + + /// Update labels at the bottom. + void UpdateLabels(); + + /// Reset the snapshot stats. + void ResetStats(); + + ResourceItemModel* table_model_; ///< The data for the resource table. + MemoryLeakFinderProxyModel* proxy_model_; ///< The proxy model for the resource table. + uint64_t resource_thresholds_[kSizeSliderRange + 1]; ///< List of resource size thresholds for the filter by size sliders. + + // struct to describe the snapshot statistics + struct SnapshotStats + { + uint32_t num_resources; + uint64_t size; + }; + + SnapshotStats stats_in_both_; ///< Attributes in both snapshots. + SnapshotStats stats_in_base_only_; ///< Attributes in the base snapshot only. + SnapshotStats stats_in_diff_only_; ///< Attributes in the diff snapshot only. + }; +} // namespace rmv + +#endif // RMV_MODELS_COMPARE_MEMORY_LEAK_FINDER_MODEL_H_ diff --git a/source/frontend/models/compare/snapshot_delta_model.cpp b/source/frontend/models/compare/snapshot_delta_model.cpp new file mode 100644 index 0000000..e287f6d --- /dev/null +++ b/source/frontend/models/compare/snapshot_delta_model.cpp @@ -0,0 +1,147 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for Heap Delta pane +//============================================================================= + +#include "models/compare/snapshot_delta_model.h" + +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" + +#include "models/trace_manager.h" +#include "views/custom_widgets/rmv_carousel.h" + +namespace rmv +{ + SnapshotDeltaModel::SnapshotDeltaModel() + : ModelViewMapper(kHeapDeltaCompareNumWidgets) + , base_index_(kSnapshotCompareBase) + , diff_index_(kSnapshotCompareDiff) + , base_snapshot_(nullptr) + , diff_snapshot_(nullptr) + { + } + + SnapshotDeltaModel::~SnapshotDeltaModel() + { + } + + void SnapshotDeltaModel::ResetModelValues() + { + SetModelData(kHeapDeltaCompareBaseName, "-"); + SetModelData(kHeapDeltaCompareDiffName, "-"); + + base_index_ = kSnapshotCompareBase; + diff_index_ = kSnapshotCompareDiff; + } + + bool SnapshotDeltaModel::Update() + { + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return false; + } + + base_snapshot_ = trace_manager.GetComparedSnapshot(base_index_); + diff_snapshot_ = trace_manager.GetComparedSnapshot(diff_index_); + + if (base_snapshot_ == nullptr || diff_snapshot_ == nullptr) + { + return false; + } + + SetModelData(kHeapDeltaCompareBaseName, trace_manager.GetCompareSnapshotName(base_index_)); + SetModelData(kHeapDeltaCompareDiffName, trace_manager.GetCompareSnapshotName(diff_index_)); + + return true; + } + + bool SnapshotDeltaModel::SwapSnapshots() + { + int temp = diff_index_; + diff_index_ = base_index_; + base_index_ = temp; + + return Update(); + } + + void SnapshotDeltaModel::UpdateCarousel(RMVCarousel* carousel) + { + carousel->UpdateModel(base_snapshot_, diff_snapshot_); + } + + const char* SnapshotDeltaModel::GetHeapName(int32_t heap_index) + { + return RmtGetHeapTypeNameFromHeapType((RmtHeapType)heap_index); + } + + bool SnapshotDeltaModel::GetHeapDelta(RmtDataSnapshot* snapshot, RmtHeapType heap_type, HeapDeltaData& delta_data) + { + bool success = false; + + if (TraceManager::Get().DataSetValid()) + { + if (snapshot != nullptr) + { + success = true; + + delta_data = {}; + uint64_t total_allocated = 0; + + for (int32_t currentVirtualAllocationIndex = 0; currentVirtualAllocationIndex < snapshot->virtual_allocation_list.allocation_count; + currentVirtualAllocationIndex++) + { + const RmtVirtualAllocation* virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[currentVirtualAllocationIndex]; + + if (virtual_allocation->heap_preferences[0] == heap_type) + { + total_allocated += RmtVirtualAllocationGetSizeInBytes(virtual_allocation); + + // accumulate in here + delta_data.allocation_count++; + delta_data.resource_count += virtual_allocation->resource_count; + delta_data.total_allocated_and_bound += RmtVirtualAllocationGetTotalResourceMemoryInBytes(snapshot, virtual_allocation); + delta_data.total_allocated_and_unbound += RmtVirtualAllocationGetTotalUnboundSpaceInAllocation(snapshot, virtual_allocation); + } + } + + delta_data.total_available_size = 0; + delta_data.free_space = 0; + } + } + + return success; + } + + bool SnapshotDeltaModel::CalcPerHeapDelta(RmtHeapType heap_type, HeapDeltaData& out_delta_data) + { + if (TraceManager::Get().DataSetValid()) + { + if (base_snapshot_ != nullptr && diff_snapshot_ != nullptr) + { + HeapDeltaData base_data = {}; + bool base_success = GetHeapDelta(base_snapshot_, heap_type, base_data); + + HeapDeltaData diff_data = {}; + bool dff_success = GetHeapDelta(diff_snapshot_, heap_type, diff_data); + + if (base_success && dff_success) + { + out_delta_data.total_available_size = base_data.total_available_size; + out_delta_data.total_allocated_and_bound = diff_data.total_allocated_and_bound - base_data.total_allocated_and_bound; + out_delta_data.total_allocated_and_unbound = diff_data.total_allocated_and_unbound - base_data.total_allocated_and_unbound; + out_delta_data.free_space = diff_data.free_space - base_data.free_space; + out_delta_data.resource_count = diff_data.resource_count - base_data.resource_count; + out_delta_data.allocation_count = diff_data.allocation_count - base_data.allocation_count; + return true; + } + } + } + + return false; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/compare/snapshot_delta_model.h b/source/frontend/models/compare/snapshot_delta_model.h new file mode 100644 index 0000000..11c4239 --- /dev/null +++ b/source/frontend/models/compare/snapshot_delta_model.h @@ -0,0 +1,90 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Heap Delta pane +//============================================================================= + +#ifndef RMV_MODELS_COMPARE_SNAPSHOT_DELTA_MODEL_H_ +#define RMV_MODELS_COMPARE_SNAPSHOT_DELTA_MODEL_H_ + +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_data_snapshot.h" +#include "rmt_types.h" + +#include "views/custom_widgets/rmv_carousel.h" + +namespace rmv +{ + /// Enum containing the id's of UI elements needed by the model. + enum HeapDeltaWidgets + { + kHeapDeltaCompareBaseName, + kHeapDeltaCompareDiffName, + + kHeapDeltaCompareNumWidgets, + }; + + /// Container class that holds model data for a given pane. + class SnapshotDeltaModel : public ModelViewMapper + { + public: + /// Contains aggregated delta data for a heap. + struct HeapDeltaData + { + int64_t total_available_size; ///< Full size of heap. + int64_t total_allocated_and_bound; ///< Allocated memory amount. + int64_t total_allocated_and_unbound; ///< Allocated but unused memory amount. + int64_t free_space; ///< Amount of free space. + int32_t resource_count; ///< Number of resources. + int32_t allocation_count; ///< Number of allocations. + }; + + /// Constructor. + explicit SnapshotDeltaModel(); + + /// Destructor. + virtual ~SnapshotDeltaModel(); + + /// Update the model. + /// \return true if the model was updated successfully, false otherwise. + bool Update(); + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Swap the snapshots. + /// \return true if the snapshots were swapped successfully, false otherwise. + bool SwapSnapshots(); + + /// Update the carousel model. + /// \param carousel The carousel to update. + void UpdateCarousel(RMVCarousel* carousel); + + /// Get the heap name from the heap index. + /// \param heap_index The heap index. + const char* GetHeapName(int32_t heap_index); + + /// Compute heap delta between snapshots. + /// \param heap_type The target heap type. + /// \param out_delta_data Structure to hold the heap data from the model. + /// \return true if data returned successfully, false otherwise. + bool CalcPerHeapDelta(RmtHeapType heap_type, HeapDeltaData& out_delta_data); + + private: + /// Compute delta between snapshots. + /// \param snapshot The target snapshot. + /// \param heap_type The target heap type. + /// \param delta_Data Structure to hold the heap data from the model. + /// \return true if data returned successfully, false otherwise. + bool GetHeapDelta(RmtDataSnapshot* snapshot, RmtHeapType heap_type, HeapDeltaData& delta_data); + + int base_index_; ///< The index of the base snapshot. + int diff_index_; ///< The index of the diff snapshot. + RmtDataSnapshot* base_snapshot_; ///< The base snapshot. + RmtDataSnapshot* diff_snapshot_; ///< The diff snapshot. + }; +} // namespace rmv + +#endif // RMV_MODELS_COMPARE_SNAPSHOT_DELTA_MODEL_H_ diff --git a/source/frontend/models/heap_combo_box_model.cpp b/source/frontend/models/heap_combo_box_model.cpp new file mode 100644 index 0000000..8c84ab0 --- /dev/null +++ b/source/frontend/models/heap_combo_box_model.cpp @@ -0,0 +1,87 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a model corresponding to a heap combo box. +//============================================================================= + +#include "models/heap_combo_box_model.h" + +#include + +#include "rmt_print.h" +#include "rmt_assert.h" + +namespace rmv +{ + HeapComboBoxModel::HeapComboBoxModel() + : ComboBoxModel() + { + } + + HeapComboBoxModel::~HeapComboBoxModel() + { + } + + void HeapComboBoxModel::SetupHeapComboBox(ArrowIconComboBox* combo_box) + { + combo_box->ClearItems(); + + // Add the "All" entry to the combo box + QCheckBox* checkbox = combo_box->AddCheckboxItem("All", QVariant(), false, true); + RMT_ASSERT(checkbox != nullptr); + if (checkbox != nullptr) + { + connect(checkbox, &QCheckBox::clicked, this, [=]() { emit FilterChanged(true); }); + } + + for (uint32_t i = 0; i < (uint32_t)kRmtHeapTypeCount; i++) + { + checkbox = combo_box->AddCheckboxItem(RmtGetHeapTypeNameFromHeapType(RmtHeapType(i)), QVariant(), false, false); + RMT_ASSERT(checkbox != nullptr); + if (checkbox != nullptr) + { + connect(checkbox, &QCheckBox::clicked, this, [=]() { emit FilterChanged(true); }); + } + } + ResetHeapComboBox(combo_box); + } + + void HeapComboBoxModel::ResetHeapComboBox(ArrowIconComboBox* combo_box) + { + // index 0 is the "all" combo box entry + int combo_box_row = 0; + combo_box->SetChecked(combo_box_row, true); + combo_box_row++; + + for (uint32_t i = 0; i < (uint32_t)kRmtHeapTypeCount; i++) + { + combo_box->SetChecked(combo_box_row, true); + combo_box_row++; + } + SetupState(combo_box); + } + + QString HeapComboBoxModel::GetFilterString(const ArrowIconComboBox* combo_box) + { + SetupState(combo_box); + + // build the heap filter + QString heap_filter = QString("(-|ORPHANED"); + for (int heap = 0; heap < kRmtHeapTypeCount; heap++) + { + if (ItemInList(heap) == true) + { + heap_filter += "|" + QString(RmtGetHeapTypeNameFromHeapType((RmtHeapType)heap)); + } + } + heap_filter += ")"; + + return heap_filter; + } + + void HeapComboBoxModel::SetupState(const ArrowIconComboBox* combo_box) + { + ComboBoxModel::SetupState(combo_box, true); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/heap_combo_box_model.h b/source/frontend/models/heap_combo_box_model.h new file mode 100644 index 0000000..342b718 --- /dev/null +++ b/source/frontend/models/heap_combo_box_model.h @@ -0,0 +1,49 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a model corresponding to a heap combo box. +//============================================================================= + +#ifndef RMV_MODELS_HEAP_COMBO_BOX_MODEL_H_ +#define RMV_MODELS_HEAP_COMBO_BOX_MODEL_H_ + +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" + +#include "combo_box_model.h" + +namespace rmv +{ + /// Model encapsulating everything needed for a heap combo box. + class HeapComboBoxModel : public ComboBoxModel + { + Q_OBJECT + + public: + /// Constructor. + HeapComboBoxModel(); + + /// Destructor. + ~HeapComboBoxModel(); + + /// Set up the heap combo box taking into account any resources that are to be ignored. + void SetupHeapComboBox(ArrowIconComboBox* combo_box); + + /// Reset the heap combo box to its default values. + void ResetHeapComboBox(ArrowIconComboBox* combo_box); + + /// Get the Filter string for the regular expression to be used when filtering a resource list table by heap. + QString GetFilterString(const ArrowIconComboBox* combo_box); + + /// Check the state of the combo box and setup the internal state representation + /// of the ArrowIconComboBox. + /// \param combo_box Pointer to the combo box whose state is to be examined. + void SetupState(const ArrowIconComboBox* combo_box); + + signals: + /// signal emitted when a combo box item is changed. + void FilterChanged(bool checked); + }; +} // namespace rmv + +#endif // RMV_MODELS_HEAP_COMBO_BOX_MODEL_H_ diff --git a/source/frontend/models/message_manager.cpp b/source/frontend/models/message_manager.cpp new file mode 100644 index 0000000..b8f3a20 --- /dev/null +++ b/source/frontend/models/message_manager.cpp @@ -0,0 +1,17 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the MessageManager class. Basically a bunch of +/// signals that other UI elements can observe and react to +//============================================================================= + +#include "models/message_manager.h" + +// single instance of the Message Manager. +static MessageManager message_manager; + +MessageManager& MessageManager::Get() +{ + return message_manager; +} diff --git a/source/frontend/models/message_manager.h b/source/frontend/models/message_manager.h new file mode 100644 index 0000000..8c8504c --- /dev/null +++ b/source/frontend/models/message_manager.h @@ -0,0 +1,78 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Declaration for the MessageManager +//============================================================================= + +#ifndef RMV_MODELS_MESSAGE_MANAGER_H_ +#define RMV_MODELS_MESSAGE_MANAGER_H_ + +#include + +#include "rmt_data_set.h" +#include "rmt_resource_list.h" + +#include "views/pane_manager.h" + +/// Class that allows communication between any custom QObjects. +class MessageManager : public QObject +{ + Q_OBJECT + +public: + /// Accessor for singleton instance. + /// \return A reference to the message manager. + static MessageManager& Get(); + +signals: + /// Signal to open a RMV trace. + /// \param trace_file The name of the trace file to open. + void OpenTrace(QString trace_file); + + /// Signal for when a snapshot was opened. + /// \param snapshot_point The snapshot point of the snapshot to open. + void OpenSnapshot(RmtSnapshotPoint* snapshot_point); + + /// Signal for when a snapshot was to be compared. + /// \param snapshot_base The snapshot point of the first snapshot to open. + /// \param snapshot_diff The snapshot point of the second snapshot to open. + void CompareSnapshot(RmtSnapshotPoint* snapshot_base, RmtSnapshotPoint* snapshot_diff); + + /// Signal for when a snapshot was clicked. + /// \param snapshot_point The snapshot point of the snapshot selected. + void SelectSnapshot(RmtSnapshotPoint* snapshot_point); + + /// Signal a resource was selected. + /// \param resource_identifier The resource identifier of the resource selected. + void ResourceSelected(RmtResourceIdentifier resource_identifier); + + /// Signal an unbound resource was selected (pass its allocation). + /// \param allocation The allocation containing the unbound resource selected. + void UnboundResourceSelected(const RmtVirtualAllocation* allocation); + + /// Signal a new snapshot point was created. + /// \param snapshot_point The snapshot point of the snapshot added. + void SnapshotAdded(RmtSnapshotPoint* snapshot_point); + + /// Signal a snapshot point was renamed. + /// \param snapshot_point The snapshot point of the snapshot renamed. + void SnapshotRenamed(RmtSnapshotPoint* snapshot_point); + + /// Signal a snapshot point was deleted. + /// \param snapshot_point The snapshot point of the snapshot deleted. + void SnapshotDeleted(RmtSnapshotPoint* snapshot_point); + + /// Signal to navigate to a specific pane. + /// \param pane The pane to navigate to. + void NavigateToPane(rmv::RMVPane pane); + + /// Signal to navigate to a specific pane without going through nav manager. + /// \param pane The pane to navigate to. + void NavigateToPaneUnrecorded(rmv::RMVPane pane); + + /// Signal for when the hash values changed. + void UpdateHashes(); +}; + +#endif // RMV_MODELS_MESSAGE_MANAGER_H_ diff --git a/source/frontend/models/proxy_models/allocation_proxy_model.cpp b/source/frontend/models/proxy_models/allocation_proxy_model.cpp new file mode 100644 index 0000000..4c1960a --- /dev/null +++ b/source/frontend/models/proxy_models/allocation_proxy_model.cpp @@ -0,0 +1,149 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a proxy filter that processes multiple columns +/// of the allocation table. +//============================================================================= + +#include "models/proxy_models/allocation_proxy_model.h" + +#include + +#include "rmt_assert.h" + +#include "models/allocation_item_model.h" +#include "models/snapshot/allocation_explorer_model.h" + +namespace rmv +{ + AllocationProxyModel::AllocationProxyModel(QObject* parent) + : TableProxyModel(parent) + { + } + + AllocationProxyModel::~AllocationProxyModel() + { + } + + AllocationItemModel* AllocationProxyModel::InitializeAllocationTableModels(QTableView* table_view, int num_rows, int num_columns) + { + AllocationItemModel* model = new AllocationItemModel(); + model->SetRowCount(num_rows); + model->SetColumnCount(num_columns); + + setSourceModel(model); + SetFilterKeyColumns({kVirtualAllocationColumnId, + kVirtualAllocationColumnAllocationSize, + kVirtualAllocationColumnBound, + kVirtualAllocationColumnUnbound, + kVirtualAllocationColumnAverageResourceSize, + kVirtualAllocationColumnResourceSizeStdDev, + kVirtualAllocationColumnResourceCount, + kVirtualAllocationColumnPreferredHeapName, + kVirtualAllocationColumnInvisiblePercentage, + kVirtualAllocationColumnLocalPercentage, + kVirtualAllocationColumnSystemPercentage, + kVirtualAllocationColumnUnmappedPercentage}); + + table_view->setModel(this); + return model; + } + + bool AllocationProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const + { + if (FilterSizeSlider(source_row, kVirtualAllocationColumnAllocationSize, source_parent) == false) + { + return false; + } + + if (FilterSearchString(source_row, source_parent) == false) + { + return false; + } + + return true; + } + + bool AllocationProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const + { + if ((left.column() == kVirtualAllocationColumnId && right.column() == kVirtualAllocationColumnId)) + { + const qulonglong addr_left = left.data().toULongLong(); + const qulonglong addr_right = right.data().toULongLong(); + + return addr_left < addr_right; + } + else if ((left.column() == kVirtualAllocationColumnAllocationSize && right.column() == kVirtualAllocationColumnAllocationSize)) + { + const qulonglong size_left = left.data(Qt::UserRole).toULongLong(); + const qulonglong size_right = right.data(Qt::UserRole).toULongLong(); + + return size_left < size_right; + } + else if ((left.column() == kVirtualAllocationColumnBound && right.column() == kVirtualAllocationColumnBound)) + { + const qulonglong bound_left = left.data(Qt::UserRole).toULongLong(); + const qulonglong bound_right = right.data(Qt::UserRole).toULongLong(); + + return bound_left < bound_right; + } + else if ((left.column() == kVirtualAllocationColumnUnbound && right.column() == kVirtualAllocationColumnUnbound)) + { + const qulonglong unbound_left = left.data(Qt::UserRole).toULongLong(); + const qulonglong unbound_right = right.data(Qt::UserRole).toULongLong(); + + return unbound_left < unbound_right; + } + else if ((left.column() == kVirtualAllocationColumnAverageResourceSize && right.column() == kVirtualAllocationColumnAverageResourceSize)) + { + const qulonglong size_left = left.data(Qt::UserRole).toULongLong(); + const qulonglong size_right = right.data(Qt::UserRole).toULongLong(); + + return size_left < size_right; + } + else if ((left.column() == kVirtualAllocationColumnResourceSizeStdDev && right.column() == kVirtualAllocationColumnResourceSizeStdDev)) + { + const qulonglong std_dev_left = left.data(Qt::UserRole).toULongLong(); + const qulonglong std_dev_right = right.data(Qt::UserRole).toULongLong(); + + return std_dev_left < std_dev_right; + } + else if ((left.column() == kVirtualAllocationColumnResourceCount && right.column() == kVirtualAllocationColumnResourceCount)) + { + const int resource_count_left = left.data(Qt::UserRole).toInt(); + const int resource_count_right = right.data(Qt::UserRole).toInt(); + + return resource_count_left < resource_count_right; + } + else if ((left.column() == kVirtualAllocationColumnInvisiblePercentage && right.column() == kVirtualAllocationColumnInvisiblePercentage)) + { + const int resource_count_left = left.data(Qt::UserRole).toULongLong(); + const int resource_count_right = right.data(Qt::UserRole).toULongLong(); + + return resource_count_left < resource_count_right; + } + else if ((left.column() == kVirtualAllocationColumnLocalPercentage && right.column() == kVirtualAllocationColumnLocalPercentage)) + { + const int resource_count_left = left.data(Qt::UserRole).toULongLong(); + const int resource_count_right = right.data(Qt::UserRole).toULongLong(); + + return resource_count_left < resource_count_right; + } + else if ((left.column() == kVirtualAllocationColumnSystemPercentage && right.column() == kVirtualAllocationColumnSystemPercentage)) + { + const int resource_count_left = left.data(Qt::UserRole).toULongLong(); + const int resource_count_right = right.data(Qt::UserRole).toULongLong(); + + return resource_count_left < resource_count_right; + } + else if ((left.column() == kVirtualAllocationColumnUnmappedPercentage && right.column() == kVirtualAllocationColumnUnmappedPercentage)) + { + const int resource_count_left = left.data(Qt::UserRole).toULongLong(); + const int resource_count_right = right.data(Qt::UserRole).toULongLong(); + + return resource_count_left < resource_count_right; + } + return QSortFilterProxyModel::lessThan(left, right); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/proxy_models/allocation_proxy_model.h b/source/frontend/models/proxy_models/allocation_proxy_model.h new file mode 100644 index 0000000..f43799a --- /dev/null +++ b/source/frontend/models/proxy_models/allocation_proxy_model.h @@ -0,0 +1,54 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a proxy filter that processes multiple columns +/// of the allocation table. +//============================================================================= + +#ifndef RMV_MODELS_PROXY_MODELS_ALLOCATION_PROXY_MODEL_H_ +#define RMV_MODELS_PROXY_MODELS_ALLOCATION_PROXY_MODEL_H_ + +#include + +#include "models/allocation_item_model.h" +#include "models/proxy_models/table_proxy_model.h" + +namespace rmv +{ + /// Class to filter out and sort an allocation table. + class AllocationProxyModel : public TableProxyModel + { + Q_OBJECT + + public: + /// Constructor. + /// \param parent The parent widget. + explicit AllocationProxyModel(QObject* parent = nullptr); + + /// Destructor. + virtual ~AllocationProxyModel(); + + /// Initialize the allocation table model. + /// \param table_view table view. + /// \param num_rows row count. + /// \param num_columns column count. + /// \return the model for the allocation table model. + AllocationItemModel* InitializeAllocationTableModels(QTableView* table_view, int num_rows, int num_columns); + + protected: + /// Make the filter run across multiple columns. + /// \param source_row the target row. + /// \param source_parent the source parent. + /// \return pass or not. + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + + /// The sort comparator. + /// \param left the left item to compare. + /// \param right the right item to compare. + /// \return true if left is less than right, false otherwise. + virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + }; +} // namespace rmv + +#endif // RMV_MODELS_PROXY_MODELS_ALLOCATION_PROXY_MODEL_H_ diff --git a/source/frontend/models/proxy_models/memory_leak_finder_proxy_model.cpp b/source/frontend/models/proxy_models/memory_leak_finder_proxy_model.cpp new file mode 100644 index 0000000..7a24c8c --- /dev/null +++ b/source/frontend/models/proxy_models/memory_leak_finder_proxy_model.cpp @@ -0,0 +1,49 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a proxy filter that processes multiple columns. +//============================================================================= + +#include "models/proxy_models/memory_leak_finder_proxy_model.h" + +#include "rmt_assert.h" + +#include "models/resource_item_model.h" + +namespace rmv +{ + MemoryLeakFinderProxyModel::MemoryLeakFinderProxyModel(uint32_t compare_id_filter, QObject* parent) + : ResourceProxyModel(parent) + , compare_id_filter_(compare_id_filter) + { + } + + MemoryLeakFinderProxyModel::~MemoryLeakFinderProxyModel() + { + } + + void MemoryLeakFinderProxyModel::UpdateCompareFilter(SnapshotCompareId compare_filter) + { + compare_id_filter_ = compare_filter; + } + + bool MemoryLeakFinderProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const + { + bool pass = ResourceProxyModel::filterAcceptsRow(source_row, source_parent); + + if (pass) + { + QModelIndex index = sourceModel()->index(source_row, kResourceColumnCompareId, source_parent); + + QString index_data = index.data().toString(); + + bool ok = false; + const uint32_t compare_id = index_data.toULong(&ok, 0); + + pass = ((compare_id_filter_ & compare_id) != 0U); + } + + return pass; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/proxy_models/memory_leak_finder_proxy_model.h b/source/frontend/models/proxy_models/memory_leak_finder_proxy_model.h new file mode 100644 index 0000000..4852e60 --- /dev/null +++ b/source/frontend/models/proxy_models/memory_leak_finder_proxy_model.h @@ -0,0 +1,44 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a proxy filter that processes multiple columns. +//============================================================================= + +#ifndef RMV_MODELS_PROXY_MODELS_MEMORY_LEAK_FINDER_PROXY_MODEL_H_ +#define RMV_MODELS_PROXY_MODELS_MEMORY_LEAK_FINDER_PROXY_MODEL_H_ + +#include "models/proxy_models/resource_proxy_model.h" + +namespace rmv +{ + /// Class to filter out and sort the memory leak table. + class MemoryLeakFinderProxyModel : public ResourceProxyModel + { + Q_OBJECT + + public: + /// Constructor. + /// \param compare_id_filter The compare filter. + /// \param parent The parent widget. + explicit MemoryLeakFinderProxyModel(uint32_t compare_id_filter, QObject* parent = nullptr); + + /// Destructor. + virtual ~MemoryLeakFinderProxyModel(); + + /// Update the filter. + /// \param compare_filter The new filter. + void UpdateCompareFilter(SnapshotCompareId compare_filter); + + protected: + /// Make the filter run across multiple columns. + /// \param source_row The target row. + /// \param source_parent The source parent. + /// \return true if the row passed the filter, false if not. + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + + uint32_t compare_id_filter_; ///< Filtering flags specified in the UI. + }; +} // namespace rmv + +#endif // RMV_MODELS_PROXY_MODELS_MEMORY_LEAK_FINDER_PROXY_MODEL_H_ diff --git a/source/frontend/models/proxy_models/resource_details_proxy_model.cpp b/source/frontend/models/proxy_models/resource_details_proxy_model.cpp new file mode 100644 index 0000000..40802d6 --- /dev/null +++ b/source/frontend/models/proxy_models/resource_details_proxy_model.cpp @@ -0,0 +1,69 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a proxy filter that processes the resource details +/// table in the resource details pane. +//============================================================================= + +#include "models/proxy_models/resource_details_proxy_model.h" + +#include + +#include "rmt_assert.h" + +#include "models/snapshot/allocation_explorer_model.h" +#include "models/snapshot/resource_timeline_item_model.h" + +namespace rmv +{ + ResourceDetailsProxyModel::ResourceDetailsProxyModel(QObject* parent) + : TableProxyModel(parent) + { + } + + ResourceDetailsProxyModel::~ResourceDetailsProxyModel() + { + } + + ResourceTimelineItemModel* ResourceDetailsProxyModel::InitializeResourceTableModels(QTableView* view, int num_rows, int num_columns) + { + ResourceTimelineItemModel* model = new ResourceTimelineItemModel(); + model->SetRowCount(num_rows); + model->SetColumnCount(num_columns); + + setSourceModel(model); + SetFilterKeyColumns({kResourceHistoryTime, kResourceHistoryEvent}); + + view->setModel(this); + + return model; + } + + bool ResourceDetailsProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const + { + Q_UNUSED(source_row); + Q_UNUSED(source_parent); + bool pass = true; + return pass; + } + + bool ResourceDetailsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const + { + if ((left.column() == kResourceHistoryTime && right.column() == kResourceHistoryTime)) + { + const double left_data = left.data(Qt::UserRole).toULongLong(); + const double right_data = right.data(Qt::UserRole).toULongLong(); + return left_data < right_data; + } + + if ((left.column() == kResourceHistoryEvent && right.column() == kResourceHistoryEvent)) + { + QModelIndex left_data = sourceModel()->index(left.row(), kResourceHistoryEvent, QModelIndex()); + QModelIndex right_data = sourceModel()->index(right.row(), kResourceHistoryEvent, QModelIndex()); + return QString::localeAwareCompare(left_data.data().toString(), right_data.data().toString()) < 0; + } + + return QSortFilterProxyModel::lessThan(left, right); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/proxy_models/resource_details_proxy_model.h b/source/frontend/models/proxy_models/resource_details_proxy_model.h new file mode 100644 index 0000000..fcb6bd0 --- /dev/null +++ b/source/frontend/models/proxy_models/resource_details_proxy_model.h @@ -0,0 +1,54 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a proxy filter that processes the resource details table +/// in the resource details pane. +//============================================================================= + +#ifndef RMV_MODELS_PROXY_MODELS_RESOURCE_DETAILS_PROXY_MODEL_H_ +#define RMV_MODELS_PROXY_MODELS_RESOURCE_DETAILS_PROXY_MODEL_H_ + +#include + +#include "models/proxy_models/table_proxy_model.h" +#include "models/snapshot/resource_timeline_item_model.h" + +namespace rmv +{ + /// Class to filter out and sort the resource details table. + class ResourceDetailsProxyModel : public TableProxyModel + { + Q_OBJECT + + public: + /// Constructor. + /// \param parent The parent widget. + explicit ResourceDetailsProxyModel(QObject* parent = nullptr); + + /// Destructor. + virtual ~ResourceDetailsProxyModel(); + + /// Initialize the resource table model. + /// \param view The table view. + /// \param num_rows row count. + /// \param num_columns column count. + /// \return the model for the resource table model. + ResourceTimelineItemModel* InitializeResourceTableModels(QTableView* view, int num_rows, int num_columns); + + protected: + /// Make the filter run across multiple columns. + /// \param source_row the target row. + /// \param source_parent the source parent. + /// \return pass or not. + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + + /// The sort comparator. + /// \param left the left item to compare. + /// \param right the right item to compare. + /// \return true if left is less than right, false otherwise. + virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + }; +} // namespace rmv + +#endif // RMV_MODELS_PROXY_MODELS_RESOURCE_DETAILS_PROXY_MODEL_H_ diff --git a/source/frontend/models/proxy_models/resource_proxy_model.cpp b/source/frontend/models/proxy_models/resource_proxy_model.cpp new file mode 100644 index 0000000..890ae3d --- /dev/null +++ b/source/frontend/models/proxy_models/resource_proxy_model.cpp @@ -0,0 +1,142 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a proxy filter that processes multiple columns. +//============================================================================= + +#include "models/proxy_models/resource_proxy_model.h" + +#include + +#include "rmt_assert.h" + +#include "models/resource_item_model.h" +#include "models/snapshot/allocation_explorer_model.h" + +namespace rmv +{ + ResourceProxyModel::ResourceProxyModel(QObject* parent) + : TableProxyModel(parent) + { + } + + ResourceProxyModel::~ResourceProxyModel() + { + } + + ResourceItemModel* ResourceProxyModel::InitializeResourceTableModels(QTableView* view, int num_rows, int num_columns) + { + ResourceItemModel* model = new ResourceItemModel(); + model->SetRowCount(num_rows); + model->SetColumnCount(num_columns); + + setSourceModel(model); + SetFilterKeyColumns({kResourceColumnName, + kResourceColumnVirtualAddress, + kResourceColumnSize, + kResourceColumnMappedInvisible, + kResourceColumnMappedLocal, + kResourceColumnMappedHost, + kResourceColumnMappedNone, + kResourceColumnPreferredHeap, + kResourceColumnUsage}); + + view->setModel(this); + return model; + } + + void ResourceProxyModel::SetPreferredHeapFilter(const QString& preferred_heap_filter) + { + preferred_heap_filter_ = QRegularExpression(preferred_heap_filter, QRegularExpression::CaseInsensitiveOption); + } + + void ResourceProxyModel::SetResourceUsageFilter(const QString& resource_usage_filter) + { + resource_usage_filter_ = QRegularExpression(resource_usage_filter, QRegularExpression::CaseInsensitiveOption); + } + + bool ResourceProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const + { + if (FilterSizeSlider(source_row, kResourceColumnSize, source_parent) == false) + { + return false; + } + + if (FilterSearchString(source_row, source_parent) == false) + { + return false; + } + + // Apply the preferred heap filter. + const QModelIndex& preferred_heap_index = sourceModel()->index(source_row, kResourceColumnPreferredHeap, source_parent); + if (preferred_heap_index.column() == kResourceColumnPreferredHeap) + { + QString index_data = preferred_heap_index.data().toString(); + if (preferred_heap_filter_.match(index_data).hasMatch() == false) + { + return false; + } + } + + // Apply the resource usage filter. + const QModelIndex& resource_usage_index = sourceModel()->index(source_row, kResourceColumnUsage, source_parent); + if (resource_usage_index.column() == kResourceColumnUsage) + { + QString index_data = resource_usage_index.data().toString(); + if (resource_usage_filter_.match(index_data).hasMatch() == false) + { + return false; + } + } + + return true; + } + + bool ResourceProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const + { + if ((left.column() == kResourceColumnVirtualAddress && right.column() == kResourceColumnVirtualAddress)) + { + const qulonglong left_data = left.data(Qt::UserRole).toULongLong(); + const qulonglong right_data = right.data(Qt::UserRole).toULongLong(); + + return left_data < right_data; + } + else if ((left.column() == kResourceColumnSize && right.column() == kResourceColumnSize)) + { + const qulonglong left_data = left.data(Qt::UserRole).toULongLong(); + const qulonglong right_data = right.data(Qt::UserRole).toULongLong(); + + return left_data < right_data; + } + else if ((left.column() == kResourceColumnMappedInvisible && right.column() == kResourceColumnMappedInvisible)) + { + const double left_data = left.data(Qt::UserRole).toDouble(); + const double right_data = right.data(Qt::UserRole).toDouble(); + + return left_data < right_data; + } + else if ((left.column() == kResourceColumnMappedLocal && right.column() == kResourceColumnMappedLocal)) + { + const double left_data = left.data(Qt::UserRole).toDouble(); + const double right_data = right.data(Qt::UserRole).toDouble(); + + return left_data < right_data; + } + else if ((left.column() == kResourceColumnMappedHost && right.column() == kResourceColumnMappedHost)) + { + const double left_data = left.data(Qt::UserRole).toDouble(); + const double right_data = right.data(Qt::UserRole).toDouble(); + + return left_data < right_data; + } + else if ((left.column() == kResourceColumnMappedNone && right.column() == kResourceColumnMappedNone)) + { + const double left_data = left.data(Qt::UserRole).toDouble(); + const double right_data = right.data(Qt::UserRole).toDouble(); + + return left_data < right_data; + } + return QSortFilterProxyModel::lessThan(left, right); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/proxy_models/resource_proxy_model.h b/source/frontend/models/proxy_models/resource_proxy_model.h new file mode 100644 index 0000000..601ac00 --- /dev/null +++ b/source/frontend/models/proxy_models/resource_proxy_model.h @@ -0,0 +1,69 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a proxy filter that processes multiple resource columns. +//============================================================================= + +#ifndef RMV_MODELS_PROXY_MODELS_RESOURCE_PROXY_MODEL_H_ +#define RMV_MODELS_PROXY_MODELS_RESOURCE_PROXY_MODEL_H_ + +#include + +#include "models/proxy_models/table_proxy_model.h" +#include "models/resource_item_model.h" + +namespace rmv +{ + /// Class to filter out and sort a resource table + class ResourceProxyModel : public TableProxyModel + { + Q_OBJECT + + public: + /// Constructor. + /// \param parent The parent widget. + explicit ResourceProxyModel(QObject* parent = nullptr); + + /// Destructor. + virtual ~ResourceProxyModel(); + + /// Initialize the resource table model. + /// \param view table view. + /// \param num_rows row count. + /// \param num_columns column count. + /// \return the model for the resource table model. + ResourceItemModel* InitializeResourceTableModels(QTableView* view, int num_rows, int num_columns); + + /// Set the preferred heap filter regular expression. Called when the user selects visible + /// heaps from the 'preferred heap' combo box. Rather than rebuild the table, this regular + /// expression is added to the filter to filter out heaps that don't need to be shown. + /// \param heap_filter The regular expression for the heap filter. + void SetPreferredHeapFilter(const QString& preferred_heap_filter); + + /// Set the preferred heap filter regular expression. Called when the user selects visible + /// heaps from the 'preferred heap' combo box. Rather than rebuild the table, this regular + /// expression is added to the filter to filter out heaps that don't need to be shown. + /// \param heap_filter The regular expression for the heap filter. + void SetResourceUsageFilter(const QString& resource_usage_filter); + + protected: + /// Make the filter run across multiple columns. + /// \param source_row the target row. + /// \param source_parent the source parent. + /// \return pass or not. + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + + /// The sort comparator. + /// \param left the left item to compare. + /// \param right the right item to compare. + /// \return true if left is less than right, false otherwise. + virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + + private: + QRegularExpression preferred_heap_filter_; ///< The preferred heap filter regular expression. + QRegularExpression resource_usage_filter_; ///< The resource usage filter regular expression. + }; +} // namespace rmv + +#endif // RMV_MODELS_PROXY_MODELS_RESOURCE_PROXY_MODEL_H_ diff --git a/source/frontend/models/proxy_models/snapshot_timeline_proxy_model.cpp b/source/frontend/models/proxy_models/snapshot_timeline_proxy_model.cpp new file mode 100644 index 0000000..5a7e34a --- /dev/null +++ b/source/frontend/models/proxy_models/snapshot_timeline_proxy_model.cpp @@ -0,0 +1,101 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a proxy filter that processes multiple columns. +//============================================================================= + +#include "models/proxy_models/snapshot_timeline_proxy_model.h" + +#include "rmt_assert.h" + +#include "models/timeline/snapshot_item_model.h" + +namespace rmv +{ + SnapshotTimelineProxyModel::SnapshotTimelineProxyModel(QObject* parent) + : TableProxyModel(parent) + { + } + + SnapshotTimelineProxyModel::~SnapshotTimelineProxyModel() + { + } + + bool SnapshotTimelineProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const + { + if (FilterSizeSlider(source_row, kSnapshotTimelineColumnResources, source_parent) == false) + { + return false; + } + + return FilterSearchString(source_row, source_parent); + } + + bool SnapshotTimelineProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const + { + if ((left.column() == kSnapshotTimelineColumnName && right.column() == kSnapshotTimelineColumnName)) + { + const QString left_data = sourceModel()->data(left).toString(); + const QString right_data = sourceModel()->data(right).toString(); + return QString::localeAwareCompare(left_data, right_data) < 0; + } + else if ((left.column() == kSnapshotTimelineColumnTime && right.column() == kSnapshotTimelineColumnTime)) + { + const double left_data = left.data(Qt::UserRole).toULongLong(); + const double right_data = right.data(Qt::UserRole).toULongLong(); + return left_data < right_data; + } + else if ((left.column() == kSnapshotTimelineColumnVirtualAllocations && right.column() == kSnapshotTimelineColumnVirtualAllocations)) + { + const double left_data = left.data(Qt::UserRole).toInt(); + const double right_data = right.data(Qt::UserRole).toInt(); + return left_data < right_data; + } + else if ((left.column() == kSnapshotTimelineColumnResources && right.column() == kSnapshotTimelineColumnResources)) + { + const double left_data = left.data(Qt::UserRole).toInt(); + const double right_data = right.data(Qt::UserRole).toInt(); + return left_data < right_data; + } + else if ((left.column() == kSnapshotTimelineColumnAllocatedTotalVirtualMemory && right.column() == kSnapshotTimelineColumnAllocatedTotalVirtualMemory)) + { + const double left_data = left.data(Qt::UserRole).toULongLong(); + const double right_data = right.data(Qt::UserRole).toULongLong(); + return left_data < right_data; + } + else if ((left.column() == kSnapshotTimelineColumnAllocatedBoundVirtualMemory && right.column() == kSnapshotTimelineColumnAllocatedBoundVirtualMemory)) + { + const double left_data = left.data(Qt::UserRole).toULongLong(); + const double right_data = right.data(Qt::UserRole).toULongLong(); + return left_data < right_data; + } + else if ((left.column() == kSnapshotTimelineColumnAllocatedUnboundVirtualMemory && + right.column() == kSnapshotTimelineColumnAllocatedUnboundVirtualMemory)) + { + const double left_data = left.data(Qt::UserRole).toULongLong(); + const double right_data = right.data(Qt::UserRole).toULongLong(); + return left_data < right_data; + } + else if ((left.column() == kSnapshotTimelineColumnCommittedLocal && right.column() == kSnapshotTimelineColumnCommittedLocal)) + { + const double left_data = left.data(Qt::UserRole).toULongLong(); + const double right_data = right.data(Qt::UserRole).toULongLong(); + return left_data < right_data; + } + else if ((left.column() == kSnapshotTimelineColumnCommittedInvisible && right.column() == kSnapshotTimelineColumnCommittedInvisible)) + { + const double left_data = left.data(Qt::UserRole).toULongLong(); + const double right_data = right.data(Qt::UserRole).toULongLong(); + return left_data < right_data; + } + else if ((left.column() == kSnapshotTimelineColumnCommittedHost && right.column() == kSnapshotTimelineColumnCommittedHost)) + { + const double left_data = left.data(Qt::UserRole).toULongLong(); + const double right_data = right.data(Qt::UserRole).toULongLong(); + return left_data < right_data; + } + + return QSortFilterProxyModel::lessThan(left, right); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/proxy_models/snapshot_timeline_proxy_model.h b/source/frontend/models/proxy_models/snapshot_timeline_proxy_model.h new file mode 100644 index 0000000..8290c15 --- /dev/null +++ b/source/frontend/models/proxy_models/snapshot_timeline_proxy_model.h @@ -0,0 +1,43 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a proxy filter that processes multiple columns. +//============================================================================= + +#ifndef RMV_MODELS_PROXY_MODELS_SNAPSHOT_TIMELINE_PROXY_MODEL_H_ +#define RMV_MODELS_PROXY_MODELS_SNAPSHOT_TIMELINE_PROXY_MODEL_H_ + +#include "models/proxy_models/table_proxy_model.h" + +namespace rmv +{ + /// Class to filter out and sort the snapshot table on the timeline pane. + class SnapshotTimelineProxyModel : public TableProxyModel + { + Q_OBJECT + + public: + /// Constructor. + /// \param parent The parent widget. + explicit SnapshotTimelineProxyModel(QObject* parent = nullptr); + + /// Destructor. + virtual ~SnapshotTimelineProxyModel(); + + protected: + /// Make the filter run across multiple columns. + /// \param source_row the target row. + /// \param source_parent the source parent. + /// \return pass or not. + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + + /// Implement the comparison for sorting. + /// \param left the left item to compare. + /// \param right the right item to compare. + /// \return If left < right then true, else false. + virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + }; +} // namespace rmv + +#endif // RMV_MODELS_PROXY_MODELS_SNAPSHOT_TIMELINE_PROXY_MODEL_H_ diff --git a/source/frontend/models/proxy_models/table_proxy_model.cpp b/source/frontend/models/proxy_models/table_proxy_model.cpp new file mode 100644 index 0000000..48adce2 --- /dev/null +++ b/source/frontend/models/proxy_models/table_proxy_model.cpp @@ -0,0 +1,118 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a proxy filter that processes multiple columns. +//============================================================================= + +#include "models/proxy_models/table_proxy_model.h" + +namespace rmv +{ + TableProxyModel::TableProxyModel(QObject* parent) + : QSortFilterProxyModel(parent) + , min_size_(0) + , max_size_(UINT64_MAX) + { + } + + TableProxyModel::~TableProxyModel() + { + } + + void TableProxyModel::SetFilterKeyColumns(const QList& columns) + { + column_filters_.clear(); + + search_filter_ = QString(); + for (int i = 0; i < columns.size(); i++) + { + column_filters_.insert(columns[i]); + } + } + + void TableProxyModel::SetSearchFilter(const QString& filter) + { + search_filter_ = filter; + } + + void TableProxyModel::SetSizeFilter(uint64_t min, uint64_t max) + { + min_size_ = min; + max_size_ = max; + } + + uint64_t TableProxyModel::GetIndexValue(const QModelIndex& index) const + { + const QString data = sourceModel()->data(index).toString(); + bool ok = false; + return data.toULongLong(&ok, 0); + } + + qulonglong TableProxyModel::GetData(int row, int column) + { + qulonglong out = 0; + + const QModelIndex model_index = index(row, column, QModelIndex()); + + if (model_index.isValid() == true) + { + out = data(model_index, Qt::UserRole).toULongLong(); + } + return out; + } + + QModelIndex TableProxyModel::FindModelIndex(qulonglong lookup, int column) const + { + QModelIndex out_model_index; + + const int num_rows = rowCount(); + + for (int row = 0; row < num_rows; row++) + { + const QModelIndex model_index = index(row, column, QModelIndex()); + + if (model_index.isValid() == true) + { + qulonglong value = data(model_index, Qt::UserRole).toULongLong(); + if (value == lookup) + { + out_model_index = model_index; + break; + } + } + } + + return out_model_index; + } + + bool TableProxyModel::FilterSizeSlider(int row, int column, const QModelIndex& source_parent) const + { + const QModelIndex& size_filter_index = sourceModel()->index(row, column, source_parent); + bool ok = false; + const uint64_t size = size_filter_index.data(Qt::UserRole).toULongLong(&ok); + + return !(size < min_size_ || size > max_size_); + } + + bool TableProxyModel::FilterSearchString(int row, const QModelIndex& source_parent) const + { + if (column_filters_.empty() == false && search_filter_.isEmpty() == false) + { + for (auto iter = column_filters_.begin(); iter != column_filters_.end(); ++iter) + { + const QModelIndex& index = sourceModel()->index(row, *iter, source_parent); + + QString indexData = index.data().toString(); + + if (indexData.contains(search_filter_, Qt::CaseInsensitive) == true) + { + // Found a match so don't need to check for further matches. + return true; + } + } + return false; + } + return true; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/proxy_models/table_proxy_model.h b/source/frontend/models/proxy_models/table_proxy_model.h new file mode 100644 index 0000000..66fd4c0 --- /dev/null +++ b/source/frontend/models/proxy_models/table_proxy_model.h @@ -0,0 +1,84 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a proxy filter that processes multiple columns. +//============================================================================= + +#ifndef RMV_MODELS_PROXY_MODELS_TABLE_PROXY_MODEL_H_ +#define RMV_MODELS_PROXY_MODELS_TABLE_PROXY_MODEL_H_ + +#include +#include + +namespace rmv +{ + /// Class to filter out and sort a table. + class TableProxyModel : public QSortFilterProxyModel + { + Q_OBJECT + + public: + /// Constructor. + /// \param parent The parent widget. + explicit TableProxyModel(QObject* parent = nullptr); + + /// Destructor. + virtual ~TableProxyModel(); + + /// Specify which columns should be sorted/filtered. + /// \param columns list of columns. + void SetFilterKeyColumns(const QList& columns); + + /// Specify string to use as search filter. + /// \param filter the search filter. + void SetSearchFilter(const QString& filter); + + /// Specify range to use as size filter. + /// \param min the min size. + /// \param max the max size. + void SetSizeFilter(uint64_t min, uint64_t max); + + /// Get content from proxy model. + /// \param row The row where the data is located. + /// \param column The column where the data is located. + /// \return The contents at row, column. + qulonglong GetData(int row, int column); + + /// Find a model index corresponding to the passed in data. + /// \param lookup The value to find. + /// \param column The column to search. + /// \return the model index containing the data. + QModelIndex FindModelIndex(qulonglong lookup, int column) const; + + protected: + /// Methods that must be implemented by derived classes. + virtual bool filterAcceptsRow(int row, const QModelIndex& source_parent) const = 0; + virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const = 0; + + /// Filter the size slider. + /// \param row The row to apply the size filter to. + /// \param column The columnto apply the size filter to. + /// \param 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 FilterSizeSlider(int row, int column, const QModelIndex& source_parent) const; + + /// Filter the search string. + /// \param row The row to apply the size filter to. + /// \param 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 FilterSearchString(int row, const QModelIndex& source_parent) const; + + /// Extract a uint64_t from a model index. + /// \param index the index. + /// \return the uint64 value stored at the model index. + uint64_t GetIndexValue(const QModelIndex& index) const; + + std::set column_filters_; ///< Holds which columns are being filtered. + 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. + }; +} // namespace rmv + +#endif // RMV_MODELS_PROXY_MODELS_TABLE_PROXY_MODEL_H_ diff --git a/source/frontend/models/resource_item_model.cpp b/source/frontend/models/resource_item_model.cpp new file mode 100644 index 0000000..e296096 --- /dev/null +++ b/source/frontend/models/resource_item_model.cpp @@ -0,0 +1,265 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a resource item model. Used for the resource +/// list tables +//============================================================================= + +#include "models/resource_item_model.h" + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" +#include "rmt_resource_list.h" +#include "rmt_util.h" + +#include "models/trace_manager.h" +#include "util/string_util.h" + +namespace rmv +{ + ResourceItemModel::ResourceItemModel(QObject* parent) + : QAbstractItemModel(parent) + , num_rows_(0) + , num_columns_(0) + { + } + + ResourceItemModel::~ResourceItemModel() + { + } + + void ResourceItemModel::SetRowCount(int rows) + { + num_rows_ = rows; + cache_.clear(); + } + + void ResourceItemModel::SetColumnCount(int columns) + { + num_columns_ = columns; + } + + void ResourceItemModel::Initialize(ScaledTableView* resource_table, bool compare_visible) + { + resource_table->horizontalHeader()->setSectionsClickable(true); + + // Set default column widths wide enough to show table contents. + resource_table->SetColumnPadding(0); + resource_table->SetColumnWidthEms(kResourceColumnCompareId, 8); + resource_table->SetColumnWidthEms(kResourceColumnName, 20); + resource_table->SetColumnWidthEms(kResourceColumnVirtualAddress, 11); + resource_table->SetColumnWidthEms(kResourceColumnSize, 8); + resource_table->SetColumnWidthEms(kResourceColumnPreferredHeap, 11); + resource_table->SetColumnWidthEms(kResourceColumnMappedInvisible, 13); + resource_table->SetColumnWidthEms(kResourceColumnMappedLocal, 11); + resource_table->SetColumnWidthEms(kResourceColumnMappedHost, 11); + resource_table->SetColumnWidthEms(kResourceColumnMappedNone, 8); + resource_table->SetColumnWidthEms(kResourceColumnUsage, 10); + resource_table->SetColumnWidthEms(kResourceColumnAllocationIdInternal, 10); + resource_table->SetColumnWidthEms(kResourceColumnGlobalId, 10); + + // Allow users to resize columns if desired. + resource_table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Interactive); + + if (!compare_visible) + { + resource_table->hideColumn(kResourceColumnCompareId); + } + + // hide columns used for proxy models + resource_table->hideColumn(kResourceColumnAllocationIdInternal); + resource_table->hideColumn(kResourceColumnGlobalId); + } + + void ResourceItemModel::AddResource(const RmtDataSnapshot* snapshot, const RmtResource* resource, SnapshotCompareId compare_id) + { + uint64_t memory_segment_histogram[kRmtResourceBackingStorageCount] = {0}; + + RmtResourceGetBackingStorageHistogram(snapshot, resource, memory_segment_histogram); + + uint64_t total_memory_mapped = 0; + for (int32_t currentMemoryHistogramIndex = 0; currentMemoryHistogramIndex < kRmtResourceBackingStorageCount; ++currentMemoryHistogramIndex) + { + total_memory_mapped += memory_segment_histogram[currentMemoryHistogramIndex]; + } + + const char buffer[RMT_MAXIMUM_NAME_LENGTH] = " - "; + const char* buf_ptr = &buffer[0]; + RmtResourceGetName(resource, RMT_MAXIMUM_NAME_LENGTH, (char**)&buf_ptr); + + DataCache cache; + cache.resource = resource; + cache.compare_id = compare_id; + cache.resource_name = QString(buffer); + cache.local_bytes = 0; + cache.invisible_bytes = 0; + cache.host_bytes = 0; + cache.unmapped_bytes = 0; + if (total_memory_mapped > 0) + { + cache.local_bytes = memory_segment_histogram[kRmtHeapTypeLocal]; + cache.invisible_bytes = memory_segment_histogram[kRmtHeapTypeInvisible]; + cache.host_bytes = memory_segment_histogram[kRmtHeapTypeSystem]; + cache.unmapped_bytes = memory_segment_histogram[kRmtResourceBackingStorageUnmapped]; + } + cache_.push_back(cache); + } + + QVariant ResourceItemModel::data(const QModelIndex& index, int role) const + { + if (!index.isValid()) + { + return QVariant(); + } + + int row = index.row(); + const RmtResource* resource = cache_[row].resource; + if (resource == nullptr) + { + return QVariant(); + } + + if (role == Qt::DisplayRole) + { + switch (index.column()) + { + case kResourceColumnCompareId: + return QString::number(cache_[row].compare_id); + case kResourceColumnName: + return cache_[row].resource_name; + case kResourceColumnVirtualAddress: + return rmv::string_util::LocalizedValueAddress(RmtResourceGetVirtualAddress(resource)); + case kResourceColumnSize: + return rmv::string_util::LocalizedValueMemory(resource->size_in_bytes, false, false); + case kResourceColumnMappedInvisible: + return rmv::string_util::LocalizedValueMemory(cache_[row].invisible_bytes, false, false); + case kResourceColumnMappedLocal: + return rmv::string_util::LocalizedValueMemory(cache_[row].local_bytes, false, false); + case kResourceColumnMappedHost: + return rmv::string_util::LocalizedValueMemory(cache_[row].host_bytes, false, false); + case kResourceColumnMappedNone: + return rmv::string_util::LocalizedValueMemory(cache_[row].unmapped_bytes, false, false); + case kResourceColumnPreferredHeap: + return RmtResourceGetHeapTypeName(resource); + case kResourceColumnUsage: + { + const RmtResourceUsageType resource_usage_type = RmtResourceGetUsageType(resource); + return rmv::string_util::GetResourceUsageString(resource_usage_type); + } + case kResourceColumnAllocationIdInternal: + { + if (resource->bound_allocation != nullptr) + { + return QString::number(resource->bound_allocation->guid); + } + else + { + return RmtResourceGetHeapTypeName(resource); + } + } + case kResourceColumnGlobalId: + return QString::number(resource->identifier); + + default: + break; + } + } + else if (role == Qt::UserRole) + { + switch (index.column()) + { + case kResourceColumnCompareId: + return QVariant::fromValue(cache_[row].compare_id); + case kResourceColumnName: + return QVariant::fromValue(resource->identifier); + case kResourceColumnVirtualAddress: + return QVariant::fromValue(RmtResourceGetVirtualAddress(resource)); + case kResourceColumnSize: + return QVariant::fromValue(resource->size_in_bytes); + case kResourceColumnMappedInvisible: + return QVariant::fromValue(cache_[row].invisible_bytes); + case kResourceColumnMappedLocal: + return QVariant::fromValue(cache_[row].local_bytes); + case kResourceColumnMappedHost: + return QVariant::fromValue(cache_[row].host_bytes); + case kResourceColumnMappedNone: + return QVariant::fromValue(cache_[row].unmapped_bytes); + case kResourceColumnGlobalId: + return QVariant::fromValue(resource->identifier); + + default: + break; + } + } + + return QVariant(); + } + + Qt::ItemFlags ResourceItemModel::flags(const QModelIndex& index) const + { + return QAbstractItemModel::flags(index); + } + + QVariant ResourceItemModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case kResourceColumnCompareId: + return "Compare ID"; + case kResourceColumnName: + return "Name"; + case kResourceColumnVirtualAddress: + return "Virtual address"; + case kResourceColumnSize: + return "Size"; + case kResourceColumnPreferredHeap: + return "Preferred heap"; + case kResourceColumnMappedLocal: + return "Committed local"; + case kResourceColumnMappedInvisible: + return "Committed invisible"; + case kResourceColumnMappedHost: + return "Committed host"; + case kResourceColumnMappedNone: + return "Unmapped"; + case kResourceColumnUsage: + return "Usage"; + default: + break; + } + } + + return QAbstractItemModel::headerData(section, orientation, role); + } + + QModelIndex ResourceItemModel::index(int row, int column, const QModelIndex& parent) const + { + if (!hasIndex(row, column, parent)) + { + return QModelIndex(); + } + + return createIndex(row, column); + } + + QModelIndex ResourceItemModel::parent(const QModelIndex& index) const + { + Q_UNUSED(index); + return QModelIndex(); + } + + int ResourceItemModel::rowCount(const QModelIndex& parent) const + { + Q_UNUSED(parent); + return num_rows_; + } + + int ResourceItemModel::columnCount(const QModelIndex& parent) const + { + Q_UNUSED(parent); + return num_columns_; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/resource_item_model.h b/source/frontend/models/resource_item_model.h new file mode 100644 index 0000000..2b0e1e7 --- /dev/null +++ b/source/frontend/models/resource_item_model.h @@ -0,0 +1,117 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a resource item model. Used for the resource list tables +//============================================================================= + +#ifndef RMV_MODELS_RESOURCE_ITEM_MODEL_H_ +#define RMV_MODELS_RESOURCE_ITEM_MODEL_H_ + +#include + +#include "qt_common/custom_widgets/scaled_table_view.h" + +#include "rmt_data_snapshot.h" +#include "rmt_resource_list.h" + +/// Column Id's for the fields in the resource tables. +enum ResourceColumn +{ + kResourceColumnCompareId, + kResourceColumnName, + kResourceColumnVirtualAddress, + kResourceColumnSize, + kResourceColumnPreferredHeap, + kResourceColumnMappedInvisible, + kResourceColumnMappedLocal, + kResourceColumnMappedHost, + kResourceColumnMappedNone, + kResourceColumnUsage, + + // Hidden, these columns are used as proxies for sorting by other columns. + kResourceColumnAllocationIdInternal, + kResourceColumnGlobalId, + + kResourceColumnCount, +}; + +/// Snapshot compare Id types used in the memory leak pane. +enum SnapshotCompareId +{ + kSnapshotCompareIdUndefined = 0x0, + kSnapshotCompareIdCommon = 0x1, + kSnapshotCompareIdOpen = 0x2, + kSnapshotCompareIdCompared = 0x4, +}; + +namespace rmv +{ + class ResourceItemModel : public QAbstractItemModel + { + public: + /// Constructor. + explicit ResourceItemModel(QObject* parent = nullptr); + + /// Destructor. + ~ResourceItemModel(); + + /// Set the number of rows in the table. + /// \param rows The number of rows required. + void SetRowCount(int rows); + + /// Set the number of columns in the table. + /// \param columns The number of columns required. + void SetColumnCount(int columns); + + /// Initialize the resource list table. An instance of this table + /// is present in the resource list, allocation explorer and memory leak panes. + /// \param resource_table The table to initialize. + /// \param compare_visible If false, hide the compare column. + void Initialize(ScaledTableView* resource_table, bool compare_visible); + + /// Add a resource to the table. + /// \param snapshot The snapshot where the resource data is located. + /// \param resource The resource to add. + /// \param compare_id The ID when used to compare 2 resources. + void AddResource(const RmtDataSnapshot* snapshot, const RmtResource* resource, SnapshotCompareId compare_id); + + // QAbstractItemModel overrides. See Qt documentation for parameter and return values + virtual QVariant data(const QModelIndex& index, int role) const Q_DECL_OVERRIDE; + virtual Qt::ItemFlags flags(const QModelIndex& index) const Q_DECL_OVERRIDE; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const Q_DECL_OVERRIDE; + virtual QModelIndex parent(const QModelIndex& index) const Q_DECL_OVERRIDE; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + + private: + /// Data from the backend that needs caching for speed. + struct DataCache + { + DataCache() + : resource(nullptr) + , local_bytes(0) + , invisible_bytes(0) + , host_bytes(0) + , unmapped_bytes(0) + , compare_id(kSnapshotCompareIdUndefined) + { + } + + const RmtResource* resource; ///< The resource. + double local_bytes; ///< Amount of local memory. + double invisible_bytes; ///< Amount of invisible memory. + double host_bytes; ///< Amount of host memory. + double unmapped_bytes; ///< Amount of unmapped memory. + SnapshotCompareId compare_id; ///< The comparison id (if any). + QString resource_name; ///< The resource name. + }; + + int num_rows_; ///< The number of rows in the table. + int num_columns_; ///< The number of columns in the table. + std::vector cache_; ///< Cached data from the backend. + }; +} // namespace rmv + +#endif // RMV_MODELS_RESOURCE_ITEM_MODEL_H_ diff --git a/source/frontend/models/resource_sorter.cpp b/source/frontend/models/resource_sorter.cpp new file mode 100644 index 0000000..c1182e6 --- /dev/null +++ b/source/frontend/models/resource_sorter.cpp @@ -0,0 +1,73 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a resource sorter. Contains a list of resources +/// and allows them to be sorted and returns sorted values and smaller values +/// grouped together as "other" +//============================================================================= + +#include "models/resource_sorter.h" + +#include "rmt_assert.h" + +namespace rmv +{ + ResourceSorter::ResourceSorter() + { + } + + ResourceSorter::~ResourceSorter() + { + } + + void ResourceSorter::Clear() + { + resource_list_.clear(); + } + + void ResourceSorter::AddResource(RmtResourceUsageType type, uint64_t count) + { + ResourceInfo resource_info; + resource_info.type = type; + resource_info.count = count; + resource_list_.push_back(resource_info); + } + + void ResourceSorter::Sort() + { + std::stable_sort(resource_list_.begin(), resource_list_.end(), &ResourceSorter::SortComparator); + } + + size_t ResourceSorter::GetNumResources() + { + return resource_list_.size(); + } + + RmtResourceUsageType ResourceSorter::GetResourceType(size_t index) + { + RMT_ASSERT(index < resource_list_.size()); + return resource_list_[index].type; + } + + uint64_t ResourceSorter::GetResourceValue(size_t index) + { + RMT_ASSERT(index < resource_list_.size()); + return resource_list_[index].count; + } + + int64_t ResourceSorter::GetRemainder(int start_index) + { + int64_t value = 0; + for (size_t i = start_index; i < GetNumResources(); i++) + { + value += resource_list_[i].count; + } + return value; + } + + bool ResourceSorter::SortComparator(const ResourceInfo& resource_info_a, const ResourceInfo& resource_info_b) + { + return resource_info_a.count > resource_info_b.count; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/resource_sorter.h b/source/frontend/models/resource_sorter.h new file mode 100644 index 0000000..2949943 --- /dev/null +++ b/source/frontend/models/resource_sorter.h @@ -0,0 +1,80 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a resource sorter. Contains a list of resources +/// and allows them to be sorted and returns sorted values and smaller values +/// grouped together as "other". +//============================================================================= + +#ifndef RMV_MODELS_RESOURCE_SORTER_H_ +#define RMV_MODELS_RESOURCE_SORTER_H_ + +#include +#include + +#include "rmt_resource_list.h" + +namespace rmv +{ + /// Container class for a list of resources that need to be sorted + class ResourceSorter + { + public: + /// Constructor. + ResourceSorter(); + + /// Destructor. + ~ResourceSorter(); + + /// Clear the list of resources. + void Clear(); + + /// Add a resource to the list. + /// \param type The type of resource to add. + /// \param count The amount of this type. + void AddResource(RmtResourceUsageType type, uint64_t count); + + /// Sort the resource list, ordered by amount, largest first. + void Sort(); + + /// Get the number of resources in the list. + /// \return the number of resources. + size_t GetNumResources(); + + /// Get the resource type for a particular index. + /// \param index The index of the resource. + /// \return The value of the resource at the index specified. + RmtResourceUsageType GetResourceType(size_t index); + + /// Get the resource value for a particular index. + /// \param index The index of the resource. + /// \return The value of the resource at the index specified. + uint64_t GetResourceValue(size_t index); + + /// Get the remainder of resources in the list from the specified. + /// index to the end. This value is shown in the UI as "other". + /// \param start_index The first index where counting is to begin. + /// \return the total value of all resources from start_index to the end + /// of the list. + int64_t GetRemainder(int start_index); + + private: + // struct for sorting resource types by their amount. + struct ResourceInfo + { + RmtResourceUsageType type; ///< The type of resource. + uint64_t count; ///< The amount (could be a count, memory used or some other amount). + }; + + /// Sort comparator for sorting the resources into order by quantity. + /// \param resource_info_a The first resource to compare. + /// \param resource_info_b The second resource to compare. + /// \return true if the first resource is larger than the second. + static bool SortComparator(const ResourceInfo& resource_info_a, const ResourceInfo& resource_info_b); + + std::vector resource_list_; ///< The list of resources. + }; +} // namespace rmv + +#endif // RMV_MODELS_RESOURCE_SORTER_H_ diff --git a/source/frontend/models/resource_usage_combo_box_model.cpp b/source/frontend/models/resource_usage_combo_box_model.cpp new file mode 100644 index 0000000..6f29ab8 --- /dev/null +++ b/source/frontend/models/resource_usage_combo_box_model.cpp @@ -0,0 +1,121 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a model corresponding to a resource combo box. +//============================================================================= + +#include "models/resource_usage_combo_box_model.h" + +#include + +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" + +#include "rmt_resource_list.h" +#include "rmt_assert.h" + +#include "util/string_util.h" + +// Set of resources that shouldn't be in the resource combo box. +static const std::set kExcludedResources = {kRmtResourceUsageTypeUnknown}; + +// Set of resources that should be disabled in the resource combo box. +static const std::set kDisabledResources = {kRmtResourceUsageTypeHeap, kRmtResourceUsageTypeFree}; + +namespace rmv +{ + ResourceUsageComboBoxModel::ResourceUsageComboBoxModel() + : ComboBoxModel() + { + // inform the model which entries are ignored in the UI. + SetupExcludeIndexList(kExcludedResources); + } + + ResourceUsageComboBoxModel::~ResourceUsageComboBoxModel() + { + } + + void ResourceUsageComboBoxModel::SetupResourceComboBox(ArrowIconComboBox* combo_box) + { + // Add the "All" entry to the combo box + QCheckBox* checkbox = combo_box->AddCheckboxItem("All", QVariant(), false, true); + RMT_ASSERT(checkbox != nullptr); + if (checkbox != nullptr) + { + connect(checkbox, &QCheckBox::clicked, this, [=]() { emit FilterChanged(true); }); + } + + // Add resources if they are not excluded + for (int i = 0; i < static_cast(kRmtResourceUsageTypeCount); i++) + { + auto it = kExcludedResources.find(i); + if (it == kExcludedResources.end()) + { + checkbox = combo_box->AddCheckboxItem(rmv::string_util::GetResourceUsageString(RmtResourceUsageType(i)), QVariant(), false, false); + RMT_ASSERT(checkbox != nullptr); + if (checkbox != nullptr) + { + connect(checkbox, &QCheckBox::clicked, this, [=]() { emit FilterChanged(true); }); + } + } + } + ResetResourceComboBox(combo_box); + } + + void ResourceUsageComboBoxModel::ResetResourceComboBox(ArrowIconComboBox* combo_box) + { + // index 0 is the "all" combo box entry + if (kDisabledResources.size() > 0) + { + // If there are disabled resources, then disable the "All" checkbox. + int combo_box_row = 0; + combo_box->SetChecked(combo_box_row, false); + combo_box_row++; + + for (int i = 0; i < static_cast(kRmtResourceUsageTypeCount); i++) + { + // if this resource is excluded, skip it + auto exclude_it = kExcludedResources.find(i); + if (exclude_it != kExcludedResources.end()) + { + continue; + } + + auto disabled_it = kDisabledResources.find(i); + if (disabled_it != kDisabledResources.end()) + { + combo_box->SetChecked(combo_box_row, false); + } + else + { + combo_box->SetChecked(combo_box_row, true); + } + combo_box_row++; + } + } + SetupState(combo_box); + } + + QString ResourceUsageComboBoxModel::GetFilterString(const ArrowIconComboBox* combo_box) + { + SetupState(combo_box); + + // build the resource filter + QString resource_filter = QString("(="); + for (int resource = 0; resource < kRmtResourceUsageTypeCount; resource++) + { + if (ItemInList(resource) == true) + { + resource_filter += "|" + QString(rmv::string_util::GetResourceUsageString(static_cast(resource))); + } + } + resource_filter += ")"; + + return resource_filter; + } + + void ResourceUsageComboBoxModel::SetupState(const ArrowIconComboBox* combo_box) + { + ComboBoxModel::SetupState(combo_box, true); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/resource_usage_combo_box_model.h b/source/frontend/models/resource_usage_combo_box_model.h new file mode 100644 index 0000000..901fc7b --- /dev/null +++ b/source/frontend/models/resource_usage_combo_box_model.h @@ -0,0 +1,47 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a model corresponding to a resource combo box. +//============================================================================= + +#ifndef RMV_MODELS_RESOURCE_USAGE_COMBO_BOX_MODEL_H_ +#define RMV_MODELS_RESOURCE_USAGE_COMBO_BOX_MODEL_H_ + +#include "models/combo_box_model.h" + +namespace rmv +{ + /// Model encapsulating everything needed for a resource combo box. + class ResourceUsageComboBoxModel : public ComboBoxModel + { + Q_OBJECT + + public: + /// Constructor. + ResourceUsageComboBoxModel(); + + /// Destructor. + ~ResourceUsageComboBoxModel(); + + /// Set up the resource combo box taking into account any resources that are to be ignored. + void SetupResourceComboBox(ArrowIconComboBox* combo_box); + + /// Reset the resource combo box to its default values. Some values may be disabled by default. + void ResetResourceComboBox(ArrowIconComboBox* combo_box); + + /// Get the Filter string for the regular expression to be used when filtering a resource list table by heap. + QString GetFilterString(const ArrowIconComboBox* combo_box); + + /// Check the state of the combo box and setup the internal state representation + /// of the ArrowIconComboBox. + /// \param combo_box Pointer to the combo box whose state is to be examined. + void SetupState(const ArrowIconComboBox* combo_box); + + signals: + /// signal emitted when a combo box item is changed. + void FilterChanged(bool checked); + }; +} // namespace rmv + +#endif // RMV_MODELS_RESOURCE_USAGE_COMBO_BOX_MODEL_H_ diff --git a/source/frontend/models/snapshot/allocation_explorer_model.cpp b/source/frontend/models/snapshot/allocation_explorer_model.cpp new file mode 100644 index 0000000..b907c9b --- /dev/null +++ b/source/frontend/models/snapshot/allocation_explorer_model.cpp @@ -0,0 +1,235 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for Allocation explorer pane +//============================================================================= + +#include "models/snapshot/allocation_explorer_model.h" + +#include +#include +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" + +#include "models/trace_manager.h" + +namespace rmv +{ + VirtualAllocationExplorerModel::VirtualAllocationExplorerModel(int32_t num_allocation_models) + : ModelViewMapper(kVirtualAllocationExplorerNumWidgets) + , allocation_table_model_(nullptr) + , resource_table_model_(nullptr) + , allocation_proxy_model_(nullptr) + , resource_proxy_model_(nullptr) + , resource_thresholds_{} + , minimum_allocation_size_(0) + , maximum_allocation_size_(0) + { + allocation_bar_model_ = new AllocationBarModel(num_allocation_models, false); + } + + VirtualAllocationExplorerModel::~VirtualAllocationExplorerModel() + { + delete allocation_bar_model_; + delete allocation_table_model_; + delete resource_table_model_; + delete allocation_proxy_model_; + delete resource_proxy_model_; + } + + void VirtualAllocationExplorerModel::ResetModelValues() + { + allocation_table_model_->removeRows(0, allocation_table_model_->rowCount()); + allocation_table_model_->SetRowCount(0); + resource_table_model_->removeRows(0, resource_table_model_->rowCount()); + resource_table_model_->SetRowCount(0); + allocation_proxy_model_->invalidate(); + resource_proxy_model_->invalidate(); + memset(resource_thresholds_, 0, sizeof(uint64_t) * (kSizeSliderRange + 1)); + allocation_bar_model_->ClearSelectionState(); + } + + bool VirtualAllocationExplorerModel::OpenSnapshot(const RmtDataSnapshot* snapshot) + { + if (snapshot->virtual_allocation_list.allocation_count <= 0) + { + return false; + } + + allocation_bar_model_->ClearSelectionState(); + return true; + } + + void VirtualAllocationExplorerModel::UpdateAllocationTable() + { + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return; + } + + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + + allocation_table_model_->removeRows(0, allocation_table_model_->rowCount()); + + minimum_allocation_size_ = UINT64_MAX; + maximum_allocation_size_ = 0; + + // update allocation table and min/max allocation sizes + int32_t allocation_count = open_snapshot->virtual_allocation_list.allocation_count; + allocation_table_model_->SetRowCount(allocation_count); + for (int32_t allocation_index = 0; allocation_index < allocation_count; allocation_index++) + { + const RmtVirtualAllocation* virtual_allocation = &open_snapshot->virtual_allocation_list.allocation_details[allocation_index]; + allocation_table_model_->AddAllocation(open_snapshot, virtual_allocation); + uint64_t allocation_size = RmtVirtualAllocationGetSizeInBytes(virtual_allocation); + if (allocation_size < minimum_allocation_size_) + { + minimum_allocation_size_ = allocation_size; + } + if (allocation_size > maximum_allocation_size_) + { + maximum_allocation_size_ = allocation_size; + } + } + allocation_proxy_model_->invalidate(); + } + + int32_t VirtualAllocationExplorerModel::UpdateResourceTable() + { + int resource_count = 0; + + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return resource_count; + } + + const RmtVirtualAllocation* selected_allocation = allocation_bar_model_->GetAllocation(0, 0); + if (selected_allocation == nullptr) + { + return resource_count; + } + + resource_table_model_->removeRows(0, resource_table_model_->rowCount()); + + // update resource table + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + resource_count = selected_allocation->resource_count; + resource_table_model_->SetRowCount(resource_count); + for (int32_t i = 0; i < resource_count; i++) + { + resource_table_model_->AddResource(open_snapshot, selected_allocation->resources[i], kSnapshotCompareIdUndefined); + } + resource_proxy_model_->invalidate(); + return resource_count; + } + + void VirtualAllocationExplorerModel::InitializeAllocationTableModel(ScaledTableView* table_view, uint num_rows, uint num_columns) + { + RMT_ASSERT(allocation_proxy_model_ == nullptr); + allocation_proxy_model_ = new AllocationProxyModel(); + allocation_table_model_ = allocation_proxy_model_->InitializeAllocationTableModels(table_view, num_rows, num_columns); + + table_view->horizontalHeader()->setSectionsClickable(true); + + table_view->SetColumnPadding(0); + table_view->SetColumnWidthEms(kVirtualAllocationColumnId, 9); + table_view->SetColumnWidthEms(kVirtualAllocationColumnAllocationSize, 10); + table_view->SetColumnWidthEms(kVirtualAllocationColumnBound, 8); + table_view->SetColumnWidthEms(kVirtualAllocationColumnUnbound, 8); + table_view->SetColumnWidthEms(kVirtualAllocationColumnAverageResourceSize, 12); + table_view->SetColumnWidthEms(kVirtualAllocationColumnResourceSizeStdDev, 15); + table_view->SetColumnWidthEms(kVirtualAllocationColumnResourceCount, 11); + table_view->SetColumnWidthEms(kVirtualAllocationColumnPreferredHeapName, 11); + table_view->SetColumnWidthEms(kVirtualAllocationColumnInvisiblePercentage, 13); + table_view->SetColumnWidthEms(kVirtualAllocationColumnLocalPercentage, 11); + table_view->SetColumnWidthEms(kVirtualAllocationColumnSystemPercentage, 11); + table_view->SetColumnWidthEms(kVirtualAllocationColumnUnmappedPercentage, 8); + + table_view->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Interactive); + } + + void VirtualAllocationExplorerModel::InitializeResourceTableModel(ScaledTableView* table_view, uint num_rows, uint num_columns) + { + RMT_ASSERT(resource_proxy_model_ == nullptr); + resource_proxy_model_ = new ResourceProxyModel(); + resource_table_model_ = resource_proxy_model_->InitializeResourceTableModels(table_view, num_rows, num_columns); + resource_table_model_->Initialize(table_view, false); + } + + void VirtualAllocationExplorerModel::AllocationSearchBoxChanged(const QString& filter) + { + allocation_proxy_model_->SetSearchFilter(filter); + allocation_proxy_model_->invalidate(); + } + + void VirtualAllocationExplorerModel::AllocationSizeFilterChanged(int min_value, int max_value) const + { + const uint64_t diff = maximum_allocation_size_ - minimum_allocation_size_; + const uint64_t scaled_min = ((min_value * diff) / kSizeSliderRange) + minimum_allocation_size_; + const uint64_t scaled_max = ((max_value * diff) / kSizeSliderRange) + minimum_allocation_size_; + + allocation_proxy_model_->SetSizeFilter(scaled_min, scaled_max); + allocation_proxy_model_->invalidate(); + } + + void VirtualAllocationExplorerModel::ResourceSearchBoxChanged(const QString& filter) + { + resource_proxy_model_->SetSearchFilter(filter); + resource_proxy_model_->invalidate(); + } + + void VirtualAllocationExplorerModel::ResourceSizeFilterChanged(int min_value, int max_value) + { + if (allocation_bar_model_->GetAllocation(0, 0) != nullptr) + { + const uint64_t scaled_min = resource_thresholds_[min_value]; + const uint64_t scaled_max = resource_thresholds_[max_value]; + + resource_proxy_model_->SetSizeFilter(scaled_min, scaled_max); + resource_proxy_model_->invalidate(); + } + } + + AllocationProxyModel* VirtualAllocationExplorerModel::GetAllocationProxyModel() const + { + return allocation_proxy_model_; + } + + ResourceProxyModel* VirtualAllocationExplorerModel::GetResourceProxyModel() const + { + return resource_proxy_model_; + } + + void VirtualAllocationExplorerModel::BuildResourceSizeThresholds() + { + std::vector resource_sizes; + + const RmtVirtualAllocation* selected_allocation = allocation_bar_model_->GetAllocation(0, 0); + + if (selected_allocation != nullptr && selected_allocation->resource_count > 0) + { + int32_t resource_count = selected_allocation->resource_count; + resource_sizes.reserve(resource_count); + for (int current_resource_index = 0; current_resource_index < selected_allocation->resource_count; current_resource_index++) + { + resource_sizes.push_back(selected_allocation->resources[current_resource_index]->size_in_bytes); + } + TraceManager::Get().BuildResourceSizeThresholds(resource_sizes, resource_thresholds_); + } + } + + AllocationBarModel* VirtualAllocationExplorerModel::GetAllocationBarModel() const + { + return allocation_bar_model_; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/allocation_explorer_model.h b/source/frontend/models/snapshot/allocation_explorer_model.h new file mode 100644 index 0000000..9fe45a8 --- /dev/null +++ b/source/frontend/models/snapshot/allocation_explorer_model.h @@ -0,0 +1,132 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Allocation explorer pane. +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_ALLOCATION_EXPLORER_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_ALLOCATION_EXPLORER_MODEL_H_ + +#include "qt_common/custom_widgets/scaled_table_view.h" +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_virtual_allocation_list.h" + +#include "models/allocation_bar_model.h" +#include "models/allocation_item_model.h" +#include "models/proxy_models/resource_proxy_model.h" +#include "models/proxy_models/allocation_proxy_model.h" +#include "models/resource_item_model.h" +#include "util/definitions.h" + +/// Enum containing indices for columns in the allocation table. +enum VirtualAllocationColumn +{ + kVirtualAllocationColumnId, + kVirtualAllocationColumnAllocationSize, + kVirtualAllocationColumnBound, + kVirtualAllocationColumnUnbound, + kVirtualAllocationColumnAverageResourceSize, + kVirtualAllocationColumnResourceSizeStdDev, + kVirtualAllocationColumnResourceCount, + kVirtualAllocationColumnPreferredHeapName, + kVirtualAllocationColumnInvisiblePercentage, + kVirtualAllocationColumnLocalPercentage, + kVirtualAllocationColumnSystemPercentage, + kVirtualAllocationColumnUnmappedPercentage, + + kVirtualAllocationColumnCount, +}; + +namespace rmv +{ + /// Enum containing indices for the widgets shared between the model and UI. + enum VirtualAllocationExplorerWidgets + { + kVirtualAllocationExplorerNumWidgets, + }; + + /// Container class that holds model data for a given pane. + class VirtualAllocationExplorerModel : public ModelViewMapper + { + public: + /// Constructor. + explicit VirtualAllocationExplorerModel(int32_t num_allocation_models); + + /// Destructor. + virtual ~VirtualAllocationExplorerModel(); + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Set up the model when a snapshot is opened. + /// \param snapshot The snapshot being opened. + /// \return true if snapshot is valid, false otherwise. + bool OpenSnapshot(const RmtDataSnapshot* snapshot); + + /// Initialize the allocation table model. + /// \param table_view The view to the table. + /// \param num_rows Total rows of the table. + /// \param num_columns Total columns of the table. + void InitializeAllocationTableModel(ScaledTableView* table_view, uint num_rows, uint num_columns); + + /// Initialize the resource table model. + /// \param table_view The view to the table. + /// \param num_rows Total rows of the table. + /// \param num_columns Total columns of the table. + void InitializeResourceTableModel(ScaledTableView* table_view, uint num_rows, uint num_columns); + + /// Handle what happens when the allocation table search filter changes. + /// \param filter The search text filter. + void AllocationSearchBoxChanged(const QString& filter); + + /// Handle what happens when the allocation table size filter changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void AllocationSizeFilterChanged(int min_value, int max_value) const; + + /// Handle what happens when the resource table search filter changes. + /// \param filter The search text filter. + void ResourceSearchBoxChanged(const QString& filter); + + /// Handle what happens when the resource table size filter changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void ResourceSizeFilterChanged(int min_value, int max_value); + + /// Update the allocation table. Only needs to be done when loading in a new snapshot. + void UpdateAllocationTable(); + + /// Update the resource table. Updated when an allocation is selected. + /// \return The number of resources in the allocation. + int32_t UpdateResourceTable(); + + /// Build a list of resource size thresholds for the filter by size slider. + void BuildResourceSizeThresholds(); + + /// Get the allocation proxy model. Used to set up a connection between the table being sorted and the UI update. + /// \return the proxy model. + AllocationProxyModel* GetAllocationProxyModel() const; + + /// Get the resource proxy model. Used to set up a connection between the table being sorted and the UI update. + /// \return the proxy model. + ResourceProxyModel* GetResourceProxyModel() const; + + /// Get the model for the allocation bar. + /// \return The allocation bar model. + AllocationBarModel* GetAllocationBarModel() const; + + private: + AllocationBarModel* allocation_bar_model_; ///< The model for the allocation bar graph. + AllocationItemModel* allocation_table_model_; ///< Holds the allocation table data. + ResourceItemModel* resource_table_model_; ///< Holds the resource table data. + AllocationProxyModel* allocation_proxy_model_; ///< Allocation table proxy model. + ResourceProxyModel* resource_proxy_model_; ///< Resource table proxy model. + uint64_t resource_thresholds_[kSizeSliderRange + 1]; ///< List of resource size thresholds for the filter by size sliders. + uint64_t minimum_allocation_size_; ///< The size of the smallest allocation. + uint64_t maximum_allocation_size_; ///< The size of the largest allocation. + }; +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_ALLOCATION_EXPLORER_MODEL_H_ diff --git a/source/frontend/models/snapshot/allocation_overview_model.cpp b/source/frontend/models/snapshot/allocation_overview_model.cpp new file mode 100644 index 0000000..2e8c509 --- /dev/null +++ b/source/frontend/models/snapshot/allocation_overview_model.cpp @@ -0,0 +1,72 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for the allocation overview pane +//============================================================================= + +#include "models/snapshot/allocation_overview_model.h" + +#include + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" + +#include "models/trace_manager.h" +#include "util/string_util.h" + +namespace rmv +{ + AllocationOverviewModel::AllocationOverviewModel(int32_t num_allocation_models) + : ModelViewMapper(kAllocationOverviewNumWidgets) + , sort_mode_(AllocationOverviewModel::SortMode::kSortModeAllocationSize) + , sort_ascending_(false) + { + allocation_bar_model_ = new MultiAllocationBarModel(num_allocation_models); + ResetModelValues(); + } + + AllocationOverviewModel::~AllocationOverviewModel() + { + delete allocation_bar_model_; + } + + void AllocationOverviewModel::ResetModelValues() + { + allocation_bar_model_->ResetModelValues(); + } + + size_t AllocationOverviewModel::GetViewableAllocationCount() const + { + return allocation_bar_model_->GetViewableAllocationCount(); + } + + void AllocationOverviewModel::SetNormalizeAllocations(bool normalized) + { + allocation_bar_model_->SetNormalizeAllocations(normalized); + } + + void AllocationOverviewModel::Sort(int sort_mode, bool ascending) + { + sort_mode_ = static_cast(sort_mode); + sort_ascending_ = ascending; + allocation_bar_model_->Sort(sort_mode, ascending); + } + + void AllocationOverviewModel::ApplyFilters(const QString& filter_text, const bool* heap_array_flags) + { + ResetModelValues(); + allocation_bar_model_->ApplyAllocationFilters(filter_text, heap_array_flags, sort_mode_, sort_ascending_); + } + + size_t AllocationOverviewModel::SelectResource(RmtResourceIdentifier resource_identifier, int32_t model_index) + { + return allocation_bar_model_->SelectResource(resource_identifier, model_index); + } + + MultiAllocationBarModel* AllocationOverviewModel::GetAllocationBarModel() const + { + return allocation_bar_model_; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/allocation_overview_model.h b/source/frontend/models/snapshot/allocation_overview_model.h new file mode 100644 index 0000000..bd50e66 --- /dev/null +++ b/source/frontend/models/snapshot/allocation_overview_model.h @@ -0,0 +1,102 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Allocation overview pane +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_ALLOCATION_OVERVIEW_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_ALLOCATION_OVERVIEW_MODEL_H_ + +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_virtual_allocation_list.h" + +#include "models/allocation_multi_bar_model.h" + +namespace rmv +{ + /// Enum containing indices for the widgets shared between the model and UI. + enum AllocationOverviewWidgets + { + kAllocationOverviewNumWidgets, + }; + + class AllocationOverviewModel : public ModelViewMapper + { + public: + /// Sort modes available for memory allocations. Each sort mode should have a sort function. + /// The order here is the order the sort modes will be in the combo box (default at the top). + enum SortMode + { + kSortModeAllocationSize, + kSortModeAllocationID, + kSortModeAllocationAge, + kSortModeResourceCount, + kSortModeFragmentationScore, + + kSortModeCount + }; + + /// Sort direction (ascending or descending). + /// The order here is the order the sort directions will be in the combo box (default at the top). + enum SortDirection + { + kSortDirectionDescending, + kSortDirectionAscending, + + kSortDirectionCount + }; + + /// Constructor. + /// \param num_allocation_models The number of models to represent the allocations. + explicit AllocationOverviewModel(int32_t num_allocation_models); + + /// Destructor. + virtual ~AllocationOverviewModel(); + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Sort the allocations. + /// \param sort_mode The sort mode to sort by. + /// \param ascending Whether to use ascending or descending ordering. + void Sort(int sort_mode, bool ascending); + + /// Apply filters and rebuild the list of allocations. + /// \param filter_text The search text specified in the UI. + /// \param heap_array_flags An array of flags indicating if the corresponding heap + /// should be shown. + void ApplyFilters(const QString& filter_text, const bool* heap_array_flags); + + /// Get the number of viewable allocations. These are the allocations that can + /// be seen in the scene and are the ones which pass all the text and heap filtering + /// tests. + /// \return The number of viewable allocations. + size_t GetViewableAllocationCount() const; + + /// Set whether the allocations should be normalized. + /// \param normalized true if the allocations should be normalized, false if not. + void SetNormalizeAllocations(bool normalized); + + /// Select a resource on this pane. This is usually called when selecting a resource + /// on a different pane to make sure the resource selection is propagated to all + /// interested panes. + /// \param resource_identifier the resource identifier of the resource to select. + /// \param model_index The index of the model referred to. This pane uses a single model + /// to represent all the allocations. + /// \return The index in the scene of the selected resource. + size_t SelectResource(RmtResourceIdentifier resource_identifier, int32_t model_index); + + /// Get the model for the allocation bar. + /// \return The allocation bar model. + MultiAllocationBarModel* GetAllocationBarModel() const; + + private: + MultiAllocationBarModel* allocation_bar_model_; ///< The model for the allocation bar graphs. + AllocationOverviewModel::SortMode sort_mode_; ///< The sort mode to use for the comparison. + bool sort_ascending_; ///< If true, use ascending sort. Otherwise descending. + }; +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_ALLOCATION_OVERVIEW_MODEL_H_ diff --git a/source/frontend/models/snapshot/heap_overview_heap_model.cpp b/source/frontend/models/snapshot/heap_overview_heap_model.cpp new file mode 100644 index 0000000..f0bf141 --- /dev/null +++ b/source/frontend/models/snapshot/heap_overview_heap_model.cpp @@ -0,0 +1,183 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a model for a heap layout for the Heap Overview +/// pane +//============================================================================= + +#include "models/snapshot/heap_overview_heap_model.h" + +#include + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_print.h" + +#include "models/resource_sorter.h" +#include "models/trace_manager.h" +#include "util/string_util.h" + +namespace rmv +{ + static const QString kHeapDescriptions[kRmtHeapTypeCount] = { + QString("This heap is in local (video) memory. It is mappable by the CPU, but does not use the CPU cache."), + QString("This heap is in local (video) memory. It is not mappable by the CPU."), + QString("This heap is in host (system) memory. It is intended for write-only data on the CPU side.")}; + + static const QString kWarningHeader = "WARNING!
"; + + static const QString kWarningOverSubscribed = + "This heap is currently oversubscribed. This means more memory is requested from this heap than exists on your system."; + + static const QString kWarningCloseToOverSubscribed = + "This heap is very close to over-subscription which may cause paging of your allocations to a non-preferred heap."; + + HeapOverviewHeapModel::HeapOverviewHeapModel(RmtHeapType heap) + : ModelViewMapper(kHeapOverviewNumWidgets) + , heap_(heap) + , segment_status_{} + { + } + + HeapOverviewHeapModel::~HeapOverviewHeapModel() + { + } + + void HeapOverviewHeapModel::ResetModelValues() + { + memset(&segment_status_, 0, sizeof(RmtSegmentStatus)); + + SetModelData(kHeapOverviewTitle, "-"); + SetModelData(kHeapOverviewDescription, "-"); + + SetModelData(kHeapOverviewWarningText, "-"); + + SetModelData(kHeapOverviewLocation, "-"); + SetModelData(kHeapOverviewCpuCached, "-"); + SetModelData(kHeapOverviewCpuVisible, "-"); + SetModelData(kHeapOverviewGpuCached, "-"); + SetModelData(kHeapOverviewGpuVisible, "-"); + SetModelData(kHeapOverviewSmallestAllocation, "-"); + SetModelData(kHeapOverviewLargestAllocation, "-"); + SetModelData(kHeapOverviewMeanAllocation, "-"); + } + + bool HeapOverviewHeapModel::ShowSubscriptionWarning() + { + RmtSegmentSubscriptionStatus status = RmtSegmentStatusGetOversubscribed(&segment_status_); + + if (status == kRmtSegmentSubscriptionStatusOverLimit || status == kRmtSegmentSubscriptionStatusCloseToLimit) + { + return true; + } + return false; + } + + void HeapOverviewHeapModel::Update() + { + const RmtDataSnapshot* snapshot = GetSnapshot(); + if (snapshot == nullptr) + { + return; + } + + ResetModelValues(); + + // update global data + SetModelData(kHeapOverviewTitle, RmtGetHeapTypeNameFromHeapType(heap_)); + SetModelData(kHeapOverviewDescription, kHeapDescriptions[heap_]); + + // call the backend to get the segment data + RmtDataSnapshotGetSegmentStatus(snapshot, heap_, &segment_status_); + + // update subscription warning + RmtSegmentSubscriptionStatus status = RmtSegmentStatusGetOversubscribed(&segment_status_); + if (status == kRmtSegmentSubscriptionStatusOverLimit) + { + SetModelData(kHeapOverviewWarningText, kWarningHeader + kWarningOverSubscribed); + } + else if (status == kRmtSegmentSubscriptionStatusCloseToLimit) + { + SetModelData(kHeapOverviewWarningText, kWarningHeader + kWarningCloseToOverSubscribed); + } + + // update summary data + if ((segment_status_.flags & kRmtSegmentStatusFlagVram) != 0) + { + SetModelData(kHeapOverviewLocation, "Video memory"); + } + else if ((segment_status_.flags & kRmtSegmentStatusFlagHost) != 0) + { + SetModelData(kHeapOverviewLocation, "System memory"); + } + + SetModelData(kHeapOverviewCpuCached, ((segment_status_.flags & kRmtSegmentStatusFlagCpuCached) != 0) ? "Yes" : "No"); + SetModelData(kHeapOverviewCpuVisible, ((segment_status_.flags & kRmtSegmentStatusFlagCpuVisible) != 0) ? "Yes" : "No"); + SetModelData(kHeapOverviewGpuCached, ((segment_status_.flags & kRmtSegmentStatusFlagGpuCached) != 0) ? "Yes" : "No"); + SetModelData(kHeapOverviewGpuVisible, ((segment_status_.flags & kRmtSegmentStatusFlagGpuVisible) != 0) ? "Yes" : "No"); + SetModelData(kHeapOverviewSmallestAllocation, rmv::string_util::LocalizedValueMemory(segment_status_.min_allocation_size, false, false)); + SetModelData(kHeapOverviewLargestAllocation, rmv::string_util::LocalizedValueMemory(segment_status_.max_allocation_size, false, false)); + SetModelData(kHeapOverviewMeanAllocation, rmv::string_util::LocalizedValueMemory(segment_status_.mean_allocation_size, false, false)); + } + + void HeapOverviewHeapModel::GetMemoryParameters(uint64_t& total_physical_size, + uint64_t& total_virtual_memory_requested, + uint64_t& total_bound_virtual_memory, + uint64_t& total_physical_mapped_by_process, + uint64_t& total_physical_mapped_by_other_processes, + RmtSegmentSubscriptionStatus& subscription_status) const + { + total_physical_size = segment_status_.total_physical_size; + total_virtual_memory_requested = segment_status_.total_virtual_memory_requested; + total_bound_virtual_memory = segment_status_.total_bound_virtual_memory; + total_physical_mapped_by_process = segment_status_.total_physical_mapped_by_process; + total_physical_mapped_by_other_processes = segment_status_.total_physical_mapped_by_other_processes; + subscription_status = RmtSegmentStatusGetOversubscribed(&segment_status_); + } + + int HeapOverviewHeapModel::GetResourceData(int num_resources, uint64_t* resource_info, int* other_value) const + { + const uint64_t* physical_bytes_per_resource_usage = segment_status_.physical_bytes_per_resource_usage; + + // add all resource totals to the sorter and sort + ResourceSorter sorter; + for (int i = 0; i < kRmtResourceUsageTypeCount; i++) + { + sorter.AddResource((RmtResourceUsageType)i, physical_bytes_per_resource_usage[i]); + } + sorter.Sort(); + + // return the most abundant resources + int resource_count = 0; + for (resource_count = 0; resource_count < num_resources; resource_count++) + { + int64_t value = sorter.GetResourceValue(resource_count); + if (value <= 0) + { + break; + } + *resource_info++ = sorter.GetResourceType(resource_count); + *resource_info++ = value; + } + + // get what's left as a single value + *other_value = sorter.GetRemainder(num_resources); + + return resource_count; + } + + const RmtDataSnapshot* HeapOverviewHeapModel::GetSnapshot() const + { + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return nullptr; + } + + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + const RmtDataSnapshot* snapshot = open_snapshot; + RMT_ASSERT(snapshot != nullptr); + return snapshot; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/heap_overview_heap_model.h b/source/frontend/models/snapshot/heap_overview_heap_model.h new file mode 100644 index 0000000..c8e55c3 --- /dev/null +++ b/source/frontend/models/snapshot/heap_overview_heap_model.h @@ -0,0 +1,98 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a model for a heap layout for the Heap Overview pane +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_HEAP_OVERVIEW_HEAP_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_HEAP_OVERVIEW_HEAP_MODEL_H_ + +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_data_snapshot.h" +#include "rmt_types.h" + +namespace rmv +{ + /// Enum containing indices for the widgets shared between the model and UI. + enum HeapOverviewWidgets + { + // global widgets + kHeapOverviewTitle, + kHeapOverviewDescription, + + // bar graph widgets (first column) + kHeapOverviewWarningText, + + // summary widgets (middle column) + kHeapOverviewLocation, + kHeapOverviewCpuCached, + kHeapOverviewCpuVisible, + kHeapOverviewGpuCached, + kHeapOverviewGpuVisible, + kHeapOverviewSmallestAllocation, + kHeapOverviewLargestAllocation, + kHeapOverviewMeanAllocation, + + kHeapOverviewNumWidgets, + }; + + class QTableView; + + /// Container class that holds model data for a given pane. + class HeapOverviewHeapModel : public ModelViewMapper + { + public: + /// Constructor. + explicit HeapOverviewHeapModel(RmtHeapType heap); + + /// Destructor. + virtual ~HeapOverviewHeapModel(); + + /// Should the subscription warning be shown. + /// \return true if a warning should be shown, false if not. + bool ShowSubscriptionWarning(); + + /// Read the dataset and update model. + void Update(); + + /// Get the data for the resources. Returns a list of resources and their amounts, including + /// groups of smaller resources lumped under "other". + /// \param num_resources The number of resources to show explicitly. All other resources + /// will be grouped under "other". + /// \param resource_info An array to receive the explicit resource info. + /// \param other_value The value of the "other" resources. + /// \return The number of resources in the array. + int GetResourceData(int num_resources, uint64_t* resource_info, int* other_value) const; + + /// Get the memory parameters. Displayed in the UI as a series of horizontal bars. + /// \param total_physical_size A variable to receive the total physical memory size. + /// \param total_virtual_memory_requested A variable to receive the total virtual memory requested. + /// \param total_boud_virtual_memor A variable to receive the total virtual memory that was bound. + /// \param total_physical_mapped_by_process A variable to receive the total physical mapped memory + /// by the current process. + /// \param total_physical_mapped_by_other_processes A variable to receive the total physical mapped + /// memory by other processes. + /// \param subscription_status A variable to receive the subscription status. + void GetMemoryParameters(uint64_t& total_physical_size, + uint64_t& total_virtual_memory_requested, + uint64_t& total_bound_virtual_memory, + uint64_t& total_physical_mapped_by_process, + uint64_t& total_physical_mapped_by_other_processes, + RmtSegmentSubscriptionStatus& subscription_status) const; + + private: + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Get the currently opened snapshot. + /// \return A pointer to the currently opened snapshot (or nullptr if a snapshot isn't opened). + const RmtDataSnapshot* GetSnapshot() const; + + RmtHeapType heap_; ///< The heap for this widget. + RmtSegmentStatus segment_status_; ///< The currently cached segment status. + }; +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_HEAP_OVERVIEW_MODEL_H_ diff --git a/source/frontend/models/snapshot/memory_map_model.cpp b/source/frontend/models/snapshot/memory_map_model.cpp new file mode 100644 index 0000000..ae5006f --- /dev/null +++ b/source/frontend/models/snapshot/memory_map_model.cpp @@ -0,0 +1,115 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the memory map model. This handles any data needed +/// from the backend and passes it to the UI when requested +//============================================================================= + +#include "models/snapshot/memory_map_model.h" + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" + +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" + +namespace rmv +{ + MemoryMapModel::MemoryMapModel(Colorizer* colorizer) + : granularity_(0) + , num_blocks_(0) + , block_offset_(0) + , minimum_virtual_address_(0) + , maximum_virtual_address_(0) + , colorizer_(colorizer) + { + } + + MemoryMapModel::~MemoryMapModel() + { + } + + void MemoryMapModel::UpdateGranularity(int granularity) + { + granularity_ = granularity; + + // calculate block sizes based on trim values from the snapshot + const TraceManager& trace_manager = TraceManager::Get(); + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + if (trace_manager.DataSetValid() && open_snapshot != nullptr) + { + minimum_virtual_address_ = open_snapshot->minimum_virtual_address; + maximum_virtual_address_ = open_snapshot->maximum_virtual_address; + RMT_ASSERT(maximum_virtual_address_ >= minimum_virtual_address_); + + block_offset_ = minimum_virtual_address_ / granularity; + num_blocks_ = (maximum_virtual_address_ - minimum_virtual_address_) / granularity; + } + } + + const RmtVirtualAllocation* MemoryMapModel::GetAllocation(uint64_t base_address) const + { + const TraceManager& trace_manager = TraceManager::Get(); + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + if (trace_manager.DataSetValid() && (open_snapshot != nullptr)) + { + const RmtVirtualAllocation* current_allocation = nullptr; + RmtErrorCode error_code = + RmtVirtualAllocationListGetAllocationForAddress(&open_snapshot->virtual_allocation_list, base_address, ¤t_allocation); + if (error_code == RMT_OK && current_allocation != nullptr) + { + return current_allocation; + } + } + return nullptr; + } + + const RmtResource* MemoryMapModel::GetResource(uint64_t base_address) const + { + const TraceManager& trace_manager = TraceManager::Get(); + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + if (trace_manager.DataSetValid() && (open_snapshot != nullptr)) + { + const RmtVirtualAllocation* current_allocation = nullptr; + RmtErrorCode error_code = + RmtVirtualAllocationListGetAllocationForAddress(&open_snapshot->virtual_allocation_list, base_address, ¤t_allocation); + if (error_code == RMT_OK && current_allocation != nullptr) + { + // now walk the resources and find one with the base address. + for (int32_t current_resource_index = 0; current_resource_index < current_allocation->resource_count; ++current_resource_index) + { + const RmtResource* current_resource = current_allocation->resources[current_resource_index]; + if (current_resource->address <= base_address && base_address < (current_resource->address + current_resource->size_in_bytes)) + { + return current_resource; + } + } + + return nullptr; + } + } + return nullptr; + } + + uint64_t MemoryMapModel::GetBlockOffset() const + { + return block_offset_; + } + + uint64_t MemoryMapModel::GetNumBlocks() const + { + return num_blocks_; + } + + const QColor MemoryMapModel::GetColor(uint64_t block_offset) const + { + uint64_t visible_base_address = block_offset * granularity_; + visible_base_address += minimum_virtual_address_; + + const RmtVirtualAllocation* current_allocation = GetAllocation(visible_base_address); + const RmtResource* current_resource = GetResource(visible_base_address); + + return colorizer_->GetColor(current_allocation, current_resource); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/memory_map_model.h b/source/frontend/models/snapshot/memory_map_model.h new file mode 100644 index 0000000..eb50ba2 --- /dev/null +++ b/source/frontend/models/snapshot/memory_map_model.h @@ -0,0 +1,68 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the memory map model. +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_MEMORY_MAP_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_MEMORY_MAP_MODEL_H_ + +#include +#include + +#include "rmt_resource_list.h" +#include "rmt_types.h" +#include "rmt_virtual_allocation_list.h" + +#include "views/colorizer.h" + +namespace rmv +{ + class MemoryMapModel + { + public: + /// Constructor. + explicit MemoryMapModel(Colorizer* colorizer); + + /// Destructor. + virtual ~MemoryMapModel(); + + /// Update the granularity. Also causes the block data to be recalculated. + /// \param granularity The new granularity to use, in bytes. + void UpdateGranularity(int granularity); + + /// Get the color of a memory cell. This will depend on the granularity and the coloring mode. + /// \param block_offset The offset in blocks of the cell to color. The offset is based on the granularity. + /// \return The color to use. + const QColor GetColor(uint64_t block_offset) const; + + /// Get the block offset. + ///\ return The block offset. + uint64_t GetBlockOffset() const; + + /// Get the total number of memory blocks at the current granularity. + /// \return The number of memory blocks. + uint64_t GetNumBlocks() const; + + private: + /// Get the allocation corresponding to a given base address. + /// \param baseAddress The base address to find the allocation for. + /// \return The virtual allocation corresponding to the given base address. + const RmtVirtualAllocation* GetAllocation(uint64_t base_address) const; + + /// Get the resource corresponding to a given base address. + /// \param baseAddress The base address to find the resource for. + /// \return The resource corresponding to the given base address. + const RmtResource* GetResource(uint64_t base_address) const; + + int granularity_; ///< The current granularity set in the UI, in bytes. + uint64_t num_blocks_; ///< Total number of blocks at the current granularity. + uint64_t block_offset_; ///< The offset into the memory map, in blocks at the current granularity. + RmtGpuAddress minimum_virtual_address_; ///< Trimmed start address of the memory map. Anything before this is not important. + RmtGpuAddress maximum_virtual_address_; ///< Trimmed end address of the memory map. Anything after this is not important. + const Colorizer* colorizer_; ///< The colorizer used by the 'color by' combo box. + }; +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_MEMORY_MAP_MODEL_H_ diff --git a/source/frontend/models/snapshot/resource_details_model.cpp b/source/frontend/models/snapshot/resource_details_model.cpp new file mode 100644 index 0000000..b90b52b --- /dev/null +++ b/source/frontend/models/snapshot/resource_details_model.cpp @@ -0,0 +1,510 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the Resource details model +//============================================================================= + +#include "models/snapshot/resource_details_model.h" + +#include + +#include "qt_common/utils/qt_util.h" + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" +#include "rmt_util.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" +#include "util/string_util.h" +#include "util/time_util.h" +#include "views/colorizer.h" + +namespace rmv +{ + ResourceDetailsModel::ResourceDetailsModel() + : ModelViewMapper(kResourceDetailsNumWidgets) + , timeline_model_(nullptr) + , timeline_proxy_model_(nullptr) + , properties_model_(nullptr) + , highlighted_row_(-1) + { + resource_history_.event_count = -1; + } + + ResourceDetailsModel::~ResourceDetailsModel() + { + delete timeline_model_; + delete properties_model_; + } + + void ResourceDetailsModel::InitializeTimelineTableModel(QTableView* timeline_table_view, uint num_rows, uint num_columns) + { + if (timeline_proxy_model_ != nullptr) + { + delete timeline_proxy_model_; + timeline_proxy_model_ = nullptr; + } + timeline_proxy_model_ = new ResourceDetailsProxyModel(); + timeline_model_ = timeline_proxy_model_->InitializeResourceTableModels(timeline_table_view, num_rows, num_columns); + } + + void ResourceDetailsModel::InitializePropertiesTableModel(QTableView* properties_table_view, uint num_rows, uint num_columns) + { + RMT_ASSERT(properties_model_ == nullptr); + properties_model_ = new rmv::ResourcePropertiesModel(); + properties_model_->InitializeTableModel(properties_table_view, num_rows, num_columns); + } + + bool ResourceDetailsModel::IsResourceValid(RmtResourceIdentifier resource_identifier) const + { + const RmtResource* resource = nullptr; + + return GetResourceFromResourceId(resource_identifier, &resource); + } + + bool ResourceDetailsModel::IsResourceBaseAddressValid(RmtResourceIdentifier resource_identifier) const + { + const RmtResource* resource = nullptr; + if (GetResourceFromResourceId(resource_identifier, &resource) == true) + { + const uint64_t base_address = (resource->bound_allocation != nullptr) ? resource->bound_allocation->base_address : 0; + if (base_address != 0) + { + return true; + } + } + return false; + } + + bool ResourceDetailsModel::GetResourceFromResourceId(RmtResourceIdentifier resource_identifier, const RmtResource** resource) const + { + const TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + const RmtDataSnapshot* snapshot = open_snapshot; + if (snapshot != nullptr) + { + const RmtErrorCode error_code = RmtResourceListGetResourceByResourceId(&snapshot->resource_list, resource_identifier, resource); + if (error_code == RMT_OK) + { + return true; + } + } + } + return false; + } + + void ResourceDetailsModel::ResetModelValues() + { + timeline_model_->removeRows(0, timeline_model_->rowCount()); + SetModelData(kResourceDetailsResourceName, "-"); + + SetModelData(kResourceDetailsAllocationBaseAddress, "-"); + SetModelData(kResourceDetailsAllocationOffset, "-"); + SetModelData(kResourceDetailsBaseAddress, "-"); + SetModelData(kResourceDetailsSize, "-"); + SetModelData(kResourceDetailsType, "-"); + SetModelData(kResourceDetailsHeap, "-"); + SetModelData(kResourceDetailsFullyMapped, "-"); + SetModelData(kResourceDetailsUnmappedPercentage, "-"); + SetModelData(kResourceDetailsCreateTime, "-"); + SetModelData(kResourceDetailsBindTime, "-"); + SetModelData(kResourceDetailsCommitTime, "-"); + SetModelData(kResourceDetailsOwnerTime, "-"); + SetModelData(kResourceDetailsFlags, "-"); + + highlighted_row_ = -1; + } + + int32_t ResourceDetailsModel::Update(RmtResourceIdentifier resource_identifier) + { + ResetModelValues(); + UpdateTimelineTable(); + + const RmtResource* resource = nullptr; + if (GetResourceFromResourceId(resource_identifier, &resource) == true) + { + const char buffer[RMT_MAXIMUM_NAME_LENGTH] = " - "; + const char* buf_ptr = &buffer[0]; + RmtResourceGetName(resource, RMT_MAXIMUM_NAME_LENGTH, (char**)&buf_ptr); + SetModelData(kResourceDetailsResourceName, buffer); + + const uint64_t base_address = (resource->bound_allocation != nullptr) ? resource->bound_allocation->base_address : 0; + if (base_address != 0) + { + SetModelData(kResourceDetailsAllocationBaseAddress, QString("0x") + QString::number(base_address, 16)); + } + else + { + SetModelData(kResourceDetailsAllocationBaseAddress, QString("Orphaned")); + } + SetModelData(kResourceDetailsAllocationOffset, rmv::string_util::LocalizedValue(RmtResourceGetOffsetFromBoundAllocation(resource))); + SetModelData(kResourceDetailsBaseAddress, QString("0x") + QString::number(resource->address, 16)); + SetModelData(kResourceDetailsSize, rmv::string_util::LocalizedValueMemory(resource->size_in_bytes, false, false)); + SetModelData(kResourceDetailsType, rmv::string_util::GetResourceUsageString(RmtResourceGetUsageType(resource))); + SetModelData(kResourceDetailsHeap, RmtResourceGetHeapTypeName(resource)); + + const TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + SetModelData(kResourceDetailsFullyMapped, QString(RmtResourceIsCompletelyInPreferredHeap(open_snapshot, resource) ? "Yes" : "No")); + + // calculate histogram. + uint64_t memory_segment_histogram[kRmtResourceBackingStorageCount] = {0}; + RmtResourceGetBackingStorageHistogram(open_snapshot, resource, memory_segment_histogram); + double unmapped_percentage = ((double)memory_segment_histogram[kRmtResourceBackingStorageUnmapped] / (double)resource->size_in_bytes) * 100; + SetModelData(kResourceDetailsUnmappedPercentage, QString::number(unmapped_percentage) + "%"); + } + + SetModelData(kResourceDetailsCreateTime, rmv::time_util::ClockToTimeUnit(resource->create_time)); + SetModelData(kResourceDetailsBindTime, rmv::time_util::ClockToTimeUnit(resource->bind_time)); + SetModelData(kResourceDetailsCommitTime, RmtGetCommitTypeNameFromCommitType(resource->commit_type)); + SetModelData(kResourceDetailsOwnerTime, QString::number(resource->owner_type)); + SetModelData(kResourceDetailsFlags, QString::number(resource->flags)); + } + + return properties_model_->Update(resource_identifier); + } + + void ResourceDetailsModel::UpdateTimelineTable() + { + uint64_t snapshot_timestamp = 0; + + const TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSnapshot* snapshot = trace_manager.GetOpenSnapshot(); + if (snapshot != nullptr) + { + snapshot_timestamp = snapshot->timestamp; + } + } + + int32_t event_count = resource_history_.event_count; + timeline_model_->SetRowCount(event_count + 1); + + // find the index of the snapshot timestamp + int32_t event_index = 0; + for (int32_t table_row = 0; table_row < event_count + 1; table_row++) + { + uint64_t timestamp = resource_history_.events[event_index].timestamp; + if (table_row >= event_count || timestamp > snapshot_timestamp) + { + timeline_model_->SetSnapshotParameters(table_row, snapshot_timestamp, &resource_history_); + break; + } + + event_index++; + } + timeline_proxy_model_->invalidate(); + } + + void ResourceDetailsModel::TimelineEventSelected(const QModelIndex& proxy_index) + { + if (proxy_index.isValid()) + { + QModelIndex source_index = timeline_proxy_model_->mapToSource(proxy_index); + highlighted_row_ = source_index.row(); + } + } + + QColor ResourceDetailsModel::GetColorFromEventType(RmtResourceHistoryEventType event_type, bool highlighted) const + { + if (highlighted) + { + return RMVSettings::Get().GetColorResourceHistoryHighlight(); + } + + switch (event_type) + { + case kRmtResourceHistoryEventResourceCreated: + case kRmtResourceHistoryEventResourceDestroyed: + case kRmtResourceHistoryEventResourceBound: + return RMVSettings::Get().GetColorResourceHistoryResourceEvent(); + + case kRmtResourceHistoryEventVirtualMemoryMapped: + case kRmtResourceHistoryEventVirtualMemoryUnmapped: + case kRmtResourceHistoryEventVirtualMemoryAllocated: + case kRmtResourceHistoryEventVirtualMemoryFree: + return RMVSettings::Get().GetColorResourceHistoryCpuMapping(); + + case kRmtResourceHistoryEventVirtualMemoryMakeResident: + case kRmtResourceHistoryEventVirtualMemoryEvict: + return RMVSettings::Get().GetColorResourceHistoryResidencyUpdate(); + + case kRmtResourceHistoryEventPhysicalMapToLocal: + case kRmtResourceHistoryEventPhysicalMapToHost: + case kRmtResourceHistoryEventPhysicalUnmap: + case kRmtResourceHistoryEventBackingMemoryPaged: + return RMVSettings::Get().GetColorResourceHistoryPageTableUpdate(); + + case kRmtResourceHistoryEventSnapshotTaken: + return RMVSettings::Get().GetColorResourceHistorySnapshot(); + + default: + RMT_ASSERT_MESSAGE(false, "Invalid event type"); + return QColor(Qt::black); + } + } + + ResourceIconShape ResourceDetailsModel::GetShapeFromEventType(RmtResourceHistoryEventType event_type) const + { + switch (event_type) + { + case kRmtResourceHistoryEventResourceCreated: + case kRmtResourceHistoryEventVirtualMemoryMapped: + case kRmtResourceHistoryEventVirtualMemoryMakeResident: + case kRmtResourceHistoryEventPhysicalMapToLocal: + case kRmtResourceHistoryEventPhysicalMapToHost: + return kIconShapeCircle; + + case kRmtResourceHistoryEventResourceDestroyed: + case kRmtResourceHistoryEventVirtualMemoryUnmapped: + case kRmtResourceHistoryEventVirtualMemoryEvict: + case kRmtResourceHistoryEventPhysicalUnmap: + return kIconShapeCross; + + case kRmtResourceHistoryEventResourceBound: + case kRmtResourceHistoryEventVirtualMemoryAllocated: + return kIconShapeTriangle; + + case kRmtResourceHistoryEventVirtualMemoryFree: + return kIconShapeSquare; + + case kRmtResourceHistoryEventBackingMemoryPaged: + return kIconShapeSquare; + + case kRmtResourceHistoryEventSnapshotTaken: + return kIconShapeInvertedTriangle; + + default: + RMT_ASSERT_MESSAGE(false, "Invalid event type"); + return kIconShapeCross; + } + } + + void ResourceDetailsModel::GenerateResourceHistory(RmtResourceIdentifier resource_identifier) + { + const TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + resource_history_.event_count = -1; + const RmtResource* resource = nullptr; + GetResourceFromResourceId(resource_identifier, &resource); + + RmtDataSnapshotGenerateResourceHistory(open_snapshot, resource, &resource_history_); + } + } + + int ResourceDetailsModel::GetEventRowFromTimeline(double logical_position, double icon_size) + { + int32_t event_count = resource_history_.event_count; + uint64_t start_timestamp = resource_history_.events[0].timestamp; + uint64_t end_timestamp = resource_history_.events[event_count - 1].timestamp; + double duration = end_timestamp - start_timestamp; + + logical_position *= duration; + logical_position += start_timestamp; + icon_size *= duration; + + // go through the list and decide what's been clicked on + highlighted_row_ = 0; + + for (int32_t count = 0; count < timeline_model_->rowCount(); count++) + { + uint64_t min_time = timeline_model_->data(timeline_model_->index(count, kResourceHistoryTime), Qt::UserRole).toULongLong(); + uint64_t max_time = min_time + icon_size; + if (logical_position > min_time && logical_position < max_time) + { + // map from proxy model + QModelIndex index = timeline_model_->index(highlighted_row_, 0, QModelIndex()); + QModelIndex proxy_index = timeline_proxy_model_->mapFromSource(index); + return proxy_index.row(); + } + highlighted_row_++; + } + highlighted_row_ = -1; + + return highlighted_row_; + } + + bool ResourceDetailsModel::GetEventData(int index, int width, ResourceEvent& out_event_data) const + { + uint64_t snapshot_timestamp = UINT64_MAX; + const TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSnapshot* snapshot = trace_manager.GetOpenSnapshot(); + if (snapshot != nullptr) + { + snapshot_timestamp = snapshot->timestamp; + } + } + + int32_t event_count = timeline_model_->rowCount(); + uint64_t start_timestamp = resource_history_.events[0].timestamp; + uint64_t end_timestamp = resource_history_.events[resource_history_.event_count - 1].timestamp; + double duration = end_timestamp - start_timestamp; + + if (index < 0 || index >= event_count) + { + return false; + } + + if (highlighted_row_ != -1 && index >= highlighted_row_) + { + // if there's a highlighted row, defer the drawing of it to last + if (index < event_count - 1) + { + index++; + } + else + { + index = highlighted_row_; + } + } + + const QModelIndex legend_model_index = timeline_model_->index(index, kResourceHistoryLegend); + RmtResourceHistoryEventType event_type = static_cast(timeline_model_->data(legend_model_index).toInt()); + int event_index = timeline_model_->data(legend_model_index, Qt::UserRole).toInt(); + uint64_t timestamp = 0; + if (event_type == kRmtResourceHistoryEventSnapshotTaken) + { + timestamp = snapshot_timestamp; + } + else + { + timestamp = resource_history_.events[event_index].timestamp; + } + + timestamp -= start_timestamp; + timestamp *= width; + timestamp /= duration; + out_event_data.timestamp = timestamp; + + bool highlighted = false; + if (index == highlighted_row_) + { + highlighted = true; + } + + out_event_data.color = GetColorFromEventType(event_type, highlighted); + out_event_data.shape = GetShapeFromEventType(event_type); + + return true; + } + + bool ResourceDetailsModel::GetResidencyData(RmtResourceIdentifier resource_identifier, int index, int& value, QString& name, QColor& color) const + { + if (index < 0 || index >= kRmtResourceBackingStorageCount) + { + return false; + } + + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return false; + } + const RmtResource* resource = nullptr; + + if (GetResourceFromResourceId(resource_identifier, &resource) == false) + { + return false; + } + + // calculate histogram to get residency per heap. + uint64_t memory_segment_histogram[kRmtResourceBackingStorageCount] = {0}; + + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + RmtResourceGetBackingStorageHistogram(open_snapshot, resource, memory_segment_histogram); + + value = 0; + if (resource->size_in_bytes > 0) + { + value = (memory_segment_histogram[index] * 100) / resource->size_in_bytes; + } + + color = Colorizer::GetHeapColor(static_cast(index)); + + switch (index) + { + case kRmtHeapTypeLocal: + name = "Local"; + break; + case kRmtHeapTypeInvisible: + name = "Invisible"; + break; + case kRmtHeapTypeSystem: + name = "Host"; + break; + case kRmtResourceBackingStorageUnmapped: + name = "Unmapped"; + break; + } + + return true; + } + + bool ResourceDetailsModel::GetUnmappedResidencyData(RmtResourceIdentifier resource_identifier, int& value, QString& name, QColor& color) const + { + const TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return false; + } + + const RmtResource* resource = nullptr; + + if (GetResourceFromResourceId(resource_identifier, &resource) == false) + { + return false; + } + + // calculate histogram to get residency per heap. + uint64_t memory_segment_histogram[kRmtResourceBackingStorageCount] = {0}; + + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + RmtResourceGetBackingStorageHistogram(open_snapshot, resource, memory_segment_histogram); + + value = 0; + if (resource->size_in_bytes > 0) + { + value = (memory_segment_histogram[kRmtResourceBackingStorageUnmapped] * 100) / resource->size_in_bytes; + } + + color = RMVSettings::Get().GetColorResourceFreeSpace(); + + name = "Unmapped"; + + return true; + } + + bool ResourceDetailsModel::PhysicalMemoryInPreferredHeap(RmtResourceIdentifier resource_identifier) const + { + const RmtResource* resource = nullptr; + if (GetResourceFromResourceId(resource_identifier, &resource) == true) + { + const TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + return RmtPageTableIsEntireResourcePhysicallyMapped(&open_snapshot->page_table, resource); + } + } + return false; + } + + ResourceDetailsProxyModel* ResourceDetailsModel::GetTimelineProxyModel() const + { + return timeline_proxy_model_; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/resource_details_model.h b/source/frontend/models/snapshot/resource_details_model.h new file mode 100644 index 0000000..257a6da --- /dev/null +++ b/source/frontend/models/snapshot/resource_details_model.h @@ -0,0 +1,194 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Resource details model +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_RESOURCE_DETAILS_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_RESOURCE_DETAILS_MODEL_H_ + +#include + +#include "qt_common/custom_widgets/scaled_table_view.h" +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_resource_history.h" +#include "rmt_resource_list.h" + +#include "models/proxy_models/resource_details_proxy_model.h" +#include "models/snapshot/resource_properties_model.h" +#include "models/snapshot/resource_timeline_item_model.h" + +namespace rmv +{ + /// Enum containing indices for the widgets shared between the model and UI. + enum ResourceDetailsWidgets + { + kResourceDetailsResourceName, + + kResourceDetailsAllocationBaseAddress, + kResourceDetailsAllocationOffset, + kResourceDetailsBaseAddress, + kResourceDetailsSize, + kResourceDetailsType, + kResourceDetailsHeap, + kResourceDetailsFullyMapped, + kResourceDetailsUnmappedPercentage, + kResourceDetailsCreateTime, + kResourceDetailsBindTime, + kResourceDetailsCommitTime, + kResourceDetailsOwnerTime, + kResourceDetailsFlags, + + kResourceDetailsNumWidgets, + }; + + /// Enum containing indices for the icon shapes. + enum ResourceIconShape + { + kIconShapeCross, + kIconShapeCircle, + kIconShapeTriangle, + kIconShapeInvertedTriangle, + kIconShapeSquare, + }; + + /// struct containing resource event data for the timeline icons. + struct ResourceEvent + { + ResourceEvent() + : timestamp(0) + , shape(kIconShapeCross) + { + } + + uint64_t timestamp; ///< The timestamp for the event. + QColor color; ///< The event color. + ResourceIconShape shape; ///< The event shape. + }; + + /// Container class that holds model data for a given pane. + class ResourceDetailsModel : public ModelViewMapper + { + public: + /// Constructor. + explicit ResourceDetailsModel(); + + /// Destructor. + virtual ~ResourceDetailsModel(); + + /// Is the resource valid. + /// \param resource_identifier The resource to check for validity. + /// \return true if resource is valid, false if not. + bool IsResourceValid(RmtResourceIdentifier resource_identifier) const; + + /// Get whether the resource base address is valid. + /// \param resource_identifier The resource whose base address to check for validity. + /// \return true if base address is valid, false if not (orphaned). + bool IsResourceBaseAddressValid(RmtResourceIdentifier resource_identifier) const; + + /// Initialize the timeline table model. + /// \param timeline_table_view The view to the table. + /// \param num_rows Total rows of the table. + /// \param num_columns Total columns of the table. + void InitializeTimelineTableModel(QTableView* timeline_table_view, uint num_rows, uint num_columns); + + /// Initialize the resource properties table model. + /// \param properties_table_view The view to the table. + /// \param num_rows Total rows of the table. + /// \param num_columns Total columns of the table. + void InitializePropertiesTableModel(QTableView* properties_table_view, uint num_rows, uint num_columns); + + /// Get the resource event data for a particular index. All events for a resource are logged in an + /// array. + /// \param index The resource event table index to query. + /// \param width The width of the timeline. Timeline output will be scaled. + /// \param out_event_data The variable to accept the resource event data. + /// \return true if event data was successfully obtained, false if error (ie index out of range). + bool GetEventData(int index, int width, ResourceEvent& out_event_data) const; + + /// Update the model. + /// \param resource_identifier The identifier of the resource being shown. + /// \return The number of properties for the resource. + int32_t Update(RmtResourceIdentifier resource_identifier); + + /// Get the row in the resource event table that corresponds to the event selected on the timeline. + /// Coordinate values passed in are logical positions between 0.0 and 1.0, where 0.0 corresponds to + /// the left of the timeline and 1.0 corresponds to the right. + /// \param logical_position The logical position clicked on in the timeline. + /// \param icon_size The size of the icon in logical coordinates. + /// \return The row in the table, or -1 if nothing selected in the timeline. + int GetEventRowFromTimeline(double logical_position, double icon_size); + + /// Get the color based on the event type. + /// \param event_type The type of resource event. + /// \param highlighted Is the event to be highlighted (true if so). + /// \return The required color. + QColor GetColorFromEventType(RmtResourceHistoryEventType event_type, bool highlighted) const; + + /// Get the shape based on the event type. + /// \param event_type The type of resource event. + /// \return The required shape. + ResourceIconShape GetShapeFromEventType(RmtResourceHistoryEventType event_type) const; + + /// Generate the resource history from the backend data. + /// This is run in a background thread so it's important to check the data is valid before + /// trying to access it. + void GenerateResourceHistory(RmtResourceIdentifier resource_identifier); + + /// Get the data for the heap residency. + /// \param resource_identifier The ID of the resource to obtain. + /// \param index The index of the heap (an RmtHeapType). + /// \param [out] value The percentage value of memory in the heap specified. + /// \param [out] name The name of the heap specified. + /// \param [out] color The color to use for the heap specified. + /// \return true if residency data was found, false if error. + bool GetResidencyData(RmtResourceIdentifier resource_identifier, int index, int& value, QString& name, QColor& color) const; + + /// Get the data for the unmapped memory. + /// \param resource_identifier The ID of the resource to obtain. + /// \param [out] value The percentage value of unmapped memory. + /// \param [out] name The name to use for the unmapped memory. + /// \param [out] color The color to use for the unmapped memory. + /// \return true if residency data was found, false if error. + bool GetUnmappedResidencyData(RmtResourceIdentifier resource_identifier, int& value, QString& name, QColor& color) const; + + /// Is all the physical memory mapped to the preferred heap. + /// This will be used to show a warning message in the UI. + /// \param resource_identifier The ID of the resource to check. + /// \return true if all physical memory is where it was requested, false if not. + bool PhysicalMemoryInPreferredHeap(RmtResourceIdentifier resource_identifier) const; + + /// Get the timeline proxy model. Used to set up a connection between the table being sorted and the UI update. + /// \return the timeline proxy model. + ResourceDetailsProxyModel* GetTimelineProxyModel() const; + + public slots: + /// Slot to handle what happens when a row is selected in the timeline table. + /// \param proxy_index The proxy model index. + void TimelineEventSelected(const QModelIndex& proxy_index); + + private: + /// Get a resource from its ID. + /// \param resource_identifier The ID of the resource to obtain. + /// \param resource The returned resource (if valid). + /// \return true if the resource has been found, false if error. + bool GetResourceFromResourceId(RmtResourceIdentifier resource_identifier, const RmtResource** resource) const; + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Update the resource timeline table. + void UpdateTimelineTable(); + + ResourceTimelineItemModel* timeline_model_; ///< Holds data for the resource timeline table. + ResourceDetailsProxyModel* timeline_proxy_model_; ///< Timeline table proxy. + rmv::ResourcePropertiesModel* properties_model_; ///< Holds data for the resource properties model. + int highlighted_row_; ///< The row in the timeline table currently selected. + RmtResourceHistory resource_history_; ///< The resource history for the selected event. + }; + +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_RESOURCE_DETAILS_MODEL_H_ diff --git a/source/frontend/models/snapshot/resource_list_model.cpp b/source/frontend/models/snapshot/resource_list_model.cpp new file mode 100644 index 0000000..51d3c26 --- /dev/null +++ b/source/frontend/models/snapshot/resource_list_model.cpp @@ -0,0 +1,141 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for Allocation List pane +//============================================================================= + +#include "models/snapshot/resource_list_model.h" + +#include + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" +#include "rmt_util.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/trace_manager.h" +#include "util/string_util.h" + +namespace rmv +{ + ResourceListModel::ResourceListModel() + : ModelViewMapper(kResourceListNumWidgets) + , table_model_(nullptr) + , proxy_model_(nullptr) + { + } + + ResourceListModel::~ResourceListModel() + { + delete table_model_; + } + + void ResourceListModel::ResetModelValues() + { + table_model_->removeRows(0, table_model_->rowCount()); + table_model_->SetRowCount(0); + + SetModelData(kResourceListTotalResources, "-"); + SetModelData(kResourceListTotalSize, "-"); + } + + void ResourceListModel::UpdateBottomLabels() + { + int32_t row_count = proxy_model_->rowCount(); + int64_t total_size = 0; + + for (int32_t i = 0; i < row_count; i++) + { + const uint64_t size = proxy_model_->GetData(i, kResourceColumnSize); + total_size += size; + } + + SetModelData(kResourceListTotalResources, rmv::string_util::LocalizedValue(proxy_model_->rowCount())); + SetModelData(kResourceListTotalSize, rmv::string_util::LocalizedValueMemory(total_size, false, false)); + } + + void ResourceListModel::Update() + { + ResetModelValues(); + UpdateTable(); + UpdateBottomLabels(); + } + + void ResourceListModel::UpdateTable() + { + const TraceManager& trace_manager = TraceManager::Get(); + RmtDataSnapshot* snapshot = trace_manager.GetOpenSnapshot(); + + if (trace_manager.DataSetValid() && (snapshot != nullptr)) + { + const RmtResourceList* resource_list = &snapshot->resource_list; + + RMT_ASSERT(resource_list); + + table_model_->SetRowCount(resource_list->resource_count); + for (int32_t currentResourceIndex = 0; currentResourceIndex < resource_list->resource_count; currentResourceIndex++) + { + const RmtResource* resource = &resource_list->resources[currentResourceIndex]; + table_model_->AddResource(snapshot, resource, kSnapshotCompareIdUndefined); + } + proxy_model_->invalidate(); + } + } + + void ResourceListModel::UpdatePreferredHeapList(const QString& preferred_heap_filter) + { + proxy_model_->SetPreferredHeapFilter(preferred_heap_filter); + proxy_model_->invalidate(); + + UpdateBottomLabels(); + } + + void ResourceListModel::UpdateResourceUsageList(const QString& resource_usage_filter) + { + proxy_model_->SetResourceUsageFilter(resource_usage_filter); + proxy_model_->invalidate(); + + UpdateBottomLabels(); + } + + void ResourceListModel::InitializeTableModel(ScaledTableView* table_view, uint num_rows, uint num_columns) + { + if (proxy_model_ != nullptr) + { + delete proxy_model_; + proxy_model_ = nullptr; + } + + proxy_model_ = new ResourceProxyModel(); + table_model_ = proxy_model_->InitializeResourceTableModels(table_view, num_rows, num_columns); + table_model_->Initialize(table_view, false); + } + + void ResourceListModel::SearchBoxChanged(const QString& filter) + { + proxy_model_->SetSearchFilter(filter); + proxy_model_->invalidate(); + + UpdateBottomLabels(); + } + + void ResourceListModel::FilterBySizeChanged(int min_value, int max_value) + { + const TraceManager& trace_manager = TraceManager::Get(); + const uint64_t scaled_min = trace_manager.GetSizeFilterThreshold(min_value); + const uint64_t scaled_max = trace_manager.GetSizeFilterThreshold(max_value); + + proxy_model_->SetSizeFilter(scaled_min, scaled_max); + proxy_model_->invalidate(); + + UpdateBottomLabels(); + } + + ResourceProxyModel* ResourceListModel::GetResourceProxyModel() const + { + return proxy_model_; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/resource_list_model.h b/source/frontend/models/snapshot/resource_list_model.h new file mode 100644 index 0000000..8482527 --- /dev/null +++ b/source/frontend/models/snapshot/resource_list_model.h @@ -0,0 +1,87 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Allocation List pane +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_RESOURCE_LIST_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_RESOURCE_LIST_MODEL_H_ + +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" +#include "qt_common/custom_widgets/scaled_table_view.h" +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_resource_list.h" + +#include "models/proxy_models/resource_proxy_model.h" +#include "models/resource_item_model.h" +#include "util/definitions.h" + +namespace rmv +{ + /// Enum containing indices for the widgets shared between the model and UI. + enum ResourceListWidgets + { + kResourceListTotalResources, + kResourceListTotalSize, + + kResourceListNumWidgets, + }; + + /// Container class that holds model data for a given pane. + class ResourceListModel : public ModelViewMapper + { + public: + /// Constructor. + explicit ResourceListModel(); + + /// Destructor. + virtual ~ResourceListModel(); + + /// Initialize the table model. + /// \param table_view The view to the table. + /// \param num_rows Total rows of the table. + /// \param num_columns Total columns of the table. + void InitializeTableModel(ScaledTableView* table_view, uint num_rows, uint num_columns); + + /// Handle what happens when user changes the filter. + /// \param filter new text filter. + void SearchBoxChanged(const QString& filter); + + /// Handle what happens when the size filter changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void FilterBySizeChanged(int min_value, int max_value); + + /// Read the dataset and update model. + void Update(); + + /// Update the list of heaps selected. This is set up from the preferred heap combo box. + /// \param preferred_heap_filter The regular expression string of selected heaps. + void UpdatePreferredHeapList(const QString& preferred_heap_filter); + + /// Update the list of resources available. This is set up from the resource usage combo box. + /// \param resource_usage_filter The regular expression string of selected resource usage types. + void UpdateResourceUsageList(const QString& resource_usage_filter); + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Get the resource proxy model. Used to set up a connection between the table being sorted and the UI update. + /// \return the proxy model. + ResourceProxyModel* GetResourceProxyModel() const; + + private: + /// Update the labels on the bottom. + void UpdateBottomLabels(); + + /// Update the resource list table. + void UpdateTable(); + + ResourceItemModel* table_model_; ///< Resource table model data. + ResourceProxyModel* proxy_model_; ///< Proxy model for resource table. + }; +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_RESOURCE_LIST_MODEL_H_ diff --git a/source/frontend/models/snapshot/resource_overview_model.cpp b/source/frontend/models/snapshot/resource_overview_model.cpp new file mode 100644 index 0000000..11758b5 --- /dev/null +++ b/source/frontend/models/snapshot/resource_overview_model.cpp @@ -0,0 +1,91 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for the Resource Overview pane +//============================================================================= + +#include "models/snapshot/resource_overview_model.h" + +#include + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" + +#include "models/trace_manager.h" +#include "util/string_util.h" + +namespace rmv +{ + ResourceOverviewModel::ResourceOverviewModel() + : ModelViewMapper(kResourceOverviewNumWidgets) + , min_resource_size_(0) + , max_resource_size_(0) + { + } + + ResourceOverviewModel::~ResourceOverviewModel() + { + } + + void ResourceOverviewModel::ResetModelValues() + { + SetModelData(kResourceOverviewTotalAvailableSize, "-"); + SetModelData(kResourceOverviewTotalAllocatedAndUsed, "-"); + SetModelData(kResourceOverviewTotalAllocatedAndUnused, "-"); + SetModelData(kResourceOverviewAllocationCount, "-"); + SetModelData(kResourceOverviewResourceCount, "-"); + + const TraceManager& trace_manager = TraceManager::Get(); + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + if (trace_manager.DataSetValid() && (open_snapshot != nullptr)) + { + min_resource_size_ = open_snapshot->minimum_resource_size_in_bytes; + max_resource_size_ = open_snapshot->maximum_resource_size_in_bytes; + } + } + + void ResourceOverviewModel::FilterBySizeChanged(int min_value, int max_value) + { + const TraceManager& trace_manager = TraceManager::Get(); + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + if (trace_manager.DataSetValid() && (open_snapshot != nullptr)) + { + min_resource_size_ = trace_manager.GetSizeFilterThreshold(min_value); + max_resource_size_ = trace_manager.GetSizeFilterThreshold(max_value); + } + } + + bool ResourceOverviewModel::IsSizeInRange(uint64_t resource_size) const + { + if (resource_size >= min_resource_size_ && resource_size <= max_resource_size_) + { + return true; + } + return false; + } + + void ResourceOverviewModel::Update() + { + ResetModelValues(); + + const TraceManager& trace_manager = TraceManager::Get(); + const RmtDataSnapshot* snapshot = trace_manager.GetOpenSnapshot(); + if (!trace_manager.DataSetValid() || snapshot == nullptr) + { + return; + } + + const uint64_t total_available = RmtVirtualAllocationListGetTotalSizeInBytes(&snapshot->virtual_allocation_list); + const uint64_t allocated_and_used = RmtVirtualAllocationListGetBoundTotalSizeInBytes(snapshot, &snapshot->virtual_allocation_list); + const uint64_t allocated_and_unused = RmtVirtualAllocationListGetUnboundTotalSizeInBytes(snapshot, &snapshot->virtual_allocation_list); + + SetModelData(kResourceOverviewTotalAvailableSize, rmv::string_util::LocalizedValueMemory(total_available, false, false)); + SetModelData(kResourceOverviewTotalAllocatedAndUsed, rmv::string_util::LocalizedValueMemory(allocated_and_used, false, false)); + SetModelData(kResourceOverviewTotalAllocatedAndUnused, rmv::string_util::LocalizedValueMemory(allocated_and_unused, false, false)); + + SetModelData(kResourceOverviewAllocationCount, rmv::string_util::LocalizedValue(snapshot->virtual_allocation_list.allocation_count)); + SetModelData(kResourceOverviewResourceCount, rmv::string_util::LocalizedValue(snapshot->resource_list.resource_count)); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/resource_overview_model.h b/source/frontend/models/snapshot/resource_overview_model.h new file mode 100644 index 0000000..e01eb7a --- /dev/null +++ b/source/frontend/models/snapshot/resource_overview_model.h @@ -0,0 +1,59 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Resource Overview pane +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_RESOURCE_OVERVIEW_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_RESOURCE_OVERVIEW_MODEL_H_ + +#include "qt_common/utils/model_view_mapper.h" + +namespace rmv +{ + /// Enum containing indices for the widgets shared between the model and UI. + enum ResourceOverviewWidgets + { + kResourceOverviewTotalAvailableSize, + kResourceOverviewTotalAllocatedAndUsed, + kResourceOverviewTotalAllocatedAndUnused, + kResourceOverviewAllocationCount, + kResourceOverviewResourceCount, + + kResourceOverviewNumWidgets, + }; + + /// Container class that holds model data for a given pane. + class ResourceOverviewModel : public ModelViewMapper + { + public: + /// Constructor. + explicit ResourceOverviewModel(); + + /// Destructor. + virtual ~ResourceOverviewModel(); + + /// Handle what happens when the size filter changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void FilterBySizeChanged(int min_value, int max_value); + + /// Check to see if a resource size is within the slider range. + /// \param resource_size The size of the resource to check. + /// \return true if the size is in range, false otherwise. + bool IsSizeInRange(uint64_t resource_size) const; + + /// Update the model. + void Update(); + + private: + /// Initialize blank data for the model. + void ResetModelValues(); + + uint64_t min_resource_size_; ///< The minimum resource size to show. + uint64_t max_resource_size_; ///< The maximum resource size to show. + }; +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_RESOURCE_OVERVIEW_MODEL_H_ diff --git a/source/frontend/models/snapshot/resource_properties_model.cpp b/source/frontend/models/snapshot/resource_properties_model.cpp new file mode 100644 index 0000000..bcf2180 --- /dev/null +++ b/source/frontend/models/snapshot/resource_properties_model.cpp @@ -0,0 +1,345 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the Resource properties model. This is a model +/// to go with a QTableView showing the properties for each type of resource. +/// The table will be populated with properties specific to a resource type. +//============================================================================= + +#include "models/snapshot/resource_properties_model.h" + +#include + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" +#include "rmt_util.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/trace_manager.h" +#include "util/string_util.h" + +namespace rmv +{ + // Enum for column indices in the resource properties table. + enum ResourcePropertiesColumn + { + kResourcePropertyName, + kResourcePropertyValue, + }; + + // Some sufficiently large value to set up the number of rows in the + // table so the row count doesn't need to be precalculated (it will + // vary depending on the resource type). + static const int kMaxProperties = 200; + + ResourcePropertiesModel::ResourcePropertiesModel() + : table_model_(nullptr) + { + } + + ResourcePropertiesModel::~ResourcePropertiesModel() + { + delete table_model_; + } + + void ResourcePropertiesModel::InitializeTableModel(QTableView* table_view, uint num_rows, uint num_columns) + { + table_model_ = new QStandardItemModel(num_rows, num_columns); + + int columns = 0; + table_model_->setHorizontalHeaderItem(columns++, new QStandardItem("Property name")); + table_model_->setHorizontalHeaderItem(columns++, new QStandardItem("Property value")); + + table_model_->setColumnCount(columns); + + table_view->setModel(table_model_); + } + + void ResourcePropertiesModel::ResetModelValues() + { + table_model_->removeRows(0, table_model_->rowCount()); + } + + int32_t ResourcePropertiesModel::Update(RmtResourceIdentifier resource_identifier) + { + ResetModelValues(); + return UpdateTable(resource_identifier); + } + + int32_t ResourcePropertiesModel::UpdateTable(RmtResourceIdentifier resource_identifier) + { + const TraceManager& trace_manager = TraceManager::Get(); + int row_index = 0; + + if (trace_manager.DataSetValid()) + { + const RmtDataSnapshot* snapshot = trace_manager.GetOpenSnapshot(); + if (snapshot != nullptr) + { + const RmtResource* resource = nullptr; + const RmtErrorCode error_code = RmtResourceListGetResourceByResourceId(&snapshot->resource_list, resource_identifier, &resource); + if (error_code == RMT_OK) + { + table_model_->setRowCount(kMaxProperties); + + switch (resource->resource_type) + { + case kRmtResourceTypeImage: + row_index = AddImageTableData(resource, row_index); + break; + + case kRmtResourceTypeBuffer: + row_index = AddBufferTableData(resource, row_index); + break; + + case kRmtResourceTypeGpuEvent: + row_index = AddGPUEventTableData(resource, row_index); + break; + + case kRmtResourceTypeBorderColorPalette: + row_index = AddBorderColorPaletteTableData(resource, row_index); + break; + + case kRmtResourceTypePerfExperiment: + row_index = AddPerfExperimentTableData(resource, row_index); + break; + + case kRmtResourceTypeQueryHeap: + row_index = AddQueryHeapTableData(resource, row_index); + break; + + case kRmtResourceTypeVideoDecoder: + row_index = AddVideoDecoderTableData(resource, row_index); + break; + + case kRmtResourceTypeVideoEncoder: + row_index = AddVideoEncoderTableData(resource, row_index); + break; + + case kRmtResourceTypeHeap: + row_index = AddHeapTableData(resource, row_index); + break; + + case kRmtResourceTypePipeline: + row_index = AddPipelineTableData(resource, row_index); + break; + + case kRmtResourceTypeDescriptorHeap: + row_index = AddDescriptorHeapTableData(resource, row_index); + break; + + case kRmtResourceTypeDescriptorPool: + row_index = AddDescriptorPoolTableData(resource, row_index); + break; + + case kRmtResourceTypeCommandAllocator: + row_index = AddCommandAllocatorTableData(resource, row_index); + break; + + default: + break; + } + + table_model_->setRowCount(row_index); + } + } + } + return row_index; + } + + void ResourcePropertiesModel::SetupResourceRow(QString name, QString value, int row) + { + table_model_->setData(table_model_->index(row, kResourcePropertyName), name); + table_model_->setData(table_model_->index(row, kResourcePropertyValue), value); + } + + int ResourcePropertiesModel::AddImageTableData(const RmtResource* resource, int row_index) + { + char flags_text[1024]; + + RmtGetImageCreationNameFromImageCreationFlags(resource->image.create_flags, flags_text, 1024); + SetupResourceRow("Create flags", flags_text, row_index++); + + RmtGetImageUsageNameFromImageUsageFlags(resource->image.usage_flags, flags_text, 1024); + SetupResourceRow("Usage flags", flags_text, row_index++); + + SetupResourceRow("Image type", RmtGetImageTypeNameFromImageType(resource->image.image_type), row_index++); + SetupResourceRow("X Dimension", rmv::string_util::LocalizedValue(resource->image.dimension_x), row_index++); + SetupResourceRow("Y Dimension", rmv::string_util::LocalizedValue(resource->image.dimension_y), row_index++); + SetupResourceRow("Z Dimension", rmv::string_util::LocalizedValue(resource->image.dimension_z), row_index++); + + SetupResourceRow("Format", QString(RmtGetFormatNameFromFormat(resource->image.format.format)), row_index++); + + char swizzle_pattern[8]; + RmtGetSwizzlePatternFromImageFormat(&resource->image.format, swizzle_pattern, sizeof(swizzle_pattern)); + SetupResourceRow("Swizzle", QString(swizzle_pattern), row_index++); + + SetupResourceRow("Mip levels", rmv::string_util::LocalizedValue(resource->image.mip_levels), row_index++); + SetupResourceRow("Slices", rmv::string_util::LocalizedValue(resource->image.slices), row_index++); + SetupResourceRow("Sample count", rmv::string_util::LocalizedValue(resource->image.sample_count), row_index++); + SetupResourceRow("Fragment count", rmv::string_util::LocalizedValue(resource->image.fragment_count), row_index++); + SetupResourceRow("Tiling type", RmtGetTilingNameFromTilingType(resource->image.tiling_type), row_index++); + SetupResourceRow( + "Tiling optimization mode", RmtGetTilingOptimizationModeNameFromTilingOptimizationMode(resource->image.tiling_optimization_mode), row_index++); + SetupResourceRow("Metadata mode", rmv::string_util::LocalizedValue(resource->image.metadata_mode), row_index++); + SetupResourceRow("Max base alignment", rmv::string_util::LocalizedValueMemory(resource->image.max_base_alignment, false, false), row_index++); + SetupResourceRow("Image offset", rmv::string_util::LocalizedValueMemory(resource->image.image_offset, false, false), row_index++); + SetupResourceRow("Image size", rmv::string_util::LocalizedValueMemory(resource->image.image_size, false, false), row_index++); + SetupResourceRow("Image alignment", rmv::string_util::LocalizedValueMemory(resource->image.image_alignment, false, false), row_index++); + SetupResourceRow("Metadata head offset", rmv::string_util::LocalizedValueMemory(resource->image.metadata_head_offset, false, false), row_index++); + SetupResourceRow("Metadata head size", rmv::string_util::LocalizedValueMemory(resource->image.metadata_head_size, false, false), row_index++); + SetupResourceRow("Metadata head alignment", rmv::string_util::LocalizedValueMemory(resource->image.metadata_head_alignment, false, false), row_index++); + SetupResourceRow("Metadata tail offset", rmv::string_util::LocalizedValueMemory(resource->image.metadata_tail_offset, false, false), row_index++); + SetupResourceRow("Metadata tail size", rmv::string_util::LocalizedValueMemory(resource->image.metadata_tail_size, false, false), row_index++); + SetupResourceRow("Metadata tail alignment", rmv::string_util::LocalizedValueMemory(resource->image.metadata_tail_alignment, false, false), row_index++); + SetupResourceRow("Presentable", resource->image.presentable == true ? "True" : "False", row_index++); + SetupResourceRow("Fullscreen", resource->image.fullscreen == true ? "True" : "False", row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddBufferTableData(const RmtResource* resource, int row_index) + { + char flags_text[1024]; + + RmtGetBufferCreationNameFromBufferCreationFlags(resource->buffer.create_flags, flags_text, 1024); + SetupResourceRow("Create flags", flags_text, row_index++); + + RmtGetBufferUsageNameFromBufferUsageFlags(resource->buffer.usage_flags, flags_text, 1024); + SetupResourceRow("Usage flags", flags_text, row_index++); + + SetupResourceRow("Size", rmv::string_util::LocalizedValueMemory(resource->buffer.size_in_bytes, false, false), row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddGPUEventTableData(const RmtResource* resource, int row_index) + { + char flags_text[1024]; + + RmtGetGpuEventNameFromGpuEventFlags(resource->gpu_event.flags, flags_text, 1024); + SetupResourceRow("Flags", flags_text, row_index++); + + return row_index; + } + + int ResourcePropertiesModel::AddBorderColorPaletteTableData(const RmtResource* resource, int row_index) + { + SetupResourceRow("Size in entries", rmv::string_util::LocalizedValue(resource->border_color_palette.size_in_entries), row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddPerfExperimentTableData(const RmtResource* resource, int row_index) + { + SetupResourceRow("SPM memory size", rmv::string_util::LocalizedValueMemory(resource->perf_experiment.spm_size, false, false), row_index++); + SetupResourceRow("SQTT memory size", rmv::string_util::LocalizedValueMemory(resource->perf_experiment.sqtt_size, false, false), row_index++); + SetupResourceRow("Counter memory size", rmv::string_util::LocalizedValueMemory(resource->perf_experiment.counter_size, false, false), row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddQueryHeapTableData(const RmtResource* resource, int row_index) + { + SetupResourceRow("Heap type", rmv::string_util::LocalizedValue(resource->query_heap.heap_type), row_index++); + SetupResourceRow("Enable CPU access", resource->query_heap.enable_cpu_access == true ? "True" : "False", row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddVideoDecoderTableData(const RmtResource* resource, int row_index) + { + SetupResourceRow("Engine type", rmv::string_util::LocalizedValue(resource->video_decoder.engine_type), row_index++); + SetupResourceRow("Decoder type", rmv::string_util::LocalizedValue(resource->video_decoder.decoder_type), row_index++); + SetupResourceRow("Width", rmv::string_util::LocalizedValue(resource->video_decoder.width), row_index++); + SetupResourceRow("Height", rmv::string_util::LocalizedValue(resource->video_decoder.height), row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddVideoEncoderTableData(const RmtResource* resource, int row_index) + { + SetupResourceRow("Engine type", rmv::string_util::LocalizedValue(resource->video_encoder.engine_type), row_index++); + SetupResourceRow("Encoder type", rmv::string_util::LocalizedValue(resource->video_encoder.encoder_type), row_index++); + SetupResourceRow("Width", rmv::string_util::LocalizedValue(resource->video_encoder.width), row_index++); + SetupResourceRow("Height", rmv::string_util::LocalizedValue(resource->video_encoder.height), row_index++); + SetupResourceRow( + "Format", + QString(RmtGetFormatNameFromFormat(resource->video_encoder.format.format)) + " (" + QString::number(resource->video_encoder.format.format) + ")", + row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddHeapTableData(const RmtResource* resource, int row_index) + { + SetupResourceRow("Flags", rmv::string_util::LocalizedValue(resource->heap.flags), row_index++); + SetupResourceRow("Size", rmv::string_util::LocalizedValueMemory(resource->heap.size, false, false), row_index++); + SetupResourceRow("Alignment", rmv::string_util::LocalizedValue(resource->heap.alignment), row_index++); + SetupResourceRow("Segment index", rmv::string_util::LocalizedValue(resource->heap.segment_index), row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddPipelineTableData(const RmtResource* resource, int row_index) + { + char flags_text[1024]; + RmtGetPipelineCreationNameFromPipelineCreationFlags(resource->pipeline.create_flags, flags_text, 1024); + SetupResourceRow("Create flags", flags_text, row_index++); + + SetupResourceRow( + "Internal Pipeline hash", + rmv::string_util::Convert128BitHashToString(resource->pipeline.internal_pipeline_hash_hi, resource->pipeline.internal_pipeline_hash_lo), + row_index++); + + RmtGetPipelineStageNameFromPipelineStageFlags(resource->pipeline.stage_mask, flags_text, 1024); + SetupResourceRow("Stage mask", flags_text, row_index++); + + SetupResourceRow("Is NGG", resource->pipeline.is_ngg == true ? "True" : "False", row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddDescriptorHeapTableData(const RmtResource* resource, int row_index) + { + SetupResourceRow("Descriptor Type", rmv::string_util::LocalizedValue(resource->descriptor_heap.descriptor_type), row_index++); + SetupResourceRow("Shader visible", resource->descriptor_heap.shader_visible == true ? "True" : "False", row_index++); + SetupResourceRow("GPU mask", rmv::string_util::LocalizedValue(resource->descriptor_heap.gpu_mask), row_index++); + SetupResourceRow("Num descriptors", rmv::string_util::LocalizedValue(resource->descriptor_heap.num_descriptors), row_index++); + return row_index; + } + + int ResourcePropertiesModel::AddDescriptorPoolTableData(const RmtResource* resource, int row_index) + { + SetupResourceRow("Max sets", rmv::string_util::LocalizedValue(resource->descriptor_pool.max_sets), row_index++); + SetupResourceRow("Pools count", rmv::string_util::LocalizedValue(resource->descriptor_pool.pools_count), row_index++); + + RMT_ASSERT(resource->descriptor_pool.pools_count < RMT_MAX_POOLS); + for (int i = 0; i < resource->descriptor_pool.pools_count; i++) + { + QString typeString = QString("Pool[%1] type").arg(i); + QString descriptorString = QString("Pool[%1] descriptor count").arg(i); + + SetupResourceRow(typeString, rmv::string_util::LocalizedValue(resource->descriptor_pool.pools[i].type), row_index++); + SetupResourceRow(descriptorString, rmv::string_util::LocalizedValue(resource->descriptor_pool.pools[i].num_descriptors), row_index++); + } + return row_index; + } + + int ResourcePropertiesModel::AddCommandAllocatorTableData(const RmtResource* resource, int row_index) + { + char flags_text[1024]; + RmtGetCmdAllocatorNameFromCmdAllocatorFlags(resource->command_allocator.flags, flags_text, 1024); + + SetupResourceRow("Flags", flags_text, row_index++); + SetupResourceRow("Executable preferred heap", rmv::string_util::LocalizedValue(resource->command_allocator.cmd_data_heap), row_index++); + SetupResourceRow("Executable size", rmv::string_util::LocalizedValueMemory(resource->command_allocator.cmd_data_size, false, false), row_index++); + SetupResourceRow( + "Executable suballoc size", rmv::string_util::LocalizedValueMemory(resource->command_allocator.cmd_data_suballoc_size, false, false), row_index++); + SetupResourceRow("Embedded preferred heap", rmv::string_util::LocalizedValue(resource->command_allocator.embed_data_heap), row_index++); + SetupResourceRow("Embedded size", rmv::string_util::LocalizedValueMemory(resource->command_allocator.embed_data_size, false, false), row_index++); + SetupResourceRow( + "Embedded suballoc size", rmv::string_util::LocalizedValueMemory(resource->command_allocator.embed_data_suballoc_size, false, false), row_index++); + SetupResourceRow("GPU scratch preferred heap", rmv::string_util::LocalizedValue(resource->command_allocator.embed_data_heap), row_index++); + SetupResourceRow("GPU scratch size", rmv::string_util::LocalizedValueMemory(resource->command_allocator.embed_data_size, false, false), row_index++); + SetupResourceRow("GPU scratch suballoc size", + rmv::string_util::LocalizedValueMemory(resource->command_allocator.embed_data_suballoc_size, false, false), + row_index++); + return row_index; + } + +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/resource_properties_model.h b/source/frontend/models/snapshot/resource_properties_model.h new file mode 100644 index 0000000..9f90772 --- /dev/null +++ b/source/frontend/models/snapshot/resource_properties_model.h @@ -0,0 +1,140 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Resource properties model. This is a model to go +/// with a QTableView showing the properties for each type of resource. +/// The table will be populated with properties specific to a resource type. +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_RESOURCE_PROPERTIES_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_RESOURCE_PROPERTIES_MODEL_H_ + +#include + +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_resource_list.h" + +namespace rmv +{ + /// Container class that holds model data for the resource properties table + /// in the resource details pane. + class ResourcePropertiesModel + { + public: + /// Constructor. + ResourcePropertiesModel(); + + /// Destructor. + ~ResourcePropertiesModel(); + + /// Initialize the table model. + /// \param table_view The view to the table. + /// \param num_rows Total rows of the table. + /// \param num_columns Total columns of the table. + void InitializeTableModel(QTableView* table_view, uint num_rows, uint num_columns); + + /// Update the model. + /// \param resource_identifier The resource identifier. + /// \return The number of properties for the resource. + int32_t Update(RmtResourceIdentifier resource_identifier); + + private: + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Update the resource list table. + /// \param resource_identifier the resource identifier. + /// \return The number of properties for the resource. + int32_t UpdateTable(RmtResourceIdentifier resource_identifier); + + /// Set up the data for 1 row in the table. + /// \param name The property name. + /// \param value The property value. + /// \param row The row in the table where the data is to be written. + void SetupResourceRow(QString name, QString value, int row); + + /// Add the image properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddImageTableData(const RmtResource* resource, int row_index); + + /// Add the buffer properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddBufferTableData(const RmtResource* resource, int row_index); + + /// Add the GPU event properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddGPUEventTableData(const RmtResource* resource, int row_index); + + /// Add the border color palette properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddBorderColorPaletteTableData(const RmtResource* resource, int row_index); + + /// Add the perf experiment properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddPerfExperimentTableData(const RmtResource* resource, int row_index); + + /// Add the query heap properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddQueryHeapTableData(const RmtResource* resource, int row_index); + + /// Add the video decoder properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddVideoDecoderTableData(const RmtResource* resource, int row_index); + + /// Add the video encoder properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddVideoEncoderTableData(const RmtResource* resource, int row_index); + + /// Add the heap properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddHeapTableData(const RmtResource* resource, int row_index); + + /// Add the pipeline properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddPipelineTableData(const RmtResource* resource, int row_index); + + /// Add the descriptor heap properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddDescriptorHeapTableData(const RmtResource* resource, int row_index); + + /// Add the descriptor pool properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddDescriptorPoolTableData(const RmtResource* resource, int row_index); + + /// Add the command allocator properties to the table. + /// \param resource The resource containing the data to add to the table. + /// \param row_index The index of the row in the table where data is to be written. + /// \return the new row index. + int AddCommandAllocatorTableData(const RmtResource* resource, int row_index); + + QStandardItemModel* table_model_; ///< Holds table data. + }; +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_RESOURCE_PROPERTIES_MODEL_H_ diff --git a/source/frontend/models/snapshot/resource_timeline_item_model.cpp b/source/frontend/models/snapshot/resource_timeline_item_model.cpp new file mode 100644 index 0000000..b37d605 --- /dev/null +++ b/source/frontend/models/snapshot/resource_timeline_item_model.cpp @@ -0,0 +1,239 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a resource timeline item model. Used for the +/// resource timeline table in the resource details pane +//============================================================================= + +#include "models/snapshot/resource_timeline_item_model.h" + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" +#include "rmt_resource_list.h" +#include "rmt_util.h" + +#include "models/trace_manager.h" +#include "util/time_util.h" + +namespace rmv +{ + ResourceTimelineItemModel::ResourceTimelineItemModel(QObject* parent) + : QAbstractItemModel(parent) + , num_rows_(0) + , num_columns_(0) + , snapshot_table_index_(0) + , snapshot_timestamp_(0) + , resource_history_(nullptr) + { + } + + ResourceTimelineItemModel::~ResourceTimelineItemModel() + { + } + + void ResourceTimelineItemModel::SetRowCount(int rows) + { + num_rows_ = rows; + } + + void ResourceTimelineItemModel::SetColumnCount(int columns) + { + num_columns_ = columns; + } + + void ResourceTimelineItemModel::SetSnapshotParameters(int32_t snapshot_table_index, uint64_t snapshot_timestamp, RmtResourceHistory* resource_history) + { + snapshot_table_index_ = snapshot_table_index; + snapshot_timestamp_ = snapshot_timestamp; + resource_history_ = resource_history; + } + + QVariant ResourceTimelineItemModel::data(const QModelIndex& index, int role) const + { + if (!index.isValid()) + { + return QVariant(); + } + + if (resource_history_ == nullptr) + { + return QVariant(); + } + + int row = index.row(); + + // set up data for snapshot entry + uint64_t timestamp = snapshot_timestamp_; + RmtResourceHistoryEventType event_type = kRmtResourceHistoryEventSnapshotTaken; + int row_index = row; + + if (row != snapshot_table_index_) + { + // if not snapshot and after the snapshot position, row index in the history data is + // 1 less than the table row since the snapshot position has been added to the table + if (row > snapshot_table_index_) + { + row_index = row - 1; + } + event_type = resource_history_->events[row_index].event_type; + timestamp = resource_history_->events[row_index].timestamp; + } + + if (role == Qt::DisplayRole) + { + switch (index.column()) + { + case kResourceHistoryLegend: + return event_type; + case kResourceHistoryEvent: + return GetTextFromEventType(event_type); + case kResourceHistoryTime: + return rmv::time_util::ClockToTimeUnit(timestamp); + + default: + break; + } + } + else if (role == Qt::UserRole) + { + switch (index.column()) + { + case kResourceHistoryLegend: + return QVariant::fromValue(row_index); + case kResourceHistoryEvent: + return QVariant::fromValue(event_type); + case kResourceHistoryTime: + return QVariant::fromValue(timestamp); + + default: + break; + } + } + else if (role == Qt::TextColorRole) + { + switch (index.column()) + { + case kResourceHistoryEvent: + case kResourceHistoryTime: + if (row > snapshot_table_index_) + { + return QColor(Qt::lightGray); + } + + default: + break; + } + } + + return QVariant(); + } + + Qt::ItemFlags ResourceTimelineItemModel::flags(const QModelIndex& index) const + { + return QAbstractItemModel::flags(index); + } + + QVariant ResourceTimelineItemModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case kResourceHistoryLegend: + return "Legend"; + case kResourceHistoryEvent: + return "Event"; + case kResourceHistoryTime: + return "Timestamp"; + case kResourceHistoryDetails: + return "Details"; + + default: + break; + } + } + + return QAbstractItemModel::headerData(section, orientation, role); + } + + QModelIndex ResourceTimelineItemModel::index(int row, int column, const QModelIndex& parent) const + { + if (!hasIndex(row, column, parent)) + { + return QModelIndex(); + } + + return createIndex(row, column); + } + + QModelIndex ResourceTimelineItemModel::parent(const QModelIndex& index) const + { + Q_UNUSED(index); + return QModelIndex(); + } + + int ResourceTimelineItemModel::rowCount(const QModelIndex& parent) const + { + Q_UNUSED(parent); + return num_rows_; + } + + int ResourceTimelineItemModel::columnCount(const QModelIndex& parent) const + { + Q_UNUSED(parent); + return num_columns_; + } + + QString ResourceTimelineItemModel::GetTextFromEventType(RmtResourceHistoryEventType event_type) const + { + switch (event_type) + { + case kRmtResourceHistoryEventResourceCreated: + return "Resource created"; + + case kRmtResourceHistoryEventResourceBound: + return "Resource bound"; + + case kRmtResourceHistoryEventVirtualMemoryMapped: + return "CPU Mapped"; + + case kRmtResourceHistoryEventVirtualMemoryUnmapped: + return "CPU Unmapped"; + + case kRmtResourceHistoryEventBackingMemoryPaged: + return "Page table updated"; + + case kRmtResourceHistoryEventVirtualMemoryMakeResident: + return "Made Resident"; + + case kRmtResourceHistoryEventVirtualMemoryEvict: + return "Evicted"; + + case kRmtResourceHistoryEventResourceDestroyed: + return "Resource destroyed"; + + case kRmtResourceHistoryEventVirtualMemoryAllocated: + return "Virtual memory allocated"; + + case kRmtResourceHistoryEventVirtualMemoryFree: + return "Virtual memory freed"; + + case kRmtResourceHistoryEventPhysicalMapToLocal: + return "Physical memory mapped to VRAM"; + + case kRmtResourceHistoryEventPhysicalUnmap: + return "Physical memory unmapped"; + + case kRmtResourceHistoryEventPhysicalMapToHost: + return "Physical memory mapped to host"; + + case kRmtResourceHistoryEventSnapshotTaken: + return "Snapshot taken"; + + default: + return "-"; + break; + } + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/snapshot/resource_timeline_item_model.h b/source/frontend/models/snapshot/resource_timeline_item_model.h new file mode 100644 index 0000000..244bc92 --- /dev/null +++ b/source/frontend/models/snapshot/resource_timeline_item_model.h @@ -0,0 +1,76 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a resource timeline item model. Used for the resource +/// timeline table in the resource details pane +//============================================================================= + +#ifndef RMV_MODELS_SNAPSHOT_RESOURCE_TIMELINE_ITEM_MODEL_H_ +#define RMV_MODELS_SNAPSHOT_RESOURCE_TIMELINE_ITEM_MODEL_H_ + +#include + +#include "rmt_resource_history.h" + +/// Column Id's for fields in the resource history table in the resource details pane. +enum ResourceHistoryColumn +{ + kResourceHistoryLegend, + kResourceHistoryEvent, + kResourceHistoryTime, + kResourceHistoryDetails, + + kResourceHistoryCount +}; + +namespace rmv +{ + /// Container class that holds model data for the resource timeline table + /// in the resource details pane. + class ResourceTimelineItemModel : public QAbstractItemModel + { + public: + /// Constructor. + explicit ResourceTimelineItemModel(QObject* parent = nullptr); + + /// Destructor. + ~ResourceTimelineItemModel(); + + /// Set the number of rows in the table. + /// \param rows The number of rows required. + void SetRowCount(int rows); + + /// Set the number of columns in the table. + /// \param columns The number of columns required. + void SetColumnCount(int columns); + + /// Set the snapshot parameters. + /// \param snapshot_table_index The table index when the snapshot occurred. + /// \param snapshot_timestamp The time when the snapshot was taken. + /// \param resource_history Pointer to the generated reosurce history data. + void SetSnapshotParameters(int32_t snapshot_table_index, uint64_t snapshot_timestamp, RmtResourceHistory* resource_history); + + // QAbstractItemModel overrides. See Qt documentation for parameter and return values + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + Qt::ItemFlags flags(const QModelIndex& index) const Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex parent(const QModelIndex& index) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + + private: + /// Get the text based on the event type. + /// \param event_type The type of resource event. + QString GetTextFromEventType(RmtResourceHistoryEventType event_type) const; + + int num_rows_; ///< The number of rows in the table. + int num_columns_; ///< The number of columns in the table. + int32_t snapshot_table_index_; ///< The table index when the snapshot was taken. + uint64_t snapshot_timestamp_; ///< The time the snapshot was taken. + RmtResourceHistory* resource_history_; ///< Pointer to generated resource history. + }; +} // namespace rmv + +#endif // RMV_MODELS_SNAPSHOT_RESOURCE_TIMELINE_ITEM_MODEL_H_ diff --git a/source/frontend/models/snapshot_manager.cpp b/source/frontend/models/snapshot_manager.cpp new file mode 100644 index 0000000..8a568c3 --- /dev/null +++ b/source/frontend/models/snapshot_manager.cpp @@ -0,0 +1,162 @@ +//============================================================================== +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the RMV Snapshot Manager. +//============================================================================== + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" + +#include "models/message_manager.h" +#include "models/snapshot_manager.h" +#include "models/trace_manager.h" + +/// Worker class definition to generate a snapshot on a separate thread. +class SnapshotWorker : public rmv::BackgroundTask +{ +public: + /// Constructor for single snapshot generation. + /// \param snapshot_point The object containing the snapshot information. + explicit SnapshotWorker(RmtSnapshotPoint* snapshot_point) + : BackgroundTask() + { + snapshot_point_[kSnapshotCompareBase] = snapshot_point; + snapshot_point_[kSnapshotCompareDiff] = nullptr; + snapshot_[kSnapshotCompareBase] = &snapshot_point->cached_snapshot; + snapshot_[kSnapshotCompareDiff] = nullptr; + } + + /// Constructor for comparison snapshot generation. + /// \param snapshot_base_point The object containing the snapshot information for the base snapshot. + /// \param snapshot_diff_point The object containing the snapshot information for the diff snapshot. + explicit SnapshotWorker(RmtSnapshotPoint* snapshot_base_point, RmtSnapshotPoint* snapshot_diff_point) + : BackgroundTask() + { + snapshot_point_[kSnapshotCompareBase] = snapshot_base_point; + snapshot_point_[kSnapshotCompareDiff] = snapshot_diff_point; + snapshot_[kSnapshotCompareBase] = &snapshot_base_point->cached_snapshot; + snapshot_[kSnapshotCompareDiff] = &snapshot_diff_point->cached_snapshot; + } + + /// Destructor + ~SnapshotWorker() + { + } + + /// Worker thread function. + virtual void ThreadFunc() + { + GenerateSnapshot(kSnapshotCompareBase); + GenerateSnapshot(kSnapshotCompareDiff); + + if (snapshot_[kSnapshotCompareBase] != nullptr) + { + if (snapshot_[kSnapshotCompareDiff] != nullptr) + { + emit MessageManager::Get().CompareSnapshot(snapshot_point_[kSnapshotCompareBase], snapshot_point_[kSnapshotCompareDiff]); + } + else + { + emit MessageManager::Get().OpenSnapshot(snapshot_point_[kSnapshotCompareBase]); + } + } + } + +private: + /// Call the backend function to generate the snapshot. If the snapshot is already + /// cached, use that instead. + /// \param index The index of the snapshot point to use (base or diff). + void GenerateSnapshot(int32_t index) + { + if (snapshot_point_[index] != nullptr) + { + if (snapshot_point_[index]->cached_snapshot == nullptr) + { + RmtDataSnapshot* new_snapshot = new RmtDataSnapshot(); + RmtDataSet* data_set = TraceManager::Get().GetDataSet(); + const RmtErrorCode error_code = RmtDataSetGenerateSnapshot(data_set, snapshot_point_[index], new_snapshot); + RMT_ASSERT(error_code == RMT_OK); + + *snapshot_[index] = new_snapshot; + } + else + { + *snapshot_[index] = snapshot_point_[index]->cached_snapshot; + } + } + } + + RmtSnapshotPoint* snapshot_point_[kSnapshotCompareCount]; ///< The snapshot point the snapshot was taken. + RmtDataSnapshot** snapshot_[kSnapshotCompareCount]; ///< Pointer to where the snapshot is stored. +}; + +// Ths single snapshot manager instance. +static SnapshotManager snapshot_manager; + +SnapshotManager::SnapshotManager() + : thread_controller_(nullptr) + , selected_snapshot_(nullptr) +{ +} + +SnapshotManager::~SnapshotManager() +{ +} + +SnapshotManager& SnapshotManager::Get() +{ + return snapshot_manager; +} + +void SnapshotManager::GenerateSnapshot(RmtSnapshotPoint* snapshot_point, MainWindow* main_window, QWidget* parent_widget) +{ + RMT_ASSERT(thread_controller_ == nullptr); + if (thread_controller_ == nullptr) + { + // start the processing thread and pass in the worker object. The thread controller will take ownership + // of the worker and delete it once it's complete. Passes in a pointer to a variable that accepts the + // generated snapshot ID so this can be saved after the worker thread finishes + thread_controller_ = new rmv::ThreadController(main_window, parent_widget, new SnapshotWorker(snapshot_point)); + + // when the worker thread has finished, a signal will be emitted. Wait for the signal here and update + // the UI with the newly acquired data from the worker thread + connect(thread_controller_, &rmv::ThreadController::ThreadFinished, this, &SnapshotManager::GenerateSnapshotCompleted); + } +} + +void SnapshotManager::GenerateComparison(RmtSnapshotPoint* snapshot_base_point, + RmtSnapshotPoint* snapshot_diff_point, + MainWindow* main_window, + QWidget* parent_widget) +{ + RMT_ASSERT(thread_controller_ == nullptr); + if (thread_controller_ == nullptr) + { + // start the processing thread and pass in the worker object. The thread controller will take ownership + // of the worker and delete it once it's complete. Passes in a pointer to a variable that accepts the + // generated snapshot ID so this can be saved after the worker thread finishes + thread_controller_ = new rmv::ThreadController(main_window, parent_widget, new SnapshotWorker(snapshot_base_point, snapshot_diff_point)); + + // when the worker thread has finished, a signal will be emitted. Wait for the signal here and update + // the UI with the newly acquired data from the worker thread + connect(thread_controller_, &rmv::ThreadController::ThreadFinished, this, &SnapshotManager::GenerateSnapshotCompleted); + } +} + +void SnapshotManager::GenerateSnapshotCompleted() +{ + disconnect(thread_controller_, &rmv::ThreadController::ThreadFinished, this, &SnapshotManager::GenerateSnapshotCompleted); + thread_controller_->deleteLater(); + thread_controller_ = nullptr; +} + +RmtSnapshotPoint* SnapshotManager::GetSelectedSnapshotPoint() const +{ + return selected_snapshot_; +} + +void SnapshotManager::SetSelectedSnapshotPoint(RmtSnapshotPoint* snapshot_point) +{ + selected_snapshot_ = snapshot_point; +} diff --git a/source/frontend/models/snapshot_manager.h b/source/frontend/models/snapshot_manager.h new file mode 100644 index 0000000..cad60a5 --- /dev/null +++ b/source/frontend/models/snapshot_manager.h @@ -0,0 +1,76 @@ +//============================================================================== +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Class definition for the RMV Snapshot Manager. +//============================================================================== + +#ifndef RMV_MODELS_SNAPSHOT_MANAGER_H_ +#define RMV_MODELS_SNAPSHOT_MANAGER_H_ + +#include + +#include "rmt_data_set.h" + +#include "util/thread_controller.h" + +enum CompareSnapshots +{ + kSnapshotCompareBase, + kSnapshotCompareDiff, + + kSnapshotCompareCount +}; + +/// Class to handle the generation of a single snapshot and snapshots used for comparison. +/// Since snapshot generation can take a few seconds, the generation itself is done on a +/// worker thread while the main UI thread displays a loading animation. This functionality +/// is abstracted away in this class and a couple methods are added to initiate snapshot +/// generation. +class SnapshotManager : public QObject +{ + Q_OBJECT + +public: + /// Constructor. + explicit SnapshotManager(); + + /// Destructor. + ~SnapshotManager(); + + /// Accessor for singleton instance. + /// \return A reference to the snapshot manager. + static SnapshotManager& Get(); + + /// Create a new snapshot. Run the snapshot generation in a separate thread and use the main thread + /// to show the loading animation in the cases where the snapshot generation takes a while. + /// \param snapshot_point The object containing the snapshot information. + /// \param main_window Pointer to the main window. + /// \param parent_widget The widget where the loading animation is to be displayed. + void GenerateSnapshot(RmtSnapshotPoint* snapshot_point, MainWindow* main_window, QWidget* parent_widget); + + /// Create snapshots for comparison. Run the snapshot generation in a separate thread and use the + /// main thread to show the loading animation in the cases where the snapshot generation takes a while. + /// \param snapshot_base_point The object containing the snapshot information for the base snapshot. + /// \param snapshot_diff_point The object containing the snapshot information for the diff snapshot. + /// \param main_window Pointer to the main window. + /// \param parent_widget The widget where the loading animation is to be displayed. + void GenerateComparison(RmtSnapshotPoint* snapshot_base_point, RmtSnapshotPoint* snapshot_diff_point, MainWindow* main_window, QWidget* parent_widget); + + /// Get the snapshot point selected in the UI. + RmtSnapshotPoint* GetSelectedSnapshotPoint() const; + + /// Set the snapshot point from the UI. + /// \param snapshot_point The snapshot point selected. + void SetSelectedSnapshotPoint(RmtSnapshotPoint* snapshot_point); + +private slots: + /// Slot to handle what happens when the snapshot worker thread has finished. + void GenerateSnapshotCompleted(); + +private: + rmv::ThreadController* thread_controller_; ///< The thread for processing backend data. + RmtSnapshotPoint* selected_snapshot_; ///< The selected snapshot point. +}; + +#endif // RMV_MODELS_SNAPSHOT_MANAGER_H_ diff --git a/source/frontend/models/timeline/device_configuration_model.cpp b/source/frontend/models/timeline/device_configuration_model.cpp new file mode 100644 index 0000000..5258f70 --- /dev/null +++ b/source/frontend/models/timeline/device_configuration_model.cpp @@ -0,0 +1,77 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for the Device configuration pane +//============================================================================= + +#include "models/timeline/device_configuration_model.h" + +#include + +#include "rmt_adapter_info.h" +#include "rmt_data_set.h" + +#include "models/trace_manager.h" +#include "util/string_util.h" +#include "util/widget_util.h" + +namespace rmv +{ + DeviceConfigurationModel::DeviceConfigurationModel() + : ModelViewMapper(kDeviceConfigurationNumWidgets) + { + } + + DeviceConfigurationModel::~DeviceConfigurationModel() + { + } + + void DeviceConfigurationModel::ResetModelValues() + { + SetModelData(kDeviceConfigurationDeviceName, "-"); + SetModelData(kDeviceConfigurationDeviceID, "-"); + SetModelData(kDeviceConfigurationMemorySize, "-"); + SetModelData(kDeviceConfigurationShaderCoreClockFrequency, "-"); + SetModelData(kDeviceConfigurationMemoryClockFrequency, "-"); + SetModelData(kDeviceConfigurationLocalMemoryBandwidth, "-"); + SetModelData(kDeviceConfigurationLocalMemoryType, "-"); + SetModelData(kDeviceConfigurationLocalMemoryBusWidth, "-"); + } + + void DeviceConfigurationModel::Update() + { + TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSet* data_set = trace_manager.GetDataSet(); + const RmtAdapterInfo& adapter = data_set->adapter_info; + const uint32_t compound_device_id = ((adapter.device_id & 0xffff) << 8) | (adapter.pcie_revision_id & 0xff); + + uint64_t video_memory_size = data_set->segment_info[kRmtHeapTypeLocal].size + data_set->segment_info[kRmtHeapTypeInvisible].size; + + SetModelData(kDeviceConfigurationDeviceName, adapter.name); + SetModelData(kDeviceConfigurationDeviceID, rmv::string_util::ToUpperCase(QString::number(compound_device_id, 16)).rightJustified(6, '0')); + + SetModelData(kDeviceConfigurationMemorySize, rmv::string_util::LocalizedValueMemory(video_memory_size, false, false)); + + SetModelData(kDeviceConfigurationShaderCoreClockFrequency, + rmv::string_util::LocalizedValue(adapter.minimum_engine_clock) + QString(" MHz (min) ") + + rmv::string_util::LocalizedValue(adapter.maximum_engine_clock) + QString(" MHz (max)")); + + SetModelData(kDeviceConfigurationMemoryClockFrequency, + rmv::string_util::LocalizedValue(adapter.minimum_memory_clock) + QString(" MHz (min) ") + + rmv::string_util::LocalizedValue(adapter.maximum_memory_clock) + QString(" MHz (max)")); + + uint64_t memory_bandwidth = adapter.memory_bandwidth; + memory_bandwidth *= (1024 * 1024); + SetModelData(kDeviceConfigurationLocalMemoryBandwidth, rmv::string_util::LocalizedValueMemory(memory_bandwidth, true, true) + QString("/s")); + SetModelData(kDeviceConfigurationLocalMemoryType, RmtAdapterInfoGetVideoMemoryType(&adapter)); + SetModelData(kDeviceConfigurationLocalMemoryBusWidth, QString::number(adapter.memory_bus_width) + QString("-bit")); + } + else + { + ResetModelValues(); + } + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/timeline/device_configuration_model.h b/source/frontend/models/timeline/device_configuration_model.h new file mode 100644 index 0000000..093d757 --- /dev/null +++ b/source/frontend/models/timeline/device_configuration_model.h @@ -0,0 +1,50 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Device configuration pane +//============================================================================= + +#ifndef RMV_MODELS_TIMELINE_DEVICE_CONFIGURATION_MODEL_H_ +#define RMV_MODELS_TIMELINE_DEVICE_CONFIGURATION_MODEL_H_ + +#include "qt_common/utils/model_view_mapper.h" + +namespace rmv +{ + /// An enum of widgets used by the UI and model. Used to map UI widgets to their + /// corresponding model data. + enum DeviceConfigurationWidgets + { + kDeviceConfigurationDeviceName, + kDeviceConfigurationDeviceID, + kDeviceConfigurationMemorySize, + kDeviceConfigurationShaderCoreClockFrequency, + kDeviceConfigurationMemoryClockFrequency, + kDeviceConfigurationLocalMemoryBandwidth, + kDeviceConfigurationLocalMemoryType, + kDeviceConfigurationLocalMemoryBusWidth, + + kDeviceConfigurationNumWidgets + }; + + /// Container class that holds model data for a given pane. + class DeviceConfigurationModel : public ModelViewMapper + { + public: + /// Constructor. + explicit DeviceConfigurationModel(); + + /// Destructor. + virtual ~DeviceConfigurationModel(); + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Update the model with data from the back end. + void Update(); + }; + +} // namespace rmv + +#endif // RMV_MODELS_TIMELINE_DEVICE_CONFIGURATION_MODEL_H_ diff --git a/source/frontend/models/timeline/snapshot_item_model.cpp b/source/frontend/models/timeline/snapshot_item_model.cpp new file mode 100644 index 0000000..25a87d7 --- /dev/null +++ b/source/frontend/models/timeline/snapshot_item_model.cpp @@ -0,0 +1,278 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a snapshot item model. Used for the snapshot +/// table in the snapshot generation pane in the timeline tab +//============================================================================= + +#include "models/timeline/snapshot_item_model.h" + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_util.h" + +#include "models/trace_manager.h" +#include "util/string_util.h" +#include "util/time_util.h" + +namespace rmv +{ + SnapshotItemModel::SnapshotItemModel(QObject* parent) + : QAbstractItemModel(parent) + , num_rows_(0) + , num_columns_(0) + { + } + + SnapshotItemModel::~SnapshotItemModel() + { + } + + void SnapshotItemModel::SetRowCount(int rows) + { + num_rows_ = rows; + } + + void SnapshotItemModel::SetColumnCount(int columns) + { + num_columns_ = columns; + } + + bool SnapshotItemModel::setData(const QModelIndex& index, const QVariant& value, int role) + { + if (index.column() == kSnapshotTimelineColumnName && role == Qt::EditRole) + { + if (!checkIndex(index)) + { + return false; + } + + TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return false; + } + + RmtDataSet* data_set = trace_manager.GetDataSet(); + int row = index.row(); + if (row >= data_set->snapshot_count) + { + return false; + } + + // validate that the string isn't empty or is too long + QString new_snapshot_name = value.toString(); + if (new_snapshot_name.isEmpty()) + { + return false; + } + + if (new_snapshot_name.length() >= RMT_MAXIMUM_NAME_LENGTH) + { + return false; + } + + // make sure this new snapshot name doesn't exist already in the table. This also tests + // the case to make sure the current snapshot name has changed + for (int i = 0; i < data_set->snapshot_count; i++) + { + const RmtSnapshotPoint* snapshot_point = &data_set->snapshots[i]; + if (QString::compare(new_snapshot_name, snapshot_point->name, Qt::CaseSensitive) == 0) + { + return false; + } + } + + // set data in the model + RmtDataSetRenameSnapshot(data_set, row, new_snapshot_name.toLatin1().data()); + return true; + } + + return QAbstractItemModel::setData(index, value, role); + } + + QVariant SnapshotItemModel::data(const QModelIndex& index, int role) const + { + if (!index.isValid()) + { + return QVariant(); + } + + TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return QVariant(); + } + + const RmtDataSet* data_set = trace_manager.GetDataSet(); + int row = index.row(); + if (row >= data_set->snapshot_count) + { + return QVariant(); + } + + const RmtSnapshotPoint* snapshot_point = &data_set->snapshots[row]; + int column = index.column(); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case kSnapshotTimelineColumnID: + return reinterpret_cast(snapshot_point); + case kSnapshotTimelineColumnName: + return QString(snapshot_point->name); + case kSnapshotTimelineColumnTime: + return rmv::time_util::ClockToTimeUnit(snapshot_point->timestamp); + default: + break; + } + + if (snapshot_point->cached_snapshot != nullptr) + { + switch (column) + { + case kSnapshotTimelineColumnVirtualAllocations: + return rmv::string_util::LocalizedValue(snapshot_point->virtual_allocations); + case kSnapshotTimelineColumnResources: + return rmv::string_util::LocalizedValue(snapshot_point->resource_count); + case kSnapshotTimelineColumnAllocatedTotalVirtualMemory: + return rmv::string_util::LocalizedValueMemory(snapshot_point->total_virtual_memory, false, false); + case kSnapshotTimelineColumnAllocatedBoundVirtualMemory: + return rmv::string_util::LocalizedValueMemory(snapshot_point->bound_virtual_memory, false, false); + case kSnapshotTimelineColumnAllocatedUnboundVirtualMemory: + return rmv::string_util::LocalizedValueMemory(snapshot_point->unbound_virtual_memory, false, false); + case kSnapshotTimelineColumnCommittedLocal: + return rmv::string_util::LocalizedValueMemory(snapshot_point->committed_memory[kRmtHeapTypeLocal], false, false); + case kSnapshotTimelineColumnCommittedInvisible: + return rmv::string_util::LocalizedValueMemory(snapshot_point->committed_memory[kRmtHeapTypeInvisible], false, false); + case kSnapshotTimelineColumnCommittedHost: + return rmv::string_util::LocalizedValueMemory(snapshot_point->committed_memory[kRmtHeapTypeSystem], false, false); + default: + break; + } + } + } + else if (role == Qt::UserRole) + { + switch (column) + { + case kSnapshotTimelineColumnID: + return QVariant::fromValue(reinterpret_cast(snapshot_point)); + case kSnapshotTimelineColumnTime: + return QVariant::fromValue(snapshot_point->timestamp); + default: + break; + } + + if (snapshot_point->cached_snapshot != nullptr) + { + switch (column) + { + case kSnapshotTimelineColumnVirtualAllocations: + return QVariant::fromValue(snapshot_point->virtual_allocations); + case kSnapshotTimelineColumnResources: + return QVariant::fromValue(snapshot_point->resource_count); + case kSnapshotTimelineColumnAllocatedTotalVirtualMemory: + return QVariant::fromValue(snapshot_point->total_virtual_memory); + case kSnapshotTimelineColumnAllocatedBoundVirtualMemory: + return QVariant::fromValue(snapshot_point->bound_virtual_memory); + case kSnapshotTimelineColumnAllocatedUnboundVirtualMemory: + return QVariant::fromValue(snapshot_point->unbound_virtual_memory); + case kSnapshotTimelineColumnCommittedLocal: + return QVariant::fromValue(snapshot_point->committed_memory[kRmtHeapTypeLocal]); + case kSnapshotTimelineColumnCommittedInvisible: + return QVariant::fromValue(snapshot_point->committed_memory[kRmtHeapTypeInvisible]); + case kSnapshotTimelineColumnCommittedHost: + return QVariant::fromValue(snapshot_point->committed_memory[kRmtHeapTypeSystem]); + default: + break; + } + } + } + if (role == Qt::EditRole) + { + if (column == kSnapshotTimelineColumnName) + { + return QString(snapshot_point->name); + } + } + + return QVariant(); + } + + Qt::ItemFlags SnapshotItemModel::flags(const QModelIndex& index) const + { + if (index.column() == kSnapshotTimelineColumnName) + { + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; + } + return QAbstractItemModel::flags(index); + } + + QVariant SnapshotItemModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case kSnapshotTimelineColumnID: + return "ID"; + case kSnapshotTimelineColumnName: + return "Snapshot name"; + case kSnapshotTimelineColumnTime: + return "Timestamp"; + case kSnapshotTimelineColumnVirtualAllocations: + return "Virtual allocations"; + case kSnapshotTimelineColumnResources: + return "Resources"; + case kSnapshotTimelineColumnAllocatedTotalVirtualMemory: + return "Total virtual memory"; + case kSnapshotTimelineColumnAllocatedBoundVirtualMemory: + return "Bound virtual memory"; + case kSnapshotTimelineColumnAllocatedUnboundVirtualMemory: + return "Unbound virtual memory"; + case kSnapshotTimelineColumnCommittedLocal: + return "Committed local memory"; + case kSnapshotTimelineColumnCommittedInvisible: + return "Committed invisible memory"; + case kSnapshotTimelineColumnCommittedHost: + return "Committed host memory"; + default: + break; + } + } + + return QAbstractItemModel::headerData(section, orientation, role); + } + + QModelIndex SnapshotItemModel::index(int row, int column, const QModelIndex& parent) const + { + if (!hasIndex(row, column, parent)) + { + return QModelIndex(); + } + + return createIndex(row, column); + } + + QModelIndex SnapshotItemModel::parent(const QModelIndex& index) const + { + Q_UNUSED(index); + return QModelIndex(); + } + + int SnapshotItemModel::rowCount(const QModelIndex& parent) const + { + Q_UNUSED(parent); + return num_rows_; + } + + int SnapshotItemModel::columnCount(const QModelIndex& parent) const + { + Q_UNUSED(parent); + return num_columns_; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/timeline/snapshot_item_model.h b/source/frontend/models/timeline/snapshot_item_model.h new file mode 100644 index 0000000..68f1cbb --- /dev/null +++ b/source/frontend/models/timeline/snapshot_item_model.h @@ -0,0 +1,67 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a snapshot item model. Used for the snapshot list table +/// in the snapshot generation pane in the timeline tab +//============================================================================= + +#ifndef RMV_MODELS_TIMELINE_SNAPSHOT_ITEM_MODEL_H_ +#define RMV_MODELS_TIMELINE_SNAPSHOT_ITEM_MODEL_H_ + +#include + +/// Enum corresponding to table columns in the snapshot table on the timeline pane. +enum SnapshotTimelineColumn +{ + kSnapshotTimelineColumnID, + kSnapshotTimelineColumnName, + kSnapshotTimelineColumnTime, + kSnapshotTimelineColumnVirtualAllocations, + kSnapshotTimelineColumnResources, + kSnapshotTimelineColumnAllocatedTotalVirtualMemory, + kSnapshotTimelineColumnAllocatedBoundVirtualMemory, + kSnapshotTimelineColumnAllocatedUnboundVirtualMemory, + kSnapshotTimelineColumnCommittedLocal, + kSnapshotTimelineColumnCommittedInvisible, + kSnapshotTimelineColumnCommittedHost, + + kSnapshotTimelineColumnCount, +}; + +namespace rmv +{ + class SnapshotItemModel : public QAbstractItemModel + { + public: + /// Constructor. + explicit SnapshotItemModel(QObject* parent = nullptr); + + /// Destructor. + ~SnapshotItemModel(); + + /// Set the number of rows in the table. + /// \param rows The number of rows required. + void SetRowCount(int rows); + + /// Set the number of columns in the table. + /// \param columns The number of columns required. + void SetColumnCount(int columns); + + // QAbstractItemModel overrides. See Qt documentation for parameter and return values + virtual bool setData(const QModelIndex& index, const QVariant& value, int role) Q_DECL_OVERRIDE; + virtual QVariant data(const QModelIndex& index, int role) const Q_DECL_OVERRIDE; + virtual Qt::ItemFlags flags(const QModelIndex& index) const Q_DECL_OVERRIDE; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const Q_DECL_OVERRIDE; + virtual QModelIndex parent(const QModelIndex& index) const Q_DECL_OVERRIDE; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + + private: + int num_rows_; ///< The number of rows in the table. + int num_columns_; ///< The number of columns in the table. + }; +} // namespace rmv + +#endif // RMV_MODELS_TIMELINE_SNAPSHOT_ITEM_MODEL_H_ diff --git a/source/frontend/models/timeline/timeline_model.cpp b/source/frontend/models/timeline/timeline_model.cpp new file mode 100644 index 0000000..ce1dfa3 --- /dev/null +++ b/source/frontend/models/timeline/timeline_model.cpp @@ -0,0 +1,437 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model implementation for Timeline pane +//============================================================================= + +#include "models/timeline/timeline_model.h" + +#include "qt_common/utils/common_definitions.h" +#include "qt_common/utils/qt_util.h" + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" +#include "rmt_job_system.h" +#include "rmt_print.h" +#include "rmt_util.h" + +#include "models/resource_sorter.h" +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" +#include "views/colorizer.h" +#include "views/main_window.h" +#include "util/log_file_writer.h" +#include "util/string_util.h" +#include "util/time_util.h" + +#ifndef _WIN32 +#include +#endif + +namespace rmv +{ + // The number of buckets used for the timeline graph. This tan be visualized as taking + // the whole timeline display and slicing it vertically into this number of buckets. + static const int32_t kNumBuckets = 500; + + // The maximum number of lines of info to show in the timeline tooltip + static const int kMaxTooltipLines = 6; + + TimelineModel::TimelineModel() + : ModelViewMapper(kTimelineNumWidgets) + , table_model_(nullptr) + , proxy_model_(nullptr) + , min_visible_(0) + , max_visible_(0) + , histogram_{} + , timeline_type_(kRmtDataTimelineTypeResourceUsageVirtualSize) + { + } + + TimelineModel::~TimelineModel() + { + delete table_model_; + } + + void TimelineModel::ResetModelValues() + { + table_model_->removeRows(0, table_model_->rowCount()); + table_model_->SetRowCount(0); + proxy_model_->invalidate(); + + SetModelData(kTimelineSnapshotCount, "-"); + } + + void TimelineModel::GenerateTimeline(RmtDataTimelineType timeline_type) + { + TraceManager& trace_manager = TraceManager::Get(); + + if (trace_manager.DataSetValid()) + { + // recreate the timeline for the data set. + RmtDataSet* data_set = trace_manager.GetDataSet(); + RmtDataTimeline* timeline = trace_manager.GetTimeline(); + RmtErrorCode error_code = RmtDataTimelineDestroy(timeline); + RMT_UNUSED(error_code); + RMT_ASSERT_MESSAGE(error_code == RMT_OK, "Error destroying old timeline"); + error_code = RmtDataSetGenerateTimeline(data_set, timeline_type, timeline); + RMT_UNUSED(error_code); + RMT_ASSERT_MESSAGE(error_code == RMT_OK, "Error generating new timeline type"); + } + } + + void TimelineModel::Update() + { + ResetModelValues(); + + TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSet* data_set = trace_manager.GetDataSet(); + table_model_->SetRowCount(data_set->snapshot_count); + + SetModelData(kTimelineSnapshotCount, rmv::string_util::LocalizedValue(data_set->snapshot_count)); + } + + proxy_model_->invalidate(); + } + + void TimelineModel::InitializeTableModel(QTableView* table_view, uint num_rows, uint num_columns) + { + if (table_model_ == nullptr) + { + table_model_ = new SnapshotItemModel(); + table_model_->SetRowCount(num_rows); + table_model_->SetColumnCount(num_columns); + + proxy_model_ = new SnapshotTimelineProxyModel(); + + proxy_model_->setSourceModel(table_model_); + proxy_model_->SetFilterKeyColumns({kSnapshotTimelineColumnName, + kSnapshotTimelineColumnTime, + kSnapshotTimelineColumnVirtualAllocations, + kSnapshotTimelineColumnResources, + kSnapshotTimelineColumnAllocatedTotalVirtualMemory, + kSnapshotTimelineColumnAllocatedBoundVirtualMemory, + kSnapshotTimelineColumnAllocatedUnboundVirtualMemory, + kSnapshotTimelineColumnCommittedLocal, + kSnapshotTimelineColumnCommittedInvisible, + kSnapshotTimelineColumnCommittedHost}); + + connect(table_model_, &QAbstractItemModel::dataChanged, this, &TimelineModel::OnModelChanged); + + table_view->setModel(proxy_model_); + } + } + + RmtSnapshotPoint* TimelineModel::AddSnapshot(uint64_t snapshot_time) + { + // Create a snapshot point. + TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return nullptr; + } + + // Generate the snapshot name. Name will be "Snapshot N" where N is + // snapshot_num. Use the number of snapshots so far as a start value + // for snapshot_num. If this snapshot exists already, increment snapshot_num + // and repeat until the snapshot name is unique + RmtDataSet* data_set = trace_manager.GetDataSet(); + int snapshot_num = data_set->snapshot_count; + static const char* const kSnapshotName = "Snapshot "; + char name_buffer[128]; + bool found_duplicate = false; + + do + { + found_duplicate = false; + sprintf_s(name_buffer, 128, "%s%d", kSnapshotName, snapshot_num); + for (int i = 0; i < data_set->snapshot_count; i++) + { + if (strcmp(data_set->snapshots[i].name, name_buffer) == 0) + { + found_duplicate = true; + break; + } + } + if (found_duplicate) + { + snapshot_num++; + } + } while (found_duplicate); + + RmtSnapshotPoint* snapshot_point = nullptr; + RmtDataSetAddSnapshot(data_set, name_buffer, snapshot_time, &snapshot_point); + if (snapshot_point != nullptr) + { + Update(); + } + + return snapshot_point; + } + + void TimelineModel::OnModelChanged(const QModelIndex& top_left, const QModelIndex& bottom_right) + { + Q_UNUSED(bottom_right); + Q_UNUSED(top_left); + } + + void TimelineModel::RemoveSnapshot(RmtSnapshotPoint* snapshot_point) + { + TraceManager& trace_manager = TraceManager::Get(); + RmtDataSet* data_set = trace_manager.GetDataSet(); + + for (int32_t current_snapshot_point_index = 0; current_snapshot_point_index < data_set->snapshot_count; ++current_snapshot_point_index) + { + RmtSnapshotPoint* current_snapshot_point = &data_set->snapshots[current_snapshot_point_index]; + if (current_snapshot_point == snapshot_point) + { + RmtDataSetRemoveSnapshot(data_set, current_snapshot_point_index); + } + } + + // Update the model as we've done edits. + Update(); + } + + int TimelineModel::RowCount() + { + return proxy_model_->rowCount(); + } + + void TimelineModel::SearchBoxChanged(const QString& filter) + { + proxy_model_->SetSearchFilter(filter); + proxy_model_->invalidate(); + } + + void TimelineModel::FilterBySizeChanged(int min_value, int max_value) + { + if (TraceManager::Get().DataSetValid()) + { + uint64_t max_usage = 0; + + const uint64_t scaled_min = max_usage * ((double)min_value / 100.0F); + const uint64_t scaled_max = max_usage * ((double)max_value / 100.0F); + + proxy_model_->SetSizeFilter(scaled_min, scaled_max); + proxy_model_->invalidate(); + } + } + + void TimelineModel::UpdateMemoryGraph(uint64_t min_visible, uint64_t max_visible) + { + min_visible_ = min_visible; + max_visible_ = max_visible; + + LogFileWriter::Get().WriteLog(LogFileWriter::kDebug, "UpdateMemoryUsage: minVisible %lld, maxVisible %lld", min_visible, max_visible); + + TraceManager& trace_manager = TraceManager::Get(); + if (!trace_manager.DataSetValid()) + { + return; + } + + RmtDataTimeline* timeline = trace_manager.GetTimeline(); + uint64_t duration = max_visible - min_visible; + + Q_ASSERT(duration > 0); + + double bucket_step = duration / (double)kNumBuckets; + + const RmtErrorCode error_code = + RmtDataTimelineCreateHistogram(timeline, MainWindow::GetJobQueue(), kNumBuckets, bucket_step, min_visible_, max_visible_, &histogram_); + + RMT_ASSERT(error_code == RMT_OK); + RMT_UNUSED(error_code); + } + + int TimelineModel::GetNumBuckets() const + { + return kNumBuckets; + } + + int TimelineModel::GetNumBucketGroups() const + { + return histogram_.bucket_group_count; + } + + void TimelineModel::SetTimelineType(RmtDataTimelineType new_timeline_type) + { + timeline_type_ = new_timeline_type; + } + + QString TimelineModel::GetValueString(int64_t value, bool display_as_memory) const + { + QString value_string; + if (!display_as_memory) + { + value_string = QString::number(value); + } + else + { + value_string = rmv::string_util::LocalizedValueMemory(value, false, false); + } + return value_string; + } + + void TimelineModel::GetResourceTooltipInfo(int bucket_index, bool display_as_memory, QList& tooltip_info_list) + { + ResourceSorter sorter; + + // build an array of resource type to count + for (int i = 0; i < GetNumBucketGroups(); i++) + { + if (i == kRmtResourceUsageTypeUnknown) + { + continue; + } + sorter.AddResource((RmtResourceUsageType)i, RmtDataTimelineHistogramGetValue(&histogram_, bucket_index, i)); + } + + sorter.Sort(); + + // take the top n values and show them + size_t count = std::min(kMaxTooltipLines - 1, sorter.GetNumResources()); + int64_t value = 0; + + TooltipInfo tooltip_info; + for (size_t i = 0; i < count; i++) + { + value = sorter.GetResourceValue(i); + int type = sorter.GetResourceType(i); + tooltip_info.text = QString("%1: %2") + .arg(rmv::string_util::GetResourceUsageString(static_cast(type))) + .arg(GetValueString(value, display_as_memory)); + tooltip_info.color = Colorizer::GetResourceUsageColor(static_cast(type)); + tooltip_info_list.push_back(tooltip_info); + } + + // total up the rest and show them as "Other" + value = sorter.GetRemainder(kMaxTooltipLines - 1); + tooltip_info.text = QString("Other: %1").arg(GetValueString(value, display_as_memory)); + tooltip_info.color = Colorizer::GetResourceUsageColor(kRmtResourceUsageTypeFree); + tooltip_info_list.push_back(tooltip_info); + } + + bool TimelineModel::GetTimelineTooltipInfo(qreal x_pos, QList& tooltip_info_list) + { + int bucket_index = x_pos * kNumBuckets; + + switch (timeline_type_) + { + case kRmtDataTimelineTypeResourceUsageCount: + // number of each type of resource + GetResourceTooltipInfo(bucket_index, false, tooltip_info_list); + break; + + case kRmtDataTimelineTypeResourceUsageVirtualSize: + // memory for each type of resource + GetResourceTooltipInfo(bucket_index, true, tooltip_info_list); + break; + + case kRmtDataTimelineTypeVirtualMemory: + // calculate memory in each heap + for (int i = 0; i < GetNumBucketGroups(); i++) + { + int64_t value = RmtDataTimelineHistogramGetValue(&histogram_, bucket_index, i); + TooltipInfo tooltip_info; + tooltip_info.text = + QString("%1: %2").arg(RmtGetHeapTypeNameFromHeapType(RmtHeapType(i))).arg(rmv::string_util::LocalizedValueMemory(value, false, false)); + tooltip_info.color = Colorizer::GetHeapColor(static_cast(i)); + tooltip_info_list.push_back(tooltip_info); + } + break; + + case kRmtDataTimelineTypeCommitted: + for (int i = 0; i < GetNumBucketGroups(); i++) + { + int64_t value = RmtDataTimelineHistogramGetValue(&histogram_, bucket_index, i); + TooltipInfo tooltip_info; + tooltip_info.text = + QString("%1: %2").arg(RmtGetHeapTypeNameFromHeapType(RmtHeapType(i))).arg(rmv::string_util::LocalizedValueMemory(value, false, false)); + tooltip_info.color = Colorizer::GetHeapColor(static_cast(i)); + tooltip_info_list.push_back(tooltip_info); + } + break; + + default: + return false; + } + + return true; + } + + bool TimelineModel::GetHistogramData(int bucket_group_index, int bucket_index, qreal& out_y_pos, qreal& out_height) + { + if (bucket_index < kNumBuckets) + { + TraceManager& trace_manager = TraceManager::Get(); + const RmtDataTimeline* timeline = trace_manager.GetTimeline(); + + // The heights in the bucket consist of the memory allocated for each process. + // Since the timeline view is a stacked graph, the heights of the previous buckets + // need to be taken into account and used as an offset for the current bucket + out_y_pos = 0.0; + qreal histogram_value = 0.0; + for (int i = 0; i <= bucket_group_index; i++) + { + histogram_value = (double)RmtDataTimelineHistogramGetValue(&histogram_, bucket_index, i); + histogram_value /= timeline->maximum_value_in_all_series; + out_y_pos += histogram_value; + } + + // height is just the data for this particular sub-bucket + out_height = histogram_value; + + return true; + } + return false; + } + + qulonglong TimelineModel::GetProxyData(int row, int col) + { + return proxy_model_->GetData(row, col); + } + + SnapshotTimelineProxyModel* TimelineModel::GetProxyModel() const + { + return proxy_model_; + } + + void TimelineModel::ValidateTimeUnits() const + { + TraceManager& trace_manager = TraceManager::Get(); + bool valid = false; + + if (trace_manager.DataSetValid()) + { + const RmtDataSet* data_set = trace_manager.GetDataSet(); + if (RmtDataSetGetCpuClockTimestampValid(data_set) == RMT_OK) + { + valid = true; + } + else + { + RMVSettings::Get().SetUnits(kTimeUnitTypeClk); + } + + RMVSettings::Get().SetUnitsOverrideEnable(!valid); + } + } + + uint64_t TimelineModel::GetMaxTimestamp() const + { + TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSet* data_set = trace_manager.GetDataSet(); + return data_set->maximum_timestamp; + } + + return 0; + } + +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/models/timeline/timeline_model.h b/source/frontend/models/timeline/timeline_model.h new file mode 100644 index 0000000..cf95ae0 --- /dev/null +++ b/source/frontend/models/timeline/timeline_model.h @@ -0,0 +1,170 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Model header for the Timeline pane +//============================================================================= + +#ifndef RMV_MODELS_TIMELINE_TIMELINE_MODEL_H_ +#define RMV_MODELS_TIMELINE_TIMELINE_MODEL_H_ + +#include + +#include "qt_common/utils/model_view_mapper.h" + +#include "rmt_data_set.h" +#include "rmt_data_timeline.h" + +#include "models/proxy_models/snapshot_timeline_proxy_model.h" +#include "models/timeline/snapshot_item_model.h" +#include "views/custom_widgets/rmv_timeline_tooltip.h" +#include "util/definitions.h" +#include "util/thread_controller.h" + +class MainWindow; + +namespace rmv +{ + /// UI widgets that are updated by the model. + enum TimelineModelWidgets + { + kTimelineSnapshotCount, + + kTimelineNumWidgets, + }; + + /// Container class that holds model data for a given pane. + class TimelineModel : public ModelViewMapper + { + public: + /// Constructor. + explicit TimelineModel(); + + /// Destructor. + virtual ~TimelineModel(); + + /// Initialize the table model. + /// \param table_view The view to the table. + /// \param num_rows Total rows of the table. + /// \param num_columns Total columns of the table. + void InitializeTableModel(QTableView* table_view, uint num_rows, uint num_columns); + + /// Call the backend to create the graphical representation of the timeline. + /// \param timeline_type The timeline type. + void GenerateTimeline(RmtDataTimelineType timeline_type); + + /// Read the dataset and update model. + void Update(); + + /// Add a new snapshot. Create the snapshot name and call the backend + /// function to add the snapshot to the trace file. Also generates a + /// RmtSnapshotPoint structure containing useful information about the + /// snapshot that is displayed in the snapshot table. + /// \param snapshot_time The time the snapshot was taken. + /// \return An RmtSnapshotPoint structure containing data that is used to. + /// populate the snapshot table, or nullptr if error. + RmtSnapshotPoint* AddSnapshot(uint64_t snapshot_time); + + /// Remove a snapshot from the model. + /// \param snapshot_point The snapshot to remove. + void RemoveSnapshot(RmtSnapshotPoint* snapshot_point); + + /// Get number of rows in the snapshot table. + /// \return number of visible rows. + int RowCount(); + + /// Handle what happens when user changes the filter. + /// \param filter The filter string to search for. + void SearchBoxChanged(const QString& filter); + /// Handle what happens when user changes the size filter. + /// \param min_value The minimum size. + /// \param max_value The maximum size. + + void FilterBySizeChanged(int min_value, int max_value); + /// Update the memory graph on the timeline. Recalculate the height data for + /// the buckets depending on the current zoom level and offset into the + /// timeline. + /// This needs to be called when the user changes zoom level or scrolls around + /// the timeline. + /// \param [in] min_visible The minimum slider value. + /// \param [in] max_visible The maximum slider value. + void UpdateMemoryGraph(uint64_t min_visible, uint64_t max_visible); + + /// Initialize blank data for the model. + void ResetModelValues(); + + /// Get content from proxy model. + /// \param row The table row. + /// \param col The table column. + /// \return The user role data at row, col. + qulonglong GetProxyData(int row, int col); + + /// Function to return histogram data. + /// \param [in] bucket_group_index The sub-bucket index. + /// \param [in] bucket_index The bucket index. + /// \param [out] out_y_pos The y position offset for this bucket and sub-bucket. + /// \param [out] out_height The height for this bucket and sub-bucket. + /// \return true if bucket/sub-bucket is valid, false if not. + bool GetHistogramData(int bucket_group_index, int bucket_index, qreal& out_y_pos, qreal& out_height); + + /// Get the number of buckets. + /// \return The number of buckets. + int GetNumBuckets() const; + + /// Get the number of grouping modes. + /// \return The number of grouping modes. + int GetNumBucketGroups() const; + + /// Set the timeline type. + /// \param new_timeline_type The new timeline type. + void SetTimelineType(RmtDataTimelineType new_timeline_type); + + /// Get the tooltip string for the timeline. + /// \param x_pos The x position of the mouse on the scene in logical coordinates + /// (0.0 is left scene bound, 1.0 is right scene bound). + /// \param tooltip_info_list A list of structs to receive the tooltip text and color. + /// \return true if string is valid, false otherwise (not over valid data). + bool GetTimelineTooltipInfo(qreal x_pos, QList& tooltip_info_list); + + /// Get the proxy model. Used to set up a connection between the table being sorted and the UI update. + /// \return the proxy model. + SnapshotTimelineProxyModel* GetProxyModel() const; + + /// Validate the time units. Usually called after a trace is loaded. If the timestamps are invalid for some + /// reason, use clocks to show timings and don't allow the user to toggle the time units. + void ValidateTimeUnits() const; + + /// Get the maximum timestamp in the currently loaded trace. + /// \return the maximum timestamp, or 0 if the timestamp is invalid. + uint64_t GetMaxTimestamp() const; + + private slots: + /// Handle what happens when the model data changes. Used to capture snapshot renaming. + /// \param top_left The top left model index in the table that changed. + /// \param bottom_right The bottom right model index in the table that changed. + void OnModelChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + + private: + /// Get a value as a string. + /// \param value The value to convert. + /// \param display_as_memory If true, display the value as an amount of memory (ie KB, MB etc). + /// \return The value as a string. + QString GetValueString(int64_t value, bool display_as_memory) const; + + /// Get the resource-specific tool tip Info. Sort the resources into numerical order and show details in the + /// tooltip (color swatch and text). + /// \param bucket_index The bucket index. + /// \param display_as_memory If true, display the value as an amount of memory (ie KB, MB etc). + /// \param tooltip_info_list A list of structs to receive the tooltip text and color. + void GetResourceTooltipInfo(int bucket_index, bool display_as_memory, QList& tooltip_info_list); + + SnapshotItemModel* table_model_; ///< Holds snapshot table data. + SnapshotTimelineProxyModel* proxy_model_; ///< Table proxy. + uint64_t min_visible_; ///< Minimum visible timestamp. + uint64_t max_visible_; ///< Maximum visible timestamp. + RmtDataTimelineHistogram histogram_; ///< The histogram to render. + RmtDataTimelineType timeline_type_; ///< The timeline type. + }; +} // namespace rmv + +#endif // RMV_MODELS_TIMELINE_TIMELINE_MODEL_H_ diff --git a/source/frontend/models/trace_manager.cpp b/source/frontend/models/trace_manager.cpp new file mode 100644 index 0000000..786c54c --- /dev/null +++ b/source/frontend/models/trace_manager.cpp @@ -0,0 +1,467 @@ +//============================================================================== +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the RMV Trace Manager. +//============================================================================== + +#include "models/trace_manager.h" + +#include +#include +#include +#include + +#include "qt_common/utils/qt_util.h" + +#ifndef _WIN32 +#include +#endif + +#include "rmt_assert.h" +#include "rmt_error.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/message_manager.h" +#include "settings/rmv_settings.h" +#include "util/definitions.h" + +/// Spawns a thread to load a dataset of either VMA or RMV origin +class LoadingThread : public QThread +{ +public: + explicit LoadingThread(const QString& path) + : path_data(path.toLatin1()) + { + } + + void run() Q_DECL_OVERRIDE + { + const char* trace_path = path_data.data(); + const TraceLoadReturnCode error_code = TraceManager::Get().TraceLoad(trace_path); + emit TraceManager::Get().TraceLoadComplete(error_code); + } + +private: + QByteArray path_data; ///< The path to the trace being loaded +}; + +/// Pointer to the loading thread object. +static LoadingThread* loading_thread = nullptr; + +/// The single instance of the trace manager. +static TraceManager trace_manager; + +TraceManager& TraceManager::Get() +{ + return trace_manager; +} + +TraceManager::TraceManager(QObject* parent) + : QObject(parent) + , open_snapshot_(nullptr) + , compared_snapshots_{} + , main_window_(nullptr) + , resource_thresholds_{} +{ + int id = qRegisterMetaType(); + Q_UNUSED(id); + + ClearTrace(); +} + +TraceManager::~TraceManager() +{ +} + +void TraceManager::Initialize(MainWindow* main_window) +{ + main_window_ = main_window; +} + +TraceLoadReturnCode TraceManager::TraceLoad(const char* trace_file_name) +{ + // Load a snapshot for viewing + active_trace_path_ = QDir::toNativeSeparators(trace_file_name); + + open_snapshot_ = nullptr; + compared_snapshots_[kSnapshotCompareBase] = nullptr; + compared_snapshots_[kSnapshotCompareDiff] = nullptr; + + // Loading regular binary RMV data + RmtErrorCode error_code = RmtDataSetInitialize(trace_file_name, &data_set_); + if (error_code != RMT_OK) + { + memset(&data_set_, 0, sizeof(RmtDataSet)); + return kTraceLoadReturnFail; + } + + // create the default timeline for the data set. + error_code = RmtDataSetGenerateTimeline(&data_set_, kRmtDataTimelineTypeResourceUsageVirtualSize, &timeline_); + if (error_code != RMT_OK) + { + return kTraceLoadReturnFail; + } + + return kTraceLoadReturnSuccess; +} + +void TraceManager::ClearTrace() +{ + if (DataSetValid()) + { + // clean up any cached snapshots. + for (int32_t current_snapshot_point_index = 0; current_snapshot_point_index < data_set_.snapshot_count; ++current_snapshot_point_index) + { + RmtSnapshotPoint* current_snapshot_point = &data_set_.snapshots[current_snapshot_point_index]; + if (current_snapshot_point->cached_snapshot) + { + RmtDataSnapshotDestroy(current_snapshot_point->cached_snapshot); + delete current_snapshot_point->cached_snapshot; + } + } + + RmtDataTimelineDestroy(&timeline_); + RmtDataSetDestroy(&data_set_); + } + + compared_snapshots_[kSnapshotCompareBase] = nullptr; + compared_snapshots_[kSnapshotCompareDiff] = nullptr; + open_snapshot_ = nullptr; + memset(&data_set_, 0, sizeof(RmtDataSet)); + + active_trace_path_.clear(); + alias_model_.Clear(); +} + +bool TraceManager::LoadTrace(const QString& path, bool compare) +{ + bool result = false; + + QFileInfo trace_file(path); + + if (!path.isEmpty() && trace_file.exists()) + { + // Nothing loaded, so load + if (!DataSetValid()) + { + // save the file location for future reference + RMVSettings::Get().SetLastFileOpenLocation(path); + + // set up callback for when loading thread is done + connect(&TraceManager::Get(), &TraceManager::TraceLoadComplete, &TraceManager::Get(), &TraceManager::OnTraceLoadComplete); + + loading_thread = new LoadingThread(path); + loading_thread->start(); + + result = true; + } + + // Load up a supplemental trace for comparison + else if (DataSetValid() && compare) + { + loading_thread = new LoadingThread(path); + loading_thread->start(); + + result = true; + } + + // Fire up a new instance if desired trace is different than current + else if (SameTrace(trace_file) == false) + { + // Attempt to open a new instance of RMV using the selected trace file as an argument + const QString rmv_executable = qApp->applicationDirPath() + GetDefaultRmvName(); + + // If Rmv executable does not exist, put up a message box + QFileInfo rmv_file(rmv_executable); + if (rmv_file.exists()) + { + QProcess* rmv_process = new QProcess(this); + if (rmv_process != nullptr) + { + QStringList rmv_args; + rmv_args << path; + + bool process_result = rmv_process->startDetached(rmv_executable, rmv_args); + + if (!process_result) + { + // The selected trace file is missing on the disk so display a message box stating so + const QString text = rmv::text::kOpenRecentTraceStart + trace_file.fileName() + rmv::text::kOpenRecentTraceEnd; + QtCommon::QtUtils::ShowMessageBox(main_window_, QMessageBox::Ok, QMessageBox::Critical, rmv::text::kOpenRecentTraceTitle, text); + } + } + } + else + { + // If the executable does not exist, put up a message box + const QString text = rmv_executable + " does not exist"; + QtCommon::QtUtils::ShowMessageBox(main_window_, QMessageBox::Ok, QMessageBox::Critical, rmv::text::kOpenRecentTraceTitle, text); + } + } + else // reload the same file + { + main_window_->CloseTrace(); + + // set up callback for when loading thread is done + connect(&TraceManager::Get(), &TraceManager::TraceLoadComplete, &TraceManager::Get(), &TraceManager::OnTraceLoadComplete); + + loading_thread = new LoadingThread(path); + loading_thread->start(); + + result = true; + } + } + else + { + // The selected trace file is missing on the disk so display a message box stating so + QString text = rmv::text::kOpenRecentTraceStart + trace_file.fileName() + rmv::text::kOpenRecentTraceEnd; + QtCommon::QtUtils::ShowMessageBox(main_window_, QMessageBox::Ok, QMessageBox::Critical, rmv::text::kOpenRecentTraceTitle, text); + } + + return result; +} + +void TraceManager::OnTraceLoadComplete(TraceLoadReturnCode error_code) +{ + bool remove_from_list = false; + + if (error_code != kTraceLoadReturnSuccess) + { + // if the trace file doesn't exist, ask the user if they want to remove it from + // the recent traces list. This has to be done from the main thread. + QFileInfo file_info(active_trace_path_); + QString text = rmv::text::kDeleteRecentTraceText.arg(file_info.fileName()); + + const int ret = QtCommon::QtUtils::ShowMessageBox( + main_window_, QMessageBox::Yes | QMessageBox::No, QMessageBox::Question, rmv::text::kDeleteRecentTraceTitle, text); + + if (ret == QMessageBox::Yes) + { + remove_from_list = true; + } + } + + if (DataSetValid()) + { + const RmtDataSet* data_set = GetDataSet(); + if (data_set->read_only == true) + { + // Another instance already has the trace file opened, so pop up an OK dialog box + const int ret = QtCommon::QtUtils::ShowMessageBox( + main_window_, QMessageBox::Ok, QMessageBox::Warning, rmv::text::kRecentTraceAlreadyOpenedTitle, rmv::text::kRecentTraceAlreadyOpenedText); + Q_UNUSED(ret); + } + + RMVSettings::Get().TraceLoaded(active_trace_path_.toLatin1().data(), data_set, remove_from_list); + RMVSettings::Get().SaveSettings(); + + if (error_code == kTraceLoadReturnSuccess) + { + main_window_->TraceLoadComplete(); + } + } + main_window_->StopAnimation(); + + disconnect(&TraceManager::Get(), &TraceManager::TraceLoadComplete, &TraceManager::Get(), &TraceManager::OnTraceLoadComplete); + + // defer deleting of this object until later, in case the thread is still executing something + // under the hood and can't be deleted right now. Even though the thread may have finished working, + // it may still have access to mutexes and deleting right now might be bad. + loading_thread->deleteLater(); + loading_thread = nullptr; + + if (error_code != kTraceLoadReturnSuccess) + { + TraceManager::Get().ClearTrace(); + } +} + +bool TraceManager::SameTrace(const QFileInfo& new_trace) const +{ + const QString new_trace_file_path = QDir::toNativeSeparators(new_trace.absoluteFilePath()); + const QString active_trace_file_path = QDir::toNativeSeparators(active_trace_path_); + + return (new_trace_file_path.compare(active_trace_file_path) == 0); +} + +bool TraceManager::ReadyToLoadTrace() const +{ + return (loading_thread == nullptr || loading_thread->isRunning() == false); +} + +bool TraceManager::TraceValidToLoad(const QString& trace_path) const +{ + bool may_load = false; + + QFileInfo trace_file(trace_path); + if (trace_file.exists() && trace_file.isFile()) + { + may_load = true; + } + + return may_load; +} + +void TraceManager::SetOpenSnapshot(RmtDataSnapshot* snapshot) +{ + memset(resource_thresholds_, 0, sizeof(uint64_t) * (rmv::kSizeSliderRange + 1)); + + open_snapshot_ = snapshot; + + std::vector resource_sizes; + + const RmtResourceList& resource_list = snapshot->resource_list; + int resource_count = resource_list.resource_count; + if (resource_count > 0) + { + resource_sizes.reserve(resource_count); + + for (int loop = 0; loop < resource_count; loop++) + { + const RmtResource& resource = resource_list.resources[loop]; + resource_sizes.push_back(resource.size_in_bytes); + } + BuildResourceSizeThresholds(resource_sizes, resource_thresholds_); + } + + const RmtVirtualAllocationList& allocation_list = snapshot->virtual_allocation_list; + const int32_t allocation_count = allocation_list.allocation_count; + if (allocation_count > 0) + { + alias_model_.Clear(); + for (int32_t loop = 0; loop < allocation_count; loop++) + { + alias_model_.Generate(&allocation_list.allocation_details[loop]); + } + } +} + +void TraceManager::BuildResourceSizeThresholds(std::vector& resource_sizes, uint64_t* resource_thresholds) +{ + std::stable_sort(resource_sizes.begin(), resource_sizes.end()); + + float step_size = (resource_sizes.size() - 1) / static_cast(rmv::kSizeSliderRange); + float index = 0.0F; + for (int loop = 0; loop <= rmv::kSizeSliderRange; loop++) + { + resource_thresholds[loop] = resource_sizes[static_cast(round(index))]; + index += step_size; + } +} + +uint64_t TraceManager::GetSizeFilterThreshold(int index) const +{ + return resource_thresholds_[index]; +} + +void TraceManager::SetComparedSnapshot(RmtDataSnapshot* snapshot_base, RmtDataSnapshot* snapshot_diff) +{ + compared_snapshots_[kSnapshotCompareBase] = snapshot_base; + compared_snapshots_[kSnapshotCompareDiff] = snapshot_diff; +} + +void TraceManager::SwapComparedSnapshots() +{ + RmtDataSnapshot* temp = compared_snapshots_[kSnapshotCompareBase]; + compared_snapshots_[kSnapshotCompareBase] = compared_snapshots_[kSnapshotCompareDiff]; + compared_snapshots_[kSnapshotCompareDiff] = temp; +} + +const char* TraceManager::GetOpenSnapshotName() const +{ + return GetSnapshotName(open_snapshot_); +} + +const char* TraceManager::GetCompareSnapshotName(int index) const +{ + return GetSnapshotName(compared_snapshots_[index]); +} + +const char* TraceManager::GetSnapshotName(const RmtDataSnapshot* snapshot) const +{ + if (snapshot != nullptr) + { + if (snapshot->snapshot_point != nullptr) + { + return snapshot->snapshot_point->name; + } + else + { + return snapshot->name; + } + } + return nullptr; +} + +bool TraceManager::SnapshotAlreadyOpened(const RmtDataSnapshot* snapshot) const +{ + return snapshot == open_snapshot_; +} + +QString TraceManager::GetDefaultRmvName() const +{ + QString default_rmv_name; + default_rmv_name.append(QDir::separator()); + default_rmv_name.append(rmv::kRmvExecutableBaseFilename); +#ifdef _DEBUG + default_rmv_name.append(rmv::kRmvExecutableDebugIdentifier); +#endif + +#ifdef WIN32 + // Append an extension only in Windows. + default_rmv_name.append(".exe"); +#endif + + return default_rmv_name; +} + +QString TraceManager::GetTracePath() const +{ + return active_trace_path_.mid(active_trace_path_.lastIndexOf("/") + 1, active_trace_path_.length()); +} + +bool TraceManager::DataSetValid() const +{ + if (data_set_.file_handle == nullptr) + { + return false; + } + return true; +} + +RmtDataSet* TraceManager::GetDataSet() +{ + return &data_set_; +} + +RmtDataTimeline* TraceManager::GetTimeline() +{ + return &timeline_; +} + +RmtDataSnapshot* TraceManager::GetOpenSnapshot() const +{ + return open_snapshot_; +} + +RmtDataSnapshot* TraceManager::GetComparedSnapshot(int snapshot_id) const +{ + return compared_snapshots_[snapshot_id]; +} + +void TraceManager::ClearOpenSnapshot() +{ + open_snapshot_ = nullptr; +} + +void TraceManager::ClearComparedSnapshots() +{ + compared_snapshots_[kSnapshotCompareBase] = nullptr; + compared_snapshots_[kSnapshotCompareDiff] = nullptr; +} + +const rmv::AliasedResourceModel& TraceManager::GetAliasModel() +{ + return alias_model_; +} diff --git a/source/frontend/models/trace_manager.h b/source/frontend/models/trace_manager.h new file mode 100644 index 0000000..53ae8a1 --- /dev/null +++ b/source/frontend/models/trace_manager.h @@ -0,0 +1,194 @@ +//============================================================================== +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Class definition for the RMV Trace Manager. +//============================================================================== + +#ifndef RMV_MODELS_TRACE_MANAGER_H_ +#define RMV_MODELS_TRACE_MANAGER_H_ + +#include +#include +#include + +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" + +#include "models/aliased_resource_model.h" +#include "models/snapshot_manager.h" +#include "util/definitions.h" +#include "views/main_window.h" + +Q_DECLARE_METATYPE(RmtErrorCode); + +/// Enum of trace loading thread return codes. +enum TraceLoadReturnCode +{ + kTraceLoadReturnError, + kTraceLoadReturnSuccess, + kTraceLoadReturnFail, + kTraceLoadReturnAlreadyOpened +}; + +Q_DECLARE_METATYPE(TraceLoadReturnCode) + +/// This class owns and manages growth and updating of the dataset. +class TraceManager : public QObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The parent widget. + explicit TraceManager(QObject* parent = nullptr); + + /// Destructor. + ~TraceManager(); + + /// Accessor for singleton instance. + static TraceManager& Get(); + + /// Initialize the trace manager. + /// \param main_window Pointer to main window widget. Used as the parent for + /// pop up message boxes. + void Initialize(MainWindow* main_window); + + /// Determine if we're ready to load a trace. + /// \return true if ready. + bool ReadyToLoadTrace() const; + + /// Load a trace into memory. Note: This function runs in a separate thread so + /// doesn't have access to anything QT-related (including the Debug Window). + /// \param trace_file_name the name of the RMV trace file. + /// \return A LoadTraceErrorCode. + TraceLoadReturnCode TraceLoad(const char* trace_file_name); + + /// Update the currently active snapshot. + /// \param snapshot The snapshot to open. + void SetOpenSnapshot(RmtDataSnapshot* snapshot); + + /// Update the currently active snapshot. + /// \param snapshot_base The base snapshot to compare. + /// \param snapshot_diff The snapshot to compare with the base snapshot. + void SetComparedSnapshot(RmtDataSnapshot* snapshot_base, RmtDataSnapshot* snapshot_diff); + + /// Swap the comparison snapshots. + void SwapComparedSnapshots(); + + /// Return whether a trace may be loaded. + /// \param trace_path the path to the trace. + /// \return true if we may attempt an actual trace load. + bool TraceValidToLoad(const QString& trace_path) const; + + /// Clear a trace from memory. + /// This function should effectively clean up the ActiveTraceData struct. + void ClearTrace(); + + /// Get the snapshot name from a snapshot. Prefer the name from the snapshot point. + /// If that doesn't exist, use the name from the snapshot itself. + /// \return The snapshot name. + const char* GetOpenSnapshotName() const; + + /// Get the snapshot name from a snapshot. Prefer the name from the snapshot point. + /// If that doesn't exist, use the name from the snapshot itself. + /// \param index The compare snapshot index. + /// \return The snapshot name. + const char* GetCompareSnapshotName(int index) const; + + /// Is a snapshot already opened. + /// \param snapshot The snapshot to compare. + /// \return true if opened, false if not. + bool SnapshotAlreadyOpened(const RmtDataSnapshot* snapshot) const; + + /// Build a list of resource thresholds used by the 'filter by size' slider. + /// \param resource_sizes An vector containing a list of resource sizes. + /// \param resource_thresholds An array to accept the resource thresholds. + void BuildResourceSizeThresholds(std::vector& resource_sizes, uint64_t* resource_thresholds); + + /// Get the 'filter by size' value based on where the slider is. The filtering + /// used by the 'filter by size' slider is non-linear so this uses a lookup + /// to get the threshold values used to compare the resource sizes depending on + /// where the slider value is. + /// \param index The index position of the slider. + /// \return The value of the filter threshold used for comparison. + uint64_t GetSizeFilterThreshold(int index) const; + + /// Get the full path to the trace file. + /// \return The trace path. + QString GetTracePath() const; + + /// Is the data set valid, meaning does it contain a valid trace. + /// \return true if data set is valid, false if not. + bool DataSetValid() const; + + /// Get a pointer to the loaded data set. + /// \return The data set. + RmtDataSet* GetDataSet(); + + /// Get a pointer to the timeline. + /// \return The timeline. + RmtDataTimeline* GetTimeline(); + + /// Get a pointer to the opened snapshot. + /// \return The opened snapshot. + RmtDataSnapshot* GetOpenSnapshot() const; + + /// Get a pointer to a comparison snapshot. + /// \param snapshot_id The snapshot id, either base or diff. + /// \return The snapshot. + RmtDataSnapshot* GetComparedSnapshot(int snapshot_id) const; + + /// Clear the opened snapshot. + void ClearOpenSnapshot(); + + /// Clear the comparison snapshots. + void ClearComparedSnapshots(); + + /// Get the model responsible for managing resource aliasing. + /// \return The alias model. + const rmv::AliasedResourceModel& GetAliasModel(); + +public slots: + /// Load an RMV trace. + /// \param path path to the trace file. + /// \param compare this trace should be used for comparison. + /// \return true if the trace path is valid. + bool LoadTrace(const QString& path, bool compare); + +signals: + /// Signal to indicate that the snapshot has completed loading. + /// \param error_code The error code returned from the loader. + void TraceLoadComplete(TraceLoadReturnCode error_code); + +private slots: + /// Kill loading thread and emit a signal saying loading completed. + /// \param error_code process the error code. + void OnTraceLoadComplete(TraceLoadReturnCode error_code); + +private: + /// Compare a trace with one that is already open. + /// \param new_trace path to trace to compare with. + /// \return true if both traces are the same. + bool SameTrace(const QFileInfo& new_trace) const; + + /// Get the snapshot name from a snapshot. Prefer the name from the snapshot point. + /// If that doesn't exist, use the name from the snapshot itself. + /// \param snapshot The snapshot. + /// \return The snapshot name. + const char* GetSnapshotName(const RmtDataSnapshot* snapshot) const; + + /// Get the default RMV name (OS-aware). + /// \return The default name string. + QString GetDefaultRmvName() const; + + RmtDataSet data_set_ = {}; ///< The dataset read from file. + RmtDataTimeline timeline_; ///< The timeline. + RmtDataSnapshot* open_snapshot_; ///< A pointer to the open snapshot. + RmtDataSnapshot* compared_snapshots_[kSnapshotCompareCount]; ///< A pointer to the compared snapshot. + MainWindow* main_window_; ///< Pointer to the main window. + QString active_trace_path_; ///< The path to currently opened file. + uint64_t resource_thresholds_[rmv::kSizeSliderRange + 1]; ///< List of resource size thresholds for the filter by size sliders. + rmv::AliasedResourceModel alias_model_; ///< The model used for showing aliased resources. +}; +#endif // RMV_MODELS_TRACE_MANAGER_H_ diff --git a/source/frontend/resources.qrc b/source/frontend/resources.qrc new file mode 100644 index 0000000..f01dff2 --- /dev/null +++ b/source/frontend/resources.qrc @@ -0,0 +1,25 @@ + + + ../assets/amd_logo.svg + ../assets/browse_back_normal.svg + ../assets/browse_back_pressed.svg + ../assets/browse_back_disabled.svg + ../assets/browse_fwd_normal.svg + ../assets/browse_fwd_pressed.svg + ../assets/browse_fwd_disabled.svg + ../assets/rmv_icon_32x32.png + ../assets/rmv_icon_48x48.png + ../assets/stop_128x128.png + ../assets/zoom_in.svg + ../assets/zoom_in_disabled.svg + ../assets/zoom_out.svg + ../assets/zoom_out_disabled.svg + ../assets/zoom_to_selection.svg + ../assets/zoom_to_selection_disabled.svg + ../assets/zoom_reset.svg + ../assets/zoom_reset_disabled.svg + ../assets/third_party/ionicons/search_icon.png + ../assets/third_party/ionicons/warning.svg + stylesheet.qss + + diff --git a/source/frontend/settings/rmv_geometry_settings.cpp b/source/frontend/settings/rmv_geometry_settings.cpp new file mode 100644 index 0000000..eea4b76 --- /dev/null +++ b/source/frontend/settings/rmv_geometry_settings.cpp @@ -0,0 +1,110 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for RMV Geometry settings. Leverages Qt's +/// saveGeometry/restoreGeometry methods to persist a widget's position, size and state. +/// The data is saved in the RMV settings file as a hex string. +//============================================================================= + +#include "settings/rmv_geometry_settings.h" + +#include +#include +#include +#include +#include + +#include "rmt_assert.h" + +RMVGeometrySettings::RMVGeometrySettings() +{ +} + +RMVGeometrySettings::~RMVGeometrySettings() +{ +} + +void RMVGeometrySettings::Save(QWidget* widget) +{ + QByteArray array; + QDataStream stream(&array, QIODevice::WriteOnly | QIODevice::ReadOnly); + if (widget != nullptr) + { + array = widget->saveGeometry(); + QString geometry_data(array.toHex()); + RMVSettings::Get().SetStringValue(kSettingMainWindowGeometryData, geometry_data); + RMVSettings::Get().SaveSettings(); + } +} + +bool RMVGeometrySettings::Restore(QWidget* widget) +{ + QString geometry_data = RMVSettings::Get().GetStringValue(kSettingMainWindowGeometryData); + QByteArray array(QByteArray::fromHex(geometry_data.toLocal8Bit())); + QDataStream stream(&array, QIODevice::WriteOnly | QIODevice::ReadOnly); + if (widget != nullptr) + { + bool result = widget->restoreGeometry(array); + Adjust(widget); + return result; + } + return false; +} + +void RMVGeometrySettings::Adjust(QWidget* widget) +{ + QRect widget_rect(widget->geometry()); + QRect current_screen_rect(QGuiApplication::primaryScreen()->availableGeometry()); + QPoint current_pos = QPoint(widget_rect.x(), widget_rect.y()); + for (QScreen* screen : QGuiApplication::screens()) + { + QRect screen_rect = screen->availableGeometry(); + if (screen_rect.contains(current_pos)) + { + current_screen_rect = screen_rect; + break; + } + } + + QRect adjusted_rect(widget_rect); + if (widget_rect.width() > current_screen_rect.width()) + { + adjusted_rect.setWidth(current_screen_rect.width()); + } + + if (widget_rect.right() > current_screen_rect.right()) + { + adjusted_rect.moveRight(current_screen_rect.right()); + } + + if (widget_rect.left() < current_screen_rect.left()) + { + adjusted_rect.moveLeft(current_screen_rect.left()); + } + + if (widget_rect.height() > current_screen_rect.height()) + { + adjusted_rect.setHeight(current_screen_rect.height()); + } + + if (widget_rect.bottom() > current_screen_rect.bottom()) + { + adjusted_rect.moveBottom(current_screen_rect.bottom()); + } + + if (widget_rect.top() < current_screen_rect.top()) + { + adjusted_rect.moveTop(current_screen_rect.top()); + } + + if (widget_rect != adjusted_rect) + { + int titlebar_height = qApp->style()->pixelMetric(QStyle::PM_TitleBarHeight); + int frame_thickness = qApp->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + adjusted_rect.adjust(frame_thickness, frame_thickness + titlebar_height, -frame_thickness, -frame_thickness); + widget->resize(adjusted_rect.size()); + widget->move(adjusted_rect.topLeft()); + RMVGeometrySettings::Save(widget); + } +} diff --git a/source/frontend/settings/rmv_geometry_settings.h b/source/frontend/settings/rmv_geometry_settings.h new file mode 100644 index 0000000..b5983f6 --- /dev/null +++ b/source/frontend/settings/rmv_geometry_settings.h @@ -0,0 +1,36 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Define RMV's window geometry settings. +//============================================================================= + +#ifndef RMV_SETTINGS_RMV_GEOMETRY_SETTINGS_H_ +#define RMV_SETTINGS_RMV_GEOMETRY_SETTINGS_H_ + +#include + +#include "settings/rmv_settings.h" + +class RMVGeometrySettings +{ +public: + /// Constructor. + RMVGeometrySettings(); + + /// Destructor. + ~RMVGeometrySettings(); + + /// Saves a widget's position, size and state in the RMV settings file as a hex string. + /// \param widget Pointer to the widget who's geometry is to be saved. + static void Save(QWidget* widget); + + /// Updates a widget's position, size and state from the RMV settings file. + /// \param widget Pointer to the widget who's geometry is to be restored. + static bool Restore(QWidget* widget); + + /// Adjust a widget's geometry so that it fit on a single monitor. + /// \param widget Pointer to the widget who's geometry is to be adjusted. + static void Adjust(QWidget* widget); +}; +#endif // RMV_SETTINGS_RMV_GEOMETRY_SETTINGS_H_ diff --git a/source/frontend/settings/rmv_settings.cpp b/source/frontend/settings/rmv_settings.cpp new file mode 100644 index 0000000..5d31177 --- /dev/null +++ b/source/frontend/settings/rmv_settings.cpp @@ -0,0 +1,766 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for Rmv settings. +//============================================================================= + +#include "settings/rmv_settings.h" + +#include +#include +#include + +#include "rmt_assert.h" + +#include "settings/settings_reader.h" +#include "settings/settings_writer.h" +#include "util/constants.h" +#include "util/rmv_util.h" + +// Single instance of the RMVSettings. +static RMVSettings rmv_settings; + +RMVSettings& RMVSettings::Get() +{ + return rmv_settings; +} + +RMVSettings::RMVSettings() + : color_palette_(nullptr) + , override_units_(false) +{ + InitDefaultSettings(); +} + +RMVSettings::~RMVSettings() +{ + delete color_palette_; +} + +void RMVSettings::AddRecentFile(const RecentFileData& recent_file) +{ + recent_files_.push_back(recent_file); +} + +void RMVSettings::TraceLoaded(const char* trace_file_name, const RmtDataSet* data_set, bool remove_from_list) +{ + RecentFileData trace_file; + + // make sure there's a valid trace loaded + if (data_set != nullptr) + { + trace_file.path = trace_file_name; + trace_file.keywords.clear(); + + trace_file.created = "0"; + + std::chrono::system_clock::time_point tp = std::chrono::system_clock::now(); + std::time_t now = std::chrono::system_clock::to_time_t(tp); + trace_file.accessed = QString::number(now); + + // If the file loaded is from the recent files list, remove it + RemoveRecentFile(trace_file_name); + + // add the loaded file to the top of the recent file list + recent_files_.insert(recent_files_.begin(), trace_file); + } + + if (remove_from_list) + { + // trace failed to load, so remove it from the recent file list + RemoveRecentFile(trace_file_name); + } +} + +void RMVSettings::RemoveRecentFile(const char* file_name) +{ + const int num_recent_files = this->recent_files_.size(); + for (int loop = 0; loop < num_recent_files; loop++) + { + if (file_name != nullptr && recent_files_[loop].path.compare(file_name) == 0) + { + recent_files_.remove(loop); + break; + } + } +} + +void RMVSettings::RemoveRecentFile(const QString& trace_name) +{ + const int num_recent_files = this->recent_files_.size(); + for (int loop = 0; loop < num_recent_files; loop++) + { + if (recent_files_[loop].path.compare(trace_name) == 0) + { + recent_files_.remove(loop); + break; + } + } +} + +void RMVSettings::AddPotentialSetting(const QString& name, const QString& value) +{ + for (RMVSettingsMap::iterator i = default_settings_.begin(); i != default_settings_.end(); ++i) + { + if (i.value().name.compare(name) == 0) + { + AddActiveSetting(i.key(), {name, value}); + break; + } + } +} + +QString RMVSettings::GetSettingsFileLocation() const +{ + QString xml_file = ""; + + // Get file location + xml_file = rmv_util::GetFileLocation(); + + // Add the file name + xml_file.append("/RmvSettings.xml"); + + return xml_file; +} + +bool RMVSettings::LoadSettings() +{ + // Begin by applying the defaults + for (RMVSettingsMap::iterator i = default_settings_.begin(); i != default_settings_.end(); ++i) + { + AddPotentialSetting(i.value().name, i.value().value); + } + + QFile file(GetSettingsFileLocation()); + + bool read_settings_file = file.open(QFile::ReadOnly | QFile::Text); + + // Override the defaults + if (read_settings_file) + { + rmv::SettingsReader xml_reader(&RMVSettings::Get()); + + read_settings_file = xml_reader.Read(&file); + + file.close(); + + // Make sure the XML parse worked + Q_ASSERT(read_settings_file == true); + } + + // If there is not file or if the parsing of an existing file failed, save a new file + if (!read_settings_file) + { + SaveSettings(); + + read_settings_file = false; + } + + SetColorPalette(ColorPalette(active_settings_[kSettingThemesAndColorsPalette].value)); + + return read_settings_file; +} + +void RMVSettings::SaveSettings() const +{ + QFile file(GetSettingsFileLocation()); + bool success = file.open(QFile::WriteOnly | QFile::Text); + + if (success) + { + rmv::SettingsWriter xml_writer(&RMVSettings::Get()); + success = xml_writer.Write(&file); + file.close(); + + RMT_ASSERT(success == true); + } +} + +void RMVSettings::InitDefaultSettings() +{ + override_units_ = false; + + default_settings_[kSettingMainWindowGeometryData] = {"WindowGeometryData", ""}; + default_settings_[kSettingMainWindowWidth] = {"WindowWidth", "0"}; + default_settings_[kSettingMainWindowHeight] = {"WindowHeight", "0"}; + default_settings_[kSettingMainWindowXpos] = {"WindowXPos", "100"}; + default_settings_[kSettingMainWindowYpos] = {"WindowYPos", "100"}; + default_settings_[kSettingLastFileOpenLocation] = {"LastFileOpenLocation", ""}; + default_settings_[kSettingGeneralCheckForUpdatesOnStartup] = {"CheckForUpdatesOnStartup", "False"}; + default_settings_[kSettingGeneralTimeUnits] = {"TimeUnits", rmv::text::kSettingsUnitsSeconds}; + + default_settings_[kSettingThemesAndColorsPalette] = {"ColorPalette", + "#FFFFBA02,#FFFF8B00,#FFF76210,#FFE17F35,#FFDA3B01,#FFEF6950,#FFD03438,#FFFF4343," + "#FFFF6062,#FFE81123,#FFEA015D,#FFC40052,#FFFF0080,#FFFF97FF,#FFFF4CFF,#FFDC00DD," + "#FF0278D8,#FF0063B1,#FF8E8CD7,#FF6B69D6,#FF7F00FF,#FF754CA8,#FFAF47C2,#FF871797," + "#FFC3C3C3,#FF2D7C9A,#FF01B7C5,#FF038288,#FF00B394,#FF018675,#FF00CC69,#FF10883E"}; + + default_settings_[kSettingThemesAndColorsSnapshotViewed] = {"SnapshotViewedColor", "16"}; + default_settings_[kSettingThemesAndColorsSnapshotCompared] = {"SnapshotComparedColor", "1"}; + default_settings_[kSettingThemesAndColorsSnapshotLive] = {"SnapshotLiveColor", "9"}; + default_settings_[kSettingThemesAndColorsSnapshotGenerated] = {"SnapshotGeneratedColor", "14"}; + default_settings_[kSettingThemesAndColorsSnapshotVma] = {"SnapshotVmaColor", "15"}; + + default_settings_[kSettingThemesAndColorsResourceDsBuffer] = {"ResourceDSBufferColor", "28"}; + default_settings_[kSettingThemesAndColorsResourceRenderTarget] = {"ResourceRenderTargetBufferColor", "8"}; + default_settings_[kSettingThemesAndColorsResourceTexture] = {"ResourceTextureBufferColor", "3"}; + default_settings_[kSettingThemesAndColorsResourceVertexBuffer] = {"ResourceVertexBufferColor", "0"}; + default_settings_[kSettingThemesAndColorsResourceIndexBuffer] = {"ResourceIndexBufferColor", "16"}; + default_settings_[kSettingThemesAndColorsResourceUav] = {"ResourceUAVColor", "21"}; + default_settings_[kSettingThemesAndColorsResourceShaderPipeline] = {"ResourceShaderPipelineColor", "18"}; + default_settings_[kSettingThemesAndColorsResourceCommandBuffer] = {"ResourceCommandBufferColor", "13"}; + default_settings_[kSettingThemesAndColorsResourceHeap] = {"ResourceHeapColor", "30"}; + default_settings_[kSettingThemesAndColorsResourceDescriptors] = {"ResourceDescriptorsColor", "9"}; + default_settings_[kSettingThemesAndColorsResourceBuffer] = {"ResourceBufferColor", "22"}; + default_settings_[kSettingThemesAndColorsResourceGpuEvent] = {"ResourceGPUEventColor", "19"}; + default_settings_[kSettingThemesAndColorsResourceFreeSpace] = {"ResourceFreeSpaceColor", "24"}; + default_settings_[kSettingThemesAndColorsResourceInternal] = {"ResourceInternalColor", "31"}; + + default_settings_[kSettingThemesAndColorsDeltaIncrease] = {"DeltaIncreaseColor", "31"}; + default_settings_[kSettingThemesAndColorsDeltaDecrease] = {"DeltaDecreaseColor", "9"}; + default_settings_[kSettingThemesAndColorsDeltaNoChange] = {"DeltaNoChangeColor", "24"}; + + default_settings_[kSettingThemesAndColorsHeapLocal] = {"HeapLocal", "17"}; + default_settings_[kSettingThemesAndColorsHeapInvisible] = {"HeapInvisible", "18"}; + default_settings_[kSettingThemesAndColorsHeapSystem] = {"HeapSystem", "7"}; + default_settings_[kSettingThemesAndColorsHeapUnspecified] = {"HeapUnspecified", "24"}; + + default_settings_[kSettingThemesAndColorsCpuMapped] = {"CPUMapped", "7"}; + default_settings_[kSettingThemesAndColorsNotCpuMapped] = {"NotCPUMapped", "24"}; + + default_settings_[kSettingThemesAndColorsInPreferredHeap] = {"InPreferredHeap", "24"}; + default_settings_[kSettingThemesAndColorsNotInPreferredHeap] = {"NotInPreferredHeap", "7"}; + + default_settings_[kSettingThemesAndColorsAliased] = {"Aliased", "7"}; + default_settings_[kSettingThemesAndColorsNotAliased] = {"NotAliased", "24"}; + + default_settings_[kSettingThemesAndColorsResourceHistoryResourceEvent] = {"ResourceHistoryResourceEvent", "1"}; + default_settings_[kSettingThemesAndColorsResourceHistoryCpuMapUnmap] = {"ResourceHistoryCpuMapping", "16"}; + default_settings_[kSettingThemesAndColorsResourceHistoryResidencyUpdate] = {"ResourceHistoryResidency", "31"}; + default_settings_[kSettingThemesAndColorsResourceHistoryPageTableUpdate] = {"ResourceHistoryPageTable", "0"}; + default_settings_[kSettingThemesAndColorsResourceHistoryHighlight] = {"ResourceHistoryHighlight", "13"}; + default_settings_[kSettingThemesAndColorsResourceHistorySnapshot] = {"ResourceHistorySnapshot", "9"}; + + default_settings_[kSettingThemesAndColorsCommitTypeCommitted] = {"CommitTypeCommitted", "31"}; + default_settings_[kSettingThemesAndColorsCommitTypePlaced] = {"CommitTypePlaced", "17"}; + default_settings_[kSettingThemesAndColorsCommitTypeVirtual] = {"CommitTypeVirtual", "1"}; + + color_palette_ = new ColorPalette(default_settings_[kSettingThemesAndColorsPalette].value); +} + +void RMVSettings::AddActiveSetting(RMVSettingID setting_id, const RMVSetting& setting) +{ + active_settings_[setting_id] = setting; +} + +QMap& RMVSettings::Settings() +{ + return active_settings_; +} + +const QVector& RMVSettings::RecentFiles() +{ + return recent_files_; +} + +QString RMVSettings::GetStringValue(const RMVSettingID setting_id) const +{ + return active_settings_[setting_id].value; +} + +bool RMVSettings::GetBoolValue(RMVSettingID setting_id) const +{ + return (active_settings_[setting_id].value.compare("True") == 0) ? true : false; +} + +int RMVSettings::GetIntValue(RMVSettingID setting_id) const +{ + return active_settings_[setting_id].value.toInt(); +} + +void RMVSettings::SetStringValue(RMVSettingID setting_id, const QString& value) +{ + AddPotentialSetting(default_settings_[setting_id].name, value); +} + +void RMVSettings::SetToDefaultValue(RMVSettingID setting_id) +{ + active_settings_[setting_id].value = default_settings_[setting_id].value; +} + +void RMVSettings::SetBoolValue(RMVSettingID setting_id, const bool value) +{ + if (value) + { + AddPotentialSetting(default_settings_[setting_id].name, "True"); + } + else + { + AddPotentialSetting(default_settings_[setting_id].name, "False"); + } +} + +void RMVSettings::SetIntValue(RMVSettingID setting_id, const int value) +{ + AddPotentialSetting(default_settings_[setting_id].name, QString::number(value)); +} + +bool RMVSettings::IsUnitsOverrideEnabled() const +{ + return override_units_; +} + +TimeUnitType RMVSettings::GetUnits() const +{ + const QString value = active_settings_[kSettingGeneralTimeUnits].value; + + if (!override_units_) + { + if (value.compare(rmv::text::kSettingsUnitsClocks) == 0) + { + return kTimeUnitTypeClk; + } + else if (value.compare(rmv::text::kSettingsUnitsMilliseconds) == 0) + { + return kTimeUnitTypeMillisecond; + } + else if (value.compare(rmv::text::kSettingsUnitsSeconds) == 0) + { + return kTimeUnitTypeSecond; + } + return kTimeUnitTypeMinute; + } + else + { + return kTimeUnitTypeClk; + } +} + +int RMVSettings::GetWindowWidth() const +{ + return GetIntValue(kSettingMainWindowWidth); +} + +int RMVSettings::GetWindowHeight() const +{ + return GetIntValue(kSettingMainWindowHeight); +} + +int RMVSettings::GetWindowXPos() const +{ + return GetIntValue(kSettingMainWindowXpos); +} + +int RMVSettings::GetWindowYPos() const +{ + return GetIntValue(kSettingMainWindowYpos); +} + +QString& RMVSettings::GetLastFileOpenLocation() +{ + return active_settings_[kSettingLastFileOpenLocation].value; +} + +void RMVSettings::SetUnitsOverrideEnable(bool enable) +{ + override_units_ = enable; +} + +void RMVSettings::SetUnits(const TimeUnitType units) +{ + switch (units) + { + case kTimeUnitTypeClk: + AddPotentialSetting(default_settings_[kSettingGeneralTimeUnits].name, rmv::text::kSettingsUnitsClocks); + break; + + case kTimeUnitTypeMillisecond: + AddPotentialSetting(default_settings_[kSettingGeneralTimeUnits].name, rmv::text::kSettingsUnitsMilliseconds); + break; + + case kTimeUnitTypeSecond: + AddPotentialSetting(default_settings_[kSettingGeneralTimeUnits].name, rmv::text::kSettingsUnitsSeconds); + break; + + case kTimeUnitTypeMinute: + default: + AddPotentialSetting(default_settings_[kSettingGeneralTimeUnits].name, rmv::text::kSettingsUnitsMinutes); + break; + } + SaveSettings(); +} + +void RMVSettings::SetLastFileOpenLocation(const QString& last_file_open_location) +{ + AddPotentialSetting("LastFileOpenLocation", last_file_open_location); + SaveSettings(); +} + +void RMVSettings::SetWindowSize(const int width, const int height) +{ + AddPotentialSetting(default_settings_[kSettingMainWindowWidth].name, QString::number(width)); + AddPotentialSetting(default_settings_[kSettingMainWindowHeight].name, QString::number(height)); + SaveSettings(); +} + +void RMVSettings::SetWindowPos(const int x_pos, const int y_pos) +{ + AddPotentialSetting(default_settings_[kSettingMainWindowXpos].name, QString::number(x_pos)); + AddPotentialSetting(default_settings_[kSettingMainWindowYpos].name, QString::number(y_pos)); + SaveSettings(); +} + +void RMVSettings::SetCheckForUpdatesOnStartup(const bool value) +{ + SetBoolValue(kSettingGeneralCheckForUpdatesOnStartup, value); + SaveSettings(); +} + +void RMVSettings::SetAllocUniqunessHeap(const bool value) +{ + SetBoolValue(kSettingGeneralAllocUniquenessHeap, value); + SaveSettings(); +} + +void RMVSettings::SetAllocUniqunessAllocation(const bool value) +{ + SetBoolValue(kSettingGeneralAllocUniquenessAllocation, value); + SaveSettings(); +} + +void RMVSettings::SetAllocUniqunessOffset(const bool value) +{ + SetBoolValue(kSettingGeneralAllocUniquenessOffset, value); + SaveSettings(); +} + +void RMVSettings::SetCheckBoxStatus(const RMVSettingID setting_id, const bool value) +{ + SetBoolValue(setting_id, value); + SaveSettings(); +} + +bool RMVSettings::GetCheckBoxStatus(const RMVSettingID setting_id) const +{ + return GetBoolValue(setting_id); +} + +bool RMVSettings::GetCheckForUpdatesOnStartup() +{ + return GetBoolValue(kSettingGeneralCheckForUpdatesOnStartup); +} + +bool RMVSettings::GetAllocUniqunessHeap() +{ + return GetBoolValue(kSettingGeneralAllocUniquenessHeap); +} + +bool RMVSettings::GetAllocUniqunessAllocation() +{ + return GetBoolValue(kSettingGeneralAllocUniquenessAllocation); +} + +bool RMVSettings::GetAllocUniqunessOffset() +{ + return GetBoolValue(kSettingGeneralAllocUniquenessOffset); +} + +const ColorPalette& RMVSettings::GetColorPalette() const +{ + return *color_palette_; +} + +int RMVSettings::GetPaletteId(RMVSettingID setting_id) +{ + return GetIntValue(setting_id); +} + +void RMVSettings::SetPaletteId(RMVSettingID setting_id, const int value) +{ + SetIntValue(setting_id, value); + SaveSettings(); +} + +void RMVSettings::CachePalette() +{ + if (color_palette_) + { + delete color_palette_; + color_palette_ = new ColorPalette(active_settings_[kSettingThemesAndColorsPalette].value); + } +} + +void RMVSettings::SetColorPalette(const ColorPalette& value) +{ + active_settings_[kSettingThemesAndColorsPalette].value = value.GetString(); + CachePalette(); + SaveSettings(); +} + +void RMVSettings::RestoreDefaultColors() +{ + SetToDefaultValue(kSettingThemesAndColorsSnapshotViewed); + SetToDefaultValue(kSettingThemesAndColorsSnapshotCompared); + SetToDefaultValue(kSettingThemesAndColorsSnapshotLive); + SetToDefaultValue(kSettingThemesAndColorsSnapshotGenerated); + SetToDefaultValue(kSettingThemesAndColorsSnapshotVma); + SetToDefaultValue(kSettingThemesAndColorsResourceDsBuffer); + SetToDefaultValue(kSettingThemesAndColorsResourceRenderTarget); + SetToDefaultValue(kSettingThemesAndColorsResourceTexture); + SetToDefaultValue(kSettingThemesAndColorsResourceVertexBuffer); + SetToDefaultValue(kSettingThemesAndColorsResourceIndexBuffer); + SetToDefaultValue(kSettingThemesAndColorsResourceUav); + SetToDefaultValue(kSettingThemesAndColorsResourceShaderPipeline); + SetToDefaultValue(kSettingThemesAndColorsResourceCommandBuffer); + SetToDefaultValue(kSettingThemesAndColorsResourceHeap); + SetToDefaultValue(kSettingThemesAndColorsResourceDescriptors); + SetToDefaultValue(kSettingThemesAndColorsResourceBuffer); + SetToDefaultValue(kSettingThemesAndColorsResourceGpuEvent); + SetToDefaultValue(kSettingThemesAndColorsResourceFreeSpace); + SetToDefaultValue(kSettingThemesAndColorsResourceInternal); + SetToDefaultValue(kSettingThemesAndColorsDeltaIncrease); + SetToDefaultValue(kSettingThemesAndColorsDeltaDecrease); + SetToDefaultValue(kSettingThemesAndColorsDeltaNoChange); + SetToDefaultValue(kSettingThemesAndColorsHeapLocal); + SetToDefaultValue(kSettingThemesAndColorsHeapInvisible); + SetToDefaultValue(kSettingThemesAndColorsHeapSystem); + SetToDefaultValue(kSettingThemesAndColorsHeapUnspecified); + SetToDefaultValue(kSettingThemesAndColorsCpuMapped); + SetToDefaultValue(kSettingThemesAndColorsNotCpuMapped); + SetToDefaultValue(kSettingThemesAndColorsInPreferredHeap); + SetToDefaultValue(kSettingThemesAndColorsNotInPreferredHeap); + SetToDefaultValue(kSettingThemesAndColorsAliased); + SetToDefaultValue(kSettingThemesAndColorsNotAliased); + SetToDefaultValue(kSettingThemesAndColorsResourceHistoryResourceEvent); + SetToDefaultValue(kSettingThemesAndColorsResourceHistoryCpuMapUnmap); + SetToDefaultValue(kSettingThemesAndColorsResourceHistoryResidencyUpdate); + SetToDefaultValue(kSettingThemesAndColorsResourceHistoryPageTableUpdate); + SetToDefaultValue(kSettingThemesAndColorsResourceHistoryHighlight); + SetToDefaultValue(kSettingThemesAndColorsResourceHistorySnapshot); + SetToDefaultValue(kSettingThemesAndColorsCommitTypeCommitted); + SetToDefaultValue(kSettingThemesAndColorsCommitTypePlaced); + SetToDefaultValue(kSettingThemesAndColorsCommitTypeVirtual); + + SaveSettings(); +} + +void RMVSettings::RestoreDefaultPalette() +{ + SetToDefaultValue(kSettingThemesAndColorsPalette); + CachePalette(); + SaveSettings(); +} + +QColor RMVSettings::GetColorValue(RMVSettingID setting_id) const +{ + const ColorPalette& palette = GetColorPalette(); + int palette_id = GetIntValue(setting_id); + + return palette.GetColor(palette_id); +} + +QColor RMVSettings::GetColorSnapshotViewed() const +{ + return GetColorValue(kSettingThemesAndColorsSnapshotViewed); +} + +QColor RMVSettings::GetColorSnapshotCompared() const +{ + return GetColorValue(kSettingThemesAndColorsSnapshotCompared); +} + +QColor RMVSettings::GetColorSnapshotLive() const +{ + return GetColorValue(kSettingThemesAndColorsSnapshotLive); +} + +QColor RMVSettings::GetColorSnapshotGenerated() const +{ + return GetColorValue(kSettingThemesAndColorsSnapshotGenerated); +} + +QColor RMVSettings::GetColorSnapshotVMA() const +{ + return GetColorValue(kSettingThemesAndColorsSnapshotVma); +} + +QColor RMVSettings::GetColorResourceDepthStencil() const +{ + return GetColorValue(kSettingThemesAndColorsResourceDsBuffer); +} + +QColor RMVSettings::GetColorResourceRenderTarget() const +{ + return GetColorValue(kSettingThemesAndColorsResourceRenderTarget); +} + +QColor RMVSettings::GetColorResourceTexture() const +{ + return GetColorValue(kSettingThemesAndColorsResourceTexture); +} + +QColor RMVSettings::GetColorResourceVertexBuffer() const +{ + return GetColorValue(kSettingThemesAndColorsResourceVertexBuffer); +} + +QColor RMVSettings::GetColorResourceIndexBuffer() const +{ + return GetColorValue(kSettingThemesAndColorsResourceIndexBuffer); +} + +QColor RMVSettings::GetColorResourceUAV() const +{ + return GetColorValue(kSettingThemesAndColorsResourceUav); +} + +QColor RMVSettings::GetColorResourceShaderPipeline() const +{ + return GetColorValue(kSettingThemesAndColorsResourceShaderPipeline); +} + +QColor RMVSettings::GetColorResourceCommandBuffer() const +{ + return GetColorValue(kSettingThemesAndColorsResourceCommandBuffer); +} + +QColor RMVSettings::GetColorResourceHeap() const +{ + return GetColorValue(kSettingThemesAndColorsResourceHeap); +} + +QColor RMVSettings::GetColorResourceDescriptors() const +{ + return GetColorValue(kSettingThemesAndColorsResourceDescriptors); +} + +QColor RMVSettings::GetColorResourceBuffer() const +{ + return GetColorValue(kSettingThemesAndColorsResourceBuffer); +} + +QColor RMVSettings::GetColorResourceGPUEvent() const +{ + return GetColorValue(kSettingThemesAndColorsResourceGpuEvent); +} + +QColor RMVSettings::GetColorResourceFreeSpace() const +{ + return GetColorValue(kSettingThemesAndColorsResourceFreeSpace); +} + +QColor RMVSettings::GetColorResourceInternal() const +{ + return GetColorValue(kSettingThemesAndColorsResourceInternal); +} + +QColor RMVSettings::GetColorDeltaIncrease() const +{ + return GetColorValue(kSettingThemesAndColorsDeltaIncrease); +} + +QColor RMVSettings::GetColorDeltaDecrease() const +{ + return GetColorValue(kSettingThemesAndColorsDeltaDecrease); +} + +QColor RMVSettings::GetColorDeltaNoChange() const +{ + return GetColorValue(kSettingThemesAndColorsDeltaNoChange); +} + +QColor RMVSettings::GetColorHeapLocal() const +{ + return GetColorValue(kSettingThemesAndColorsHeapLocal); +} + +QColor RMVSettings::GetColorHeapInvisible() const +{ + return GetColorValue(kSettingThemesAndColorsHeapInvisible); +} + +QColor RMVSettings::GetColorHeapSystem() const +{ + return GetColorValue(kSettingThemesAndColorsHeapSystem); +} + +QColor RMVSettings::GetColorHeapUnspecified() const +{ + return GetColorValue(kSettingThemesAndColorsHeapUnspecified); +} + +QColor RMVSettings::GetColorCPUMapped() const +{ + return GetColorValue(kSettingThemesAndColorsCpuMapped); +} + +QColor RMVSettings::GetColorNotCPUMapped() const +{ + return GetColorValue(kSettingThemesAndColorsNotCpuMapped); +} + +QColor RMVSettings::GetColorInPreferredHeap() const +{ + return GetColorValue(kSettingThemesAndColorsInPreferredHeap); +} + +QColor RMVSettings::GetColorNotInPreferredHeap() const +{ + return GetColorValue(kSettingThemesAndColorsNotInPreferredHeap); +} + +QColor RMVSettings::GetColorAliased() const +{ + return GetColorValue(kSettingThemesAndColorsAliased); +} + +QColor RMVSettings::GetColorNotAliased() const +{ + return GetColorValue(kSettingThemesAndColorsNotAliased); +} + +QColor RMVSettings::GetColorResourceHistoryResourceEvent() const +{ + return GetColorValue(kSettingThemesAndColorsResourceHistoryResourceEvent); +} + +QColor RMVSettings::GetColorResourceHistoryCpuMapping() const +{ + return GetColorValue(kSettingThemesAndColorsResourceHistoryCpuMapUnmap); +} + +QColor RMVSettings::GetColorResourceHistoryResidencyUpdate() const +{ + return GetColorValue(kSettingThemesAndColorsResourceHistoryResidencyUpdate); +} + +QColor RMVSettings::GetColorResourceHistoryPageTableUpdate() const +{ + return GetColorValue(kSettingThemesAndColorsResourceHistoryPageTableUpdate); +} + +QColor RMVSettings::GetColorResourceHistoryHighlight() const +{ + return GetColorValue(kSettingThemesAndColorsResourceHistoryHighlight); +} + +QColor RMVSettings::GetColorResourceHistorySnapshot() const +{ + return GetColorValue(kSettingThemesAndColorsResourceHistorySnapshot); +} + +QColor RMVSettings::GetColorCommitTypeCommitted() const +{ + return GetColorValue(kSettingThemesAndColorsCommitTypeCommitted); +} + +QColor RMVSettings::GetColorCommitTypePlaced() const +{ + return GetColorValue(kSettingThemesAndColorsCommitTypePlaced); +} + +QColor RMVSettings::GetColorCommitTypeVirtual() const +{ + return GetColorValue(kSettingThemesAndColorsCommitTypeVirtual); +} diff --git a/source/frontend/settings/rmv_settings.h b/source/frontend/settings/rmv_settings.h new file mode 100644 index 0000000..eb50c6e --- /dev/null +++ b/source/frontend/settings/rmv_settings.h @@ -0,0 +1,503 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Define Rmv settings and information about recently opened traces. +//============================================================================= + +#ifndef RMV_SETTINGS_RMV_SETTINGS_H_ +#define RMV_SETTINGS_RMV_SETTINGS_H_ + +#include +#include + +#include "qt_common/utils/color_palette.h" +#include "qt_common/utils/common_definitions.h" + +#include "rmt_data_set.h" + +#include "util/definitions.h" + +/// A struct for a setting key-value pair. +struct RMVSetting +{ + QString name; ///< Name of the setting. + QString value; ///< Value of the setting. +}; + +/// Enum of all settings. +enum RMVSettingID +{ + kSettingMainWindowGeometryData, + kSettingMainWindowWidth, + kSettingMainWindowHeight, + kSettingMainWindowXpos, + kSettingMainWindowYpos, + + kSettingLastFileOpenLocation, + kSettingGeneralCheckForUpdatesOnStartup, + kSettingGeneralTimeUnits, + kSettingGeneralAllocUniquenessHeap, + kSettingGeneralAllocUniquenessAllocation, + kSettingGeneralAllocUniquenessOffset, + + kSettingThemesAndColorsPalette, + + kSettingThemesAndColorsSnapshotViewed, + kSettingThemesAndColorsSnapshotCompared, + kSettingThemesAndColorsSnapshotLive, + kSettingThemesAndColorsSnapshotGenerated, + kSettingThemesAndColorsSnapshotVma, + + kSettingThemesAndColorsResourceDsBuffer, + kSettingThemesAndColorsResourceRenderTarget, + kSettingThemesAndColorsResourceTexture, + kSettingThemesAndColorsResourceVertexBuffer, + kSettingThemesAndColorsResourceIndexBuffer, + kSettingThemesAndColorsResourceUav, + kSettingThemesAndColorsResourceShaderPipeline, + kSettingThemesAndColorsResourceCommandBuffer, + kSettingThemesAndColorsResourceHeap, + kSettingThemesAndColorsResourceDescriptors, + kSettingThemesAndColorsResourceBuffer, + kSettingThemesAndColorsResourceGpuEvent, + kSettingThemesAndColorsResourceFreeSpace, + kSettingThemesAndColorsResourceInternal, + + kSettingThemesAndColorsDeltaIncrease, + kSettingThemesAndColorsDeltaDecrease, + kSettingThemesAndColorsDeltaNoChange, + + kSettingThemesAndColorsHeapLocal, + kSettingThemesAndColorsHeapInvisible, + kSettingThemesAndColorsHeapSystem, + kSettingThemesAndColorsHeapUnspecified, + + kSettingThemesAndColorsCpuMapped, + kSettingThemesAndColorsNotCpuMapped, + + kSettingThemesAndColorsInPreferredHeap, + kSettingThemesAndColorsNotInPreferredHeap, + + kSettingThemesAndColorsAliased, + kSettingThemesAndColorsNotAliased, + + kSettingThemesAndColorsResourceHistoryResourceEvent, + kSettingThemesAndColorsResourceHistoryCpuMapUnmap, + kSettingThemesAndColorsResourceHistoryResidencyUpdate, + kSettingThemesAndColorsResourceHistoryPageTableUpdate, + kSettingThemesAndColorsResourceHistoryHighlight, + kSettingThemesAndColorsResourceHistorySnapshot, + + kSettingThemesAndColorsCommitTypeCommitted, + kSettingThemesAndColorsCommitTypePlaced, + kSettingThemesAndColorsCommitTypeVirtual, + + kSettingCount, +}; + +typedef QMap RMVSettingsMap; + +/// Support for Rmv's settings. +class RMVSettings +{ +public: + /// Get the single settings object. + /// \return a reference to the RMVSettings. + static RMVSettings& Get(); + + /// Constructor. + RMVSettings(); + + /// Destructor. + ~RMVSettings(); + + /// Get file path to RMV settings. Find the 'Temp' folder on the local OS + /// and create an RMV subfolder (on linux, create .RMV folder). + /// \return The name and location of the xml file. + QString GetSettingsFileLocation() const; + + /// Apply default settings and then override them if found on disk. + /// \return true if settings were read from file, and false otherwise. + bool LoadSettings(); + + /// Save the settings (and list of recent files) to disk. + void SaveSettings() const; + + /// Add a setting to our active map if it is recognized. + /// \param name The name of the setting to add. + /// \param value The value of the setting. + void AddPotentialSetting(const QString& name, const QString& value); + + /// Add a recent file to the settings. + /// \param recent_file The recent file to add. + void AddRecentFile(const RecentFileData& recent_file); + + /// Update the recent files list. Called when loading a new trace file. If the file + /// already exists in the recent files list, bump it to the top. If it doesn't exist + /// then add it to the list. + /// \param trace_file_name The trace file name to add/remove. + /// \param data_set The data set. + /// \param remove_from_list If true, remove the trace file from the list. + void TraceLoaded(const char* trace_file_name, const RmtDataSet* data_set, bool remove_from_list); + + /// Remove a file from the recent files list. + /// \param trace_name The name of the file to remove. + void RemoveRecentFile(const QString& trace_name); + + /// Get a reference to the settings. + /// \return A reference to the settings. + QMap& Settings(); + + /// Get a reference to the recent files list. + /// \return A reference to the recent files list. + const QVector& RecentFiles(); + + /// Get a setting as a string value. + /// \param setting_id The identifier for this setting. + /// \return The string value for the setting specified. + QString GetStringValue(const RMVSettingID setting_id) const; + + /// Set a setting as an string value. + /// \param setting_id The identifier for this setting. + /// \param value The new value of the setting. + void SetStringValue(RMVSettingID setting_id, const QString& value); + + /// Get window width from the settings. + /// \return Main window width. + int GetWindowWidth() const; + + /// Get window height from the settings. + /// \return Main window height. + int GetWindowHeight() const; + + /// Get window X screen position from the settings. + /// \return Main window x position. + int GetWindowXPos() const; + + /// Get window Y screen position from the settings. + /// \return Main window y position. + int GetWindowYPos() const; + + /// Get the time units override setting. + /// \return If the override is enabled, true is returned. Otherwise false is + /// returned. + bool IsUnitsOverrideEnabled() const; + + /// Get timing units from the settings. If the time units override flag is + /// set, then return clock cycles as the unit. + /// \return A TimeUnitType value. + TimeUnitType GetUnits() const; + + /// Get last file open location from the settings. + /// \return Path to last opened file dir. + QString& GetLastFileOpenLocation(); + + /// Sets the size of the window (width and height) in the settings. + /// \param width The new width. + /// \param height The new height. + void SetWindowSize(const int width, const int height); + + /// Sets the position of the window on the screen in the settings. + /// \param x_pos The new X position. + /// \param y_pos The new Y Position. + void SetWindowPos(const int x_pos, const int y_pos); + + /// Set last file open location in the settings. + /// \param last_file_open_location path + filename. + void SetLastFileOpenLocation(const QString& last_file_open_location); + + /// Set the value of kSettingGeneralCheckForUpdatesOnStartup in the settings. + /// \param value The new value of kSettingGeneralCheckForUpdatesOnStartup. + void SetCheckForUpdatesOnStartup(const bool value); + + /// Allows units to be displayed as clock cycles for traces with invalid clock frequencies. + /// \param enable When set to true, units are displayed as clock cycles regardless + /// of the user's time unit preference. When set to false, the last saved time + /// units preference is used. + void SetUnitsOverrideEnable(bool enable); + + /// Set the timing units in the settings. + /// \param units The new value of the timing units. + void SetUnits(const TimeUnitType units); + + /// Set the value of kSettingGeneralAllocUniquenessHeap in the settings. + /// \param value The new value of kSettingGeneralAllocUniquenessHeap. + void SetAllocUniqunessHeap(const bool value); + + /// Set the value of kSettingGeneralAllocUniquenessAllocation in the settings. + /// \param value The new value of kSettingGeneralAllocUniquenessAllocation. + void SetAllocUniqunessAllocation(const bool value); + + /// Set the value of kSettingGeneralAllocUniquenessOffset in the settings. + /// \param value The new value of kSettingGeneralAllocUniquenessOffset. + void SetAllocUniqunessOffset(const bool value); + + /// Get the value of kSettingGeneralCheckForUpdatesOnStartup in the settings. + /// \return The value of kSettingGeneralCheckForUpdatesOnStartup. + bool GetCheckForUpdatesOnStartup(); + + /// Get the value of kSettingGeneralAllocUniquenessHeap in the settings. + /// \return The value of kSettingGeneralAllocUniquenessHeap. + bool GetAllocUniqunessHeap(); + + /// Get the value of kSettingGeneralAllocUniquenessAllocation in the settings. + /// \return The value of kSettingGeneralAllocUniquenessAllocation. + bool GetAllocUniqunessAllocation(); + + /// Get the value of kSettingGeneralAllocUniquenessOffset in the settings. + /// \return The value of kSettingGeneralAllocUniquenessOffset. + bool GetAllocUniqunessOffset(); + + /// Get the color palette from the settings. + /// \return The current color palette. + const ColorPalette& GetColorPalette() const; + + /// Get the value of a palette id from the settings. + /// \param setting_id The id of the setting to query. + /// \return The palette id value of this item. + int GetPaletteId(RMVSettingID setting_id); + + /// Set the value of a palette id in the settings. + /// \param setting_id The id of the setting to change. + /// \param value The new palette id value of this item. + void SetPaletteId(RMVSettingID setting_id, const int value); + + /// Cache the color palette. Creating a temporary ColorPalette object with a + /// palette string for each palette query can be time consuming. + void CachePalette(); + + /// Set the color palette. + /// \param value The new color palette value. + void SetColorPalette(const ColorPalette& value); + + /// Restore all color settings to their default value. + void RestoreDefaultColors(); + + /// Restore all palette settings to their default value. + void RestoreDefaultPalette(); + + /// Get a setting as a QColor object. + /// \param setting_id The identifier for this setting. + /// \return The color value for the setting specified. + QColor GetColorValue(RMVSettingID setting_id) const; + + /// Get kSettingThemesAndColorsSnapshotViewed from the settings. + /// \return The color value of this snapshot. + QColor GetColorSnapshotViewed() const; + + /// Get kSettingThemesAndColorsSnapshotComapred from the settings. + /// \return The color value of this snapshot. + QColor GetColorSnapshotCompared() const; + + /// Get kSettingThemesAndColorsSnapshotLive from the settings. + /// \return The color value of this snapshot. + QColor GetColorSnapshotLive() const; + + /// Get kSettingThemesAndColorsSnapshotGenerated from the settings. + /// \return The color value of this snapshot. + QColor GetColorSnapshotGenerated() const; + + /// Get kSettingThemesAndColorsSnapshotVma from the settings. + /// \return The color value of this snapshot. + QColor GetColorSnapshotVMA() const; + + /// Get kSettingThemesAndColorsResourceDsBuffer from the settings. + /// \return The color value of this resource. + QColor GetColorResourceDepthStencil() const; + + /// Get kSettingThemesAndColorsResourceRenderTarget from the settings. + /// \return The color value of this resource. + QColor GetColorResourceRenderTarget() const; + + /// Get kSettingThemesAndColorsResourceTexture from the settings. + /// \return The color value of this resource. + QColor GetColorResourceTexture() const; + + /// Get kSettingThemesAndColorsResourceVertexBuffer from the settings. + /// \return The color value of this resource. + QColor GetColorResourceVertexBuffer() const; + + /// Get kSettingThemesAndColorsResourceIndexBuffer from the settings. + /// \return The color value of this resource. + QColor GetColorResourceIndexBuffer() const; + + /// Get kSettingThemesAndColorsResourceUav from the settings. + /// \return The color value of this resource. + QColor GetColorResourceUAV() const; + + /// Get kSettingThemesAndColorsResourceShaderPipeline from the settings. + /// \return The color value of this resource. + QColor GetColorResourceShaderPipeline() const; + + /// Get kSettingThemesAndColorsResourceCommandBuffer from the settings. + /// \return The color value of this resource. + QColor GetColorResourceCommandBuffer() const; + + /// Get kSettingThemesAndColorsResourceHeap from the settings. + /// \return The color value of this resource. + QColor GetColorResourceHeap() const; + + /// Get kSettingThemesAndColorsResourceDescriptors from the settings. + /// \return The color value of this resource. + QColor GetColorResourceDescriptors() const; + + /// Get kSettingThemesAndColorsResourceBuffer from the settings. + /// \return The color value of this heresourceap. + QColor GetColorResourceBuffer() const; + + /// Get kSettingThemesAndColorsResourceGpuEvent from the settings. + /// \return The color value of this resource. + QColor GetColorResourceGPUEvent() const; + + /// Get kSettingThemesAndColorsResourceFreeSpace from the settings. + /// \return The color value of this resource. + QColor GetColorResourceFreeSpace() const; + + /// Get kSettingThemesAndColorsResourceInternal from the settings. + /// \return The color value of this resource. + QColor GetColorResourceInternal() const; + + /// Get kSettingThemesAndColorsDeltaIncrease from the settings. + /// \return The color value of this delta type. + QColor GetColorDeltaIncrease() const; + + /// Get kSettingThemesAndColorsDeltaDecrease from the settings. + /// \return The color value of this delta type. + QColor GetColorDeltaDecrease() const; + + /// Get kSettingThemesAndColorsDeltaNoChange from the settings. + /// \return The color value of this delta type. + QColor GetColorDeltaNoChange() const; + + /// Get kSettingThemesAndColorsHeapLocal from the settings. + /// \return The color value of this heap. + QColor GetColorHeapLocal() const; + + /// Get kSettingThemesAndColorsHeapInvisible from the settings. + /// \return The color value of this heap. + QColor GetColorHeapInvisible() const; + + /// Get kSettingThemesAndColorsHeapSystem from the settings. + /// \return The color value of this heap. + QColor GetColorHeapSystem() const; + + /// Get kSettingThemesAndColorsHeapUnspecified from the settings. + /// \return The color value of this heap. + QColor GetColorHeapUnspecified() const; + + /// Get kSettingThemesAndColorsCpuMapped from the settings. + /// \return The color value of this heap. + QColor GetColorCPUMapped() const; + + /// Get kSettingThemesAndColorsNotCpuMapped from the settings. + /// \return The color value of this heap. + QColor GetColorNotCPUMapped() const; + + /// Get kSettingThemesAndColorsInPreferredHeap from the settings. + /// \return The color value of this heap. + QColor GetColorInPreferredHeap() const; + + /// Get kSettingThemesAndColorsNotInPreferredHeap from the settings. + /// \return The color value of this heap. + QColor GetColorNotInPreferredHeap() const; + + /// Get kSettingThemesAndColorsAliased from the settings. + /// \return The color value of this heap. + QColor GetColorAliased() const; + + /// Get kSettingThemesAndColorsNotAliased from the settings. + /// \return The color value of this heap. + QColor GetColorNotAliased() const; + + /// Get kSettingThemesAndColorsResourceHistoryResourceEvent from the settings. + /// \return The color value of this resource history event. + QColor GetColorResourceHistoryResourceEvent() const; + + /// Get kSettingThemesAndColorsResourceHistoryCpuMapUnmap from the settings. + /// \return The color value of this resource history event. + QColor GetColorResourceHistoryCpuMapping() const; + + /// Get kSettingThemesAndColorsResourceHistoryResidencyUpdate from the settings. + /// \return The color value of this resource history event. + QColor GetColorResourceHistoryResidencyUpdate() const; + + /// Get kSettingThemesAndColorsResourceHistoryPageTableUpdate from the settings. + /// \return The color value of this resource history event. + QColor GetColorResourceHistoryPageTableUpdate() const; + + /// Get kSettingThemesAndColorsResourceHistoryHighlight from the settings. + /// \return The color value of this resource history event. + QColor GetColorResourceHistoryHighlight() const; + + /// Get kSettingThemesAndColorsResourceHistorySnapshot from the settings. + /// \return The color value of this resource history event. + QColor GetColorResourceHistorySnapshot() const; + + /// Get kSettingThemesAndColorsCommitTypeCommitted from the settings. + /// \return The color value of this commit type. + QColor GetColorCommitTypeCommitted() const; + + /// Get kSettingThemesAndColorsCommitTypePlaced from the settings. + /// \return The color value of this commit type. + QColor GetColorCommitTypePlaced() const; + + /// Get kSettingThemesAndColorsCommitTypeVirtual from the settings. + /// \return The color value of this commit type. + QColor GetColorCommitTypeVirtual() const; + +private: + /// Set the value of checkbox's state in the settings. + /// \param setting_id The setting id of checkbox. + /// \param value The new value of checkbox state. + void SetCheckBoxStatus(const RMVSettingID setting_id, const bool value); + + /// Get checkbox state from the settings. + /// \param setting_id The setting id of checkbox. + /// \return The value of checkbox state. + bool GetCheckBoxStatus(const RMVSettingID setting_id) const; + + /// Initialize our table with default settings. + void InitDefaultSettings(); + + /// Store an active setting. + /// \param setting_id The identifier for this setting. + /// \param setting The setting containing name and value. + void AddActiveSetting(RMVSettingID id, const RMVSetting& setting); + + /// Get a setting as a boolean value. + /// \param setting_id The identifier for this setting. + /// \return The boolean value for the setting specified. + bool GetBoolValue(RMVSettingID setting_id) const; + + /// Get a setting as an integer value. + /// \param setting_id The identifier for this setting. + /// \return The integer value for the setting specified. + int GetIntValue(RMVSettingID setting_id) const; + + /// Set a setting as a boolean value. + /// \param setting_id The identifier for this setting. + /// \param value The new value of the setting. + void SetBoolValue(RMVSettingID setting_id, const bool value); + + /// Set a setting as an integer value. + /// \param setting_id The identifier for this setting. + /// \param value The new value of the setting. + void SetIntValue(RMVSettingID setting_id, const int value); + + /// Restore a setting to its default value. + /// \param setting_id The identifier for this setting. + void SetToDefaultValue(RMVSettingID setting_id); + + /// Remove a file from the recent files list. + /// \param file_name The name of the file to remove. + void RemoveRecentFile(const char* file_name); + + QVector recent_files_; ///< Vector of recently opened files. + RMVSettingsMap active_settings_; ///< Map containing active settings. + RMVSettingsMap default_settings_; ///< Map containing default settings. + ColorPalette* color_palette_; ///< The currently cached color palette. + bool override_units_; ///< Force time units to be in clock cycles (for traces with invalid CPU frequencies). +}; + +#endif // RMV_SETTINGS_RMV_SETTINGS_H_ diff --git a/source/frontend/settings/settings_reader.cpp b/source/frontend/settings/settings_reader.cpp new file mode 100644 index 0000000..dc847c7 --- /dev/null +++ b/source/frontend/settings/settings_reader.cpp @@ -0,0 +1,158 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Rmv XML settings reader. +//============================================================================= + +#include "settings/settings_reader.h" + +namespace rmv +{ + SettingsReader::SettingsReader(RMVSettings* settings) + : settings_(settings) + { + } + + SettingsReader::~SettingsReader() + { + } + + bool SettingsReader::Read(QIODevice* device) + { + reader_.setDevice(device); + + if (reader_.readNextStartElement()) + { + if (reader_.name() == "RMV") + { + ReadSettingsAndRecents(); + } + } + + return reader_.error() == false; + } + + void SettingsReader::ReadSettingsAndRecents() + { + while (reader_.readNextStartElement()) + { + if (reader_.name() == "GlobalSettings") + { + ReadSettings(); + } + else if (reader_.name() == "RecentFiles") + { + ReadRecentFiles(); + } + else + { + reader_.skipCurrentElement(); + } + } + } + + void SettingsReader::ReadSettings() + { + while (reader_.readNextStartElement()) + { + if (reader_.name() == "Setting") + { + ReadSetting(); + } + else + { + reader_.skipCurrentElement(); + } + } + } + + void SettingsReader::ReadSetting() + { + RMVSetting setting; + + while (reader_.readNextStartElement()) + { + if (reader_.name() == "Name") + { + setting.name = reader_.readElementText(); + } + else if (reader_.name() == "Value") + { + setting.value = reader_.readElementText(); + } + else + { + reader_.skipCurrentElement(); + } + } + + settings_->AddPotentialSetting(setting.name, setting.value); + } + + void SettingsReader::ReadRecentFiles() + { + while (reader_.readNextStartElement()) + { + if (reader_.name() == "RecentFile") + { + ReadRecentFile(); + } + else + { + reader_.skipCurrentElement(); + } + } + } + + void SettingsReader::ReadRecentFile() + { + RecentFileData recent_file; + + while (reader_.readNextStartElement()) + { + if (reader_.name() == "Path") + { + recent_file.path = reader_.readElementText(); + } + else if (reader_.name() == "Keywords") + { + recent_file.keywords = reader_.readElementText(); + } + else if (reader_.name() == "API") + { + recent_file.api = reader_.readElementText(); + } + else if (reader_.name() == "Created") + { + recent_file.created = reader_.readElementText(); + } + else if (reader_.name() == "Accessed") + { + recent_file.accessed = reader_.readElementText(); + } + else if (reader_.name() == "Events") + { + recent_file.events = reader_.readElementText(); + } + else if (reader_.name() == "DeviceID") + { + recent_file.device_id = reader_.readElementText(); + } + else if (reader_.name() == "DeviceRevisionID") + { + recent_file.device_revision_id = reader_.readElementText(); + } + else if (reader_.name() == "DeviceString") + { + recent_file.device_string = reader_.readElementText(); + } + else + { + reader_.skipCurrentElement(); + } + } + + settings_->AddRecentFile(recent_file); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/settings/settings_reader.h b/source/frontend/settings/settings_reader.h new file mode 100644 index 0000000..ef63b05 --- /dev/null +++ b/source/frontend/settings/settings_reader.h @@ -0,0 +1,54 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV XML settings reader. +//============================================================================= + +#ifndef RMV_SETTINGS_SETTINGS_READER_H_ +#define RMV_SETTINGS_SETTINGS_READER_H_ + +#include +#include + +#include "settings/rmv_settings.h" + +namespace rmv +{ + /// Support for RMV XML settings reader. + class SettingsReader + { + public: + /// Constructor. + /// \param settings Output settings class. + explicit SettingsReader(RMVSettings* settings); + + /// Destructor. + ~SettingsReader(); + + /// Begin reading RMV XML file and make sure it's valid. + /// \param device The XML file represented by a Qt IO device. + bool Read(QIODevice* device); + + private: + /// Detect global settings and recently used files section. + void ReadSettingsAndRecents(); + + /// Detect setting list. + void ReadSettings(); + + /// Read individual settings. + void ReadSetting(); + + /// Detect recently opened file list. + void ReadRecentFiles(); + + /// Read individual recently opened files. + void ReadRecentFile(); + + QXmlStreamReader reader_; ///< Qt's XML stream. + RMVSettings* settings_; ///< Belongs to the caller, not this class. + }; +} // namespace rmv + +#endif // RMV_SETTINGS_SETTINGS_READER_H_ diff --git a/source/frontend/settings/settings_writer.cpp b/source/frontend/settings/settings_writer.cpp new file mode 100644 index 0000000..2dbe2b3 --- /dev/null +++ b/source/frontend/settings/settings_writer.cpp @@ -0,0 +1,89 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV XML settings writer. +//============================================================================= + +#include "settings/settings_writer.h" + +namespace rmv +{ + SettingsWriter::SettingsWriter(RMVSettings* settings) + : settings_(settings) + { + } + + SettingsWriter::~SettingsWriter() + { + } + + bool SettingsWriter::Write(QIODevice* device) + { + writer_.setDevice(device); + + writer_.writeStartDocument(); + writer_.writeStartElement("RMV"); + + WriteSettingsAndRecents(); + + writer_.writeEndElement(); + writer_.writeEndDocument(); + + return writer_.hasError() == false; + } + + void SettingsWriter::WriteSettingsAndRecents() + { + writer_.writeStartElement("GlobalSettings"); + WriteSettings(); + writer_.writeEndElement(); + + writer_.writeStartElement("RecentFiles"); + WriteRecentFiles(); + writer_.writeEndElement(); + } + + void SettingsWriter::WriteSettings() + { + RMVSettingsMap& settings = settings_->Settings(); + + for (RMVSettingsMap::iterator i = settings.begin(); i != settings.end(); ++i) + { + writer_.writeStartElement("Setting"); + WriteSetting(i.value()); + writer_.writeEndElement(); + } + } + + void SettingsWriter::WriteSetting(const RMVSetting& setting) + { + writer_.writeTextElement("Name", setting.name); + writer_.writeTextElement("Value", setting.value); + } + + void SettingsWriter::WriteRecentFiles() + { + const QVector& recent_files = settings_->RecentFiles(); + + for (int loop = 0; loop < recent_files.size(); loop++) + { + writer_.writeStartElement("RecentFile"); + WriteRecentFile(recent_files.at(loop)); + writer_.writeEndElement(); + } + } + + void SettingsWriter::WriteRecentFile(const RecentFileData& recent_file) + { + writer_.writeTextElement("Path", recent_file.path); + writer_.writeTextElement("Keywords", recent_file.keywords); + writer_.writeTextElement("API", recent_file.api); + writer_.writeTextElement("Created", recent_file.created); + writer_.writeTextElement("Accessed", recent_file.accessed); + writer_.writeTextElement("Events", recent_file.events); + writer_.writeTextElement("DeviceID", recent_file.device_id); + writer_.writeTextElement("DeviceRevisionID", recent_file.device_revision_id); + writer_.writeTextElement("DeviceString", recent_file.device_string); + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/settings/settings_writer.h b/source/frontend/settings/settings_writer.h new file mode 100644 index 0000000..1c8452e --- /dev/null +++ b/source/frontend/settings/settings_writer.h @@ -0,0 +1,56 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV XML settings writer. +//============================================================================= + +#ifndef RMV_SETTINGS_SETTINGS_WRITER_H_ +#define RMV_SETTINGS_SETTINGS_WRITER_H_ + +#include +#include + +#include "settings/rmv_settings.h" + +namespace rmv +{ + /// Support for RMV XML settings writer. + class SettingsWriter + { + public: + /// Constructor. + /// \param pSettings Output settings class. + explicit SettingsWriter(RMVSettings* settings); + + /// Destructor. + ~SettingsWriter(); + + /// Begin writing RMV XML file and make sure it's valid. + /// \param device The XML file represented by a Qt IO device. + bool Write(QIODevice* device); + + private: + /// Detect global settings and recently used files section. + void WriteSettingsAndRecents(); + + /// Detect setting list. + void WriteSettings(); + + /// Write individual settings. + /// \param setting The RMVSetting structure to write out. + void WriteSetting(const RMVSetting& setting); + + /// Detect recently opened file list. + void WriteRecentFiles(); + + /// Write individual recently opened files. + /// \param recentFile The name of the file to write. + void WriteRecentFile(const RecentFileData& recent_file); + + QXmlStreamWriter writer_; ///< Qt's XML stream. + RMVSettings* settings_; ///< Belongs to the caller, not this class. + }; +} // namespace rmv + +#endif // RMV_SETTINGS_SETTINGS_WRITER_H_ diff --git a/source/frontend/stylesheet.qss b/source/frontend/stylesheet.qss new file mode 100644 index 0000000..b546938 --- /dev/null +++ b/source/frontend/stylesheet.qss @@ -0,0 +1,343 @@ +/** +* RMV stylesheet +* +* Centralized location for styling of widgets in RMV +*/ + +/*************************************/ +/* Global +/*************************************/ + +QWidget#main_content_ +{ + background-color: white; +} + +QTableView::item:selected +{ + border: none; + color: white; + background-color: #0078d7; +} + +QSplitter::handle +{ + background: gray; + height: 1px; +} + +FileLoadingWidget +{ + background-color: rgb(255, 255, 255); + color: gray; +} + +RecentTraceMiniWidget +{ + font-size: 9.75pt; +} + +ScaledTableView, ScaledTreeView +{ + font-size: 9pt; +} + +ScaledHeaderView +{ + font: bold 9pt; +} + +ScaledTabWidget +{ + font: bold 8pt; +} + +ScaledTabWidget > QTabBar::tab +{ + padding: 0.5em 1em 0.5em 1em; +} + +ScaledTabWidget > QTabBar::tab:!first +{ + margin-left: -1px; +} + +ScaledTabWidget > QTabBar::tab:selected +{ + background-color: white; + border: 1px solid #C4C4C3; + border-bottom: 1px solid white; +} + +ScaledTabWidget > QTabBar::tab:!selected +{ + background-color: rgb(236, 236, 236); + border-right: 1px solid #C4C4C3; + border-left: 1px solid #C4C4C3; + border-bottom: 1px solid #C4C4C3; +} + +ScaledTabWidget > QTabBar::tab:!selected:hover { + background-color: rgb(237, 246, 255) +} + +ScaledLabel, ScaledPushButton, QLabel, QPushButton +{ + font-size: 9.75pt; +} + +ColoredLegendGraphicsView +{ + font-size: 8pt; +} + +ArrowIconComboBox +{ + background: white; + border: none; + font-size: 9.75pt; + text-align:left; +} + +TextSearchWidget +{ + font-size: 9.75pt; + border: 1px solid gray; + background: white; +} + +QFrame#warning_widget_ +{ + background-color: #f0e5ad; +} + +/*************************************/ +/* RMV-Specific styles +/*************************************/ + +RMVColoredCheckbox +{ + font-size: 9.75pt; + qproperty-button_text_ratio: 1.2; +} + +/*************************************/ +/* MainWindow +/*************************************/ + +MainWindow QWidget#snapshot_label_ +{ + background-color: rgb(240, 240, 240); +} + +MainWindow QWidget#snapshot_combo_box_ +{ + background-color: rgb(240, 240, 240); +} + +MainWindow QWidget#bottom_spacer_label_ +{ + background-color: rgb(240, 240, 240); +} + +MainWindow QTabWidget::pane +{ + border : 0px solid #444; + background: solid white; +} + +MainWindow #main_tab_widget_ > QTabWidget::pane +{ + border-top: 0px solid #444; + border-bottom: 0px solid #444; +} + +MainWindow #main_tab_widget_ > QTabBar +{ + font: bold 10.5pt; +} + +MainWindow #main_tab_widget_ > QTabBar::tab +{ + background: rgb(51,51,51); + color: #666; + bottom: 1px; +} + +MainWindow #main_tab_widget_ > QTabBar::tab:top +{ + margin: 0px 0px 0 0; + padding: 4px 8px; + border-bottom: 4px solid transparent; +} + +MainWindow #main_tab_widget_ > QTabBar::tab:selected +{ + color: white; +} + +MainWindow #main_tab_widget_ > QTabBar::tab:top:hover +{ + border-bottom: 4px solid rgb(135,20,16); +} + +MainWindow #main_tab_widget_ > QTabBar::tab:top:selected +{ + border-bottom: 4px solid rgb(224,30,55); +} + +MainWindow #main_tab_widget_ > QTabBar::tab:!selected:hover +{ + color: #999; +} + +/*************************************/ +/* Welcome Pane +/*************************************/ + +WelcomePane QLabel, ScaledLabel +{ + font-size: 11.75pt; +} + +/*************************************/ +/* About Pane +/*************************************/ + +AboutPane QPushButton +{ + color: rgb(0, 0, 255); + border: none; + text-align: left; +} + +AboutPane QPushButton:hover +{ + color: rgb(255, 128, 0); +} + +AboutPane QLabel, ScaledLabel +{ + font-size: 8.75pt; +} + +AboutPane ScaledLabel#label_app_title_ +{ + font-size: 11.75pt; +} + +AboutPane ScaledLabel#label_related_actions_ +{ + font-size: 11.75pt; +} + +/*************************************/ +/* Snapshot start Pane +/*************************************/ + +SnapshotStartPane ScaledLabel#title_text_ +{ + font-size: 14pt; +} + +SnapshotStartPane ScaledLabel#instruction_title_ +{ + font-size: 14pt; +} + +/*************************************/ +/* Heap overview Pane +/*************************************/ + +HeapOverviewHeapLayout ScaledLabel#title_label_ +{ + font-size: 11pt; +} + +/*************************************/ +/* Resource details Pane +/*************************************/ + +ResourceDetailsPane ScaledLabel#label_title_ +{ + font-size: 11.75pt; +} + +/*************************************/ +/* Compare start Pane +/*************************************/ + +CompareStartPane ScaledLabel#title_text_ +{ + font-size: 14pt; +} + +CompareStartPane ScaledLabel#instruction_title_ +{ + font-size: 14pt; +} + +/*************************************/ +/* Snapshot delta Pane +/*************************************/ + +SnapshotDeltaPane ScaledLabel#base_snapshot_label_ +{ + font-size: 12pt; +} + +SnapshotDeltaPane ScaledLabel#diff_snapshot_label_ +{ + font-size: 12pt; +} + +/*************************************/ +/* Memory leak finder Pane +/*************************************/ + +MemoryLeakFinderPane ScaledLabel#base_snapshot_label_ +{ + font-size: 12pt; +} + +MemoryLeakFinderPane ScaledLabel#diff_snapshot_label_ +{ + font-size: 12pt; +} + +/*************************************/ +/* Zoom icons +/*************************************/ + +QPushButton#zoom_to_selection_button_ +{ + background-color: white; + border: none; +} + +QPushButton#zoom_reset_button_ +{ + background-color: white; + border: none; +} + +QPushButton#zoom_in_button_ +{ + background-color: white; + border: none; +} + +QPushButton#zoom_out_button_ +{ + background-color: white; + border: none; +} + +/*************************************/ +/* Device Configuration Pane +/*************************************/ + +DeviceConfigurationPane QLabel#label_amd_logo_ +{ + image: url(:/Resources/assets/amd_logo.svg); +} diff --git a/source/frontend/util/constants.h b/source/frontend/util/constants.h new file mode 100644 index 0000000..c89bd5b --- /dev/null +++ b/source/frontend/util/constants.h @@ -0,0 +1,134 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Intended to hold globally-known definitions. +//============================================================================= + +#ifndef RMV_UTIL_CONSTANTS_H_ +#define RMV_UTIL_CONSTANTS_H_ + +#include + +namespace rmv +{ + static const QString kRmvExecutableBaseFilename = "RadeonMemoryVisualizer"; + +#ifdef _DEBUG + static const QString kRmvExecutableDebugIdentifier = "-d"; +#endif + + // Checking for updates. + static const QString kRmvUpdateCheckAssetName = "RMV-Updates.json"; + static const QString kRmvUpdateCheckCheckingForUpdates = "Checking for updates..."; + static const QString kRmvUpdateCheckNoUpdatesAvailable = "Checking for updates... done.\n\nNo updates available."; + +#ifdef _DEBUG + static const QString kRmvUpdateCheckUrl = "."; +#else + static const QString kRmvUpdateCheckUrl = "https://api.github.com/repos/GPUOpen-Tools/radeon_memory_visualizer/releases/latest"; +#endif + + static const int kUpdatesPendingDialogWidth = 400; + static const int kUpdatesPendingDialogHeight = 150; + static const int kUpdatesResultsDialogWidth = 400; + static const int kUpdatesResultsDialogHeight = 300; + + // Rendering things. + static const int kHoverDarkenColor = 125; + + // memory subscription colors. + static const QColor kUnderSubscribedColor = QColor(0, 175, 80); + static const QColor kOverSubscribedColor = QColor(255, 0, 0); + static const QColor kCloseToSubscribedColor = QColor(226, 124, 48); + + // Widget lengths and heights. + static const int kSearchBoxWidth = 200; + static const int kDoubleSliderWidth = 200; + static const int kDoubleSliderHeight = 20; + static const int kColoredLegendsHeight = 20; + static const int kAllocationHeight = 100; + static const int kHeapComboBoxWidth = 140; + static const int kResourceComboBoxWidth = 140; + static const int kSizeSliderRange = 100; + + // Control window sizes. + static const int kDesktopMargin = 25; + static const float kDesktopAvailableWidthPercentage = 99.0F; + static const float kDesktopAvailableHeightPercentage = 95.0F; + static const float kDebugWindowDesktopWidthPercentage = 66.0F; + static const float kDebugWindowDesktopHeightPercentage = 25.0F; + + // App colors. + static const QColor kCheckboxEnableColor = QColor(0, 122, 217); + + namespace text + { + // Delete recent file pop up dialog. + static const QString kDeleteRecentTraceTitle = "Error"; + static const QString kDeleteRecentTraceText = "The trace failed to load. Do you want to remove the trace %1 from the recent files list?"; + static const QString kRecentTraceAlreadyOpenedTitle = "Warning"; + static const QString kRecentTraceAlreadyOpenedText = + "Opening the trace file as read-only (snapshot edits will not be saved). The RMV file is either read only or has been opened in another instance " + "of RMV"; + + // Open recent trace missing pop up dialog. + static const QString kOpenRecentTraceTitle = "Trace not opened"; + static const QString kOpenRecentTraceStart = "Trace \""; + static const QString kOpenRecentTraceEnd = "\" does not exist!"; + + // Allocation overview pane sort modes. + static const QString kSortByAllocationId = "Sort by allocation id"; + static const QString kSortByAllocationSize = "Sort by allocation size"; + static const QString kSortByAllocationAge = "Sort by allocation age"; + static const QString kSortByResourceCount = "Sort by resource count"; + static const QString kSortByFragmentationScore = "Sort by fragmentation score"; + + // Sort directions. + static const QString kSortAscending = "Ascending"; + static const QString kSortDescending = "Descending"; + + // Other text strings. + static const QString kPreferredHeap = "Preferred heap"; + static const QString kActualHeap = "Actual heap"; + static const QString kResourceUsage = "Resource usage"; + + // Time units. + static const QString kSettingsUnitsClocks = "Clocks"; + static const QString kSettingsUnitsMilliseconds = "Milliseconds"; + static const QString kSettingsUnitsSeconds = "Seconds"; + static const QString kSettingsUnitsMinutes = "Minutes"; + + // The file extension for RMV trace files. + static const QString kTraceFileExtension = ".rmv"; + + // Help file locations for trace and RMV. + static const QString kTraceHelpFile = "/docs/help/rdp/html/index.html"; + static const QString kRmvHelpFile = "/docs/help/rmv/html/index.html"; + static const QString kRmvLicenseFile = "/docs/License.htm"; + static const QString kSampleTraceLocation = "/samples/sampleTrace" + kTraceFileExtension; + static const QString kFileOpenFileTypes = "RMV trace files (*.rmv);;All files (*)"; + static const QString kMissingRmvTrace = "Missing RMV sample trace: "; + static const QString kMissingRmvHelpFile = "Missing RMV help file: "; + } // namespace text + + namespace resource + { + // Stylesheet resource. + static const QString kStylesheet = ":/Resources/stylesheet.qss"; + + // Zoom in/out svg resources. + static const QString kZoomInEnabled = ":/Resources/assets/zoom_in.svg"; + static const QString kZoomOutEnabled = ":/Resources/assets/zoom_out.svg"; + static const QString kZoomInDisabled = ":/Resources/assets/zoom_in_disabled.svg"; + static const QString kZoomOutDisabled = ":/Resources/assets/zoom_out_disabled.svg"; + + static const QString kZoomToSelectionEnabled = ":/Resources/assets/zoom_to_selection.svg"; + static const QString kZoomResetEnabled = ":/Resources/assets/zoom_reset.svg"; + static const QString kZoomToSelectionDisabled = ":/Resources/assets/zoom_to_selection_disabled.svg"; + static const QString kZoomResetDisabled = ":/Resources/assets/zoom_reset_disabled.svg"; + + } // namespace resource +} // namespace rmv + +#endif // RMV_UTIL_CONSTANTS_H_ diff --git a/source/frontend/util/definitions.h b/source/frontend/util/definitions.h new file mode 100644 index 0000000..43b1f96 --- /dev/null +++ b/source/frontend/util/definitions.h @@ -0,0 +1,23 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Intended to hold globally-known definitions. +//============================================================================= + +#ifndef RMV_UTIL_DEFINITIONS_H_ +#define RMV_UTIL_DEFINITIONS_H_ + +#ifdef _WIN32 +#pragma warning(disable : 4189) +#endif + +#ifdef _DEBUG +#define RMV_DEBUG_WINDOW 1 +#else +#define RMV_DEBUG_WINDOW 0 +#endif + +#include "util/constants.h" + +#endif // RMV_UTIL_DEFINITIONS_H_ diff --git a/source/frontend/util/log_file_writer.cpp b/source/frontend/util/log_file_writer.cpp new file mode 100644 index 0000000..d425033 --- /dev/null +++ b/source/frontend/util/log_file_writer.cpp @@ -0,0 +1,102 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the LogFileWriter. The LogFileWriter facilitates +/// writing of log messages to a log file. +//============================================================================= + +#include "util/log_file_writer.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + +#include + +#include "util/rmv_util.h" + +namespace rmv +{ + // LogFileWriter instance + LogFileWriter* LogFileWriter::instance_ = nullptr; + + LogFileWriter::LogFileWriter() +#ifdef _DEBUG + : log_level_(kDebug) +#else + : log_level_(kError) +#endif + { + // Delete the log file from the previous instance + QFile::remove(GetLogFileLocation()); + } + + LogFileWriter::~LogFileWriter(){}; + + LogFileWriter& LogFileWriter::Get() + { + if (instance_ == nullptr) + { + instance_ = new LogFileWriter(); + } + + return *instance_; + } + + void LogFileWriter::WriteLogMessage(const char* log_message) + { + // Lock the mutex before writing out the log to the file + // The mutex gets released when this method returns + QMutexLocker locker(&mutex_); + + // Get the file name and location + QFile file(GetLogFileLocation()); + + // Open the file + if (file.open(QIODevice::WriteOnly | QIODevice::Append)) + { + // Write the data to the file + file.write(log_message); + + // Add a new line to the file + file.write("\r\n"); + + // Now close the file + file.close(); + } + } + + void LogFileWriter::WriteLog(LogLevel log_level, const char* log_message, ...) + { + if (log_level <= log_level_) + { + static const int kBufferSize = 2048; + char buffer[kBufferSize]; + va_list args; + + va_start(args, log_message); + vsnprintf(buffer, kBufferSize, log_message, args); + WriteLogMessage(buffer); + va_end(args); + } + } + + QString LogFileWriter::GetLogFileLocation() + { + QString log_file = ""; + + // Get file location + log_file = rmv_util::GetFileLocation(); + + // Add the file name + log_file.append("/RMVLogFile.txt"); + + return log_file; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/util/log_file_writer.h b/source/frontend/util/log_file_writer.h new file mode 100644 index 0000000..4739f48 --- /dev/null +++ b/source/frontend/util/log_file_writer.h @@ -0,0 +1,59 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Declaration for the log file writer +//============================================================================= + +#ifndef RMV_UTIL_LOG_FILE_WRITER_H_ +#define RMV_UTIL_LOG_FILE_WRITER_H_ + +#include +#include + +namespace rmv +{ + /// Log file writer class definition. + class LogFileWriter + { + public: + // Log levels used by the logger from most severe to least severe. + enum LogLevel + { + kError, + kWarning, + kInfo, + kDebug, + }; + + /// LogFileWriter instance get function. + /// \return a reference to the LogFileWriter instance. + static LogFileWriter& Get(); + + /// Write a string to the log file. + /// \param log_level The log level to use for this message. + /// \param log_message The message to write to the log file. + void WriteLog(LogLevel log_level, const char* log_message, ...); + + /// Get the location of the log file. + /// \return The location of the log file. + QString GetLogFileLocation(); + + private: + /// Constructor. + explicit LogFileWriter(); + + /// Destructor. + ~LogFileWriter(); + + /// Write the log out to the log file. + /// \param log_message The message to write to the log file. + void WriteLogMessage(const char* log_message); + + QMutex mutex_; ///< The mutex to write the log. + static LogFileWriter* instance_; ///< LogFileWriter instance pointer. + LogLevel log_level_; ///< The current log level. + }; +} // namespace rmv + +#endif // RMV_UTIL_LOG_FILE_WRITER_H_ diff --git a/source/frontend/util/rmv_util.cpp b/source/frontend/util/rmv_util.cpp new file mode 100644 index 0000000..8df2a19 --- /dev/null +++ b/source/frontend/util/rmv_util.cpp @@ -0,0 +1,145 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of rmv_util which holds useful utility functions. +//============================================================================= + +#include "util/rmv_util.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include + +#include +#endif + +#include + +#include "rmt_assert.h" + +#include "settings/rmv_settings.h" +#include "util/version.h" + +// Get the brightness of a given color. Adds weighting values to the color +// components to compute a color brightness. HSV won't work that well here +// as the brightness for 2 given hues may be different for identical +// saturation and value. +// Uses a standard luminance formula. +// \param background_color The color to calculate the brightness of. +// \return The brightness (0 is black, 255 is white). +static int GetColorBrightness(const QColor& background_color) +{ + double r = background_color.red(); + double g = background_color.green(); + double b = background_color.blue(); + double brightness = (0.3 * r) + (0.59 * g) + 0.11 * b; + + return static_cast(brightness); +} + +QColor rmv_util::GetTextColorForBackground(const QColor& background_color, bool has_white_background) +{ + int brightness = GetColorBrightness(background_color); + if (brightness > 128) + { + return Qt::black; + } + else + { + if (has_white_background) + { + return Qt::lightGray; + } + return Qt::white; + } +} + +QString rmv_util::GetFileLocation() +{ + QString file_location = ""; + +#ifdef _WIN32 + LPWSTR wsz_path = nullptr; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &wsz_path); + Q_UNUSED(hr); + Q_ASSERT(hr == S_OK); + + file_location = QString::fromUtf16((const ushort*)wsz_path); + file_location.append("/" + rmv::kRmvExecutableBaseFilename); + +#else + + struct passwd* pw = getpwuid(getuid()); + if (pw != nullptr) + { + const char* homedir = pw->pw_dir; + file_location = homedir; + } + file_location.append("/." + rmv::kRmvExecutableBaseFilename); +#endif + + // make sure the folder exists. If not, create it + std::string dir = file_location.toStdString(); + if (QDir(dir.c_str()).exists() == false) + { + QDir qdir; + qdir.mkpath(dir.c_str()); + } + + return file_location; +} + +QColor rmv_util::GetSnapshotStateColor(SnapshotState state) +{ + QColor out = Qt::black; + + switch (state) + { + case kSnapshotStateNone: + out = Qt::black; + break; + case kSnapshotStateViewed: + out = RMVSettings::Get().GetColorSnapshotViewed(); + break; + case kSnapshotStateCompared: + out = RMVSettings::Get().GetColorSnapshotCompared(); + break; + case kSnapshotStateCount: + out = Qt::black; + break; + default: + break; + } + + return out; +} + +QColor rmv_util::GetDeltaChangeColor(DeltaChange delta) +{ + QColor out = Qt::black; + + switch (delta) + { + case kDeltaChangeIncrease: + out = RMVSettings::Get().GetColorDeltaIncrease(); + break; + case kDeltaChangeDecrease: + out = RMVSettings::Get().GetColorDeltaDecrease(); + break; + case kDeltaChangeNone: + out = RMVSettings::Get().GetColorDeltaNoChange(); + break; + case kDeltaChangeCount: + out = Qt::black; + break; + default: + break; + } + + return out; +} diff --git a/source/frontend/util/rmv_util.h b/source/frontend/util/rmv_util.h new file mode 100644 index 0000000..9cc1502 --- /dev/null +++ b/source/frontend/util/rmv_util.h @@ -0,0 +1,63 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for rmv_util which holds useful utility functions. +//============================================================================= + +#ifndef RMV_UTIL_RMV_UTIL_H_ +#define RMV_UTIL_RMV_UTIL_H_ + +#include "util/definitions.h" + +/// The state of a snapshot in the timeline pane. +enum SnapshotState +{ + kSnapshotStateNone, + kSnapshotStateViewed, + kSnapshotStateCompared, + + kSnapshotStateCount, +}; + +/// The state of a comparison between 2 snapshot paramters. +enum DeltaChange +{ + kDeltaChangeIncrease, + kDeltaChangeDecrease, + kDeltaChangeNone, + + kDeltaChangeCount, +}; + +namespace rmv_util +{ + /// Get file path to RMV log/settings file. Find the 'Temp' folder on the local OS + /// and create an RMV subfolder (on linux, create .RMV folder). + /// \return The location of the settings and log file. + QString GetFileLocation(); + + /// Get the text color that works best displayed on top of a given background + /// color. Make the light color off-white so it can be seen against the white + /// background. + /// \param background_color The color that the text is to be displayed on top of. + /// \param has_white_background Is the text drawn to a mainly white background? + /// This will be the case for small objects that need coloring where the text + /// extends onto the background. If this is the case and the text color needs to + /// be light to contrast against a darker color, use a light gray rather than white. + /// \return The text color, either black or white/light gray. + QColor GetTextColorForBackground(const QColor& background_color, bool has_white_background = false); + + /// Get the color needed for a snapshot state. + /// \param state The snapshot state. + /// \return a QColor assigned to the state. + QColor GetSnapshotStateColor(SnapshotState state); + + /// Get the color needed for a delta change. + /// \param delta The type of delta. + /// \return a QColor assigned to the delta type. + QColor GetDeltaChangeColor(DeltaChange delta); + +}; // namespace rmv_util + +#endif // RMV_UTIL_RMV_UTIL_H_ diff --git a/source/frontend/util/string_util.cpp b/source/frontend/util/string_util.cpp new file mode 100644 index 0000000..ea4e22d --- /dev/null +++ b/source/frontend/util/string_util.cpp @@ -0,0 +1,190 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a number of string utilities. +//============================================================================= + +#include "string_util.h" + +#include +#include + +#include "qt_common/utils/qt_util.h" + +QString rmv::string_util::ToUpperCase(const QString& string) +{ + QString out; + + for (int i = 0; i < string.length(); i++) + { + char c = string.at(i).toLatin1(); + out.append(c >= 'a' && c <= 'z' ? c - 32 : c); + } + + return out; +} + +QString rmv::string_util::Convert128BitHashToString(uint64_t upper_bits, uint64_t lower_bits) +{ + QString out = ""; + + if (upper_bits == 0 && lower_bits == 0) + { + out = "N/A"; + } + else + { + if (upper_bits == 0) + { + out = "0x" + QtCommon::QtUtils::HashToStr(lower_bits); + } + else + { + out = "0x" + QtCommon::QtUtils::HashToStr(upper_bits) + QtCommon::QtUtils::HashToStr(lower_bits); + } + } + + return out; +} + +QString rmv::string_util::LocalizedValue(int64_t value) +{ + QString str = ""; + QTextStream out(&str); + out.setRealNumberNotation(QTextStream::FixedNotation); + out.setLocale(QLocale::English); + out << value; + return str; +} + +QString rmv::string_util::LocalizedValuePrecise(double value) +{ + QString str = ""; + QTextStream out(&str); + out.setRealNumberPrecision(2); + out.setRealNumberNotation(QTextStream::FixedNotation); + out.setLocale(QLocale::English); + out << value; + return str; +} + +QString rmv::string_util::LocalizedValueMemory(double value, bool base_10, bool use_round) +{ + double multiple; + if (base_10) + { + multiple = 1000; + } + else + { + multiple = 1024; + } + + double scaled_size = value; + + int postfix_index = 0; + while (fabs(scaled_size) >= multiple) + { + scaled_size /= multiple; + postfix_index++; + } + + if (use_round) + { + scaled_size = round(scaled_size); + } + + static const QString kBinarySizePostfix[] = {" bytes", " KiB", " MiB", " GiB", " TiB", " PiB"}; + static const QString kBase10SizePostfix[] = {" bytes", " KB", " MB", " GB", " TB", " PB"}; + + // If index is too large, it's probably down to bad data so display as bytes in this case + if (postfix_index >= 6) + { + postfix_index = 0; + scaled_size = value; + } + + // Display value string to 2 decimal places if not bytes. No fractional part for bytes + QString value_string; + if (postfix_index != 0) + { + value_string = LocalizedValuePrecise(scaled_size); + } + else + { + value_string = LocalizedValue(scaled_size); + } + + if (base_10) + { + return value_string + kBase10SizePostfix[postfix_index]; + } + else + { + return value_string + kBinarySizePostfix[postfix_index]; + } +} + +QString rmv::string_util::LocalizedValueAddress(RmtGpuAddress address) +{ + QString str = "0x" + QString::number(address, 16); + return str; +} + +QString rmv::string_util::GetResourceUsageString(RmtResourceUsageType usage_type) +{ + QString out = ""; + + switch (usage_type) + { + case kRmtResourceUsageTypeDepthStencil: + out = "Depth stencil buffer"; + break; + case kRmtResourceUsageTypeRenderTarget: + out = "Render target"; + break; + case kRmtResourceUsageTypeTexture: + out = "Texture"; + break; + case kRmtResourceUsageTypeVertexBuffer: + out = "Vertex buffer"; + break; + case kRmtResourceUsageTypeIndexBuffer: + out = "Index buffer"; + break; + case kRmtResourceUsageTypeUav: + out = "UAV"; + break; + case kRmtResourceUsageTypeShaderPipeline: + out = "Shader pipeline"; + break; + case kRmtResourceUsageTypeCommandBuffer: + out = "Command buffer"; + break; + case kRmtResourceUsageTypeHeap: + out = "Heap"; + break; + case kRmtResourceUsageTypeDescriptors: + out = "Descriptors"; + break; + case kRmtResourceUsageTypeBuffer: + out = "Multi-use buffer"; + break; + case kRmtResourceUsageTypeFree: + out = "Unbound"; + break; + case kRmtResourceUsageTypeGpuEvent: + out = "Event"; + break; + case kRmtResourceUsageTypeInternal: + out = "Internal"; + break; + + default: + out = "Unknown"; + break; + } + + return out; +} diff --git a/source/frontend/util/string_util.h b/source/frontend/util/string_util.h new file mode 100644 index 0000000..877c708 --- /dev/null +++ b/source/frontend/util/string_util.h @@ -0,0 +1,62 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Definition of a number of string utilities. +//============================================================================= + +#ifndef RMV_UTIL_STRING_UTIL_H_ +#define RMV_UTIL_STRING_UTIL_H_ + +#include +#include + +#include "rmt_resource_list.h" + +namespace rmv +{ + namespace string_util + { + /// Using Qt's toUpper() throus a warning so just do this manually here. + /// Assumes ASCII and really only intended to capitalize hex number representations. + /// \param string The string to capitalize. + /// \return capitalized string. + QString ToUpperCase(const QString& string); + + /// Construct a string representation of a shader address. + /// \param upper_bits bits on the left. + /// \param lower_bits bits on the right. + /// \return a flat string. + QString Convert128BitHashToString(uint64_t upper_bits, uint64_t lower_bits); + + /// Given an integer, return a string localized to English format. + /// \param value The value to convert. + QString LocalizedValue(int64_t value); + + /// Given an integer, return a string localized to English format. + /// \param value The value to convert. + QString LocalizedValuePrecise(double value); + + /// Get the localized string as a memory size. Append the memory units to the + /// end of the string. + /// \param value The value to display. + /// \param base_10 Whether to use base 10 values. If true, memory is returned + /// assuming factors of 1000. If false, use 1024 (base 2 values). Units are + /// appended to display XB for base 10 or XiB for base 2. + /// \return The localized string. + QString LocalizedValueMemory(double value, bool base_10, bool use_round); + + /// Format an address for printing. + /// \param address The address to convert. + /// \return The localized string. + QString LocalizedValueAddress(RmtGpuAddress address); + + /// Helper function to fetch a string representation of a resource usage type. + /// \param usage_type resource usage type. + /// \return A string representation of a resource usage. + QString GetResourceUsageString(RmtResourceUsageType usage_type); + + } // namespace string_util +} // namespace rmv + +#endif // RMV_UTIL_STRING_UTIL_H_ diff --git a/source/frontend/util/thread_controller.cpp b/source/frontend/util/thread_controller.cpp new file mode 100644 index 0000000..33da3a0 --- /dev/null +++ b/source/frontend/util/thread_controller.cpp @@ -0,0 +1,70 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a thread controller. +//============================================================================= + +#include "util/thread_controller.h" + +#include "rmt_util.h" + +#include "views/main_window.h" + +namespace rmv +{ + BackgroundTask::BackgroundTask() + { + } + + BackgroundTask::~BackgroundTask() + { + } + + void BackgroundTask::Start() + { + ThreadFunc(); + emit WorkerFinished(); + } + + ThreadController::ThreadController(MainWindow* main_window, QWidget* parent, BackgroundTask* background_task) + : main_window_(main_window) + , background_task_(background_task) + , finished_(false) + { + // start the loading animation + main_window_->StartAnimation(parent, 0); + + // create the thread. It will be setup to be deleted below when the thread has + // finished (it emits a QThread::Finished signal) + thread_ = new QThread(); + background_task_->moveToThread(thread_); + + connect(thread_, &QThread::started, background_task_, &BackgroundTask::Start); + connect(background_task_, &BackgroundTask::WorkerFinished, thread_, &QThread::quit); + connect(background_task_, &BackgroundTask::WorkerFinished, this, &ThreadController::WorkerFinished); + + // set up signal connections to automatically delete thread_ and background_task_ when work is done: + connect(background_task_, &BackgroundTask::WorkerFinished, background_task_, &BackgroundTask::deleteLater); + connect(thread_, &QThread::finished, thread_, &QThread::deleteLater); + + thread_->start(); + } + + ThreadController::~ThreadController() + { + } + + void ThreadController::WorkerFinished() + { + main_window_->StopAnimation(); + finished_ = true; + emit ThreadFinished(); + } + + bool ThreadController::Finished() const + { + return finished_; + } + +} // namespace rmv diff --git a/source/frontend/util/thread_controller.h b/source/frontend/util/thread_controller.h new file mode 100644 index 0000000..6b51ee2 --- /dev/null +++ b/source/frontend/util/thread_controller.h @@ -0,0 +1,79 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a thread controller. The thread controller is used to +/// manage work done on a separate thread so as to not lock up the UI (main) +/// thread. Responsible for setting up and starting the worker thread and +/// starting and stopping the loading animation in the UI thread while the work +/// is done in the worker thread +//============================================================================= + +#ifndef RMV_UTIL_THREAD_CONTROLLER_H_ +#define RMV_UTIL_THREAD_CONTROLLER_H_ + +#include +#include + +class MainWindow; + +namespace rmv +{ + /// The base class for a background task. This is the object that will be run + /// from the thread_controller. Custom jobs can inherit from this class + /// and implement the ThreadFunc() function. + class BackgroundTask : public QObject + { + Q_OBJECT + + public: + /// Constructor. + BackgroundTask(); + + /// Destructor. + ~BackgroundTask(); + + /// Implement these in derived classes. + virtual void ThreadFunc() = 0; + + /// The function that runs the thread. Calls the derived + /// ThreadFunc() and cleans up afterwards. + void Start(); + + signals: + /// Indicate that initial processing of the pane has completed. + void WorkerFinished(); + }; + + class ThreadController : public QObject + { + Q_OBJECT + + public: + /// Constructor + /// Will take ownership of the worker thread and delete it later. + explicit ThreadController(MainWindow* main_window, QWidget* parent, BackgroundTask* worker); + + /// Destructor + virtual ~ThreadController(); + + /// This is run in the main thread once the worker thread has finished. + void WorkerFinished(); + + /// Has the thread finished. + /// \return true if finished, false if not. + bool Finished() const; + + signals: + /// Indicate that the worker thread has finished. + void ThreadFinished(); + + private: + MainWindow* main_window_; ///< Pointer to the main window. + QThread* thread_; ///< The worker thread. + BackgroundTask* background_task_; ///< The worker object that does the work. + bool finished_; ///< Is the data valid. + }; +} // namespace rmv + +#endif // RMV_UTIL_THREAD_CONTROLLER_H_ diff --git a/source/frontend/util/time_util.cpp b/source/frontend/util/time_util.cpp new file mode 100644 index 0000000..7977493 --- /dev/null +++ b/source/frontend/util/time_util.cpp @@ -0,0 +1,62 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a number of time-related utilities. +//============================================================================= + +#include "util/time_util.h" + +#include "qt_common/utils/qt_util.h" + +#include "rmt_assert.h" +#include "rmt_error.h" +#include "rmt_data_set.h" + +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" + +namespace rmv +{ + QString time_util::ClockToTimeUnit(uint64_t clk) + { + double time = 0.0; + + TimeUnitType unit_type = RMVSettings::Get().GetUnits(); + + if (unit_type == kTimeUnitTypeClk) + { + time = clk; + } + else + { + TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + // This should only get called if the CPU clock timestamp is valid + const RmtDataSet* data_set = trace_manager.GetDataSet(); + RmtErrorCode error_code = RmtDataSetGetCpuClockTimestamp(data_set, clk, &time); + RMT_UNUSED(error_code); + RMT_ASSERT(error_code == RMT_OK); + } + } + + return QtCommon::QtUtils::ClockToTimeUnit(time, unit_type); + } + + double time_util::TimeToClockRatio() + { + double ratio = 1.0; + if (RMVSettings::Get().GetUnits() != kTimeUnitTypeClk) + { + TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSet* data_set = trace_manager.GetDataSet(); + RmtDataSetGetCpuClockTimestamp(data_set, 1, &ratio); + } + RMT_ASSERT(ratio > 0.0); + } + return ratio; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/util/time_util.h b/source/frontend/util/time_util.h new file mode 100644 index 0000000..4908070 --- /dev/null +++ b/source/frontend/util/time_util.h @@ -0,0 +1,31 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Definition of a number of time-related utilities. +//============================================================================= + +#ifndef RMV_UTIL_TIME_UTIL_H_ +#define RMV_UTIL_TIME_UTIL_H_ + +#include + +#include "rmt_data_set.h" + +namespace rmv +{ + namespace time_util + { + /// Convert a clock to a time unit and output as string. + /// \param clk input clock to convert. + /// \return a string representing a clock value. + QString ClockToTimeUnit(uint64_t clk); + + /// Get the ratio of time units to clock units. Used to convert from time to + /// clocks and vice versa. + /// \return The ratio of clock to time. + double TimeToClockRatio(); + } // namespace time_util +} // namespace rmv + +#endif // RMV_UTIL_TIME_UTIL_H_ diff --git a/source/frontend/util/version.cpp b/source/frontend/util/version.cpp new file mode 100644 index 0000000..8201b71 --- /dev/null +++ b/source/frontend/util/version.cpp @@ -0,0 +1,10 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Version number info for RMV +//============================================================================= + +#include "util/version.h" + +const char* version_string = "RmvVersion=" RMV_VERSION_STRING; diff --git a/source/frontend/util/version.h b/source/frontend/util/version.h new file mode 100644 index 0000000..d65d207 --- /dev/null +++ b/source/frontend/util/version.h @@ -0,0 +1,29 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Version number info for RMV +//============================================================================= + +#ifndef RMV_VERSION_H +#define RMV_VERSION_H + +#define STRINGIFY_VALUE(x) STRINGIFY(x) +#define STRINGIFY(x) #x + +#define RMV_APP_NAME "Radeon Memory Visualizer" ///< Application name +#define RMV_BUILD_SUFFIX "" ///< The build suffix to apply to the product name.(alpha, beta etc.) + +#define RMV_MAJOR_VERSION 1 ///< major version number +#define RMV_MINOR_VERSION 1 ///< minor version number +#define RMV_BUGFIX_NUMBER 0 ///< bugfix number +#define RMV_BUILD_NUMBER 0 ///< build number +#define RMV_BUILD_DATE_STRING "11/24/2020" ///< build date string + +#define RMV_COPYRIGHT_STRING "Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved." + +#define RMV_VERSION_STRING \ + STRINGIFY_VALUE(RMV_MAJOR_VERSION) \ + "." STRINGIFY_VALUE(RMV_MINOR_VERSION) "." STRINGIFY_VALUE(RMV_BUGFIX_NUMBER) "." STRINGIFY_VALUE(RMV_BUILD_NUMBER) ///< The full revision string. + +#endif // RMV_VERSION_H diff --git a/source/frontend/util/widget_util.cpp b/source/frontend/util/widget_util.cpp new file mode 100644 index 0000000..f43c573 --- /dev/null +++ b/source/frontend/util/widget_util.cpp @@ -0,0 +1,116 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a number of widget utilities. These functions +/// apply a common look and feel to various widget types. +//============================================================================= + +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "util/constants.h" +#include "util/widget_util.h" + +namespace rmv +{ + void widget_util::InitDoubleSlider(DoubleSliderWidget* double_slider_widgets) + { + double_slider_widgets->setFixedWidth(ScalingManager::Get().Scaled(rmv::kDoubleSliderWidth)); + double_slider_widgets->setFixedHeight(ScalingManager::Get().Scaled(rmv::kDoubleSliderHeight)); + double_slider_widgets->setCursor(Qt::PointingHandCursor); + double_slider_widgets->setMinimum(0); + double_slider_widgets->setMaximum(kSizeSliderRange); + double_slider_widgets->Init(); + } + + void widget_util::InitSingleSelectComboBox(QWidget* parent, + ArrowIconComboBox* combo_box, + const QString& default_text, + bool retain_default_text, + const QString prefix_text) + { + if (combo_box != nullptr) + { + combo_box->InitSingleSelect(parent, default_text, retain_default_text, prefix_text); + combo_box->setCursor(Qt::PointingHandCursor); + } + } + + void widget_util::InitMultiSelectComboBox(QWidget* parent, ArrowIconComboBox* combo_box, const QString& default_text) + { + if (combo_box != nullptr) + { + combo_box->InitMultiSelect(parent, default_text); + combo_box->setCursor(Qt::PointingHandCursor); + } + } + + void widget_util::InitGraphicsView(QGraphicsView* view, uint32_t fixed_height) + { + if (view != nullptr) + { + view->setFixedHeight(fixed_height); + view->setFrameStyle(QFrame::NoFrame); + view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + } + + void widget_util::SetWidgetBackgroundColor(QWidget* widget, const QColor& color) + { + if (widget != nullptr) + { + QPalette palette(widget->palette()); + palette.setColor(QPalette::Background, color); + widget->setPalette(palette); + widget->setAutoFillBackground(true); + } + } + + void widget_util::ApplyStandardPaneStyle(QWidget* root, QWidget* main_content, QScrollArea* scroll_area) + { + SetWidgetBackgroundColor(root, Qt::white); + SetWidgetBackgroundColor(main_content, Qt::white); + scroll_area->setFrameStyle(QFrame::NoFrame); + SetWidgetBackgroundColor(scroll_area, Qt::white); + } + + void widget_util::InitCommonFilteringComponents(TextSearchWidget* text_search_widget, DoubleSliderWidget* double_slider_widget) + { + text_search_widget->setFixedWidth(rmv::kSearchBoxWidth); + + double_slider_widget->setFixedWidth(ScalingManager::Get().Scaled(rmv::kDoubleSliderWidth)); + double_slider_widget->setFixedHeight(ScalingManager::Get().Scaled(rmv::kDoubleSliderHeight)); + double_slider_widget->setCursor(Qt::PointingHandCursor); + double_slider_widget->setMinimum(0); + double_slider_widget->setMaximum(kSizeSliderRange); + double_slider_widget->Init(); + } + + void widget_util::InitColorLegend(ColoredLegendScene*& legend_widget, QGraphicsView* view) + { + legend_widget = new ColoredLegendScene(); + view->setScene(legend_widget); + } + + void widget_util::UpdateTablePalette(QTableView* view) + { + QPalette p = view->palette(); + p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight)); + p.setColor(QPalette::Inactive, QPalette::HighlightedText, p.color(QPalette::Active, QPalette::HighlightedText)); + view->setPalette(p); + } + + int widget_util::GetTableHeight(const QTableView* table_view, int row_count) + { + // Calculate the maximum height the table needs to be. + // Note the frame width is the gap between the frame and the surrounded + // widget; there is no frame height since it's the same as the frame width. + int row_height = table_view->rowHeight(0); + int header_height = table_view->horizontalHeader()->height(); + int frame_width = 2 * table_view->frameWidth(); + return (row_count * row_height) + header_height + frame_width; + } +} // namespace rmv diff --git a/source/frontend/util/widget_util.h b/source/frontend/util/widget_util.h new file mode 100644 index 0000000..e629422 --- /dev/null +++ b/source/frontend/util/widget_util.h @@ -0,0 +1,88 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Definition of a number of widget utilities. These functions +/// apply a common look and feel to various widget types. +//============================================================================= + +#ifndef RMV_UTIL_WIDGET_UTIL_H_ +#define RMV_UTIL_WIDGET_UTIL_H_ + +#include +#include +#include +#include +#include + +#include "qt_common/custom_widgets/text_search_widget.h" +#include "qt_common/custom_widgets/double_slider_widget.h" +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" +#include "qt_common/custom_widgets/colored_legend_scene.h" + +namespace rmv +{ + namespace widget_util + { + /// Initialize a double-slider widgets. + void InitDoubleSlider(DoubleSliderWidget* double_slider_widgets); + + /// Initialize an ArrowIconComboBox for single selection. + /// \param parent The combo box parent. + /// \param combo_box The combo box to initialize. + /// \param default_text The default text string. + /// \param retain_default_text If true, the combo box text doesn't change to the selected + /// item text (in the case of combo boxes requiring check boxes). + /// \param prefix_text The text used to prefix the default text. + void InitSingleSelectComboBox(QWidget* parent, + ArrowIconComboBox* combo_box, + const QString& default_text, + bool retain_default_text, + const QString prefix_text = ""); + + /// Initialize an ArrowIconComboBox for multi selection. + /// \param parent The combo box parent. + /// \param combo_box The combo box to initialize. + /// \param default_text The default text string. + void InitMultiSelectComboBox(QWidget* parent, ArrowIconComboBox* combo_box, const QString& default_text); + + /// Initialize a graphics view to some common defaults. + /// \param view Pointer to the QGraphicsView. + /// \param fixed_height The maximum height. + void InitGraphicsView(QGraphicsView* view, uint32_t fixed_height); + + /// Set a widget's background color. + /// \param widget The widget to change background color. + /// \param color The new color. + void SetWidgetBackgroundColor(QWidget* widget, const QColor& color); + + /// Apply standard styling for a given top level pane. + /// \param root The root widget. + /// \param main_content The widget containing all content. + /// \param scroll_area The scroll area. + void ApplyStandardPaneStyle(QWidget* root, QWidget* main_content, QScrollArea* scroll_area); + + /// Init search box and double slider. + /// \param text_search_widget text search item. + /// \param double_slider_widget double slider item. + void InitCommonFilteringComponents(TextSearchWidget* text_search_widget, DoubleSliderWidget* double_slider_widget); + + /// Method to initialize color legends. + /// \param legend_widget The scene for this legend. + /// \param view The graphics view for this legend. + void InitColorLegend(ColoredLegendScene*& legend_widget, QGraphicsView* view); + + /// Set a custom palette for tables. + /// \param view the table view. + void UpdateTablePalette(QTableView* view); + + /// Get the height of a table depending on how many rows are in the table. Used to make the + /// table as large as it needs to be so there are no empty rows. + /// \param table_view The table. + /// \param row_count The number of rows. + int GetTableHeight(const QTableView* table_view, int row_count); + + } // namespace widget_util +} // namespace rmv + +#endif // RMV_UTIL_WIDGET_UTIL_H_ diff --git a/source/frontend/views/base_pane.cpp b/source/frontend/views/base_pane.cpp new file mode 100644 index 0000000..f494cf9 --- /dev/null +++ b/source/frontend/views/base_pane.cpp @@ -0,0 +1,42 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a base pane class +//============================================================================= + +#include "base_pane.h" + +BasePane::BasePane(QWidget* parent) + : QWidget(parent) +{ +} + +BasePane::~BasePane() +{ +} + +void BasePane::OpenSnapshot(RmtDataSnapshot* snapshot) +{ + Q_UNUSED(snapshot); +} + +void BasePane::PaneSwitched() +{ +} + +void BasePane::SwitchTimeUnits() +{ +} + +void BasePane::OnTraceClose() +{ +} + +void BasePane::Reset() +{ +} + +void BasePane::ChangeColoring() +{ +} diff --git a/source/frontend/views/base_pane.h b/source/frontend/views/base_pane.h new file mode 100644 index 0000000..6730f78 --- /dev/null +++ b/source/frontend/views/base_pane.h @@ -0,0 +1,48 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a base pane class +//============================================================================= + +#ifndef RMV_VIEWS_BASE_PANE_H_ +#define RMV_VIEWS_BASE_PANE_H_ + +#include + +typedef struct RmtDataSnapshot RmtDataSnapshot; + +/// Base class for a pane in the UI. +class BasePane : public QWidget +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The parent widget. + explicit BasePane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~BasePane(); + + /// Open a snapshot. + /// \param snapshot The snapshot to open. + virtual void OpenSnapshot(RmtDataSnapshot* snapshot); + + /// Switch panes. + virtual void PaneSwitched(); + + /// Switch the time units. + virtual void SwitchTimeUnits(); + + /// Trace closed. + virtual void OnTraceClose(); + + /// Reset the UI. + virtual void Reset(); + + /// Change the UI coloring based on the settings. + virtual void ChangeColoring(); +}; + +#endif // RMV_VIEWS_BASE_PANE_H_ diff --git a/source/frontend/views/colorizer.cpp b/source/frontend/views/colorizer.cpp new file mode 100644 index 0000000..869a5fa --- /dev/null +++ b/source/frontend/views/colorizer.cpp @@ -0,0 +1,115 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a colorizer control. +/// The colorizer is responsible for the functionality for the "color by" +/// combo box across multiple panes. It sets up the combo box with all or +/// a subset of the available coloring modes and updates the allocations and +/// resource widgets and the legends depending on which coloring mode is +/// required. +//============================================================================= + +#include "views/colorizer.h" + +#include + +#include "rmt_assert.h" + +#include "util/widget_util.h" + +static const QString kColorByAllocation = "Color by allocation"; +static const QString kColorByPreferredHeap = "Color by preferred heap"; +static const QString kColorByActualHeap = "Color by actual heap"; +static const QString kColorByResourceUsage = "Color by resource usage"; +static const QString kColorByAllocationAge = "Color by allocation age"; +static const QString kColorByResourceCreateAge = "Color by resource create time"; +static const QString kColorByResourceBindAge = "Color by resource bind time"; +static const QString kColorByResourceGUID = "Color by resource id"; +static const QString kColorByCPUMapped = "Color by CPU mapped"; +static const QString kColorByNotAllPreferred = "Color by not all in preferred heap"; +static const QString kColorByAliasing = "Color by aliasing"; +static const QString kColorByCommitType = "Color by commit type"; + +Colorizer::Colorizer() + : ColorizerBase() +{ +} + +Colorizer::~Colorizer() +{ + disconnect(combo_box_, &ArrowIconComboBox::SelectionChanged, this, &Colorizer::ApplyColorMode); +} + +void Colorizer::Initialize(QWidget* parent, ArrowIconComboBox* combo_box, ColoredLegendGraphicsView* legends_view, const ColorMode* mode_list) +{ + // Add text strings to the sort combo box + static const std::map kColorMap = {{kColorModeResourceUsageType, kColorByResourceUsage}, + {kColorModePreferredHeap, kColorByPreferredHeap}, + {kColorModeActualHeap, kColorByActualHeap}, + {kColorModeAllocationAge, kColorByAllocationAge}, + {kColorModeResourceCreateAge, kColorByResourceCreateAge}, + {kColorModeResourceBindAge, kColorByResourceBindAge}, + {kColorModeResourceGUID, kColorByResourceGUID}, + {kColorModeResourceCPUMapped, kColorByCPUMapped}, + {kColorModeNotAllPreferred, kColorByNotAllPreferred}, + {kColorModeAliasing, kColorByAliasing}, + {kColorModeCommitType, kColorByCommitType}}; + + // Set up the combo box. Get the title string from the first entry in mode_list + auto it = kColorMap.begin(); + QString combo_title_string = (*it).second; + RMT_ASSERT(mode_list != nullptr); + if (mode_list != nullptr) + { + color_mode_ = mode_list[0]; + if (color_mode_ < kColorModeCount) + { + it = kColorMap.find(color_mode_); + if (it != kColorMap.end()) + { + combo_title_string = (*it).second; + } + } + } + + rmv::widget_util::InitSingleSelectComboBox(parent, combo_box, combo_title_string, false); + + // Add the required coloring modes to the combo box and the internal map + combo_box->ClearItems(); + if (mode_list != nullptr) + { + int list_index = 0; + bool done = false; + do + { + ColorMode color_mode = mode_list[list_index]; + if (color_mode < kColorModeCount) + { + it = kColorMap.find(color_mode); + if (it != kColorMap.end()) + { + combo_box->AddItem((*it).second); + color_mode_map_[list_index] = (*it).first; + list_index++; + } + } + else + { + done = true; + } + } while (!done); + } + + // Set up connections when combo box items are selected + connect(combo_box, &ArrowIconComboBox::SelectionChanged, this, &Colorizer::ApplyColorMode); + + ColorizerBase::Initialize(combo_box, legends_view); +} + +void Colorizer::ApplyColorMode() +{ + int index = combo_box_->CurrentRow(); + color_mode_ = color_mode_map_[index]; + UpdateLegends(); +} diff --git a/source/frontend/views/colorizer.h b/source/frontend/views/colorizer.h new file mode 100644 index 0000000..98f2b0d --- /dev/null +++ b/source/frontend/views/colorizer.h @@ -0,0 +1,47 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \brief Header file for a colorizer control. +/// The colorizer is responsible for the functionality for the "color by" +/// combo box across multiple panes. It sets up the combo box with all or +/// a subset of the available coloring modes and updates the allocations and +/// resource widgets and the legends depending on which coloring mode is +/// required. +//============================================================================= + +#ifndef RMV_VIEWS_COLORIZER_H_ +#define RMV_VIEWS_COLORIZER_H_ + +#include + +#include "qt_common/custom_widgets/colored_legend_graphics_view.h" +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" + +#include "colorizer_base.h" + +/// Handles control of the "color by" combo boxes and picking which colors to use. +class Colorizer : public ColorizerBase +{ + Q_OBJECT + +public: + /// Constructor. + explicit Colorizer(); + + /// Destructor. + virtual ~Colorizer(); + + /// Initialize the colorizer. + /// \param parent The parent pane or widget. + /// \param combo_box The 'color by' combo box to set up. + /// \param legends_view The graphics view containing the color legends. + /// \param mode_list The list of color modes required. + void Initialize(QWidget* parent, ArrowIconComboBox* combo_box, ColoredLegendGraphicsView* legends_view, const ColorMode* mode_list); + +public slots: + + /// Slot to handle what happens when the combo box is selected. + void ApplyColorMode(); +}; + +#endif // RMV_VIEWS_COLORIZER_H_ diff --git a/source/frontend/views/colorizer_base.cpp b/source/frontend/views/colorizer_base.cpp new file mode 100644 index 0000000..669add8 --- /dev/null +++ b/source/frontend/views/colorizer_base.cpp @@ -0,0 +1,450 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \brief Implementation for the colorizer base class. +/// Derived classes of this will implement the "color by" combo boxes +/// throughout the UI and the colorizing of the timeline +//============================================================================= + +#include "views/colorizer_base.h" + +#include + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" + +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" +#include "util/string_util.h" +#include "util/widget_util.h" + +// The number of age buckets +static const int kNumAllocationAgeBuckets = 10; + +ColorizerBase::ColorizerBase() + : combo_box_(nullptr) + , legends_scene_(nullptr) + , legends_view_(nullptr) + , color_mode_(kColorModeResourceUsageType) + , color_mode_map_{} +{ + for (int count = 0; count < kColorModeCount; count++) + { + color_mode_map_[count] = kColorModeCount; + } +} + +ColorizerBase::~ColorizerBase() +{ +} + +void ColorizerBase::Initialize(ArrowIconComboBox* combo_box, QGraphicsView* legends_view) +{ + combo_box_ = combo_box; + + // Make sure the legends view is fixed-size + rmv::widget_util::InitGraphicsView(legends_view, rmv::kColoredLegendsHeight); + + rmv::widget_util::InitColorLegend(legends_scene_, legends_view); + legends_view_ = legends_view; + UpdateLegends(); +} + +QColor ColorizerBase::GetAgeColor(int32_t age_index) const +{ + struct Color + { + double r; + double g; + double b; + }; + + // source and destination rgb color values + static const Color kSrc = {34, 68, 48}; + static const Color kDest = {240, 240, 240}; + + double range = GetNumAgeBuckets(); + double lerp = std::min(1.0, static_cast(age_index) / range); + lerp = std::max(0.0, lerp); + + double r = (kSrc.r * (1.0 - lerp)) + (kDest.r * lerp); + double g = (kSrc.g * (1.0 - lerp)) + (kDest.g * lerp); + double b = (kSrc.b * (1.0 - lerp)) + (kDest.b * lerp); + + return QColor::fromRgb(r, g, b); +} + +QColor ColorizerBase::GetHeapColor(RmtHeapType heap_type) +{ + switch (heap_type) + { + case kRmtHeapTypeLocal: + return RMVSettings::Get().GetColorHeapLocal(); + + case kRmtHeapTypeInvisible: + return RMVSettings::Get().GetColorHeapInvisible(); + + case kRmtHeapTypeSystem: + return RMVSettings::Get().GetColorHeapSystem(); + + case kRmtHeapTypeNone: + return RMVSettings::Get().GetColorHeapUnspecified(); + + default: + return RMVSettings::Get().GetColorResourceFreeSpace(); + } +} + +QColor ColorizerBase::GetResourceUsageColor(RmtResourceUsageType usage_type) +{ + QColor out = Qt::black; + + switch (usage_type) + { + case kRmtResourceUsageTypeDepthStencil: + out = RMVSettings::Get().GetColorResourceDepthStencil(); + break; + case kRmtResourceUsageTypeRenderTarget: + out = RMVSettings::Get().GetColorResourceRenderTarget(); + break; + case kRmtResourceUsageTypeTexture: + out = RMVSettings::Get().GetColorResourceTexture(); + break; + case kRmtResourceUsageTypeVertexBuffer: + out = RMVSettings::Get().GetColorResourceVertexBuffer(); + break; + case kRmtResourceUsageTypeIndexBuffer: + out = RMVSettings::Get().GetColorResourceIndexBuffer(); + break; + case kRmtResourceUsageTypeUav: + out = RMVSettings::Get().GetColorResourceUAV(); + break; + case kRmtResourceUsageTypeShaderPipeline: + out = RMVSettings::Get().GetColorResourceShaderPipeline(); + break; + case kRmtResourceUsageTypeCommandBuffer: + out = RMVSettings::Get().GetColorResourceCommandBuffer(); + break; + case kRmtResourceUsageTypeHeap: + out = RMVSettings::Get().GetColorResourceHeap(); + break; + case kRmtResourceUsageTypeDescriptors: + out = RMVSettings::Get().GetColorResourceDescriptors(); + break; + case kRmtResourceUsageTypeBuffer: + out = RMVSettings::Get().GetColorResourceBuffer(); + break; + case kRmtResourceUsageTypeGpuEvent: + out = RMVSettings::Get().GetColorResourceGPUEvent(); + break; + case kRmtResourceUsageTypeFree: + out = RMVSettings::Get().GetColorResourceFreeSpace(); + break; + case kRmtResourceUsageTypeInternal: + out = RMVSettings::Get().GetColorResourceInternal(); + break; + + default: + out = Qt::black; + break; + } + + return out; +} + +QColor ColorizerBase::GetColor(const uint32_t color_index) +{ + switch (color_mode_) + { + case kColorModePreferredHeap: + case kColorModeActualHeap: + return GetHeapColor((RmtHeapType)color_index); + + case kColorModeResourceUsageType: + return GetResourceUsageColor(RmtResourceUsageType(color_index)); + + default: + break; + } + + // default is free + return RMVSettings::Get().GetColorResourceFreeSpace(); +} + +QColor ColorizerBase::GetColor(const RmtVirtualAllocation* const allocation, const RmtResource* const resource) const +{ + switch (color_mode_) + { + case kColorModePreferredHeap: + if (allocation != nullptr) + { + return GetHeapColor(allocation->heap_preferences[0]); + } + break; + + case kColorModeActualHeap: + { + const TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid()) + { + const RmtDataSnapshot* snapshot = trace_manager.GetOpenSnapshot(); + RmtHeapType heap_type = RmtResourceGetActualHeap(snapshot, resource); + return GetHeapColor(heap_type); + } + break; + } + + case kColorModeResourceUsageType: + if (allocation != nullptr) + { + if (allocation->resource_count == 0) + { + return RMVSettings::Get().GetColorResourceFreeSpace(); + } + if (resource != nullptr) + { + if (resource->identifier != 0) + { + return GetResourceUsageColor(RmtResourceGetUsageType(resource)); + } + } + return RMVSettings::Get().GetColorResourceFreeSpace(); + } + break; + + case kColorModeAllocationAge: + if (allocation != nullptr) + { + int32_t age = GetAgeIndex(allocation->timestamp); + if (age != -1) + { + return GetAgeColor(age); + } + } + break; + + case kColorModeResourceCreateAge: + if (resource != nullptr) + { + int32_t age = GetAgeIndex(resource->create_time); + if (age != -1) + { + return GetAgeColor(age); + } + } + break; + + case kColorModeResourceBindAge: + if (resource != nullptr) + { + int32_t age = GetAgeIndex(resource->bind_time); + if (age != -1) + { + return GetAgeColor(age); + } + } + break; + + case kColorModeResourceGUID: + if (resource != nullptr) + { + srand(resource->identifier); + return QColor::fromRgb(rand() % 255, rand() % 255, rand() % 255); + } + break; + + case kColorModeResourceCPUMapped: + if (resource != nullptr && resource->bound_allocation != nullptr) + { + if ((resource->bound_allocation->flags & kRmtAllocationDetailIsCpuMapped) == kRmtAllocationDetailIsCpuMapped) + return RMVSettings::Get().GetColorCPUMapped(); + else + return RMVSettings::Get().GetColorNotCPUMapped(); + } + break; + + case kColorModeNotAllPreferred: + { + const RmtDataSnapshot* open_snapshot = TraceManager::Get().GetOpenSnapshot(); + + uint64_t memory_segment_histogram[kRmtResourceBackingStorageCount] = {0}; + RmtResourceGetBackingStorageHistogram(open_snapshot, resource, memory_segment_histogram); + + if (!resource || !resource->bound_allocation || resource->resource_type == kRmtResourceTypeCount) + return RMVSettings::Get().GetColorResourceFreeSpace(); + + // Check that the preferred heap contains all the bytes. + const RmtHeapType preferred_heap = resource->bound_allocation->heap_preferences[0]; + if (memory_segment_histogram[preferred_heap] != resource->size_in_bytes) + return RMVSettings::Get().GetColorNotInPreferredHeap(); + else + return RMVSettings::Get().GetColorInPreferredHeap(); + } + break; + + case kColorModeAliasing: + { + const RmtDataSnapshot* open_snapshot = TraceManager::Get().GetOpenSnapshot(); + + uint64_t memory_segment_histogram[kRmtResourceBackingStorageCount] = {0}; + RmtResourceGetBackingStorageHistogram(open_snapshot, resource, memory_segment_histogram); + + if (!resource || !resource->bound_allocation || resource->resource_type == kRmtResourceTypeCount) + return RMVSettings::Get().GetColorResourceFreeSpace(); + + // Check if the resource is aliased + if (RmtResourceGetAliasCount(resource) > 0) + { + return RMVSettings::Get().GetColorAliased(); + } + else + { + return RMVSettings::Get().GetColorNotAliased(); + } + } + break; + + case kColorModeCommitType: + if (resource != nullptr && resource->bound_allocation != nullptr) + { + switch (resource->commit_type) + { + case kRmtCommitTypeCommitted: + return RMVSettings::Get().GetColorCommitTypeCommitted(); + case kRmtCommitTypePlaced: + return RMVSettings::Get().GetColorCommitTypePlaced(); + case kRmtCommitTypeVirtual: + return RMVSettings::Get().GetColorCommitTypeVirtual(); + default: + break; + } + } + break; + + default: + break; + } + + // default is free + return RMVSettings::Get().GetColorResourceFreeSpace(); +} + +void ColorizerBase::UpdateLegends() +{ + UpdateLegends(legends_scene_, color_mode_); +} + +void ColorizerBase::UpdateLegends(ColoredLegendScene* legends_scene, ColorMode color_mode) +{ + RMT_ASSERT(legends_scene != nullptr); + + legends_scene->Clear(); + + switch (color_mode) + { + case kColorModePreferredHeap: + case kColorModeActualHeap: + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorHeapLocal(), RmtGetHeapTypeNameFromHeapType(kRmtHeapTypeLocal)); + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorHeapInvisible(), RmtGetHeapTypeNameFromHeapType(kRmtHeapTypeInvisible)); + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorHeapSystem(), RmtGetHeapTypeNameFromHeapType(kRmtHeapTypeSystem)); + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorHeapUnspecified(), RmtGetHeapTypeNameFromHeapType(kRmtHeapTypeNone)); + break; + + case kColorModeResourceUsageType: + { + for (int32_t index = 0; index < kRmtResourceUsageTypeCount; ++index) + { + if (index != kRmtResourceUsageTypeUnknown) + { + const RmtResourceUsageType resource_usage_type = (RmtResourceUsageType)index; + legends_scene->AddColorLegendItem(GetResourceUsageColor(resource_usage_type), rmv::string_util::GetResourceUsageString(resource_usage_type)); + } + } + } + break; + + case kColorModeAllocationAge: + case kColorModeResourceCreateAge: + case kColorModeResourceBindAge: + { + int num_resource_age_buckets = GetNumAgeBuckets(); + for (int32_t current_allocation_age_range_bucket_index = 0; current_allocation_age_range_bucket_index < num_resource_age_buckets; + ++current_allocation_age_range_bucket_index) + { + QString text; + if (current_allocation_age_range_bucket_index == 0) + { + text = QString("Oldest"); + } + else if (current_allocation_age_range_bucket_index == num_resource_age_buckets - 1) + { + text = QString("Youngest"); + } + legends_scene->AddColorLegendItem(GetAgeColor(current_allocation_age_range_bucket_index), text); + } + } + break; + + case kColorModeResourceGUID: + legends_scene->AddTextLegendItem("Each color represents a different resource."); + break; + + case kColorModeResourceCPUMapped: + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorCPUMapped(), "CPU mapped"); + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorNotCPUMapped(), "Not CPU mapped"); + break; + + case kColorModeNotAllPreferred: + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorInPreferredHeap(), "All in preferred heap"); + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorNotInPreferredHeap(), "Not all in preferred heap"); + break; + + case kColorModeAliasing: + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorAliased(), "Aliased"); + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorNotAliased(), "Not aliased"); + break; + + case kColorModeCommitType: + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorCommitTypeCommitted(), "Committed"); + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorCommitTypePlaced(), "Placed"); + legends_scene->AddColorLegendItem(RMVSettings::Get().GetColorCommitTypeVirtual(), "Virtual"); + break; + + default: + break; + } + + // set the view sizes to match the scene sizes so the legends appear left-justified + if (legends_view_ != nullptr && legends_scene != nullptr) + { + legends_view_->setFixedSize(legends_scene->itemsBoundingRect().size().toSize()); + } +} + +int32_t ColorizerBase::GetNumAgeBuckets() +{ + return kNumAllocationAgeBuckets; +} + +int32_t ColorizerBase::GetAgeIndex(uint64_t timestamp) +{ + const RmtDataSnapshot* snapshot = TraceManager::Get().GetOpenSnapshot(); + if (snapshot == nullptr) + { + return -1; + } + + uint64_t age_range = snapshot->maximum_allocation_timestamp - snapshot->minimum_allocation_timestamp; + const uint64_t bucket_width = age_range / GetNumAgeBuckets(); + if (bucket_width == 0) + { + return -1; + } + + const uint64_t allocation_age = timestamp - snapshot->minimum_allocation_timestamp; + int32_t result = allocation_age / bucket_width; + result = std::min(result, (GetNumAgeBuckets() - 1)); + result = std::max(result, 0); + return result; +} diff --git a/source/frontend/views/colorizer_base.h b/source/frontend/views/colorizer_base.h new file mode 100644 index 0000000..64ab967 --- /dev/null +++ b/source/frontend/views/colorizer_base.h @@ -0,0 +1,111 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \brief Header file for the colorizer base class. +/// Derived classes of this will implement the "color by" combo boxes +/// throughout the UI and the colorizing of the timeline +//============================================================================= + +#ifndef RMV_VIEWS_COLORIZER_BASE_H_ +#define RMV_VIEWS_COLORIZER_BASE_H_ + +#include +#include + +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" +#include "qt_common/custom_widgets/colored_legend_scene.h" + +#include "rmt_format.h" +#include "rmt_resource_list.h" +#include "rmt_virtual_allocation_list.h" + +/// The colorizer base class. Handles basic colorizing across different +/// selection modes. +class ColorizerBase : public QObject +{ +public: + /// Constructor. + explicit ColorizerBase(); + + /// Destructor. + virtual ~ColorizerBase(); + + /// enum of the different 'color by' modes available. + enum ColorMode + { + kColorModeResourceUsageType, + kColorModePreferredHeap, + kColorModeActualHeap, + kColorModeAllocationAge, + kColorModeResourceCreateAge, + kColorModeResourceBindAge, + kColorModeResourceGUID, + kColorModeResourceCPUMapped, + kColorModeNotAllPreferred, + kColorModeAliasing, + kColorModeCommitType, + + kColorModeCount, + }; + + /// Initialize the colorizer. + /// \param combo_box The combo box containing the different coloring modes. + /// \param legends_view The graphics view containing the color legends. + void Initialize(ArrowIconComboBox* combo_box, QGraphicsView* legends_view); + + /// Function to call when picking the color based on color mode. + /// NOTE: The input parameters can be null since not all cases may be + /// required or valid. In this case, the color indicating 'unallocated' + /// will be returned. + /// \param allocation The allocation to use when deciding the coloring. + /// \param resource The resource to use when deciding the color. + /// \return The color to use. + QColor GetColor(const RmtVirtualAllocation* allocation, const RmtResource* resource) const; + + /// Function to call when picking the color based on color mode. + /// \param The index of the color for the current color mode. + /// \return The color to use. + QColor GetColor(const uint32_t color_index); + + /// Update color legends to the UI depending on the coloring mode. + void UpdateLegends(); + + /// Get the resource usage color. + /// NOTE: currently static so it can be used without a colorizer object + /// \param usage_type The resource usage type. + /// \return The color to use. + static QColor GetResourceUsageColor(RmtResourceUsageType usage_type); + + /// Get the color corresponding to the heap of a resource. + /// \return the color required. + static QColor GetHeapColor(RmtHeapType heap_type); + + /// Get the number of age buckets. For now this value is shared between + /// resources and allocations. + /// \return The number of buckets. + static int32_t GetNumAgeBuckets(); + + /// Get the age index for the age of a given allocation. The larger the value, the older it is. + /// The allocation. Range is from 0 to GetNumResourceAgeBuckets() - 1 + /// \param timestamp The timestamp of the objects whose age is to be calculated. + /// \return The age index, or -1 if error. + static int32_t GetAgeIndex(const uint64_t timestamp); + +protected: + /// Update color legends to the UI depending on the coloring mode. + void UpdateLegends(ColoredLegendScene* legends_scene, ColorMode color_mode); + + /// Get the color corresponding to the age of a resource. + /// \param age_index The age index of the resource. A value of 0 + /// is the oldest. + /// \return the color required. + QColor GetAgeColor(int32_t age_index) const; + + ArrowIconComboBox* combo_box_; ///< The combo box holding the color modes available. + ColoredLegendScene* legends_scene_; ///< The legends scene showing what the colors represent. + QGraphicsView* legends_view_; ///< The legends view associated with the scene. + ColorMode color_mode_; ///< THe current coloring mode. + ColorMode color_mode_map_[kColorModeCount]; ///< The mapping of combo box index to color mode. +}; + +#endif // RMV_VIEWS_COLORIZER_BASE_H_ diff --git a/source/frontend/views/compare/compare_start_pane.cpp b/source/frontend/views/compare/compare_start_pane.cpp new file mode 100644 index 0000000..7772f70 --- /dev/null +++ b/source/frontend/views/compare/compare_start_pane.cpp @@ -0,0 +1,109 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Compare start pane. +//============================================================================= + +#include "views/compare/compare_start_pane.h" + +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_data_snapshot.h" + +#include "models/message_manager.h" +#include "settings/rmv_settings.h" +#include "util/widget_util.h" +#include "views/pane_manager.h" + +static const qreal kCircleSeparationFactor = 5.0; +static const qreal kSceneMargin = 10.0; + +CompareStartPane::CompareStartPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::CompareStartPane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + rmv::widget_util::InitGraphicsView(ui_->graphics_view_, ScalingManager::Get().Scaled(kCircleDiameter)); + ui_->graphics_view_->setFixedWidth(ScalingManager::Get().Scaled(kCircleDiameter * 2 - kCircleDiameter / kCircleSeparationFactor)); + + scene_ = new QGraphicsScene(); + ui_->graphics_view_->setScene(scene_); + + RMVCameraSnapshotWidgetConfig config = {}; + config.height = ui_->graphics_view_->height(); + config.width = ui_->graphics_view_->width(); + config.margin = kSceneMargin; + + config.base_color = RMVSettings::Get().GetColorSnapshotViewed(); + snapshot_widget_left_ = new RMVCameraSnapshotWidget(config); + + config.base_color = RMVSettings::Get().GetColorSnapshotCompared(); + snapshot_widget_right_ = new RMVCameraSnapshotWidget(config); + + scene_->addItem(snapshot_widget_left_); + scene_->addItem(snapshot_widget_right_); + + UpdateCirclePositions(); + + // respond to the Navigate() signal from the snapshot widgets. Uses a lambda to emit a navigate-to-pane message + connect( + snapshot_widget_left_, &RMVCameraSnapshotWidget::Navigate, [=]() { emit MessageManager::Get().NavigateToPane(rmv::kPaneTimelineGenerateSnapshot); }); + connect( + snapshot_widget_right_, &RMVCameraSnapshotWidget::Navigate, [=]() { emit MessageManager::Get().NavigateToPane(rmv::kPaneTimelineGenerateSnapshot); }); + + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &CompareStartPane::OnScaleFactorChanged); +} + +CompareStartPane::~CompareStartPane() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &CompareStartPane::OnScaleFactorChanged); +} + +void CompareStartPane::resizeEvent(QResizeEvent* event) +{ + UpdateCirclePositions(); + QWidget::resizeEvent(event); +} + +void CompareStartPane::OnScaleFactorChanged() +{ + UpdateCirclePositions(); +} + +void CompareStartPane::UpdateCirclePositions() +{ + const qreal circle_diameter = kCircleDiameter - kSceneMargin * 2; + + snapshot_widget_left_->UpdateDimensions(circle_diameter, circle_diameter); + snapshot_widget_right_->UpdateDimensions(circle_diameter, circle_diameter); + qreal overlap_start = kSceneMargin + ScalingManager::Get().Scaled(circle_diameter); + snapshot_widget_right_->setPos(overlap_start - (overlap_start / kCircleSeparationFactor), 0); + + QRectF scene_rect = scene_->itemsBoundingRect(); + ui_->graphics_view_->setSceneRect(scene_rect); + ui_->graphics_view_->setFixedSize(scene_rect.toRect().size()); +} + +void CompareStartPane::Reset() +{ + snapshot_widget_left_->UpdateName("Current snapshot"); + snapshot_widget_left_->update(); + + snapshot_widget_right_->UpdateName("Load comparison trace"); + snapshot_widget_right_->update(); +} + +void CompareStartPane::ChangeColoring() +{ + snapshot_widget_left_->UpdateBaseColor(RMVSettings::Get().GetColorSnapshotViewed()); + snapshot_widget_right_->UpdateBaseColor(RMVSettings::Get().GetColorSnapshotCompared()); +} + +void CompareStartPane::OpenSnapshot(RmtDataSnapshot* snapshot) +{ + snapshot_widget_left_->UpdateName(QString(snapshot->name)); +} \ No newline at end of file diff --git a/source/frontend/views/compare/compare_start_pane.h b/source/frontend/views/compare/compare_start_pane.h new file mode 100644 index 0000000..626b1cf --- /dev/null +++ b/source/frontend/views/compare/compare_start_pane.h @@ -0,0 +1,61 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Compare start pane. +//============================================================================= + +#ifndef RMV_VIEWS_COMPARE_COMPARE_START_PANE_H_ +#define RMV_VIEWS_COMPARE_COMPARE_START_PANE_H_ + +#include "ui_compare_start_pane.h" + +#include + +#include "views/base_pane.h" +#include "views/custom_widgets/rmv_camera_snapshot_widget.h" + +/// Class declaration. +class CompareStartPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit CompareStartPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~CompareStartPane(); + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + + /// Open a snapshot. + /// \param snapshot snapshot to open. + virtual void OpenSnapshot(RmtDataSnapshot* snapshot) Q_DECL_OVERRIDE; + +private slots: + /// Callback for when the DPI Scale changes. + void OnScaleFactorChanged(); + +private: + /// Repositions the right circle and then. + /// resizes the GraphicsView to fit the whole scene. + void UpdateCirclePositions(); + + Ui::CompareStartPane* ui_; ///< Pointer to the Qt UI design. + + QGraphicsScene* scene_; ///< Qt scene for the camera drawing. + RMVCameraSnapshotWidget* snapshot_widget_left_; ///< Left circle with camera. + RMVCameraSnapshotWidget* snapshot_widget_right_; ///< Right circle with camera. +}; + +#endif // RMV_VIEWS_COMPARE_COMPARE_START_PANE_H_ diff --git a/source/frontend/views/compare/compare_start_pane.ui b/source/frontend/views/compare/compare_start_pane.ui new file mode 100644 index 0000000..f6f3ea6 --- /dev/null +++ b/source/frontend/views/compare/compare_start_pane.ui @@ -0,0 +1,159 @@ + + + CompareStartPane + + + + 0 + 0 + 1392 + 1116 + + + + + + + true + + + + + 0 + 0 + 1368 + 1092 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + 20 + + + + + + 0 + 0 + + + + You haven't made a comparison yet! + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 700 + 185 + + + + <html><head/><body><p><span style=" color:#5a5a5a;">To compare two snapshots:<br/>1. Go to the TIMELINE tab.<br/>2. Select two snapshots from the snapshot list.<br/>3. Click on the &quot;Compare snapshots&quot; button.</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + + + + + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+
+ + + + +
diff --git a/source/frontend/views/compare/memory_leak_finder_pane.cpp b/source/frontend/views/compare/memory_leak_finder_pane.cpp new file mode 100644 index 0000000..18965db --- /dev/null +++ b/source/frontend/views/compare/memory_leak_finder_pane.cpp @@ -0,0 +1,267 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the Memory leak finder pane. +//============================================================================= + +#include "views/compare/memory_leak_finder_pane.h" + +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_util.h" + +#include "models/compare/memory_leak_finder_model.h" +#include "models/message_manager.h" +#include "models/proxy_models/memory_leak_finder_proxy_model.h" +#include "models/resource_item_model.h" +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" +#include "views/custom_widgets/rmv_colored_checkbox.h" +#include "util/rmv_util.h" + +MemoryLeakFinderPane::MemoryLeakFinderPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::MemoryLeakFinderPane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + model_ = new rmv::MemoryLeakFinderModel(); + + model_->InitializeModel(ui_->base_allocations_label_, rmv::kMemoryLeakFinderBaseStats, "text"); + model_->InitializeModel(ui_->both_allocations_label_, rmv::kMemoryLeakFinderBothStats, "text"); + model_->InitializeModel(ui_->diff_allocations_label_, rmv::kMemoryLeakFinderDiffStats, "text"); + model_->InitializeModel(ui_->total_resources_label_, rmv::kMemoryLeakFinderTotalResources, "text"); + model_->InitializeModel(ui_->total_size_label_, rmv::kMemoryLeakFinderTotalSize, "text"); + model_->InitializeModel(ui_->base_allocations_checkbox_, rmv::kMemoryLeakFinderBaseCheckbox, "text"); + model_->InitializeModel(ui_->diff_allocations_checkbox_, rmv::kMemoryLeakFinderDiffCheckbox, "text"); + model_->InitializeModel(ui_->base_snapshot_label_, rmv::kMemoryLeakFinderBaseSnapshot, "text"); + model_->InitializeModel(ui_->diff_snapshot_label_, rmv::kMemoryLeakFinderDiffSnapshot, "text"); + + model_->InitializeTableModel(ui_->resource_table_view_, 0, kResourceColumnCount, kSnapshotCompareIdCommon); + + rmv::widget_util::InitMultiSelectComboBox(this, ui_->preferred_heap_combo_box_, rmv::text::kPreferredHeap); + rmv::widget_util::InitMultiSelectComboBox(this, ui_->resource_usage_combo_box_, rmv::text::kResourceUsage); + + preferred_heap_combo_box_model_ = new rmv::HeapComboBoxModel(); + preferred_heap_combo_box_model_->SetupHeapComboBox(ui_->preferred_heap_combo_box_); + connect(preferred_heap_combo_box_model_, &rmv::HeapComboBoxModel::FilterChanged, this, &MemoryLeakFinderPane::HeapChanged); + + resource_usage_combo_box_model_ = new rmv::ResourceUsageComboBoxModel(); + resource_usage_combo_box_model_->SetupResourceComboBox(ui_->resource_usage_combo_box_); + connect(resource_usage_combo_box_model_, &rmv::ResourceUsageComboBoxModel::FilterChanged, this, &MemoryLeakFinderPane::ResourceChanged); + + compare_id_delegate_ = new RMVCompareIdDelegate(); + ui_->resource_table_view_->setItemDelegateForColumn(kResourceColumnCompareId, compare_id_delegate_); + + // set the row height according to the compare ID column delegate. + ui_->resource_table_view_->verticalHeader()->setDefaultSectionSize(compare_id_delegate_->DefaultSizeHint().height()); + + rmv::widget_util::InitCommonFilteringComponents(ui_->search_box_, ui_->size_slider_); + + ui_->base_allocations_checkbox_->Initialize(false, RMVSettings::Get().GetColorSnapshotViewed(), Qt::black); + ui_->both_allocations_checkbox_->Initialize(true, RMVSettings::Get().GetColorSnapshotViewed(), RMVSettings::Get().GetColorSnapshotCompared(), true); + ui_->diff_allocations_checkbox_->Initialize(false, RMVSettings::Get().GetColorSnapshotCompared(), Qt::black); + + CompareFilterChanged(); + + connect(ui_->size_slider_, &DoubleSliderWidget::SpanChanged, this, &MemoryLeakFinderPane::FilterBySizeSliderChanged); + 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); + connect(ui_->base_allocations_checkbox_, &RMVColoredCheckbox::Clicked, this, &MemoryLeakFinderPane::CompareFilterChanged); + connect(ui_->diff_allocations_checkbox_, &RMVColoredCheckbox::Clicked, this, &MemoryLeakFinderPane::CompareFilterChanged); + connect(&MessageManager::Get(), &MessageManager::UpdateHashes, this, &MemoryLeakFinderPane::UpdateHashes); + + // set up a connection between the timeline being sorted and making sure the selected event is visible + connect(model_->GetResourceProxyModel(), &rmv::MemoryLeakFinderProxyModel::layoutChanged, this, &MemoryLeakFinderPane::ScrollToSelectedResource); + + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &MemoryLeakFinderPane::OnScaleFactorChanged); +} + +MemoryLeakFinderPane::~MemoryLeakFinderPane() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &MemoryLeakFinderPane::OnScaleFactorChanged); + + delete compare_id_delegate_; + delete resource_usage_combo_box_model_; + delete preferred_heap_combo_box_model_; + delete model_; +} + +void MemoryLeakFinderPane::showEvent(QShowEvent* event) +{ + HeapChanged(false); + ResourceChanged(false); + Update(false); + + QWidget::showEvent(event); +} + +void MemoryLeakFinderPane::OnScaleFactorChanged() +{ + // Get the height of the delegate to update the checkmark geometry and table row height; + const int checkmark_icon_height = compare_id_delegate_->DefaultSizeHint().height(); + + // Update checkmark geometry in the CompareIdDelegate. + compare_id_delegate_->CalculateCheckmarkGeometry(checkmark_icon_height); + + // Update the table row height according to the compare ID column delegate (which is dependent on DPI scale). + // This item is the tallest object in the table, so this height is a good one to use. + ui_->resource_table_view_->verticalHeader()->setDefaultSectionSize(checkmark_icon_height); +} + +void MemoryLeakFinderPane::Refresh() +{ + Update(true); +} + +SnapshotCompareId MemoryLeakFinderPane::GetCompareIdFilter() const +{ + uint32_t filter = 0; + + if (ui_->base_allocations_checkbox_->isChecked()) + { + filter |= kSnapshotCompareIdOpen; + } + + if (ui_->both_allocations_checkbox_->isChecked()) + { + filter |= kSnapshotCompareIdCommon; + } + + if (ui_->diff_allocations_checkbox_->isChecked()) + { + filter |= kSnapshotCompareIdCompared; + } + + return (SnapshotCompareId)filter; +} + +void MemoryLeakFinderPane::Update(bool reset_filters) +{ + if (reset_filters == true) + { + ui_->base_allocations_checkbox_->setChecked(false); + ui_->both_allocations_checkbox_->setChecked(true); + ui_->diff_allocations_checkbox_->setChecked(false); + } + + // Prior to doing a table update, disable sorting since Qt is super slow about it + ui_->resource_table_view_->setSortingEnabled(false); + + const SnapshotCompareId compare_filter = GetCompareIdFilter(); + model_->Update(compare_filter); + + ui_->resource_table_view_->setSortingEnabled(true); + + ui_->resource_table_view_->sortByColumn(kResourceColumnName, Qt::DescendingOrder); + + SetMaximumResourceTableHeight(); +} + +void MemoryLeakFinderPane::OnTraceClose() +{ + preferred_heap_combo_box_model_->ResetHeapComboBox(ui_->preferred_heap_combo_box_); + resource_usage_combo_box_model_->ResetResourceComboBox(ui_->resource_usage_combo_box_); +} + +void MemoryLeakFinderPane::Reset() +{ + model_->ResetModelValues(); + + ui_->size_slider_->SetLowerValue(0); + ui_->size_slider_->SetUpperValue(rmv::kSizeSliderRange); + ui_->search_box_->setText(""); +} + +void MemoryLeakFinderPane::ChangeColoring() +{ + ui_->base_allocations_checkbox_->UpdatePrimaryColor(rmv_util::GetSnapshotStateColor(kSnapshotStateViewed)); + ui_->diff_allocations_checkbox_->UpdatePrimaryColor(rmv_util::GetSnapshotStateColor(kSnapshotStateCompared)); +} + +void MemoryLeakFinderPane::SearchBoxChanged() +{ + model_->SearchBoxChanged(ui_->search_box_->text()); + SetMaximumResourceTableHeight(); +} + +void MemoryLeakFinderPane::FilterBySizeSliderChanged(int min_value, int max_value) +{ + model_->FilterBySizeChanged(min_value, max_value); + SetMaximumResourceTableHeight(); +} + +void MemoryLeakFinderPane::CompareFilterChanged() +{ + SnapshotCompareId filter = GetCompareIdFilter(); + model_->Update(filter); + SetMaximumResourceTableHeight(); +} + +void MemoryLeakFinderPane::HeapChanged(bool checked) +{ + // rebuild the table depending on what the state of the combo box items is + RMT_UNUSED(checked); + + QString filter_string = preferred_heap_combo_box_model_->GetFilterString(ui_->preferred_heap_combo_box_); + model_->UpdatePreferredHeapList(filter_string); + SetMaximumResourceTableHeight(); +} + +void MemoryLeakFinderPane::ResourceChanged(bool checked) +{ + // rebuild the table depending on what the state of the combo box items is + RMT_UNUSED(checked); + + QString filter_string = resource_usage_combo_box_model_->GetFilterString(ui_->resource_usage_combo_box_); + model_->UpdateResourceUsageList(filter_string); + SetMaximumResourceTableHeight(); +} + +void MemoryLeakFinderPane::UpdateHashes() +{ + if (TraceManager::Get().GetComparedSnapshot(kSnapshotCompareDiff) != nullptr) + { + Update(false); + } +} + +void MemoryLeakFinderPane::TableDoubleClicked(const QModelIndex& index) +{ + if (index.isValid() == true) + { + const RmtResourceIdentifier resource_identifier = model_->GetResourceProxyModel()->GetData(index.row(), kResourceColumnGlobalId); + RmtSnapshotPoint* snapshot_point = model_->LoadSnapshot(index); + if (snapshot_point) + { + emit MessageManager::Get().OpenSnapshot(snapshot_point); + } + + emit MessageManager::Get().ResourceSelected(resource_identifier); + + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotResourceDetails); + } +} + +void MemoryLeakFinderPane::ScrollToSelectedResource() +{ + QItemSelectionModel* selected_item = ui_->resource_table_view_->selectionModel(); + if (selected_item->hasSelection()) + { + QModelIndexList item_list = selected_item->selectedRows(); + if (item_list.size() > 0) + { + QModelIndex model_index = item_list[0]; + ui_->resource_table_view_->scrollTo(model_index, QAbstractItemView::ScrollHint::PositionAtTop); + } + } +} diff --git a/source/frontend/views/compare/memory_leak_finder_pane.h b/source/frontend/views/compare/memory_leak_finder_pane.h new file mode 100644 index 0000000..65b4dfc --- /dev/null +++ b/source/frontend/views/compare/memory_leak_finder_pane.h @@ -0,0 +1,107 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Memory leak finder pane. +//============================================================================= + +#ifndef RMV_VIEWS_COMPARE_MEMORY_LEAK_FINDER_PANE_H_ +#define RMV_VIEWS_COMPARE_MEMORY_LEAK_FINDER_PANE_H_ + +#include "ui_memory_leak_finder_pane.h" + +#include "models/compare/memory_leak_finder_model.h" +#include "models/heap_combo_box_model.h" +#include "models/resource_usage_combo_box_model.h" +#include "util/widget_util.h" +#include "views/base_pane.h" +#include "views/delegates/rmv_compare_id_delegate.h" + +/// Class declaration. +class MemoryLeakFinderPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit MemoryLeakFinderPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~MemoryLeakFinderPane(); + + /// Overridden show event. Fired when this pane is opened. + /// \param event the show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Clean up. + virtual void OnTraceClose() Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + + /// Refresh what's visible on the UI. + void Refresh(); + +private slots: + /// Handle what happens when user changes the filter. + void SearchBoxChanged(); + + /// Update UI elements as needed due to DPI scale factor changes. + void OnScaleFactorChanged(); + + /// Slot to handle what happens when the 'filter by size' slider changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void FilterBySizeSliderChanged(int min_value, int max_value); + + /// Checkboxes on the top were clicked. + void CompareFilterChanged(); + + /// Refresh content if hashes changed. + void UpdateHashes(); + + /// Slot to handle what happens after the resource list table is sorted. + /// Make sure the selected item (if there is one) is visible. + void ScrollToSelectedResource(); + + /// Handle what happens when a checkbox in the heap dropdown is checked or unchecked. + /// \param checked Whether the checkbox is checked or unchecked. + void HeapChanged(bool checked); + + /// Handle what happens when a checkbox in the resource dropdown is checked or unchecked. + /// \param checked Whether the checkbox is checked or unchecked. + void ResourceChanged(bool checked); + + /// Slot to handle what happens when a resource in the table is double-clicked on. + /// Select the resource and go to resource details. + /// \param index the model index of the selected item in the table. + void TableDoubleClicked(const QModelIndex& index); + +private: + /// Refresh what's visible on the UI. + /// \param reset_filters go back to init state. + void Update(bool reset_filters); + + /// Figure out a filter given current UI state. + /// \return filter flags. + SnapshotCompareId GetCompareIdFilter() const; + + /// Helper function to set the maximum height of the table so it only contains rows with valid data. + inline void SetMaximumResourceTableHeight() + { + ui_->resource_table_view_->setMaximumHeight(rmv::widget_util::GetTableHeight(ui_->resource_table_view_, model_->GetResourceProxyModel()->rowCount())); + } + + Ui::MemoryLeakFinderPane* ui_; ///< Pointer to the Qt UI design. + + rmv::MemoryLeakFinderModel* model_; ///< Container class for the widget models. + rmv::HeapComboBoxModel* preferred_heap_combo_box_model_; ///< The heap combo box model. + rmv::ResourceUsageComboBoxModel* resource_usage_combo_box_model_; ///< The resource usage model. + RMVCompareIdDelegate* compare_id_delegate_; ///< Custom delegate for compare ID column. +}; + +#endif // RMV_VIEWS_COMPARE_MEMORY_LEAK_FINDER_PANE_H_ diff --git a/source/frontend/views/compare/memory_leak_finder_pane.ui b/source/frontend/views/compare/memory_leak_finder_pane.ui new file mode 100644 index 0000000..8fd38ab --- /dev/null +++ b/source/frontend/views/compare/memory_leak_finder_pane.ui @@ -0,0 +1,531 @@ + + + MemoryLeakFinderPane + + + + 0 + 0 + 1375 + 1109 + + + + + + + true + + + + + 0 + 0 + 1355 + 1089 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Snapshot X + + + + + + + + 0 + 0 + + + + vs. + + + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Snapshot Y + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 0 + + + + Identify which resources are present or absent across snapshots. + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + test 2 + + + + + + + + 0 + 0 + + + + test 3 + + + + + + + + 0 + 0 + + + + Resources common to both snapshots + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + test 1 + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 60 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 60 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + PushButton + + + + + + + PushButton + + + + + + + + 0 + 0 + + + + Filter by size: + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + 260 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 40 + 5 + + + + QSizePolicy::MinimumExpanding + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Total resources: + + + + + + + + 0 + 0 + + + + {0} + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + Total size: + + + + + + + + 0 + 0 + + + + {0} + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ArrowIconComboBox + QPushButton +
qt_common/custom_widgets/arrow_icon_combo_box.h
+
+ + TextSearchWidget + QLineEdit +
qt_common/custom_widgets/text_search_widget.h
+
+ + DoubleSliderWidget + QSlider +
qt_common/custom_widgets/double_slider_widget.h
+
+ + RMVColoredCheckbox + QCheckBox +
views/custom_widgets/rmv_colored_checkbox.h
+
+ + RMVClickableTableView + ScaledTableView +
views/custom_widgets/rmv_clickable_table_view.h
+
+
+ + + + +
diff --git a/source/frontend/views/compare/snapshot_delta_pane.cpp b/source/frontend/views/compare/snapshot_delta_pane.cpp new file mode 100644 index 0000000..9004e55 --- /dev/null +++ b/source/frontend/views/compare/snapshot_delta_pane.cpp @@ -0,0 +1,202 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Snapshot delta pane. +//============================================================================= + +#include "views/compare/snapshot_delta_pane.h" + +#include "qt_common/utils/scaling_manager.h" + +#include "util/rmv_util.h" +#include "util/widget_util.h" + +typedef struct RmtDataSnapshot RmtDataSnapshot; + +SnapshotDeltaPane::SnapshotDeltaPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::SnapshotDeltaPane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + model_ = new rmv::SnapshotDeltaModel(); + + model_->InitializeModel(ui_->base_snapshot_label_, rmv::kHeapDeltaCompareBaseName, "text"); + model_->InitializeModel(ui_->diff_snapshot_label_, rmv::kHeapDeltaCompareDiffName, "text"); + + delta_items_.push_back({"Available size", kDeltaValueTypeValueLabeled, false, 0, "", QColor()}); + delta_items_.push_back({"Allocated and bound", kDeltaValueTypeValueLabeled, true, 0, "", QColor()}); + delta_items_.push_back({"Allocated and unbound", kDeltaValueTypeValueLabeled, true, 0, "", QColor()}); + delta_items_.push_back({"Allocations", kDeltaValueTypeValue, true, 0, "", QColor()}); + delta_items_.push_back({"Resources", kDeltaValueTypeValue, true, 0, "", QColor()}); + + delta_line_pairs_[0].display = ui_->delta_view_heap_0_; + delta_line_pairs_[0].line = nullptr; + + delta_line_pairs_[1].display = ui_->delta_view_heap_1_; + delta_line_pairs_[1].line = ui_->delta_view_line_1_; + + delta_line_pairs_[2].display = ui_->delta_view_heap_2_; + delta_line_pairs_[2].line = ui_->delta_view_line_2_; + + for (int32_t current_heap_index = 0; current_heap_index <= kRmtHeapTypeSystem; current_heap_index++) + { + delta_line_pairs_[current_heap_index].display->Init(model_->GetHeapName(current_heap_index), delta_items_); + } + + rmv::widget_util::InitGraphicsView(ui_->carousel_view_, ScalingManager::Get().Scaled(kCarouselItemHeight)); + + RMVCarouselConfig config = {}; + config.height = ui_->carousel_view_->height(); + config.data_type = kCarouselDataTypeDelta; + + carousel_ = new RMVCarousel(config); + ui_->carousel_view_->setScene(carousel_->Scene()); + + ui_->legends_view_->setFrameStyle(QFrame::NoFrame); + ui_->legends_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_->legends_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + rmv::widget_util::InitColorLegend(legends_, ui_->legends_view_); + AddMemoryDeltaLegends(); + + QRectF legend_rect = legends_->itemsBoundingRect(); + ui_->legends_view_->setFixedSize(legend_rect.toRect().size()); + ui_->legends_view_->setSceneRect(legend_rect); + + connect(ui_->switch_button_, &QPushButton::pressed, this, &SnapshotDeltaPane::SwitchSnapshots); + + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &SnapshotDeltaPane::OnScaleFactorChanged); +} + +SnapshotDeltaPane::~SnapshotDeltaPane() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &SnapshotDeltaPane::OnScaleFactorChanged); + delete carousel_; + delete model_; +} + +void SnapshotDeltaPane::showEvent(QShowEvent* event) +{ + Refresh(); + + QWidget::showEvent(event); +} + +void SnapshotDeltaPane::OnScaleFactorChanged() +{ + // Carousel + ui_->carousel_view_->setFixedHeight(ScalingManager::Get().Scaled(kCarouselItemHeight)); + + // Legend + QRectF legend_rect = legends_->itemsBoundingRect(); + ui_->legends_view_->setFixedSize(legend_rect.toRect().size()); + ui_->legends_view_->setSceneRect(legend_rect); + + // Delta Displays + ui_->delta_view_heap_0_->setFixedHeight(ScalingManager::Get().Scaled(kHeapDeltaWidgetHeight)); + ui_->delta_view_heap_1_->setFixedHeight(ScalingManager::Get().Scaled(kHeapDeltaWidgetHeight)); + ui_->delta_view_heap_2_->setFixedHeight(ScalingManager::Get().Scaled(kHeapDeltaWidgetHeight)); +} + +void SnapshotDeltaPane::SwitchSnapshots() +{ + if (model_->SwapSnapshots() == true) + { + UpdateUI(); + } +} + +void SnapshotDeltaPane::UpdateUI() +{ + // update delta information + for (int32_t current_heap_index = 0; current_heap_index <= kRmtHeapTypeSystem; current_heap_index++) + { + delta_line_pairs_[current_heap_index].display->Init(model_->GetHeapName(current_heap_index), delta_items_); + } + + model_->UpdateCarousel(carousel_); + + // Update heap data + for (int32_t current_heap_index = 0; current_heap_index <= kRmtHeapTypeSystem; current_heap_index++) + { + rmv::SnapshotDeltaModel::HeapDeltaData heap_delta_data; + + if (model_->CalcPerHeapDelta((RmtHeapType)current_heap_index, heap_delta_data) == true) + { + delta_items_[kSnapshotDeltaTypeAvailableSize].value_num = heap_delta_data.total_available_size; + delta_items_[kSnapshotDeltaTypeAllocatedAndBound].value_num = heap_delta_data.total_allocated_and_bound; + delta_items_[kSnapshotDeltaTypeAllocatedAndUnbound].value_num = heap_delta_data.total_allocated_and_unbound; + delta_items_[kSnapshotDeltaTypeAllocationCount].value_num = heap_delta_data.allocation_count; + delta_items_[kSnapshotDeltaTypeResourceCount].value_num = heap_delta_data.resource_count; + + for (int j = 0; j < kSnapshotDeltaTypeCount; j++) + { + delta_line_pairs_[current_heap_index].display->UpdateItem(delta_items_[j]); + } + } + } + + ResizeItems(); +} + +void SnapshotDeltaPane::Refresh() +{ + if (model_->Update() == true) + { + UpdateUI(); + + for (int32_t current_heap_index = 0; current_heap_index <= kRmtHeapTypeSystem; current_heap_index++) + { + if (delta_line_pairs_[current_heap_index].display != nullptr) + { + delta_line_pairs_[current_heap_index].display->show(); + } + + if (delta_line_pairs_[current_heap_index].line != nullptr) + { + delta_line_pairs_[current_heap_index].line->show(); + } + } + } +} + +void SnapshotDeltaPane::PaneSwitched() +{ + ResizeItems(); +} + +void SnapshotDeltaPane::Reset() +{ + model_->ResetModelValues(); +} + +void SnapshotDeltaPane::ChangeColoring() +{ + legends_->Clear(); + AddMemoryDeltaLegends(); +} + +void SnapshotDeltaPane::ResizeItems() +{ + if (carousel_ != nullptr) + { + carousel_->ResizeEvent(ui_->carousel_view_->width(), ui_->carousel_view_->height()); + } +} + +void SnapshotDeltaPane::resizeEvent(QResizeEvent* event) +{ + ResizeItems(); + QWidget::resizeEvent(event); +} + +void SnapshotDeltaPane::AddMemoryDeltaLegends() +{ + legends_->AddColorLegendItem(rmv_util::GetDeltaChangeColor(kDeltaChangeIncrease), "Increase"); + legends_->AddColorLegendItem(rmv_util::GetDeltaChangeColor(kDeltaChangeDecrease), "Decrease"); + legends_->AddColorLegendItem(rmv_util::GetDeltaChangeColor(kDeltaChangeNone), "No delta"); +} diff --git a/source/frontend/views/compare/snapshot_delta_pane.h b/source/frontend/views/compare/snapshot_delta_pane.h new file mode 100644 index 0000000..3f8686a --- /dev/null +++ b/source/frontend/views/compare/snapshot_delta_pane.h @@ -0,0 +1,101 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Snapshot Delta pane. +//============================================================================= + +#ifndef RMV_VIEWS_COMPARE_SNAPSHOT_DELTA_PANE_H_ +#define RMV_VIEWS_COMPARE_SNAPSHOT_DELTA_PANE_H_ + +#include "ui_snapshot_delta_pane.h" + +#include "qt_common/custom_widgets/colored_legend_scene.h" + +#include "rmt_format.h" + +#include "models/compare/snapshot_delta_model.h" +#include "views/base_pane.h" +#include "views/custom_widgets/rmv_carousel.h" +#include "views/custom_widgets/rmv_delta_display.h" +#include "util/definitions.h" + +/// Enum containing indices for the snapshot delta information. +enum SnapshotDeltaDataType +{ + kSnapshotDeltaTypeAvailableSize, + kSnapshotDeltaTypeAllocatedAndBound, + kSnapshotDeltaTypeAllocatedAndUnbound, + kSnapshotDeltaTypeAllocationCount, + kSnapshotDeltaTypeResourceCount, + + kSnapshotDeltaTypeCount, +}; + +/// Pairs a delta display row with a simple line. +struct DeltaDisplayLinePair +{ + RMVDeltaDisplay* display; ///< The delta items. + QFrame* line; ///< The separator line. +}; + +/// Class declaration. +class SnapshotDeltaPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit SnapshotDeltaPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~SnapshotDeltaPane(); + + /// Overridden show event. Fired when this pane is opened. + /// \param event the show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Update this pane with any redraw ops that should happen on pane switch. + virtual void PaneSwitched() Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + + /// Refresh what's visible on the UI. + void Refresh(); + +private slots: + /// Switch snapshots. + void SwitchSnapshots(); + + /// Resize widgets as needed according to the DPI Scale. + void OnScaleFactorChanged(); + +private: + /// Update the UI. + void UpdateUI(); + + /// Resize relevant items. + void ResizeItems(); + + /// Add the memory delta legends to the required scene. + void AddMemoryDeltaLegends(); + + Ui::SnapshotDeltaPane* ui_; ///< Pointer to the Qt UI design. + + rmv::SnapshotDeltaModel* model_; ///< Container class for the widget models. + RMVCarousel* carousel_; ///< Pointer to the carousel object. + ColoredLegendScene* legends_; ///< Pointer to the legends scene. + QVector delta_items_; ///< Array of delta items. + DeltaDisplayLinePair delta_line_pairs_[kRmtHeapTypeCount]; ///< Array of delta pairs. +}; + +#endif // RMV_VIEWS_COMPARE_SNAPSHOT_DELTA_PANE_H_ diff --git a/source/frontend/views/compare/snapshot_delta_pane.ui b/source/frontend/views/compare/snapshot_delta_pane.ui new file mode 100644 index 0000000..cc99659 --- /dev/null +++ b/source/frontend/views/compare/snapshot_delta_pane.ui @@ -0,0 +1,293 @@ + + + SnapshotDeltaPane + + + + 0 + 0 + 1787 + 1801 + + + + + + + true + + + + + 0 + 0 + 1767 + 1781 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Snapshot X + + + + + + + + 0 + 0 + + + + vs. + + + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Snapshot Y + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Switch snapshots + + + + + + + + + + + 0 + 0 + + + + You are comparing two memory snapshots. If you want to reverse the order, click on the top right. + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + ColoredLegendGraphicsView + QGraphicsView +
qt_common/custom_widgets/colored_legend_graphics_view.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledPushButton + QPushButton +
qt_common/custom_widgets/scaled_push_button.h
+
+ + RMVDeltaDisplay + QGraphicsView +
views/custom_widgets/rmv_delta_display.h
+
+
+ + + + +
diff --git a/source/frontend/views/custom_widgets/rmv_allocation_bar.cpp b/source/frontend/views/custom_widgets/rmv_allocation_bar.cpp new file mode 100644 index 0000000..d64a184 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_allocation_bar.cpp @@ -0,0 +1,275 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of an allocation graphics object +//============================================================================= + +#include "views/custom_widgets/rmv_allocation_bar.h" + +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_util.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/message_manager.h" + +static const int kTitleFontSize = 8; +static const int kDefaultWidth = 300; +static const int kDefaultBarPadding = 5; +static const int kDefaultAllocationBarHeight = 50; + +RMVAllocationBar::RMVAllocationBar(rmv::AllocationBarModel* model, int32_t allocation_index, int32_t model_index, const Colorizer* colorizer) + : model_(model) + , allocation_index_(allocation_index) + , model_index_(model_index) + , colorizer_(colorizer) + , item_width_(0) + , item_height_(0) + , max_bar_width_(0) + , allocation_bar_height_(kDefaultAllocationBarHeight) +{ + setAcceptHoverEvents(true); + + title_font_.setPointSizeF(kTitleFontSize); + title_font_.setBold(true); + + description_font_.setPointSizeF(kTitleFontSize); + description_font_.setBold(false); + + UpdateDimensions(kDefaultWidth, allocation_bar_height_); +} + +RMVAllocationBar::~RMVAllocationBar() +{ +} + +QRectF RMVAllocationBar::boundingRect() const +{ + return QRectF(0, 0, item_width_, item_height_); +} + +void RMVAllocationBar::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + const RmtVirtualAllocation* allocation = model_->GetAllocation(allocation_index_, model_index_); + if (allocation == nullptr || max_bar_width_ <= 0) + { + return; + } + + double scaled_bar_y_offset = ScalingManager::Get().Scaled(kDefaultBarPadding); + + // draw the details if necessary + if (model_->ShowDetails()) + { + // Measure title text + QString title_text = model_->GetTitleText(allocation_index_, model_index_); + QFontMetrics font_metrics = ScalingManager::Get().ScaledFontMetrics(title_font_); + QSize title_size = font_metrics.size(0, title_text); + + // Draw title text + painter->setFont(title_font_); + painter->drawText(0, title_size.height(), title_text); + + // Draw description text + painter->setFont(description_font_); + QString description_string = model_->GetDescriptionText(allocation_index_, model_index_); + painter->drawText(title_size.width(), title_size.height(), description_string); + + // Update bar y offset based on the font size. + scaled_bar_y_offset += title_size.height(); + } + + // Calculate width of the current allocation bar. + // May be affected by Normalization in GetBytesPerPixel. + const uint64_t allocation_size = RmtVirtualAllocationGetSizeInBytes(allocation); + const double bytes_per_pixel = model_->GetBytesPerPixel(allocation_index_, model_index_, max_bar_width_); + const int allocation_bar_width = (double)allocation_size / bytes_per_pixel; + + // paint the background first. Needs to be colored based on the coloring mode. + QColor background_brush_color = colorizer_->GetColor(allocation, nullptr); + painter->setPen(Qt::NoPen); + painter->setBrush(background_brush_color); + painter->drawRect(0, scaled_bar_y_offset, allocation_bar_width, allocation_bar_height_); + + // now paint all the resources on top. + int num_rows = model_->GetNumRows(allocation); + if (num_rows > 0) + { + double resource_height = allocation_bar_height_; + resource_height /= num_rows; + + const int resource_count = allocation->resource_count; + for (int current_resource_index = 0; current_resource_index < resource_count; current_resource_index++) + { + const RmtResource* resource = allocation->resources[current_resource_index]; + + if (resource->resource_type == kRmtResourceTypeHeap) + { + continue; + } + + int row = model_->GetRowForResourceAtIndex(allocation, current_resource_index); + double y_offset = scaled_bar_y_offset + (resource_height * row); + + QColor resource_background_brush_color = colorizer_->GetColor(resource->bound_allocation, resource); + + if (current_resource_index == model_->GetHoveredResourceForAllocation(allocation_index_, model_index_)) + { + resource_background_brush_color = resource_background_brush_color.dark(rmv::kHoverDarkenColor); + } + + // calculate the size of the resource + const uint64_t offset_in_bytes = RmtResourceGetOffsetFromBoundAllocation(resource); + const int x_pos = (double)offset_in_bytes / bytes_per_pixel; + const int resource_bar_width = RMT_MAXIMUM(1, (double)resource->size_in_bytes / bytes_per_pixel); + + // render the resource + QPen resource_border_pen(Qt::black); + if (current_resource_index == model_->GetSelectedResourceForAllocation(allocation_index_, model_index_)) + { + resource_border_pen.setWidth(ScalingManager::Get().Scaled(2)); + } + else + { + resource_border_pen.setWidth(ScalingManager::Get().Scaled(1)); + } + painter->setPen(resource_border_pen); + + Qt::BrushStyle style = ((RmtResourceGetAliasCount(resource) > 0) ? Qt::BrushStyle::Dense1Pattern : Qt::BrushStyle::SolidPattern); + const QBrush curr_brush(resource_background_brush_color, style); + painter->setBrush(curr_brush); + painter->drawRect(x_pos, y_offset, resource_bar_width + 1, resource_height); + } + } + + // render border around the whole allocation + painter->setPen(QColor(0, 0, 0)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(0, scaled_bar_y_offset, allocation_bar_width, allocation_bar_height_); +} + +void RMVAllocationBar::UpdateDimensions(const int width, const int height) +{ + int title_height = ScalingManager::Get().ScaledFontMetrics(title_font_).height(); + int scaled_padding = ScalingManager::Get().Scaled(kDefaultBarPadding); + + // Let the scene know that this object is changing sizes. + prepareGeometryChange(); + + // Update the width. + item_width_ = width; + max_bar_width_ = item_width_ - scaled_padding; + + Q_ASSERT(height != -1); + + // This will calculate bar_height_ based on the scaled font size and supplied overall height. + + // Overall item_height_ needs no adjusting since the user is specifying it. + item_height_ = height; + + // Calculate the bar height based on what remains after accounting for: + // the title, padding, (the bar), and another padding. + // This should be similar, but reversed, math to what is done above. + + // These are the items that have a fixed height. + int fixed_height = scaled_padding + scaled_padding; + + if (model_->ShowDetails()) + { + fixed_height += title_height; + } + + // The supplied height must be greater than these fixed heights in order for the bar + // to get displayed at all. + if (height > fixed_height) + { + allocation_bar_height_ = item_height_ - fixed_height; + } + else + { + Q_ASSERT(height > fixed_height); + allocation_bar_height_ = 0; + } +} + +void RMVAllocationBar::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + qreal scaled_bar_y_offset = ScalingManager::Get().Scaled(kDefaultBarPadding); + if (model_->ShowDetails()) + { + // Measure title text + QString title_text = model_->GetTitleText(allocation_index_, model_index_); + QFontMetrics font_metrics = ScalingManager::Get().ScaledFontMetrics(title_font_); + QSize title_size = font_metrics.size(0, title_text); + + // Update bar y offset based on the font size. + scaled_bar_y_offset += title_size.height(); + } + + QPointF mouse_pos = QPointF(event->pos().x(), event->pos().y() - scaled_bar_y_offset); + if (mouse_pos.y() >= 0) + { + setCursor(Qt::PointingHandCursor); + model_->SetHoveredResourceForAllocation(allocation_index_, model_index_, max_bar_width_, allocation_bar_height_, mouse_pos); + } + else + { + setCursor(Qt::ArrowCursor); + model_->SetHoveredResourceForAllocation(allocation_index_, -1, model_index_); + } + update(); +} + +void RMVAllocationBar::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + + model_->SetHoveredResourceForAllocation(allocation_index_, -1, model_index_); + update(); +} + +void RMVAllocationBar::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + model_->SetSelectedResourceForAllocation(allocation_index_, -1, model_index_); + + RmtResourceIdentifier resource_identifier = model_->FindResourceIdentifier(allocation_index_, model_index_); + { + emit ResourceSelected(resource_identifier, false); + } +} + +void RMVAllocationBar::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + const RmtVirtualAllocation* allocation = model_->GetAllocation(allocation_index_, model_index_); + if (allocation == nullptr) + { + return; + } + + int32_t selected_resource = model_->GetSelectedResourceForAllocation(allocation_index_, model_index_); + + if (selected_resource >= 0) + { + // selected a resource so emit a signal indicating so + Q_ASSERT(allocation->resources[selected_resource]); + RmtResourceIdentifier resource_id = allocation->resources[selected_resource]->identifier; + + emit ResourceSelected(resource_id, true); + } + else + { + // Didn't find a resource so probably clicked on an unbound area, so select the allocation and + // navigate to the allocation explorer + emit MessageManager::Get().UnboundResourceSelected(allocation); + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotAllocationExplorer); + } +} diff --git a/source/frontend/views/custom_widgets/rmv_allocation_bar.h b/source/frontend/views/custom_widgets/rmv_allocation_bar.h new file mode 100644 index 0000000..c56c864 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_allocation_bar.h @@ -0,0 +1,91 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for an allocation graphics object +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_ALLOCATION_BAR_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_ALLOCATION_BAR_H_ + +#include +#include + +#include "rmt_types.h" + +#include "models/allocation_bar_model.h" +#include "util/definitions.h" +#include "views/colorizer.h" + +/// Container class for a memory block widget. +class RMVAllocationBar : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param model The underlying model holding the backend data. + /// \param allocation_index The index of the allocation in the model containing the raw allocation data. + /// \param model_index The allocation model index this graphic item refers to (for panes with multiple allocation displays). + /// \param colorizer The colorizer used to color this widget. + explicit RMVAllocationBar(rmv::AllocationBarModel* model, int32_t allocation_index, int32_t model_index, const Colorizer* colorizer); + + /// Destructor. + virtual ~RMVAllocationBar(); + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param option Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + void paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) Q_DECL_OVERRIDE; + + /// Mouse hover over event. + /// \param event the QGraphicsSceneHoverEvent. + void hoverMoveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse hover leave event. + /// \param event the QGraphicsSceneHoverEvent. + void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse press event. + /// \param event the QGraphicsSceneHoverEvent. + void mousePressEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Mouse double click event. + /// \param event the QGraphicsSceneHoverEvent. + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Set Dimensions of the item. + /// Since the text has a fixed height, this method has the desired effect + /// of controlling the maximum width of the bar, and the height of the bar. + /// \param width The new mamximum width. + /// \param height The new total height. + /// height of the items adjusted for DPI scaling. + void UpdateDimensions(const int width, const int height); + +signals: + /// signal that a resource has been selected. + /// \param resource_identifier The selected resource. + /// \param navigate_to_pane If true, indicate that navigation to another pane is requested. + /// It is up the the slot to decide which pane to navigate to. + void ResourceSelected(RmtResourceIdentifier resource_Identifier, bool navigate_to_pane); + +private: + rmv::AllocationBarModel* model_; ///< The underlying model holding the backend data. + int32_t allocation_index_; ///< The index of this object in the scene. + int32_t model_index_; ///< The allocation model index this graphic item refers to (for panes with multiple allocation displays). + const Colorizer* colorizer_; ///< The colorizer used to color this widget. + QFont title_font_; ///< Font used for painting the title. + QFont description_font_; ///< Font used for painting the description. + + int item_width_; ///< Pixel width of this item (ie: bounding rect width), see UpdateDimensions(). + int item_height_; ///< Pixel height of this item (ie: bounding rect height), see UpdateDimensions(). + int max_bar_width_; ///< Maximum bar width after accounting for the bar padding along the right side, see UpdateDimensions(). + int allocation_bar_height_; ///< Pixel height of the allocation bar; already includes scaling factor, see UpdateDimensions(). +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_ALLOCATION_BAR_H_ diff --git a/source/frontend/views/custom_widgets/rmv_camera_snapshot_widget.cpp b/source/frontend/views/custom_widgets/rmv_camera_snapshot_widget.cpp new file mode 100644 index 0000000..67275b3 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_camera_snapshot_widget.cpp @@ -0,0 +1,179 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of memory block widget +//============================================================================= + +#include "views/custom_widgets/rmv_camera_snapshot_widget.h" + +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "models/trace_manager.h" +#include "models/message_manager.h" + +RMVCameraSnapshotWidget::RMVCameraSnapshotWidget(const RMVCameraSnapshotWidgetConfig& config) + : config_(config) + , render_color_(config_.base_color) +{ + setAcceptHoverEvents(true); +} + +RMVCameraSnapshotWidget::~RMVCameraSnapshotWidget() +{ +} + +QRectF RMVCameraSnapshotWidget::boundingRect() const +{ + return QRectF(ScaledMargin(), ScaledMargin(), ScaledWidth(), ScaledHeight()); +} + +QPainterPath RMVCameraSnapshotWidget::shape() const +{ + QPainterPath path; + path.addEllipse(boundingRect()); + return path; +} + +void RMVCameraSnapshotWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) +{ + Q_UNUSED(item); + Q_UNUSED(widget); + + painter->setPen(Qt::NoPen); + painter->setBrush(render_color_); + painter->setCompositionMode(QPainter::CompositionMode_Multiply); + + painter->setRenderHint(QPainter::Antialiasing); + + const int camera_width = ScalingManager::Get().Scaled(120); + const int camera_height = ScalingManager::Get().Scaled(80); + + const int circle_diameter = ScalingManager::Get().Scaled(kCircleDiameter) - ScaledMargin() * 2; + + painter->drawEllipse(ScaledMargin(), ScaledMargin(), ScaledWidth(), ScaledHeight()); + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + + painter->setBrush(Qt::white); + painter->drawRoundedRect( + QRect(ScaledMargin() + circle_diameter / 2 - camera_width / 2, ScaledMargin() + circle_diameter / 2 - camera_height / 2, camera_width, camera_height), + ScalingManager::Get().Scaled(10), + ScalingManager::Get().Scaled(10)); + + painter->setBrush(Qt::white); + painter->drawRect(ScaledMargin() + circle_diameter / 2 - camera_width / 2 + 15, + ScaledMargin() + circle_diameter / 2 - camera_height / 2 - ScalingManager::Get().Scaled(5), + ScalingManager::Get().Scaled(20), + ScalingManager::Get().Scaled(20)); + + painter->setBrush(render_color_); + painter->drawRect(ScaledMargin() + circle_diameter / 2 + 30, + ScaledMargin() + circle_diameter / 2 - ScalingManager::Get().Scaled(30), + ScalingManager::Get().Scaled(20), + ScalingManager::Get().Scaled(10)); + + const int lens_diameter_0 = ScalingManager::Get().Scaled(50); + painter->setBrush(render_color_); + painter->drawEllipse(ScaledMargin() + circle_diameter / 2 - lens_diameter_0 / 2, + ScaledMargin() + circle_diameter / 2 - lens_diameter_0 / 2 + 3, + lens_diameter_0, + lens_diameter_0); + + const int lens_diameter_1 = ScalingManager::Get().Scaled(40); + painter->setBrush(Qt::white); + painter->drawEllipse(ScaledMargin() + circle_diameter / 2 - lens_diameter_1 / 2, + ScaledMargin() + circle_diameter / 2 - lens_diameter_1 / 2 + 3, + lens_diameter_1, + lens_diameter_1); + + const int lens_diameter_2 = ScalingManager::Get().Scaled(30); + painter->setBrush(render_color_); + painter->drawEllipse(ScaledMargin() + circle_diameter / 2 - lens_diameter_2 / 2, + ScaledMargin() + circle_diameter / 2 - lens_diameter_2 / 2 + 3, + lens_diameter_2, + lens_diameter_2); + + painter->setPen(Qt::white); + const int snapshot_name_length = QtCommon::QtUtils::GetPainterTextWidth(painter, config_.snapshot_name); + painter->drawText( + ScaledMargin() + circle_diameter / 2 - snapshot_name_length / 2, ScaledMargin() + circle_diameter / 2 + circle_diameter / 4, config_.snapshot_name); +} + +void RMVCameraSnapshotWidget::UpdateDimensions(int width, int height) +{ + config_.width = width - 2; + config_.height = height - 2; +} + +void RMVCameraSnapshotWidget::UpdateName(const QString& name) +{ + config_.snapshot_name = name; + + update(); +} + +void RMVCameraSnapshotWidget::UpdateBaseColor(const QColor& color) +{ + config_.base_color = color; + + render_color_ = color; + + update(); +} + +void RMVCameraSnapshotWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + + if (config_.interactive) + { + setCursor(Qt::PointingHandCursor); + + render_color_ = config_.base_color.dark(125); + } + + update(); +} + +void RMVCameraSnapshotWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + + if (config_.interactive) + { + render_color_ = config_.base_color; + } + + update(); +} + +void RMVCameraSnapshotWidget::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + if (config_.interactive) + { + if (TraceManager::Get().DataSetValid()) + { + emit Navigate(); + } + } +} + +int32_t RMVCameraSnapshotWidget::ScaledHeight() const +{ + return ScalingManager::Get().Scaled(config_.height); +} + +int32_t RMVCameraSnapshotWidget::ScaledWidth() const +{ + return ScalingManager::Get().Scaled(config_.width); +} + +int32_t RMVCameraSnapshotWidget::ScaledMargin() const +{ + return ScalingManager::Get().Scaled(config_.margin); +} \ No newline at end of file diff --git a/source/frontend/views/custom_widgets/rmv_camera_snapshot_widget.h b/source/frontend/views/custom_widgets/rmv_camera_snapshot_widget.h new file mode 100644 index 0000000..a641ae3 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_camera_snapshot_widget.h @@ -0,0 +1,108 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a memory block widget +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAMERA_SNAPSHOT_WIDGET_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAMERA_SNAPSHOT_WIDGET_H_ + +#include + +#include "util/definitions.h" + +/// The camera widget diameter. +static const qreal kCircleDiameter = 300.0; + +/// Holds data for rendering of a camera widget. +struct RMVCameraSnapshotWidgetConfig +{ + RMVCameraSnapshotWidgetConfig() + : width(0) + , height(0) + , margin(0) + , interactive(false) + { + } + + int width; ///< Widget width. + int height; ///< Widget height. + int margin; ///< How far from the edge. + QColor base_color; ///< Background color. + QString snapshot_name; ///< Name of the snapshot. + bool interactive; ///< Can be clicked. +}; + +/// Container class for a camera widget which gets rendered when nothing has been compared yet. +class RMVCameraSnapshotWidget : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVCameraSnapshotWidget(const RMVCameraSnapshotWidgetConfig& config); + + /// Destructor. + virtual ~RMVCameraSnapshotWidget(); + + /// Mouse hover over event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse hover leave event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse press event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's bounding shape for this item. + /// \return The item's QPainterPath. + virtual QPainterPath shape() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param item Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) Q_DECL_OVERRIDE; + + /// Set max width. + /// \param width the width. + void UpdateDimensions(int width, int height); + + /// Update current snapshot name. + /// \param name new name. + void UpdateName(const QString& name); + + /// Update current base color. + /// \param color new color. + void UpdateBaseColor(const QColor& color); + +signals: + /// Signal emitted when a snapshot is clicked on. + void Navigate(); + +private: + /// Get scaled height. + /// \return scaled height. + int32_t ScaledHeight() const; + + /// Get scaled width. + /// \return scaled width. + int32_t ScaledWidth() const; + + /// Get scaled margin. + /// \return scaled margin. + int32_t ScaledMargin() const; + + RMVCameraSnapshotWidgetConfig config_; ///< Description of this widget. + QColor render_color_; ///< Color to use when drawing the widget. +}; +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAMERA_SNAPSHOT_WIDGET_H_ diff --git a/source/frontend/views/custom_widgets/rmv_carousel.cpp b/source/frontend/views/custom_widgets/rmv_carousel.cpp new file mode 100644 index 0000000..89b1248 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel.cpp @@ -0,0 +1,214 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for RMV's carousel +//============================================================================= + +#include "views/custom_widgets/rmv_carousel.h" + +#include "qt_common/utils/scaling_manager.h" + +#include "views/custom_widgets/rmv_carousel_memory_footprint.h" +#include "views/custom_widgets/rmv_carousel_allocation_sizes.h" +#include "views/custom_widgets/rmv_carousel_memory_types.h" +#include "views/custom_widgets/rmv_carousel_resource_types.h" + +static const int kNavButtonWidth = 30; + +RMVCarousel::RMVCarousel(const RMVCarouselConfig& config) + : config_(config) + , carousel_index_(1) +{ + scene_ = new QGraphicsScene(); + + RMVCarouselConfig item_config = {}; + item_config.width = 0; + item_config.height = config_.height; + item_config.data_type = config_.data_type; + + model_ = new CarouselModel(); + + // add items to the scene. The scene takes ownership of the items + // with addItem() so no need to delete these objects + left_nav_button_ = new RMVCarouselNavButton(item_config.width, item_config.height, true); + scene_->addItem(left_nav_button_); + + right_nav_button_ = new RMVCarouselNavButton(item_config.width, item_config.height, false); + scene_->addItem(right_nav_button_); + + info_text_ = scene_->addText(""); + info_text_->setPos(0, item_config.height - 20.0); + + // don't show the carousel counter (for now) + info_text_->hide(); + + // add the carousel widgets + CreateCarouselItem(item_config); + CreateCarouselItem(item_config); + RMVCarouselMemoryTypes* virtual_memory = CreateCarouselItem(item_config); + RMVCarouselMemoryTypes* physical_memory = CreateCarouselItem(item_config); + CreateCarouselItem(item_config); + + // set the heap types required for the heap carousel items + virtual_memory->SetIsPhysicalHeap(false); + physical_memory->SetIsPhysicalHeap(true); + + connect(left_nav_button_, &RMVCarouselNavButton::PressedButton, this, &RMVCarousel::MoveCarousel); + connect(right_nav_button_, &RMVCarouselNavButton::PressedButton, this, &RMVCarousel::MoveCarousel); +} + +RMVCarousel::~RMVCarousel() +{ + delete scene_; + delete model_; +} + +void RMVCarousel::MoveCarousel(bool left_direction) +{ + if (carousel_items_.empty() == false) + { + const int carousel_size = carousel_items_.size(); + + if (left_direction) + { + RMVCarouselItem* temp = carousel_items_[carousel_size - 1]; + + for (int i = carousel_size - 1; i > 0; i--) + { + carousel_items_[i] = carousel_items_[i - 1]; + } + + carousel_items_[0] = temp; + + // decrease carousel index (1-based) + carousel_index_--; + if (carousel_index_ < 1) + { + carousel_index_ = carousel_size; + } + } + else + { + RMVCarouselItem* temp = carousel_items_[0]; + + for (int i = 0; i < carousel_size - 1; i++) + { + carousel_items_[i] = carousel_items_[i + 1]; + } + + carousel_items_[carousel_size - 1] = temp; + + // increase carousel index (1-based) + carousel_index_++; + if (carousel_index_ > carousel_size) + { + carousel_index_ = 1; + } + } + + Update(); + } +} + +void RMVCarousel::Update() +{ + const QRectF scene_rect = QRectF(0, 0, config_.width, config_.height); + + scene_->setSceneRect(scene_rect); + + int nav_button_height = config_.height / 2; + int nav_button_width = ScalingManager::Get().Scaled(kNavButtonWidth); + + left_nav_button_->UpdateDimensions(nav_button_width, nav_button_height); + right_nav_button_->UpdateDimensions(nav_button_width, nav_button_height); + + int y_nav_pos = nav_button_height / 2; + + left_nav_button_->setPos(0, y_nav_pos); + int right_nav_x_pos = config_.width - nav_button_width; + right_nav_button_->setPos(right_nav_x_pos, y_nav_pos); + + int widget_start_pos = nav_button_width; + int widget_end_pos = config_.width - nav_button_width; + int available_pixels = widget_end_pos - widget_start_pos; + + int potential_pixels = 0; + int consumed_pixels = 0; + int widget_fit_count = 0; + + for (int i = 0; i < carousel_items_.size(); i++) + { + potential_pixels += carousel_items_[i]->boundingRect().width(); + + if (potential_pixels < available_pixels - (ScalingManager::Get().Scaled(5))) + { + widget_fit_count++; + consumed_pixels += carousel_items_[i]->boundingRect().width(); + + carousel_items_[i]->show(); + } + else + { + carousel_items_[i]->hide(); + } + } + + int free_pixels = available_pixels - consumed_pixels; + int free_space_count = widget_fit_count + 1; + int free_space_pixel_width = free_pixels / free_space_count; + + int x_pos = widget_start_pos + free_space_pixel_width; + for (int i = 0; i < carousel_items_.size(); i++) + { + carousel_items_[i]->setPos(x_pos, 0); + x_pos += (carousel_items_[i]->boundingRect().width() + free_space_pixel_width); + } + + // update the carousel info + QString info_string = QString::number(carousel_index_) + QString("/") + QString::number(carousel_items_.size()); + info_text_->setPlainText(info_string); +} + +QGraphicsScene* RMVCarousel::Scene() +{ + return scene_; +} + +void RMVCarousel::ResizeEvent(int width, int height) +{ + config_.width = width; + config_.height = height; + + Update(); +} + +void RMVCarousel::SetData(const RMVCarouselData& carousel_data) +{ + for (auto it : carousel_items_) + { + it->SetData(carousel_data); + } + + update(); +} + +void RMVCarousel::ClearData() +{ + RMVCarouselData empty_data = {}; + SetData(empty_data); +} + +void RMVCarousel::UpdateModel() +{ + RMVCarouselData carousel_data = {}; + model_->GetCarouselData(carousel_data); + SetData(carousel_data); +} + +void RMVCarousel::UpdateModel(RmtDataSnapshot* base_snapshot, RmtDataSnapshot* diff_snapshot) +{ + RMVCarouselData carousel_delta_data = {}; + model_->CalcGlobalCarouselData(base_snapshot, diff_snapshot, carousel_delta_data); + SetData(carousel_delta_data); +} diff --git a/source/frontend/views/custom_widgets/rmv_carousel.h b/source/frontend/views/custom_widgets/rmv_carousel.h new file mode 100644 index 0000000..3a6d3a5 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel.h @@ -0,0 +1,87 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's carousel +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_H_ + +#include +#include + +#include "rmt_data_set.h" + +#include "models/carousel_model.h" +#include "views/custom_widgets/rmv_carousel_item.h" +#include "views/custom_widgets/rmv_carousel_nav_button.h" + +/// Container class for a carousel. +class RMVCarousel : public QWidget +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config The configuration parameters. + explicit RMVCarousel(const RMVCarouselConfig& config); + + /// Destructor. + virtual ~RMVCarousel(); + + /// Overridden window resize event. + /// \param width The new width. + /// \param height The new height. + void ResizeEvent(int width, int height); + + /// Get the graphics scene for the carousel. + /// \return The carousel graphics scene. + QGraphicsScene* Scene(); + + /// Clear out the data. + void ClearData(); + + /// Update model for a single snapshot. + void UpdateModel(); + + /// Update model for 2 compared snapshots. + /// \param base_snapshot The first (base) snapshot. + /// \param diff_snapshot The second snapshot to compare against the first. + void UpdateModel(RmtDataSnapshot* base_snapshot, RmtDataSnapshot* diff_snapshot); + +private slots: + /// Move the carousel. + /// \param left_direction If true, move left, otherwise move right. + void MoveCarousel(bool left_direction); + +private: + /// set the UI data for the individual carousel items. + /// \param carousel_data The information needed by the carousel. + void SetData(const RMVCarouselData& carousel_data); + + /// Template function to create a new carousel item of a certain type. + /// Also saves the base pointer to an array. + template + CarouselItemType* CreateCarouselItem(RMVCarouselConfig& config) + { + CarouselItemType* item = new CarouselItemType(config); + scene_->addItem(item); + carousel_items_.push_back(item); + return item; + } + + /// Refresh the carousel. + void Update(); + + RMVCarouselConfig config_; ///< The initial configuration parameters for the carousel. + QGraphicsScene* scene_; ///< Pointer to the graphics scene. + RMVCarouselNavButton* left_nav_button_; ///< The left button graphic. + RMVCarouselNavButton* right_nav_button_; ///< The right button graphic. + QVector carousel_items_; ///< The list of carousel items. + QGraphicsTextItem* info_text_; ///< The info text showing the current carousel index. + int32_t carousel_index_; ///< The current carousel index. + CarouselModel* model_; ///< The carousel model which interfaces with the backend. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_H_ diff --git a/source/frontend/views/custom_widgets/rmv_carousel_allocation_sizes.cpp b/source/frontend/views/custom_widgets/rmv_carousel_allocation_sizes.cpp new file mode 100644 index 0000000..79e8da8 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_allocation_sizes.cpp @@ -0,0 +1,133 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV's carousel allocation sizes widget +//============================================================================= + +#include "views/custom_widgets/rmv_carousel_allocation_sizes.h" + +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "util/rmv_util.h" + +static const int kBarTopOffset = 45; +static const int kBarHeight = 180; + +RMVCarouselAllocationSizes::RMVCarouselAllocationSizes(const RMVCarouselConfig& config) + : RMVCarouselItem(config) + , data_{} +{ +} + +RMVCarouselAllocationSizes::~RMVCarouselAllocationSizes() +{ +} + +void RMVCarouselAllocationSizes::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + DrawCarouselBaseComponents(painter, "Virtual memory allocation size"); + + const QString size_strings[] = {"0MiB", "1MiB", "2MiB", "4MiB", "8MiB", "16MiB", "32MiB", "64MiB", "128MiB", "256MiB", "512MiB", "1GiB"}; + + int gap = 5; + int bar_width = config_.width / (kNumAllocationSizeBuckets + 3); + int x_offset = (config_.width - (((kNumAllocationSizeBuckets - 1) * gap) + (kNumAllocationSizeBuckets * bar_width))) / 2; + + for (int index = 0; index < kNumAllocationSizeBuckets; index++) + { + int x_pos = ScalingManager::Get().Scaled((index * (gap + bar_width)) + x_offset); + DrawAllocationBar(painter, bar_width, x_pos, data_.buckets[index], size_strings[index]); + } +} + +void RMVCarouselAllocationSizes::DrawAllocationBar(QPainter* painter, int bar_width, int x_pos, int32_t value, const QString& label_string) +{ + QColor text_color = Qt::black; + QColor fill_color = kDefaultCarouselBarColor; + + int origin = 0; + float bar_scale = 1.0; + bool negative = false; + + if (config_.data_type == kCarouselDataTypeDelta) + { + if (value > 0) + { + fill_color = rmv_util::GetDeltaChangeColor(kDeltaChangeIncrease); + } + else if (value < 0) + { + fill_color = rmv_util::GetDeltaChangeColor(kDeltaChangeDecrease); + negative = true; + } + else + { + fill_color = rmv_util::GetDeltaChangeColor(kDeltaChangeNone); + } + text_color = fill_color; + + value = abs(value); + bar_scale = 0.5; + origin = ScalingManager::Get().Scaled(kBarHeight / 2); + } + + // Calculate the positioning of the bars given a spacing between the bars and the width of each bar + int bar_top = ScalingManager::Get().Scaled(kBarTopOffset); + int bar_bottom = ScalingManager::Get().Scaled(kBarTopOffset + kBarHeight); + int bar_height = ScalingManager::Get().Scaled(kBarHeight); + int font_size = ScalingManager::Get().Scaled(9); + int bar_length = ScalingManager::Get().Scaled(bar_width); + + // draw the bar + painter->setPen(Qt::NoPen); + painter->setBrush(Qt::white); + painter->drawRect(x_pos, bar_top, bar_length, bar_height); + + // draw the data in the bar if data is valid + painter->setBrush(fill_color); + if (data_.num_allocations > 0) + { + float height = (value * bar_height) / data_.num_allocations; + height *= bar_scale; + height = std::max(height, 1.0); + if (!negative) + { + origin += height; + } + painter->drawRect(x_pos, bar_bottom - origin, bar_length, height); + } + + // set up the text drawing + QFont font = painter->font(); + font.setBold(false); + font.setPixelSize(font_size); + painter->setPen(Qt::black); + painter->setFont(font); + QFontMetricsF scaled_font_metrics = ScalingManager::Get().ScaledFontMetrics(font); + + // draw the text label under the bar + qreal text_width = scaled_font_metrics.width(label_string); + int text_x_offset = x_pos - (text_width / 2); + int text_y_offset = bar_top + bar_height + font_size; + painter->drawText(text_x_offset, text_y_offset, label_string); + + // draw the value string above the bar, centered + QString value_string = QString::number(value); + text_width = scaled_font_metrics.width(value_string); + text_x_offset = x_pos + ((bar_width - text_width) / 2); + text_y_offset = bar_top - (font_size / 2); + painter->setPen(text_color); + painter->drawText(text_x_offset, text_y_offset, value_string); +} + +void RMVCarouselAllocationSizes::SetData(const RMVCarouselData& data) +{ + data_ = data.allocation_sizes_data; + update(); +} diff --git a/source/frontend/views/custom_widgets/rmv_carousel_allocation_sizes.h b/source/frontend/views/custom_widgets/rmv_carousel_allocation_sizes.h new file mode 100644 index 0000000..c595640 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_allocation_sizes.h @@ -0,0 +1,53 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's carousel allocation sizes widget +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_ALLOCATION_SIZES_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_ALLOCATION_SIZES_H_ + +#include + +#include "models/carousel_model.h" +#include "views/custom_widgets/rmv_carousel_item.h" + +/// Container class for the carousel allocation sizes component. +class RMVCarouselAllocationSizes : public RMVCarouselItem +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVCarouselAllocationSizes(const RMVCarouselConfig& config); + + /// Destructor + virtual ~RMVCarouselAllocationSizes(); + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param option Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + /// Set new data and update. + /// \param data new data. + void SetData(const RMVCarouselData& data) override; + +private: + /// Draw a single allocation bar. This is currently a vertical bar representing + /// the number of allocations in a bucket. + /// \param painter The painter object to use. + /// \param bar_width The width of the bar in pixels (unscaled). + /// \param x_pos The x position of the bar, in pixels (unscaled). + /// \param value The value represented by the bar. + /// \param label_string The text label to display under the bar, indicating the range + /// of values this bar represents. + void DrawAllocationBar(QPainter* painter, int bar_width, int x_pos, int32_t value, const QString& label_string); + + RMVCarouselAllocationSizesData data_; ///< The model data for this carousel item. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_ALLOCATION_SIZES_H_ diff --git a/source/frontend/views/custom_widgets/rmv_carousel_item.cpp b/source/frontend/views/custom_widgets/rmv_carousel_item.cpp new file mode 100644 index 0000000..411201a --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_item.cpp @@ -0,0 +1,184 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the carousel item widget base class. +//============================================================================= + +#include "views/custom_widgets/rmv_carousel_item.h" + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "util/rmv_util.h" +#include "util/string_util.h" + +static const int kSummaryTextOffset = 15; + +RMVCarouselItem::RMVCarouselItem(const RMVCarouselConfig& config) + : config_(config) +{ + config_.width = kCarouselItemWidth; + config_.height = kCarouselItemHeight; +} + +RMVCarouselItem::~RMVCarouselItem() +{ +} + +QRectF RMVCarouselItem::boundingRect() const +{ + return QRectF(0, 0, ScalingManager::Get().Scaled(config_.width), ScalingManager::Get().Scaled(config_.height)); +} + +void RMVCarouselItem::UpdateDimensions(int width, int height) +{ + config_.width = width; + config_.height = height; +} + +void RMVCarouselItem::DrawCarouselBaseComponents(QPainter* painter, const QString& title) const +{ + int widget_width = config_.width; + int widget_height = config_.height; + + widget_width = ScalingManager::Get().Scaled(widget_width); + widget_height = ScalingManager::Get().Scaled(widget_height); + + painter->setPen(Qt::NoPen); + painter->setBrush(kCarouselBaseColor); + painter->drawRect(0, 0, widget_width, widget_height); + + QFont font; + font.setPixelSize(ScalingManager::Get().Scaled(15)); + font.setBold(true); + painter->setFont(font); + painter->setPen(Qt::black); + painter->drawText( + ScalingManager::Get().Scaled(10), ScalingManager::Get().Scaled(20), title + ((config_.data_type == kCarouselDataTypeDelta) ? " delta" : "")); +} + +void RMVCarouselItem::DrawHorizontalBarComponent(QPainter* painter, + const QString& bar_title, + uint32_t x_pos, + uint32_t y_pos, + uint32_t bar_length, + uint32_t bar_width, + int64_t value, + int64_t max, + bool show_summary) const +{ + DrawColoredHorizontalBarComponent(painter, bar_title, kDefaultCarouselBarColor, x_pos, y_pos, bar_length, bar_width, value, max, show_summary); +} + +void RMVCarouselItem::DrawColoredHorizontalBarComponent(QPainter* painter, + const QString& bar_title, + const QColor& bar_color, + uint32_t x_pos, + uint32_t y_pos, + uint32_t bar_length, + uint32_t bar_width, + int64_t value, + int64_t max, + bool show_summary) const +{ + QColor fill_color = bar_color; + QColor text_color = Qt::black; + + int origin = 0; + float bar_scale = 1.0; + bool negative = false; + + if (config_.data_type == kCarouselDataTypeDelta) + { + if (value > 0) + { + fill_color = rmv_util::GetDeltaChangeColor(kDeltaChangeIncrease); + } + else if (value < 0) + { + fill_color = rmv_util::GetDeltaChangeColor(kDeltaChangeDecrease); + negative = true; + } + else + { + fill_color = rmv_util::GetDeltaChangeColor(kDeltaChangeNone); + } + text_color = fill_color; + + value = abs(value); + max = abs(max); + bar_scale = 0.5; + origin = ScalingManager::Get().Scaled(int(bar_length / 2)); + } + + QFont font = painter->font(); + + x_pos = ScalingManager::Get().Scaled((int)x_pos); + y_pos = ScalingManager::Get().Scaled((int)y_pos); + bar_length = ScalingManager::Get().Scaled((int)bar_length); + bar_width = ScalingManager::Get().Scaled((int)bar_width); + + if (bar_title.isEmpty() == false) + { + int title_font_size = ScalingManager::Get().Scaled(12); + font.setBold(false); + font.setPixelSize(title_font_size); + painter->setPen(Qt::black); + painter->setFont(font); + painter->drawText(x_pos, y_pos - title_font_size, bar_title); + } + + // The length of the bar that contains data + uint32_t filled_bar_length = max != 0 ? (value * bar_length) / max : bar_length; + filled_bar_length = std::min(filled_bar_length, bar_length); + + // make sure bar is within bounds + if (filled_bar_length > bar_length) + { + filled_bar_length = bar_length; + } + + // take into account scale. The bar is scaled by 0.5 in delta mode since values + // go positive or negative + filled_bar_length *= bar_scale; + + // make sure at least 1 pixel row is drawn + if (filled_bar_length < 1.0) + { + filled_bar_length = 1.0; + } + + // move the origin if the bar is negative + if (negative) + { + origin -= filled_bar_length; + } + + // paint the bar background in white + painter->setPen(Qt::NoPen); + painter->setBrush(Qt::white); + painter->drawRect(x_pos, y_pos, bar_length, bar_width); + + // paint the bar + if (max != 0) + { + painter->setBrush(fill_color); + painter->drawRect(x_pos + origin, y_pos, filled_bar_length, bar_width); + + if (show_summary) + { + font.setBold(true); + font.setPixelSize(ScalingManager::Get().Scaled(10)); + painter->setFont(font); + painter->setPen(text_color); + + QString allocated_description = + rmv::string_util::LocalizedValueMemory(value, false, false) + " out of " + rmv::string_util::LocalizedValueMemory(max, false, false); + int allocated_length = QtCommon::QtUtils::GetTextWidth(font, allocated_description); + + painter->drawText( + x_pos + bar_length - allocated_length, y_pos + bar_width + ScalingManager::Get().Scaled(kSummaryTextOffset), allocated_description); + } + } +} diff --git a/source/frontend/views/custom_widgets/rmv_carousel_item.h b/source/frontend/views/custom_widgets/rmv_carousel_item.h new file mode 100644 index 0000000..0eebca4 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_item.h @@ -0,0 +1,114 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the carousel item widget base class. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_ITEM_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_ITEM_H_ + +#include +#include + +#include "models/carousel_model.h" + +static const int kCarouselItemHeight = 275; +static const int kCarouselItemWidth = 450; +static const QColor kCarouselBaseColor = QColor(240, 240, 240); +static const QColor kDefaultCarouselBarColor = QColor(127, 127, 127); + +/// Enum for the carousel type. +enum CarouselDataType +{ + kCarouselDataTypeRegular, + kCarouselDataTypeDelta +}; + +/// Configuration struct for carousel. +struct RMVCarouselConfig +{ + int width; ///< Width. + int height; ///< Height. + CarouselDataType data_type; ///< Either a regular carousel, or a delta carousel. +}; + +/// Class to describe an item on the carousel. +class RMVCarouselItem : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Constructor. + explicit RMVCarouselItem(const RMVCarouselConfig& config); + + /// Destructor. + virtual ~RMVCarouselItem(); + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const Q_DECL_OVERRIDE; + + /// Update the dimensions of this object. + /// \param width The new width. + /// \param height The new height. + void UpdateDimensions(int width, int height); + + /// Set the UI data. + /// \param data The data from the model. + virtual void SetData(const RMVCarouselData& data) = 0; + +protected: + /// Helper func to draw a carousel box with a title. + /// \param painter The Qt painter. + /// \param title How to name it. + void DrawCarouselBaseComponents(QPainter* painter, const QString& title) const; + + /// Helper function to draw a horizontal carousel bar item with text. + /// \param painter The Qt painter. + /// \param bar_title The title of the bar. If empty string, title will not be displayed. + /// \param x_pos The x position of the bar, relative to the parent carousel box. + /// \param y_pos The y position of the bar, relative to the parent carousel box. + /// \param bar_length The length of the bar (horizontally). + /// \param bar_width The width of the bar (the vertical height of the bar). + /// \param value The value to display. + /// \param max The maximum value. + /// \param show_summary If true, show a summary of the value and max values. + void DrawHorizontalBarComponent(QPainter* painter, + const QString& bar_title, + uint32_t x_pos, + uint32_t y_pos, + uint32_t bar_length, + uint32_t bar_width, + int64_t value, + int64_t max, + bool show_summary) const; + + /// Helper function to draw a horizontal carousel bar item with text. + /// \param painter The Qt painter. + /// \param bar_title The title of the bar. If empty string, title will not be displayed. + /// \param bar_color The color of the bar if a single snapshot. Compare color will override this. + /// \param x_pos The x position of the bar, relative to the parent carousel box. + /// \param y_pos The y position of the bar, relative to the parent carousel box. + /// \param bar_length The length of the bar (horizontally). + /// \param bar_width The width of the bar (the vertical height of the bar). + /// \param value The value to display. + /// \param max The maximum value. + /// \param show_summary If true, show a summary of the value and max values. + void DrawColoredHorizontalBarComponent(QPainter* painter, + const QString& bar_title, + const QColor& bar_color, + uint32_t x_pos, + uint32_t y_pos, + uint32_t bar_length, + uint32_t bar_width, + int64_t value, + int64_t max, + bool show_summary) const; + + RMVCarouselConfig config_; ///< The configuration for the carousel. + +private: +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_ITEM_H_ diff --git a/source/frontend/views/custom_widgets/rmv_carousel_memory_footprint.cpp b/source/frontend/views/custom_widgets/rmv_carousel_memory_footprint.cpp new file mode 100644 index 0000000..e228f05 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_memory_footprint.cpp @@ -0,0 +1,41 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV's carousel memory footprint widget +//============================================================================= + +#include "views/custom_widgets/rmv_carousel_memory_footprint.h" + +#include + +static const int kBarWidth = 20; +static const int kEdgeMargin = 10; + +RMVCarouselMemoryFootprint::RMVCarouselMemoryFootprint(const RMVCarouselConfig& config) + : RMVCarouselItem(config) + , data_{} +{ +} + +RMVCarouselMemoryFootprint::~RMVCarouselMemoryFootprint() +{ +} + +void RMVCarouselMemoryFootprint::paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) +{ + Q_UNUSED(item); + Q_UNUSED(widget); + + DrawCarouselBaseComponents(painter, "Virtual memory"); + + int bar_length = config_.width - (2 * kEdgeMargin); + DrawHorizontalBarComponent(painter, "Bound virtual memory", kEdgeMargin, 80, bar_length, kBarWidth, data_.total_allocated_memory, data_.max_memory, true); + DrawHorizontalBarComponent(painter, "Unbound virtual memory", kEdgeMargin, 170, bar_length, kBarWidth, data_.total_unused_memory, data_.max_memory, true); +} + +void RMVCarouselMemoryFootprint::SetData(const RMVCarouselData& data) +{ + data_ = data.memory_footprint_data; + update(); +} diff --git a/source/frontend/views/custom_widgets/rmv_carousel_memory_footprint.h b/source/frontend/views/custom_widgets/rmv_carousel_memory_footprint.h new file mode 100644 index 0000000..3910598 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_memory_footprint.h @@ -0,0 +1,43 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's carousel memory footprint widget +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_MEMORY_FOOTPRINT_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_MEMORY_FOOTPRINT_H_ + +#include + +#include "models/carousel_model.h" +#include "views/custom_widgets/rmv_carousel_item.h" + +/// Container class for the carousel's memory footprint component. +class RMVCarouselMemoryFootprint : public RMVCarouselItem +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVCarouselMemoryFootprint(const RMVCarouselConfig& config); + + /// Destructor. + virtual ~RMVCarouselMemoryFootprint(); + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param item Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + void paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) override; + + /// Set new data and update. + /// \param data The new data. + void SetData(const RMVCarouselData& data) override; + +private: + RMVCarouselMemoryFootprintData data_; ///< The data required by this item. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_MEMORY_FOOTPRINT_H_ diff --git a/source/frontend/views/custom_widgets/rmv_carousel_memory_types.cpp b/source/frontend/views/custom_widgets/rmv_carousel_memory_types.cpp new file mode 100644 index 0000000..5673419 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_memory_types.cpp @@ -0,0 +1,91 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV's carousel memory types widget +//============================================================================= + +#include "views/custom_widgets/rmv_carousel_memory_types.h" + +#include + +#include "rmt_print.h" + +static const int kBarWidth = 20; +static const int kEdgeMargin = 10; + +RMVCarouselMemoryTypes::RMVCarouselMemoryTypes(const RMVCarouselConfig& config) + : RMVCarouselItem(config) + , data_{} + , physical_heap_(false) +{ +} + +RMVCarouselMemoryTypes::~RMVCarouselMemoryTypes() +{ +} + +void RMVCarouselMemoryTypes::SetIsPhysicalHeap(bool is_physical_heap) +{ + physical_heap_ = is_physical_heap; +} + +void RMVCarouselMemoryTypes::paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) +{ + Q_UNUSED(item); + Q_UNUSED(widget); + + QString title_string; + HeapData* heap; + if (physical_heap_) + { + title_string = "Committed virtual memory"; + heap = data_.physical_heap; + } + else + { + title_string = "Requested virtual memory"; + heap = data_.preferred_heap; + } + + DrawCarouselBaseComponents(painter, title_string); + + int bar_length = config_.width - (2 * kEdgeMargin); + + DrawColoredHorizontalBarComponent(painter, + RmtGetHeapTypeNameFromHeapType(kRmtHeapTypeLocal), + heap[kRmtHeapTypeLocal].color, + kEdgeMargin, + 60, + bar_length, + kBarWidth, + heap[kRmtHeapTypeLocal].value, + heap[kRmtHeapTypeLocal].max, + true); + DrawColoredHorizontalBarComponent(painter, + RmtGetHeapTypeNameFromHeapType(kRmtHeapTypeInvisible), + heap[kRmtHeapTypeInvisible].color, + kEdgeMargin, + 130, + bar_length, + kBarWidth, + heap[kRmtHeapTypeInvisible].value, + heap[kRmtHeapTypeInvisible].max, + true); + DrawColoredHorizontalBarComponent(painter, + RmtGetHeapTypeNameFromHeapType(kRmtHeapTypeSystem), + heap[kRmtHeapTypeSystem].color, + kEdgeMargin, + 200, + bar_length, + kBarWidth, + heap[kRmtHeapTypeSystem].value, + heap[kRmtHeapTypeSystem].max, + true); +} + +void RMVCarouselMemoryTypes::SetData(const RMVCarouselData& data) +{ + data_ = data.memory_types_data; + update(); +} \ No newline at end of file diff --git a/source/frontend/views/custom_widgets/rmv_carousel_memory_types.h b/source/frontend/views/custom_widgets/rmv_carousel_memory_types.h new file mode 100644 index 0000000..2769078 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_memory_types.h @@ -0,0 +1,48 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's carousel memory types widget +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_MEMORY_TYPES_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_MEMORY_TYPES_H_ + +#include + +#include "models/carousel_model.h" +#include "views/custom_widgets/rmv_carousel_item.h" + +/// Container class for the carousel's memory types component. +class RMVCarouselMemoryTypes : public RMVCarouselItem +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVCarouselMemoryTypes(const RMVCarouselConfig& config); + + /// Destructor. + virtual ~RMVCarouselMemoryTypes(); + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param item Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + void paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) override; + + /// Should this item display the physical heap data. + /// \param is_physical_heap If true, show the physical heap. + void SetIsPhysicalHeap(bool is_physical_heap); + + /// Set new data and update. + /// \param data new data. + void SetData(const RMVCarouselData& data) override; + +private: + RMVCarouselMemoryTypesData data_; ///< The data required by this item. + bool physical_heap_; ///< If true, display physical heap data, otherwise preferred heap. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_MEMORY_TYPES_H_ diff --git a/source/frontend/views/custom_widgets/rmv_carousel_nav_button.cpp b/source/frontend/views/custom_widgets/rmv_carousel_nav_button.cpp new file mode 100644 index 0000000..8a0575b --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_nav_button.cpp @@ -0,0 +1,140 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV's carousel navigation button +//============================================================================= + +#include "views/custom_widgets/rmv_carousel_nav_button.h" + +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +static const QColor kDefaultColor = QColor(210, 210, 210); +static const QColor kHoveredColor = QColor(180, 180, 180); +static const QColor kPressedColor = QColor(150, 150, 150); + +RMVCarouselNavButton::RMVCarouselNavButton(int width, int height, bool left_direction) + : width_(width) + , height_(height) + , left_direction_(left_direction) + , hovered_(false) + , pressed_(false) +{ + setAcceptHoverEvents(true); +} + +RMVCarouselNavButton::~RMVCarouselNavButton() +{ +} + +QRectF RMVCarouselNavButton::boundingRect() const +{ + return QRectF(0, 0, width_, height_); +} + +QPolygonF RMVCarouselNavButton::GetTriangle(int width, int height, bool left_direction, double scaling_factor) +{ + const int triangle_height = 100 * scaling_factor; + const int center_pos_y = height / 2; + + QPolygonF triangle; + if (left_direction) + { + triangle << QPoint(width, center_pos_y - triangle_height / 2) << QPoint(width, center_pos_y + triangle_height / 2) << QPoint(0, center_pos_y); + } + else + { + triangle << QPoint(0, center_pos_y - triangle_height / 2) << QPoint(0, center_pos_y + triangle_height / 2) << QPoint(width, center_pos_y); + } + + return triangle; +} + +QPainterPath RMVCarouselNavButton::shape() const +{ + QPainterPath path; + + path.addPolygon(GetTriangle(width_, height_, left_direction_, ScalingManager::Get().Scaled(1.0))); + + return path; +} + +void RMVCarouselNavButton::paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) +{ + Q_UNUSED(item); + Q_UNUSED(widget); + + QColor arrow_color = kDefaultColor; + if (pressed_) + { + arrow_color = kPressedColor; + } + else if (hovered_) + { + arrow_color = kHoveredColor; + } + + painter->setPen(Qt::NoPen); + painter->setBrush(arrow_color); + painter->setRenderHint(QPainter::Antialiasing); + painter->drawPolygon(GetTriangle(width_, height_, left_direction_, ScalingManager::Get().Scaled(1.0))); +} + +void RMVCarouselNavButton::UpdateDimensions(int width, int height) +{ + width_ = width; + height_ = height; +} + +void RMVCarouselNavButton::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + + setCursor(Qt::PointingHandCursor); + + hovered_ = true; + + update(); +} + +void RMVCarouselNavButton::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + + hovered_ = false; + + update(); +} + +void RMVCarouselNavButton::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + pressed_ = true; + + emit PressedButton(left_direction_); + + update(); +} + +void RMVCarouselNavButton::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + pressed_ = false; + + update(); +} + +int32_t RMVCarouselNavButton::ScaledHeight() const +{ + return ScalingManager::Get().Scaled(height_); +} + +int32_t RMVCarouselNavButton::ScaledWidth() const +{ + return ScalingManager::Get().Scaled(width_); +} diff --git a/source/frontend/views/custom_widgets/rmv_carousel_nav_button.h b/source/frontend/views/custom_widgets/rmv_carousel_nav_button.h new file mode 100644 index 0000000..8458acf --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_nav_button.h @@ -0,0 +1,88 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's carousel navigation button +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_NAV_BUTTON_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_NAV_BUTTON_H_ + +#include + +/// Container class for the carousel's L/R nav buttons. +class RMVCarouselNavButton : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param width The width of the button. + /// \param height The height of the button. + /// \param left_direction If true, the button points to the left. + RMVCarouselNavButton(int width, int height, bool leftDirection); + + /// Destructor. + virtual ~RMVCarouselNavButton(); + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's bounding shape for this item. + /// \return The item's QPainterPath. + virtual QPainterPath shape() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param item Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + void paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) Q_DECL_OVERRIDE; + + /// Mouse hover over event. + /// \param event the QGraphicsSceneHoverEvent. + void hoverMoveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse hover leave event. + /// \param event the QGraphicsSceneHoverEvent. + void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse press event. + /// \param event the QGraphicsSceneMouseEvent. + void mousePressEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Mouse release event. + /// \param event the QGraphicsSceneMouseEvent. + void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Set dimensions of this widget. + /// \param width The width. + /// \param height The height. + void UpdateDimensions(int width, int height); + +signals: + /// Signal fired when a button is pressed. + /// \param left_direction Was the left button pressed. + void PressedButton(bool left_direction); + +private: + /// Build a polygon that represents a triangle. + /// \return The triangle as a QPolygonF type. + static QPolygonF GetTriangle(int width, int height, bool left_direction, double scaling_factor); + + /// Get scaled height. + /// \return The scaled height. + int32_t ScaledHeight() const; + + /// Get scaled width. + /// \return The scaled width. + int32_t ScaledWidth() const; + + int width_; ///< Widget width. + int height_; ///< Widget height. + bool left_direction_; ///< Whether it's a left arrow or not. + bool hovered_; ///< Is the mouse currently hovered over this widget. + bool pressed_; ///< Has the mouse clicked this widget. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_NAV_BUTTON_H_ diff --git a/source/frontend/views/custom_widgets/rmv_carousel_resource_types.cpp b/source/frontend/views/custom_widgets/rmv_carousel_resource_types.cpp new file mode 100644 index 0000000..3cd051a --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_resource_types.cpp @@ -0,0 +1,135 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV's carousel resource types widget +//============================================================================= + +#include "views/custom_widgets/rmv_carousel_resource_types.h" + +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "util/rmv_util.h" +#include "util/string_util.h" +#include "views/colorizer.h" + +static const int kLegendStartOffset = 44; +static const int kLegendHeight = 26; +static const int kEdgeMargin = 5; +static const int kIconWidth = 20; +static const int kNumResourcesToShow = 6; +static const int kTextGap = 5; /// The gap between the text desc + +RMVCarouselResourceTypes::RMVCarouselResourceTypes(const RMVCarouselConfig& config) + : RMVCarouselItem(config) + , data_{} +{ +} + +RMVCarouselResourceTypes::~RMVCarouselResourceTypes() +{ +} + +void RMVCarouselResourceTypes::DrawCarouselMemoryUsageLegend(QPainter* painter, + uint32_t y_offset, + const QString& resource_name, + const QColor& resource_color, + int32_t usage_amount) +{ + static const int kFontPixelSize = 12; + static const int kTextOffset = ScalingManager::Get().Scaled((kIconWidth + kFontPixelSize) / 2); + + y_offset = ScalingManager::Get().Scaled((int)y_offset); + uint32_t margin_length = ScalingManager::Get().Scaled(kEdgeMargin); + + QFont font = painter->font(); + font.setBold(false); + font.setPixelSize(ScalingManager::Get().Scaled(kFontPixelSize)); + + painter->setFont(font); + painter->setPen(Qt::NoPen); + painter->setBrush(resource_color); + painter->drawRect(margin_length, y_offset, ScalingManager::Get().Scaled(kIconWidth), ScalingManager::Get().Scaled(kIconWidth)); + + const int text_pos_x = (2 * margin_length) + ScalingManager::Get().Scaled(kIconWidth); + QString usage_description = resource_name; + if (usage_description.at(usage_description.size() - 1) != 's') + { + usage_description += 's'; + } + + painter->setPen(Qt::black); + painter->drawText(text_pos_x, y_offset + kTextOffset, usage_description); + + QString amount = "("; + + if (config_.data_type == kCarouselDataTypeDelta) + { + if (usage_amount > 0) + { + painter->setPen(rmv_util::GetDeltaChangeColor(kDeltaChangeIncrease)); + amount += "+"; + } + else if (usage_amount < 0) + { + painter->setPen(rmv_util::GetDeltaChangeColor(kDeltaChangeDecrease)); + } + else + { + painter->setPen(rmv_util::GetDeltaChangeColor(kDeltaChangeNone)); + } + } + + amount += rmv::string_util::LocalizedValue(usage_amount) + ")"; + + const int description_length = QtCommon::QtUtils::GetPainterTextWidth(painter, usage_description); + + painter->drawText(text_pos_x + description_length + ScalingManager::Get().Scaled(kTextGap), y_offset + kTextOffset, amount); +} + +void RMVCarouselResourceTypes::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + DrawCarouselBaseComponents(painter, "Resource types"); + + int bar_offset = (config_.width / 2); + int bar_length = (config_.width / 2) - kEdgeMargin; + int y_pos = 0; + + // Display the most abundant resource types + for (int i = 0; i < kNumResourcesToShow; i++) + { + y_pos = kLegendStartOffset + (i * kLegendHeight); + RmtResourceUsageType usage_type = data_.usage_map[i].usage_type; + + DrawCarouselMemoryUsageLegend(painter, + y_pos, + rmv::string_util::GetResourceUsageString(usage_type), + Colorizer::GetResourceUsageColor(usage_type), + data_.usage_map[i].usage_amount); + DrawHorizontalBarComponent(painter, "", bar_offset, y_pos, bar_length, kIconWidth, data_.usage_map[i].usage_amount, data_.usage_maximum, false); + } + + // Total up all the other resources + int32_t other_amount = 0; + for (int i = kNumResourcesToShow; i < kRmtResourceUsageTypeCount; i++) + { + other_amount += data_.usage_map[i].usage_amount; + } + + // Show the other resources + y_pos = kLegendStartOffset + (kNumResourcesToShow * kLegendHeight); + DrawCarouselMemoryUsageLegend(painter, y_pos, "Other", Colorizer::GetResourceUsageColor(kRmtResourceUsageTypeFree), other_amount); + DrawHorizontalBarComponent(painter, "", bar_offset, y_pos, bar_length, kIconWidth, other_amount, data_.usage_maximum, false); +} + +void RMVCarouselResourceTypes::SetData(const RMVCarouselData& data) +{ + data_ = data.resource_types_data; + update(); +} diff --git a/source/frontend/views/custom_widgets/rmv_carousel_resource_types.h b/source/frontend/views/custom_widgets/rmv_carousel_resource_types.h new file mode 100644 index 0000000..2ac9132 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_carousel_resource_types.h @@ -0,0 +1,51 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's carousel resource types widget +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_RESOURCE_TYPES_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_RESOURCE_TYPES_H_ + +#include + +#include "models/carousel_model.h" +#include "views/custom_widgets/rmv_carousel_item.h" + +/// Container class for the carousel's resource types component. +class RMVCarouselResourceTypes : public RMVCarouselItem +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVCarouselResourceTypes(const RMVCarouselConfig& config); + + /// Destructor. + virtual ~RMVCarouselResourceTypes(); + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param option Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + /// Update the data for this item. + /// \param data new data. + void SetData(const RMVCarouselData& data) override; + +private: + /// Helper function to draw color key for a memory usage. + /// \param painter Rhe Qt painter. + /// \param y_offset How far down should this get drawn. + /// \param resource_name The resource name. + /// \param resource_color The resource color. + /// \param usage_amount the data. + void DrawCarouselMemoryUsageLegend(QPainter* painter, uint32_t y_offset, const QString& resource_name, const QColor& resource_color, int32_t usage_amount); + + RMVCarouselResourceTypesData data_; ///< The model data for this carousel item. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CAROUSEL_RESOURCE_TYPES_H_ diff --git a/source/frontend/views/custom_widgets/rmv_clickable_table_view.cpp b/source/frontend/views/custom_widgets/rmv_clickable_table_view.cpp new file mode 100644 index 0000000..0ee6c7e --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_clickable_table_view.cpp @@ -0,0 +1,33 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a clickable table view. Extend the ScaledTableView +/// and allow the mouse cursor to change to a hand cursor if the table elements +/// can be clicked on. +//============================================================================= + +#include "views/custom_widgets/rmv_clickable_table_view.h" + +#include + +RMVClickableTableView::RMVClickableTableView(QWidget* parent) + : ScaledTableView(parent) +{ +} + +RMVClickableTableView::~RMVClickableTableView() +{ +} + +void RMVClickableTableView::enterEvent(QEvent* event) +{ + Q_UNUSED(event); + qApp->setOverrideCursor(Qt::PointingHandCursor); +} + +void RMVClickableTableView::leaveEvent(QEvent* event) +{ + Q_UNUSED(event); + qApp->restoreOverrideCursor(); +} diff --git a/source/frontend/views/custom_widgets/rmv_clickable_table_view.h b/source/frontend/views/custom_widgets/rmv_clickable_table_view.h new file mode 100644 index 0000000..2952352 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_clickable_table_view.h @@ -0,0 +1,34 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a clickable table view. Extend the ScaledTableView +/// and allow the mouse cursor to change to a hand cursor if the table elements +/// can be clicked on. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_CLICKABLE_TABLE_VIEW_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_CLICKABLE_TABLE_VIEW_H_ + +#include "qt_common/custom_widgets/scaled_table_view.h" + +/// Container class for the clickable table view. +class RMVClickableTableView : public ScaledTableView +{ +public: + /// Constructor + explicit RMVClickableTableView(QWidget* parent = nullptr); + + /// Destructor + virtual ~RMVClickableTableView(); + + /// Overridden Qt enterEvent. + /// \param event The event object. + void enterEvent(QEvent* event) override; + + /// Overridden Qt leaveEvent. + /// \param event The event object. + void leaveEvent(QEvent* event) override; +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_CLICKABLE_TABLE_VIEW_H_ diff --git a/source/frontend/views/custom_widgets/rmv_color_picker_widget.cpp b/source/frontend/views/custom_widgets/rmv_color_picker_widget.cpp new file mode 100644 index 0000000..9f13cd3 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_color_picker_widget.cpp @@ -0,0 +1,236 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a color picker widget. +//============================================================================= + +#include "views/custom_widgets/rmv_color_picker_widget.h" + +#include +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +const static QString kDefaultPaletteString("#000,#111,#222,#333,#444,#555,#666,#777,#888,#999,#AAA,#BBB,#CCC,#DDD,#EEE,#FFF"); +static const int kDefaultPaletteSize = 16; +static const int kDefaultPaletteColumns = 4; +static const int kDefaultPaletteRows = 4; +static const int kDefaultButtonDimension = 60; + +RMVColorPickerWidget::RMVColorPickerWidget(QWidget* parent) + : QWidget(parent) + , palette_(kDefaultPaletteSize) + , grid_layout_(this) + , button_group_(this) + , grid_row_count_(0) + , grid_column_count_(0) +{ + // Setup grid layout + grid_layout_.setSpacing(0); + grid_layout_.setContentsMargins(0, 0, 0, 0); + setSizePolicy(QSizePolicy::Policy::Fixed, QSizePolicy::Policy::Fixed); + + // Initial dimensions + SetRowAndColumnCount(kDefaultPaletteRows, kDefaultPaletteColumns); + SetPalette(kDefaultPaletteString); + + // Set up signals/slots + connect(&button_group_, SIGNAL(buttonClicked(int)), this, SLOT(ButtonClicked(int))); + + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &QWidget::updateGeometry); +} + +RMVColorPickerWidget::~RMVColorPickerWidget() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &QWidget::updateGeometry); +} + +void RMVColorPickerWidget::SetRowAndColumnCount(unsigned int rows, unsigned int columns) +{ + // Store dimension values + grid_row_count_ = rows; + grid_column_count_ = columns; + + // Generate a new set of buttons + GenerateButtons(); +} + +QColor RMVColorPickerWidget::GetSelectedColor() +{ + return palette_.GetColor(button_group_.checkedId()); +} + +int RMVColorPickerWidget::GetSelectedPaletteId() +{ + return button_group_.checkedId(); +} + +ColorPalette RMVColorPickerWidget::GetPalette() +{ + return palette_; +} + +void RMVColorPickerWidget::Select(int id) +{ + button_group_.button(id)->setChecked(true); +} + +void RMVColorPickerWidget::SetPalette(const ColorPalette& palette) +{ + palette_ = palette; + + SetButtonColors(); + update(); + + // Indicate the palette has changed + emit PaletteChanged(palette_); +} + +void RMVColorPickerWidget::GenerateButtons() +{ + // Delete any previous buttons + while (QLayoutItem* item = grid_layout_.takeAt(0)) + { + if (item->widget() != nullptr) + { + delete item->widget(); + } + + delete item; + } + + // Button size policy + QSizePolicy size_policy(QSizePolicy::Fixed, QSizePolicy::Fixed); + size_policy.setHorizontalStretch(0); + size_policy.setVerticalStretch(0); + + // Generate a button for each grid spot + for (int i = 0; i < grid_row_count_; i++) + { + for (int j = 0; j < grid_column_count_; j++) + { + PickerButton* button = new PickerButton(this); + size_policy.setHeightForWidth(true); + button->setSizePolicy(size_policy); + button->setCheckable(true); + button->setCursor(QCursor(Qt::PointingHandCursor)); + + // Add button to containers + button_group_.addButton(button); + grid_layout_.addWidget(button, i, j, 1, 1); + } + } + + // Initialize button colors + SetButtonColors(); +} + +void RMVColorPickerWidget::SetButtonColors() +{ + // Set id and color for all buttons + for (int button_id = 0; button_id < button_group_.buttons().size(); button_id++) + { + PickerButton* button = static_cast(button_group_.buttons()[button_id]); + button_group_.setId(button, button_id); + + // Set button color + button->SetColor(palette_.GetColor(button_id)); + } +} + +void RMVColorPickerWidget::ButtonClicked(int button_id) +{ + emit ColorSelected(button_id, palette_.GetColor(button_id)); +} + +RMVColorPickerWidget::PickerButton::PickerButton(QWidget* parent) + : QPushButton(parent) +{ + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &QPushButton::updateGeometry); +} + +RMVColorPickerWidget::PickerButton::~PickerButton() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &QPushButton::updateGeometry); +} + +void RMVColorPickerWidget::PickerButton::SetColor(const QColor& color) +{ + button_color_ = color; +} + +int RMVColorPickerWidget::PickerButton::heightForWidth(int width) const +{ + return width; +} + +QSize RMVColorPickerWidget::PickerButton::sizeHint() const +{ + return ScalingManager::Get().Scaled(minimumSizeHint()); +} + +QSize RMVColorPickerWidget::PickerButton::minimumSizeHint() const +{ + return QSize(kDefaultButtonDimension, kDefaultButtonDimension); +} + +void RMVColorPickerWidget::PickerButton::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + ScalingManager& sm = ScalingManager::Get(); + QPainter painter(this); + + const int pos_adj = sm.Scaled(1); + const int size_adj = pos_adj * 2; + const int outline_width = sm.Scaled(2); + + // Rectangle used for drawing button and its border + QRect r1(pos_adj, pos_adj, this->size().width() - size_adj, this->size().height() - size_adj); + + const int left = r1.left() + outline_width; + const int right = left + r1.width() - (outline_width * 2); + const int top = r1.top() + outline_width; + const int bottom = top + r1.height() - (outline_width * 2); + const int center_x = (left + right) / 2; + const int center_y = (top + bottom) / 2; + + if (this->isChecked() || this->underMouse()) + { + // Fill rect with black to form border + painter.fillRect(r1, QBrush(Qt::black)); + + QPolygon polygon; + QPolygon polygon2; + + // Determine polygon shapes based on button status + if (this->isChecked()) + { + polygon << QPoint(center_x, top) << QPoint(right, top) << QPoint(right, bottom) << QPoint(left, bottom) << QPoint(left, center_y); + + polygon2 << QPoint(center_x, top) << QPoint(right - 1, top) << QPoint(right - 1, bottom - 1) << QPoint(left, bottom - 1) << QPoint(left, center_y); + } + else if (this->underMouse()) + { + polygon << QPoint(left, top) << QPoint(right, top) << QPoint(right, bottom) << QPoint(left, bottom); + + polygon2 << QPoint(left, top) << QPoint(right - 1, top) << QPoint(right - 1, bottom - 1) << QPoint(left, bottom - 1); + } + + // Draw colored polygon + QPainterPath path; + path.addPolygon(polygon); + painter.fillPath(path, QBrush(button_color_)); + + // Draw white interior border + painter.setPen(QPen(Qt::white, 1)); + painter.drawPolygon(polygon2); + } + else + { + // Fill rect with black to form border + painter.fillRect(r1, button_color_); + } +} diff --git a/source/frontend/views/custom_widgets/rmv_color_picker_widget.h b/source/frontend/views/custom_widgets/rmv_color_picker_widget.h new file mode 100644 index 0000000..5bb6f44 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_color_picker_widget.h @@ -0,0 +1,129 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a color picker widget. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_COLOR_PICKER_WIDGET_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_COLOR_PICKER_WIDGET_H_ + +#include +#include +#include +#include +#include + +#include "qt_common/utils/color_palette.h" + +/// Support for RMV's color picker widget. +class RMVColorPickerWidget : public QWidget +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The color picker widget's parent. + explicit RMVColorPickerWidget(QWidget* parent = nullptr); + + /// Destructor. + virtual ~RMVColorPickerWidget(); + + /// Set the number of button rows/columns. + /// \param rows The button row count. + /// \param columns The button column count. + void SetRowAndColumnCount(unsigned int rows, unsigned int columns); + + /// Get the currently selected color. + /// \return The currently selected color on the picker. + QColor GetSelectedColor(); + + /// Get the palette id of the currently selected color. + /// \return The palette id of the currently selected color on the picker. + int GetSelectedPaletteId(); + + /// Get the color palette used by this color picker. + /// \return The color palette currently in use by this picker. + ColorPalette GetPalette(); + + /// Set the selected color on the picker given a palette id. + /// \param id The palette id of the color to select. + void Select(int id); + + /// Set the palette for this picker to use. + /// \param palette The ColorPalette object this picker will use. + void SetPalette(const ColorPalette& palette); + +signals: + /// Signals that a color has been selected. + /// \param palette_id The id of the color that has been changed in the palette + /// \param color The color that corresponds to the id + void ColorSelected(int palette_id, QColor color); + + /// Signals that the palette has changed. + /// \param palette The new palette + void PaletteChanged(const ColorPalette& palette); + +private: + ColorPalette palette_; ///< Color palette used by this picker. + QGridLayout grid_layout_; ///< Grid layout used to layout button array. + QButtonGroup button_group_; ///< Button group used to group all color buttons together. + int grid_row_count_; ///< Grid row count. + int grid_column_count_; ///< Grid column count. + + /// Generate and arrange the collection of buttons that make up this color + /// picker. + void GenerateButtons(); + + /// Correctly set the color of all the buttons using colors from the palette. + void SetButtonColors(); + + class PickerButton; + +private slots: + + /// Slot that is called when one of the buttons is clicked. + /// \param button_id The id of the button that is clicked. + void ButtonClicked(int button_id); +}; + +/// Helper class for color picker - allows custom button painting. +class RMVColorPickerWidget::PickerButton : public QPushButton +{ +public: + /// Constructor. + /// \param parent The parent widget. + explicit PickerButton(QWidget* parent = nullptr); + + /// Destructor. + virtual ~PickerButton(); + + /// Set the color of the button. + /// \param color The button color + void SetColor(const QColor& color); + + /// Provides the desired height for the specified width which will + /// keep the button square. + /// \param width The pixel width of this widget. + /// \return The desired pixel height of this width. + virtual int heightForWidth(int width) const Q_DECL_OVERRIDE; + + /// Size hint, which is the scaled default button dimensions. + /// \return The scaled size hint. + virtual QSize sizeHint() const Q_DECL_OVERRIDE; + + /// Minimum size hint, which is the unscaled default button dimensions. + /// \return The mimium size hint. + virtual QSize minimumSizeHint() const Q_DECL_OVERRIDE; + +protected: + /// Picker button paint event - overrides button draw function to implement + /// custom drawing functionality. + /// \param event Qt paint event. + virtual void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE; + +private: + QColor button_color_; ///< Color of this button. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_COLOR_PICKER_WIDGET_H_ diff --git a/source/frontend/views/custom_widgets/rmv_colored_checkbox.cpp b/source/frontend/views/custom_widgets/rmv_colored_checkbox.cpp new file mode 100644 index 0000000..333d966 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_colored_checkbox.cpp @@ -0,0 +1,223 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a widget that implements a custom check box. +//============================================================================= + +#include "views/custom_widgets/rmv_colored_checkbox.h" + +#include +#include +#include +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +RMVColoredCheckbox::RMVColoredCheckbox(QWidget* parent) + : QCheckBox(parent) + , multi_color_(false) + , button_text_ratio_(1.0) +{ + setMouseTracking(true); + setChecked(false); + setCursor(Qt::PointingHandCursor); + + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &RMVColoredCheckbox::OnScaleFactorChanged); +} + +RMVColoredCheckbox::~RMVColoredCheckbox() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &RMVColoredCheckbox::OnScaleFactorChanged); +} + +void RMVColoredCheckbox::Initialize(bool checked, const QColor& primary_color, const QColor& secondary_color, bool multi_color) +{ + UpdatePrimaryColor(primary_color); + UpdateSecondaryColor(secondary_color); + UpdateMultiColorState(multi_color); + setChecked(checked); +} + +void RMVColoredCheckbox::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QStylePainter painter(this); + + QFontMetricsF font_metrics = painter.fontMetrics(); + + // Example sizes: + // height is 20 px + // switch height is 20px + // white space height is 16px + // colored dot is 12 px + + // switch width is 40px + // white space width is 36px + // colored dot is 12 px + + const qreal switch_height = GetSwitchHeight(font_metrics); + const qreal switch_width = switch_height * 2; + const qreal half_height = switch_height / 2; + const qreal switch_radius = half_height; + + const qreal space_y_coord = switch_height * 0.1; + const qreal space_x_coord = switch_height * 0.1; + const qreal space_height = switch_height * 0.8; + const qreal space_width = switch_width * 0.9; + const qreal space_radius = space_height / 2; + + const qreal button_diameter = switch_height * 0.6; + const qreal button_y_coord = switch_height * 0.2; + const qreal button_x_coord_off = switch_height * 0.2; + const qreal button_x_coord_on = switch_width * 0.6; + + int scaled_padding = this->style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, nullptr, this); + + painter.setFont(QWidget::font()); + + painter.setRenderHint(QPainter::Antialiasing); + + painter.setPen(Qt::NoPen); + + // Set 'off' color based on enabled state. + Qt::GlobalColor default_color = Qt::black; + bool enabled = isEnabled(); + if (!enabled) + { + default_color = Qt::lightGray; + } + + // If not checked or disabled, draw grayed out and unchecked. + if (isChecked() == false || enabled == false) + { + // Draw switch + painter.setBrush(default_color); + QRectF outer_rect(0, 0, switch_width, switch_height); + painter.drawRoundedRect(outer_rect, switch_radius, switch_radius); + + // Draw space + painter.setBrush(Qt::white); + QRectF inner_rect(space_x_coord, space_y_coord, space_width, space_height); + painter.drawRoundedRect(inner_rect, space_radius, space_radius); + + // Draw button + painter.setBrush(default_color); + const QRectF button_rect = QRectF(button_x_coord_off, button_y_coord, button_diameter, button_diameter); + painter.drawEllipse(button_rect); + } + else + { + const QRectF outer_rect(0, 0, switch_width, switch_height); + + if (!multi_color_) + { + // Draw single color switch + painter.setBrush(primary_color_); + painter.drawRoundedRect(outer_rect, switch_radius, switch_radius); + } + else + { + // Stencil the area to be painted + painter.setBrush(Qt::black); + painter.drawRoundedRect(outer_rect, switch_radius, switch_radius); + + painter.setCompositionMode(QPainter::CompositionMode_Plus); + + // paint top half of switch + painter.setBrush(primary_color_); + painter.drawRect(0, 0, switch_width, half_height); + + // paint bottom half of switch + painter.setBrush(secondary_color_); + painter.drawRect(0, half_height, switch_width, ceil(half_height)); + } + + // Draw white button in the middle + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + + painter.setBrush(Qt::white); + const QRectF button_rect = QRectF(button_x_coord_on, button_y_coord, button_diameter, button_diameter); + painter.drawEllipse(button_rect); + } + + qreal text_height = font_metrics.capHeight(); + qreal text_base = half_height + (text_height / 2.0); + + painter.setPen(Qt::black); + painter.drawText(switch_width + scaled_padding, text_base, this->text()); +} + +void RMVColoredCheckbox::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); +} + +void RMVColoredCheckbox::mousePressEvent(QMouseEvent* event) +{ + Q_UNUSED(event); + setChecked(!isChecked()); + emit Clicked(); + this->update(); +} + +QSize RMVColoredCheckbox::sizeHint() const +{ + QFontMetricsF font_metrics = fontMetrics(); + + // Height is defined by max of font height and scaled checkbox height + const qreal base_height = GetSwitchHeight(font_metrics); + int height = std::max(font_metrics.height(), base_height); + + // Width is defined by switch width (which is 2*height) plus kPaddingBetweenSwitchAndText, plus the width of the text. + int switch_width = base_height * 2; + qreal scaled_padding = this->style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, nullptr, this); + int text_width = font_metrics.width(this->text()); + + return QSize(switch_width + scaled_padding + text_width, height); +} + +void RMVColoredCheckbox::OnScaleFactorChanged() +{ + QtCommon::QtUtils::InvalidateFontMetrics(this); + this->updateGeometry(); +} + +void RMVColoredCheckbox::UpdateText(const QString& text) +{ + setText(text); + QtCommon::QtUtils::InvalidateFontMetrics(this); + this->updateGeometry(); +} + +void RMVColoredCheckbox::UpdatePrimaryColor(const QColor& color) +{ + primary_color_ = color; +} + +void RMVColoredCheckbox::UpdateSecondaryColor(const QColor& color) +{ + secondary_color_ = color; +} + +void RMVColoredCheckbox::UpdateMultiColorState(bool multi_color) +{ + multi_color_ = multi_color; +} + +void RMVColoredCheckbox::SetButtonTextRatio(qreal button_text_ratio) +{ + button_text_ratio_ = button_text_ratio; +} + +qreal RMVColoredCheckbox::GetButtonTextRatio() const +{ + return button_text_ratio_; +} + +qreal RMVColoredCheckbox::GetSwitchHeight(const QFontMetricsF& font_metrics) const +{ + return font_metrics.height() * button_text_ratio_; +} diff --git a/source/frontend/views/custom_widgets/rmv_colored_checkbox.h b/source/frontend/views/custom_widgets/rmv_colored_checkbox.h new file mode 100644 index 0000000..b33c46b --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_colored_checkbox.h @@ -0,0 +1,95 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header of a widget that implements a custom check box. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_COLORED_CHECKBOX_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_COLORED_CHECKBOX_H_ + +#include + +/// RMV-style colored checkbox that is aware of resize and mouse events. +class RMVColoredCheckbox : public QCheckBox +{ + Q_OBJECT + Q_PROPERTY(qreal button_text_ratio READ GetButtonTextRatio WRITE SetButtonTextRatio) + +public: + /// Constructor. + /// \param parent The parent widget. + explicit RMVColoredCheckbox(QWidget* parent); + + /// Destructor. + virtual ~RMVColoredCheckbox(); + + /// Initialize an RMVColoredCheckbox. + /// \param checked Whether the checkbox starts in the checked state or not. + /// \param primary_color Primary color theme. + /// \param secondary_color Secondary color theme. + /// \param multi_color True if this is a multi-color color checkbox. + void Initialize(bool checked, const QColor& primary_color, const QColor& secondary_color, bool multi_color = false); + + /// Update the text to be displayed next to the checkbox. + /// \param test The text to be displayed. + void UpdateText(const QString& text); + + /// Update the primary checkbox color. This is the color used in single-colored checkboxes. + /// \param color The primary color to use. + void UpdatePrimaryColor(const QColor& color); + + /// Update the secondary checkbox color. + /// \param color The primary color to use. + void UpdateSecondaryColor(const QColor& color); + + /// Should this checkbox be used a multi-color mode. + /// \param multi_color If true, set as multi-color, otherwise single color. + void UpdateMultiColorState(bool multi_color); + + /// Set the button-to-text ratio. + /// \param button_text_ratio The button to text ratio. + void SetButtonTextRatio(qreal button_text_ratio); + + /// Get the button-to-text ratio. + /// \return The button to text ratio. + qreal GetButtonTextRatio() const; + +signals: + /// Signal to indicate that the checkbox was clicked on. + void Clicked(); + +protected: + /// Provides a desired sizeHint that allows the text and bar to be visible. + /// \return The size hint. + virtual QSize sizeHint() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's paint for this widget. + /// \param paint_event The paint event. + virtual void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE; + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Implementation of Qt's mouse press event for this widget. + /// \param event The mouse press event. + virtual void mousePressEvent(QMouseEvent* event) Q_DECL_OVERRIDE; + +private slots: + /// Update checkbox as needed due to DPI scale factor changes. + void OnScaleFactorChanged(); + +private: + /// Get the switch height based on the font size. + /// \param font_metrics The font metrics for the font used by this widget. + /// \return The switch height. + qreal GetSwitchHeight(const QFontMetricsF& font_metrics) const; + + QColor primary_color_; ///< Primary color for checkbox. Either full color, or top half color if multicolor. + QColor secondary_color_; ///< Secondary color for checkbox. Either not used, or bottom half color if multicolor. + bool multi_color_; ///< When enabled, top half will be primary color, and bottom half will be secondary color. + qreal button_text_ratio_; ///< The button-to-text ratio ie how much bigger the button is relative to the button text. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_COLORED_CHECKBOX_H_ diff --git a/source/frontend/views/custom_widgets/rmv_delta_display.cpp b/source/frontend/views/custom_widgets/rmv_delta_display.cpp new file mode 100644 index 0000000..953bfc4 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_delta_display.cpp @@ -0,0 +1,138 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a graphics view that data delta +//============================================================================= + +#include "views/custom_widgets/rmv_delta_display.h" + +#include +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +static const qreal kDeltaDisplayWidth = 200.0; +static const qreal kDeltaDisplayHeight = 20.0; + +RMVDeltaDisplay::RMVDeltaDisplay(QWidget* parent) + : QGraphicsView(parent) +{ + setMouseTracking(true); + + setFrameStyle(QFrame::NoFrame); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + verticalScrollBar()->blockSignals(true); + horizontalScrollBar()->blockSignals(true); + + setFixedHeight(kHeapDeltaWidgetHeight); + + title_.setPlainText("Title"); + title_.setPos(0, 0); + + QFont font = title_.font(); + font.setBold(true); + title_.setFont(font); + scene_.addItem(&title_); + + UpdateDimensions(); + + setScene(&scene_); +} + +RMVDeltaDisplay::~RMVDeltaDisplay() +{ +} + +void RMVDeltaDisplay::Init(const QString& title, const QVector& items, float width_scaler) +{ + title_.setPlainText(title); + + for (int i = 0; i < deltas_.size(); i++) + { + RMVDeltaDisplayWidget* widget = deltas_[i].widget; + if (widget != nullptr) + { + scene_.removeItem(widget); + delete widget; + } + QGraphicsTextItem* description = deltas_[i].description; + if (description != nullptr) + { + scene_.removeItem(description); + delete description; + } + } + + deltas_.clear(); + + const double scale_factor = ScalingManager::Get().Scaled(1.0); + const double y_base_pos = title.isEmpty() == true ? 12.0 : 25.0; + const double display_width = kDeltaDisplayWidth * width_scaler; + + for (int i = 0; i < items.size(); i++) + { + DeltaComponent deltaComponent = {}; + deltaComponent.item_data = items[i]; + + qreal x_pos = static_cast(i) * display_width * scale_factor; + + deltaComponent.description = new QGraphicsTextItem(); + deltaComponent.description->setPlainText(items[i].name); + deltaComponent.description->setPos(x_pos, y_base_pos * scale_factor); + scene_.addItem(deltaComponent.description); + + RMVDeltaDisplayWidgetConfig config; + config.width = display_width; + config.height = kDeltaDisplayHeight; + config.font = GetFont(); + + deltaComponent.widget = new RMVDeltaDisplayWidget(config); + deltaComponent.widget->setPos(x_pos, (y_base_pos + 25.0) * scale_factor); + scene_.addItem(deltaComponent.widget); + + deltas_.push_back(deltaComponent); + } +} + +void RMVDeltaDisplay::UpdateItem(const DeltaItem& item) +{ + for (int32_t i = 0; i < deltas_.size(); i++) + { + if (item.name.compare(deltas_[i].item_data.name) == 0) + { + deltas_[i].description->setPlainText(item.name); + deltas_[i].widget->UpdateDataType(item.type); + deltas_[i].widget->UpdateDataValueNum(item.value_num); + deltas_[i].widget->UpdateDataValueStr(item.value_string); + deltas_[i].widget->UpdateDataCustomColor(item.custom_color); + deltas_[i].widget->UpdateDataGraphic(item.graphic); + + break; + } + } +} + +void RMVDeltaDisplay::resizeEvent(QResizeEvent* event) +{ + QGraphicsView::resizeEvent(event); + + UpdateDimensions(); +} + +void RMVDeltaDisplay::UpdateDimensions() +{ + scene_.setSceneRect(0, 0, width(), height()); +} + +QFont RMVDeltaDisplay::GetFont() const +{ + const double scaling_factor = ScalingManager::Get().Scaled(1.0); + + QFont font; + font.setPixelSize(11 * scaling_factor); + + return font; +} diff --git a/source/frontend/views/custom_widgets/rmv_delta_display.h b/source/frontend/views/custom_widgets/rmv_delta_display.h new file mode 100644 index 0000000..93cfb6f --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_delta_display.h @@ -0,0 +1,99 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a graphics view that implements data delta +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_DELTA_DISPLAY_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_DELTA_DISPLAY_H_ + +#include +#include + +#include "views/custom_widgets/rmv_delta_display_widget.h" + +static const int kHeapDeltaWidgetHeight = 80; + +/// Generic structure designed to hold either +/- data for an item. +struct DeltaItem +{ + DeltaItem() + : type(kDeltaValueTypeString) + , graphic(false) + , value_num(0) + { + } + + DeltaItem(QString in_name, DeltaValueType in_type, bool in_graphic, int64_t in_value_num, QString in_value_string, QColor in_custom_color) + : name(in_name) + , type(in_type) + , graphic(in_graphic) + , value_num(in_value_num) + , value_string(in_value_string) + , custom_color(in_custom_color) + { + } + + QString name; ///< Component name. + DeltaValueType type; ///< Component type (string or value). + bool graphic; ///< Should include a rendered graphic. + int64_t value_num; ///< The value (numeric). + QString value_string; ///< The value (string). + QColor custom_color; ///< Uses a non-standard color. +}; + +/// Encapsulates data used to render an individual delta component. +struct DeltaComponent +{ + DeltaComponent() + : description(nullptr) + , widget(nullptr) + { + } + + DeltaItem item_data; ///< Backing data. + QGraphicsTextItem* description; ///< Qt item to render its text. + RMVDeltaDisplayWidget* widget; ///< Custom Qt widget. +}; + +/// Graphics view that is aware of resize and mouse events. +class RMVDeltaDisplay : public QGraphicsView +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The parent widget. + explicit RMVDeltaDisplay(QWidget* parent); + + /// Destructor. + virtual ~RMVDeltaDisplay(); + + /// Capture a resize event. + /// \param event The resize event. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Add a new item. + /// \param title The delta title. + /// \param item The new item. + /// \param width_scaler How much to scale spacing by. + void Init(const QString& title, const QVector& items, float width_scaler = 1.0F); + + /// Update a single item based on name. + /// \param item The item to update. + void UpdateItem(const DeltaItem& item); + +private: + /// Update view dimensions. + void UpdateDimensions(); + + /// Get the font used by the checkbox. + QFont GetFont() const; + + QGraphicsScene scene_; ///< The scene containing the delta objects. + QGraphicsTextItem title_; ///< The title of these delta objects. + QVector deltas_; ///< The list of delta components. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_DELTA_DISPLAY_H_ diff --git a/source/frontend/views/custom_widgets/rmv_delta_display_widget.cpp b/source/frontend/views/custom_widgets/rmv_delta_display_widget.cpp new file mode 100644 index 0000000..c366ea9 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_delta_display_widget.cpp @@ -0,0 +1,142 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a delta display widget +//============================================================================= + +#include "views/custom_widgets/rmv_delta_display_widget.h" + +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "util/rmv_util.h" +#include "util/string_util.h" + +RMVDeltaDisplayWidget::RMVDeltaDisplayWidget(const RMVDeltaDisplayWidgetConfig& config) + : config_(config) +{ +} + +RMVDeltaDisplayWidget::~RMVDeltaDisplayWidget() +{ +} + +QRectF RMVDeltaDisplayWidget::boundingRect() const +{ + return QRectF(0, 0, ScalingManager::Get().Scaled(config_.width), ScalingManager::Get().Scaled(config_.height)); +} + +void RMVDeltaDisplayWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) +{ + Q_UNUSED(item); + Q_UNUSED(widget); + + painter->setFont(config_.font); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->setPen(Qt::NoPen); + + int x_pos = ScalingManager::Get().Scaled(5); + + if (config_.graphic == true) + { + if (config_.type == kDeltaValueTypeString) + { + painter->setBrush(config_.custom_color); + painter->drawEllipse(QRect(0, 0, ScalingManager::Get().Scaled(20), ScalingManager::Get().Scaled(20))); + } + else + { + if (config_.value_num > 0) + { + painter->setBrush(rmv_util::GetDeltaChangeColor(kDeltaChangeIncrease)); + + QPolygonF polygon; + polygon << QPoint(0, ScalingManager::Get().Scaled(20)) << QPoint(ScalingManager::Get().Scaled(10), 0) + << QPoint(ScalingManager::Get().Scaled(20), ScalingManager::Get().Scaled(20)); + painter->drawPolygon(polygon); + } + else if (config_.value_num < 0) + { + painter->setBrush(rmv_util::GetDeltaChangeColor(kDeltaChangeDecrease)); + + QPolygonF polygon; + polygon << QPoint(0, 0) << QPoint(ScalingManager::Get().Scaled(10), ScalingManager::Get().Scaled(20)) + << QPoint(ScalingManager::Get().Scaled(20), 0); + painter->drawPolygon(polygon); + } + else + { + painter->setBrush(rmv_util::GetDeltaChangeColor(kDeltaChangeNone)); + painter->drawEllipse(QRect(0, 0, ScalingManager::Get().Scaled(20), ScalingManager::Get().Scaled(20))); + } + } + + x_pos += ScalingManager::Get().Scaled(config_.height); + } + + QString text = "N/A"; + + if (config_.type == kDeltaValueTypeString) + { + text = config_.value_string; + } + else if (config_.type == kDeltaValueTypeValue) + { + text = rmv::string_util::LocalizedValue(config_.value_num); + } + else if (config_.type == kDeltaValueTypeValueLabeled) + { + text = rmv::string_util::LocalizedValueMemory(config_.value_num, false, false); + } + + painter->setPen(Qt::black); + painter->drawText(x_pos, ScalingManager::Get().Scaled(15), text); +} + +void RMVDeltaDisplayWidget::UpdateDimensions(int width, int height) +{ + config_.width = width; + config_.height = height; + + update(); +} + +void RMVDeltaDisplayWidget::UpdateDataType(DeltaValueType type) +{ + config_.type = type; + + update(); +} + +void RMVDeltaDisplayWidget::UpdateDataValueNum(int64_t value) +{ + config_.value_num = value; + + update(); +} + +void RMVDeltaDisplayWidget::UpdateDataValueStr(const QString& str) +{ + config_.value_string = str; + + update(); +} + +void RMVDeltaDisplayWidget::UpdateDataCustomColor(const QColor& color) +{ + config_.custom_color = color; + + update(); +} + +void RMVDeltaDisplayWidget::UpdateDataGraphic(bool graphic) +{ + config_.graphic = graphic; + + update(); +} diff --git a/source/frontend/views/custom_widgets/rmv_delta_display_widget.h b/source/frontend/views/custom_widgets/rmv_delta_display_widget.h new file mode 100644 index 0000000..0b0779c --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_delta_display_widget.h @@ -0,0 +1,98 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a delta display widget +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_DELTA_DISPLAY_WIDGET_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_DELTA_DISPLAY_WIDGET_H_ + +#include +#include + +/// Enum of delta data types. +enum DeltaValueType +{ + kDeltaValueTypeString, + kDeltaValueTypeValue, + kDeltaValueTypeValueLabeled, + + kDeltaValueTypeCount, +}; + +/// Configuration struct for a widget showing delta value. +struct RMVDeltaDisplayWidgetConfig +{ + RMVDeltaDisplayWidgetConfig() + : width(0) + , height(0) + , graphic(false) + , type(kDeltaValueTypeString) + , value_num(0) + { + } + + int width; ///< Widget width. + int height; ///< Widget height. + bool graphic; ///< Should render a graphic on the left. + QFont font; ///< Text font. + DeltaValueType type; ///< Which delta type (string or numeric). + int64_t value_num; ///< Value (numeric). + QString value_string; ///< Value (string). + QColor custom_color; ///< Render a non-standard color. +}; + +/// Container class for a widget designed to display delta +/- data +class RMVDeltaDisplayWidget : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVDeltaDisplayWidget(const RMVDeltaDisplayWidgetConfig& config); + + /// Destructor. + virtual ~RMVDeltaDisplayWidget(); + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param item Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) Q_DECL_OVERRIDE; + + /// Update dimensions. + /// \param width The width. + /// \param height The height. + void UpdateDimensions(int width, int height); + + /// Update data type. + /// \param type The type. + void UpdateDataType(DeltaValueType type); + + /// Update data value. + /// \param value The value. + void UpdateDataValueNum(int64_t value); + + /// Update data string. + /// \param str The string. + void UpdateDataValueStr(const QString& str); + + /// Update data color. + /// \param color The color. + void UpdateDataCustomColor(const QColor& str); + + /// Update data graphic. + /// \param graphic The graphic. + void UpdateDataGraphic(bool graphic); + +private: + RMVDeltaDisplayWidgetConfig config_; ///< Structure holding the data for this widget. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_DELTA_DISPLAY_WIDGET_H_ diff --git a/source/frontend/views/custom_widgets/rmv_heap_overview_memory_bar.cpp b/source/frontend/views/custom_widgets/rmv_heap_overview_memory_bar.cpp new file mode 100644 index 0000000..fdc3e50 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_heap_overview_memory_bar.cpp @@ -0,0 +1,136 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a heap overview memory bar +//============================================================================= + +#include "views/custom_widgets/rmv_heap_overview_memory_bar.h" + +#include +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" + +#include "util/constants.h" +#include "util/string_util.h" + +static const QColor kGrayProcessColor = QColor(128, 128, 128); +static const QColor kGrayOtherProcessColor = QColor(184, 184, 184); + +RMVHeapOverviewMemoryBar::RMVHeapOverviewMemoryBar(QWidget* parent) + : QWidget(parent) + , size_(0) + , extra_size_(0) + , max_size_(0) + , has_subscription_(false) + , subscription_status_(kRmtSegmentSubscriptionStatusUnderLimit) +{ +} + +RMVHeapOverviewMemoryBar::~RMVHeapOverviewMemoryBar() +{ +} + +QSize RMVHeapOverviewMemoryBar::sizeHint() const +{ + QSize size_hint = minimumSizeHint(); + + // Double the width so that there is room to draw the bar as well. + size_hint.rwidth() *= 2; + + return size_hint; +} + +QSize RMVHeapOverviewMemoryBar::minimumSizeHint() const +{ + QSize minimum_size = ScalingManager::Get().ScaledFontMetrics(font()).size(0, rmv::string_util::LocalizedValueMemory(size_, false, false)); + + return minimum_size; +} + +void RMVHeapOverviewMemoryBar::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QStylePainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + if (max_size_ > 0) + { + QRectF bound_rect = QRectF(0, 0, width(), height()); + + int w = bound_rect.width(); + int h = bound_rect.height(); + + QColor bar_color = kGrayProcessColor; + if (has_subscription_) + { + switch (subscription_status_) + { + case kRmtSegmentSubscriptionStatusOverLimit: + bar_color = rmv::kOverSubscribedColor; + break; + + case kRmtSegmentSubscriptionStatusUnderLimit: + bar_color = rmv::kUnderSubscribedColor; + break; + + case kRmtSegmentSubscriptionStatusCloseToLimit: + bar_color = rmv::kCloseToSubscribedColor; + break; + + default: + break; + } + } + + // calc length of first rectangle and paint it + uint64_t width = std::max(1, (size_ * w) / max_size_); + QRect memory_rect = QRect(0, 0, width, h); + painter.fillRect(memory_rect, bar_color); + + // draw the extra bar for other processes + if (extra_size_ > 0) + { + // calc extra rectangle + uint64_t width_2 = (extra_size_ * w) / max_size_; + painter.fillRect(width, 0, width_2, h, kGrayOtherProcessColor); + } + + // draw the amount of memory in this rectangle + // if bar is < 50% of the max, display the text string after the bar. Otherwise display it over the bar + if (((size_ * 100) / max_size_) < 50) + { + painter.setPen(Qt::black); + int offset = width + 3; + QRect text_rect(offset, 0, w - offset, h); + painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, rmv::string_util::LocalizedValueMemory(size_, false, false)); + } + else + { + painter.setPen(Qt::white); + painter.drawText(memory_rect, Qt::AlignCenter | Qt::AlignHCenter, rmv::string_util::LocalizedValueMemory(size_, false, false)); + } + } +} + +void RMVHeapOverviewMemoryBar::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); +} + +void RMVHeapOverviewMemoryBar::SetParameters(uint64_t size, + uint64_t extra_size, + uint64_t max_size, + bool has_subscription, + RmtSegmentSubscriptionStatus subscription_status) +{ + size_ = size; + extra_size_ = extra_size; + max_size_ = max_size; + has_subscription_ = has_subscription; + subscription_status_ = subscription_status; +} diff --git a/source/frontend/views/custom_widgets/rmv_heap_overview_memory_bar.h b/source/frontend/views/custom_widgets/rmv_heap_overview_memory_bar.h new file mode 100644 index 0000000..cdbc18e --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_heap_overview_memory_bar.h @@ -0,0 +1,67 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a heap overview memory bar +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_HEAP_OVERVIEW_MEMORY_BAR_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_HEAP_OVERVIEW_MEMORY_BAR_H_ + +#include + +#include "rmt_data_snapshot.h" + +/// Support for the heap overview memory bar widget. +class RMVHeapOverviewMemoryBar : public QWidget +{ +public: + /// Constructor. + /// \param parent Pointer to the parent widget. + explicit RMVHeapOverviewMemoryBar(QWidget* parent = nullptr); + + /// Destructor. + virtual ~RMVHeapOverviewMemoryBar(); + + /// Provides a desired sizeHint that allows the text and bar to be visible. + /// \return a size hint. + virtual QSize sizeHint() const Q_DECL_OVERRIDE; + + /// Provides a minimum sizeHint that ensures that the text should always be visible. + /// \return a minimum size hint. + virtual QSize minimumSizeHint() const Q_DECL_OVERRIDE; + + /// Set the parameters for the memory bar. The first 3 size parameters' units don't matter, so + /// long as they are all consistent (can be bytes, KB or pixels). + /// The bar can be visualized in 3 sections: + /// |xxxxxxxxxxxooooo | + /// The '|' respresent the total extent of the bar (max_size, below) + /// The 'x's represent the normal bar data (size, below) + /// The 'o's represent any extra data shown after the normal data (extra_data, below) + /// \param size The size of the bar showing data. + /// \param extra_size The size of the bar showing extra data. This is a value corresponding to just + /// the extra data, not the length of the bar from the start. + /// \param max_size The maximum size of the bar. + /// \param has_subscription Does this bar need to take into account memory subscription? + /// If so it will be colored based on its subscription status, otherwise it will be gray. + /// \param subscription_status The current subscription status showing if the memory is oversubscribed or not. + void SetParameters(uint64_t size, uint64_t extra_size, uint64_t max_size, bool has_subscription, RmtSegmentSubscriptionStatus subscription_status); + +protected: + /// Implementation of Qt's paint event. + /// \param event The paint event. + virtual void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE; + + /// Capture a resize event. + /// \param event The resize event. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + +private: + uint64_t size_; ///< Size of bar, in bytes. + uint64_t extra_size_; ///< Size of the hashed bit at the end. + uint64_t max_size_; ///< Max size of bar. Used to scale all bars. + bool has_subscription_; ///< Does this bar need subscription coloring. + RmtSegmentSubscriptionStatus subscription_status_; ///< Subscription(none, over, under, near). Will determine bar color. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_HEAP_OVERVIEW_MEMORY_BAR_H_ diff --git a/source/frontend/views/custom_widgets/rmv_resource_details.cpp b/source/frontend/views/custom_widgets/rmv_resource_details.cpp new file mode 100644 index 0000000..4319f6d --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_resource_details.cpp @@ -0,0 +1,152 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the resource details widget +//============================================================================= + +#include "views/custom_widgets/rmv_resource_details.h" + +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_data_set.h" +#include "rmt_assert.h" + +#include "util/string_util.h" + +RMVResourceDetails::RMVResourceDetails(const RMVResourceDetailsConfig& config) + : config_(config) +{ +} + +RMVResourceDetails::~RMVResourceDetails() +{ +} + +QRectF RMVResourceDetails::boundingRect() const +{ + return QRectF(0, 0, config_.width, config_.height); +} + +void RMVResourceDetails::paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) +{ + Q_UNUSED(item); + Q_UNUSED(widget); + + if (config_.resource_valid == true) + { + const int header_height = ScalingManager::Get().Scaled(20); + const int value_height = ScalingManager::Get().Scaled(40); + const int bar_width = ScalingManager::Get().Scaled(500); + const int bar_height = ScalingManager::Get().Scaled(15); + const int bar_y_pos = ScalingManager::Get().Scaled(30); + + const RmtVirtualAllocation* allocation = config_.resource.bound_allocation; + const uint64_t allocation_size = RmtVirtualAllocationGetSizeInBytes(allocation); + const RmtResourceIdentifier identifier = config_.resource.identifier; + const int64_t size_in_bytes = config_.resource.size_in_bytes; + const uint64_t offset = RmtResourceGetOffsetFromBoundAllocation(&config_.resource); + const RmtResourceUsageType usage_type = (identifier != 0) ? RmtResourceGetUsageType(&config_.resource) : kRmtResourceUsageTypeFree; + + int x_pos = 0; + + if (config_.allocation_thumbnail == true) + { + painter->setPen(Qt::black); + + // It's possible for a resource to not have a parent allocation, so handle it + if (allocation != nullptr) + { + if (identifier != 0) + { + painter->drawText( + 0, + header_height, + "Resource ID " + QString::number(identifier) + " in allocation " + QString::number(static_cast(allocation->base_address))); + } + else + { + painter->drawText(0, header_height, "Unbound resource in allocation " + QString::number(static_cast(allocation->base_address))); + } + } + else + { + painter->drawText(0, header_height, "Resource ID " + QString::number(identifier) + " has no parent allocation"); + } + + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(230, 230, 230)); + painter->drawRect(0, bar_y_pos, bar_width, bar_height); + + // Only draw the resource in the parent allocation if it has one + if (allocation != nullptr) + { + double pixels_per_byte = static_cast(bar_width) / static_cast(allocation_size); + const int32_t width = (int32_t)(size_in_bytes * pixels_per_byte); + + QPen pen; + pen.setBrush(Qt::black); + pen.setWidth(ScalingManager::Get().Scaled(1.0)); + painter->setPen(pen); + painter->setBrush(config_.colorizer->GetColor(allocation, &config_.resource)); + painter->drawRect((offset * pixels_per_byte) + ScalingManager::Get().Scaled(1.0), bar_y_pos, width, bar_height); + } + x_pos += ScalingManager::Get().Scaled(550); + } + + painter->setPen(Qt::black); + painter->drawText(x_pos, header_height, "Size"); + painter->drawText(x_pos, value_height, rmv::string_util::LocalizedValueMemory(size_in_bytes, false, false)); + + x_pos += ScalingManager::Get().Scaled(100); + painter->drawText(x_pos, header_height, "Offset"); + painter->drawText(x_pos, value_height, rmv::string_util::LocalizedValue(offset)); + + x_pos += ScalingManager::Get().Scaled(250); + painter->drawText(x_pos, header_height, "Usage"); + painter->drawText(x_pos, value_height, rmv::string_util::GetResourceUsageString(usage_type)); + } + else + { + const QString nothing_selected_string = "Nothing selected"; + const uint32_t string_length = QtCommon::QtUtils::GetPainterTextWidth(painter, nothing_selected_string); + const uint32_t x_pos = config_.width / 2 - string_length / 2; + + QFont font = painter->font(); + font.setPixelSize(ScalingManager::Get().Scaled(18)); + + painter->setFont(font); + painter->setPen(Qt::gray); + painter->drawText(x_pos, ScalingManager::Get().Scaled(30), nothing_selected_string); + } +} + +void RMVResourceDetails::UpdateResource(const RmtResource* resource) +{ + if (resource != nullptr) + { + // make a copy of the resource since in the case of unbounded resources + // the pointers will change when the heap overview size is changed + memcpy(&config_.resource, resource, sizeof(RmtResource)); + config_.resource_valid = true; + } + else + { + config_.resource_valid = false; + } + update(); +} + +const RmtResource* RMVResourceDetails::GetResource() const +{ + return &config_.resource; +} + +void RMVResourceDetails::UpdateDimensions(int width, int height) +{ + config_.width = width - 2; + config_.height = height - 2; +} diff --git a/source/frontend/views/custom_widgets/rmv_resource_details.h b/source/frontend/views/custom_widgets/rmv_resource_details.h new file mode 100644 index 0000000..051bb74 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_resource_details.h @@ -0,0 +1,73 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the resource details widget. This is the details +/// widget at the bottom of the resource overview that's shown when a resource +/// is clicked on. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_RESOURCE_DETAILS_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_RESOURCE_DETAILS_H_ + +#include + +#include "rmt_resource_list.h" +#include "rmt_virtual_allocation_list.h" + +#include "views/colorizer.h" + +static const int kResourceDetailsHeight = 50; + +/// Describes an allocation details widget. +struct RMVResourceDetailsConfig +{ + uint32_t width; ///< Widget width. + uint32_t height; ///< Widget height. + RmtResource resource; ///< A copy of the resource. + bool resource_valid; ///< Is the resource above valid. + bool allocation_thumbnail; ///< Should the allocation rendering on the left be included? + const Colorizer* colorizer; ///< The colorizer used to color this widget. +}; + +/// Container class for resource details which is rendered in resource overview and allocation delta pane. +class RMVResourceDetails : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVResourceDetails(const RMVResourceDetailsConfig& config); + + /// Destructor. + virtual ~RMVResourceDetails(); + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param item Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) Q_DECL_OVERRIDE; + + /// Update the current resource. + /// \param resource The resource to update. + void UpdateResource(const RmtResource* resource); + + /// Set width and height of the QGraphicsView being drawn into. + /// \param width The width. + /// \param height The height. + void UpdateDimensions(int width, int height); + + /// Get the resource being displayed. + /// \return The resource. + const RmtResource* GetResource() const; + +private: + RMVResourceDetailsConfig config_; ///< Description of this widget. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_RESOURCE_DETAILS_H_ diff --git a/source/frontend/views/custom_widgets/rmv_resource_timeline.cpp b/source/frontend/views/custom_widgets/rmv_resource_timeline.cpp new file mode 100644 index 0000000..a6064ec --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_resource_timeline.cpp @@ -0,0 +1,88 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a resource timeline widget +//============================================================================= + +#include "views/custom_widgets/rmv_resource_timeline.h" + +#include +#include +#include +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "views/delegates/rmv_resource_event_delegate.h" + +// Some defaults. +static const int32_t kDefaultTimelineWidthHint = 100; +static const int32_t kDefaultTimelineHeightHint = RMVResourceEventDelegate::kIconDefaultSizeHint; + +RMVResourceTimeline::RMVResourceTimeline(QWidget* parent) + : QWidget(parent) + , model_(nullptr) +{ + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &QWidget::updateGeometry); +} + +RMVResourceTimeline::~RMVResourceTimeline() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &QWidget::updateGeometry); +} + +void RMVResourceTimeline::Initialize(rmv::ResourceDetailsModel* model) +{ + model_ = model; +} + +QSize RMVResourceTimeline::sizeHint() const +{ + return ScalingManager::Get().Scaled(QSize(kDefaultTimelineWidthHint, kDefaultTimelineHeightHint)); +} + +void RMVResourceTimeline::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QPainter painter(this); + + const QRect& r = rect(); + int mid_y = r.y() + (r.height() / 2); + + painter.setRenderHint(QPainter::Antialiasing); + + painter.setPen(QPen(Qt::black, 1)); + painter.drawLine(r.left(), mid_y, r.right(), mid_y); + + // Draw the resource events. Get the data from the model + int icon_size = r.height() * RMVResourceEventDelegate::kIconSizeFactor; + int width = r.width() - icon_size; + int index = 0; + rmv::ResourceEvent resource_event; + bool success = true; + do + { + success = model_->GetEventData(index, width, resource_event); + if (success) + { + int left_pos = r.left() + resource_event.timestamp; + event_icons_.DrawIcon(&painter, left_pos, mid_y, icon_size, resource_event.color, resource_event.shape); + } + index++; + } while (success); +} + +void RMVResourceTimeline::mousePressEvent(QMouseEvent* event) +{ + double icon_size = this->height() * RMVResourceEventDelegate::kIconSizeFactor; + int x_pos = event->pos().x(); + double width = this->width() - icon_size; + + double logical_position = (double)x_pos / width; + double tolerance = icon_size / width; + + emit TimelineSelected(logical_position, tolerance); +} diff --git a/source/frontend/views/custom_widgets/rmv_resource_timeline.h b/source/frontend/views/custom_widgets/rmv_resource_timeline.h new file mode 100644 index 0000000..37d44f0 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_resource_timeline.h @@ -0,0 +1,62 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a resource timeline widget +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_RESOURCE_TIMELINE_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_RESOURCE_TIMELINE_H_ + +#include + +#include "models/snapshot/resource_details_model.h" +#include "views/snapshot/resource_event_icons.h" + +/// Support for a donut pie graphics item widget. +class RMVResourceTimeline : public QWidget +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The parent widget. + explicit RMVResourceTimeline(QWidget* parent); + + /// Destructor. + virtual ~RMVResourceTimeline(); + + /// Implementation of Qt's sizeHint for this widget. + /// /return A default sizeHint since the size of this widget can grow to fit + /// the space allowed by the layout. + virtual QSize sizeHint() const Q_DECL_OVERRIDE; + + /// Initialize the widget with non-default values. + /// \param model The model where the timeline data is stored. + void Initialize(rmv::ResourceDetailsModel* model); + +signals: + /// Indicate that the timeline was clicked on. + /// \param logical_position The logical position on the timeline clicked on. + /// The absolute position is converted to a logical position between 0 and 1. + /// A value of 0.5 would be half way along the timeline. + /// \param tolerance A factor around the logical_position still considered to + /// be valid. This should allow for the size of the icon. Tolerance is on the + /// same scale as the logical position. + void TimelineSelected(double logical_position, double tolerance); + +protected: + /// Implementation of Qt's paint for this widget. + /// \param paint_event The paint event. + virtual void paintEvent(QPaintEvent* paint_event) Q_DECL_OVERRIDE; + + /// Implementation of Qt's mouse press event for this widget. + /// \param event The mouse press event. + virtual void mousePressEvent(QMouseEvent* event) Q_DECL_OVERRIDE; + +private: + rmv::ResourceDetailsModel* model_; ///< Pointer to the model data. + rmv::ResourceEventIcons event_icons_; ///< The icon painter helper object. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_RESOURCE_TIMELINE_H_ diff --git a/source/frontend/views/custom_widgets/rmv_scaled_donut_widget.cpp b/source/frontend/views/custom_widgets/rmv_scaled_donut_widget.cpp new file mode 100644 index 0000000..182ca0e --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_scaled_donut_widget.cpp @@ -0,0 +1,219 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a donut widget. +//============================================================================= + +// use definition of PI from math.h +#define _USE_MATH_DEFINES + +#include "views/custom_widgets/rmv_scaled_donut_widget.h" + +#include +#include +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" + +// The arc width is the ratio of the arc width in pixels to the width of the +// donut widget. The larger this number, the thicker the arc +static const qreal kArcWidthScale = 0.0921; + +// font size scalings, based on the widget width. The larger the number, the +// smaller the font +static const int kDonutValuePixelFontSize = 36; +static const int kDonutTextPixelFontSize = 14; + +RMVScaledDonutWidget::RMVScaledDonutWidget(QWidget* parent) + : QWidget(parent) + , width_(0) + , height_(0) + , num_segments_(0) + , arc_width_(0) +{ + AdjustSize(); +} + +RMVScaledDonutWidget::~RMVScaledDonutWidget() +{ +} + +int RMVScaledDonutWidget::heightForWidth(int width) const +{ + return width; +} + +void RMVScaledDonutWidget::AdjustSize() +{ + width_ = geometry().width(); + setMaximumHeight(width_); // force proportional aspect ratio + height_ = geometry().height(); + arc_width_ = width_ * kArcWidthScale; + + // scale font sizes in multiples of 2, incase the odd font size doesn't exist + // Note: Font point size is adjusted based on the width of widget and the base font point size (before DPI scaling is applied). + // The size of the widget should be increased based on the DPI scaling. + //qreal dpiScale = ScalingManager::Get().GetScaleFactor(); +} + +void RMVScaledDonutWidget::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QStylePainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QRectF bound_rect = QRectF(0, 0, width_, height_); + + int w = bound_rect.width(); + int h = bound_rect.height(); + + painter.setRenderHint(QPainter::Antialiasing); + + // calculate the total range of the values. This is used to calculate how wide + // each segment should be + qreal range = 0.0; + + for (unsigned int loop = 0; loop < num_segments_; loop++) + { + range += slices_[loop].value; + } + + // calculate the draw rectangle. Take into account the width of the pen and subtract this + // from the rectangle bounds + QRectF rect(arc_width_ / 2.0, arc_width_ / 2.0, w - arc_width_, h - arc_width_); + + QFont font; + font.setFamily(font.defaultFamily()); + // iterate over all segments and draw each one + // set start to 6 o'clock position, clockwise (default is 3 o'clock, so add 90 degrees counterclockwise) + // angles are specified in 1/16 of a degree, negative angles are counterclockwise + int start_pos = -90 * 16; + + QQueue labelPositions; + const uint32_t segment_count = RMT_MINIMUM((uint32_t)slices_.count(), num_segments_); + for (uint32_t loop = 0; loop < segment_count; loop++) + { + // create the pen and set up the color for this slice + QPen pen(slices_[loop].fill_color, arc_width_, Qt::SolidLine); + pen.setCapStyle(Qt::FlatCap); + + // calculate the arc angle for this slice + qreal angle = (360.0 * 16.0 * slices_[loop].value) / range; + + // draw the arc + painter.setPen(pen); + painter.drawArc(rect, start_pos, static_cast(angle)); + + // figure out where to draw the text on the arc + qreal text_angle = angle / 2.0; + text_angle += start_pos; + + // convert to radians + text_angle *= M_PI / (180.0 * 16.0); + + // calculate text position + int radius = rect.width() / 2; + qreal x_pos = radius + (radius * cos(text_angle)); + qreal y_pos = radius - (radius * sin(text_angle)); + + // take into account the donut draw rectangle and the bounding rectangle of the font + QRect textRect = painter.boundingRect(QRect(0, 0, 0, 0), Qt::AlignLeft, slices_[loop].slice_text); + x_pos += rect.x() - (textRect.width() / 2); + y_pos += rect.y() + (textRect.height() / 2); + + // Save label positions and render later once all arc sections have been drawn. + labelPositions.enqueue(QPoint(x_pos, y_pos)); + + // set the start position of the next arc + start_pos += angle; + } + + // draw the text labels on the arcs + painter.setPen(Qt::white); + for (unsigned int loop = 0; loop < segment_count; loop++) + { + const QPoint labelPos = labelPositions.dequeue(); + painter.drawText(labelPos.x(), labelPos.y(), slices_[loop].slice_text); + } + + font.setPixelSize(kDonutValuePixelFontSize); + painter.setFont(font); + painter.setPen(Qt::black); + + int text_width = QtCommon::QtUtils::GetPainterTextWidth(&painter, text_line_one_); + + int x_pos = (w - text_width) / 2; + int y_pos = (h * 52) / 100; + painter.drawText(x_pos, y_pos, text_line_one_); + + // draw the description text + font.setPixelSize(kDonutTextPixelFontSize); + painter.setFont(font); + text_width = QtCommon::QtUtils::GetPainterTextWidth(&painter, text_line_two_); + x_pos = (w - text_width) / 2; + painter.drawText(x_pos, (h * 66) / 100, text_line_two_); +} + +void RMVScaledDonutWidget::SetNumSegments(unsigned int num_segments) +{ + if (num_segments_ != num_segments) + { + slices_.resize(num_segments); + num_segments_ = num_segments; + } +} + +void RMVScaledDonutWidget::SetIndexValue(unsigned int index, qreal value) +{ + if (index < num_segments_) + { + slices_[index].value = value; + } +} + +void RMVScaledDonutWidget::SetIndexColor(unsigned int index, const QColor& fill_color) +{ + if (index < num_segments_) + { + slices_[index].fill_color = fill_color; + } +} + +void RMVScaledDonutWidget::SetIndexText(unsigned int index, const QString& text) +{ + if (index < num_segments_) + { + slices_[index].slice_text = text; + } +} + +void RMVScaledDonutWidget::SetArcWidth(qreal arc_width) +{ + arc_width_ = arc_width; +} + +void RMVScaledDonutWidget::SetTextLineOne(const QString& text) +{ + text_line_one_ = text; +} + +void RMVScaledDonutWidget::SetTextLineTwo(const QString& text) +{ + text_line_two_ = text; +} + +void RMVScaledDonutWidget::SetBackgroundColor(const QColor& color) +{ + background_color_ = color; +} + +void RMVScaledDonutWidget::resizeEvent(QResizeEvent* event) +{ + AdjustSize(); + QWidget::resizeEvent(event); +} diff --git a/source/frontend/views/custom_widgets/rmv_scaled_donut_widget.h b/source/frontend/views/custom_widgets/rmv_scaled_donut_widget.h new file mode 100644 index 0000000..54bab5a --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_scaled_donut_widget.h @@ -0,0 +1,110 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the donut widget. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_SCALED_DONUT_WIDGET_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_SCALED_DONUT_WIDGET_H_ + +#include +#include + +/// Support for the donut graphics item widget. +class RMVScaledDonutWidget : public QWidget +{ +public: + /// Constructor. + /// \param parent The parent widget. + explicit RMVScaledDonutWidget(QWidget* parent = nullptr); + + /// Destructor. + virtual ~RMVScaledDonutWidget(); + + /// Implementation of Qt's heightForWidth method. + /// \return the width (force proportional aspect ratio). + virtual int heightForWidth(int width) const Q_DECL_OVERRIDE; + + /// Set the number of segments for this control. This is the number of + /// unique data elements to be shown in this widget. + /// \param num_segments the number of segments needed. + void SetNumSegments(unsigned int num_segments); + + /// Set the value for the given index for the widget. + /// \param index the index whose value is to change. + /// \param value the new value to use. + void SetIndexValue(unsigned int index, qreal value); + + /// Set the fill color for the given index for the widget. + /// \param index the index whose color is to change. + /// \param fill_color the color to use. + void SetIndexColor(unsigned int index, const QColor& fill_color); + + /// Set the text to be displayed in the pie segment. + /// \param index The index whose text is to change. + /// \param text The text to be shown. + void SetIndexText(unsigned int index, const QString& text); + + /// Set how wide the donut section should be. + /// \param arc_width the width of the donut arc. + void SetArcWidth(qreal arc_width); + + /// Set the first line of text inside the donut. + /// \param text Text to set. + void SetTextLineOne(const QString& text); + + /// Set the second line of text inside the donut. + /// \param text Text to set. + void SetTextLineTwo(const QString& text); + + /// Set the background color. + /// \param color The color to set. + void SetBackgroundColor(const QColor& color); + + /// Adjust the size of the widget and proportionately adjust the font + /// and pen point sizes. + void AdjustSize(); + +protected: + /// Implementation of Qt's paint event for this widget. + /// \param event The paint event. + virtual void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE; + + /// Capture a resize event. + /// \param event The resize event. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + +private: + /// Container class for each slice in the donut. + class SliceData + { + public: + SliceData() + : value(0) + , fill_color(Qt::black) + , slice_text() + { + } + + ~SliceData() + { + } + + qreal value; ///< Current value to represent. + QColor fill_color; ///< Color used to fill the value part of the arc. + QString slice_text; ///< Additional text description. + }; + + QVector slices_; ///< The list of donut slices. + + int width_; ///< Width of this widget. + int height_; ///< Height of this widget. + unsigned int num_segments_; ///< Number of segments (values) in the donut. + qreal arc_width_; ///< Width of the control arc, in pixels. This is used as the size of the pen used to draw the arc. + QString text_line_one_; ///< Text in the center of the donut. + QString text_line_two_; ///< Text in the center of the donut. + QColor background_color_; ///< The background color. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_SCALED_DONUT_WIDGET_H_ diff --git a/source/frontend/views/custom_widgets/rmv_snapshot_marker.cpp b/source/frontend/views/custom_widgets/rmv_snapshot_marker.cpp new file mode 100644 index 0000000..f31ae26 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_snapshot_marker.cpp @@ -0,0 +1,181 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a snapshot marker +//============================================================================= + +#include "views/custom_widgets/rmv_snapshot_marker.h" + +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "models/message_manager.h" +#include "util/rmv_util.h" + +RMVSnapshotMarker::RMVSnapshotMarker(const RMVSnapshotMarkerConfig& config) + : config_(config) + , state_(kSnapshotStateNone) + , selected_(false) + , hovered_(false) +{ + setAcceptHoverEvents(true); +} + +RMVSnapshotMarker::~RMVSnapshotMarker() +{ +} + +QPolygonF RMVSnapshotMarker::GetTriangle(int length) +{ + QPolygonF triangle; + triangle << QPoint(length / 2 * -1, 0) << QPoint(length / 2, 0) << QPoint(0, length); + return triangle; +} + +QRectF RMVSnapshotMarker::boundingRect() const +{ + return QRectF(static_cast(config_.width) / 2.0 * -1.0, 0.0, config_.width, config_.height); +} + +void RMVSnapshotMarker::paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) +{ + Q_UNUSED(item); + Q_UNUSED(widget); + + QColor base_color = rmv_util::GetSnapshotStateColor(state_); + + if (state_ == kSnapshotStateNone) + { + //base_color = rmv_util::GetSnapshotTypeColor(config_.data.type); + } + + const bool darken = (selected_) || ((hovered_) && (!selected_)); + + if ((darken) && (state_ == kSnapshotStateNone)) + { + base_color = base_color.darker(125); + } + + painter->setBrush(base_color); + + if (darken) + { + QPen pen; + pen.setWidth(ScalingManager::Get().Scaled(2)); + pen.setBrush(Qt::black); + painter->setPen(pen); + setZValue(0.99); + } + else + { + painter->setPen(Qt::NoPen); + setZValue(0.98); + } + + painter->setRenderHint(QPainter::Antialiasing); + + QPolygonF polygon = GetTriangle(config_.width); + painter->drawPolygon(polygon); + + QFont font; + font.setPixelSize(ScalingManager::Get().Scaled(10)); + painter->setFont(font); + + QPen pen; + pen.setStyle(Qt::DashLine); + pen.setWidth(1); + if (darken) + { + pen.setColor(Qt::black); + } + else + { + pen.setColor(base_color); + } + + painter->setPen(pen); + painter->drawLine(0, config_.width, 0, config_.height); + + pen.setStyle(Qt::SolidLine); + pen.setBrush(rmv_util::GetTextColorForBackground(base_color)); + painter->setPen(pen); + + painter->setRenderHint(QPainter::TextAntialiasing); + + //painter->drawText(QPoint(QtCommon::QtUtil::GetPainterTextWidth(painter, QString::number(config_.data.id)) / 2 * -1, config_.width / 2 - 1), + // QString::number(config_.data.id)); +} + +void RMVSnapshotMarker::UpdateDimensions(int width, int height) +{ + config_.width = width - 2; + config_.height = height - 2; +} + +void RMVSnapshotMarker::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + + hovered_ = true; + + setCursor(Qt::PointingHandCursor); + + update(); +} + +void RMVSnapshotMarker::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + + hovered_ = false; + + update(); +} + +void RMVSnapshotMarker::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + hovered_ = false; + + // get the snapshot id + if (config_.snapshot_point != nullptr) + { + emit MessageManager::Get().SelectSnapshot(config_.snapshot_point); + } +} + +void RMVSnapshotMarker::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + hovered_ = false; + + if (config_.snapshot_point != nullptr) + { + emit MessageManager::Get().OpenSnapshot(config_.snapshot_point); + } +} + +void RMVSnapshotMarker::SetSelected(bool selected) +{ + selected_ = selected; +} + +void RMVSnapshotMarker::SetState(SnapshotState state) +{ + state_ = state; +} + +RmtSnapshotPoint* RMVSnapshotMarker::GetSnapshotPoint() +{ + return config_.snapshot_point; +} + +SnapshotState RMVSnapshotMarker::GetState() +{ + return state_; +} diff --git a/source/frontend/views/custom_widgets/rmv_snapshot_marker.h b/source/frontend/views/custom_widgets/rmv_snapshot_marker.h new file mode 100644 index 0000000..ad20c9b --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_snapshot_marker.h @@ -0,0 +1,97 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a snapshot marker +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_MARKER_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_MARKER_H_ + +#include + +#include "rmt_data_set.h" + +#include "util/rmv_util.h" + +/// Describes the little triangle+line indicating a snapshot on the timeline. +struct RMVSnapshotMarkerConfig +{ + int width; ///< Widget width. + int height; ///< Widget height. + RmtSnapshotPoint* snapshot_point; ///< Snapshot point. +}; + +/// Container class for a widget which shows when a snapshot lives on a timeline. +class RMVSnapshotMarker : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVSnapshotMarker(const RMVSnapshotMarkerConfig& config); + + ///Destructor. + virtual ~RMVSnapshotMarker(); + + /// Build a polygon that represents a triangle. + /// \param length The triangle line length, in pixels. + /// \return The triangle polygon. + static QPolygonF GetTriangle(int length); + + /// Mouse hover over event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse hover leave event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse press event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Mouse double click event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param item Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* item, QWidget* widget) Q_DECL_OVERRIDE; + + /// Update the widget dimensions. + /// \param width The width. + /// \param height The height. + void UpdateDimensions(int width, int height); + + /// Set the selected state of this snapshot marker. + /// \param selected Whether this snapshot is selected or not. + void SetSelected(bool selected); + + /// Set the state of this snapshot marker. + /// \param state The state of this snapshot marker. + void SetState(SnapshotState state); + + /// Get the snapshot point for this snapshot marker. + /// \return The snapshot point. + RmtSnapshotPoint* GetSnapshotPoint(); + + /// Get the state of this snapshot marker. + /// \return The state. + SnapshotState GetState(); + +private: + RMVSnapshotMarkerConfig config_; ///< Description of this widget. + SnapshotState state_; ///< Set the state of the snapshot. + bool selected_; ///< Is this snapshot marker selected. + bool hovered_; ///< is it hovered over? +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_MARKER_H_ diff --git a/source/frontend/views/custom_widgets/rmv_snapshot_table_view.cpp b/source/frontend/views/custom_widgets/rmv_snapshot_table_view.cpp new file mode 100644 index 0000000..e463d9e --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_snapshot_table_view.cpp @@ -0,0 +1,24 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the snapshot table view. Emit a signal whenever +/// the table selection is changed so external objects can respond to it. +//============================================================================= + +#include "views/custom_widgets/rmv_snapshot_table_view.h" + +RMVSnapshotTableView::RMVSnapshotTableView(QWidget* parent) + : RMVClickableTableView(parent) +{ +} + +RMVSnapshotTableView::~RMVSnapshotTableView() +{ +} + +void RMVSnapshotTableView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + RMVClickableTableView::selectionChanged(selected, deselected); + emit SelectionChanged(); +} diff --git a/source/frontend/views/custom_widgets/rmv_snapshot_table_view.h b/source/frontend/views/custom_widgets/rmv_snapshot_table_view.h new file mode 100644 index 0000000..3f611d3 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_snapshot_table_view.h @@ -0,0 +1,37 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the snapshot table view. Emit a signal whenever the table +/// selection is changed so external objects can respond to it. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_TABLE_VIEW_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_TABLE_VIEW_H_ + +#include "views/custom_widgets/rmv_clickable_table_view.h" + +/// Class declaration for the Snapshot table view. +class RMVSnapshotTableView : public RMVClickableTableView +{ + Q_OBJECT +public: + /// Constructor. + explicit RMVSnapshotTableView(QWidget* parent = NULL); + + /// Destructor. + virtual ~RMVSnapshotTableView(); + +protected: + /// Overridden Qt selectionChanged method. Called when a table entry is changed, either by + /// mouse clicking on an entry or using the cursor keys. + /// \param selected The items selected. + /// \param deselected The items deselected. + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) override; + +signals: + /// Signal the table selection has changed. + void SelectionChanged(); +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_TABLE_VIEW_H_ diff --git a/source/frontend/views/custom_widgets/rmv_snapshot_timeline.cpp b/source/frontend/views/custom_widgets/rmv_snapshot_timeline.cpp new file mode 100644 index 0000000..e15700c --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_snapshot_timeline.cpp @@ -0,0 +1,252 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV's snapshot timeline visualization. +//============================================================================= + +#include "views/custom_widgets/rmv_snapshot_timeline.h" + +#include +#include +#include +#include +#include + +#include "qt_common/utils/common_definitions.h" +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "util/time_util.h" + +static const int kDefaultMarkerWidth = 25; + +RMVSnapshotTimeline::RMVSnapshotTimeline(QWidget* parent) + : TimelineView(parent) +{ +} + +RMVSnapshotTimeline::~RMVSnapshotTimeline() +{ +} + +void RMVSnapshotTimeline::mousePressEvent(QMouseEvent* event) +{ + TimelineView::mousePressEvent(event); + + emit UpdateSelectedDuration(selected_end_clock_ - selected_start_clock_); + emit UpdateHoverClock(last_hovered_clock_); + emit UpdateZoomButtonsForZoomToSelection(RegionSelected()); +} + +void RMVSnapshotTimeline::mouseMoveEvent(QMouseEvent* event) +{ + TimelineView::mouseMoveEvent(event); + + emit UpdateSelectedDuration(selected_end_clock_ - selected_start_clock_); + emit UpdateHoverClock(last_hovered_clock_); + emit UpdateZoomButtonsForZoomToSelection(RegionSelected()); + +#if 0 + QPoint mouseCoords = mapFromGlobal(QCursor::pos()); + QPointF sceneCoords = mapToScene(mouseCoords); + qDebug() << "mouse: " << mouseCoords; + qDebug() << "sceneCoords: " << sceneCoords; + qDebug() << "---------"; +#endif +} + +RMVSnapshotMarker* RMVSnapshotTimeline::AddSnapshot(RmtSnapshotPoint* snapshot_point) +{ + RMVSnapshotMarkerConfig config = {}; + config.width = kDefaultMarkerWidth; + config.height = height() - kDefaultRulerHeight; + config.snapshot_point = snapshot_point; + + TimelineItem content_object = {}; + RMVSnapshotMarker* marker = new RMVSnapshotMarker(config); + marker->SetState(kSnapshotStateViewed); + + content_object.item = marker; + content_object.clock = snapshot_point->timestamp; + + content_object.item->setY(ScalingManager::Get().Scaled(kDefaultRulerHeight)); + content_.push_back(content_object); + + scene_->addItem(content_object.item); + + UpdateScene(); + + return dynamic_cast(content_object.item); +} + +RMVTimelineGraph* RMVSnapshotTimeline::AddTimelineGraph(rmv::TimelineModel* timeline_model, TimelineColorizer* colorizer) +{ + RMVTimelineGraphConfig config = {}; + config.model_data = timeline_model; + config.colorizer = colorizer; + config.width = 25; + config.height = height() - kDefaultRulerHeight; + + TimelineItem content_object = {}; + content_object.item = new RMVTimelineGraph(config); + + content_object.item->setY(ScalingManager::Get().Scaled(kDefaultRulerHeight)); + content_object.item->setX(100); + + content_.push_back(content_object); + + scene_->addItem(content_object.item); + + UpdateScene(); + + return dynamic_cast(content_object.item); +} + +void RMVSnapshotTimeline::SelectSnapshot(const RmtSnapshotPoint* snapshot_point) +{ + for (int32_t i = 0; i < content_.size(); i++) + { + RMVSnapshotMarker* current_marker = dynamic_cast(content_[i].item); + + if (current_marker != nullptr) + { + if (current_marker->GetSnapshotPoint() == snapshot_point) + { + current_marker->SetSelected(true); + } + else + { + current_marker->SetSelected(false); + } + + current_marker->update(); + } + } +} + +void RMVSnapshotTimeline::Clear() +{ + for (int i = 0; i < content_.size(); i++) + { + scene_->removeItem(content_[i].item); + } + + content_.clear(); + + UpdateScene(); +} + +void RMVSnapshotTimeline::ClearSnapshotMarkers() +{ + QVector snapshot_markers; ///< Vector of all objects in the scene added by child implementations + + for (int current_index = 0; current_index < content_.size(); ++current_index) + { + RMVSnapshotMarker* current_marker = dynamic_cast(content_[current_index].item); + if (current_marker == nullptr) + continue; + + scene_->removeItem(content_[current_index].item); + snapshot_markers.push_back(current_index); + } + + for (int32_t current_snapshot_marker_index = snapshot_markers.size() - 1; current_snapshot_marker_index >= 0; --current_snapshot_marker_index) + { + int32_t remove_index = snapshot_markers[current_snapshot_marker_index]; + content_.remove(remove_index); + } + + UpdateScene(); +} + +void RMVSnapshotTimeline::contextMenuEvent(QContextMenuEvent* event) +{ + QGraphicsView::contextMenuEvent(event); + + QAction action("Add snapshot at " + rmv::time_util::ClockToTimeUnit(last_hovered_clock_)); + + QMenu menu; + menu.addAction(&action); + + QAction* menu_action = menu.exec(event->globalPos()); + + if (menu_action != nullptr) + { + emit GenerateSnapshotAtTime(last_hovered_clock_); + } + + // swallow the event so we don't pass it out to parent controls. + event->accept(); +} + +void RMVSnapshotTimeline::wheelEvent(QWheelEvent* event) +{ + Qt::KeyboardModifiers keyboard_modifiers = QApplication::keyboardModifiers(); + + if (keyboard_modifiers & Qt::ControlModifier) + { + const int delta = event->delta(); + + if (delta < 0) + { + bool zoom = ZoomOutMousePosition(); + emit UpdateZoomButtonsForZoomOut(zoom); + } + else + { + bool zoom = ZoomInMousePosition(); + emit UpdateZoomButtonsForZoomIn(zoom); + } + } + else + { + QGraphicsView::wheelEvent(event); + } +} + +void RMVSnapshotTimeline::resizeEvent(QResizeEvent* event) +{ + TimelineView::resizeEvent(event); + + UpdateScene(); +} + +void RMVSnapshotTimeline::UpdateContent() +{ + UpdateSnapshotMarkers(); +} + +void RMVSnapshotTimeline::UpdateSnapshotMarkers() +{ + for (int32_t i = 0; i < content_.size(); i++) + { + RMVSnapshotMarker* marker = dynamic_cast(content_[i].item); + + if (marker != nullptr) + { + marker->UpdateDimensions(ScalingManager::Get().Scaled(kDefaultMarkerWidth), height()); + marker->setX(ClockToSceneCoordinate(content_[i].clock)); + marker->setY(ScalingManager::Get().Scaled(kDefaultRulerHeight)); + } + + RMVTimelineGraph* allocation = dynamic_cast(content_[i].item); + if (allocation != nullptr) + { + allocation->setX(ClockToSceneCoordinate(ViewableStartClk())); + allocation->setY(ScalingManager::Get().Scaled(kDefaultRulerHeight)); + allocation->UpdateDimensions(width(), BasePosY() - ScalingManager::Get().Scaled(kDefaultRulerHeight)); + } + } +} + +void RMVSnapshotTimeline::UpdateTimeUnits(TimeUnitType time_unit, double time_to_clock_ratio) +{ + ruler_config_.unit_type = time_unit; + ruler_config_.time_to_clock_ratio = time_to_clock_ratio; + UpdateScene(); + + // update the time values below the timeline + emit UpdateSelectedDuration(selected_end_clock_ - selected_start_clock_); + emit UpdateHoverClock(last_hovered_clock_); +} diff --git a/source/frontend/views/custom_widgets/rmv_snapshot_timeline.h b/source/frontend/views/custom_widgets/rmv_snapshot_timeline.h new file mode 100644 index 0000000..b5b8c83 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_snapshot_timeline.h @@ -0,0 +1,119 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's snapshot timeline visualization. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_TIMELINE_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_TIMELINE_H_ + +#include "qt_common/custom_widgets/timeline_view.h" + +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" + +#include "models/timeline/timeline_model.h" +#include "util/definitions.h" +#include "views/custom_widgets/rmv_snapshot_marker.h" +#include "views/custom_widgets/rmv_timeline_graph.h" + +/// Holds and controls the entire queue timings visualization. +class RMVSnapshotTimeline : public TimelineView +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The parent widget. + explicit RMVSnapshotTimeline(QWidget* parent); + + /// Destructor. + virtual ~RMVSnapshotTimeline(); + + /// Handle resizing. + /// \param event The resize event. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Handle a mouse press event. + /// \param event The mouse event. + virtual void mousePressEvent(QMouseEvent* event) Q_DECL_OVERRIDE; + + /// Handle a mouse move event. + /// \param event The move event. + virtual void mouseMoveEvent(QMouseEvent* event) Q_DECL_OVERRIDE; + + /// Capture a mouse wheel event. This allows the user to zoom in and out if + /// the control key is also pressed. + /// \param event The wheel event. + virtual void wheelEvent(QWheelEvent* event) Q_DECL_OVERRIDE; + + /// Update objects inside the timeline. + virtual void UpdateContent() Q_DECL_OVERRIDE; + + /// Create a context menu to add a new snapshot. + /// \param event The context menu event. + virtual void contextMenuEvent(QContextMenuEvent* event) Q_DECL_OVERRIDE; + + /// Add a new snapshot. + /// \param snapshot_point The new snapshot point. + RMVSnapshotMarker* AddSnapshot(RmtSnapshotPoint* snapshot_point); + + /// Select a snapshot. + /// \param snapshot_point The snapshot point to select. + void SelectSnapshot(const RmtSnapshotPoint* snapshot_point); + + /// Add a new timeline graph. This is the representation of different processes + /// and how their memory usage varies over time. Data is displayed as a slabbed + /// graph. + /// \param timeline_model Pointer to the timeline model. + /// \param colorizer Pointer to the colorizer object. + RMVTimelineGraph* AddTimelineGraph(rmv::TimelineModel* timeline_model, TimelineColorizer* colorizer); + + /// Clear out scene content. + void Clear(); + + /// Clear the snapshot markers. + void ClearSnapshotMarkers(); + + /// Update the ruler time units. + /// \param time_units The new time unit to use. + /// \param time_to_clock_ratio The ratio of time units to clock units. Used to convert from time to + /// clocks and vice versa. + void UpdateTimeUnits(TimeUnitType time_unit, double time_to_clock_ratio); + +signals: + /// Signal for when the selected region in the timeline needs updating. + /// \param duration The duration. + void UpdateSelectedDuration(uint64_t duration); + + /// Signal for when the clock needs updating when moving the mouse. + /// \param clock The time in clocks on the timeline the mouse is over. + void UpdateHoverClock(uint64_t clock); + + /// Signal for when a snapshot is to be generated. + /// \param time The time, in clocks, where the snapshot is to be generated. + void GenerateSnapshotAtTime(uint64_t time); + + /// Signal for when the zoom in button needs updating. Typically called wnen + /// selecting a region on the timeline or using the zoom buttons. + /// \param zoom If true, zoom in is allowed. + void UpdateZoomButtonsForZoomIn(bool zoom); + + /// Signal for when the zoom out button needs updating. Typically called wnen + /// selecting a region on the timeline or using the zoom buttons. + /// \param zoom If true, zoom out is allowed. + void UpdateZoomButtonsForZoomOut(bool zoom); + + /// Signal for when the zoom to selection button needs updating. Typically called wnen + /// selecting a region on the timeline. + /// \param zoom If true, zoom in is allowed. + void UpdateZoomButtonsForZoomToSelection(bool selected_region); + +private: + /// Update marker locations. Uses a dynamic cast to decide whether it's using + /// the correct type. + void UpdateSnapshotMarkers(); +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_SNAPSHOT_TIMELINE_H_ diff --git a/source/frontend/views/custom_widgets/rmv_timeline_graph.cpp b/source/frontend/views/custom_widgets/rmv_timeline_graph.cpp new file mode 100644 index 0000000..290f3db --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_timeline_graph.cpp @@ -0,0 +1,206 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a widget that shows how memory usage changes per +/// process over time. +//============================================================================= + +#include "views/custom_widgets/rmv_timeline_graph.h" + +#include +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "models/timeline/timeline_model.h" + +// Slightly transparent white background color for the custom tooltip +static const QColor kTooltipBackgroundColor = QColor(255, 255, 255, 230); + +static const double kTooltipTextOffset = 3.0; + +// The position to place the tooltip to the right of the mouse so that the +// mouse cursor isn't obscured +static const qreal kMouseXOffset = 25.0; + +RMVTimelineGraph::RMVTimelineGraph(const RMVTimelineGraphConfig& config) + : mouse_hovering_(false) + , tooltip_contents_(nullptr) + , tooltip_background_(nullptr) +{ + setAcceptHoverEvents(true); + + config_ = config; +} + +RMVTimelineGraph::~RMVTimelineGraph() +{ +} + +QRectF RMVTimelineGraph::boundingRect() const +{ + return QRectF(0, 0, ScaledWidth(), ScaledHeight()); +} + +void RMVTimelineGraph::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + painter->save(); + + qreal scaled_height = ScaledHeight(); + int num_buckets = config_.model_data->GetNumBuckets(); + + // for each bucket, ask the model for a rectangle to draw. + int bucket_index = 0; + bool success = true; + do + { + for (int bucket_group_index = 0; bucket_group_index < config_.model_data->GetNumBucketGroups(); bucket_group_index++) + { + QColor color = config_.colorizer->GetColor(bucket_group_index); + qreal y_pos = 0.0f; + qreal height = 0.0f; + success = config_.model_data->GetHistogramData(bucket_group_index, bucket_index, y_pos, height); + if (success) + { + QRectF rect; + + // flip the y-coord so (0, 0) is at the bottom left and scale values up to fit the view + rect.setY(scaled_height - (y_pos * scaled_height)); + rect.setHeight(height * scaled_height); + + // calculate the x offset and width based on the bucket number and number of buckets + qreal w = (qreal)ScaledWidth() / (qreal)num_buckets; + rect.setX(w * (qreal)bucket_index); + + // allow for rounding error on width since coords are floating point + rect.setWidth(w + 0.5); + + painter->fillRect(rect, color); + } + else + { + break; + } + } + bucket_index++; + } while (success); + + painter->restore(); +} + +void RMVTimelineGraph::hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + mouse_hovering_ = true; + last_scene_hover_pos_ = event->scenePos(); + last_hover_pos_ = event->pos(); +} + +void RMVTimelineGraph::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + last_scene_hover_pos_ = event->scenePos(); + last_hover_pos_ = event->pos(); + UpdateToolTip(); +} + +void RMVTimelineGraph::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + mouse_hovering_ = false; + + if (tooltip_background_ != nullptr) + { + tooltip_background_->hide(); + } + + if (tooltip_contents_ != nullptr) + { + tooltip_contents_->hide(); + } +} + +void RMVTimelineGraph::UpdateDimensions(int width, int height) +{ + config_.width = width; + config_.height = height; +} + +int32_t RMVTimelineGraph::ScaledWidth() const +{ + return config_.width; +} + +int32_t RMVTimelineGraph::ScaledHeight() const +{ + return config_.height; +} + +void RMVTimelineGraph::UpdateToolTip() +{ + if (!tooltip_contents_ || !tooltip_background_) + { + tooltip_background_ = scene()->addRect(QRect(), QPen(), kTooltipBackgroundColor); + tooltip_contents_ = new RMVTimelineTooltip(); + scene()->addItem(tooltip_contents_); + } + + if (mouse_hovering_) + { + QString str; + const qreal view_width = ScaledWidth(); + Q_ASSERT(view_width > 0); + qreal x_pos = last_hover_pos_.x() / view_width; + QList tooltip_info; + if (config_.model_data->GetTimelineTooltipInfo(x_pos, tooltip_info)) + { + tooltip_background_->show(); + tooltip_contents_->show(); + + const qreal view_height = ScaledHeight(); + const qreal tooltip_width = tooltip_background_->rect().width(); + const qreal tooltip_height = tooltip_background_->rect().height(); + + // If the tooltip is to the right of the mouse, offset it slightly so it isn't + // hidden under the mouse + static const qreal mouse_x_offset = kMouseXOffset; + QPointF tooltip_offset = QPointF(mouse_x_offset, 0); + + // If the tooltip is going to run off the right of the scene, position it to the left + // of the mouse + if (last_hover_pos_.x() > (view_width - mouse_x_offset - tooltip_width)) + { + tooltip_offset.setX(-tooltip_width); + } + + // If the tooltip is going to run off the bottom of the scene, position it at the bottom of the scene + // so all the tooltip is visible + qreal max_y_pos = view_height - tooltip_height; + if (last_hover_pos_.y() > max_y_pos) + { + tooltip_offset.setY(max_y_pos - last_hover_pos_.y()); + } + + // tooltip labels use scene coordinates + const QPointF label_pos = last_scene_hover_pos_ + tooltip_offset; + tooltip_contents_->setPos(label_pos); + tooltip_contents_->SetData(tooltip_info); + + // Add a small margin so text is not overlapping the outline and adjust position accordingly + QRectF rect = tooltip_contents_->boundingRect(); + qreal offset = 2.0 * kTooltipTextOffset; + rect.setWidth(rect.width() + offset); + rect.setHeight(rect.height() + offset); + tooltip_background_->setPos(label_pos - QPointF(kTooltipTextOffset, kTooltipTextOffset)); + tooltip_background_->setRect(rect); + } + else + { + tooltip_background_->hide(); + tooltip_contents_->hide(); + } + } +} diff --git a/source/frontend/views/custom_widgets/rmv_timeline_graph.h b/source/frontend/views/custom_widgets/rmv_timeline_graph.h new file mode 100644 index 0000000..3a9c9e7 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_timeline_graph.h @@ -0,0 +1,95 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a widget that shows how memory usage changes per +/// process over time. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_TIMELINE_GRAPH_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_TIMELINE_GRAPH_H_ + +#include + +#include "models/timeline/timeline_model.h" +#include "util/definitions.h" +#include "views/custom_widgets/rmv_timeline_tooltip.h" +#include "views/timeline_colorizer.h" + +/// Describes the data needed for the timeline. +struct RMVTimelineGraphConfig +{ + int width; ///< Widget width. + int height; ///< Widget height. + rmv::TimelineModel* model_data; ///< Pointer to the timeline model. + TimelineColorizer* colorizer; ///< Pointer to the timeline colorizer. +}; + +/// Container class for a widget which shows how memory allocations change per process +/// over time. +class RMVTimelineGraph : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param config The configuration for this widget. + explicit RMVTimelineGraph(const RMVTimelineGraphConfig& config); + + /// Destructor. + virtual ~RMVTimelineGraph(); + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const override; + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param option Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + /// Overridden hoverEnterEvent to handle what happens when the user moves the + /// mouse into this graphics object. + /// \param event The data associated with this event. + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; + + /// Overridden hoverMoveEvent to handle what happens when the user moves the + /// mouse in this graphics object. Shows a tooltip of information about the current + /// timeline data. + /// \param event The data associated with this event. + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event) override; + + /// Overridden hoverLeaveEvent to handle what happens when the user moves the + /// mouse off this graphics object. + /// \param event The data associated with this event. + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; + + /// Update the dimensions of this widget. This widget is such that it is the same + /// size as the view and placed in the scene where it is always visible. + /// \param width The new width. + /// \param height The new height. + void UpdateDimensions(int width, int height); + +private slots: + /// Update the tool tip. + void UpdateToolTip(); + +private: + /// Get scaled width. + /// \return scaled width. + int32_t ScaledWidth() const; + + /// Get scaled height. + /// \return scaled height. + int32_t ScaledHeight() const; + + RMVTimelineGraphConfig config_; ///< Description of this widget. + bool mouse_hovering_; ///< Tracks if mouse is hovering over the widget. + QPointF last_scene_hover_pos_; ///< Keeps track of the last mouse scene hover position. + QPointF last_hover_pos_; ///< Keeps track of the last mouse hover position (view coords). + RMVTimelineTooltip* tooltip_contents_; ///< Contents of the custom tool tip implementation. + QGraphicsRectItem* tooltip_background_; ///< Background rect of the custom tool tip implementation. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_TIMELINE_GRAPH_H_ diff --git a/source/frontend/views/custom_widgets/rmv_timeline_tooltip.cpp b/source/frontend/views/custom_widgets/rmv_timeline_tooltip.cpp new file mode 100644 index 0000000..6e84085 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_timeline_tooltip.cpp @@ -0,0 +1,65 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV's custom timeline tooltip +//============================================================================= + +#include "views/custom_widgets/rmv_timeline_tooltip.h" + +RMVTimelineTooltip::RMVTimelineTooltip(QGraphicsItem* parent) + : QGraphicsSimpleTextItem(parent) + , text_height_(0) + , icon_size_(0) +{ +} + +RMVTimelineTooltip::~RMVTimelineTooltip() +{ +} + +QRectF RMVTimelineTooltip::boundingRect() const +{ + QRectF rect = QGraphicsSimpleTextItem::boundingRect(); + qreal width = rect.width(); + rect.setWidth(width + icon_size_); + return rect; +} + +void RMVTimelineTooltip::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + painter->setFont(font()); + + int offset = 0; + for (auto index = 0; index < tooltip_data_.size(); index++) + { + painter->fillRect(0, offset + 1, icon_size_, icon_size_, tooltip_data_[index].color); + painter->drawText(text_height_, icon_size_ + offset, tooltip_data_[index].text); + offset += text_height_; + } +} + +void RMVTimelineTooltip::SetData(const QList& tooltip_info_list) +{ + // set the text string for the base class + QString text; + for (int loop = 0; loop < tooltip_info_list.size(); loop++) + { + if (loop > 0) + { + text += "\n"; + } + text += tooltip_info_list[loop].text; + } + setText(text); + + tooltip_data_ = tooltip_info_list; + + int height = boundingRect().height(); + + text_height_ = height / tooltip_data_.size(); + icon_size_ = text_height_ - 2; +} diff --git a/source/frontend/views/custom_widgets/rmv_timeline_tooltip.h b/source/frontend/views/custom_widgets/rmv_timeline_tooltip.h new file mode 100644 index 0000000..902d7a6 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_timeline_tooltip.h @@ -0,0 +1,47 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's custom timeline tooltip +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_TIMELINE_TOOLTIP_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_TIMELINE_TOOLTIP_H_ + +#include +#include +#include + +struct TooltipInfo +{ + QString text; + QColor color; +}; + +/// Container class for the carousel's memory types component. +class RMVTimelineTooltip : public QGraphicsSimpleTextItem +{ +public: + /// Constructor. + explicit RMVTimelineTooltip(QGraphicsItem* parent = nullptr); + + /// Destructor. + virtual ~RMVTimelineTooltip(); + + /// Qt's overridden boundingRect method. + QRectF boundingRect() const override; + + /// Qt's overridden paint method. + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + /// Set the text and colors for the tooltip. + /// \param tooltip_info_list A list of structs containing the tooltip data (text and color swatches). + void SetData(const QList& tooltip_info_list); + +private: + QList tooltip_data_; ///< A list containing the data for each tooltip item. + int text_height_; ///< The text height. + int icon_size_; ///< The icon size. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_TIMELINE_TOOLTIP_H_ diff --git a/source/frontend/views/custom_widgets/rmv_tree_map_blocks.cpp b/source/frontend/views/custom_widgets/rmv_tree_map_blocks.cpp new file mode 100644 index 0000000..8a77b60 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_tree_map_blocks.cpp @@ -0,0 +1,1136 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \fileHeader for a tree map block collection +//============================================================================= + +#include "views/custom_widgets/rmv_tree_map_blocks.h" + +#ifndef _WIN32 +#include "linux/safe_crt.h" // for strcpy_s +#endif + +#include +#include +#include +#include +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_resource_list.h" +#include "rmt_util.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/message_manager.h" +#include "models/snapshot/resource_overview_model.h" +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" + +// The mimimum area that a resource can use. Anything smaller than this is ignored. +const static int kMinArea = 4; + +// The number of values a boolean can have (true or false). +const static int kBooleanCount = 2; + +/// Sorting function. +/// \param a1 First RmvMemoryBlockAllocation. +/// \param a2 Second RmvMemoryBlockAllocation. +/// \return true if a1 > a2, false otherwise. +static bool SortResourcesBySizeFunc(const RmtResource* a1, const RmtResource* a2) +{ + return a1->size_in_bytes > a2->size_in_bytes; +} + +RMVTreeMapBlocks::RMVTreeMapBlocks(const RMVTreeMapBlocksConfig& config) + : config_(config) + , hovered_resource_identifier_(0) + , selected_resource_identifier_(0) + , hovered_resource_(nullptr) + , selected_resource_(nullptr) + , colorizer_(nullptr) +{ + setAcceptHoverEvents(true); +} + +RMVTreeMapBlocks::~RMVTreeMapBlocks() +{ + for (int32_t i = 0; i < unbound_resources_.size(); i++) + { + delete unbound_resources_[i]; + } +} + +void RMVTreeMapBlocks::SetColorizer(const Colorizer* colorizer) +{ + colorizer_ = colorizer; +} + +QRectF RMVTreeMapBlocks::boundingRect() const +{ + return QRectF(0, 0, config_.width, config_.height); +} + +void RMVTreeMapBlocks::PaintClusterParents(QPainter* painter, ResourceCluster& cluster) +{ + // This paints the borders around slicing modes + QPen pen; + pen.setWidth(ScalingManager::Get().Scaled(2)); + pen.setColor(Qt::black); + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + painter->drawRect(cluster.geometry); + + // Go to next parent + if (cluster.sub_clusters.size() != 0) + { + QMutableMapIterator next_it(cluster.sub_clusters); + while (next_it.hasNext()) + { + next_it.next(); + + ResourceCluster& subCluster = next_it.value(); + + PaintClusterParents(painter, subCluster); + } + } +} + +void RMVTreeMapBlocks::PaintClusterChildren(QPainter* painter, + ResourceCluster& cluster, + TreeMapBlockData& hovered_resource, + TreeMapBlockData& selected_resource) +{ + // This paints blocks inside each cluster + if (cluster.sub_clusters.size() == 0) + { + QMutableMapIterator render_it(cluster.alloc_geometry_map); + while (render_it.hasNext()) + { + render_it.next(); + + const RmtResource* resource = render_it.key(); + const QRectF& bounding_rect = render_it.value(); + + const QRectF block_rect(bounding_rect.left() + 1, bounding_rect.top() + 1, bounding_rect.width() - 1, bounding_rect.height() - 1); + + if (block_rect.width() > 0 && block_rect.height() > 0) + { + const QColor& curr_color = colorizer_->GetColor(resource->bound_allocation, resource); + + // figure out the brush style. + Qt::BrushStyle style = ((RmtResourceGetAliasCount(resource) > 0) ? Qt::BrushStyle::Dense1Pattern : Qt::BrushStyle::SolidPattern); + const QBrush curr_brush(curr_color, style); + + painter->fillRect(block_rect, curr_brush); + + // Figure out what we hovered over + if (hovered_resource.is_visible == false) + { + if (hovered_resource_identifier_ == resource->identifier && hovered_resource_ == resource) + { + hovered_resource.bounding_rect = block_rect; + hovered_resource.resource = resource; + hovered_resource.is_visible = true; + } + } + + // Figure out what we selected + if (selected_resource.is_visible == false) + { + if (selected_resource_identifier_ == resource->identifier && selected_resource_ == resource) + { + selected_resource.bounding_rect = block_rect; + selected_resource.resource = resource; + selected_resource.is_visible = true; + } + } + } + } + } + + // Move onto next set of subslices + else + { + QMutableMapIterator next_it(cluster.sub_clusters); + while (next_it.hasNext()) + { + next_it.next(); + + ResourceCluster& sub_cluster = next_it.value(); + + PaintClusterChildren(painter, sub_cluster, hovered_resource, selected_resource); + } + } +} + +void RMVTreeMapBlocks::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + TreeMapBlockData hovered_block = {}; + TreeMapBlockData selected_block = {}; + + PaintClusterChildren(painter, clusters_[kSliceTypeNone], hovered_block, selected_block); + + PaintClusterParents(painter, clusters_[kSliceTypeNone]); + + if (hovered_block.resource && selected_block.resource && hovered_block.resource->identifier == selected_block.resource->identifier && + hovered_block.resource->bound_allocation == selected_block.resource->bound_allocation) + { + // decide what to do if a resource is clicked on + if ((hovered_block.is_visible == true) && (selected_block.is_visible == true)) + { + QPen pen; + pen.setBrush(Qt::black); + pen.setWidth(ScalingManager::Get().Scaled(2)); + painter->setPen(pen); + painter->setBrush(colorizer_->GetColor(selected_block.resource->bound_allocation, selected_block.resource).dark(rmv::kHoverDarkenColor)); + painter->drawRect(selected_block.bounding_rect); + } + } + else + { + if (hovered_block.is_visible == true) + { + if (hovered_block.resource->identifier != 0 || hovered_block.resource == hovered_resource_) + { + painter->setPen(Qt::NoPen); + painter->setBrush(colorizer_->GetColor(hovered_block.resource->bound_allocation, hovered_block.resource).dark(rmv::kHoverDarkenColor)); + painter->drawRect(hovered_block.bounding_rect); + } + } + + if (selected_block.is_visible == true) + { + QPen pen; + pen.setBrush(Qt::black); + pen.setWidth(ScalingManager::Get().Scaled(2)); + painter->setPen(pen); + painter->setBrush(colorizer_->GetColor(selected_block.resource->bound_allocation, selected_block.resource)); + painter->drawRect(selected_block.bounding_rect); + } + } +} + +bool RMVTreeMapBlocks::FindBlockData(ResourceCluster& cluster, QPointF user_location, RmtResourceIdentifier& resource_identifier, const RmtResource*& resource) +{ + resource_identifier = 0; + + bool result = false; + + // Move onto next levels + QMutableMapIterator next_it(cluster.sub_clusters); + while (next_it.hasNext()) + { + next_it.next(); + + ResourceCluster& sub_cluster = next_it.value(); + + result = FindBlockData(sub_cluster, user_location, resource_identifier, resource); + + if (result == true) + { + return true; + } + } + + // Only perform search at bottom-most level + if (cluster.sub_clusters.size() == 0) + { + QMutableMapIterator search_it(cluster.alloc_geometry_map); + while (search_it.hasNext()) + { + search_it.next(); + + const RmtResource* res = search_it.key(); + const QRectF& bounding_rect = search_it.value(); + + if (user_location.x() > bounding_rect.left() && user_location.x() < bounding_rect.right()) + { + if (user_location.y() > bounding_rect.top() && user_location.y() < bounding_rect.bottom()) + { + resource_identifier = res->identifier; + resource = res; + + result = true; + break; + } + } + } + } + + return result; +} + +void RMVTreeMapBlocks::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + setCursor(Qt::PointingHandCursor); + + FindBlockData(clusters_[kSliceTypeNone], event->pos(), hovered_resource_identifier_, hovered_resource_); + + update(); +} + +void RMVTreeMapBlocks::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + + hovered_resource_identifier_ = 0; + hovered_resource_ = nullptr; + + update(); +} + +void RMVTreeMapBlocks::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + const bool found_resource = FindBlockData(clusters_[kSliceTypeNone], event->pos(), selected_resource_identifier_, selected_resource_); + update(); + + if (!found_resource) + { + return; + } + + bool broadcast = true; + + if (selected_resource_identifier_ != 0) + { + emit ResourceSelected(selected_resource_identifier_, broadcast, false); + } + else if (selected_resource_ != nullptr) + { + emit UnboundResourceSelected(selected_resource_, broadcast, false); + } +} + +void RMVTreeMapBlocks::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + if (selected_resource_identifier_ != 0) + { + emit ResourceSelected(selected_resource_identifier_, true, true); + } + else if (selected_resource_ != nullptr) + { + emit UnboundResourceSelected(selected_resource_, true, true); + } +} + +void RMVTreeMapBlocks::UpdateDimensions(int width, int height) +{ + config_.width = width; + config_.height = height; +} + +int32_t RMVTreeMapBlocks::ScaledHeight() const +{ + return ScalingManager::Get().Scaled(config_.height); +} + +int32_t RMVTreeMapBlocks::ScaledWidth() const +{ + return ScalingManager::Get().Scaled(config_.width); +} + +void RMVTreeMapBlocks::ResetSelections() +{ + hovered_resource_identifier_ = 0; + selected_resource_identifier_ = 0; + hovered_resource_ = nullptr; + selected_resource_ = nullptr; +} + +void RMVTreeMapBlocks::Reset() +{ + clusters_.clear(); +} + +double RMVTreeMapBlocks::CalculateAspectRatio(double width, double height) +{ + return RMT_MAXIMUM(width / height, height / width); +} + +bool RMVTreeMapBlocks::ShouldDrawVertically(double width, double height) +{ + return (width > height); +} + +void RMVTreeMapBlocks::DumpCut(const CutData& existing_cut, uint32_t offset_x, uint32_t offset_y, AllocGeometryMap& alloc_geometry_map) const +{ + // Dump all the rectangles from the cut into the allocation rectangles. + if (!existing_cut.is_null) + { + for (int32_t current_allocation_index = 0; current_allocation_index < existing_cut.resources.size(); ++current_allocation_index) + { + bool visible_rects = true; + + for (int i = 0; i < existing_cut.rectangles.size(); i++) + { + if (existing_cut.rectangles[i].width() < 1 || existing_cut.rectangles[i].height() < 1) + { + visible_rects = false; + break; + } + } + + if (visible_rects == true) + { + for (int i = 0; i < existing_cut.rectangles.size(); i++) + { + QRectF newBound = existing_cut.rectangles[i]; + newBound.translate(offset_x, offset_y); + + alloc_geometry_map[existing_cut.resources[i]] = newBound; + } + } + } + } +} + +void RMVTreeMapBlocks::SelectResource(RmtResourceIdentifier resource_identifier) +{ + selected_resource_identifier_ = resource_identifier; + selected_resource_ = nullptr; + + update(); +} + +void RMVTreeMapBlocks::GenerateTreeMapRects(QVector& resources, + uint64_t total_size, + uint32_t view_width, + uint32_t view_height, + uint32_t offset_x, + uint32_t offset_y, + AllocGeometryMap& alloc_geometry_map) +{ + if (resources.isEmpty() == true) + { + return; + } + + // Work out how the bytes map to pixels. + QRectF draw_space(0, 0, view_width, view_height); + double bytes_per_pixel = (double)total_size / (double)(draw_space.width() * draw_space.height()); + + CutData existing_cut = {}; + existing_cut.is_null = true; + + for (int32_t i = 0; i < resources.size(); i++) + { + const RmtResource* resource = resources[i]; + + if (existing_cut.is_null == false) + { + int64_t attempted_cut_size = existing_cut.size_in_bytes + resource->size_in_bytes; + double total_cut_area = attempted_cut_size / bytes_per_pixel; + double total_cut_width = total_cut_area / existing_cut.bounding_rect.height(); + double total_cut_height = total_cut_area / existing_cut.bounding_rect.width(); + + if (existing_cut.is_vertical) + { + // Does this improve aspect ratio of most significant rectangle? + double primary_cut_area = existing_cut.resources[0]->size_in_bytes / bytes_per_pixel; + double primary_cut_new_height = primary_cut_area / total_cut_width; + double existing_aspect_ratio = CalculateAspectRatio(existing_cut.rectangles[0].width(), existing_cut.rectangles[0].height()); + double new_aspect_ratio = CalculateAspectRatio(total_cut_width, primary_cut_new_height); + double existing_error = fabs(existing_aspect_ratio - 1.0); + double new_error = fabs(new_aspect_ratio - 1.0); + + // Check if we should accept the cut. This is only true if the most + // significant rectangle is now closer to the perfect aspect ratio of 1. + if (new_error < existing_error) + { + float currentX = existing_cut.bounding_rect.x(); + float currentY = existing_cut.bounding_rect.y(); + + // To accept the cut we have to re-evaluate all rectangles in the existing cut to account for new widths. + for (int32_t currentAllocationIndex = 0; currentAllocationIndex < existing_cut.resources.size(); ++currentAllocationIndex) + { + double allocateArea = existing_cut.resources[currentAllocationIndex]->size_in_bytes / bytes_per_pixel; + double newAllocationWidth = total_cut_width; + double newAllocationHeight = allocateArea / total_cut_width; + + QRectF newAllocationRectangle(currentX, currentY, (float)newAllocationWidth, (float)newAllocationHeight); + + // Update the rectangle. + existing_cut.rectangles[currentAllocationIndex] = newAllocationRectangle; + + // Update current X and Y. + currentX = newAllocationRectangle.x(); + currentY = newAllocationRectangle.bottom(); + } + + // Add the new cut will always occupy bottom of the cut. + QRectF allocationRectangle(currentX, currentY, total_cut_width, (static_cast(view_height) - currentY)); + existing_cut.rectangles.push_back(allocationRectangle); + existing_cut.resources.push_back(resource); + + // Update the exist cut's bounding area. + existing_cut.bounding_rect = + QRectF(existing_cut.bounding_rect.x(), existing_cut.bounding_rect.y(), (float)total_cut_width, existing_cut.bounding_rect.height()); + + existing_cut.size_in_bytes = attempted_cut_size; + + // Update draw space. + draw_space.setX(existing_cut.bounding_rect.right()); + + // Move to next allocation. + continue; + } + } + else + { + // Does this improve aspect ratio of most significant rectangle? + double primary_cut_area = existing_cut.resources[0]->size_in_bytes / bytes_per_pixel; + double primary_cut_new_width = primary_cut_area / total_cut_height; + double existing_aspect_ratio = CalculateAspectRatio(existing_cut.rectangles[0].width(), existing_cut.rectangles[0].height()); + double new_aspect_ratio = CalculateAspectRatio(primary_cut_new_width, total_cut_height); + double existing_error = fabs(existing_aspect_ratio - 1.0); + double new_error = fabs(new_aspect_ratio - 1.0); + + // Check if we should accept the cut. This is only true if the most + // significant rectangle is now closer to the perfect aspect ratio of 1. + if (new_error < existing_error) + { + float currentX = existing_cut.bounding_rect.x(); + float currentY = existing_cut.bounding_rect.y(); + + // To accept the cut we have to re-evaluate all rectangles in the existing cut to account for new widths. + for (int32_t currentAllocationIndex = 0; currentAllocationIndex < existing_cut.resources.size(); ++currentAllocationIndex) + { + double allocateArea = existing_cut.resources[currentAllocationIndex]->size_in_bytes / bytes_per_pixel; + double newAllocationWidth = allocateArea / total_cut_height; + double newAllocationHeight = total_cut_height; + QRectF newAllocationRectangle(currentX, currentY, (int32_t)newAllocationWidth, (int32_t)newAllocationHeight); + + // Update the rectangle. + existing_cut.rectangles[currentAllocationIndex] = newAllocationRectangle; + + // Update current X and Y. + currentX = newAllocationRectangle.right(); + currentY = newAllocationRectangle.y(); + } + + // Add the new cut will always occupy bottom of the cut. + QRectF allocationRectangle(currentX, currentY, (static_cast(view_width) - currentX), total_cut_height); + existing_cut.rectangles.push_back(allocationRectangle); + existing_cut.resources.push_back(resource); + + // Update the exist cut's bounding area. + existing_cut.bounding_rect = + QRectF(existing_cut.bounding_rect.x(), existing_cut.bounding_rect.y(), existing_cut.bounding_rect.width(), (float)total_cut_height); + existing_cut.size_in_bytes = attempted_cut_size; + + // Update draw space. + draw_space.setY(existing_cut.bounding_rect.bottom()); + + // Move to next allocation. + continue; + } + } + } + + DumpCut(existing_cut, offset_x, offset_y, alloc_geometry_map); + existing_cut.is_null = true; + + // Fall back to making a new cut. + if (ShouldDrawVertically(draw_space.width(), draw_space.height())) + { + double area = (double)resource->size_in_bytes / bytes_per_pixel; + double width = area / draw_space.height(); + QRectF allocation_rectangle(draw_space.x(), draw_space.y(), (int32_t)width, draw_space.height()); + + // Create a new cut. + existing_cut = {}; + existing_cut.is_null = false; + existing_cut.bounding_rect = allocation_rectangle; + existing_cut.is_vertical = true; + existing_cut.size_in_bytes = resource->size_in_bytes; + existing_cut.rectangles.push_back(allocation_rectangle); + existing_cut.resources.push_back(resource); + + // Update draw space to remove what we just used. + draw_space.setX(allocation_rectangle.right()); + } + else + { + double area = (double)resource->size_in_bytes / bytes_per_pixel; + double height = area / draw_space.width(); + QRectF allocation_rectangle(draw_space.x(), draw_space.y(), draw_space.width(), (int32_t)height); + + // Create a new cut. + existing_cut = {}; + existing_cut.is_null = false; + existing_cut.bounding_rect = allocation_rectangle; + existing_cut.is_vertical = false; + existing_cut.size_in_bytes = resource->size_in_bytes; + existing_cut.rectangles.push_back(allocation_rectangle); + existing_cut.resources.push_back(resource); + + // Update draw space to remove what we just used. + draw_space.setY(allocation_rectangle.bottom()); + } + } + + DumpCut(existing_cut, offset_x, offset_y, alloc_geometry_map); + existing_cut.is_null = true; +} + +void RMVTreeMapBlocks::GenerateTreemap(const rmv::ResourceOverviewModel* overview_model, + const TreeMapModels& tree_map_models, + uint32_t view_width, + uint32_t view_height) +{ + const TraceManager& trace_manager = TraceManager::Get(); + RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + + if (trace_manager.DataSetValid() && open_snapshot != nullptr) + { + clusters_.clear(); + for (int32_t i = 0; i < unbound_resources_.size(); i++) + { + delete unbound_resources_[i]; + } + unbound_resources_.clear(); + + ResourceCluster& parent_cluster = clusters_[kSliceTypeNone]; + const int32_t allocation_count = open_snapshot->virtual_allocation_list.allocation_count; + + // calculate how much memory is to be displayed + uint64_t total_memory = 0; + for (int32_t i = 0; i < allocation_count; i++) + { + RmtVirtualAllocation* current_virtual_allocation = &open_snapshot->virtual_allocation_list.allocation_details[i]; + + if (tree_map_models.preferred_heap_model->ItemInList(current_virtual_allocation->heap_preferences[0]) == false) + { + continue; + } + + for (int32_t j = 0; j < current_virtual_allocation->resource_count; j++) + { + RmtResource* resource = current_virtual_allocation->resources[j]; + if (ResourceFiltered(overview_model, tree_map_models, open_snapshot, resource) == true) + { + total_memory += resource->size_in_bytes; + } + } + + if (tree_map_models.resource_usage_model->ItemInList(kRmtResourceUsageTypeFree) == false) + { + continue; + } + + for (int32_t current_unbound_region_index = 0; current_unbound_region_index < current_virtual_allocation->unbound_memory_region_count; + ++current_unbound_region_index) + { + // add any unbound memory + const RmtMemoryRegion* current_unbound_region = ¤t_virtual_allocation->unbound_memory_regions[current_unbound_region_index]; + if (current_unbound_region->size == 0) + { + continue; + } + if (overview_model->IsSizeInRange(current_unbound_region->size) == false) + { + continue; + } + total_memory += current_unbound_region->size; + } + } + + const double bytes_per_pixel = static_cast(total_memory) / (static_cast(view_width) * view_height); + for (int32_t i = 0; i < allocation_count; i++) + { + RmtVirtualAllocation* current_virtual_allocation = &open_snapshot->virtual_allocation_list.allocation_details[i]; + + if (tree_map_models.preferred_heap_model->ItemInList(current_virtual_allocation->heap_preferences[0]) == false) + { + continue; + } + + for (int32_t j = 0; j < current_virtual_allocation->resource_count; j++) + { + RmtResource* resource = current_virtual_allocation->resources[j]; + if (ResourceFiltered(overview_model, tree_map_models, open_snapshot, resource) == true) + { + const double total_cut_area = resource->size_in_bytes / bytes_per_pixel; + + // Only include allocations that could actually be visible + if (total_cut_area >= kMinArea) + { + parent_cluster.amount += resource->size_in_bytes; + parent_cluster.sorted_resources.push_back(resource); + } + } + } + + if (tree_map_models.resource_usage_model->ItemInList(kRmtResourceUsageTypeFree) == false) + { + continue; + } + + for (int32_t current_unbound_region_index = 0; current_unbound_region_index < current_virtual_allocation->unbound_memory_region_count; + ++current_unbound_region_index) + { + // add any unbound memory + const RmtMemoryRegion* current_unbound_region = ¤t_virtual_allocation->unbound_memory_regions[current_unbound_region_index]; + if (current_unbound_region->size == 0) + { + continue; + } + if (overview_model->IsSizeInRange(current_unbound_region->size) == false) + { + continue; + } + const double total_cut_area = current_unbound_region->size / bytes_per_pixel; + if (total_cut_area >= kMinArea) + { + // Create a temporary 'unbound' resource so the unbound resource can be sorted along with other resources + // in the tree map. All unbound resources have a resource identifier of 0. Their base address and size are + // copied from their unbound region data. + RmtResource* unbound_resource = new RmtResource(); + unbound_resource->identifier = 0; + unbound_resource->size_in_bytes = current_unbound_region->size; + unbound_resource->address = current_virtual_allocation->base_address + current_unbound_region->offset; + unbound_resource->bound_allocation = current_virtual_allocation; + unbound_resource->resource_type = kRmtResourceTypeCount; +#ifdef _DEBUG + strcpy_s(unbound_resource->name, RMT_MAXIMUM_NAME_LENGTH, "unbound"); +#endif + // keep track of the unbound resource + unbound_resources_.push_back(unbound_resource); + + parent_cluster.amount += current_unbound_region->size; + parent_cluster.sorted_resources.push_back(unbound_resource); + } + } + } + + std::stable_sort(parent_cluster.sorted_resources.begin(), parent_cluster.sorted_resources.end(), SortResourcesBySizeFunc); + + // Something actually selected in the UI + if (slice_types_.empty() == false) + { + FillClusterResources(parent_cluster, slice_types_, 0, slice_types_.size()); + } + + // Nothing selected, so just show all allocations without slicing + else + { + for (int i = 0; i < parent_cluster.sorted_resources.size(); i++) + { + const RmtResource* resource = parent_cluster.sorted_resources[i]; + + parent_cluster.sub_clusters[kSliceTypeNone].amount += resource->size_in_bytes; + parent_cluster.sub_clusters[kSliceTypeNone].sorted_resources.push_back(resource); + } + } + FillClusterGeometry(clusters_[kSliceTypeNone], view_width, view_height, 0, 0); + } +} + +bool RMVTreeMapBlocks::ResourceFiltered(const rmv::ResourceOverviewModel* overview_model, + const TreeMapModels& tree_map_models, + const RmtDataSnapshot* snapshot, + const RmtResource* resource) +{ + if (tree_map_models.actual_heap_model->ItemInList(RmtResourceGetActualHeap(snapshot, resource)) == false) + { + return false; + } + if (tree_map_models.resource_usage_model->ItemInList(RmtResourceGetUsageType(resource)) == false) + { + return false; + } + if (overview_model->IsSizeInRange(resource->size_in_bytes) == false) + { + return false; + } + return true; +} + +void RMVTreeMapBlocks::FillClusterGeometry(ResourceCluster& parent_cluster, int parent_width, int parent_height, int parent_offset_x, int parent_offset_y) +{ + parent_cluster.geometry = QRectF(parent_offset_x, parent_offset_y, parent_width, parent_height); + + if (parent_cluster.sub_clusters.size() == 0) + { + return; + } + + // Helper map to associate a slice key to a resource + QMap sliceIdToAlloc; // probably can just be an array of vectors. + + // Create some temporary allocations so we can later compute geometry for parent bounds + uint64_t temp_parent_allocs_size = 0; + QVector temp_resources; + QMapIterator it_1(parent_cluster.sub_clusters); + while (it_1.hasNext()) + { + it_1.next(); + + uint32_t sliceType = it_1.key(); + ResourceCluster subCluster = it_1.value(); + + RmtResource* temp_resource = new RmtResource(); + temp_resource->size_in_bytes = subCluster.amount; + temp_resources.push_back(temp_resource); + temp_parent_allocs_size += subCluster.amount; + + // Fill out helper map + sliceIdToAlloc[sliceType] = temp_resource; + } + + std::stable_sort(temp_resources.begin(), temp_resources.end(), SortResourcesBySizeFunc); + + // Figure out geometry for parent bounds + GenerateTreeMapRects( + temp_resources, temp_parent_allocs_size, parent_width, parent_height, parent_offset_x, parent_offset_y, parent_cluster.alloc_geometry_map); + + // Clean up temp RmvMemoryBlockAllocations + for (int32_t i = 0; i < temp_resources.size(); i++) + { + delete temp_resources[i]; + } + + // Figure out geometry for sub-clusters, but bound by parent bounds + QMutableMapIterator it_2(parent_cluster.sub_clusters); + while (it_2.hasNext()) + { + it_2.next(); + + uint32_t sliceType = it_2.key(); + ResourceCluster& subCluster = it_2.value(); + + QRectF& bounding_rect = parent_cluster.alloc_geometry_map[sliceIdToAlloc[sliceType]]; + + // Figure out child geometry + GenerateTreeMapRects(subCluster.sorted_resources, + subCluster.amount, + bounding_rect.width(), + bounding_rect.height(), + bounding_rect.left(), + bounding_rect.top(), + subCluster.alloc_geometry_map); + + // Next child + FillClusterGeometry(subCluster, bounding_rect.width(), bounding_rect.height(), bounding_rect.left(), bounding_rect.top()); + } +} + +int32_t RMVTreeMapBlocks::GetSliceCount(const SliceType slice_type, const RmtDataSnapshot* snapshot) const +{ + switch (slice_type) + { + case kSliceTypeResourceUsageType: + return kRmtResourceUsageTypeCount; + + case kSliceTypeResourceCreateAge: + case kSliceTypeResourceBindAge: + case kSliceTypeAllocationAge: + return colorizer_->GetNumAgeBuckets(); + + case kSliceTypePreferredHeap: + case kSliceTypeActualHeap: + return kRmtHeapTypeCount; + + case kSliceTypeVirtualAllocation: + return snapshot->virtual_allocation_list.allocation_count; + + case kSliceTypeCpuMapped: + case kSliceTypeInPreferredHeap: + return kBooleanCount; + + case kSliceTypeResourceCommitType: + return kRmtCommitTypeCount; + + case kSliceTypeResourceOwner: + return kRmtOwnerTypeCount; + + default: + RMT_ASSERT(false); + break; + } + return 0; +} + +void RMVTreeMapBlocks::AddClusterResource(ResourceCluster& parent_cluster, const int32_t slice_index, const RmtResource* resource, bool& resource_added) const +{ + parent_cluster.sub_clusters[slice_index].amount += resource->size_in_bytes; + parent_cluster.sub_clusters[slice_index].sorted_resources.push_back(resource); + resource_added = true; +} + +void RMVTreeMapBlocks::FilterResourceUsageType(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + RMT_ASSERT(snapshot); + if (resource != nullptr && RmtResourceGetUsageType(resource) == slice_index) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterResourceCreateAge(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + RMT_ASSERT(snapshot); + if (resource != nullptr && colorizer_->GetAgeIndex(resource->create_time) == slice_index) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterResourceBindAge(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + RMT_ASSERT(snapshot); + if (resource != nullptr && colorizer_->GetAgeIndex(resource->bind_time) == slice_index) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterAllocationAge(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + RMT_ASSERT(snapshot); + if (resource != nullptr && resource->bound_allocation != nullptr && colorizer_->GetAgeIndex(resource->bound_allocation->timestamp) == slice_index) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterPreferredHeap(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + RMT_ASSERT(snapshot); + if (resource != nullptr && resource->bound_allocation->heap_preferences[0] == slice_index) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterCpuMapped(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + RMT_ASSERT(snapshot); + if (((resource->bound_allocation->flags & kRmtAllocationDetailIsCpuMapped) == kRmtAllocationDetailIsCpuMapped) && slice_index == 1) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } + else if (((resource->bound_allocation->flags & kRmtAllocationDetailIsCpuMapped) != kRmtAllocationDetailIsCpuMapped) && slice_index == 0) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterResourceCommitType(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + RMT_ASSERT(snapshot); + if (resource != nullptr && resource->commit_type == slice_index) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterResourceOwner(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + RMT_ASSERT(snapshot); + if (resource != nullptr && resource->owner_type == slice_index) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterActualHeap(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + if (resource != nullptr && RmtResourceGetActualHeap(snapshot, resource) == slice_index) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FilterInPreferredHeap(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const +{ + if (!resource || !resource->bound_allocation || resource->resource_type == kRmtResourceTypeCount) + return; + + uint64_t memory_segment_histogram[kRmtResourceBackingStorageCount] = {0}; + RmtResourceGetBackingStorageHistogram(snapshot, resource, memory_segment_histogram); + + const RmtHeapType preferred_heap = resource->bound_allocation->heap_preferences[0]; + if (memory_segment_histogram[preferred_heap] != resource->size_in_bytes && slice_index == 0) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } + else if (memory_segment_histogram[preferred_heap] == resource->size_in_bytes && slice_index == 1) + { + AddClusterResource(parent_cluster, slice_index, resource, resource_added); + } +} + +void RMVTreeMapBlocks::FillClusterResources(ResourceCluster& parent_cluster, QVector& target_slice_types, int level, int max_level) +{ + RMT_ASSERT(level <= max_level); + + if (level == max_level) + { + return; + } + + RMT_ASSERT(level < target_slice_types.size()); + SliceType slice_type = target_slice_types[level]; + + const TraceManager& trace_manager = TraceManager::Get(); + const RmtDataSnapshot* snapshot = trace_manager.GetOpenSnapshot(); + if (!trace_manager.DataSetValid() || snapshot == nullptr) + { + return; + } + + int32_t slice_count = GetSliceCount(slice_type, snapshot); + + // Special case the virtual allocation. + if (slice_type == kSliceTypeVirtualAllocation) + { + for (int32_t slice_index = 0; slice_index < slice_count; slice_index++) + { + const RmtVirtualAllocation* virtual_allocation = &snapshot->virtual_allocation_list.allocation_details[slice_index]; + bool found_allocs = false; + + for (int32_t resource_index = 0; resource_index < parent_cluster.sorted_resources.size(); resource_index++) + { + const RmtResource* resource = parent_cluster.sorted_resources[resource_index]; + if (resource != nullptr && resource->bound_allocation == virtual_allocation) + { + AddClusterResource(parent_cluster, slice_index, resource, found_allocs); + } + } + + if (found_allocs) + { + FillClusterResources(parent_cluster.sub_clusters[slice_index], target_slice_types, level + 1, max_level); + } + } + } + + else + { + typedef void (RMVTreeMapBlocks::*FilterFunction)( + ResourceCluster & parent_cluster, int32_t slice_index, const RmtDataSnapshot* snapshot, const RmtResource* resource, bool& found_allocs) const; + + FilterFunction fn = nullptr; + + switch (slice_type) + { + case kSliceTypeResourceUsageType: + fn = &RMVTreeMapBlocks::FilterResourceUsageType; + break; + + case kSliceTypeResourceCreateAge: + fn = &RMVTreeMapBlocks::FilterResourceCreateAge; + break; + + case kSliceTypeResourceBindAge: + fn = &RMVTreeMapBlocks::FilterResourceBindAge; + break; + + case kSliceTypeAllocationAge: + fn = &RMVTreeMapBlocks::FilterAllocationAge; + break; + + case kSliceTypePreferredHeap: + fn = &RMVTreeMapBlocks::FilterPreferredHeap; + break; + + case kSliceTypeCpuMapped: + fn = &RMVTreeMapBlocks::FilterCpuMapped; + break; + + case kSliceTypeResourceCommitType: + fn = &RMVTreeMapBlocks::FilterResourceCommitType; + break; + + case kSliceTypeResourceOwner: + fn = &RMVTreeMapBlocks::FilterResourceOwner; + break; + + case kSliceTypeActualHeap: + fn = &RMVTreeMapBlocks::FilterActualHeap; + break; + + case kSliceTypeInPreferredHeap: + fn = &RMVTreeMapBlocks::FilterInPreferredHeap; + break; + + default: + break; + } + + if (fn != nullptr) + { + for (int32_t slice_index = 0; slice_index < slice_count; slice_index++) + { + bool found_allocs = false; + + for (int32_t resource_index = 0; resource_index < parent_cluster.sorted_resources.size(); resource_index++) + { + const RmtResource* resource = parent_cluster.sorted_resources[resource_index]; + (this->*fn)(parent_cluster, slice_index, snapshot, resource, found_allocs); + } + + if (found_allocs) + { + FillClusterResources(parent_cluster.sub_clusters[slice_index], target_slice_types, level + 1, max_level); + } + } + } + } +} + +void RMVTreeMapBlocks::UpdateSliceTypes(const QVector& slice_types) +{ + slice_types_ = slice_types; +} \ No newline at end of file diff --git a/source/frontend/views/custom_widgets/rmv_tree_map_blocks.h b/source/frontend/views/custom_widgets/rmv_tree_map_blocks.h new file mode 100644 index 0000000..74938e4 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_tree_map_blocks.h @@ -0,0 +1,414 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a tree map block collection +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_TREE_MAP_BLOCKS_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_TREE_MAP_BLOCKS_H_ + +#include + +#include "rmt_resource_list.h" + +#include "models/combo_box_model.h" +#include "models/heap_combo_box_model.h" +#include "models/resource_usage_combo_box_model.h" +#include "models/snapshot/resource_overview_model.h" +#include "views/colorizer.h" + +/// A single block seen in the tree map. +struct TreeMapBlockData +{ + const RmtResource* resource; ///< The represented resource. + QRectF bounding_rect; ///< The offset and size. + bool is_visible; ///< Rendered or not. +}; + +/// Basic rendering information about the tree map. +struct RMVTreeMapBlocksConfig +{ + int32_t width; ///< Widget width. + int32_t height; ///< Widget height. +}; + +/// Holds information about how rectangles are sliced during tree map generation. +struct CutData +{ + QVector rectangles; ///< Children rects. + QVector resources; ///< Children allocations. + QRectF bounding_rect; ///< Encompassing rect. + bool is_vertical; ///< Is it vertical or horizontal. + int64_t size_in_bytes; ///< How much mem does it represent. + bool is_null; ///< Good or bad. +}; + +typedef QMap AllocGeometryMap; + +/// Describes a cluster, which is a square with potentially other child clusters. +struct ResourceCluster +{ + ResourceCluster() + : amount(0) + { + } + + AllocGeometryMap alloc_geometry_map; ///< Association of allocation pointers to their rendered geometry. + QVector sorted_resources; ///< Array of all child allocations, sorted by size. + QMap sub_clusters; ///< Collection of children clusters. + uint64_t amount; ///< Total size of this cluster. + QRectF geometry; ///< Encompassing geometry. +}; + +/// Various models used to filter the tree map. +struct TreeMapModels +{ + rmv::HeapComboBoxModel* preferred_heap_model; ///< The preferred heap model. + rmv::HeapComboBoxModel* actual_heap_model; ///< The actual heap model. + rmv::ResourceUsageComboBoxModel* resource_usage_model; ///< The resource usage model. +}; + +typedef QMap ClusterMap; + +/// Container class for a widget that manages TreeMap rendering. +class RMVTreeMapBlocks : public QGraphicsObject +{ + Q_OBJECT + +public: + /// Enum of slicing mode types. + enum SliceType + { + kSliceTypeNone, + kSliceTypeResourceUsageType, + kSliceTypeResourceCreateAge, + kSliceTypeResourceBindAge, + kSliceTypeAllocationAge, + kSliceTypeVirtualAllocation, + kSliceTypePreferredHeap, + kSliceTypeActualHeap, + kSliceTypeCpuMapped, + kSliceTypeResourceCommitType, + kSliceTypeResourceOwner, + kSliceTypeInPreferredHeap, + + kSliceTypeCount, + }; + + /// Constructor. + /// \param config A configuration struct for this object. + explicit RMVTreeMapBlocks(const RMVTreeMapBlocksConfig& config); + + /// Destructor. + virtual ~RMVTreeMapBlocks(); + + /// Mouse hover over event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse hover leave event. + /// \param event the QGraphicsSceneHoverEvent. + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) Q_DECL_OVERRIDE; + + /// Mouse press event. + /// \param event the QGraphicsSceneMouseEvent. + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Mouse double click event. + /// \param event the QGraphicsSceneMouseEvent. + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; + + /// Implementation of Qt's bounding volume for this item. + /// \return The item's bounding rectangle. + virtual QRectF boundingRect() const Q_DECL_OVERRIDE; + + /// Implementation of Qt's paint for this item. + /// \param painter The painter object to use. + /// \param option Provides style options for the item, such as its state, exposed area and its level-of-detail hints. + /// \param widget Points to the widget that is being painted on if specified. + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) Q_DECL_OVERRIDE; + + /// Parse dataset and generate rectangle positions. + /// \param overview_model The model data for the resource overview model. + /// \param tree_map_models The models used to filter the resource data. + /// \param view_width available width. + /// \param view_height available height. + void GenerateTreemap(const rmv::ResourceOverviewModel* overview_model, const TreeMapModels& tree_map_models, uint32_t view_width, uint32_t view_height); + + /// Reset state. + void Reset(); + + /// Reset selections. + void ResetSelections(); + + /// Select a resource. + /// \param resource_identifier the resource identifier. + void SelectResource(RmtResourceIdentifier resource_identifier); + + /// Update the dimensions. + /// \param width the width. + /// \param height the height. + void UpdateDimensions(int width, int height); + + /// Update slicing types coming in from UI. + /// \param slice_types types in a vector. + void UpdateSliceTypes(const QVector& slice_types); + + /// Set the colorizer so that the widget knows which colors to draw the resources. + /// \param colorizer The colorizer to use. + void SetColorizer(const Colorizer* colorizer); + +signals: + /// Signal that a resource has been selected. + /// \param resource_identifier The selected resource. + /// \param broadcast If true, broadcast the ResourceSelected message to any other panes listening. + /// \param navigate_to_pane If true, indicate that navigation to another pane is requested. + /// It is up the the slot to decide how to process the second 2 arguments (which panes to + /// broadcast to and which pane to navigate to). + void ResourceSelected(RmtResourceIdentifier resource_identifier, bool broadcast, bool navigate_to_pane); + + /// signal that an unbound resource has been selected. + /// \param unbound_resource The selected unbound resource. + /// \param broadcast If true, broadcast the ResourceSelected message to any other panes listening + /// \param navigate_to_pane If true, indicate that navigation to another pane is requested. + /// It is up the the slot to decide how to process the second 2 arguments (which panes to + /// broadcast to and which pane to navigate to). + void UnboundResourceSelected(const RmtResource* unbound_resource, bool broadcast, bool navigate_to_pane); + +private: + /// Calculate aspect ratio. + /// \param width width. + /// \param height height. + /// \return aspect ratio. + double CalculateAspectRatio(double width, double height); + + /// Add a cut to the list of rectangles that will be rendered out. + /// \param existing_cut the cut. + /// \param offset_x x offset. + /// \param offset_y y offset. + /// \param alloc_geometry_map the geometry map. + void DumpCut(const CutData& existing_cut, uint32_t offset_x, uint32_t offset_y, AllocGeometryMap& alloc_geometry_map) const; + + /// Fill in a cluster with resources that fall within it. + /// \param parent_cluster the cluster process. + /// \param target_slice_types a vector of the currently selected slicing types. + /// \param level current recursion level. + /// \param max_level max allowed depth. + void FillClusterResources(ResourceCluster& parent_cluster, QVector& target_slice_types, int level, int max_mevel); + + /// Compute geometry for a cluster. + /// \param parent_cluster the cluster to look at. + /// \param parent_width parent width. + /// \param parent_height parent height. + /// \param parent_offset_x starting X-offset. + /// \param parent_offset_y starting Y-offset. + void FillClusterGeometry(ResourceCluster& parent_cluster, int parent_width, int parent_height, int parent_offset_x, int parent_offset_y); + + /// Get block data given a set of coordinates. + /// \param cluster the cluster to look at. + /// \param user_location the position. + /// \param resource_identifier output resource identifier. + /// \param out_resource output parent resource. + bool FindBlockData(ResourceCluster& cluster, QPointF user_location, RmtResourceIdentifier& resource_identifier, const RmtResource*& resource); + + /// Workhorse function to calculate tree map geometry. + /// \param resources incoming resources to map out. + /// \param total_size total byte size. + /// \param view_width available width. + /// \param view_height available height. + /// \param offset_x x offset. + /// \param offset_y y offset. + /// \param alloc_geometry_map output data containing alloc offsets and sizes. + void GenerateTreeMapRects(QVector& resources, + uint64_t total_size, + uint32_t view_width, + uint32_t view_height, + uint32_t offset_x, + uint32_t offset_y, + AllocGeometryMap& alloc_geometry_map); + + /// Recursive paint function to draw borders around slicing modes. + /// \param painter Qt painter pointer. + /// \param cluster The current cluster we're rendering. + void PaintClusterParents(QPainter* painter, ResourceCluster& cluster); + + /// Recursive paint function to paint blocks inside each cluster. + /// \param painter Qt painter pointer. + /// \param cluster The current cluster we're rendering. + /// \param hovered_resource output hovered resource. + /// \param selected_resource output selected resource. + void PaintClusterChildren(QPainter* painter, ResourceCluster& cluster, TreeMapBlockData& hovered_resource, TreeMapBlockData& selected_resource); + + /// Get scaled height. + /// \return scaled height. + int32_t ScaledHeight() const; + + /// Get scaled width. + /// \return scaled width. + int32_t ScaledWidth() const; + + /// Determine if we should draw vertically. + /// \param width width. + /// \param height height. + /// \return true if should draw vertically. + bool ShouldDrawVertically(double width, double height); + + /// Apply the filters to the resource to see if it should be shown in the treemap. + /// \param overview_model The model data for the resource overview model. + /// \param tree_map_models The models used to filter the resource data. + /// \param snapshot The current snapshot. + /// \param resource The resource to test. + /// \return true if the resource should be included, false if not. + bool ResourceFiltered(const rmv::ResourceOverviewModel* overview_model, + const TreeMapModels& tree_map_models, + const RmtDataSnapshot* snapshot, + const RmtResource* resource); + + /// Get the slice count depending on the slicing mode. This is the number of slices needed to + /// show the data, for example, slicing by whether a resource is in its preferred heap would + /// return a count of 2 (those in the preferred heap and those not). + /// \param slice_type The slicing mode. + /// \param snapshot The currently opened snapshot. + /// \return The slicing count. + int32_t GetSliceCount(const SliceType slice_type, const RmtDataSnapshot* snapshot) const; + + /// Add a resource to the current cluster. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void AddClusterResource(ResourceCluster& parent_cluster, const int32_t slice_index, const RmtResource* resource, bool& resource_added) const; + + /// Filter to slice by resource usage type. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterResourceUsageType(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by resource create age. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterResourceCreateAge(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by resource bind age. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterResourceBindAge(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by allocation age. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterAllocationAge(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by preferred heap. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterPreferredHeap(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by whether a resource is CPU mapped. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterCpuMapped(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by resource commit type. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterResourceCommitType(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by resource owner. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterResourceOwner(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by actual heap. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterActualHeap(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + /// Filter to slice by whether a resource is in its preferred heap. + /// \param parent_cluster the parent cluster. + /// \param slice_index The slice index. + /// \param snapshot The currently opened snapshot. + /// \param resource The resource to add. + /// \param resource_added A variable that is updated to indicate that a resource has been added. + void FilterInPreferredHeap(ResourceCluster& parent_cluster, + int32_t slice_index, + const RmtDataSnapshot* snapshot, + const RmtResource* resource, + bool& resource_added) const; + + RMVTreeMapBlocksConfig config_; ///< Description of this widget. + RmtResourceIdentifier hovered_resource_identifier_; ///< Id of the allocation hovered over. + RmtResourceIdentifier selected_resource_identifier_; ///< Id of the selected allocation. + const RmtResource* hovered_resource_; ///< The hovered resource (incase the resource is unbound). + const RmtResource* selected_resource_; ///< The selected resource (incase the resource is unbound). + QMap clusters_; ///< The master data structure that holds all recursive block layouts. + QVector slice_types_; ///< Holds UI slicing selections. + const Colorizer* colorizer_; ///< The colorizer for deciding how to color the blocks. + QVector unbound_resources_; ///< A list of unbound resources. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_TREE_MAP_BLOCKS_H_ diff --git a/source/frontend/views/custom_widgets/rmv_tree_map_view.cpp b/source/frontend/views/custom_widgets/rmv_tree_map_view.cpp new file mode 100644 index 0000000..671a538 --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_tree_map_view.cpp @@ -0,0 +1,119 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of a tree map view +//============================================================================= + +#include "views/custom_widgets/rmv_tree_map_view.h" + +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" + +#include "models/message_manager.h" + +static const uint32_t kViewMargin = 8; + +RMVTreeMapView::RMVTreeMapView(QWidget* parent) + : QGraphicsView(parent) + , tree_map_models_(nullptr) + , overview_model_(nullptr) +{ + setMouseTracking(true); + + setFrameStyle(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + scene_ = new QGraphicsScene(); + setScene(scene_); + + RMVTreeMapBlocksConfig config = {}; + + blocks_ = new RMVTreeMapBlocks(config); + + scene_->addItem(blocks_); +} + +RMVTreeMapView::~RMVTreeMapView() +{ +} + +void RMVTreeMapView::mousePressEvent(QMouseEvent* event) +{ + QGraphicsView::mousePressEvent(event); +} + +void RMVTreeMapView::mouseMoveEvent(QMouseEvent* event) +{ + QGraphicsView::mouseMoveEvent(event); + +#if 0 + QPoint mouseCoords = mapFromGlobal(QCursor::pos()); + QPointF sceneCoords = mapToScene(mouseCoords); + qDebug() << "mouse: " << mouseCoords; + qDebug() << "sceneCoords: " << sceneCoords; + qDebug() << "---------"; +#endif +} + +void RMVTreeMapView::resizeEvent(QResizeEvent* event) +{ + QGraphicsView::resizeEvent(event); + + const uint32_t view_width = width(); + const uint32_t view_height = height(); + + const QRectF scene_rect = QRectF(1, 1, view_width - kViewMargin, view_height - kViewMargin); + scene_->setSceneRect(scene_rect); + blocks_->UpdateDimensions(view_width - kViewMargin, view_height - kViewMargin); + + UpdateTreeMap(); +} + +void RMVTreeMapView::SetModels(const rmv::ResourceOverviewModel* overview_model, const TreeMapModels* tree_map_models, const Colorizer* colorizer) +{ + overview_model_ = overview_model; + tree_map_models_ = tree_map_models; + blocks_->SetColorizer(colorizer); +} + +void RMVTreeMapView::UpdateTreeMap() +{ + RMT_ASSERT(tree_map_models_ != nullptr); + RMT_ASSERT(overview_model_ != nullptr); + if (tree_map_models_ != nullptr && overview_model_ != nullptr) + { + blocks_->GenerateTreemap(overview_model_, *tree_map_models_, width() - kViewMargin, height() - kViewMargin); + blocks_->update(); + } +} + +void RMVTreeMapView::Reset() +{ + blocks_->Reset(); +} + +void RMVTreeMapView::SelectResource(RmtResourceIdentifier resource_identifier) +{ + blocks_->SelectResource(resource_identifier); +} + +void RMVTreeMapView::UpdateColorCache() +{ + blocks_->update(); +} + +RMVTreeMapBlocks* RMVTreeMapView::BlocksWidget() +{ + return blocks_; +} + +void RMVTreeMapView::UpdateSliceTypes(const QVector& slice_types) +{ + blocks_->UpdateSliceTypes(slice_types); +} diff --git a/source/frontend/views/custom_widgets/rmv_tree_map_view.h b/source/frontend/views/custom_widgets/rmv_tree_map_view.h new file mode 100644 index 0000000..dfc749b --- /dev/null +++ b/source/frontend/views/custom_widgets/rmv_tree_map_view.h @@ -0,0 +1,80 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a tree map view +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_RMV_TREE_MAP_VIEW_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_RMV_TREE_MAP_VIEW_H_ + +#include +#include + +#include "rmt_resource_list.h" + +#include "models/snapshot/resource_overview_model.h" +#include "util/definitions.h" +#include "views/custom_widgets/rmv_tree_map_blocks.h" + +/// Holds and controls the entire queue timings visualization. +class RMVTreeMapView : public QGraphicsView +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The parent widget. + explicit RMVTreeMapView(QWidget* parent); + + /// Destructor. + virtual ~RMVTreeMapView(); + + /// Handle resizing. + /// \param event The resize event. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Detect a mouse press event. + /// \param event The mouse press event. + virtual void mousePressEvent(QMouseEvent* event) Q_DECL_OVERRIDE; + + /// Capture a mouse move event. + /// \param event The mouse move event. + virtual void mouseMoveEvent(QMouseEvent* event) Q_DECL_OVERRIDE; + + /// Set the models. + /// \param overview_model The resource overview model. + /// \param tree_map_models The models needed for the treemap. + /// \param colorizer The colorizer object to use. + void SetModels(const rmv::ResourceOverviewModel* overview_model, const TreeMapModels* tree_map_models, const Colorizer* colorizer); + + /// Update the treemap view. + void UpdateTreeMap(); + + /// Reset UI state. + void Reset(); + + /// Update the color cache. + void UpdateColorCache(); + + /// Return the blocks widget for upper-level Qt connection. + /// \return pointer to RMVTreeMapBlocks. + RMVTreeMapBlocks* BlocksWidget(); + + /// Update slicing types coming in from UI. + /// \param slice_types types in a vector. + void UpdateSliceTypes(const QVector& slice_types); + +public slots: + /// Select a resource. + /// \param resource_identifier The resource idemtofoer of the resource to select. + void SelectResource(RmtResourceIdentifier resource_identifier); + +private: + QGraphicsScene* scene_; ///< The graphics scene associated with this view. + RMVTreeMapBlocks* blocks_; ///< Pointer to the tree map blocks graphics object. + const TreeMapModels* tree_map_models_; ///< The models needed for the tree map. + const rmv::ResourceOverviewModel* overview_model_; ///< The resource overview model. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_RMV_TREE_MAP_VIEW_H_ diff --git a/source/frontend/views/custom_widgets/themes_and_colors_item_button.cpp b/source/frontend/views/custom_widgets/themes_and_colors_item_button.cpp new file mode 100644 index 0000000..31540b0 --- /dev/null +++ b/source/frontend/views/custom_widgets/themes_and_colors_item_button.cpp @@ -0,0 +1,67 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a helper class for custom button rendering for the +/// themes and colors pane. +//============================================================================= + +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "util/rmv_util.h" +#include "views/custom_widgets/themes_and_colors_item_button.h" + +ThemesAndColorsItemButton::ThemesAndColorsItemButton(QWidget* parent) + : ScaledPushButton(parent) +{ + setContentsMargins(10, 5, 10, 5); +} + +ThemesAndColorsItemButton::~ThemesAndColorsItemButton() +{ +} + +void ThemesAndColorsItemButton::SetColor(const QColor& color) +{ + button_color_ = color; + font_color_ = rmv_util::GetTextColorForBackground(color); + update(); +} + +void ThemesAndColorsItemButton::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + ScalingManager& sm = ScalingManager::Get(); + QPainter painter(this); + + const int position_adjust = sm.Scaled(1); + const int size_adjust = position_adjust * 2; + const int outline_width = sm.Scaled(2); + + // Rectangles used for drawing button and its border. + QRect r1(position_adjust, position_adjust, this->size().width() - size_adjust, this->size().height() - size_adjust); + QRect r2(r1.left() + outline_width, r1.top() + outline_width, r1.width() - (outline_width * 2), r1.height() - (outline_width * 2)); + QRect r3(r2.left(), r2.top(), r2.width() - 1, r2.height() - 1); + + if (this->isChecked() || this->underMouse()) + { + // Fill rect with black to form border. + painter.fillRect(r1, QBrush(Qt::black)); + painter.fillRect(r2, QBrush(button_color_)); + painter.setPen(QPen(Qt::white, 1)); + painter.drawRect(r3); + } + else + { + // Fill rect with black to form border. + painter.fillRect(r1, button_color_); + } + + // Draw text. + painter.setPen(QPen(font_color_, 1)); + painter.drawText(r1, Qt::AlignHCenter | Qt::AlignVCenter, this->text()); +} diff --git a/source/frontend/views/custom_widgets/themes_and_colors_item_button.h b/source/frontend/views/custom_widgets/themes_and_colors_item_button.h new file mode 100644 index 0000000..e7ea85f --- /dev/null +++ b/source/frontend/views/custom_widgets/themes_and_colors_item_button.h @@ -0,0 +1,40 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a helper class for custom button rendering for the +/// themes and colors pane. +//============================================================================= + +#ifndef RMV_VIEWS_CUSTOM_WIDGETS_THEMES_AND_COLORS_ITEM_BUTTON_H_ +#define RMV_VIEWS_CUSTOM_WIDGETS_THEMES_AND_COLORS_ITEM_BUTTON_H_ + +#include "qt_common/custom_widgets/scaled_push_button.h" + +/// Helper class for custom button rendering. +class ThemesAndColorsItemButton : public ScaledPushButton +{ +public: + /// Constructor. + /// \param parent The parent widget. + explicit ThemesAndColorsItemButton(QWidget* parent = nullptr); + + /// Destructor. + virtual ~ThemesAndColorsItemButton(); + + /// Themes and colors button color setter - stores color and font color values + /// for use in custom drawing. + /// \param color The color to set. + void SetColor(const QColor& color); + + /// Themes and colors button paint event - overrides button draw function to + /// implement custom drawing functionality. + /// \param event Qt paint event. + void paintEvent(QPaintEvent* event) override; + +private: + QColor button_color_; ///< Color of this button. + QColor font_color_; ///< Font color of this button. +}; + +#endif // RMV_VIEWS_CUSTOM_WIDGETS_THEMES_AND_COLORS_ITEM_BUTTON_H_ diff --git a/source/frontend/views/debug_window.cpp b/source/frontend/views/debug_window.cpp new file mode 100644 index 0000000..828e4f0 --- /dev/null +++ b/source/frontend/views/debug_window.cpp @@ -0,0 +1,134 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV debug window. +//============================================================================= + +#include "views/debug_window.h" + +#include +#include +#include +#include + +#include "rmt_assert.h" + +#include "util/log_file_writer.h" +#include "util/rmv_util.h" + +#ifndef _WIN32 +#include +#endif + +// The one and only instance of the debug window +static DebugWindow* debug_window = nullptr; + +/// Assert on a Qt message. Add the Qt message as part of the ASSERT warning. +/// \param type The Qt message type. +/// \param text The text string containing the error message from Qt. +static void AssertOnQtMessage(const char* type, const QString text) +{ + static char message[2048]; + + sprintf_s(message, 2048, "Intercepted a %s message from Qt (%s). Please fix it!", type, text.toLatin1().data()); + RMT_ASSERT_MESSAGE(false, message); +} + +/// Detect the type of message sent into Qt and return it as a string. +/// \param type The Qt message type (info/error/warning/etc). +/// \param context The message context. +/// \param msg The output string. +static void MyMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) +{ + Q_UNUSED(context); + + QString txt; + + switch (type) + { + case QtInfoMsg: + txt = QString("qInfo(): %1").arg(msg); + break; + + case QtDebugMsg: + txt = QString("qDebug(): %1").arg(msg); + break; + + case QtWarningMsg: + txt = QString("qWarning(): %1").arg(msg); + AssertOnQtMessage("WARNING", txt); + break; + + case QtCriticalMsg: + txt = QString("qCritical(): %1").arg(msg); + AssertOnQtMessage("CRITICAL", txt); + break; + + case QtFatalMsg: + txt = QString("qFatal(): %1").arg(msg); + AssertOnQtMessage("FATAL", txt); + break; + + default: + txt = QString("default: %1").arg(msg); + break; + } + + debug_window->EmitSetText(txt); +} + +DebugWindow::DebugWindow() + : QDialog(nullptr) + , ui_(new Ui::DebugWindow) +{ + ui_->setupUi(this); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + // Use monospace font style so that things align + QFont font("unexistent"); + font.setStyleHint(QFont::Monospace); + ui_->plain_text_edit_->setFont(font); + + connect(this, &DebugWindow::EmitSetText, this, &DebugWindow::SetText); + + RegisterDbgWindow(); +} + +DebugWindow::~DebugWindow() +{ + delete ui_; +} + +void DebugWindow::ScrollToBottom() +{ + QScrollBar* scroll_bar = ui_->plain_text_edit_->verticalScrollBar(); + scroll_bar->setValue(scroll_bar->maximum()); +} + +void DebugWindow::SetText(const QString& string) +{ + ui_->plain_text_edit_->appendPlainText(string); + ScrollToBottom(); +} + +void DebugWindow::RegisterDbgWindow() +{ + debug_window = this; + + qInstallMessageHandler(MyMessageHandler); +} + +void DebugWindow::DbgMsg(const char* pFormat, ...) +{ + if (debug_window != nullptr) + { + char buffer[2048]; + va_list args; + va_start(args, pFormat); + vsnprintf(buffer, 2048, pFormat, args); + debug_window->EmitSetText(QString(buffer)); + rmv::LogFileWriter::Get().WriteLog(rmv::LogFileWriter::kDebug, buffer); + va_end(args); + } +} diff --git a/source/frontend/views/debug_window.h b/source/frontend/views/debug_window.h new file mode 100644 index 0000000..9f25209 --- /dev/null +++ b/source/frontend/views/debug_window.h @@ -0,0 +1,53 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV debug window. +//============================================================================= + +#ifndef RMV_VIEWS_DEBUG_WINDOW_H_ +#define RMV_VIEWS_DEBUG_WINDOW_H_ + +#include "ui_debug_window.h" + +#include + +/// Support for RMV debug window. +class DebugWindow : public QDialog +{ + Q_OBJECT + +public: + /// Constructor. + explicit DebugWindow(); + + /// Destructor. + ~DebugWindow(); + + /// Send a message to the debug window. Supports multiple arguments. + /// \param format The string containing format for each argument. + static void DbgMsg(const char* format, ...); + +signals: + /// Signal that gets emitted when the debug window has new text to add. + /// This will be picked up by the slot below. + /// \param string The new line of text to add. + void EmitSetText(const QString& string); + +private slots: + /// Add a new line of text to the debug window. + /// \param string The new line of text to add. + void SetText(const QString& string); + +private: + /// Helper function which to automatically scroll to the bottom on new line. + void ScrollToBottom(); + + /// Register the Debug Window such that it is accessible. + /// This is only to be called once, when initializing MainWindow. + void RegisterDbgWindow(); + + Ui::DebugWindow* ui_; ///< Pointer to the Qt UI design. +}; + +#endif // RMV_VIEWS_DEBUG_WINDOW_H_ diff --git a/source/frontend/views/debug_window.ui b/source/frontend/views/debug_window.ui new file mode 100644 index 0000000..e6e60fa --- /dev/null +++ b/source/frontend/views/debug_window.ui @@ -0,0 +1,30 @@ + + + DebugWindow + + + + 0 + 0 + 720 + 150 + + + + RMV Debug Output - use DbgMsg() or qDebug() + + + + :/Resources/assets/rmv_icon_48x48.png:/Resources/assets/rmv_icon_48x48.png + + + + + + + + + + + + diff --git a/source/frontend/views/delegates/rmv_compare_id_delegate.cpp b/source/frontend/views/delegates/rmv_compare_id_delegate.cpp new file mode 100644 index 0000000..6faae5d --- /dev/null +++ b/source/frontend/views/delegates/rmv_compare_id_delegate.cpp @@ -0,0 +1,161 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMVCompareIdDelegate. +//============================================================================= + +#include "views/delegates/rmv_compare_id_delegate.h" + +#include +#include +#include +#include +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "models/resource_item_model.h" +#include "util/rmv_util.h" + +// Desired size of the margin in pixels. +const qreal kMarginHint = 5; + +// Desired diameter of the circle in pixels. +const qreal kDiameterHint = 20; + +// Desired height is based on two margins and the diameter of the circle. +const qreal kHeightHint = kMarginHint + kDiameterHint + kMarginHint; + +// Desired width is based on two side-by-side circles with a margin before, after, and in between the circles. +const qreal kWidthHint = kMarginHint + kDiameterHint + kMarginHint + kDiameterHint + kMarginHint; + +RMVCompareIdDelegate::RMVCompareIdDelegate(QWidget* parent) + : QItemDelegate(parent) +{ + CalculateCheckmarkGeometry(kHeightHint); +} + +RMVCompareIdDelegate::~RMVCompareIdDelegate() +{ +} + +QSize RMVCompareIdDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(option); + Q_UNUSED(index); + + return DefaultSizeHint(); +} + +QSize RMVCompareIdDelegate::DefaultSizeHint() const +{ + const int scaled_width_hint = ScalingManager::Get().Scaled(kWidthHint); + const int scaled_height_hint = ScalingManager::Get().Scaled(kHeightHint); + + return QSize(scaled_width_hint, scaled_height_hint); +} + +void RMVCompareIdDelegate::CalculateCheckmarkGeometry(const int height) +{ + checkmark_geometry_.clear(); + + int margin; + int diameter; + HeightToMarginAndDiameter(height, margin, diameter); + + // Calculate offsets of checkmark points. + const qreal x_4_offset = (4.0 / kDiameterHint) * diameter; + const qreal x_12_offset = (12.0 / kDiameterHint) * diameter; + const qreal x_10_offset = (10.0 / kDiameterHint) * diameter; + const qreal x_2_offset = (2.0 / kDiameterHint) * diameter; + const qreal x_0_offset = 0; + + const qreal y_12_offset = (12.0 / kDiameterHint) * diameter; + const qreal y_4_offset = (4.0 / kDiameterHint) * diameter; + const qreal y_2_offset = (2.0 / kDiameterHint) * diameter; + const qreal y_9_offset = (9.0 / kDiameterHint) * diameter; + const qreal y_6_offset = (6.0 / kDiameterHint) * diameter; + const qreal y_8_offset = (8.0 / kDiameterHint) * diameter; + + // Define the checkmark points. + checkmark_geometry_ << QPoint(x_4_offset, y_12_offset); + checkmark_geometry_ << QPoint(x_12_offset, y_4_offset); + checkmark_geometry_ << QPoint(x_10_offset, y_2_offset); + checkmark_geometry_ << QPoint(x_4_offset, y_9_offset); + checkmark_geometry_ << QPoint(x_2_offset, y_6_offset); + checkmark_geometry_ << QPoint(x_0_offset, y_8_offset); +} + +void RMVCompareIdDelegate::DrawCircleCheckmark(QPainter* painter, const QColor& color, int x_pos, int y_pos, int diameter) const +{ + painter->setBrush(color); + painter->drawEllipse(x_pos, y_pos, diameter, diameter); + + // Calculate offset percentages and actual positions based on ideal sizes (aka hints) and actual diameter. + const qreal y_offset = (4.0 / kDiameterHint) * diameter; + const qreal x_offset = (4.0 / kDiameterHint) * diameter; + + const int y_base = y_pos + y_offset; + const int x_base = x_pos + x_offset; + + // Translate to new position for this checkmark. + painter->translate(x_base, y_base); + + painter->setBrush(Qt::white); + painter->drawPolygon(checkmark_geometry_); + + // Restore painter position. + painter->translate(-x_base, -y_base); +} + +void RMVCompareIdDelegate::HeightToMarginAndDiameter(const int height, int& margin, int& diameter) const +{ + // Calculate ratio of margin based on desired size. + qreal hint_ratio = kMarginHint / kHeightHint; + + // Margin is defined by the ratio. + margin = (qreal)height * hint_ratio; + + // Diameter is defined by whatever space is remaining. + diameter = height - 2 * margin; +} + +void RMVCompareIdDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + if (index.column() == kResourceColumnCompareId) + { + QItemDelegate::drawBackground(painter, option, index); + + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + + const QColor open_color = rmv_util::GetSnapshotStateColor(kSnapshotStateViewed); + const QColor compared_color = rmv_util::GetSnapshotStateColor(kSnapshotStateCompared); + + // Now calculate margin size based on the rect that was actually given for this item. + int margin; + int diameter; + HeightToMarginAndDiameter(option.rect.height(), margin, diameter); + + // Other measurements are based on margin and diameter. + const int compared_offset = margin + diameter + margin; + const int color_index = index.data().toInt(); + const int y_offset = option.rect.y() + margin; + + if (color_index == kSnapshotCompareIdCommon) + { + DrawCircleCheckmark(painter, open_color, margin, y_offset, diameter); + DrawCircleCheckmark(painter, compared_color, compared_offset, y_offset, diameter); + } + else if (color_index == kSnapshotCompareIdOpen) + { + DrawCircleCheckmark(painter, open_color, margin, y_offset, diameter); + } + else if (color_index == kSnapshotCompareIdCompared) + { + DrawCircleCheckmark(painter, compared_color, compared_offset, y_offset, diameter); + } + } +} diff --git a/source/frontend/views/delegates/rmv_compare_id_delegate.h b/source/frontend/views/delegates/rmv_compare_id_delegate.h new file mode 100644 index 0000000..de0ff83 --- /dev/null +++ b/source/frontend/views/delegates/rmv_compare_id_delegate.h @@ -0,0 +1,68 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the custom colored circle with checkmark. +//============================================================================= + +#ifndef RMV_VIEWS_DELEGATES_RMV_COMPARE_ID_DELEGATE_H_ +#define RMV_VIEWS_DELEGATES_RMV_COMPARE_ID_DELEGATE_H_ + +#include +#include + +/// Support for the custom colored circle with checkmark. +class RMVCompareIdDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The delegate's parent. + RMVCompareIdDelegate(QWidget* parent = nullptr); + + /// Destructor. + ~RMVCompareIdDelegate(); + + /// Overridden delegate paint method. This is responsible for the custom + /// painting in the Color Swatch. + /// \param painter Pointer to the painter object. + /// \param option A QStyleOptionViewItem object, which contains the bounding + /// rectangle for this element. + /// \param index The model index for this element. + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const Q_DECL_OVERRIDE; + + /// Generates the checkmark geometry based on the supplied height of the widget. + /// \param height Height of the widget in pixels, which includes the diameter + /// of the circle, and the margins above and below the circle. + void CalculateCheckmarkGeometry(const int height); + + /// Provides the default size hint, which is independent of style or index for this delegate. + /// \return A default size hint that is scaled for the current DPI settings. + QSize DefaultSizeHint() const; + +protected: + /// Overridden sizeHint of the CompareIdDelegate. See Qt documentation for parameters. + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const Q_DECL_OVERRIDE; + +private: + /// Calculates the desired margin and circle diameter based on the height which + /// this widget has to draw. + /// \param height Height of the widget in pixels, which includes the diameter + /// of the circle, and the margins above and below the circle. + /// \param [out] margin The height of the margin in pixels. + /// \param [out] diameter The diameter of the circle in pixels. + void HeightToMarginAndDiameter(const int height, int& margin, int& diameter) const; + + /// Draw a circle with a checkmark inside it. + /// \param painter Pointer to a painter object. + /// \param color Circle color + /// \param x_pos The x coord + /// \param y_pos The y coord + /// \param diameter The circle diameter. + void DrawCircleCheckmark(QPainter* painter, const QColor& color, int x_pos, int y_pos, int diameter) const; + + QPolygon checkmark_geometry_; ///< The checkmark geometry. +}; + +#endif // RMV_VIEWS_DELEGATES_RMV_COMPARE_ID_DELEGATE_H_ diff --git a/source/frontend/views/delegates/rmv_resource_event_delegate.cpp b/source/frontend/views/delegates/rmv_resource_event_delegate.cpp new file mode 100644 index 0000000..7b03b07 --- /dev/null +++ b/source/frontend/views/delegates/rmv_resource_event_delegate.cpp @@ -0,0 +1,83 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the resource event delegate. +//============================================================================= + +#include "views/delegates/rmv_resource_event_delegate.h" + +#include + +#include "qt_common/utils/scaling_manager.h" + +const static QColor kTableSelectionColor(0, 120, 215); + +// Default width and height of the icon sizeHint. +// They are the same value since it draws within a square; this includes +// a small padding around the actual icon. +const double RMVResourceEventDelegate::kIconDefaultSizeHint = 24; + +// The icon size factor (percentage) relative to the height of the available rect. If the icon is to be 70% +// the height of the rect, a value of 0.7 should be used. This allows for some space above/below the icon +// in the rect +const double RMVResourceEventDelegate::kIconSizeFactor = 0.7; + +RMVResourceEventDelegate::RMVResourceEventDelegate(QWidget* parent, rmv::ResourceDetailsModel* model) + : QStyledItemDelegate(parent) + , model_(model) +{ +} + +RMVResourceEventDelegate::~RMVResourceEventDelegate() +{ +} + +QSize RMVResourceEventDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QSize size_hint; + + if (index.column() == kResourceHistoryLegend) + { + int scaled_dimension = ScalingManager::Get().Scaled(kIconDefaultSizeHint); + size_hint.setWidth(scaled_dimension); + size_hint.setHeight(scaled_dimension); + } + else + { + size_hint = QStyledItemDelegate::sizeHint(option, index); + } + + return size_hint; +} + +void RMVResourceEventDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + if (index.column() == kResourceHistoryLegend) + { + const RmtResourceHistoryEventType event_type = (RmtResourceHistoryEventType)index.data().toInt(); + QColor color = model_->GetColorFromEventType(event_type, false); + + // Draw cell background color + if (option.state & QStyle::State_Selected) + { + // Draw selection highlight + painter->fillRect(option.rect, QBrush(kTableSelectionColor)); + color = Qt::white; + } + + painter->setRenderHint(QPainter::Antialiasing); + + rmv::ResourceIconShape shape = model_->GetShapeFromEventType(event_type); + + int mid_y = (option.rect.top() + option.rect.bottom()) / 2; + double icon_size = option.rect.height() * kIconSizeFactor; + + // calculate offset for the icon in the table + double x_offset = (option.rect.width() - icon_size) / 2.0; + x_offset = std::min(x_offset, icon_size / 2); + x_offset += option.rect.x(); + + event_icons_.DrawIcon(painter, x_offset, mid_y, icon_size, color, shape); + } +} diff --git a/source/frontend/views/delegates/rmv_resource_event_delegate.h b/source/frontend/views/delegates/rmv_resource_event_delegate.h new file mode 100644 index 0000000..270e361 --- /dev/null +++ b/source/frontend/views/delegates/rmv_resource_event_delegate.h @@ -0,0 +1,56 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the resource event delegate. +//============================================================================= + +#ifndef RMV_VIEWS_DELEGATES_RMV_RESOURCE_EVENT_DELEGATE_H_ +#define RMV_VIEWS_DELEGATES_RMV_RESOURCE_EVENT_DELEGATE_H_ + +#include + +#include "models/snapshot/resource_details_model.h" +#include "views/snapshot/resource_event_icons.h" + +/// Support for the resource event delegate. This does the custom painting +/// in the resource timeline table in the resource details view. +class RMVResourceEventDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + /// Default width and height of the icon sizeHint. + /// They are the same value since it draws within a square; this includes + /// a small padding around the actual icon. + static const double kIconDefaultSizeHint; + + /// The icon size factor relative to the height of the available rect. + static const double kIconSizeFactor; + + /// Constructor. + /// \param parent The delegate's parent. + /// \param model The model containing the resource details. + explicit RMVResourceEventDelegate(QWidget* parent, rmv::ResourceDetailsModel* model); + + /// Destructor. + ~RMVResourceEventDelegate(); + + /// Overridden sizeHint method. + /// \param option Style options related to this element. + /// \param index The model index for this element. + /// \return The desired size needed to paint this element. + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const Q_DECL_OVERRIDE; + + /// Overridden delegate paint method. + /// \param painter Pointer to the painter object. + /// \param option A QStyleOptionViewItem object, which contains the bounding rectangle for this element. + /// \param index The model index for this element. + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const Q_DECL_OVERRIDE; + +private: + rmv::ResourceEventIcons event_icons_; ///< The icon painter helper object. + rmv::ResourceDetailsModel* model_; ///< The model containing the resource details information. +}; + +#endif // RMV_VIEWS_DELEGATES_RMV_RESOURCE_EVENT_DELEGATE_H_ diff --git a/source/frontend/views/keyboard_zoom_shortcuts.cpp b/source/frontend/views/keyboard_zoom_shortcuts.cpp new file mode 100644 index 0000000..4a46201 --- /dev/null +++ b/source/frontend/views/keyboard_zoom_shortcuts.cpp @@ -0,0 +1,73 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the global keyboard shortcuts class. +//============================================================================= + +#include "views/keyboard_zoom_shortcuts.h" + +bool KeyboardZoomShortcuts::enable_shortcuts_ = false; + +KeyboardZoomShortcuts::KeyboardZoomShortcuts(QScrollBar* scroll_bar, QGraphicsView* zoom_view) + : scroll_bar_(scroll_bar) + , zoom_view_(zoom_view) +{ + SetupNavigationControl(); +} + +KeyboardZoomShortcuts::~KeyboardZoomShortcuts() +{ +} + +void KeyboardZoomShortcuts::OnScrollViewSingleStepSub(bool checked) +{ + Q_UNUSED(checked); + + scroll_bar_->triggerAction(QAbstractSlider::SliderSingleStepSub); +} + +void KeyboardZoomShortcuts::OnScrollViewPageStepSub(bool checked) +{ + Q_UNUSED(checked); + + scroll_bar_->triggerAction(QAbstractSlider::SliderPageStepSub); +} + +void KeyboardZoomShortcuts::OnScrollViewSingleStepAdd(bool checked) +{ + Q_UNUSED(checked); + + scroll_bar_->triggerAction(QAbstractSlider::SliderSingleStepAdd); +} + +void KeyboardZoomShortcuts::OnScrollViewPageStepAdd(bool checked) +{ + Q_UNUSED(checked); + + scroll_bar_->triggerAction(QAbstractSlider::SliderPageStepAdd); +} + +void KeyboardZoomShortcuts::EnableShortcuts(bool enable) +{ + enable_shortcuts_ = enable; +} + +bool KeyboardZoomShortcuts::IsShortcutsEnabled() +{ + return enable_shortcuts_; +} + +void KeyboardZoomShortcuts::SetupNavigationControl() +{ + navigation_control_[Qt::CTRL | Qt::Key_Left] = &KeyboardZoomShortcuts::OnScrollViewPageStepSub; + navigation_control_[Qt::Key_Left] = &KeyboardZoomShortcuts::OnScrollViewSingleStepSub; + navigation_control_[Qt::CTRL | Qt::Key_Right] = &KeyboardZoomShortcuts::OnScrollViewPageStepAdd; + navigation_control_[Qt::Key_Right] = &KeyboardZoomShortcuts::OnScrollViewSingleStepAdd; + navigation_control_[Qt::Key_A] = &KeyboardZoomShortcuts::OnZoomInShortCut; + navigation_control_[Qt::Key_Z] = &KeyboardZoomShortcuts::OnZoomOutShortCut; + navigation_control_[Qt::CTRL | Qt::Key_Z] = &KeyboardZoomShortcuts::OnZoomInSelection; + navigation_control_[Qt::Key_H] = &KeyboardZoomShortcuts::OnResetView; + navigation_control_[Qt::Key_S] = &KeyboardZoomShortcuts::OnZoomInMoreShortCut; + navigation_control_[Qt::Key_X] = &KeyboardZoomShortcuts::OnZoomOutMoreShortCut; +} \ No newline at end of file diff --git a/source/frontend/views/keyboard_zoom_shortcuts.h b/source/frontend/views/keyboard_zoom_shortcuts.h new file mode 100644 index 0000000..23bd405 --- /dev/null +++ b/source/frontend/views/keyboard_zoom_shortcuts.h @@ -0,0 +1,96 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the keyboard zoom shortcuts +//============================================================================= + +#ifndef RMV_VIEWS_KEYBOARD_ZOOM_SHORTCUTS_H_ +#define RMV_VIEWS_KEYBOARD_ZOOM_SHORTCUTS_H_ + +#include +#include +#include +#include + +/// Class to handle keyboard zoom shortcut keys +class KeyboardZoomShortcuts : public QObject +{ + Q_OBJECT + +public: + /// Constructor. + /// \param scroll_bar The view where the zoom scrollbar is located. + /// \param zoom_view The view where the zoom shortcuts are applied. + explicit KeyboardZoomShortcuts(QScrollBar* scroll_bar, QGraphicsView* zoom_view = nullptr); + + /// Destructor. + virtual ~KeyboardZoomShortcuts(); + + /// Enable keyboard shortcuts. + /// \param enable Boolean to indicate if shortcuts are enabled. + static void EnableShortcuts(bool enable); + + /// Are keyboard shortcuts enabled. + /// \return true if enabled, false if not. + static bool IsShortcutsEnabled(); + +public slots: + /// Action slot to scroll using right arrow key. + /// \param checked Boolean to indicate if the item checked. + virtual void OnScrollViewSingleStepAdd(bool checked); + + /// Action slot to scroll using left arrow key. + /// \param checked Boolean to indicate if the item checked. + virtual void OnScrollViewSingleStepSub(bool checked); + + /// Action slot to scroll using ctrl + right arrow key. + /// \param checked Boolean to indicate if the item checked. + void OnScrollViewPageStepAdd(bool checked); + + /// Action slot to scroll using ctrl + left arrow key. + /// \param checked Boolean to indicate if the item checked. + void OnScrollViewPageStepSub(bool checked); + + // Pure virtual methods to be implemented in the derived classes + + /// Action slot to zoom in. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomInShortCut(bool checked) = 0; + + /// Action slot to zoom out. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomOutShortCut(bool checked) = 0; + + /// Action slot to zoom in faster. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomInMoreShortCut(bool checked) = 0; + + /// Action slot to zoom out faster. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomOutMoreShortCut(bool checked) = 0; + + /// Action slot to zoom in selection. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomInSelection(bool checked) = 0; + + /// Action slot to reset the view. + /// \param checked Boolean to indicate if the item checked. + virtual void OnResetView(bool checked) = 0; + +protected: + typedef void (KeyboardZoomShortcuts::*ShortcutSlot)(bool flag); + typedef std::map NavigationControl; + + NavigationControl navigation_control_; ///< The navigation control information for each key. + +private: + /// Setup navigation controls. + void SetupNavigationControl(); + + static bool enable_shortcuts_; ///< Boolean indicate if shortcuts enabled for all panes. + QScrollBar* scroll_bar_; ///< The scrollbar used for zooming. + QGraphicsView* zoom_view_; ///< The graphics view to zoom. +}; + +#endif // RMV_VIEWS_KEYBOARD_ZOOM_SHORTCUTS_H_ diff --git a/source/frontend/views/main_window.cpp b/source/frontend/views/main_window.cpp new file mode 100644 index 0000000..04ca763 --- /dev/null +++ b/source/frontend/views/main_window.cpp @@ -0,0 +1,1008 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV main window. +//============================================================================= + +#include "views/main_window.h" + +#include "ui_main_window.h" + +#include +#include + +#include +#include +#include +#include + +#include "qt_common/utils/common_definitions.h" +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_data_snapshot.h" +#include "rmt_assert.h" + +#include "models/snapshot_manager.h" +#include "models/trace_manager.h" +#include "models/message_manager.h" +#include "views/compare/snapshot_delta_pane.h" +#include "views/compare/memory_leak_finder_pane.h" +#include "views/compare/compare_start_pane.h" +#include "views/navigation_manager.h" +#include "views/settings/settings_pane.h" +#include "views/settings/themes_and_colors_pane.h" +#include "views/settings/keyboard_shortcuts_pane.h" +#include "views/snapshot/allocation_explorer_pane.h" +#include "views/snapshot/resource_list_pane.h" +#include "views/snapshot/resource_details_pane.h" +#include "views/snapshot/resource_overview_pane.h" +#include "views/snapshot/allocation_overview_pane.h" +#include "views/snapshot/heap_overview_pane.h" +#include "views/snapshot/snapshot_start_pane.h" +#include "views/start/about_pane.h" +#include "views/timeline/timeline_pane.h" +#include "views/timeline/device_configuration_pane.h" +#include "settings/rmv_settings.h" +#include "settings/rmv_geometry_settings.h" +#include "util/time_util.h" +#include "util/thread_controller.h" +#include "util/version.h" +#include "util/widget_util.h" + +// Indices for the SNAPSHOT and COMPARE stacked widget. Index 1 is displayed if +// a snapshot loaded, otherwise and empty pane (0) is shown +static const int kIndexEmptyPane = 0; +static const int kIndexPopulatedPane = 1; + +// Thread count for the job queue. +static const int32_t kThreadCount = 8; + +static const int kMaxSubmenuSnapshots = 10; +RmtJobQueue MainWindow::job_queue_; + +MainWindow::MainWindow(QWidget* parent) + : QMainWindow(parent) + , ui_(new Ui::MainWindow) + , file_menu_(nullptr) + , open_trace_action_(nullptr) + , close_trace_action_(nullptr) + , exit_action_(nullptr) + , help_action_(nullptr) + , about_action_(nullptr) + , help_menu_(nullptr) + , recent_traces_menu_(nullptr) + , file_load_animation_(nullptr) + , navigation_bar_(this) +{ + const bool loaded_settings = RMVSettings::Get().LoadSettings(); + + ui_->setupUi(this); + + setWindowTitle(GetTitleBarString()); + setWindowIcon(QIcon(":/Resources/assets/rmv_icon_32x32.png")); + setAcceptDrops(true); + + // Set white background for this pane + rmv::widget_util::SetWidgetBackgroundColor(this, Qt::white); + + // Setup window sizes and settings + SetupWindowRects(loaded_settings); + + welcome_pane_ = CreatePane(); + recent_traces_pane_ = CreatePane(); + BasePane* about_pane = CreatePane(); + timeline_pane_ = CreatePane(); + BasePane* resource_overview_pane = CreatePane(); + BasePane* allocation_overview_pane = CreatePane(); + BasePane* resource_list_pane = CreatePane(); + BasePane* resource_details_pane = CreatePane(); + BasePane* device_configuration_pane = CreatePane(); + BasePane* allocation_explorer_pane = CreatePane(); + BasePane* heap_overview_pane = CreatePane(); + snapshot_delta_pane_ = CreatePane(); + memory_leak_finder_pane_ = CreatePane(); + settings_pane_ = CreatePane(); + ThemesAndColorsPane* themes_and_colors_pane = CreatePane(); + BasePane* keyboard_shortcuts_pane = CreatePane(); + snapshot_start_pane_ = dynamic_cast(ui_->snapshot_start_stack_->widget(0)); + compare_start_pane_ = dynamic_cast(ui_->compare_start_stack_->widget(0)); + + // NOTE: Widgets need adding in the order they are to appear in the UI + ui_->start_stack_->addWidget(welcome_pane_); + ui_->start_stack_->addWidget(recent_traces_pane_); + ui_->start_stack_->addWidget(about_pane); + ui_->timeline_stack_->addWidget(timeline_pane_); + ui_->timeline_stack_->addWidget(device_configuration_pane); + ui_->snapshot_stack_->addWidget(heap_overview_pane); + ui_->snapshot_stack_->addWidget(resource_overview_pane); + ui_->snapshot_stack_->addWidget(allocation_overview_pane); + ui_->snapshot_stack_->addWidget(resource_list_pane); + ui_->snapshot_stack_->addWidget(allocation_explorer_pane); + ui_->snapshot_stack_->addWidget(resource_details_pane); + ui_->compare_stack_->addWidget(snapshot_delta_pane_); + ui_->compare_stack_->addWidget(memory_leak_finder_pane_); + ui_->settings_stack_->addWidget(settings_pane_); + ui_->settings_stack_->addWidget(themes_and_colors_pane); + ui_->settings_stack_->addWidget(keyboard_shortcuts_pane); + + ui_->main_tab_widget_->setTabEnabled(kMainPaneTimeline, false); + ui_->main_tab_widget_->setTabEnabled(kMainPaneSnapshot, false); + ui_->main_tab_widget_->setTabEnabled(kMainPaneCompare, false); + + SetupTabBar(); + CreateActions(); + CreateMenus(); + + ResetUI(); + + rmv::widget_util::InitSingleSelectComboBox(this, ui_->snapshot_combo_box_, "Snapshot", false); + ui_->snapshot_combo_box_->SetListAboveButton(true); + connect(ui_->snapshot_combo_box_, &ArrowIconComboBox::SelectionChanged, this, &MainWindow::OpenSelectedSnapshot); + + ViewPane(rmv::kPaneStartWelcome); + + connect(&navigation_bar_.BackButton(), &QPushButton::clicked, &rmv::NavigationManager::Get(), &rmv::NavigationManager::NavigateBack); + connect(&navigation_bar_.ForwardButton(), &QPushButton::clicked, &rmv::NavigationManager::Get(), &rmv::NavigationManager::NavigateForward); + connect(&rmv::NavigationManager::Get(), &rmv::NavigationManager::EnableBackNavButton, &navigation_bar_, &NavigationBar::EnableBackButton); + connect(&rmv::NavigationManager::Get(), &rmv::NavigationManager::EnableForwardNavButton, &navigation_bar_, &NavigationBar::EnableForwardButton); + connect(&MessageManager::Get(), &MessageManager::OpenTrace, this, &MainWindow::LoadTrace); + + // Update the navigation and pane switching when the UI panes are changed + connect(ui_->start_list_, &QListWidget::currentRowChanged, this, &MainWindow::UpdateStartListRow); + connect(ui_->timeline_list_, &QListWidget::currentRowChanged, this, &MainWindow::UpdateTimelineListRow); + connect(ui_->snapshot_list_, &QListWidget::currentRowChanged, this, &MainWindow::UpdateSnapshotListRow); + connect(ui_->compare_list_, &QListWidget::currentRowChanged, this, &MainWindow::UpdateCompareListRow); + connect(ui_->settings_list_, &QListWidget::currentRowChanged, this, &MainWindow::UpdateSettingsListRow); + connect(ui_->main_tab_widget_, &QTabWidget::currentChanged, this, &MainWindow::UpdateMainTabIndex); + + // Set up the stack widget signals so they know which pane in their stack to switch to + connect(ui_->start_list_, &QListWidget::currentRowChanged, ui_->start_stack_, &QStackedWidget::setCurrentIndex); + connect(ui_->timeline_list_, &QListWidget::currentRowChanged, ui_->timeline_stack_, &QStackedWidget::setCurrentIndex); + connect(ui_->snapshot_list_, &QListWidget::currentRowChanged, ui_->snapshot_stack_, &QStackedWidget::setCurrentIndex); + connect(ui_->compare_list_, &QListWidget::currentRowChanged, ui_->compare_stack_, &QStackedWidget::setCurrentIndex); + connect(ui_->settings_list_, &QListWidget::currentRowChanged, ui_->settings_stack_, &QStackedWidget::setCurrentIndex); + + connect(&MessageManager::Get(), &MessageManager::NavigateToPane, this, &MainWindow::ViewPane); + connect(&MessageManager::Get(), &MessageManager::NavigateToPaneUnrecorded, this, &MainWindow::SetupNextPane); + + connect(&MessageManager::Get(), &MessageManager::OpenSnapshot, this, &MainWindow::OpenSnapshot); + connect(&MessageManager::Get(), &MessageManager::CompareSnapshot, this, &MainWindow::CompareSnapshot); + connect(themes_and_colors_pane, &ThemesAndColorsPane::RefreshedColors, this, &MainWindow::BroadcastChangeColoring); + + // Connect to ScalingManager for notifications + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &MainWindow::OnScaleFactorChanged); +} + +MainWindow::~MainWindow() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &MainWindow::OnScaleFactorChanged); + + delete ui_; +} + +void MainWindow::OnScaleFactorChanged() +{ + ResizeNavigationLists(); +} + +void MainWindow::ResizeNavigationLists() +{ + // Update the widths of the navigation lists so they are all the same width. + // Since these are each independent widgets, the MainWindow needs to keep them in sync. + // At some resolutions and DPI scales the text/combobox on the bottom of the snapshot pane + // may define the desired widest width, so take those into consideration as well, but their + // width doesn't need to be updated (they can be narrower than the snapshotList). + int widest_width = std::max(ui_->start_list_->sizeHint().width(), ui_->timeline_list_->sizeHint().width()); + widest_width = std::max(widest_width, ui_->snapshot_list_->sizeHint().width()); + widest_width = std::max(widest_width, ui_->snapshot_label_->sizeHint().width()); + widest_width = std::max(widest_width, ui_->snapshot_combo_box_->sizeHint().width()); + widest_width = std::max(widest_width, ui_->compare_list_->sizeHint().width()); + widest_width = std::max(widest_width, ui_->settings_list_->sizeHint().width()); + + // Also use 1/12th of the MainWindow as a minimum width for the navigation list. + int minimum_width = this->width() / 12; + widest_width = std::max(widest_width, minimum_width); + + ui_->start_list_->setFixedWidth(widest_width); + ui_->timeline_list_->setFixedWidth(widest_width); + ui_->snapshot_list_->setFixedWidth(widest_width); + ui_->compare_list_->setFixedWidth(widest_width); + ui_->settings_list_->setFixedWidth(widest_width); +} + +void MainWindow::SetupTabBar() +{ + // Set grey background for main tab bar background + rmv::widget_util::SetWidgetBackgroundColor(ui_->main_tab_widget_, QColor(51, 51, 51)); + + // set the mouse cursor to pointing hand cursor for all of the tabs + QList tab_bar = ui_->main_tab_widget_->findChildren(); + foreach (QTabBar* item, tab_bar) + { + if (item != nullptr) + { + item->setCursor(Qt::PointingHandCursor); + item->setContentsMargins(10, 10, 10, 10); + } + } + + // Set the tab that divides the left and right justified tabs. + ui_->main_tab_widget_->SetSpacerIndex(kMainPaneSpacer); + + // Adjust spacing around the navigation bar so that it appears centered on the tab bar. + navigation_bar_.layout()->setContentsMargins(15, 3, 35, 2); + + // Setup navigation browser toolbar on the main tab bar. + ui_->main_tab_widget_->SetTabTool(kMainPaneNavigation, &navigation_bar_); +} + +void MainWindow::SetupWindowRects(bool loaded_settings) +{ + if (loaded_settings) + { + RMVGeometrySettings::Restore(this); + } + else + { + // Move main window to default position if settings file was not loaded. + const QRect geometry = QApplication::desktop()->availableGeometry(); + const QPoint available_desktop_top_left = geometry.topLeft(); + move(available_desktop_top_left.x() + rmv::kDesktopMargin, available_desktop_top_left.y() + rmv::kDesktopMargin); + + const int width = geometry.width() * 0.66f; + const int height = geometry.height() * 0.66f; + resize(width, height); + } + +#if RMV_DEBUG_WINDOW + const int screen_number = QApplication::desktop()->screenNumber(this); + const QRect desktop_res = QApplication::desktop()->availableGeometry(screen_number); + + const int desktop_width = (rmv::kDesktopAvailableWidthPercentage / 100.0f) * desktop_res.width(); + const int desktop_height = (rmv::kDesktopAvailableHeightPercentage / 100.0f) * desktop_res.height(); + + const int dbg_width = desktop_width * (rmv::kDebugWindowDesktopWidthPercentage / 100.0f); + const int dbg_height = desktop_height * (rmv::kDebugWindowDesktopHeightPercentage / 100.f); + + const int dbg_loc_x = rmv::kDesktopMargin + desktop_res.left(); + const int dbg_loc_y = (desktop_height - dbg_height - rmv::kDesktopMargin) + desktop_res.top(); + + // place debug out window + debug_window_.move(dbg_loc_x, dbg_loc_y); + debug_window_.resize(dbg_width, dbg_height); + debug_window_.show(); +#endif // RMV_DEBUG_WINDOW +} + +void MainWindow::SetupHotkeyNavAction(QSignalMapper* mapper, int key, int pane) +{ + QAction* action = new QAction(this); + action->setShortcut(key | Qt::ALT); + + connect(action, SIGNAL(triggered()), mapper, SLOT(map())); + this->addAction(action); + mapper->setMapping(action, pane); +} + +void MainWindow::CreateActions() +{ + QSignalMapper* signal_mapper = new QSignalMapper(this); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoWelcomePane, rmv::kPaneStartWelcome); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoRecentSnapshotsPane, rmv::kPaneStartRecentTraces); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoGenerateSnapshotPane, rmv::kPaneTimelineGenerateSnapshot); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoDeviceConfigurationPane, rmv::kPaneTimelineDeviceConfiguration); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoHeapOverviewPane, rmv::kPaneSnapshotHeapOverview); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoResourceOverviewPane, rmv::kPaneSnapshotResourceOverview); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoAllocationOverviewPane, rmv::kPaneSnapshotAllocationOverview); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoResourceListPane, rmv::kPaneSnapshotResourceList); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoResourceHistoryPane, rmv::kPaneSnapshotResourceDetails); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoAllocationExplorerPane, rmv::kPaneSnapshotAllocationExplorer); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoSnapshotDeltaPane, rmv::kPaneCompareSnapshotDelta); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoMemoryLeakFinderPane, rmv::kPaneCompareMemoryLeakFinder); + SetupHotkeyNavAction(signal_mapper, rmv::kGotoKeyboardShortcutsPane, rmv::kPaneSettingsKeyboardShortcuts); + connect(signal_mapper, SIGNAL(mapped(int)), this, SLOT(ViewPane(int))); + + // Set up forward/backward navigation + QAction* shortcut = new QAction(this); + shortcut->setShortcut(Qt::ALT | rmv::kKeyNavForwardArrow); + + connect(shortcut, &QAction::triggered, &rmv::NavigationManager::Get(), &rmv::NavigationManager::NavigateForward); + this->addAction(shortcut); + + shortcut = new QAction(this); + shortcut->setShortcut(Qt::ALT | rmv::kKeyNavBackwardArrow); + + connect(shortcut, &QAction::triggered, &rmv::NavigationManager::Get(), &rmv::NavigationManager::NavigateBack); + this->addAction(shortcut); + + shortcut = new QAction(this); + shortcut->setShortcut(rmv::kKeyNavBackwardBackspace); + + connect(shortcut, &QAction::triggered, &rmv::NavigationManager::Get(), &rmv::NavigationManager::NavigateBack); + this->addAction(shortcut); + + // Set up time unit cycling + shortcut = new QAction(this); + shortcut->setShortcut(Qt::CTRL | Qt::Key_T); + + connect(shortcut, &QAction::triggered, this, &MainWindow::CycleTimeUnits); + this->addAction(shortcut); + + open_trace_action_ = new QAction(tr("Open trace"), this); + open_trace_action_->setShortcut(Qt::CTRL | Qt::Key_O); + connect(open_trace_action_, &QAction::triggered, this, &MainWindow::OpenTrace); + + close_trace_action_ = new QAction(tr("Close trace"), this); + close_trace_action_->setShortcut(Qt::CTRL | Qt::Key_F4); + connect(close_trace_action_, &QAction::triggered, this, &MainWindow::CloseTrace); + close_trace_action_->setDisabled(true); + + exit_action_ = new QAction(tr("Exit"), this); + connect(exit_action_, &QAction::triggered, this, &MainWindow::CloseRmv); + + for (int i = 0; i < kMaxSubmenuSnapshots; i++) + { + recent_trace_actions_.push_back(new QAction("", this)); + recent_trace_mappers_.push_back(new QSignalMapper()); + } + + help_action_ = new QAction(tr("Help"), this); + connect(help_action_, &QAction::triggered, this, &MainWindow::OpenHelp); + + about_action_ = new QAction(tr("About Radeon Memory Visualizer"), this); + connect(about_action_, &QAction::triggered, this, &MainWindow::OpenAboutPane); +} + +void MainWindow::CycleTimeUnits() +{ + settings_pane_->CycleTimeUnits(); + pane_manager_.SwitchTimeUnits(); + UpdateSnapshotCombobox(SnapshotManager::Get().GetSelectedSnapshotPoint()); + if (pane_manager_.GetMainPaneFromPane(pane_manager_.GetCurrentPane()) == kMainPaneSnapshot) + { + ResizeNavigationLists(); + } + update(); +} + +void MainWindow::SetupRecentTracesMenu() +{ + for (int i = 0; i < recent_trace_mappers_.size(); i++) + { + disconnect(recent_trace_actions_[i], SIGNAL(triggered(bool)), recent_trace_mappers_[i], SLOT(map())); + disconnect(recent_trace_mappers_[i], SIGNAL(mapped(QString)), this, SLOT(LoadTrace(QString))); + } + + recent_traces_menu_->clear(); + + const QVector& files = RMVSettings::Get().RecentFiles(); + + const int num_items = (files.size() > kMaxSubmenuSnapshots) ? kMaxSubmenuSnapshots : files.size(); + + for (int i = 0; i < num_items; i++) + { + recent_trace_actions_[i]->setText(files[i].path); + + recent_traces_menu_->addAction(recent_trace_actions_[i]); + + connect(recent_trace_actions_[i], SIGNAL(triggered(bool)), recent_trace_mappers_[i], SLOT(map())); + + recent_trace_mappers_[i]->setMapping(recent_trace_actions_[i], files[i].path); + + connect(recent_trace_mappers_[i], SIGNAL(mapped(QString)), this, SLOT(LoadTrace(QString))); + } + + emit welcome_pane_->FileListChanged(); + emit recent_traces_pane_->FileListChanged(); +} + +void MainWindow::OpenRecentTracesPane() +{ + ViewPane(rmv::kPaneStartRecentTraces); +} + +void MainWindow::CreateMenus() +{ + file_menu_ = menuBar()->addMenu(tr("File")); + recent_traces_menu_ = new QMenu("Recent traces"); + + file_menu_->addAction(open_trace_action_); + file_menu_->addAction(close_trace_action_); + file_menu_->addSeparator(); + file_menu_->addMenu(recent_traces_menu_); + file_menu_->addSeparator(); + file_menu_->addAction(exit_action_); + + file_menu_ = menuBar()->addMenu(tr("Help")); + file_menu_->addAction(help_action_); + file_menu_->addAction(about_action_); + + SetupRecentTracesMenu(); +} + +void MainWindow::LoadTrace(const QString& trace_file) +{ + TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.ReadyToLoadTrace()) + { + if (trace_manager.TraceValidToLoad(trace_file) == true) + { + bool success = trace_manager.LoadTrace(trace_file, false); + + if (success) + { + StartAnimation(ui_->main_tab_widget_, ui_->main_tab_widget_->TabHeight()); + } + } + else + { + // The selected trace file is missing on the disk so display a message box stating so + const QString text = rmv::text::kOpenRecentTraceStart + trace_file + rmv::text::kOpenRecentTraceEnd; + QtCommon::QtUtils::ShowMessageBox(this, QMessageBox::Ok, QMessageBox::Critical, rmv::text::kOpenRecentTraceTitle, text); + } + } +} + +void MainWindow::TraceLoadComplete() +{ + close_trace_action_->setDisabled(false); + timeline_pane_->OnTraceLoad(); + + ui_->main_tab_widget_->setTabEnabled(kMainPaneTimeline, true); + ui_->main_tab_widget_->setTabEnabled(kMainPaneSnapshot, true); + ui_->main_tab_widget_->setTabEnabled(kMainPaneCompare, true); + + ViewPane(rmv::kPaneTimelineGenerateSnapshot); + + SetupRecentTracesMenu(); + + UpdateTitlebar(); +} + +void MainWindow::StartAnimation(QWidget* parent, int height_offset) +{ + if (file_load_animation_ == nullptr) + { + file_load_animation_ = new FileLoadingWidget(parent); + + // Set overall size of the widget to cover the tab contents. + int width = parent->width(); + int height = parent->height() - height_offset; + file_load_animation_->setGeometry(parent->x(), parent->y() + height_offset, width, height); + + // Set the contents margin so that the animated bars cover a portion of the middle of the screen. + const int desired_loading_dimension = ScalingManager::Get().Scaled(200); + int vertical_margin = (height - desired_loading_dimension) / 2; + int horizontal_margin = (width - desired_loading_dimension) / 2; + file_load_animation_->setContentsMargins(horizontal_margin, vertical_margin, horizontal_margin, vertical_margin); + + file_load_animation_->show(); + ui_->main_tab_widget_->setDisabled(true); + file_menu_->setDisabled(true); + + qApp->setOverrideCursor(Qt::BusyCursor); + } +} + +void MainWindow::StopAnimation() +{ + if (file_load_animation_ != nullptr) + { + delete file_load_animation_; + file_load_animation_ = nullptr; + + ui_->main_tab_widget_->setEnabled(true); + file_menu_->setEnabled(true); + + qApp->restoreOverrideCursor(); + } +} + +void MainWindow::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + RMVSettings::Get().SetWindowSize(event->size().width(), event->size().height()); + + ResizeNavigationLists(); + + // Update Animation widget on window resize + if (file_load_animation_ != nullptr) + { + // Set overall size of the widget to cover the tab contents. + double height_offset = ui_->main_tab_widget_->TabHeight(); + QWidget* parent = file_load_animation_->parentWidget(); + int width = parent->width(); + int height = parent->height() - height_offset; + file_load_animation_->setGeometry(parent->x(), parent->y() + height_offset, width, height); + + // Set the contents margin so that the animated bars only cover a small area in the middle of the screen. + const int desired_loading_dimension = 200; + int vertical_margin = (height - desired_loading_dimension) / 2; + int horizontal_margin = (width - desired_loading_dimension) / 2; + file_load_animation_->setContentsMargins(horizontal_margin, vertical_margin, horizontal_margin, vertical_margin); + } +} + +void MainWindow::moveEvent(QMoveEvent* event) +{ + QMainWindow::moveEvent(event); + + RMVSettings::Get().SetWindowPos(geometry().x(), geometry().y()); +} + +void MainWindow::OpenTrace() +{ + QString file_name = QFileDialog::getOpenFileName(this, "Open file", RMVSettings::Get().GetLastFileOpenLocation(), rmv::text::kFileOpenFileTypes); + + if (!file_name.isNull()) + { + LoadTrace(file_name); + } +} + +void MainWindow::CloseTrace() +{ + TraceManager::Get().ClearTrace(); + + BroadcastOnTraceClose(); + ResetUI(); + rmv::NavigationManager::Get().Reset(); + close_trace_action_->setDisabled(true); + UpdateTitlebar(); + setWindowTitle(GetTitleBarString()); +} + +void MainWindow::ResetUI() +{ + // Default to first tab + const NavLocation& nav_location = pane_manager_.ResetNavigation(); + + ui_->main_tab_widget_->setCurrentIndex(nav_location.main_tab_index); + ui_->start_list_->setCurrentRow(nav_location.start_list_row); + ui_->timeline_list_->setCurrentRow(nav_location.timeline_list_row); + ui_->snapshot_list_->setCurrentRow(nav_location.snapshot_list_row); + ui_->compare_list_->setCurrentRow(nav_location.compare_list_row); + ui_->settings_list_->setCurrentRow(nav_location.settings_list_row); + + ui_->snapshot_start_stack_->setCurrentIndex(kIndexEmptyPane); + ui_->compare_start_stack_->setCurrentIndex(kIndexEmptyPane); + + ui_->main_tab_widget_->setTabEnabled(kMainPaneTimeline, false); + ui_->main_tab_widget_->setTabEnabled(kMainPaneSnapshot, false); + ui_->main_tab_widget_->setTabEnabled(kMainPaneCompare, false); + + BroadcastReset(); +} + +void MainWindow::OpenHelp() +{ + // Get the file info + QFileInfo file_info(QCoreApplication::applicationDirPath() + rmv::text::kRmvHelpFile); + + // Check to see if the file is not a directory and that it exists + if (file_info.isFile() && file_info.exists()) + { + QDesktopServices::openUrl(QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + rmv::text::kRmvHelpFile)); + } + else + { + // The selected help file is missing on the disk so display a message box stating so + const QString text = rmv::text::kMissingRmvHelpFile + QCoreApplication::applicationDirPath() + rmv::text::kRmvHelpFile; + QtCommon::QtUtils::ShowMessageBox(this, QMessageBox::Ok, QMessageBox::Critical, rmv::text::kMissingRmvHelpFile, text); + } +} + +void MainWindow::OpenAboutPane() +{ + ViewPane(rmv::kPaneStartAbout); +} + +void MainWindow::CloseRmv() +{ + CloseTrace(); +#if RMV_DEBUG_WINDOW + debug_window_.close(); +#endif // RMV_DEBUG_WINDOW + + close(); +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + Q_UNUSED(event); + + RMVGeometrySettings::Save(this); + + CloseRmv(); +} + +void MainWindow::dragEnterEvent(QDragEnterEvent* event) +{ + if (event != nullptr) + { + if (event->mimeData()->hasUrls()) + { + event->setDropAction(Qt::LinkAction); + event->accept(); + } + } +} + +void MainWindow::dropEvent(QDropEvent* event) +{ + if (event != nullptr) + { + const uint32_t num_urls = event->mimeData()->urls().size(); + + for (uint32_t i = 0; i < num_urls; i++) + { + const QString potential_trace_path = event->mimeData()->urls().at(i).toLocalFile(); + + if (TraceManager::Get().TraceValidToLoad(potential_trace_path) == true) + { + LoadTrace(potential_trace_path); + } + } + } +} + +rmv::RMVPane MainWindow::SetupNextPane(rmv::RMVPane pane) +{ + MainPanes main_pane = pane_manager_.GetMainPaneFromPane(pane); + const TraceManager& trace_manager = TraceManager::Get(); + + if (main_pane == kMainPaneSnapshot || main_pane == kMainPaneCompare) + { + // make sure at least one snapshot is generated before navigating to the + // snapshot or compare panes + if (!trace_manager.DataSetValid()) + { + return pane; + } + + RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + if (main_pane == kMainPaneSnapshot && open_snapshot == nullptr) + { + return pane; + } + + if (main_pane == kMainPaneCompare && + (trace_manager.GetComparedSnapshot(kSnapshotCompareBase) == nullptr || trace_manager.GetComparedSnapshot(kSnapshotCompareDiff) == nullptr)) + { + return pane; + } + } + else if (main_pane == kMainPaneTimeline) + { + // make sure a trace is loaded before navigating to the timeline pane + if (!trace_manager.DataSetValid()) + { + return pane; + } + } + + const NavLocation& nav_location = pane_manager_.SetupNextPane(pane); + + // These things emit signals + ui_->start_list_->setCurrentRow(nav_location.start_list_row); + ui_->timeline_list_->setCurrentRow(nav_location.timeline_list_row); + ui_->snapshot_list_->setCurrentRow(nav_location.snapshot_list_row); + ui_->compare_list_->setCurrentRow(nav_location.compare_list_row); + ui_->settings_list_->setCurrentRow(nav_location.settings_list_row); + ui_->main_tab_widget_->setCurrentIndex(nav_location.main_tab_index); + + rmv::RMVPane new_pane = pane_manager_.UpdateCurrentPane(); + return (new_pane); +} + +void MainWindow::ViewPane(int pane) +{ + rmv::RMVPane current_pane = SetupNextPane(static_cast(pane)); + rmv::NavigationManager::Get().RecordNavigationEventPaneSwitch(current_pane); +} + +void MainWindow::UpdateSnapshotCombobox(RmtSnapshotPoint* selected_snapshot_point) +{ + TraceManager& trace_manager = TraceManager::Get(); + + if (trace_manager.DataSetValid()) + { + const RmtDataSet* data_set = trace_manager.GetDataSet(); + ui_->snapshot_combo_box_->ClearItems(); + + int32_t selected_snapshot_point_index = 0; + for (int32_t current_snapshot_point_index = 0; current_snapshot_point_index < data_set->snapshot_count; ++current_snapshot_point_index) + { + const RmtSnapshotPoint* current_snapshot_point = &data_set->snapshots[current_snapshot_point_index]; + QString name_string = QString(current_snapshot_point->name) + " (" + rmv::time_util::ClockToTimeUnit(current_snapshot_point->timestamp) + ")"; + if (current_snapshot_point == selected_snapshot_point) + { + selected_snapshot_point_index = current_snapshot_point_index; + } + ui_->snapshot_combo_box_->AddItem(name_string, (qulonglong)current_snapshot_point); + } + + if (data_set->snapshot_count > 1) + { + ui_->main_tab_widget_->setTabEnabled(kMainPaneSnapshot, true); + ui_->main_tab_widget_->setTabEnabled(kMainPaneCompare, true); + } + + // Search the list of items in the combo box that has the open snapshot pointer. + ui_->snapshot_combo_box_->SetSelectedRow(selected_snapshot_point_index); + } +} + +void MainWindow::OpenSelectedSnapshot() +{ + // Get the RmtSnapshotPoint pointer from the current list item. + const int32_t current_row_index = ui_->snapshot_combo_box_->CurrentRow(); + if (current_row_index >= 0) + { + const QVariant item_data = ui_->snapshot_combo_box_->ItemData(current_row_index, Qt::UserRole); + const uintptr_t snapshot_point_address = item_data.toULongLong(); + RmtSnapshotPoint* snapshot_point = (RmtSnapshotPoint*)snapshot_point_address; + RMT_ASSERT(snapshot_point); + + // Do not attempt to re-open the currently open snapshot. + RmtDataSnapshot* current_snapshot = TraceManager::Get().GetOpenSnapshot(); + if (current_snapshot != nullptr && current_snapshot->snapshot_point != snapshot_point) + { + // if switching snapshot on the resource details pane, navigate to the heap overview + // pane since the selected resource will not be valid + if (pane_manager_.GetCurrentPane() == rmv::kPaneSnapshotResourceDetails) + { + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotResourceOverview); + } + + emit MessageManager::Get().SelectSnapshot(snapshot_point); + emit MessageManager::Get().OpenSnapshot(snapshot_point); + } + } +} + +void MainWindow::BroadcastOnTraceClose() +{ + pane_manager_.OnTraceClose(); +} + +void MainWindow::UpdateStartListRow(const int row) +{ + pane_manager_.UpdateStartListRow(row); + BroadcastPaneSwitched(); +} + +void MainWindow::UpdateTimelineListRow(const int row) +{ + pane_manager_.UpdateTimelineListRow(row); + BroadcastPaneSwitched(); +} + +void MainWindow::UpdateSnapshotListRow(const int row) +{ + pane_manager_.UpdateSnapshotListRow(row); + BroadcastPaneSwitched(); +} + +void MainWindow::UpdateCompareListRow(const int row) +{ + pane_manager_.UpdateCompareListRow(row); + BroadcastPaneSwitched(); +} + +void MainWindow::UpdateSettingsListRow(const int row) +{ + pane_manager_.UpdateSettingsListRow(row); + BroadcastPaneSwitched(); +} + +void MainWindow::UpdateMainTabIndex(const int row) +{ + pane_manager_.UpdateMainTabIndex(row); + BroadcastPaneSwitched(); +} + +void MainWindow::BroadcastPaneSwitched() +{ + UpdateSnapshotCombobox(SnapshotManager::Get().GetSelectedSnapshotPoint()); + + // Catch any transition to the snapshot tab from any other tab and + // make sure the snapshot is opened, specifically the case of selecting + // something in the timeline pane, and then moving to the snapshot view. + MainPanes previous_main_pane = pane_manager_.GetMainPaneFromPane(pane_manager_.GetPreviousPane()); + MainPanes current_main_pane = pane_manager_.GetMainPaneFromPane(pane_manager_.GetCurrentPane()); + if (current_main_pane == kMainPaneSnapshot && previous_main_pane != kMainPaneSnapshot) + { + // Only open the snapshot if it's different to the one already opened + RmtSnapshotPoint* snapshot_point = SnapshotManager::Get().GetSelectedSnapshotPoint(); + RmtDataSnapshot* snapshot = nullptr; + if (snapshot_point != nullptr) + { + snapshot = snapshot_point->cached_snapshot; + } + if (TraceManager::Get().SnapshotAlreadyOpened(snapshot) == false || snapshot == nullptr) + { + OpenSnapshot(snapshot_point); + } + } + UpdateTitlebar(); + ResizeNavigationLists(); + + pane_manager_.PaneSwitched(); +} + +void MainWindow::BroadcastReset() +{ + UpdateTitlebar(); + + pane_manager_.Reset(); +} + +void MainWindow::OpenSnapshot(RmtSnapshotPoint* snapshot_point) +{ + if (!snapshot_point) + { + // disable snapshot window + TraceManager::Get().ClearOpenSnapshot(); + ui_->snapshot_start_stack_->setCurrentIndex(kIndexEmptyPane); + return; + } + + if (!snapshot_point->cached_snapshot) + { + qApp->setOverrideCursor(Qt::BusyCursor); + // Generate the snapshot via a worker thread. When the worker + // thread finishes, this function will be called again but this time + // snapshot_point->cachedSnapshot will contain valid data + SnapshotManager::Get().GenerateSnapshot(snapshot_point, this, ui_->main_tab_widget_); + } + else + { + // Snapshot is loaded at this point. + // deselect any selected resource + emit MessageManager::Get().ResourceSelected(0); + + TraceManager::Get().SetOpenSnapshot(snapshot_point->cached_snapshot); + + ui_->snapshot_start_stack_->setCurrentIndex(kIndexPopulatedPane); + UpdateCompares(); + + pane_manager_.OpenSnapshot(snapshot_point->cached_snapshot); + + UpdateTitlebar(); + UpdateSnapshotCombobox(snapshot_point); + + // Should only do this jump if we're on the timeline pane as this will catch the case + // of opening a snapshot while on the timeline pane, and jump to heap overview. + // It shouldn't do this if we open a snapshot from the snapshot pane itself. + if (pane_manager_.GetCurrentPane() == rmv::kPaneTimelineGenerateSnapshot) + { + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotHeapOverview); + } + ResizeNavigationLists(); + + qApp->restoreOverrideCursor(); + } +} + +void MainWindow::CompareSnapshot(RmtSnapshotPoint* snapshot_base, RmtSnapshotPoint* snapshot_diff) +{ + if (snapshot_base == nullptr || snapshot_diff == nullptr) + { + TraceManager::Get().ClearComparedSnapshots(); + + // disable compare window + ui_->compare_start_stack_->setCurrentIndex(kIndexEmptyPane); + return; + } + + if (!snapshot_base->cached_snapshot || !snapshot_diff->cached_snapshot) + { + qApp->setOverrideCursor(Qt::BusyCursor); + // Generate the snapshots via a worker thread. When the worker + // thread finishes, this function will be called again but this time + // snapshot_point->cachedSnapshot will contain valid data + SnapshotManager::Get().GenerateComparison(snapshot_base, snapshot_diff, this, ui_->main_tab_widget_); + } + else + { + TraceManager::Get().SetComparedSnapshot(snapshot_base->cached_snapshot, snapshot_diff->cached_snapshot); + + UpdateCompares(); + + UpdateTitlebar(); + + emit MessageManager::Get().NavigateToPane(rmv::kPaneCompareSnapshotDelta); + + qApp->restoreOverrideCursor(); + } +} + +QString MainWindow::GetTitleBarString() +{ + QString title = RMV_APP_NAME RMV_BUILD_SUFFIX; + + title.append(" - "); + QString version_string; + + version_string.sprintf("V%s", RMV_VERSION_STRING); + + title.append(version_string); + + return title; +} + +void MainWindow::UpdateTitlebar() +{ + QString title = ""; + + const TraceManager& trace_manager = TraceManager::Get(); + + if (trace_manager.DataSetValid()) + { + const QString file_name = trace_manager.GetTracePath(); + + if (pane_manager_.GetMainPaneFromPane(pane_manager_.GetCurrentPane()) == kMainPaneSnapshot) + { + const char* snapshot_name = trace_manager.GetOpenSnapshotName(); + if (snapshot_name != nullptr) + { + title.append(QString(snapshot_name)); + title.append(" - "); + } + } + if (pane_manager_.GetMainPaneFromPane(pane_manager_.GetCurrentPane()) == kMainPaneCompare) + { + const char* base_name = trace_manager.GetCompareSnapshotName(kSnapshotCompareBase); + const char* diff_name = trace_manager.GetCompareSnapshotName(kSnapshotCompareDiff); + if (base_name != nullptr && diff_name != nullptr) + { + title.append(QString(base_name) + " vs. " + QString(diff_name)); + title.append(" - "); + } + } + + title.append(file_name); + title.append(" - "); + } + + title.append(GetTitleBarString()); + + setWindowTitle(title); +} + +void MainWindow::UpdateCompares() +{ + if (TraceManager::Get().GetComparedSnapshot(kSnapshotCompareDiff) != nullptr) + { + ui_->compare_start_stack_->setCurrentIndex(kIndexPopulatedPane); + + snapshot_delta_pane_->Refresh(); + memory_leak_finder_pane_->Refresh(); + } +} + +void MainWindow::BroadcastChangeColoring() +{ + pane_manager_.ChangeColoring(); + snapshot_start_pane_->ChangeColoring(); + compare_start_pane_->ChangeColoring(); +} + +void MainWindow::InitializeJobQueue() +{ + const RmtErrorCode error_code = RmtJobQueueInitialize(&job_queue_, kThreadCount); + RMT_ASSERT(error_code == RMT_OK); +} + +RmtJobQueue* MainWindow::GetJobQueue() +{ + return &job_queue_; +} + +void MainWindow::DestroyJobQueue() +{ + RmtJobQueueShutdown(&job_queue_); +} diff --git a/source/frontend/views/main_window.h b/source/frontend/views/main_window.h new file mode 100644 index 0000000..1b46c7e --- /dev/null +++ b/source/frontend/views/main_window.h @@ -0,0 +1,296 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV main window. +//============================================================================= + +#ifndef RMV_VIEWS_MAIN_WINDOW_H_ +#define RMV_VIEWS_MAIN_WINDOW_H_ + +#include +#include +#include +#include + +#include "qt_common/custom_widgets/file_loading_widget.h" +#include "qt_common/custom_widgets/navigation_bar.h" +#include "qt_common/custom_widgets/navigation_list_widget.h" + +#include "rmt_data_set.h" +#include "rmt_resource_list.h" +#include "rmt_job_system.h" + +#include "models/trace_manager.h" +#include "util/definitions.h" +#include "views/debug_window.h" +#include "views/pane_manager.h" + +#include "views/start/welcome_pane.h" +#include "views/start/recent_traces_pane.h" + +class TimelinePane; + +class SnapshotDeltaPane; +class MemoryLeakFinderPane; +class AllocationDeltaPane; + +class SettingsPane; + +namespace Ui +{ + class MainWindow; +} + +namespace rmv +{ + class ThreadController; +} // namespace rmv + +/// Support for RMV main window. +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The window's parent. + explicit MainWindow(QWidget* parent = nullptr); + + /// Destructor. + virtual ~MainWindow(); + + /// Overridden window resize event. Handle what happens when a user resizes the + /// main window. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event); + + /// Overridden window move event. Handle what happens when a user moves the + /// main window. + /// \param event the move event object. + virtual void moveEvent(QMoveEvent* event); + + /// Handle what happens when X button is pressed. + /// \param event the close event. + virtual void closeEvent(QCloseEvent* event); + + /// Reset any UI elements that need resetting when a new trace file is loaded. + /// This include references to models or event indices that rely on backend + /// data. + void ResetUI(); + + /// Initialize the job queue used globally throughout the application. + static void InitializeJobQueue(); + + /// Get the job queue. + /// \return The job queue. + static RmtJobQueue* GetJobQueue(); + + /// Destroy the job queue. + static void DestroyJobQueue(); + + /// Called when an animation needs to be loaded onto a window. + /// \param parent The parent window. + /// \param height_offset The offset from the top of the parent widget. + void StartAnimation(QWidget* parent, int height_offset); + + /// Called when trace file changed to stop animation. + void StopAnimation(); + + /// Called when trace file finished loading. + void TraceLoadComplete(); + +public slots: + /// Called when selecting a recent trace from the recent traces list. + /// \param trace_file The trace file to load, including full path. + void LoadTrace(const QString& trace_file); + + /// Close an RMV trace file. + void CloseTrace(); + + /// Open an RMV trace file. + /// Present the user with a file selection dialog box and load the trace that + /// the user chooses. + void OpenTrace(); + + /// Open recent traces pane. + void OpenRecentTracesPane(); + + /// Populate recent files menu/list. + void SetupRecentTracesMenu(); + +private slots: + /// Open RMV help file. + /// Present the user with help regarding RMV. + void OpenHelp(); + + /// Display RMV about information. + /// Present the user with information about RMV. + void OpenAboutPane(); + + /// Open a snapshot. + /// \param snapshot_point The snapshot to open. + void OpenSnapshot(RmtSnapshotPoint* snapshot_point); + + /// Open 2 snapshots for comparison. + /// \param snapshot_base The base snapshot for comparison. + /// \param snapshot_diff The snapshot to compare against the base snapshot.. + void CompareSnapshot(RmtSnapshotPoint* snapshot_base, RmtSnapshotPoint* snapshot_diff); + + /// Open currently selected snapshot from the combo box below the snapshot tab + /// menu. + void OpenSelectedSnapshot(); + + /// Cycle the time units and update the main window to ensure everything is + /// redrawn to reflect the new time units. + void CycleTimeUnits(); + + /// Update the title bar based on app state. + void UpdateTitlebar(); + + /// Update all comparison panes. + void UpdateCompares(); + + /// Update UI coloring on all panes. Also manually update coloring on the start panes + /// since these are pre-generated in the UI file and not created dynamically + void BroadcastChangeColoring(); + + /// Let all panes know they should reset themselves. + void BroadcastReset(); + + /// Let all panes know they should update themselves. + void BroadcastOnTraceClose(); + + /// Resize UI elements when the DPI scale factor changes. + void OnScaleFactorChanged(); + + /// Handle what happens when an item in the start tab list is clicked on. + /// \param row The row in the list selected. + void UpdateStartListRow(const int row); + + /// Handle what happens when an item in the timeline tab list is clicked on. + /// \param row The row in the list selected. + void UpdateTimelineListRow(const int row); + + /// Handle what happens when an item in the snapshot tab list is clicked on. + /// \param row The row in the list selected. + void UpdateSnapshotListRow(const int row); + + /// Handle what happens when an item in the compare tab list is clicked on. + /// \param row The row in the list selected. + void UpdateCompareListRow(const int row); + + /// Handle what happens when an item in the settings tab list is clicked on. + /// \param row The row in the list selected. + void UpdateSettingsListRow(const int row); + + /// Handle what happens when an item on the tab bar is clicked on. + /// \param row The row in the list selected. + void UpdateMainTabIndex(const int row); + + /// Navigate to a specific pane. + /// \param pane The pane to jump to. + void ViewPane(int pane); + +private: + /// Let all panes know a pane switch happened. + void BroadcastPaneSwitched(); + + /// Handle what happens when RMV is closed. + void CloseRmv(); + + /// Setup navigation bar on the top. + void SetupTabBar(); + + /// Create re-usable actions for our menu bar and possibly other widgets. + void CreateActions(); + + /// Fill in the menu bar with items. + void CreateMenus(); + + /// Update the snapshots combo box on the main snapshots tab. + /// \param selected_snapshot_point The snapshot point selected that should be shown + /// in the combo box. + void UpdateSnapshotCombobox(RmtSnapshotPoint* selected_snapshot_point); + + /// Resize NavigationLists across several tabs of the MainWindow so that they have a consistent + /// width. The widest width will either be defined by a text item in the navigation list, or + /// by the text or ArrowIconComboBox at the bottom of the snapshots tab. Since these snapshots are + /// saved in the trace files, loading a new trace file may also load a snapshot name which is wider + /// than the any of the navigation item names, so this method should be called whenever a trace is + /// loaded or when the DPI scale is changed. + void ResizeNavigationLists(); + + /// Setup main/debug window sizes and locations. + /// \param loaded_settings The bool to indicate if settings should be loaded. + void SetupWindowRects(bool loaded_settings); + + /// Setup mapping for keyboard binds. + /// \param mapper signal mapper. + /// \param key pressed key. + /// \param pane target pane. + void SetupHotkeyNavAction(QSignalMapper* mapper, int key, int pane); + + /// Handle a drag enter event. + /// \param event drag enter event. + void dragEnterEvent(QDragEnterEvent* event); + + /// Handle a drag-n-drop event. + /// \param event drop event. + void dropEvent(QDropEvent* event); + + /// Setup the next pane and navigate to it. + /// \param pane The pane to jump to. + rmv::RMVPane SetupNextPane(rmv::RMVPane pane); + + /// Construct title bar content. + QString GetTitleBarString(); + + /// Template function to create a new pane of a certain type + /// Also saves the base pointer to an array for the pane broadcaster + template + PaneType* CreatePane() + { + PaneType* pane = new PaneType(this); + pane_manager_.AddPane(pane); + return pane; + } + + Ui::MainWindow* ui_; ///< Pointer to the Qt UI design. + +#ifdef RMV_DEBUG_WINDOW + DebugWindow debug_window_; ///< A supplemental debug window to output custom messages. +#endif + + QMenu* file_menu_; ///< File menu control. + QAction* open_trace_action_; ///< Action to open an RMV trace. + QAction* close_trace_action_; ///< Action to close an RMV trace. + QAction* exit_action_; ///< Action to exit RMV. + QAction* help_action_; ///< Action to display help. + QAction* about_action_; ///< Action to display About Radeon Memory Visualizer. + + QMenu* help_menu_; ///< Help menu control + + QMenu* recent_traces_menu_; ///< Sub menu containing recently opened files. + QVector recent_trace_mappers_; ///< Map signals to the recent traces when clicked on. + QVector recent_trace_actions_; ///< List of actions for recent traces. + + FileLoadingWidget* file_load_animation_; ///< Widget to show animation. + + WelcomePane* welcome_pane_; ///< Pointer to welcome pane. + RecentTracesPane* recent_traces_pane_; ///< Pointer to recent traces pane. + TimelinePane* timeline_pane_; ///< Pointer to timeline pane. + + SnapshotDeltaPane* snapshot_delta_pane_; ///< Pointer to snapshot delta pane. + MemoryLeakFinderPane* memory_leak_finder_pane_; ///< Pointer to memory leak finder pane. + SettingsPane* settings_pane_; ///< Pointer to settings pane. + BasePane* snapshot_start_pane_; ///< Pointer to snapshot start pane. + BasePane* compare_start_pane_; ///< Pointer to compare start pane. + + NavigationBar navigation_bar_; ///< The Back/Forward Navigation buttons added to the main tab bar. + rmv::PaneManager pane_manager_; ///< The class responsible for managing the relationships between different panes. + + static RmtJobQueue job_queue_; ///< The job queue; +}; + +#endif // RMV_VIEWS_MAIN_WINDOW_H_ diff --git a/source/frontend/views/main_window.ui b/source/frontend/views/main_window.ui new file mode 100644 index 0000000..08afe4d --- /dev/null +++ b/source/frontend/views/main_window.ui @@ -0,0 +1,477 @@ + + + MainWindow + + + + 0 + 0 + 894 + 639 + + + + MainWindow + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + true + + + + NAVIGATION + + + + + START + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + Welcome + + + + + Recent traces + + + + + About + + + + + + + + + + + + TIMELINE + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + Snapshot generation + + + + + Device configuration + + + + + + + + + + + + SNAPSHOT + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + Heap overview + + + + + Resource overview + + + + + Allocation overview + + + + + Resource list + + + + + Allocation explorer + + + + + Resource details + + + + + + + + + 0 + 0 + + + + 1 + + + You are currently viewing: + + + 5 + + + + + + + + 0 + 0 + + + + Snapshot + + + + + + + + 0 + 0 + + + + + + + 5 + + + + + + + + + + + + + + + + + COMPARE + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + Snapshot delta + + + + + Memory leak finder + + + + + + + + + + + + + + + + SPACER + + + + + SETTINGS + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + General + + + + + Themes and colors + + + + + Keyboard shortcuts + + + + + + + + + + + + + + + + + 0 + 0 + 894 + 26 + + + + + + + + ArrowIconComboBox + QPushButton +
qt_common/custom_widgets/arrow_icon_combo_box.h
+ 1 +
+ + NavigationListWidget + QListWidget +
qt_common/custom_widgets/navigation_list_widget.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledMenuBar + QMenuBar +
qt_common/custom_widgets/scaled_menu_bar.h
+
+ + TabWidget + QTabWidget +
qt_common/custom_widgets/tab_widget.h
+ 1 +
+ + SnapshotStartPane + QWidget +
views/snapshot/snapshot_start_pane.h
+ 1 +
+ + CompareStartPane + QWidget +
views/compare/compare_start_pane.h
+ 1 +
+
+ + +
diff --git a/source/frontend/views/navigation_manager.cpp b/source/frontend/views/navigation_manager.cpp new file mode 100644 index 0000000..24f070e --- /dev/null +++ b/source/frontend/views/navigation_manager.cpp @@ -0,0 +1,293 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for back/fwd navigation manager +//============================================================================= + +#include "views/navigation_manager.h" + +#include + +#include "qt_common/custom_widgets/navigation_bar.h" + +#include "rmt_assert.h" +#include "rmt_data_set.h" + +#include "models/message_manager.h" + +namespace rmv +{ + NavigationManager::NavigationManager() + : navigation_history_location_(0) + , current_pane_(kPaneStartWelcome) + { + Reset(); + } + + NavigationManager::~NavigationManager() + { + } + + NavigationManager& NavigationManager::Get() + { + static NavigationManager instance; + + return instance; + } + + void NavigationManager::RecordNavigationEventPaneSwitch(RMVPane pane) + { + const NavEvent& curr_event = navigation_history_[navigation_history_location_]; + + if (curr_event.type == kNavigationTypePaneSwitch) + { + // Prevent dupes + if (curr_event.pane != pane) + { + AddNewPaneSwitch(pane); + } + } + else + { + AddNewPaneSwitch(pane); + } + } + + void NavigationManager::Reset() + { + navigation_history_.clear(); + + NavEvent first_event = {}; + first_event.type = kNavigationTypePaneSwitch; + first_event.pane = kPaneStartWelcome; + + navigation_history_.push_back(first_event); + navigation_history_location_ = 0; + + current_pane_ = kPaneStartWelcome; + + emit EnableBackNavButton(false); + emit EnableForwardNavButton(false); + } + + void NavigationManager::UpdateCurrentPane(RMVPane pane) + { + current_pane_ = pane; + } + + void NavigationManager::ReplayNavigationEvent(const NavEvent& event) + { + emit MessageManager::Get().NavigateToPaneUnrecorded(event.pane); + } + + NavigationManager::NavEvent NavigationManager::FindPrevNavigationEvent() + { + NavEvent out = navigation_history_[navigation_history_location_]; + NavEvent curr = navigation_history_[navigation_history_location_]; + + if (navigation_history_location_ > 0) + { + int32_t idx = navigation_history_location_ - 1; + NavEvent prev = navigation_history_[idx]; + + out = prev; + navigation_history_location_ = idx; + + if ((prev.type == kNavigationTypePaneSwitch) && (curr.pane == prev.pane)) + { + idx--; + if (idx > 0) + { + NavEvent prev_prev = navigation_history_[idx]; + out = prev_prev; + navigation_history_location_ = idx; + } + } + } + + return out; + } + + NavigationManager::NavEvent NavigationManager::FindNextNavigationEvent() + { + NavEvent out = navigation_history_[navigation_history_location_]; + + const int32_t nav_limit = navigation_history_.size() - 1; + + if (navigation_history_location_ < nav_limit) + { + int32_t idx = navigation_history_location_ + 1; + NavEvent next = navigation_history_[idx]; + + out = next; + navigation_history_location_ = idx; + } + + return out; + } + + void NavigationManager::NavigateBack() + { + if (navigation_history_location_ > 0) + { + RMT_ASSERT(navigation_history_.size() > 1); + + NavEvent prev_event = FindPrevNavigationEvent(); + + ReplayNavigationEvent(prev_event); + + emit EnableForwardNavButton(true); + } + + if (navigation_history_location_ <= 0) + { + emit EnableBackNavButton(false); + } + } + + void NavigationManager::NavigateForward() + { + const int32_t nav_limit = navigation_history_.size() - 1; + + if (navigation_history_location_ < nav_limit) + { + NavEvent next_event = FindNextNavigationEvent(); + + ReplayNavigationEvent(next_event); + + emit EnableBackNavButton(true); + } + + if (navigation_history_location_ >= nav_limit) + { + emit EnableForwardNavButton(false); + } + } + + void NavigationManager::DiscardObsoleteNavHistory() + { + for (int32_t i = navigation_history_.size() - 1; i > 0; i--) + { + if (i > navigation_history_location_) + { + navigation_history_.pop_back(); + + emit EnableForwardNavButton(false); + } + } + } + + void NavigationManager::AddNewEvent(const NavEvent& event) + { + navigation_history_.push_back(event); + navigation_history_location_++; + + emit EnableBackNavButton(true); + } + + void NavigationManager::AddNewPaneSwitch(RMVPane pane) + { + DiscardObsoleteNavHistory(); + + NavEvent new_event = {}; + new_event.type = kNavigationTypePaneSwitch; + new_event.pane = pane; + + AddNewEvent(new_event); + } + + const QString NavigationManager::NavigationEventString(const NavEvent& event) const + { + QString out = ""; + + if (event.type == kNavigationTypePaneSwitch) + { + out += GetPaneString(event.pane); + } + + return out; + } + + void NavigationManager::PrintHistory() const + { + QString out = ""; + + const uint32_t nav_history_size = navigation_history_.size(); + + for (uint32_t i = 0; i < nav_history_size; i++) + { + const NavEvent& curr_event = navigation_history_[i]; + + out += "[" + QString::number(i) + "]=" + NavigationEventString(curr_event); + + if (i < nav_history_size - 1) + { + out += " | "; + } + } + + qDebug() << out; + } + + QString NavigationManager::GetPaneString(RMVPane pane) const + { + QString out = ""; + + switch (pane) + { + case kPaneStartWelcome: + out = "Welcome"; + break; + case kPaneStartRecentTraces: + out = "Recent traces"; + break; + case kPaneStartAbout: + out = "About"; + break; + case kPaneTimelineGenerateSnapshot: + out = "Generate snapshot"; + break; + case kPaneTimelineDeviceConfiguration: + out = "Device configuration"; + break; + case kPaneSnapshotResourceOverview: + out = "Resource overview"; + break; + case kPaneSnapshotAllocationOverview: + out = "Allocation overview"; + break; + case kPaneSnapshotResourceList: + out = "Resource list"; + break; + case kPaneSnapshotResourceDetails: + out = "Resource details"; + break; + case kPaneSnapshotAllocationExplorer: + out = "Allocation explorer"; + break; + case kPaneSnapshotHeapOverview: + out = "Heap overview"; + break; + case kPaneCompareSnapshotDelta: + out = "Snapshot delta"; + break; + case kPaneCompareMemoryLeakFinder: + out = "Memory leak finder"; + break; + case kPaneSettingsGeneral: + out = "General"; + break; + case kPaneSettingsThemesAndColors: + out = "Themes and colors"; + break; + case kPaneSettingsKeyboardShortcuts: + out = "Keyboard shortcuts"; + break; + default: + break; + } + + return out; + } +} // namespace rmv \ No newline at end of file diff --git a/source/frontend/views/navigation_manager.h b/source/frontend/views/navigation_manager.h new file mode 100644 index 0000000..8deb448 --- /dev/null +++ b/source/frontend/views/navigation_manager.h @@ -0,0 +1,120 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for back/fwd navigation manager +//============================================================================= + +#ifndef RMV_VIEWS_NAVIGATION_MANAGER_H_ +#define RMV_VIEWS_NAVIGATION_MANAGER_H_ + +#include +#include +#include + +#include "util/definitions.h" +#include "views/pane_manager.h" + +namespace rmv +{ + /// Class that handles back and forward navigation. + class NavigationManager : public QWidget + { + Q_OBJECT + + public: + /// NavigationManager instance get function. + /// \return a reference to the NavigationManager instance. + static NavigationManager& Get(); + + /// Record a pane switch event. + /// \param pane The new pane. + void RecordNavigationEventPaneSwitch(RMVPane pane); + + /// Go back to starting state. + void Reset(); + + /// Update the current pane. + /// \param pane The current pane. + void UpdateCurrentPane(RMVPane pane); + + signals: + /// Signal to enable the navigation back button. + void EnableBackNavButton(bool enable); + + /// Signal to enable the navigation forward button. + void EnableForwardNavButton(bool enable); + + public slots: + /// Go forward. + void NavigateForward(); + + /// Go back. + void NavigateBack(); + + private: + /// Enum of navigation types. + enum NavType + { + kNavigationTypeUndefined, + kNavigationTypePaneSwitch, + + kNavigationTypeCount, + }; + + /// Struct to hold navigation event data. + struct NavEvent + { + NavType type; ///< Navigation type. + RMVPane pane; ///< Destination pane. + }; + + /// Constructor. + explicit NavigationManager(); + + /// Destructor. + virtual ~NavigationManager(); + + /// Remove history if user went back towards the middle and then somewhere new. + void DiscardObsoleteNavHistory(); + + /// Replay a previous navigation event. + /// \param event The navigation event. + void AddNewEvent(const NavEvent& event); + + /// Helper function to print a Navigation event. + /// \param event the navigation event. + /// \return string version of the navigation event. + const QString NavigationEventString(const NavEvent& event) const; + + /// Register a new pane switch. + /// \param pane The new pane. + void AddNewPaneSwitch(RMVPane pane); + + /// Replay a previous navigation event. + /// \param event The navigation event. + void ReplayNavigationEvent(const NavEvent& event); + + /// Intelligently find the next navigation event. + /// \return The next navigation event. + NavEvent FindNextNavigationEvent(); + + /// Intelligently find the previous navigation event. + /// \return The previous navigation event. + NavEvent FindPrevNavigationEvent(); + + /// Helper function to print the backing structure. + void PrintHistory() const; + + /// Helper function to convert RmvPane enum to string. + /// \param pane the pane. + /// \return string version of pane name. + QString GetPaneString(RMVPane pane) const; + + QList navigation_history_; ///< Track user navigation. + int32_t navigation_history_location_; ///< Current location in navigation history. + RMVPane current_pane_; ///< Navigation manager is aware of the current pane. + }; +} // namespace rmv + +#endif // RMV_VIEWS_NAVIGATION_MANAGER_H_ diff --git a/source/frontend/views/pane_manager.cpp b/source/frontend/views/pane_manager.cpp new file mode 100644 index 0000000..2dec55c --- /dev/null +++ b/source/frontend/views/pane_manager.cpp @@ -0,0 +1,386 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the pane broadcaster. +/// This class is responsible for sending event messages to all panes. +//============================================================================= + +#include "views/navigation_manager.h" +#include "views/pane_manager.h" + +namespace rmv +{ + // An enum of all the panes in the start menu. + enum StartPanes + { + kStartPaneWelcome, + kStartPaneRecentTraces, + kStartPaneAbout, + + kStartPaneCount, + }; + + // An enum of all the panes in the timeline menu. + enum TimelinePanes + { + kTimelinePaneGenerateSnapshot, + kTimelinePaneDeviceConfiguration, + + kTimelinePaneCount, + }; + + // An enum of all the panes in the snapshots menu. + enum SnapshotPanes + { + kSnapshotPaneHeapOverview, + kSnapshotPaneResourceOverview, + kSnapshotPaneAllocationOverview, + kSnapshotPaneResourceList, + kSnapshotPaneAllocationExplorer, + kSnapshotPaneResourceDetails, + + kSnapshotPaneCount, + }; + + // An enum of all the panes in the compare menu. + enum ComparePanes + { + kComparePaneSnapshotDelta, + kComparePaneMemoryLeakFinder, + + kComparePaneCount, + }; + + // An enum of all the panes in the settings menu. + enum SettingsPanes + { + kSettingsPaneGeneral, + kSettingsPaneThemesAndColors, + kSettingsPaneKeyboardShortcuts, + + kSettingsPaneCount, + }; + + static const int32_t kShift = 16; + static const int32_t kMask = 0xffff; + + // A map of pane ID to the pane components. The 2 components share an integer with each + // component using 16 bits. + static const std::map kPaneToComponentsMap = { + {kPaneStartWelcome, (kMainPaneStart << kShift) | kStartPaneWelcome}, + {kPaneStartRecentTraces, (kMainPaneStart << kShift) | kStartPaneRecentTraces}, + {kPaneStartAbout, (kMainPaneStart << kShift) | kStartPaneAbout}, + + {kPaneTimelineGenerateSnapshot, (kMainPaneTimeline << kShift) | kTimelinePaneGenerateSnapshot}, + {kPaneTimelineDeviceConfiguration, (kMainPaneTimeline << kShift) | kTimelinePaneDeviceConfiguration}, + + {kPaneSnapshotHeapOverview, (kMainPaneSnapshot << kShift) | kSnapshotPaneHeapOverview}, + {kPaneSnapshotResourceOverview, (kMainPaneSnapshot << kShift) | kSnapshotPaneResourceOverview}, + {kPaneSnapshotAllocationOverview, (kMainPaneSnapshot << kShift) | kSnapshotPaneAllocationOverview}, + {kPaneSnapshotResourceList, (kMainPaneSnapshot << kShift) | kSnapshotPaneResourceList}, + {kPaneSnapshotResourceDetails, (kMainPaneSnapshot << kShift) | kSnapshotPaneResourceDetails}, + {kPaneSnapshotAllocationExplorer, (kMainPaneSnapshot << kShift) | kSnapshotPaneAllocationExplorer}, + + {kPaneCompareSnapshotDelta, (kMainPaneCompare << kShift) | kComparePaneSnapshotDelta}, + {kPaneCompareMemoryLeakFinder, (kMainPaneCompare << kShift) | kComparePaneMemoryLeakFinder}, + + {kPaneSettingsGeneral, (kMainPaneSettings << kShift) | kSettingsPaneGeneral}, + {kPaneSettingsThemesAndColors, (kMainPaneSettings << kShift) | kSettingsPaneThemesAndColors}, + {kPaneSettingsKeyboardShortcuts, (kMainPaneSettings << kShift) | kSettingsPaneKeyboardShortcuts}, + }; + + // This is the inverse of the map above and is generated in code. + static std::map components_to_pane_map; + + PaneManager::PaneManager() + : nav_location_{} + , current_pane_(kPaneStartWelcome) + , previous_pane_(kPaneStartWelcome) + { + ResetNavigation(); + for (auto iter = kPaneToComponentsMap.begin(); iter != kPaneToComponentsMap.end(); ++iter) + { + components_to_pane_map.insert(std::make_pair((*iter).second, (*iter).first)); + } + } + + PaneManager::~PaneManager() + { + for (auto it = panes_.begin(); it != panes_.end(); ++it) + { + if ((*it) != nullptr) + { + delete (*it); + } + } + } + + const NavLocation& PaneManager::ResetNavigation() + { + nav_location_.main_tab_index = kMainPaneStart; + nav_location_.start_list_row = kStartPaneWelcome; + nav_location_.timeline_list_row = kTimelinePaneGenerateSnapshot; + nav_location_.snapshot_list_row = kSnapshotPaneHeapOverview; + nav_location_.compare_list_row = kComparePaneSnapshotDelta; + nav_location_.settings_list_row = kSettingsPaneGeneral; + + return nav_location_; + } + + MainPanes PaneManager::GetMainPaneFromPane(RMVPane pane) + { + auto iter = kPaneToComponentsMap.find(static_cast(pane)); + if (iter != kPaneToComponentsMap.end()) + { + return static_cast(iter->second >> kShift); + } + return kMainPaneStart; + } + + RMVPane PaneManager::GetCurrentPane() + { + return current_pane_; + } + + RMVPane PaneManager::GetPreviousPane() + { + return previous_pane_; + } + + const NavLocation& PaneManager::SetupNextPane(int pane) + { + auto iter = kPaneToComponentsMap.find(static_cast(pane)); + if (iter != kPaneToComponentsMap.end()) + { + int32_t pane_info = iter->second; + int32_t main_tab_index = pane_info >> kShift; + int32_t list_row = pane_info & kMask; + nav_location_.main_tab_index = main_tab_index; + + switch (main_tab_index) + { + case kMainPaneStart: + nav_location_.start_list_row = list_row; + break; + + case kMainPaneTimeline: + nav_location_.timeline_list_row = list_row; + break; + + case kMainPaneSnapshot: + nav_location_.snapshot_list_row = list_row; + break; + + case kMainPaneCompare: + nav_location_.compare_list_row = list_row; + break; + + case kMainPaneSettings: + nav_location_.settings_list_row = list_row; + break; + + default: + break; + } + } + + return nav_location_; + } + + RMVPane PaneManager::UpdateCurrentPane() + { + // create the combined component + int32_t component = (nav_location_.main_tab_index << kShift); + + switch (nav_location_.main_tab_index) + { + case kMainPaneStart: + component |= nav_location_.start_list_row; + break; + + case kMainPaneTimeline: + component |= nav_location_.timeline_list_row; + break; + + case kMainPaneSnapshot: + component |= nav_location_.snapshot_list_row; + break; + + case kMainPaneCompare: + component |= nav_location_.compare_list_row; + break; + + case kMainPaneSettings: + component |= nav_location_.settings_list_row; + break; + + default: + break; + } + + previous_pane_ = current_pane_; + auto iter = components_to_pane_map.find(component); + if (iter != components_to_pane_map.end()) + { + current_pane_ = (*iter).second; + } + + NavigationManager::Get().UpdateCurrentPane(current_pane_); + return current_pane_; + } + + bool PaneManager::UpdateMainTabIndex(int idx) + { + bool result = false; + + if ((idx >= kMainPaneStart) && (idx < kMainPaneCount)) + { + nav_location_.main_tab_index = idx; + + // if the snapshot pane is clicked on, make sure the currently viewed snapshot is selected + // and ensure the combo box is populated correctly + if (idx == kMainPaneSnapshot) + { + result = true; + } + + UpdateCurrentPane(); + + NavigationManager::Get().RecordNavigationEventPaneSwitch(current_pane_); + } + return result; + } + + void PaneManager::UpdateStartListRow(const int row) + { + if (row < kStartPaneCount) + { + nav_location_.start_list_row = row; + + UpdateCurrentPane(); + + NavigationManager::Get().RecordNavigationEventPaneSwitch(current_pane_); + } + } + + void PaneManager::UpdateTimelineListRow(const int row) + { + if (row < kTimelinePaneCount) + { + nav_location_.timeline_list_row = row; + + UpdateCurrentPane(); + + NavigationManager::Get().RecordNavigationEventPaneSwitch(current_pane_); + } + } + + void PaneManager::UpdateSnapshotListRow(const int row) + { + if (row < kSnapshotPaneCount) + { + nav_location_.snapshot_list_row = row; + + UpdateCurrentPane(); + + NavigationManager::Get().RecordNavigationEventPaneSwitch(current_pane_); + } + } + + void PaneManager::UpdateCompareListRow(const int row) + { + if (row < kComparePaneCount) + { + nav_location_.compare_list_row = row; + + UpdateCurrentPane(); + + NavigationManager::Get().RecordNavigationEventPaneSwitch(current_pane_); + } + } + + void PaneManager::UpdateSettingsListRow(const int row) + { + if (row < kSettingsPaneCount) + { + nav_location_.settings_list_row = row; + + UpdateCurrentPane(); + + NavigationManager::Get().RecordNavigationEventPaneSwitch(current_pane_); + } + } + + void PaneManager::AddPane(BasePane* pane) + { + panes_.push_back(pane); + } + + void PaneManager::OnTraceClose() + { + for (auto it = panes_.begin(); it != panes_.end(); ++it) + { + if ((*it) != nullptr) + { + (*it)->OnTraceClose(); + } + } + } + + void PaneManager::PaneSwitched() + { + for (auto it = panes_.begin(); it != panes_.end(); ++it) + { + if ((*it) != nullptr) + { + (*it)->PaneSwitched(); + } + } + } + + void PaneManager::Reset() + { + for (auto it = panes_.begin(); it != panes_.end(); ++it) + { + if ((*it) != nullptr) + { + (*it)->Reset(); + } + } + } + + void PaneManager::ChangeColoring() + { + for (auto it = panes_.begin(); it != panes_.end(); ++it) + { + if ((*it) != nullptr) + { + (*it)->ChangeColoring(); + } + } + } + + void PaneManager::OpenSnapshot(RmtDataSnapshot* snapshot) + { + for (auto it = panes_.begin(); it != panes_.end(); ++it) + { + if ((*it) != nullptr) + { + (*it)->OpenSnapshot(snapshot); + } + } + } + + void PaneManager::SwitchTimeUnits() + { + for (auto it = panes_.begin(); it != panes_.end(); ++it) + { + if ((*it) != nullptr) + { + (*it)->SwitchTimeUnits(); + } + } + } + +} // namespace rmv diff --git a/source/frontend/views/pane_manager.h b/source/frontend/views/pane_manager.h new file mode 100644 index 0000000..a05115e --- /dev/null +++ b/source/frontend/views/pane_manager.h @@ -0,0 +1,183 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the pane broadcaster. +/// This class is responsible for sending event messages to all panes. +//============================================================================= + +#ifndef RMV_VIEWS_PANE_MANAGER_H_ +#define RMV_VIEWS_PANE_MANAGER_H_ + +#include +#include + +#include "rmt_data_set.h" + +#include "base_pane.h" + +/// An enum of all the elements in the tab menu. +enum MainPanes +{ + kMainPaneNavigation, + kMainPaneStart, + kMainPaneTimeline, + kMainPaneSnapshot, + kMainPaneCompare, + kMainPaneSpacer, + kMainPaneSettings, + + kMainPaneCount, +}; + +/// Used to control and track user navigation. +struct NavLocation +{ + int32_t main_tab_index; ///< Main tab index. + int32_t start_list_row; ///< Start list row. + int32_t timeline_list_row; ///< Timeline list row. + int32_t snapshot_list_row; ///< Snapshot list row. + int32_t compare_list_row; ///< Compare list row. + int32_t settings_list_row; ///< Settings list row. +}; + +namespace rmv +{ + /// An enum of all the panes in RMV. + enum RMVPane + { + kPaneStartWelcome, + kPaneStartRecentTraces, + kPaneStartAbout, + kPaneTimelineGenerateSnapshot, + kPaneTimelineDeviceConfiguration, + kPaneSnapshotHeapOverview, + kPaneSnapshotResourceOverview, + kPaneSnapshotAllocationOverview, + kPaneSnapshotResourceList, + kPaneSnapshotAllocationExplorer, + kPaneSnapshotResourceDetails, + kPaneCompareSnapshotDelta, + kPaneCompareMemoryLeakFinder, + kPaneSettingsGeneral, + kPaneSettingsThemesAndColors, + kPaneSettingsKeyboardShortcuts, + }; + + /// Hotkeys. + static const int kGotoGenerateSnapshotPane = Qt::Key_F; + static const int kGotoDeviceConfigurationPane = Qt::Key_G; + static const int kGotoHeapOverviewPane = Qt::Key_Q; + static const int kGotoResourceOverviewPane = Qt::Key_W; + static const int kGotoAllocationOverviewPane = Qt::Key_E; + static const int kGotoResourceListPane = Qt::Key_R; + static const int kGotoAllocationExplorerPane = Qt::Key_T; + static const int kGotoResourceHistoryPane = Qt::Key_Y; + static const int kGotoSnapshotDeltaPane = Qt::Key_A; + static const int kGotoMemoryLeakFinderPane = Qt::Key_S; + static const int kGotoWelcomePane = Qt::Key_Z; + static const int kGotoRecentSnapshotsPane = Qt::Key_X; + static const int kGotoKeyboardShortcutsPane = Qt::Key_C; + static const int kKeyNavBackwardBackspace = Qt::Key_Backspace; + static const int kKeyNavBackwardArrow = Qt::Key_Left; + static const int kKeyNavForwardArrow = Qt::Key_Right; + static const int kKeyNavUpArrow = Qt::Key_Up; + static const int kKeyNavDownArrow = Qt::Key_Down; + + /// Class to manage the panes and navigating betweem them. + class PaneManager : public QObject + { + Q_OBJECT + + public: + /// Constructor. + PaneManager(); + + /// Destructor. + ~PaneManager(); + + /// Take our navigation locations to starting state. + /// \return The reset Navigation location. + const NavLocation& ResetNavigation(); + + /// Navigate to a specific pane. + /// \param pane the pane to jump to. + /// \return The Navigation location. + const NavLocation& SetupNextPane(int pane); + + /// Work out current pane from app state. + /// Called every time there's a pane switch. + /// \return The new current pane. + RMVPane UpdateCurrentPane(); + + /// Store main tab index and update current pane. + /// \param idx tab index. + /// \return true if the snapshot pane was selected, false otherwise. + bool UpdateMainTabIndex(int idx); + + /// Get the main pane group from the pane. + /// \param pane The pane to check. + /// \return The main pane. + MainPanes GetMainPaneFromPane(RMVPane pane); + + /// Get the current pane. + /// \return The current pane. + RMVPane GetCurrentPane(); + + /// Get the previous pane. + /// \return The previous pane. + RMVPane GetPreviousPane(); + + /// Add a pane to the group. + /// \param pane The pane to add. + void AddPane(BasePane* pane); + + /// Call OnTraceClose() for all panes. + void OnTraceClose(); + + /// Call PaneSwitched() for all panes. + void PaneSwitched(); + + /// Call Reset() for all panes. + void Reset(); + + /// Call ChangeColoring() for all panes. + void ChangeColoring(); + + /// Call OpenSnapshot() for all panes. + /// \param snapshot The snapshot to open. + void OpenSnapshot(RmtDataSnapshot* snapshot); + + /// Call SwitchTimeUnits() for all panes. + void SwitchTimeUnits(); + + public slots: + /// Store start list row and update current pane. + /// \param row The tab index. + void UpdateStartListRow(const int row); + + /// Store timeline list row and update current pane. + /// \param row The row index. + void UpdateTimelineListRow(const int row); + + /// Store snapshot list row and update current pane. + /// \param row The tab index. + void UpdateSnapshotListRow(const int row); + + /// Store compare list row and update current pane. + /// \param row The tab index. + void UpdateCompareListRow(const int row); + + /// Store settings list row and update current pane. + /// \param row The tab index. + void UpdateSettingsListRow(const int row); + + private: + NavLocation nav_location_; ///< Track current list and tab locations. + RMVPane current_pane_; ///< Track current pane that is open. + RMVPane previous_pane_; ///< Track previous pane that was open. + std::vector panes_; ///< The group of panes to send messages to. + }; +} // namespace rmv + +#endif // RMV_VIEWS_PANE_MANAGER_H_ diff --git a/source/frontend/views/settings/keyboard_shortcuts_pane.cpp b/source/frontend/views/settings/keyboard_shortcuts_pane.cpp new file mode 100644 index 0000000..46ca643 --- /dev/null +++ b/source/frontend/views/settings/keyboard_shortcuts_pane.cpp @@ -0,0 +1,23 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Keyboard Shortcuts pane. +//============================================================================= + +#include "views/settings/keyboard_shortcuts_pane.h" + +#include "util/widget_util.h" + +KeyboardShortcutsPane::KeyboardShortcutsPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::KeyboardShortcutsPane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); +} + +KeyboardShortcutsPane::~KeyboardShortcutsPane() +{ +} diff --git a/source/frontend/views/settings/keyboard_shortcuts_pane.h b/source/frontend/views/settings/keyboard_shortcuts_pane.h new file mode 100644 index 0000000..a396049 --- /dev/null +++ b/source/frontend/views/settings/keyboard_shortcuts_pane.h @@ -0,0 +1,32 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Keyboard Shortcuts pane. +//============================================================================= + +#ifndef RMV_VIEWS_SETTINGS_KEYBOARD_SHORTCUTS_PANE_H_ +#define RMV_VIEWS_SETTINGS_KEYBOARD_SHORTCUTS_PANE_H_ + +#include "ui_keyboard_shortcuts_pane.h" + +#include "views/base_pane.h" + +/// Class declaration. +class KeyboardShortcutsPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit KeyboardShortcutsPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~KeyboardShortcutsPane(); + +private: + Ui::KeyboardShortcutsPane* ui_; ///< Pointer to the Qt UI design. +}; + +#endif // RMV_VIEWS_SETTINGS_KEYBOARD_SHORTCUTS_PANE_H_ diff --git a/source/frontend/views/settings/keyboard_shortcuts_pane.ui b/source/frontend/views/settings/keyboard_shortcuts_pane.ui new file mode 100644 index 0000000..5f1a4be --- /dev/null +++ b/source/frontend/views/settings/keyboard_shortcuts_pane.ui @@ -0,0 +1,1088 @@ + + + KeyboardShortcutsPane + + + + 0 + 0 + 662 + 765 + + + + + + + true + + + + + 0 + 0 + 625 + 1178 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Global navigation + + + 0 + + + + + + + + 0 + + + + + + 0 + 0 + + + + Back + + + + + + + + 0 + 0 + + + + Alt + Left arrow or Backspace + + + + + + + + 0 + 0 + + + + Forward + + + + + + + + 0 + 0 + + + + Alt + Right arrow + + + + + + + + 0 + 0 + + + + Snapshot generation pane + + + + + + + + 0 + 0 + + + + Alt + F + + + + + + + + 0 + 0 + + + + Device configuration pane + + + + + + + + 0 + 0 + + + + Alt + G + + + + + + + + 0 + 0 + + + + Heap overview pane + + + + + + + + 0 + 0 + + + + Alt + Q + + + + + + + + 0 + 0 + + + + Resource overview pane + + + + + + + + 0 + 0 + + + + Alt + W + + + + + + + + 0 + 0 + + + + Allocation overview pane + + + + + + + + 0 + 0 + + + + Alt + E + + + + + + + + 0 + 0 + + + + Resource list pane + + + + + + + + 0 + 0 + + + + Alt + R + + + + + + + + 0 + 0 + + + + Allocation explorer pane + + + + + + + + 0 + 0 + + + + Alt + T + + + + + + + + 0 + 0 + + + + Resource details pane + + + + + + + + 0 + 0 + + + + Alt + Y + + + + + + + + 0 + 0 + + + + Snapshot delta pane + + + + + + + + 0 + 0 + + + + Alt + A + + + + + + + + 0 + 0 + + + + Memory leak finder pane + + + + + + + + 0 + 0 + + + + Alt + S + + + + + + + + 0 + 0 + + + + Welcome pane + + + + + + + + 0 + 0 + + + + Alt + Z + + + + + + + + 0 + 0 + + + + Recent traces pane + + + + + + + + 0 + 0 + + + + Alt + X + + + + + + + + 0 + 0 + + + + Keyboard shortcuts pane + + + + + + + + 0 + 0 + + + + Alt + C + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Timeline controls + + + + + + + + 0 + + + + + + 0 + 0 + + + + Scroll left + + + + + + + + 0 + 0 + + + + Left arrow + + + + + + + + 0 + 0 + + + + Scroll right + + + + + + + + 0 + 0 + + + + Right arrow + + + + + + + + 0 + 0 + + + + Scroll left (fast) + + + 0 + + + + + + + + 0 + 0 + + + + Ctrl + Left arrow + + + 0 + + + + + + + + 0 + 0 + + + + Scroll right (fast) + + + 0 + + + + + + + + 0 + 0 + + + + Ctrl + Right arrow + + + 0 + + + + + + + + 0 + 0 + + + + Zoom in + + + 0 + + + + + + + + 0 + 0 + + + + A + + + 0 + + + + + + + + 0 + 0 + + + + Zoom out + + + 0 + + + + + + + + 0 + 0 + + + + Z + + + 0 + + + + + + + + 0 + 0 + + + + Zoom to selection + + + 0 + + + + + + + + 0 + 0 + + + + Ctrl + Z + + + 0 + + + + + + + + 0 + 0 + + + + Zoom reset + + + 0 + + + + + + + + 0 + 0 + + + + H + + + 0 + + + + + + + + 0 + 0 + + + + Zoom in 10x + + + 0 + + + + + + + + 0 + 0 + + + + S + + + 0 + + + + + + + + 0 + 0 + + + + Zoom out 10x + + + + + + + + 0 + 0 + + + + X + + + + + + + + 0 + 0 + + + + Drag + + + + + + + + 0 + 0 + + + + Space + Left mouse button + + + + + + + + 0 + 0 + + + + Zoom in/out + + + + + + + + 0 + 0 + + + + Ctrl + Mousewheel + + + + + + + + 0 + 0 + + + + Rename a snapshot + + + + + + + + 0 + 0 + + + + F2 (select entry in table first) + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Global hotkeys + + + + + + + + 0 + + + + + + 0 + 0 + + + + Open trace + + + + + + + + 0 + 0 + + + + Ctrl + O + + + + + + + + 0 + 0 + + + + Close trace + + + + + + + + 0 + 0 + + + + Ctrl + F4 + + + + + + + + 0 + 0 + + + + Cycle time units + + + + + + + + 0 + 0 + + + + Ctrl + T + + + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+
+ + +
diff --git a/source/frontend/views/settings/settings_pane.cpp b/source/frontend/views/settings/settings_pane.cpp new file mode 100644 index 0000000..f216870 --- /dev/null +++ b/source/frontend/views/settings/settings_pane.cpp @@ -0,0 +1,147 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Settings pane. +//============================================================================= + +#include "views/settings/settings_pane.h" + +#include "qt_common/utils/scaling_manager.h" + +#include "models/message_manager.h" +#include "settings/rmv_settings.h" +#include "util/widget_util.h" +#include "views/custom_widgets/rmv_colored_checkbox.h" + +SettingsPane::SettingsPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::SettingsPane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + ui_->check_for_updates_on_startup_checkbox_->Initialize(RMVSettings::Get().GetCheckForUpdatesOnStartup(), rmv::kCheckboxEnableColor, Qt::black); + ui_->heap_uniqueness_checkbox_->Initialize(RMVSettings::Get().GetAllocUniqunessHeap(), rmv::kCheckboxEnableColor, Qt::black); + ui_->allocation_uniqueness_checkbox_->Initialize(RMVSettings::Get().GetAllocUniqunessAllocation(), rmv::kCheckboxEnableColor, Qt::black); + ui_->offset_uniqueness_checkbox_->Initialize(RMVSettings::Get().GetAllocUniqunessOffset(), rmv::kCheckboxEnableColor, Qt::black); + + // For now, hide the memory leak settings (they may be used in a future release) + ui_->memory_leak_title_->hide(); + ui_->heap_uniqueness_checkbox_->hide(); + ui_->allocation_uniqueness_checkbox_->hide(); + ui_->offset_uniqueness_checkbox_->hide(); + + // populate the time combo box + rmv::widget_util::InitSingleSelectComboBox(parent, ui_->units_combo_push_button_, rmv::text::kSettingsUnitsClocks, false); + ui_->units_combo_push_button_->ClearItems(); + ui_->units_combo_push_button_->AddItem(rmv::text::kSettingsUnitsClocks); + ui_->units_combo_push_button_->AddItem(rmv::text::kSettingsUnitsMilliseconds); + ui_->units_combo_push_button_->AddItem(rmv::text::kSettingsUnitsSeconds); + ui_->units_combo_push_button_->AddItem(rmv::text::kSettingsUnitsMinutes); + ui_->units_combo_push_button_->SetSelectedRow(0); + connect(ui_->units_combo_push_button_, &ArrowIconComboBox::SelectionChanged, this, &SettingsPane::TimeUnitsChanged); + + connect(ui_->check_for_updates_on_startup_checkbox_, &RMVColoredCheckbox::Clicked, this, &SettingsPane::CheckForUpdatesOnStartupStateChanged); + connect(ui_->heap_uniqueness_checkbox_, &RMVColoredCheckbox::Clicked, this, &SettingsPane::HeapUniquenessSelectionStateChanged); + connect(ui_->allocation_uniqueness_checkbox_, &RMVColoredCheckbox::Clicked, this, &SettingsPane::AllocationUniquenessSelectionStateChanged); + connect(ui_->offset_uniqueness_checkbox_, &RMVColoredCheckbox::Clicked, this, &SettingsPane::OffsetUniquenessSelectionStateChanged); +} + +SettingsPane::~SettingsPane() +{ +} + +void SettingsPane::showEvent(QShowEvent* event) +{ + Q_UNUSED(event); + + // Update the combo box push button text. + int units = RMVSettings::Get().GetUnits(); + UpdateTimeComboBox(units); + + QWidget::showEvent(event); +} + +void SettingsPane::SaveTimeUnits(int current_index) +{ + RMVSettings::Get().SetUnits((TimeUnitType)current_index); + RMVSettings::Get().SaveSettings(); +} + +void SettingsPane::UpdateTimeComboBox(int units) +{ + // Map back to UI row (skip the time units between TIME_UNIT_TYPE_MS and TIME_UNIT_TYPE_CLK). + if (units > 0) + { + units -= (kTimeUnitTypeMillisecond - kTimeUnitTypeClk - 1); + } + ui_->units_combo_push_button_->SetSelectedRow(units); +} + +void SettingsPane::TimeUnitsChanged() +{ + int index = ui_->units_combo_push_button_->CurrentRow(); + + // Map back from UI row in combo box (skip the time units between TIME_UNIT_TYPE_MS and TIME_UNIT_TYPE_CLK). + if (index > 0) + { + index += (kTimeUnitTypeMillisecond - kTimeUnitTypeClk - 1); + } + SaveTimeUnits(index); +} + +void SettingsPane::CycleTimeUnits() +{ + int units = RMVSettings::Get().GetUnits(); + switch (units) + { + case kTimeUnitTypeClk: + units = kTimeUnitTypeMillisecond; + break; + case kTimeUnitTypeMillisecond: + units = kTimeUnitTypeSecond; + break; + case kTimeUnitTypeSecond: + units = kTimeUnitTypeMinute; + break; + case kTimeUnitTypeMinute: + default: + units = kTimeUnitTypeClk; + break; + } + + UpdateTimeComboBox(units); + SaveTimeUnits(units); +} + +void SettingsPane::CheckForUpdatesOnStartupStateChanged() +{ + RMVSettings::Get().SetCheckForUpdatesOnStartup(ui_->check_for_updates_on_startup_checkbox_->isChecked()); + RMVSettings::Get().SaveSettings(); +} + +void SettingsPane::HeapUniquenessSelectionStateChanged() +{ + RMVSettings::Get().SetAllocUniqunessHeap(ui_->heap_uniqueness_checkbox_->isChecked()); + RMVSettings::Get().SaveSettings(); + + emit MessageManager::Get().UpdateHashes(); +} + +void SettingsPane::AllocationUniquenessSelectionStateChanged() +{ + RMVSettings::Get().SetAllocUniqunessAllocation(ui_->allocation_uniqueness_checkbox_->isChecked()); + RMVSettings::Get().SaveSettings(); + + emit MessageManager::Get().UpdateHashes(); +} + +void SettingsPane::OffsetUniquenessSelectionStateChanged() +{ + RMVSettings::Get().SetAllocUniqunessOffset(ui_->offset_uniqueness_checkbox_->isChecked()); + RMVSettings::Get().SaveSettings(); + + emit MessageManager::Get().UpdateHashes(); +} diff --git a/source/frontend/views/settings/settings_pane.h b/source/frontend/views/settings/settings_pane.h new file mode 100644 index 0000000..0bc3759 --- /dev/null +++ b/source/frontend/views/settings/settings_pane.h @@ -0,0 +1,69 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Settings pane. +//============================================================================= + +#ifndef RMV_VIEWS_SETTINGS_SETTINGS_PANE_H_ +#define RMV_VIEWS_SETTINGS_SETTINGS_PANE_H_ + +#include "ui_settings_pane.h" + +#include "views/base_pane.h" + +/// Class declaration. +class SettingsPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit SettingsPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~SettingsPane(); + + /// Overridden show event. Fired when this pane is opened. + /// \param event the show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Cycle through the available time units. + void CycleTimeUnits(); + +public slots: + /// Slot to handle what happens when the auto updates box changes. + /// Update and save the settings. + void CheckForUpdatesOnStartupStateChanged(); + + /// Slot to handle what happens when the time units combo box has changed. + void TimeUnitsChanged(); + + /// Slot to handle what happens when the heap alloc uniqueness box changes. + /// Update and save the settings. + void HeapUniquenessSelectionStateChanged(); + + /// Slot to handle what happens when the block alloc uniqueness box changes. + /// Update and save the settings. + void AllocationUniquenessSelectionStateChanged(); + + /// Slot to handle what happens when the offset alloc uniqueness box changes. + /// Update and save the settings. + void OffsetUniquenessSelectionStateChanged(); + +private: + /// Save the current time units setting. + /// \param current_index The new index of the combo box corresponding to the new + /// time units. + void SaveTimeUnits(int current_index); + + /// Update the time unit combo box. Take into account that the mapping of the + /// time value enums and the indices in the combo box is not linear. + /// \param units The time units + void UpdateTimeComboBox(int units); + + Ui::SettingsPane* ui_; ///< Pointer to the Qt UI design. +}; + +#endif // RMV_VIEWS_SETTINGS_SETTINGS_PANE_H_ diff --git a/source/frontend/views/settings/settings_pane.ui b/source/frontend/views/settings/settings_pane.ui new file mode 100644 index 0000000..ceb2cad --- /dev/null +++ b/source/frontend/views/settings/settings_pane.ui @@ -0,0 +1,442 @@ + + + SettingsPane + + + + 0 + 0 + 759 + 1402 + + + + + + + true + + + + + 0 + 0 + 735 + 1045 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Automatic updates + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Check for updates on startup. + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Time units + + + false + + + 0 + + + + + + + + 0 + 0 + + + + In timeline and tables, show units of time in: + + + 0 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 2 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Clocks + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Memory leak finder resources + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + When calculating resource uniqueness, include Heap ID. + + + + + + + Qt::Horizontal + + + + 534 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + When calculating resource uniqueness, include Allocation ID. + + + + + + + Qt::Horizontal + + + + 534 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + When calculating resource uniqueness, include Allocation Offset. + + + + + + + Qt::Horizontal + + + + 534 + 20 + + + + + + + + + + + + + + + + ArrowIconComboBox + QWidget +
qt_common/custom_widgets/arrow_icon_combo_box.h
+ 1 +
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + RMVColoredCheckbox + QCheckBox +
views/custom_widgets/rmv_colored_checkbox.h
+
+
+ + + + +
diff --git a/source/frontend/views/settings/themes_and_colors_pane.cpp b/source/frontend/views/settings/themes_and_colors_pane.cpp new file mode 100644 index 0000000..ff12680 --- /dev/null +++ b/source/frontend/views/settings/themes_and_colors_pane.cpp @@ -0,0 +1,343 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Colors and Themes pane. +//============================================================================= + +#include "views/settings/themes_and_colors_pane.h" + +#include "settings/rmv_settings.h" +#include "util/rmv_util.h" +#include "util/widget_util.h" +#include "util/string_util.h" +#include "views/debug_window.h" + +const static int kPickerRows = 4; +const static int kPickerColumns = 8; + +ThemesAndColorsPane::ThemesAndColorsPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::ThemesAndColorsPane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + // Set up buttons using RMVSettingID's as buttongroup id's. + button_group_.addButton(ui_->button_snapshots_viewed_, kSettingThemesAndColorsSnapshotViewed); + button_group_.addButton(ui_->button_snapshots_compared_, kSettingThemesAndColorsSnapshotCompared); + button_group_.addButton(ui_->button_snapshots_live_, kSettingThemesAndColorsSnapshotLive); + button_group_.addButton(ui_->button_snapshots_generated_, kSettingThemesAndColorsSnapshotGenerated); + button_group_.addButton(ui_->button_snapshots_vma_, kSettingThemesAndColorsSnapshotVma); + + button_group_.addButton(ui_->button_resource_depth_stencil_buffer_, kSettingThemesAndColorsResourceDsBuffer); + button_group_.addButton(ui_->button_resource_render_target_, kSettingThemesAndColorsResourceRenderTarget); + button_group_.addButton(ui_->button_resource_texture_, kSettingThemesAndColorsResourceTexture); + button_group_.addButton(ui_->button_resource_vertex_buffer_, kSettingThemesAndColorsResourceVertexBuffer); + button_group_.addButton(ui_->button_resource_index_buffer_, kSettingThemesAndColorsResourceIndexBuffer); + button_group_.addButton(ui_->button_resource_uav_, kSettingThemesAndColorsResourceUav); + button_group_.addButton(ui_->button_resource_shader_pipeline_, kSettingThemesAndColorsResourceShaderPipeline); + button_group_.addButton(ui_->button_resource_command_buffer_, kSettingThemesAndColorsResourceCommandBuffer); + button_group_.addButton(ui_->button_resource_heap_, kSettingThemesAndColorsResourceHeap); + button_group_.addButton(ui_->button_resource_descriptors_, kSettingThemesAndColorsResourceDescriptors); + button_group_.addButton(ui_->button_resource_buffer_, kSettingThemesAndColorsResourceBuffer); + button_group_.addButton(ui_->button_resource_gpu_event_, kSettingThemesAndColorsResourceGpuEvent); + button_group_.addButton(ui_->button_resource_free_space_, kSettingThemesAndColorsResourceFreeSpace); + button_group_.addButton(ui_->button_resource_internal_, kSettingThemesAndColorsResourceInternal); + + button_group_.addButton(ui_->button_delta_increase_, kSettingThemesAndColorsDeltaIncrease); + button_group_.addButton(ui_->button_delta_decrease_, kSettingThemesAndColorsDeltaDecrease); + button_group_.addButton(ui_->button_delta_no_change_, kSettingThemesAndColorsDeltaNoChange); + + button_group_.addButton(ui_->button_heap_local_, kSettingThemesAndColorsHeapLocal); + button_group_.addButton(ui_->button_heap_invisible_, kSettingThemesAndColorsHeapInvisible); + button_group_.addButton(ui_->button_heap_system_, kSettingThemesAndColorsHeapSystem); + button_group_.addButton(ui_->button_heap_unspecified_, kSettingThemesAndColorsHeapUnspecified); + + button_group_.addButton(ui_->button_cpu_mapped_, kSettingThemesAndColorsCpuMapped); + button_group_.addButton(ui_->button_not_cpu_mapped_, kSettingThemesAndColorsNotCpuMapped); + + button_group_.addButton(ui_->button_in_preferred_heap_, kSettingThemesAndColorsInPreferredHeap); + button_group_.addButton(ui_->button_not_in_preferred_heap_, kSettingThemesAndColorsNotInPreferredHeap); + + button_group_.addButton(ui_->button_aliased_, kSettingThemesAndColorsAliased); + button_group_.addButton(ui_->button_not_aliased_, kSettingThemesAndColorsNotAliased); + + button_group_.addButton(ui_->button_resource_history_resource_event_, kSettingThemesAndColorsResourceHistoryResourceEvent); + button_group_.addButton(ui_->button_resource_history_cpu_mapping_, kSettingThemesAndColorsResourceHistoryCpuMapUnmap); + button_group_.addButton(ui_->button_resource_history_residency_, kSettingThemesAndColorsResourceHistoryResidencyUpdate); + button_group_.addButton(ui_->button_resource_history_page_table_, kSettingThemesAndColorsResourceHistoryPageTableUpdate); + button_group_.addButton(ui_->button_resource_history_highlight_, kSettingThemesAndColorsResourceHistoryHighlight); + button_group_.addButton(ui_->button_resource_history_snapshot_, kSettingThemesAndColorsResourceHistorySnapshot); + + button_group_.addButton(ui_->button_commit_type_committed_, kSettingThemesAndColorsCommitTypeCommitted); + button_group_.addButton(ui_->button_commit_type_placed_, kSettingThemesAndColorsCommitTypePlaced); + button_group_.addButton(ui_->button_commit_type_virtual_, kSettingThemesAndColorsCommitTypeVirtual); + + // Slot/signal connection for various widgets + connect(ui_->color_widget_, &RMVColorPickerWidget::ColorSelected, this, &ThemesAndColorsPane::PickerColorSelected); + connect(&button_group_, SIGNAL(buttonClicked(int)), this, SLOT(ItemButtonClicked(int))); + connect(ui_->default_settings_button_, SIGNAL(clicked(bool)), this, SLOT(DefaultSettingsButtonClicked())); + connect(ui_->default_palette_button_, SIGNAL(clicked(bool)), this, SLOT(DefaultPaletteButtonClicked())); + connect(ui_->spin_box_color_red_, SIGNAL(valueChanged(int)), this, SLOT(RgbValuesChanged())); + connect(ui_->spin_box_color_green_, SIGNAL(valueChanged(int)), this, SLOT(RgbValuesChanged())); + connect(ui_->spin_box_color_blue_, SIGNAL(valueChanged(int)), this, SLOT(RgbValuesChanged())); + connect(ui_->spin_box_color_red_, SIGNAL(valueChanged(int)), ui_->slider_color_red_, SLOT(setValue(int))); + connect(ui_->spin_box_color_green_, SIGNAL(valueChanged(int)), ui_->slider_color_green_, SLOT(setValue(int))); + connect(ui_->spin_box_color_blue_, SIGNAL(valueChanged(int)), ui_->slider_color_blue_, SLOT(setValue(int))); + connect(ui_->slider_color_red_, SIGNAL(valueChanged(int)), ui_->spin_box_color_red_, SLOT(setValue(int))); + connect(ui_->slider_color_green_, SIGNAL(valueChanged(int)), ui_->spin_box_color_green_, SLOT(setValue(int))); + connect(ui_->slider_color_blue_, SIGNAL(valueChanged(int)), ui_->spin_box_color_blue_, SLOT(setValue(int))); + + // Set up color picker. + ui_->color_widget_->SetRowAndColumnCount(kPickerRows, kPickerColumns); + ui_->color_widget_->SetPalette(RMVSettings::Get().GetColorPalette()); + + // Initial checked item. + ui_->button_snapshots_viewed_->setChecked(true); + + // Add margins around the color picker label. + ui_->selected_color_label_->setContentsMargins(10, 5, 10, 5); + + // Initial refresh. + Refresh(); + + // Safety measure to guarantee settings values are in range (should prevent crashing + // from an invalid settings file). + for (QAbstractButton* button : button_group_.buttons()) + { + int button_id = button_group_.id(button); + int palette_id = GetSettingsPaletteId(button_id); + + if (palette_id < 0 || palette_id >= (kPickerRows * kPickerColumns)) + { + SetSettingsPaletteId(button_id, 0); + } + else + { + // Invalid settings strings which still produce an integer value in range. + // Should be overwritten with that integer value + SetSettingsPaletteId(button_id, palette_id); + } + } + + // Set the cursor to pointing hand cursor for the sliders. + ui_->slider_color_red_->setCursor(Qt::PointingHandCursor); + ui_->slider_color_green_->setCursor(Qt::PointingHandCursor); + ui_->slider_color_blue_->setCursor(Qt::PointingHandCursor); +} + +ThemesAndColorsPane::~ThemesAndColorsPane() +{ +} + +void ThemesAndColorsPane::PickerColorSelected(int palette_id, const QColor& color) +{ + Q_UNUSED(color) + + int button_id = button_group_.checkedId(); + + // Set palette id in RMV settings. + SetSettingsPaletteId(button_id, palette_id); + + Refresh(); +} + +void ThemesAndColorsPane::ItemButtonClicked(int button_id) +{ + Q_UNUSED(button_id) + + Refresh(); +} + +void ThemesAndColorsPane::DefaultSettingsButtonClicked() +{ + // Restore default palette ids. + RMVSettings::Get().RestoreDefaultColors(); + + Refresh(); +} + +void ThemesAndColorsPane::DefaultPaletteButtonClicked() +{ + // Restore default palette settings. + RMVSettings::Get().RestoreDefaultPalette(); + + Refresh(); +} + +void ThemesAndColorsPane::RgbValuesChanged() +{ + ColorPalette palette = ui_->color_widget_->GetPalette(); + int palette_id = ui_->color_widget_->GetSelectedPaletteId(); + + // Get color from spinbox values. + QColor color(ui_->spin_box_color_red_->value(), ui_->spin_box_color_green_->value(), ui_->spin_box_color_blue_->value()); + + // Set the new color in the palette. + palette.SetColor(palette_id, color); + RMVSettings::Get().SetColorPalette(palette); + + Refresh(); +} + +void ThemesAndColorsPane::Refresh() +{ + QColor color; + + // Set button color values from corresponding RMV settings. + for (QAbstractButton* button : button_group_.buttons()) + { + int button_id = button_group_.id(button); + + if (button->isChecked()) + { + // Select the picker color that matches this buttons color (default to first color). + int palette_id = GetSettingsPaletteId(button_id); + ui_->color_widget_->Select(palette_id); + } + + // Get colors. + color = GetSettingsColor(button_id); + + static_cast(button)->SetColor(color); + } + + // Set color picker palette. + ui_->color_widget_->SetPalette(RMVSettings::Get().GetColorPalette()); + + // Set RGB spinbox/slider values. + color = ui_->color_widget_->GetSelectedColor(); + ui_->spin_box_color_red_->setValue(color.red()); + ui_->spin_box_color_green_->setValue(color.green()); + ui_->spin_box_color_blue_->setValue(color.blue()); + + // Set selected color hex label. + QString color_string = rmv::string_util::ToUpperCase(QString("#") + QString::number(color.rgb(), 16)); + QString font_color_string = QString("#") + QString::number(rmv_util::GetTextColorForBackground(color_string).rgb(), 16); + ui_->selected_color_label_->setText(""); + ui_->selected_color_label_->setStyleSheet(QString("background-color:%1;color:%2;").arg(color_string).arg(font_color_string)); + + // Indicate the colors may have changed. + emit RefreshedColors(); +} + +QColor ThemesAndColorsPane::GetSettingsColor(int button_id) +{ + return RMVSettings::Get().GetColorPalette().GetColor(GetSettingsPaletteId(button_id)); +} + +void ThemesAndColorsPane::SetSettingsPaletteId(int button_id, int palette_id) +{ + switch (button_id) + { + case kSettingThemesAndColorsSnapshotViewed: + case kSettingThemesAndColorsSnapshotCompared: + case kSettingThemesAndColorsSnapshotLive: + case kSettingThemesAndColorsSnapshotGenerated: + case kSettingThemesAndColorsSnapshotVma: + case kSettingThemesAndColorsResourceDsBuffer: + case kSettingThemesAndColorsResourceRenderTarget: + case kSettingThemesAndColorsResourceTexture: + case kSettingThemesAndColorsResourceVertexBuffer: + case kSettingThemesAndColorsResourceIndexBuffer: + case kSettingThemesAndColorsResourceUav: + case kSettingThemesAndColorsResourceShaderPipeline: + case kSettingThemesAndColorsResourceCommandBuffer: + case kSettingThemesAndColorsResourceHeap: + case kSettingThemesAndColorsResourceDescriptors: + case kSettingThemesAndColorsResourceBuffer: + case kSettingThemesAndColorsResourceGpuEvent: + case kSettingThemesAndColorsResourceFreeSpace: + case kSettingThemesAndColorsResourceInternal: + case kSettingThemesAndColorsDeltaIncrease: + case kSettingThemesAndColorsDeltaDecrease: + case kSettingThemesAndColorsDeltaNoChange: + case kSettingThemesAndColorsHeapLocal: + case kSettingThemesAndColorsHeapInvisible: + case kSettingThemesAndColorsHeapSystem: + case kSettingThemesAndColorsHeapUnspecified: + case kSettingThemesAndColorsCpuMapped: + case kSettingThemesAndColorsNotCpuMapped: + case kSettingThemesAndColorsInPreferredHeap: + case kSettingThemesAndColorsNotInPreferredHeap: + case kSettingThemesAndColorsAliased: + case kSettingThemesAndColorsNotAliased: + case kSettingThemesAndColorsResourceHistoryResourceEvent: + case kSettingThemesAndColorsResourceHistoryCpuMapUnmap: + case kSettingThemesAndColorsResourceHistoryResidencyUpdate: + case kSettingThemesAndColorsResourceHistoryPageTableUpdate: + case kSettingThemesAndColorsResourceHistoryHighlight: + case kSettingThemesAndColorsResourceHistorySnapshot: + case kSettingThemesAndColorsCommitTypeCommitted: + case kSettingThemesAndColorsCommitTypePlaced: + case kSettingThemesAndColorsCommitTypeVirtual: + RMVSettings::Get().SetPaletteId(static_cast(button_id), palette_id); + break; + + default: + DebugWindow::DbgMsg("Warning: Hit unused default switch case."); + break; + } +} + +int ThemesAndColorsPane::GetSettingsPaletteId(int button_id) +{ + switch (button_id) + { + case kSettingThemesAndColorsSnapshotViewed: + case kSettingThemesAndColorsSnapshotCompared: + case kSettingThemesAndColorsSnapshotLive: + case kSettingThemesAndColorsSnapshotGenerated: + case kSettingThemesAndColorsSnapshotVma: + + case kSettingThemesAndColorsResourceDsBuffer: + case kSettingThemesAndColorsResourceRenderTarget: + case kSettingThemesAndColorsResourceTexture: + case kSettingThemesAndColorsResourceVertexBuffer: + case kSettingThemesAndColorsResourceIndexBuffer: + case kSettingThemesAndColorsResourceUav: + case kSettingThemesAndColorsResourceShaderPipeline: + case kSettingThemesAndColorsResourceCommandBuffer: + case kSettingThemesAndColorsResourceHeap: + case kSettingThemesAndColorsResourceDescriptors: + case kSettingThemesAndColorsResourceBuffer: + case kSettingThemesAndColorsResourceGpuEvent: + case kSettingThemesAndColorsResourceFreeSpace: + case kSettingThemesAndColorsResourceInternal: + + case kSettingThemesAndColorsDeltaIncrease: + case kSettingThemesAndColorsDeltaDecrease: + case kSettingThemesAndColorsDeltaNoChange: + + case kSettingThemesAndColorsHeapLocal: + case kSettingThemesAndColorsHeapInvisible: + case kSettingThemesAndColorsHeapSystem: + case kSettingThemesAndColorsHeapUnspecified: + + case kSettingThemesAndColorsCpuMapped: + case kSettingThemesAndColorsNotCpuMapped: + + case kSettingThemesAndColorsInPreferredHeap: + case kSettingThemesAndColorsNotInPreferredHeap: + + case kSettingThemesAndColorsAliased: + case kSettingThemesAndColorsNotAliased: + + case kSettingThemesAndColorsResourceHistoryResourceEvent: + case kSettingThemesAndColorsResourceHistoryCpuMapUnmap: + case kSettingThemesAndColorsResourceHistoryResidencyUpdate: + case kSettingThemesAndColorsResourceHistoryPageTableUpdate: + case kSettingThemesAndColorsResourceHistoryHighlight: + case kSettingThemesAndColorsResourceHistorySnapshot: + + case kSettingThemesAndColorsCommitTypeCommitted: + case kSettingThemesAndColorsCommitTypePlaced: + case kSettingThemesAndColorsCommitTypeVirtual: + return RMVSettings::Get().GetPaletteId(static_cast(button_id)); + + default: + return -1; + } +} diff --git a/source/frontend/views/settings/themes_and_colors_pane.h b/source/frontend/views/settings/themes_and_colors_pane.h new file mode 100644 index 0000000..3075fe4 --- /dev/null +++ b/source/frontend/views/settings/themes_and_colors_pane.h @@ -0,0 +1,87 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Colors and Themes pane. +//============================================================================= + +#ifndef RMV_VIEWS_SETTINGS_THEMES_AND_COLORS_PANE_H_ +#define RMV_VIEWS_SETTINGS_THEMES_AND_COLORS_PANE_H_ + +#include + +#include "ui_themes_and_colors_pane.h" + +#include "qt_common/custom_widgets/scaled_push_button.h" + +#include "views/base_pane.h" +#include "views/custom_widgets/themes_and_colors_item_button.h" + +/// Class declaration. +class ThemesAndColorsPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit ThemesAndColorsPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~ThemesAndColorsPane(); + +private: + /// Refresh - updates all pane elements so they reflect the settings currently + /// set in RMVSettings. + void Refresh(); + + /// Get the RMV settings color which corresponds to the button indicated by + /// button_id. + /// \param button_id The button id. + /// \return The color setting. + QColor GetSettingsColor(int button_id); + + /// Get the RMV settings palette id which corresponds to the button indicated by + /// button_id. + /// \param button_id The button id. + /// \return The palette id setting. + int GetSettingsPaletteId(int button_id); + + /// Set the RMV settings palette id which corresponds to the button indicated by + /// button_id. + /// \param button_id The button id. + /// \param palette_id The palette id to assign in RMV settings. + void SetSettingsPaletteId(int button_id, int palette_id); + +signals: + void RefreshedColors(); + +private slots: + /// Picker color selected slot - called when a color is selected on the picker + /// (also triggered by calling ColorPickerWidget::select()). + /// \param palette_id The palette if of the selected color. + /// \param color The QColor value of the selected color. + void PickerColorSelected(int palette_id, const QColor& theme_color); + + /// Item button clicked slot - called when one of the item buttons is clicked. + /// \param button_id The id of the selected button. + void ItemButtonClicked(int button_id); + + /// Default settings button clicked slot - called when the default settings + /// button is clicked. + void DefaultSettingsButtonClicked(); + + /// Default palette button clicked slot - called when the default palette + /// button is clicked. + void DefaultPaletteButtonClicked(); + + /// RGB value changed slot - called when the on screen RGB values are changed, + /// either by using the sliders or the spinboxes. + void RgbValuesChanged(); + +private: + Ui::ThemesAndColorsPane* ui_; ///< Pointer to the Qt UI design. + QButtonGroup button_group_; ///< Group for item buttons +}; + +#endif // RMV_VIEWS_SETTINGS_THEMES_AND_COLORS_PANE_H_ diff --git a/source/frontend/views/settings/themes_and_colors_pane.ui b/source/frontend/views/settings/themes_and_colors_pane.ui new file mode 100644 index 0000000..319b4ca --- /dev/null +++ b/source/frontend/views/settings/themes_and_colors_pane.ui @@ -0,0 +1,1766 @@ + + + ThemesAndColorsPane + + + + 0 + 0 + 1289 + 491 + + + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 1269 + 471 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 4 + + + 1 + + + + + + 0 + 0 + + + + + 75 + true + + + + In preferred heap + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + No + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Deltas + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + Event + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Aliased + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Resource event + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Yes + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Snapshot + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Free space + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Residency + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + No change + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + Command buffer + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Internal + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Generated + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + Shader pipeline + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + Vertex buffer + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Increase + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Cpu Mapping + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Texture + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Page table + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + Heap + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + false + PreferAntialias + + + + Snapshots + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Yes + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Live + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + Index buffer + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Yes + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + DS buffer + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Local + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Resources + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Invisible + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + VMA + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Decrease + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + UAV + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Resource history + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Compared + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + + + + CPU mapped + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + No + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Heaps + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Viewed + + + true + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Host + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Unspecified + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Highlight + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Render target + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + Buffer + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + + + + Descriptors + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + No + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Commit type + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Committed + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Placed + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + false + true + + + + PointingHandCursor + + + Virtual + + + true + + + + + + + + + + 0 + + + + + + 0 + 0 + + + + Restore default settings + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + + + + + + 0 + 0 + + + + + 400 + 200 + + + + + + + + 6 + + + + + 0 + + + 6 + + + + + + 0 + 0 + + + + 255 + + + Qt::Horizontal + + + false + + + false + + + + + + + + 0 + 0 + + + + + 50 + false + + + + 255 + + + + + + + + 0 + 0 + + + + + 50 + false + + + + 255 + + + + + + + + 0 + 0 + + + + + 50 + false + + + + R: + + + + + + + + 0 + 0 + + + + 255 + + + Qt::Horizontal + + + false + + + false + + + + + + + + 0 + 0 + + + + + 50 + false + + + + B: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + G: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + 255 + + + + + + + + 0 + 0 + + + + 255 + + + Qt::Horizontal + + + false + + + false + + + + + + + + + + + + 0 + 0 + + + + + 16 + 50 + false + + + + #DUMMY + + + Qt::AlignCenter + + + + + + + Restore default palette + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledPushButton + QPushButton +
qt_common/custom_widgets/scaled_push_button.h
+
+ + RMVColorPickerWidget + QWidget +
views/custom_widgets/rmv_color_picker_widget.h
+ 1 +
+ + ThemesAndColorsItemButton + QPushButton +
views/custom_widgets/themes_and_colors_item_button.h
+
+
+ + + + +
diff --git a/source/frontend/views/snapshot/allocation_explorer_pane.cpp b/source/frontend/views/snapshot/allocation_explorer_pane.cpp new file mode 100644 index 0000000..9753dde --- /dev/null +++ b/source/frontend/views/snapshot/allocation_explorer_pane.cpp @@ -0,0 +1,400 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Block Explorer pane. +//============================================================================= + +#include "views/snapshot/allocation_explorer_pane.h" + +#include +#include +#include + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" + +#include "qt_common/utils/scaling_manager.h" + +#include "models/message_manager.h" +#include "models/proxy_models/allocation_proxy_model.h" +#include "models/resource_item_model.h" +#include "models/snapshot/allocation_explorer_model.h" +#include "settings/rmv_settings.h" +#include "views/colorizer.h" +#include "views/pane_manager.h" + +// Enum for the number of allocation models needed. For this pane, there is only a single +// allocation graphic. +enum +{ + kAllocationModelIndex, + + kNumAllocationModels, +}; + +AllocationExplorerPane::AllocationExplorerPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::AllocationExplorerPane) +{ + ui_->setupUi(this); + + // fix up the ratios of the 3 splitter regions + ui_->splitter_->setStretchFactor(0, 4); + ui_->splitter_->setStretchFactor(1, 1); + ui_->splitter_->setStretchFactor(2, 3); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + model_ = new rmv::VirtualAllocationExplorerModel(kNumAllocationModels); + + // initialize allocation table + model_->InitializeAllocationTableModel(ui_->allocation_table_view_, 0, kVirtualAllocationColumnCount); + + // initialize resource table + model_->InitializeResourceTableModel(ui_->resource_table_view_, 0, kResourceColumnCount); + + rmv::widget_util::InitCommonFilteringComponents(ui_->resource_search_box_, ui_->resource_size_slider_); + rmv::widget_util::InitCommonFilteringComponents(ui_->allocation_search_box_, ui_->allocation_size_slider_); + ui_->aliased_resource_checkbox_->Initialize(false, rmv::kCheckboxEnableColor, Qt::black); + + colorizer_ = new Colorizer(); + + allocation_scene_ = new QGraphicsScene; + allocation_item_ = new RMVAllocationBar(model_->GetAllocationBarModel(), 0, kAllocationModelIndex, colorizer_); + allocation_scene_->addItem(allocation_item_); + ui_->memory_block_view_->setScene(allocation_scene_); + + // Set up a list of required coloring modes, in order. + // The list is terminated with COLOR_MODE_COUNT + static const Colorizer::ColorMode mode_list[] = { + Colorizer::kColorModeResourceUsageType, + Colorizer::kColorModePreferredHeap, + Colorizer::kColorModeAllocationAge, + Colorizer::kColorModeResourceCreateAge, + Colorizer::kColorModeResourceBindAge, + Colorizer::kColorModeResourceGUID, + Colorizer::kColorModeResourceCPUMapped, + Colorizer::kColorModeNotAllPreferred, + Colorizer::kColorModeAliasing, + Colorizer::kColorModeCommitType, + Colorizer::kColorModeCount, + }; + + // Initialize the "color by" UI elements. Set up the combo box, legends and signals etc + colorizer_->Initialize(parent, ui_->color_combo_box_, ui_->legends_view_, mode_list); + + connect(ui_->resource_size_slider_, &DoubleSliderWidget::SpanChanged, this, &AllocationExplorerPane::ResourceSizeFilterChanged); + connect(ui_->resource_search_box_, &QLineEdit::textChanged, this, &AllocationExplorerPane::ResourceSearchBoxChanged); + connect(ui_->allocation_size_slider_, &DoubleSliderWidget::SpanChanged, this, &AllocationExplorerPane::AllocationSizeFilterChanged); + connect(ui_->allocation_search_box_, &QLineEdit::textChanged, this, &AllocationExplorerPane::AllocationSearchBoxChanged); + connect(ui_->color_combo_box_, &ArrowIconComboBox::SelectionChanged, this, &AllocationExplorerPane::ColorModeChanged); + connect(ui_->resource_table_view_, &QTableView::doubleClicked, this, &AllocationExplorerPane::ResourceTableDoubleClicked); + connect(ui_->aliased_resource_checkbox_, &RMVColoredCheckbox::Clicked, this, &AllocationExplorerPane::AliasedResourceClicked); + + connect(ui_->allocation_table_view_->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AllocationExplorerPane::AllocationTableChanged); + connect(ui_->resource_table_view_->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AllocationExplorerPane::ResourceTableSelectionChanged); + + // Resize the memory block if the splitter is moved + connect(ui_->splitter_, &QSplitter::splitterMoved, this, [=]() { ResizeItems(); }); + + // intercept the AllocationSelected signal so the chosen resource can be set up. This signal is sent + // before the pane navigation + connect(&MessageManager::Get(), &MessageManager::ResourceSelected, this, &AllocationExplorerPane::SelectResource); + connect(&MessageManager::Get(), &MessageManager::UnboundResourceSelected, this, &AllocationExplorerPane::SelectUnboundResource); + + // set up a connection between the tables being sorted and making sure the selected event is visible + connect(model_->GetAllocationProxyModel(), &rmv::ResourceProxyModel::layoutChanged, this, &AllocationExplorerPane::ScrollToSelectedAllocation); + connect(model_->GetResourceProxyModel(), &rmv::ResourceProxyModel::layoutChanged, this, &AllocationExplorerPane::ScrollToSelectedResource); + + connect(allocation_item_, &RMVAllocationBar::ResourceSelected, this, &AllocationExplorerPane::SelectedResource); + + ui_->resource_table_valid_switch_->setCurrentIndex(0); +} + +AllocationExplorerPane::~AllocationExplorerPane() +{ + delete colorizer_; + delete allocation_scene_; +} + +void AllocationExplorerPane::showEvent(QShowEvent* event) +{ + ResizeItems(); + + SetMaximumAllocationTableHeight(); + + QWidget::showEvent(event); +} + +void AllocationExplorerPane::Refresh() +{ + // Prior to doing a table update, disable sorting since Qt is super slow about it + ui_->resource_table_view_->setSortingEnabled(false); + + model_->BuildResourceSizeThresholds(); + int32_t resource_count = model_->UpdateResourceTable(); + model_->ResourceSizeFilterChanged(ui_->resource_size_slider_->minimum(), ui_->resource_size_slider_->maximum()); + + ui_->resource_table_view_->setSortingEnabled(true); + ui_->resource_table_view_->sortByColumn(kResourceColumnMappedInvisible, Qt::DescendingOrder); + ui_->resource_table_view_->horizontalHeader()->adjustSize(); + + ResizeItems(); + + // if there are no resources to show, don't display the resources table + if (resource_count == 0) + { + ui_->resource_table_valid_switch_->setCurrentIndex(0); + } + else + { + ui_->resource_table_valid_switch_->setCurrentIndex(1); + } + + SetMaximumResourceTableHeight(); +} + +void AllocationExplorerPane::Reset() +{ + ui_->color_combo_box_->SetSelectedRow(0); + colorizer_->ApplyColorMode(); + ui_->aliased_resource_checkbox_->setChecked(false); + AliasedResourceClicked(); + + model_->ResetModelValues(); + + ui_->allocation_size_slider_->SetLowerValue(0); + ui_->allocation_size_slider_->SetUpperValue(rmv::kSizeSliderRange); + ui_->allocation_search_box_->setText(""); + + ui_->resource_size_slider_->SetLowerValue(0); + ui_->resource_size_slider_->SetUpperValue(rmv::kSizeSliderRange); + ui_->resource_search_box_->setText(""); + + SelectResource(0); +} + +void AllocationExplorerPane::ChangeColoring() +{ + colorizer_->UpdateLegends(); + ui_->memory_block_view_->viewport()->update(); +} + +void AllocationExplorerPane::ColorModeChanged() +{ + ChangeColoring(); +} + +void AllocationExplorerPane::OpenSnapshot(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot != nullptr); + if (snapshot != nullptr) + { + if (model_->OpenSnapshot(snapshot) == false) + { + Reset(); + return; + } + + SelectResource(0); + + // Build the allocation table and set sorting to the allocation size + ui_->allocation_table_view_->setSortingEnabled(false); + model_->UpdateAllocationTable(); + ui_->allocation_table_view_->setSortingEnabled(true); + ui_->allocation_table_view_->sortByColumn(kVirtualAllocationColumnAllocationSize, Qt::DescendingOrder); + + // and select it + ui_->allocation_table_view_->selectRow(0); + + Refresh(); + } +} + +void AllocationExplorerPane::resizeEvent(QResizeEvent* event) +{ + ResizeItems(); + QWidget::resizeEvent(event); +} + +void AllocationExplorerPane::ResizeItems() +{ + qreal width = ui_->memory_block_view_->width(); + qreal height = ui_->memory_block_view_->height(); + const QRectF scene_rect = QRectF(0, 0, width, height); + + allocation_scene_->setSceneRect(scene_rect); + allocation_item_->UpdateDimensions(width, height); +} + +void AllocationExplorerPane::AllocationSearchBoxChanged() +{ + model_->AllocationSearchBoxChanged(ui_->allocation_search_box_->text()); + SetMaximumAllocationTableHeight(); +} + +void AllocationExplorerPane::AllocationSizeFilterChanged(int min_value, int max_value) +{ + model_->AllocationSizeFilterChanged(min_value, max_value); + SetMaximumAllocationTableHeight(); +} + +void AllocationExplorerPane::ResourceSearchBoxChanged() +{ + model_->ResourceSearchBoxChanged(ui_->resource_search_box_->text()); + SetMaximumResourceTableHeight(); +} + +void AllocationExplorerPane::ResourceSizeFilterChanged(int min_value, int max_value) +{ + model_->ResourceSizeFilterChanged(min_value, max_value); + SetMaximumResourceTableHeight(); +} + +void AllocationExplorerPane::AliasedResourceClicked() const +{ + model_->GetAllocationBarModel()->ShowAliased(ui_->aliased_resource_checkbox_->isChecked()); + ui_->memory_block_view_->viewport()->update(); +} + +void AllocationExplorerPane::SelectedResource(RmtResourceIdentifier resource_identifier, bool navigate_to_pane) +{ + // Broadcast the resource selection to any listening panes. + emit MessageManager::Get().ResourceSelected(resource_identifier); + + if (navigate_to_pane == true) + { + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotResourceDetails); + } +} + +void AllocationExplorerPane::SelectResource(RmtResourceIdentifier resource_identifier) +{ + const RmtVirtualAllocation* allocation = model_->GetAllocationBarModel()->GetAllocationFromResourceID(resource_identifier, kAllocationModelIndex); + if (allocation != nullptr) + { + // Find the allocation in the allocation table and select it if found. + const QModelIndex& allocation_index = + model_->GetAllocationProxyModel()->FindModelIndex(reinterpret_cast(allocation), kVirtualAllocationColumnId); + if (allocation_index.isValid() == true) + { + ui_->allocation_table_view_->selectRow(allocation_index.row()); + } + + bool aliased = model_->GetAllocationBarModel()->SetSelectedResourceForAllocation(allocation, -1, kAllocationModelIndex); + ui_->aliased_resource_checkbox_->setEnabled(aliased); + + // Select the resource in the resource table. + const QModelIndex& resource_index = model_->GetResourceProxyModel()->FindModelIndex(resource_identifier, kResourceColumnGlobalId); + if (resource_index.isValid() == true) + { + ui_->resource_table_view_->selectRow(resource_index.row()); + } + } +} + +void AllocationExplorerPane::SelectUnboundResource(const RmtVirtualAllocation* virtual_allocation) +{ + if (virtual_allocation == nullptr) + { + return; + } + + model_->GetAllocationBarModel()->SetSelectedResourceForAllocation(virtual_allocation, -1, kAllocationModelIndex); + + Refresh(); + + // find the allocation in the allocation table and select it if found + QModelIndex allocation_index = + model_->GetAllocationProxyModel()->FindModelIndex(reinterpret_cast(virtual_allocation), kVirtualAllocationColumnId); + if (allocation_index.isValid() == true) + { + ui_->allocation_table_view_->selectRow(allocation_index.row()); + } +} + +void AllocationExplorerPane::ResourceTableSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + Q_UNUSED(deselected); + + // Figure out the model index of the currently selected event + QModelIndexList modelIndexList = selected.indexes(); + if (modelIndexList.size() != 0) + { + const QModelIndex index = modelIndexList.at(0); + + if (index.isValid()) + { + const QModelIndex source_index = model_->GetResourceProxyModel()->mapToSource(index); + model_->GetAllocationBarModel()->SelectResource(0, 0, source_index.row()); + allocation_item_->update(); + const RmtResourceIdentifier resource_identifier = model_->GetResourceProxyModel()->GetData(index.row(), kResourceColumnGlobalId); + emit MessageManager::Get().ResourceSelected(resource_identifier); + } + } +} + +void AllocationExplorerPane::AllocationTableChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + Q_UNUSED(deselected); + + // Figure out the model index of the currently selected event + QModelIndexList modelIndexList = selected.indexes(); + if (modelIndexList.size() != 0) + { + const QModelIndex index = modelIndexList.at(0); + + if (index.isValid()) + { + const RmtVirtualAllocation* allocation = + reinterpret_cast(model_->GetAllocationProxyModel()->GetData(index.row(), kVirtualAllocationColumnId)); + bool aliased = model_->GetAllocationBarModel()->SetSelectedResourceForAllocation(allocation, -1, kAllocationModelIndex); + ui_->aliased_resource_checkbox_->setEnabled(aliased); + + Refresh(); + } + } +} + +void AllocationExplorerPane::ResourceTableDoubleClicked(const QModelIndex& index) const +{ + if (index.isValid() == true) + { + const RmtResourceIdentifier resource_identifier = model_->GetResourceProxyModel()->GetData(index.row(), kResourceColumnGlobalId); + emit MessageManager::Get().ResourceSelected(resource_identifier); + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotResourceDetails); + } +} + +void AllocationExplorerPane::ScrollToSelectedAllocation() +{ + QItemSelectionModel* selected_item = ui_->allocation_table_view_->selectionModel(); + if (selected_item->hasSelection()) + { + QModelIndexList item_list = selected_item->selectedRows(); + if (item_list.size() > 0) + { + // get the model index of the name column since column 0 (compare ID) is hidden and scrollTo + // doesn't appear to scroll on hidden columns + QModelIndex model_index = model_->GetAllocationProxyModel()->index(item_list[0].row(), kResourceColumnName); + ui_->allocation_table_view_->scrollTo(model_index, QAbstractItemView::ScrollHint::PositionAtTop); + } + } +} + +void AllocationExplorerPane::ScrollToSelectedResource() +{ + QItemSelectionModel* selected_item = ui_->resource_table_view_->selectionModel(); + if (selected_item->hasSelection()) + { + QModelIndexList item_list = selected_item->selectedRows(); + if (item_list.size() > 0) + { + // get the model index of the name column since column 0 (compare ID) is hidden and scrollTo + // doesn't appear to scroll on hidden columns + QModelIndex model_index = model_->GetResourceProxyModel()->index(item_list[0].row(), kResourceColumnName); + ui_->resource_table_view_->scrollTo(model_index, QAbstractItemView::ScrollHint::PositionAtTop); + } + } +} diff --git a/source/frontend/views/snapshot/allocation_explorer_pane.h b/source/frontend/views/snapshot/allocation_explorer_pane.h new file mode 100644 index 0000000..0875851 --- /dev/null +++ b/source/frontend/views/snapshot/allocation_explorer_pane.h @@ -0,0 +1,147 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Block Explorer pane. +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_ALLOCATION_EXPLORER_PANE_H_ +#define RMV_VIEWS_SNAPSHOT_ALLOCATION_EXPLORER_PANE_H_ + +#include "ui_allocation_explorer_pane.h" + +#include +#include + +#include "ui_allocation_explorer_pane.h" + +#include "rmt_resource_list.h" + +#include "models/allocation_bar_model.h" +#include "models/snapshot/allocation_explorer_model.h" +#include "util/widget_util.h" +#include "views/base_pane.h" +#include "views/colorizer.h" +#include "views/custom_widgets/rmv_allocation_bar.h" + +/// Class declaration +class AllocationExplorerPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit AllocationExplorerPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~AllocationExplorerPane(); + + /// Overridden show event. Fired when this pane is opened. + /// \param event the show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + + /// Open a snapshot. + /// \param snapshot The snapshot to open. + virtual void OpenSnapshot(RmtDataSnapshot* snapshot) Q_DECL_OVERRIDE; + + /// Resize relevant items. + void ResizeItems(); + +private slots: + /// Something changed in the allocation table, so need to update the UI. Same functionality as clicking on a table entry. + /// \param selected The newly selected table item. + /// \param deselected The previously selected table item. + void AllocationTableChanged(const QItemSelection& selected, const QItemSelection& deselected); + + /// Handle what happens when a selection in the resource table changes, so need to update the UI. Same functionality as clicking on a table entry. + /// \param selected The newly selected table item. + /// \param deselected The previously selected table item. + void ResourceTableSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + + /// Select an entry and go to the resource details pane. + /// \param index the model index. + void ResourceTableDoubleClicked(const QModelIndex& index) const; + + /// Handle what happens when the 'show aliasing' checkbox is clicked on. + void AliasedResourceClicked() const; + + /// Select a resource on this pane. This is usually called when selecting a resource + /// on a different pane to make sure the resource selection is propagated to all + /// interested panes. + /// \param resource_identifier the resource identifier of the resource to select. + void SelectResource(RmtResourceIdentifier resource_identifier); + + /// Slot to handle what happens when a resource has been selected. This can be used to broadcast the resource + /// selection to any panes listening for the signal so they can also update their selected resource. + /// \param resource_identifier the resource Identifier. + /// \param navigate_to_pane If true, navigate to the allocation explorer pane. + void SelectedResource(RmtResourceIdentifier resource_identifier, bool navigate_to_pane); + + /// Handle what happens when the allocation search filter changes. + void AllocationSearchBoxChanged(); + + /// Handle what happens when the allocation 'filter by size' slider changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void AllocationSizeFilterChanged(int min_value, int max_value); + + /// Handle what happens when the resource search filter changes. + void ResourceSearchBoxChanged(); + + /// Slot to handle what happens when the resource 'filter by size' slider changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void ResourceSizeFilterChanged(int min_value, int max_value); + + /// Handle what happens when the color mode changes. + void ColorModeChanged(); + + /// Slot to handle what happens when an unbound resource is selected. + /// \param virtual_allocation The allocation of the unbound resource that was selected. + void SelectUnboundResource(const RmtVirtualAllocation* virtual_allocation); + + /// Slot to handle what happens after the allocation table is sorted. + /// Make sure the selected item (if there is one) is visible. + void ScrollToSelectedAllocation(); + + /// Slot to handle what happens after the resource list table is sorted. + /// Make sure the selected item (if there is one) is visible. + void ScrollToSelectedResource(); + + /// Helper function to set the maximum height of the allocation table so it only contains rows with valid data. + inline void SetMaximumAllocationTableHeight() + { + ui_->allocation_table_view_->setMaximumHeight( + rmv::widget_util::GetTableHeight(ui_->allocation_table_view_, model_->GetAllocationProxyModel()->rowCount())); + } + + /// Helper function to set the maximum height of the resource table so it only contains rows with valid data. + inline void SetMaximumResourceTableHeight() + { + ui_->resource_table_view_->setMaximumHeight(rmv::widget_util::GetTableHeight(ui_->resource_table_view_, model_->GetResourceProxyModel()->rowCount())); + } + +private: + /// Refresh a selected view. + void Refresh(); + + Ui::AllocationExplorerPane* ui_; ///< Pointer to the Qt UI design. + + rmv::VirtualAllocationExplorerModel* model_; ///< container class for the widget models. + QGraphicsScene* allocation_scene_; ///< The scene containing the allocation widget. + RMVAllocationBar* allocation_item_; ///< The graphic item associated with the displayed allocation. + Colorizer* colorizer_; ///< The colorizer used by the 'color by' combo box. +}; + +#endif // RMV_VIEWS_SNAPSHOT_ALLOCATION_EXPLORER_PANE_H_ diff --git a/source/frontend/views/snapshot/allocation_explorer_pane.ui b/source/frontend/views/snapshot/allocation_explorer_pane.ui new file mode 100644 index 0000000..192b56a --- /dev/null +++ b/source/frontend/views/snapshot/allocation_explorer_pane.ui @@ -0,0 +1,523 @@ + + + AllocationExplorerPane + + + + 0 + 0 + 1156 + 816 + + + + + + + true + + + + + 0 + 0 + 1119 + 841 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 8 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + + 282 + 20 + + + + + + + + + 0 + 0 + + + + Filter by size: + + + + + + + Qt::Horizontal + + + + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 40 + 5 + + + + QSizePolicy::MinimumExpanding + + + + + + + + + 0 + 0 + + + + + 0 + + + 8 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + 0 + 0 + + + + Show aliasing + + + + + + + Qt::Horizontal + + + + 783 + 20 + + + + + + + + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 8 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + :/Resources/assets/stop_128x128.png + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 12 + + + + The selected allocation does not contain any resources. + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + + 282 + 20 + + + + + + + + + 0 + 0 + + + + Filter by size: + + + + + + + Qt::Horizontal + + + + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 40 + 5 + + + + QSizePolicy::MinimumExpanding + + + + + + + + + + + + + + + + + + + + ArrowIconComboBox + QWidget +
qt_common/custom_widgets/arrow_icon_combo_box.h
+ 1 +
+ + ColoredLegendGraphicsView + QGraphicsView +
qt_common/custom_widgets/colored_legend_graphics_view.h
+
+ + DoubleSliderWidget + QSlider +
qt_common/custom_widgets/double_slider_widget.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledTableView + QTableView +
qt_common/custom_widgets/scaled_table_view.h
+
+ + TextSearchWidget + QLineEdit +
qt_common/custom_widgets/text_search_widget.h
+
+ + RMVColoredCheckbox + QCheckBox +
views/custom_widgets/rmv_colored_checkbox.h
+
+ + RMVClickableTableView + ScaledTableView +
views/custom_widgets/rmv_clickable_table_view.h
+
+
+ + + + +
diff --git a/source/frontend/views/snapshot/allocation_overview_pane.cpp b/source/frontend/views/snapshot/allocation_overview_pane.cpp new file mode 100644 index 0000000..cd3eefe --- /dev/null +++ b/source/frontend/views/snapshot/allocation_overview_pane.cpp @@ -0,0 +1,462 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the allocation overview pane. +//============================================================================= + +#include "views/snapshot/allocation_overview_pane.h" + +#include +#include +#include +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" +#include "rmt_util.h" +#include "rmt_virtual_allocation_list.h" + +#include "models/heap_combo_box_model.h" +#include "models/message_manager.h" +#include "models/snapshot/allocation_overview_model.h" +#include "settings/rmv_settings.h" +#include "util/widget_util.h" +#include "views/pane_manager.h" +#include "views/colorizer.h" +#include "views/custom_widgets/rmv_allocation_bar.h" + +// The number of graphic objects in the scene to show allocations. It's inefficient to +// have one graphics object per allocation, particularly when there are thousands of +// allocations. Instead, the graphic objects are positioned in the currently visible +// area of the scene. Rather than each graphic object having a fixed allocation index, +// an offset is added to each allocation depending on where the visible region of the +// scene is. +static const int32_t kMaxAllocationObjects = 100; + +// Enum for the number of allocation models needed. For this pane, one model is needed for all +// allocations shown in the table. +enum +{ + kAllocationModelIndex, + + kNumAllocationModels, +}; + +// Map between sort type ID and its text representation +// These are the items that will be added to the combo box +static const std::map kSortTextMap = {{rmv::AllocationOverviewModel::kSortModeAllocationID, rmv::text::kSortByAllocationId}, + {rmv::AllocationOverviewModel::kSortModeAllocationSize, rmv::text::kSortByAllocationSize}, + {rmv::AllocationOverviewModel::kSortModeAllocationAge, rmv::text::kSortByAllocationAge}, + {rmv::AllocationOverviewModel::kSortModeResourceCount, rmv::text::kSortByResourceCount}, + {rmv::AllocationOverviewModel::kSortModeFragmentationScore, rmv::text::kSortByFragmentationScore}}; + +// Map between sort direction ID and its text representation +// These are the items that will be added to the combo box +static const std::map kDirectionTextMap = { + {rmv::AllocationOverviewModel::kSortDirectionAscending, rmv::text::kSortAscending}, + {rmv::AllocationOverviewModel::kSortDirectionDescending, rmv::text::kSortDescending}, +}; + +AllocationOverviewPane::AllocationOverviewPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::allocation_overview_pane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + model_ = new rmv::AllocationOverviewModel(kNumAllocationModels); + + ui_->search_box_->setFixedWidth(rmv::kSearchBoxWidth); + ui_->normalize_allocations_checkbox_->Initialize(false, rmv::kCheckboxEnableColor, Qt::black); + ToggleNormalizeAllocations(); + connect(ui_->normalize_allocations_checkbox_, &RMVColoredCheckbox::Clicked, this, &AllocationOverviewPane::ToggleNormalizeAllocations); + + ui_->aliased_resource_checkbox_->Initialize(false, rmv::kCheckboxEnableColor, Qt::black); + connect(ui_->aliased_resource_checkbox_, &RMVColoredCheckbox::Clicked, this, &AllocationOverviewPane::ToggleAliasedResources); + + rmv::widget_util::InitMultiSelectComboBox(this, ui_->preferred_heap_combo_box_, rmv::text::kPreferredHeap); + rmv::widget_util::InitSingleSelectComboBox(this, ui_->sort_combo_box_, rmv::text::kSortByAllocationId, false); + rmv::widget_util::InitSingleSelectComboBox(this, ui_->sort_direction_combo_box_, rmv::text::kSortAscending, false); + + connect(ui_->search_box_, &QLineEdit::textChanged, this, &AllocationOverviewPane::ApplyFilters); + connect(ui_->preferred_heap_combo_box_, &ArrowIconComboBox::SelectionChanged, this, &AllocationOverviewPane::ApplyFilters); + + preferred_heap_combo_box_model_ = new rmv::HeapComboBoxModel(); + preferred_heap_combo_box_model_->SetupHeapComboBox(ui_->preferred_heap_combo_box_); + connect(preferred_heap_combo_box_model_, &rmv::HeapComboBoxModel::FilterChanged, this, &AllocationOverviewPane::HeapChanged); + + // Add text strings to the sort combo box + ui_->sort_combo_box_->ClearItems(); + for (int loop = 0; loop < rmv::AllocationOverviewModel::kSortModeCount; loop++) + { + std::map::const_iterator it = kSortTextMap.find(loop); + if (it != kSortTextMap.end()) + { + ui_->sort_combo_box_->AddItem((*it).second); + } + else + { + RMT_ASSERT_MESSAGE(false, "Sort mode not found in list"); + ui_->sort_combo_box_->AddItem("Unknown"); + } + } + + // Add text strings to the sort direction combo box + ui_->sort_direction_combo_box_->ClearItems(); + for (int loop = 0; loop < rmv::AllocationOverviewModel::kSortDirectionCount; loop++) + { + std::map::const_iterator it = kDirectionTextMap.find(loop); + if (it != kDirectionTextMap.end()) + { + ui_->sort_direction_combo_box_->AddItem((*it).second); + } + else + { + RMT_ASSERT_MESSAGE(false, "Sort direction not found in list"); + ui_->sort_direction_combo_box_->AddItem("Unknown"); + } + } + + // set up scrollbar parameters for the memory map graphics view + ui_->allocation_list_view_->setMouseTracking(true); + ui_->allocation_list_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_->allocation_list_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui_->allocation_list_view_->horizontalScrollBar()->blockSignals(true); + allocation_list_scene_ = new QGraphicsScene(); + ui_->allocation_list_view_->setScene(allocation_list_scene_); + + colorizer_ = new Colorizer(); + + // Set up a list of required coloring modes, in order + // The list is terminated with COLOR_MODE_COUNT + static const Colorizer::ColorMode mode_list[] = { + Colorizer::kColorModeResourceUsageType, + Colorizer::kColorModePreferredHeap, + Colorizer::kColorModeAllocationAge, + Colorizer::kColorModeResourceCreateAge, + Colorizer::kColorModeResourceBindAge, + Colorizer::kColorModeResourceGUID, + Colorizer::kColorModeResourceCPUMapped, + Colorizer::kColorModeNotAllPreferred, + Colorizer::kColorModeAliasing, + Colorizer::kColorModeCommitType, + Colorizer::kColorModeCount, + }; + + // Initialize the "color by" UI elements. Set up the combo box, legends and signals etc + colorizer_->Initialize(parent, ui_->color_combo_box_, ui_->legends_view_, mode_list); + + // set up what happens when the user selects an item from the sort combo box + connect(ui_->sort_combo_box_, &ArrowIconComboBox::SelectionChanged, this, &AllocationOverviewPane::ApplySort); + connect(ui_->sort_direction_combo_box_, &ArrowIconComboBox::SelectionChanged, this, &AllocationOverviewPane::ApplySort); + connect(ui_->color_combo_box_, &ArrowIconComboBox::SelectionChanged, this, &AllocationOverviewPane::ColorModeChanged); + connect(&MessageManager::Get(), &MessageManager::ResourceSelected, this, &AllocationOverviewPane::SelectResource); + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &AllocationOverviewPane::OnScaleFactorChanged); + + connect(ui_->allocation_height_slider_, &QSlider::valueChanged, this, [=]() { AllocationHeightChanged(); }); + connect(ui_->allocation_list_view_->verticalScrollBar(), &QScrollBar::valueChanged, this, [=]() { ResizeItems(); }); + + ui_->allocation_height_slider_->setCursor(Qt::PointingHandCursor); + + allocation_height_ = ui_->allocation_height_slider_->value(); +} + +AllocationOverviewPane::~AllocationOverviewPane() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &AllocationOverviewPane::OnScaleFactorChanged); + + int32_t current_size = static_cast(allocation_graphic_objects_.size()); + for (int32_t i = 0; i < current_size; i++) + { + RMVAllocationBar* allocation_item = allocation_graphic_objects_[i]; + allocation_list_scene_->removeItem(allocation_item); + disconnect(allocation_item, &RMVAllocationBar::ResourceSelected, this, &AllocationOverviewPane::SelectedResource); + } + + delete model_; + delete preferred_heap_combo_box_model_; + delete colorizer_; +} + +void AllocationOverviewPane::OnTraceClose() +{ + model_->ResetModelValues(); + allocation_graphic_objects_.clear(); + allocation_list_scene_->clear(); + preferred_heap_combo_box_model_->ResetHeapComboBox(ui_->preferred_heap_combo_box_); +} + +void AllocationOverviewPane::Reset() +{ + ui_->color_combo_box_->SetSelectedRow(0); + colorizer_->ApplyColorMode(); + ui_->sort_combo_box_->SetSelectedRow(0); + ui_->sort_direction_combo_box_->SetSelectedRow(0); + ui_->normalize_allocations_checkbox_->setChecked(false); + ToggleNormalizeAllocations(); + + ui_->aliased_resource_checkbox_->setChecked(false); + ToggleAliasedResources(); + + ui_->allocation_height_slider_->setSliderPosition(0); + + ui_->search_box_->setText(""); +} + +void AllocationOverviewPane::ChangeColoring() +{ + colorizer_->UpdateLegends(); + ResizeItems(); +} + +void AllocationOverviewPane::ColorModeChanged() +{ + ChangeColoring(); + ui_->allocation_list_view_->viewport()->update(); +} + +void AllocationOverviewPane::OpenSnapshot(RmtDataSnapshot* snapshot) +{ + RMT_ASSERT(snapshot != nullptr); + if (snapshot != nullptr) + { + ui_->sort_combo_box_->SetSelectedRow(0); + ui_->sort_direction_combo_box_->SetSelectedRow(0); + model_->ResetModelValues(); + + if (snapshot->virtual_allocation_list.allocation_count > 0) + { + // remove any old allocations from the last snapshot and disconnect any connections + size_t current_size = allocation_graphic_objects_.size(); + for (size_t i = 0; i < current_size; i++) + { + RMVAllocationBar* allocation_item = allocation_graphic_objects_[i]; + disconnect(allocation_item, &RMVAllocationBar::ResourceSelected, this, &AllocationOverviewPane::SelectedResource); + } + allocation_graphic_objects_.clear(); + allocation_list_scene_->clear(); + + // add the graphics items to the scene, one item per allocation + int32_t count = std::min(kMaxAllocationObjects, snapshot->virtual_allocation_list.allocation_count); + for (int i = 0; i < count; i++) + { + RMVAllocationBar* allocation_item = new RMVAllocationBar(model_->GetAllocationBarModel(), i, kAllocationModelIndex, colorizer_); + allocation_list_scene_->addItem(allocation_item); + allocation_graphic_objects_.push_back(allocation_item); + connect(allocation_item, &RMVAllocationBar::ResourceSelected, this, &AllocationOverviewPane::SelectedResource); + } + + // Apply filters and sorting to the newly added items + ApplyFilters(); + ApplySort(); + } + + ResizeItems(); + } +} + +void AllocationOverviewPane::resizeEvent(QResizeEvent* event) +{ + ResizeItems(); + QWidget::resizeEvent(event); +} + +void AllocationOverviewPane::showEvent(QShowEvent* event) +{ + ResizeItems(); + QWidget::showEvent(event); +} + +void AllocationOverviewPane::hideEvent(QHideEvent* event) +{ + QWidget::hideEvent(event); +} + +void AllocationOverviewPane::AllocationHeightChanged() +{ + QScrollBar* scroll_bar = ui_->allocation_list_view_->verticalScrollBar(); + if (scroll_bar != nullptr) + { + const int32_t new_allocation_height = ui_->allocation_height_slider_->value(); + + if (allocation_height_ != new_allocation_height) + { + // Reposition the slider so the same allocation as before is in view. + int32_t scroll_bar_offset = scroll_bar->value(); + scroll_bar_offset *= new_allocation_height; + scroll_bar_offset /= allocation_height_; + allocation_height_ = new_allocation_height; + + scroll_bar->setValue(scroll_bar_offset); + ResizeItems(); + } + } +} + +void AllocationOverviewPane::ResizeItems() +{ + int allocation_offset = 0; + + QScrollBar* scroll_bar = ui_->allocation_list_view_->verticalScrollBar(); + if (scroll_bar != nullptr) + { + allocation_offset = scroll_bar->value() / allocation_height_; + } + + model_->GetAllocationBarModel()->SetAllocationOffset(allocation_offset); + + int y_offset = allocation_offset * allocation_height_; + const int scrollbar_width = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); + const int view_width = ui_->allocation_list_view_->width() - scrollbar_width - 2; + + const size_t num_objects = allocation_graphic_objects_.size(); + + for (size_t current_allocation_graphic_object_index = 0; current_allocation_graphic_object_index < num_objects; current_allocation_graphic_object_index++) + { + allocation_graphic_objects_[current_allocation_graphic_object_index]->UpdateDimensions(view_width, allocation_height_); + allocation_graphic_objects_[current_allocation_graphic_object_index]->setPos(0, y_offset); + + // move down based on the size of the item that was just added. + y_offset += allocation_graphic_objects_[current_allocation_graphic_object_index]->boundingRect().height(); + } + + UpdateAllocationListSceneRect(); +} + +void AllocationOverviewPane::ApplyFilters() +{ + preferred_heap_combo_box_model_->SetupState(ui_->preferred_heap_combo_box_); + + bool heaps[kRmtHeapTypeCount]; + for (uint32_t i = 0; i < (uint32_t)kRmtHeapTypeCount; i++) + { + heaps[i] = preferred_heap_combo_box_model_->ItemInList(i); + } + + // This does not show/hide items from the scene, + // instead it changes the underlying objects that are referenced by the items in the scene. + model_->ApplyFilters(ui_->search_box_->text(), &heaps[0]); + + UpdateAllocationListSceneRect(); +} + +// Update the scene rect of the allocationListView. +// +// Background: +// This is needed because the allocationListView always has all the items in it based +// on the number of allocations that were made. When a filter is applied to the model, +// it is not being done through a proxy model and the items in the graphics view are not +// being hidden. Instead the actual model is being changed, causing the items in the +// view to reference different data that has been moved to the same index. +// For example, the item at the top of the view is always index 0 and is always visible, +// but if a heap filter or search term happens to remove all the allocations, then all +// the allocations will skip their own painting, but the scene thinks they are still +// being displayed. As such, using a call like view->scene()->itemsBoundingRect() to get +// the height of visible allocations does not work properly - it always returns a size +// that is big enough to display all the allocations, even if they have been filtered out. +// +// About this function: +// This function will instead only update the scene rect if there is at least one item +// that is not filtered out and will retrieve the size of the first item and assume that +// all items are the same height. For this view, it is currently a safe assumption. +// An alternative approach, but more expensive, would be to iterate through every item +// and accumulate their heights if the model has an allocation for that particular index. +void AllocationOverviewPane::UpdateAllocationListSceneRect() +{ + const size_t num_allocations = model_->GetViewableAllocationCount(); + const int scroll_bar_width = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); + const int view_width = ui_->allocation_list_view_->width() - scroll_bar_width - 2; + int item_height = 0; + + if (num_allocations > 0 && allocation_graphic_objects_.size() > 0) + { + // Since each item has the same height, get the height of the first item and + // update the scene rect according to the number of items that will actually get painted. + item_height = allocation_graphic_objects_[0]->boundingRect().height(); + } + + allocation_list_scene_->setSceneRect(0, 0, view_width, num_allocations * item_height); + ui_->allocation_list_view_->viewport()->update(); +} + +void AllocationOverviewPane::ApplySort() +{ + int sortMode = ui_->sort_combo_box_->CurrentRow(); + bool ascending = (ui_->sort_direction_combo_box_->CurrentRow() == rmv::AllocationOverviewModel::kSortDirectionAscending); + + model_->Sort(sortMode, ascending); + ui_->allocation_list_view_->viewport()->update(); +} + +void AllocationOverviewPane::SelectedResource(RmtResourceIdentifier resource_identifier, bool navigate_to_pane) +{ + // broadcast the resource selection to any listening panes + // or the user needs to navigate because they double clicked + emit MessageManager::Get().ResourceSelected(resource_identifier); + + if (navigate_to_pane == true) + { + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotAllocationExplorer); + } +} + +void AllocationOverviewPane::SelectResource(RmtResourceIdentifier resource_identifier) +{ + size_t allocation_offset = model_->SelectResource(resource_identifier, kAllocationModelIndex); + + QScrollBar* scroll_bar = ui_->allocation_list_view_->verticalScrollBar(); + if (scroll_bar != nullptr && allocation_offset != UINT64_MAX) + { + const int32_t view_height = ui_->allocation_list_view_->height(); + const int32_t scroll_bar_offset = scroll_bar->value(); + const int32_t top_allocation_index = scroll_bar_offset / allocation_height_; + const int32_t bottom_allocation_index = (scroll_bar_offset + view_height) / allocation_height_; + + // If the allocation is outside the visible range, move the scrollbar so it's in range. + // If the allocation is partially visible, move the scrollbar so it's all visible. + if (static_cast(allocation_offset) <= top_allocation_index) + { + scroll_bar->setValue(static_cast(allocation_offset) * allocation_height_); + } + else if (static_cast(allocation_offset) >= bottom_allocation_index) + { + int32_t offset = static_cast(allocation_offset) * allocation_height_; + offset -= view_height; + offset += allocation_height_; + scroll_bar->setValue(offset); + } + } + + ui_->allocation_list_view_->viewport()->update(); +} + +void AllocationOverviewPane::HeapChanged(bool checked) +{ + RMT_UNUSED(checked); + ApplyFilters(); +} + +void AllocationOverviewPane::OnScaleFactorChanged() +{ + ResizeItems(); +} + +void AllocationOverviewPane::ToggleNormalizeAllocations() +{ + bool checked = ui_->normalize_allocations_checkbox_->isChecked(); + model_->SetNormalizeAllocations(checked); + ui_->allocation_list_view_->viewport()->update(); +} + +void AllocationOverviewPane::ToggleAliasedResources() const +{ + bool checked = ui_->aliased_resource_checkbox_->isChecked(); + model_->GetAllocationBarModel()->ShowAliased(checked); + ui_->allocation_list_view_->viewport()->update(); +} diff --git a/source/frontend/views/snapshot/allocation_overview_pane.h b/source/frontend/views/snapshot/allocation_overview_pane.h new file mode 100644 index 0000000..949e62d --- /dev/null +++ b/source/frontend/views/snapshot/allocation_overview_pane.h @@ -0,0 +1,116 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Allocation overview pane. +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_ALLOCATION_OVERVIEW_PANE_H_ +#define RMV_VIEWS_SNAPSHOT_ALLOCATION_OVERVIEW_PANE_H_ + +#include "ui_allocation_overview_pane.h" + +#include + +#include "rmt_resource_list.h" + +#include "models/heap_combo_box_model.h" +#include "models/snapshot/allocation_overview_model.h" +#include "views/base_pane.h" +#include "views/colorizer.h" +#include "views/custom_widgets/rmv_allocation_bar.h" + +/// Class declaration. +class AllocationOverviewPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit AllocationOverviewPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~AllocationOverviewPane(); + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Overridden show event. Fired when this pane is opened. + /// \param event the show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Overridden hide event. Fired when this pane is closed. + /// \param event the hide event object. + virtual void hideEvent(QHideEvent* event) Q_DECL_OVERRIDE; + + /// Clean up. + virtual void OnTraceClose() Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + + /// Open a snapshot. + /// \param snapshot The snapshot to open. + virtual void OpenSnapshot(RmtDataSnapshot* snapshot) Q_DECL_OVERRIDE; + +private slots: + /// Handle what happens when user changes filters. + void ApplyFilters(); + + /// Handle what happens when user changes sort mode. + void ApplySort(); + + /// Handle what happens when the color mode changes. + void ColorModeChanged(); + + /// Handle what happens when a checkbox in the heap dropdown is checked or unchecked. + /// \param checked Whether the checkbox is checked or unchecked. + void HeapChanged(bool checked); + + /// Resize UI elements when the DPI scale factor changes. + void OnScaleFactorChanged(); + + /// Select a resource on this pane. This is usually called when selecting a resource + /// on a different pane to make sure the resource selection is propagated to all + /// interested panes. + /// \param resource_identifier the resource identifier of the resource to select. + void SelectResource(RmtResourceIdentifier resource_identifier); + + /// Slot to handle what happens when a resource has been selected. This can be used to broadcast the resource + /// selection to any panes listening for the signal so they can also update their selected resource. + /// \param resource_identifier the resource Identifier. + /// \param navigate_to_pane If true, navigate to the allocation explorer pane. + void SelectedResource(RmtResourceIdentifier resource_identifier, bool navigate_to_pane); + + /// Handle what happens when the Normalize allocations checkbox is clicked. + void ToggleNormalizeAllocations(); + + /// Handle what happens when the 'show aliasing' checkbox is clicked on. + void ToggleAliasedResources() const; + + /// Handle what happens when the allocation height slider changes. + void AllocationHeightChanged(); + +private: + /// Resize all views. + void ResizeItems(); + + /// Update the scene rect of the allocationListView. + void UpdateAllocationListSceneRect(); + + Ui::allocation_overview_pane* ui_; ///< Pointer to the Qt UI design. + + rmv::AllocationOverviewModel* model_; ///< Container class for the widget models. + rmv::HeapComboBoxModel* preferred_heap_combo_box_model_; ///< The heap combo box model. + QGraphicsScene* allocation_list_scene_; ///< Scene containing all allocation widgets. + std::vector allocation_graphic_objects_; ///< The list of allocation objects. + Colorizer* colorizer_; ///< The colorizer used by the 'color by' combo box. + int allocation_height_; ///< The allocation height. +}; + +#endif // RMV_VIEWS_SNAPSHOT_ALLOCATION_OVERVIEW_PANE_H_ diff --git a/source/frontend/views/snapshot/allocation_overview_pane.ui b/source/frontend/views/snapshot/allocation_overview_pane.ui new file mode 100644 index 0000000..32a6118 --- /dev/null +++ b/source/frontend/views/snapshot/allocation_overview_pane.ui @@ -0,0 +1,320 @@ + + + allocation_overview_pane + + + + 0 + 0 + 1428 + 651 + + + + + + + true + + + + + 0 + 0 + 1408 + 631 + + + + + 0 + + + 0 + + + 0 + + + 5 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + PushButton + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + Sort + + + + + + + + 0 + 0 + + + + SortDir + + + + + + + + 0 + 0 + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + Normalize allocations + + + + + + + + 0 + 0 + + + + Show aliasing + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Allocation height + + + + + + + + 0 + 0 + + + + 50 + + + 400 + + + Qt::Horizontal + + + false + + + false + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + Double-click an allocation to view its details. + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + QFrame::NoFrame + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + 0 + 0 + + + + + + + + + + + + + ArrowIconComboBox + QPushButton +
qt_common/custom_widgets/arrow_icon_combo_box.h
+ 1 +
+ + ColoredLegendGraphicsView + QGraphicsView +
qt_common/custom_widgets/colored_legend_graphics_view.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + TextSearchWidget + QLineEdit +
qt_common/custom_widgets/text_search_widget.h
+
+ + RMVColoredCheckbox + QCheckBox +
views/custom_widgets/rmv_colored_checkbox.h
+
+
+ + + + +
diff --git a/source/frontend/views/snapshot/heap_overview_heap_layout.cpp b/source/frontend/views/snapshot/heap_overview_heap_layout.cpp new file mode 100644 index 0000000..ec8e497 --- /dev/null +++ b/source/frontend/views/snapshot/heap_overview_heap_layout.cpp @@ -0,0 +1,195 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for a single heap in the heap overview pane. +//============================================================================= + +#include "views/snapshot/heap_overview_heap_layout.h" + +#include "qt_common/utils/common_definitions.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_data_snapshot.h" + +#include "models/message_manager.h" +#include "settings/rmv_settings.h" +#include "util/string_util.h" +#include "util/widget_util.h" +#include "views/colorizer.h" + +HeapOverviewHeapLayout::HeapOverviewHeapLayout(QWidget* parent) + : QWidget(parent) + , ui_(new Ui::HeapOverviewHeapLayout) + , model_(nullptr) + , resource_legends_views_{} + , resource_legends_scenes_{} +{ + ui_->setupUi(this); + + QPalette donut_palette = ui_->resource_donut_->palette(); + donut_palette.setColor(QPalette::ColorRole::Background, Qt::white); + ui_->resource_donut_->setPalette(donut_palette); + + // set up the resource legends + resource_legends_views_[0] = ui_->legends_resource_1_; + resource_legends_views_[1] = ui_->legends_resource_2_; + resource_legends_views_[2] = ui_->legends_resource_3_; + resource_legends_views_[3] = ui_->legends_resource_4_; + resource_legends_views_[4] = ui_->legends_resource_5_; + resource_legends_views_[5] = ui_->legends_resource_6_; + + for (int i = 0; i < kNumResourceLegends; i++) + { + rmv::widget_util::InitGraphicsView(resource_legends_views_[i], rmv::kColoredLegendsHeight); + rmv::widget_util::InitColorLegend(resource_legends_scenes_[i], resource_legends_views_[i]); + } +} + +HeapOverviewHeapLayout::~HeapOverviewHeapLayout() +{ + delete model_; +} + +void HeapOverviewHeapLayout::Initialize(RmtHeapType heap) +{ + Q_ASSERT(model_ == nullptr); + model_ = new rmv::HeapOverviewHeapModel(heap); + + model_->InitializeModel(ui_->title_label_, rmv::kHeapOverviewTitle, "text"); + model_->InitializeModel(ui_->title_description_, rmv::kHeapOverviewDescription, "text"); + + model_->InitializeModel(ui_->warning_message_, rmv::kHeapOverviewWarningText, "text"); + + model_->InitializeModel(ui_->content_location_, rmv::kHeapOverviewLocation, "text"); + model_->InitializeModel(ui_->content_cpu_cached_, rmv::kHeapOverviewCpuCached, "text"); + model_->InitializeModel(ui_->content_cpu_visible_, rmv::kHeapOverviewCpuVisible, "text"); + model_->InitializeModel(ui_->content_gpu_cached_, rmv::kHeapOverviewGpuCached, "text"); + model_->InitializeModel(ui_->content_gpu_visible_, rmv::kHeapOverviewGpuVisible, "text"); + model_->InitializeModel(ui_->content_smallest_allocation_, rmv::kHeapOverviewSmallestAllocation, "text"); + model_->InitializeModel(ui_->content_largest_allocation_, rmv::kHeapOverviewLargestAllocation, "text"); + model_->InitializeModel(ui_->content_mean_allocation_, rmv::kHeapOverviewMeanAllocation, "text"); +} + +int HeapOverviewHeapLayout::DonutSectionWidth() +{ + int width = ui_->donut_widget_->sizeHint().width(); + return width; +} + +void HeapOverviewHeapLayout::SetDonutSectionWidth(const int width) +{ + ui_->donut_widget_->setMinimumWidth(width); +} + +void HeapOverviewHeapLayout::resizeEvent(QResizeEvent* event) +{ + // Set the bar graph width to 1/3rd of the screen. + int bar_graph_width = this->width() / 3; + ui_->bar_graph_widget_->setFixedWidth(bar_graph_width); + + QWidget::resizeEvent(event); +} + +void HeapOverviewHeapLayout::Update() +{ + Q_ASSERT(model_ != nullptr); + if (model_ != nullptr) + { + model_->Update(); + } + + bool show_warning_message = model_->ShowSubscriptionWarning(); + ui_->warning_widget_->setVisible(show_warning_message); + + ui_->resource_donut_->SetArcWidth(20); + + uint64_t resource_data[kRmtResourceUsageTypeCount * 2] = {0}; + int num_other = 0; + int num_resources = model_->GetResourceData(kNumResourceLegends - 1, &resource_data[0], &num_other); + + if (num_resources > 0) + { + int num_segments = num_resources + ((num_other > 0) ? 1 : 0); + ui_->resource_donut_->SetNumSegments(num_segments); + + for (int i = 0; i < kNumResourceLegends; i++) + { + resource_legends_scenes_[i]->Clear(); + } + + for (int i = 0; i < num_resources; i++) + { + RmtResourceUsageType type = (RmtResourceUsageType)resource_data[i * 2]; + uint64_t value = resource_data[(i * 2) + 1]; + + ui_->resource_donut_->SetIndexValue(i, value); + const QColor& color = Colorizer::GetResourceUsageColor(type); + ui_->resource_donut_->SetIndexColor(i, color); + resource_legends_scenes_[i]->AddColorLegendItem(color, rmv::string_util::GetResourceUsageString(type)); + } + + if (num_other > 0) + { + ui_->resource_donut_->SetIndexValue(num_segments - 1, num_other); + ui_->resource_donut_->SetIndexColor(num_segments - 1, RMVSettings::Get().GetColorResourceFreeSpace()); + resource_legends_scenes_[num_segments - 1]->AddColorLegendItem(RMVSettings::Get().GetColorResourceFreeSpace(), "Other"); + } + } + else + { + ui_->resource_donut_->SetNumSegments(1); + ui_->resource_donut_->SetIndexValue(0, 1); + ui_->resource_donut_->SetIndexColor(0, RMVSettings::Get().GetColorResourceFreeSpace()); + } + + // set the view size to match the scene size so the legends appear left-justified + for (int i = 0; i < kNumResourceLegends; i++) + { + resource_legends_views_[i]->setFixedSize(resource_legends_scenes_[i]->itemsBoundingRect().size().toSize()); + } + + // Get memory parameters from the model + uint64_t total_physical_size = 0; + uint64_t total_virtual_memory_requested = 0; + uint64_t total_bound_virtual_memory = 0; + uint64_t total_physical_mapped_by_process = 0; + uint64_t total_physical_mapped_by_other_processes = 0; + RmtSegmentSubscriptionStatus subscription_status = kRmtSegmentSubscriptionStatusUnderLimit; + + model_->GetMemoryParameters(total_physical_size, + total_virtual_memory_requested, + total_bound_virtual_memory, + total_physical_mapped_by_process, + total_physical_mapped_by_other_processes, + subscription_status); + + // calc max size + uint64_t max_size = total_physical_mapped_by_process + total_physical_mapped_by_other_processes; + if (total_virtual_memory_requested > max_size) + { + max_size = total_virtual_memory_requested; + } + if (total_physical_size > max_size) + { + max_size = total_physical_size; + } + if (total_bound_virtual_memory > max_size) + { + max_size = total_bound_virtual_memory; + } + + // Apply memory parameters to the memory bars + ui_->bar_requested_->SetParameters(total_virtual_memory_requested, 0, max_size, true, subscription_status); + ui_->bar_bound_->SetParameters(total_bound_virtual_memory, 0, max_size, false, subscription_status); + ui_->bar_total_size_->SetParameters(total_physical_size, 0, max_size, false, subscription_status); + ui_->bar_used_->SetParameters(total_physical_mapped_by_process, total_physical_mapped_by_other_processes, max_size, false, subscription_status); + + // update the various UI elements + ui_->bar_requested_->update(); + ui_->bar_bound_->update(); + ui_->bar_total_size_->update(); + ui_->bar_used_->update(); + + ui_->donut_widget_->update(); +} diff --git a/source/frontend/views/snapshot/heap_overview_heap_layout.h b/source/frontend/views/snapshot/heap_overview_heap_layout.h new file mode 100644 index 0000000..5c19083 --- /dev/null +++ b/source/frontend/views/snapshot/heap_overview_heap_layout.h @@ -0,0 +1,66 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for a layout for a single heap in the heap overview pane. +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_HEAP_OVERVIEW_HEAP_LAYOUT_H_ +#define RMV_VIEWS_SNAPSHOT_HEAP_OVERVIEW_HEAP_LAYOUT_H_ + +#include "ui_heap_overview_heap_layout.h" + +#include + +#include "qt_common/custom_widgets/colored_legend_scene.h" +#include "qt_common/custom_widgets/colored_legend_graphics_view.h" + +#include "rmt_types.h" + +#include "models/snapshot/heap_overview_heap_model.h" + +/// Class declaration +class HeapOverviewHeapLayout : public QWidget +{ + Q_OBJECT + +public: + /// The number of resource legends set up in the UI. + static const int kNumResourceLegends = 6; + + /// Constructor. + /// \param parent Pointer to the parent widget. + explicit HeapOverviewHeapLayout(QWidget* parent = nullptr); + + /// Destructor. + virtual ~HeapOverviewHeapLayout(); + + /// Initialize the widget. + /// \param heap The heap this widget represents. + void Initialize(RmtHeapType heap); + + /// Update the widget. + void Update(); + + /// Gets the width of the section containing the donut, legend, and a horizontal spacer. + /// \return width of the donut section in pixels. + int DonutSectionWidth(); + + /// Sets the minimum width of the donut section. + /// \param width The minimum width in pixels to set the donut section. + void SetDonutSectionWidth(const int width); + +protected: + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + +private: + Ui::HeapOverviewHeapLayout* ui_; ///< Pointer to the Qt UI design. + + rmv::HeapOverviewHeapModel* model_; ///< Container class for the widget model. + ColoredLegendGraphicsView* resource_legends_views_[kNumResourceLegends]; ///< Duplicate of ui elements in array form for easy access. + ColoredLegendScene* resource_legends_scenes_[kNumResourceLegends]; ///< Pointer to the heap residency legends scene. +}; + +#endif // RMV_VIEWS_SNAPSHOT_HEAP_OVERVIEW_HEAP_LAYOUT_H_ diff --git a/source/frontend/views/snapshot/heap_overview_heap_layout.ui b/source/frontend/views/snapshot/heap_overview_heap_layout.ui new file mode 100644 index 0000000..bc004be --- /dev/null +++ b/source/frontend/views/snapshot/heap_overview_heap_layout.ui @@ -0,0 +1,941 @@ + + + HeapOverviewHeapLayout + + + + 0 + 0 + 2429 + 1425 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 11 + 75 + true + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 10 + 10 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 8 + + + 0 + + + 5 + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + The total size of the memory in this heap + + + Total size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + The total size of the memory in this heap + + + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + The amount of memory in this heap requested by the application + + + Requested + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + The amount of memory in this heap requested by the application + + + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + The amount of memory in this heap that has bound resources + + + Bound + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + The amount of memory in this heap that has bound resources + + + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + The amount of memory in this heap that has been commited to physical memory + + + Committed + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + The amount of memory in this heap that has been commited to physical memory + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 10 + 5 + + + + + + + + QFrame::Box + + + QFrame::Plain + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + + :/Resources/assets/third_party/ionicons/warning.svg + + + true + + + Qt::AlignLeading + + + + + + + + 0 + 0 + + + + + + + Qt::RichText + + + Qt::AlignLeading + + + true + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 10 + 5 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 10 + + + + + + 0 + 0 + + + + Largest allocation: + + + + + + + + 0 + 0 + + + + largest + + + + + + + + 0 + 0 + + + + GPU Visible: + + + + + + + + 0 + 0 + + + + CPU Cached: + + + + + + + + 0 + 0 + + + + Smallest allocation: + + + + + + + + 0 + 0 + + + + location + + + + + + + + 0 + 0 + + + + CPU Visible: + + + + + + + + 0 + 0 + + + + yes / no + + + + + + + + 0 + 0 + + + + smallest + + + + + + + + 0 + 0 + + + + yes / no + + + + + + + + 0 + 0 + + + + GPU Cached: + + + + + + + + 0 + 0 + + + + yes / no + + + + + + + + 0 + 0 + + + + yes / no + + + + + + + + 0 + 0 + + + + Physical location of heap: + + + + + + + + 0 + 0 + + + + Mean allocation: + + + + + + + + 0 + 0 + + + + mean + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 10 + 5 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + + 180 + 180 + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 10 + 5 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 10 + 5 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + ColoredLegendGraphicsView + QGraphicsView +
qt_common/custom_widgets/colored_legend_graphics_view.h
+
+ + DonutPieWidget + QWidget +
qt_common/custom_widgets/donut_pie_widget.h
+ 1 +
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + RMVHeapOverviewMemoryBar + QWidget +
views/custom_widgets/rmv_heap_overview_memory_bar.h
+ 1 +
+
+ + + + +
diff --git a/source/frontend/views/snapshot/heap_overview_pane.cpp b/source/frontend/views/snapshot/heap_overview_pane.cpp new file mode 100644 index 0000000..cc53581 --- /dev/null +++ b/source/frontend/views/snapshot/heap_overview_pane.cpp @@ -0,0 +1,74 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the Heap Overview pane. +//============================================================================= + +#include "views/snapshot/heap_overview_pane.h" + +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_types.h" + +#include "models/snapshot/heap_overview_heap_model.h" +#include "util/widget_util.h" + +HeapOverviewPane::HeapOverviewPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::HeapOverviewPane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + ui_->local_heap_view_->Initialize(kRmtHeapTypeLocal); + ui_->invisible_heap_view_->Initialize(kRmtHeapTypeInvisible); + ui_->system_heap_view_->Initialize(kRmtHeapTypeSystem); +} + +HeapOverviewPane::~HeapOverviewPane() +{ +} + +void HeapOverviewPane::Refresh() +{ + ui_->local_heap_view_->Update(); + ui_->invisible_heap_view_->Update(); + ui_->system_heap_view_->Update(); +} + +void HeapOverviewPane::OpenSnapshot(RmtDataSnapshot* snapshot) +{ + Q_UNUSED(snapshot); + + Refresh(); +} + +void HeapOverviewPane::ResizeItems() +{ + // Ensure the donut sections have matching widths. + int row1_donut_width = ui_->local_heap_view_->DonutSectionWidth(); + int row2_donut_width = ui_->invisible_heap_view_->DonutSectionWidth(); + int row3_donut_width = ui_->system_heap_view_->DonutSectionWidth(); + + int widest_donut_width = std::max(row1_donut_width, row2_donut_width); + widest_donut_width = std::max(widest_donut_width, row3_donut_width); + + ui_->local_heap_view_->SetDonutSectionWidth(widest_donut_width); + ui_->invisible_heap_view_->SetDonutSectionWidth(widest_donut_width); + ui_->system_heap_view_->SetDonutSectionWidth(widest_donut_width); +} + +void HeapOverviewPane::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + + ResizeItems(); +} + +void HeapOverviewPane::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + ResizeItems(); +} \ No newline at end of file diff --git a/source/frontend/views/snapshot/heap_overview_pane.h b/source/frontend/views/snapshot/heap_overview_pane.h new file mode 100644 index 0000000..d950ce9 --- /dev/null +++ b/source/frontend/views/snapshot/heap_overview_pane.h @@ -0,0 +1,51 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Heap Overview pane. +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_HEAP_OVERVIEW_PANE_H_ +#define RMV_VIEWS_SNAPSHOT_HEAP_OVERVIEW_PANE_H_ + +#include "ui_heap_overview_pane.h" + +#include "views/base_pane.h" + +/// Class declaration +class HeapOverviewPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The parent widget. + explicit HeapOverviewPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~HeapOverviewPane(); + + /// Open a snapshot. + /// \param snapshot The snapshot to open. + virtual void OpenSnapshot(RmtDataSnapshot* snapshot) Q_DECL_OVERRIDE; + +protected: + /// Overridden show event. Fired when this pane is opened. + /// \param event the show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + +private: + /// Refresh what's visible on the UI. + void Refresh(); + + /// Resize all relevant UI items. + void ResizeItems(); + + Ui::HeapOverviewPane* ui_; ///< Pointer to the Qt UI design. +}; + +#endif // RMV_VIEWS_SNAPSHOT_HEAP_OVERVIEW_PANE_H_ diff --git a/source/frontend/views/snapshot/heap_overview_pane.ui b/source/frontend/views/snapshot/heap_overview_pane.ui new file mode 100644 index 0000000..8ea2d57 --- /dev/null +++ b/source/frontend/views/snapshot/heap_overview_pane.ui @@ -0,0 +1,125 @@ + + + HeapOverviewPane + + + + 0 + 0 + 1137 + 791 + + + + + + + true + + + + + 0 + 0 + 1113 + 767 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + + + + Qt::Vertical + + + + 10 + 10 + + + + + + + + + + + + + HeapOverviewHeapLayout + QWidget +
views/snapshot/heap_overview_heap_layout.h
+
+
+ + + + +
diff --git a/source/frontend/views/snapshot/resource_details_pane.cpp b/source/frontend/views/snapshot/resource_details_pane.cpp new file mode 100644 index 0000000..78347a9 --- /dev/null +++ b/source/frontend/views/snapshot/resource_details_pane.cpp @@ -0,0 +1,357 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the Resource details pane. +//============================================================================= + +#include "views/snapshot/resource_details_pane.h" + +#include +#include + +#include "qt_common/utils/common_definitions.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" + +#include "models/message_manager.h" +#include "models/proxy_models/resource_details_proxy_model.h" +#include "util/widget_util.h" +#include "views/main_window.h" + +static const int kDonutThickness = 20; +static const int kDonutDimension = 200; + +/// Worker class definition to do the processing of the resource history data +/// on a separate thread. +class ResourceWorker : public rmv::BackgroundTask +{ +public: + /// Constructor + ResourceWorker(rmv::ResourceDetailsModel* model, RmtResourceIdentifier resource_identifier) + : BackgroundTask() + , model_(model) + , resource_identifier_(resource_identifier) + { + } + + /// Destructor + ~ResourceWorker() + { + } + + /// Worker thread function + virtual void ThreadFunc() + { + model_->GenerateResourceHistory(resource_identifier_); + } + +private: + rmv::ResourceDetailsModel* model_; ///< Pointer to the model data + RmtResourceIdentifier resource_identifier_; ///< The selected resource identifier +}; + +ResourceDetailsPane::ResourceDetailsPane(MainWindow* parent) + : BasePane(parent) + , ui_(new Ui::ResourceDetailsPane) + , main_window_(parent) + , resource_identifier_(0) + , thread_controller_(nullptr) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + model_ = new rmv::ResourceDetailsModel(); + + model_->InitializeModel(ui_->label_title_, rmv::kResourceDetailsResourceName, "text"); + + model_->InitializeModel(ui_->content_base_address_, rmv::kResourceDetailsAllocationBaseAddress, "text"); + model_->InitializeModel(ui_->content_offset_, rmv::kResourceDetailsAllocationOffset, "text"); + model_->InitializeModel(ui_->content_resource_address_, rmv::kResourceDetailsBaseAddress, "text"); + model_->InitializeModel(ui_->content_size_, rmv::kResourceDetailsSize, "text"); + model_->InitializeModel(ui_->content_type_, rmv::kResourceDetailsType, "text"); + model_->InitializeModel(ui_->content_preferred_heap_, rmv::kResourceDetailsHeap, "text"); + model_->InitializeModel(ui_->content_fully_mapped_, rmv::kResourceDetailsFullyMapped, "text"); + model_->InitializeModel(ui_->content_unmapped_percentage_, rmv::kResourceDetailsUnmappedPercentage, "text"); + model_->InitializeModel(ui_->content_create_time_, rmv::kResourceDetailsCreateTime, "text"); + model_->InitializeModel(ui_->content_bind_time_, rmv::kResourceDetailsBindTime, "text"); + model_->InitializeModel(ui_->content_commit_type_, rmv::kResourceDetailsCommitTime, "text"); + model_->InitializeModel(ui_->content_owner_type_, rmv::kResourceDetailsOwnerTime, "text"); + model_->InitializeModel(ui_->content_flags_, rmv::kResourceDetailsFlags, "text"); + + model_->InitializePropertiesTableModel(ui_->resource_properties_table_view_, 0, 2); + + // The properties table is a small one, so resize this based on contents of every row (ie: setting precision to -1), + // and add a 20 pixel padding after cell contents. + ui_->resource_properties_table_view_->SetColumnPadding(20); + ui_->resource_properties_table_view_->horizontalHeader()->setResizeContentsPrecision(-1); + ui_->resource_properties_table_view_->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents); + ui_->resource_properties_table_view_->horizontalHeader()->setSectionsClickable(true); + ui_->resource_properties_table_view_->horizontalHeader()->setStretchLastSection(true); + ui_->resource_properties_table_view_->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents); + ui_->resource_properties_table_view_->verticalHeader()->setResizeContentsPrecision(1); + + model_->InitializeTimelineTableModel(ui_->resource_timeline_table_view_, 0, kResourceHistoryCount); + + // The Resource timeline table has lots of horizontal space, + // so these column widths are a bit wider than the actual table contents. + ui_->resource_timeline_table_view_->SetColumnPadding(0); + ui_->resource_timeline_table_view_->SetColumnWidthEms(0, 6); + ui_->resource_timeline_table_view_->SetColumnWidthEms(1, 30); + ui_->resource_timeline_table_view_->SetColumnWidthEms(2, 8); + + // Stil let the user resize the columns if desired. + ui_->resource_timeline_table_view_->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Interactive); + + // set up the residency legends + rmv::widget_util::InitGraphicsView(ui_->legends_view_local_, rmv::kColoredLegendsHeight); + rmv::widget_util::InitGraphicsView(ui_->legends_view_invisible_, rmv::kColoredLegendsHeight); + rmv::widget_util::InitGraphicsView(ui_->legends_view_system_, rmv::kColoredLegendsHeight); + rmv::widget_util::InitGraphicsView(ui_->legends_view_unmapped_, rmv::kColoredLegendsHeight); + + rmv::widget_util::InitColorLegend(legends_scene_heaps_[kResidencyLocal], ui_->legends_view_local_); + rmv::widget_util::InitColorLegend(legends_scene_heaps_[kResidencyInvisible], ui_->legends_view_invisible_); + rmv::widget_util::InitColorLegend(legends_scene_heaps_[kResidencySystem], ui_->legends_view_system_); + rmv::widget_util::InitColorLegend(legends_scene_heaps_[kResidencyUnmapped], ui_->legends_view_unmapped_); + + ui_->resource_timeline_->Initialize(model_); + + // set up the residency donut widget + ui_->residency_donut_->setFixedWidth(ScalingManager::Get().Scaled(kDonutDimension)); + ui_->residency_donut_->setFixedHeight(ScalingManager::Get().Scaled(kDonutDimension)); + ui_->residency_donut_->SetArcWidth(ScalingManager::Get().Scaled(kDonutThickness)); + ui_->residency_donut_->SetNumSegments(kRmtHeapTypeSystem + 2); + + ui_->resource_timeline_table_view_->setFrameStyle(QFrame::StyledPanel); + ui_->resource_timeline_table_view_->horizontalHeader()->setSectionsClickable(true); + ui_->resource_timeline_table_view_->horizontalHeader()->setResizeContentsPrecision(32); + + // Allow the resource_timeline_table_view_ to resize the rows based on the size of the first column. + ui_->resource_timeline_table_view_->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents); + ui_->resource_timeline_table_view_->verticalHeader()->setResizeContentsPrecision(1); + + // hide the details column until it's hooked up + ui_->resource_timeline_table_view_->hideColumn(kResourceHistoryDetails); + + // hide the 'owner type' and 'flags' in the public build + ui_->label_owner_type_->hide(); + ui_->content_owner_type_->hide(); + ui_->label_flags_->hide(); + ui_->content_flags_->hide(); + + // add a delegate to the resource timeline table to allow custom painting + legend_delegate_ = new RMVResourceEventDelegate(nullptr, model_); + ui_->resource_timeline_table_view_->setItemDelegateForColumn(kResourceHistoryLegend, legend_delegate_); + + // intercept the ResourceSelected signal so the chosen resource can be set up. This signal is sent + // before the pane navigation + connect(&MessageManager::Get(), &MessageManager::ResourceSelected, this, &ResourceDetailsPane::SelectResource); + + connect(ui_->resource_timeline_, &RMVResourceTimeline::TimelineSelected, this, &ResourceDetailsPane::TimelineSelected); + + // click on the table to update the selected icon on the timeline, then call update via a lambda which will cause a repaint + connect(ui_->resource_timeline_table_view_, &QTableView::clicked, model_, &rmv::ResourceDetailsModel::TimelineEventSelected); + connect(ui_->resource_timeline_table_view_, &QTableView::clicked, [=]() { ui_->resource_timeline_->update(); }); + + // resource base address should navigate to allocation explorer + ui_->content_base_address_->setCursor(Qt::PointingHandCursor); + connect(ui_->content_base_address_, &QPushButton::clicked, [=]() { emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotAllocationExplorer); }); + + // set up a connection between the timeline being sorted and making sure the selected event is visible + connect(model_->GetTimelineProxyModel(), &rmv::ResourceDetailsProxyModel::layoutChanged, this, &ResourceDetailsPane::ScrollToSelectedEvent); + + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &ResourceDetailsPane::OnScaleFactorChanged); +} + +ResourceDetailsPane::~ResourceDetailsPane() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &ResourceDetailsPane::OnScaleFactorChanged); + + delete legend_delegate_; + delete model_; + delete ui_; +} + +void ResourceDetailsPane::OnScaleFactorChanged() +{ + // Resize the donut + ui_->residency_donut_->setFixedSize(ScalingManager::Get().Scaled(kDonutDimension), ScalingManager::Get().Scaled(kDonutDimension)); + ui_->residency_donut_->SetArcWidth(ScalingManager::Get().Scaled(kDonutThickness)); +} + +void ResourceDetailsPane::showEvent(QShowEvent* event) +{ + if (model_->IsResourceValid(resource_identifier_) == true) + { + // enable the active resource history page in the stacked widget + ui_->resource_valid_switch_->setCurrentIndex(0); + + // start the processing thread and pass in the worker object. The thread controller will take ownership + // of the worker and delete it once it's complete + thread_controller_ = new rmv::ThreadController(main_window_, this, new ResourceWorker(model_, resource_identifier_)); + + // when the worker thread has finished, a signal will be emitted. Wait for the signal here and update + // the UI with the newly acquired data from the worker thread + connect(thread_controller_, &rmv::ThreadController::ThreadFinished, this, &ResourceDetailsPane::Refresh); + } + else + { + // enable the invalid resource history page in the stacked widget + ui_->resource_valid_switch_->setCurrentIndex(1); + } + + ResizeItems(); + QWidget::showEvent(event); +} + +void ResourceDetailsPane::hideEvent(QHideEvent* event) +{ + if (model_->IsResourceValid(resource_identifier_) == true && thread_controller_ != nullptr) + { + disconnect(thread_controller_, &rmv::ThreadController::ThreadFinished, this, &ResourceDetailsPane::Refresh); + thread_controller_->deleteLater(); + thread_controller_ = nullptr; + } + QWidget::hideEvent(event); +} + +void ResourceDetailsPane::Refresh() +{ + // be sure to make sure the worker thread has populated the resource history table before + // updating the model. + if (thread_controller_ != nullptr && thread_controller_->Finished()) + { + // Prior to doing a table update, disable sorting since Qt is super slow about it + ui_->resource_timeline_table_view_->setSortingEnabled(false); + + int32_t num_properties = model_->Update(resource_identifier_); + if (num_properties == 0) + { + ui_->resource_properties_valid_switch_->setCurrentIndex(0); + } + else + { + ui_->resource_properties_valid_switch_->setCurrentIndex(1); + } + + ui_->resource_timeline_table_view_->setSortingEnabled(true); + ui_->resource_timeline_table_view_->sortByColumn(kResourceHistoryTime, Qt::AscendingOrder); + rmv::widget_util::SetWidgetBackgroundColor(ui_->residency_donut_, Qt::white); + + int value; + QString name; + QColor color; + + RmtResourceBackingStorage heapTypes[kResidencyCount] = {(RmtResourceBackingStorage)kRmtHeapTypeLocal, + (RmtResourceBackingStorage)kRmtHeapTypeInvisible, + (RmtResourceBackingStorage)kRmtHeapTypeSystem, + kRmtResourceBackingStorageUnmapped}; + + for (int i = 0; i < kResidencyCount; i++) + { + RMT_ASSERT(legends_scene_heaps_[i] != nullptr); + legends_scene_heaps_[i]->Clear(); + + value = 0; + if (model_->GetResidencyData(resource_identifier_, heapTypes[i], value, name, color) == true) + { + legends_scene_heaps_[i]->AddColorLegendItem(color, name + QString(" (%1%)").arg(value)); + ui_->residency_donut_->SetIndexValue(i, value); + ui_->residency_donut_->SetIndexColor(i, color); + } + } + + // set the view sizes to match the scene sizes so the legends appear left-justified + ui_->legends_view_local_->setFixedSize(legends_scene_heaps_[kResidencyLocal]->itemsBoundingRect().size().toSize()); + ui_->legends_view_invisible_->setFixedSize(legends_scene_heaps_[kResidencyInvisible]->itemsBoundingRect().size().toSize()); + ui_->legends_view_system_->setFixedSize(legends_scene_heaps_[kResidencySystem]->itemsBoundingRect().size().toSize()); + ui_->legends_view_unmapped_->setFixedSize(legends_scene_heaps_[kResidencyUnmapped]->itemsBoundingRect().size().toSize()); + + bool baseAddressValid = model_->IsResourceBaseAddressValid(resource_identifier_); + ui_->content_base_address_->setEnabled(baseAddressValid); + if (baseAddressValid == true) + { + ui_->content_base_address_->setStyleSheet(kLinkButtonStylesheet); + } + else + { + ui_->content_base_address_->setStyleSheet("QPushButton { color : lightGray; border: none; text-align: left}"); + } + + // Show the warning message if the memory isn't all in the preferred heap + if (model_->PhysicalMemoryInPreferredHeap(resource_identifier_) == true) + { + ui_->warning_widget_->hide(); + } + else + { + ui_->warning_widget_->show(); + } + } + + // Make sure the properties table can display the contents. + ui_->resource_properties_table_view_->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); + + SetMaximumTimelineTableHeight(); +} + +void ResourceDetailsPane::SwitchTimeUnits() +{ + if (resource_identifier_ != 0) + { + model_->Update(resource_identifier_); + } +} + +void ResourceDetailsPane::ChangeColoring() +{ + Refresh(); +} + +void ResourceDetailsPane::ResizeItems() +{ + Refresh(); +} + +void ResourceDetailsPane::resizeEvent(QResizeEvent* event) +{ + ResizeItems(); + QWidget::resizeEvent(event); +} + +void ResourceDetailsPane::SelectResource(RmtResourceIdentifier resource_identifier) +{ + resource_identifier_ = resource_identifier; +} + +void ResourceDetailsPane::TimelineSelected(double logical_position, double icon_size) +{ + int row = model_->GetEventRowFromTimeline(logical_position, icon_size); + if (row != -1) + { + ui_->resource_timeline_table_view_->selectRow(row); + } + else + { + ui_->resource_timeline_table_view_->clearSelection(); + } + ui_->resource_timeline_->update(); +} + +void ResourceDetailsPane::ScrollToSelectedEvent() +{ + QItemSelectionModel* selected_item = ui_->resource_timeline_table_view_->selectionModel(); + if (selected_item->hasSelection()) + { + QModelIndexList item_list = selected_item->selectedRows(); + if (item_list.size() > 0) + { + QModelIndex model_index = item_list[0]; + ui_->resource_timeline_table_view_->scrollTo(model_index, QAbstractItemView::ScrollHint::PositionAtTop); + } + } +} diff --git a/source/frontend/views/snapshot/resource_details_pane.h b/source/frontend/views/snapshot/resource_details_pane.h new file mode 100644 index 0000000..1b14e6e --- /dev/null +++ b/source/frontend/views/snapshot/resource_details_pane.h @@ -0,0 +1,109 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Resource details pane. +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_RESOURCE_DETAILS_PANE_H_ +#define RMV_VIEWS_SNAPSHOT_RESOURCE_DETAILS_PANE_H_ + +#include "ui_resource_details_pane.h" + +#include "qt_common/custom_widgets/colored_legend_scene.h" + +#include "models/snapshot/resource_details_model.h" +#include "views/base_pane.h" +#include "views/delegates/rmv_resource_event_delegate.h" +#include "util/thread_controller.h" +#include "util/widget_util.h" + +class MainWindow; + +/// Class declaration. +class ResourceDetailsPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit ResourceDetailsPane(MainWindow* parent = nullptr); + + /// Destructor. + virtual ~ResourceDetailsPane(); + + /// Update time units. + virtual void SwitchTimeUnits() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Overridden show event. Fired when this pane is opened. + /// \param event the show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Overridden hide event. Fired when this pane is closed. + /// \param event the hide event object. + virtual void hideEvent(QHideEvent* event) Q_DECL_OVERRIDE; + +private slots: + /// Slot to handle what happens when the timeline is clicked on. + /// Coordinate values passed in are logical positions between 0.0 and 1.0, where 0.0 corresponds to + /// the left of the timeline and 1.0 corresponds to the right. + /// \param logical_position The logical position clicked on in the timeline. + /// \param icon_size The size of the icon in logical coordinates. + void TimelineSelected(double logical_position, double icon_size); + + /// Select a resource on this pane. This is usually called when selecting a resource + /// on a different pane to make sure the resource selection is propagated to all + /// interested panes. + /// \param resource_identifier the resource identifier of the resource to select. + void SelectResource(RmtResourceIdentifier resource_identifier); + + /// Slot to handle what happens after the resource history table is sorted. + /// Make sure the selected item (if there is one) is visible. + void ScrollToSelectedEvent(); + + /// Respond to DPI scale factor changes. + void OnScaleFactorChanged(); + +private: + /// Helper function to set the maximum height of the timeline table so it only contains rows with valid data. + inline void SetMaximumTimelineTableHeight() + { + ui_->resource_timeline_table_view_->setMaximumHeight( + rmv::widget_util::GetTableHeight(ui_->resource_timeline_table_view_, model_->GetTimelineProxyModel()->rowCount())); + } + + /// Enum of residency types. + enum ResidencyTypes + { + kResidencyLocal, + kResidencyInvisible, + kResidencySystem, + kResidencyUnmapped, + + kResidencyCount + }; + + /// Refresh the UI. + void Refresh(); + + /// Resize all relevant UI items. + void ResizeItems(); + + Ui::ResourceDetailsPane* ui_; ///< Pointer to the Qt UI design. + MainWindow* main_window_; ///< Reference to the mainwindow (parent). + rmv::ResourceDetailsModel* model_; ///< Container class for the widget models. + RmtResourceIdentifier resource_identifier_; ///< The selected resource identifier. + ColoredLegendScene* legends_scene_heaps_[kResidencyCount]; ///< Pointer to the residency legends scene. + RMVResourceEventDelegate* legend_delegate_; ///< Delegate responsible for custom painting in the timeline table. + rmv::ThreadController* thread_controller_; ///< The thread for processing backend data. +}; + +#endif // RMV_VIEWS_SNAPSHOT_RESOURCE_HISTORY_PANE_H_ diff --git a/source/frontend/views/snapshot/resource_details_pane.ui b/source/frontend/views/snapshot/resource_details_pane.ui new file mode 100644 index 0000000..babf086 --- /dev/null +++ b/source/frontend/views/snapshot/resource_details_pane.ui @@ -0,0 +1,1471 @@ + + + ResourceDetailsPane + + + + 0 + 0 + 2420 + 1315 + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 2402 + 1297 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Resource details + + + + + + + Qt::Horizontal + + + + 10 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 10 + 10 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 20 + + + + + + 0 + 0 + + + + Preferred heap + + + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + - + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + Summary + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + Parent allocation base address + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + Size in bytes + + + + + + + + 8 + 75 + true + + + + + + + + + + + + 0 + 0 + + + + Offset within allocation + + + + + + + + 0 + 0 + + + + Commit type + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + Bind time + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + Creation time + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + Resource type + + + + + + + + 0 + 0 + + + + Unmapped percentage + + + + + + + + 0 + 0 + + + + Fully mapped in preferred + + + + + + + + 0 + 0 + + + + - + + + + + + + + 0 + 0 + + + + Owner type + + + + + + + + 0 + 0 + + + + Resource virtual address + + + + + + + + 0 + 0 + + + + Flags + + + + + + + + 0 + 0 + + + + - + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 10 + 5 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + :/Resources/assets/stop_128x128.png + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 12 + + + + No additional resource properties. + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + Properties + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + QAbstractScrollArea::AdjustToContents + + + + + + + + + + + Qt::Horizontal + + + + 10 + 10 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + Physical residency + + + + + + + QFrame::Box + + + QFrame::Plain + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + + :/Resources/assets/third_party/ionicons/warning.svg + + + true + + + Qt::AlignLeading + + + + + + + + 0 + 0 + + + + <b>WARNING!</b><br>Not all of this resource is currently contained within its preferred heap. + + + Qt::RichText + + + Qt::AlignLeading + + + + + + + + + + + 20 + + + 0 + + + 8 + + + 0 + + + + + + 0 + 0 + + + + + 200 + 200 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 10 + 5 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 10 + 5 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 8 + 75 + true + + + + History + + + + + + + Qt::Horizontal + + + + 10 + 10 + + + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 10 + 5 + + + + + + + + + 0 + 200 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + + + :/Resources/assets/stop_128x128.png + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + + + 120 + 120 + 120 + + + + + + + 120 + 120 + 120 + + + + + + + 120 + 120 + 120 + + + + + + + 151 + 151 + 151 + + + + + + + + + 12 + + + + No resource is currently selected + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + 151 + 151 + 151 + + + + + + + + + 120 + 120 + 120 + + + + + + + 120 + 120 + 120 + + + + + + + 120 + 120 + 120 + + + + + + + 151 + 151 + 151 + + + + + + + + Select a resource from the Resource overview or Allocation explorer by double-clicking the left mouse button on a resource + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + ColoredLegendGraphicsView + QGraphicsView +
qt_common/custom_widgets/colored_legend_graphics_view.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledTableView + QTableView +
qt_common/custom_widgets/scaled_table_view.h
+
+ + ScaledPushButton + QPushButton +
qt_common/custom_widgets/scaled_push_button.h
+
+ + RMVResourceTimeline + QWidget +
views/custom_widgets/rmv_resource_timeline.h
+
+ + RMVScaledDonutWidget + QWidget +
views/custom_widgets/rmv_scaled_donut_widget.h
+ 1 +
+ + RMVClickableTableView + ScaledTableView +
views/custom_widgets/rmv_clickable_table_view.h
+
+
+ + + + +
diff --git a/source/frontend/views/snapshot/resource_event_icons.cpp b/source/frontend/views/snapshot/resource_event_icons.cpp new file mode 100644 index 0000000..0c0116c --- /dev/null +++ b/source/frontend/views/snapshot/resource_event_icons.cpp @@ -0,0 +1,107 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the resource event icons. +//============================================================================= + +#include "views/snapshot/resource_event_icons.h" + +#include "qt_common/utils/scaling_manager.h" + +namespace rmv +{ + ResourceEventIcons::ResourceEventIcons() + { + } + + ResourceEventIcons::~ResourceEventIcons() + { + } + + void ResourceEventIcons::DrawIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color, ResourceIconShape shape) const + { + switch (shape) + { + case rmv::kIconShapeCircle: + DrawCircleIcon(painter, x_pos, y_pos, icon_size, color); + break; + + case rmv::kIconShapeTriangle: + DrawTriangleIcon(painter, x_pos, y_pos, icon_size, color, false); + break; + + case rmv::kIconShapeInvertedTriangle: + DrawTriangleIcon(painter, x_pos, y_pos, icon_size, color, true); + break; + + case rmv::kIconShapeSquare: + DrawSquareIcon(painter, x_pos, y_pos, icon_size, color); + break; + + case rmv::kIconShapeCross: + DrawCrossIcon(painter, x_pos, y_pos, icon_size, color); + break; + + default: + break; + } + } + + void ResourceEventIcons::DrawCircleIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color) const + { + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawEllipse(QRect(x_pos, y_pos - (icon_size / 2), icon_size, icon_size)); + } + + void ResourceEventIcons::DrawTriangleIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color, bool inverted) const + { + QPainterPath path; + + int tip; + int base; + if (!inverted) + { + tip = y_pos - (icon_size / 2); + base = tip + icon_size; + } + else + { + tip = y_pos + (icon_size / 2); + base = tip - icon_size; + } + + path.moveTo(x_pos, base); + path.lineTo(static_cast(x_pos) + (static_cast(icon_size) / 2.0), tip); + path.lineTo(static_cast(x_pos) + static_cast(icon_size), base); + path.lineTo(x_pos, base); + + painter->setPen(Qt::NoPen); + painter->fillPath(path, QBrush(color)); + } + + void ResourceEventIcons::DrawSquareIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color) const + { + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawRect(x_pos, y_pos - (icon_size / 2), icon_size, icon_size); + } + + void ResourceEventIcons::DrawCrossIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color) const + { + static const int kPenSize = icon_size / 4; + static const int kScaledIconSize = (icon_size - kPenSize); + static const int kScaledOffset = (kPenSize / 2); + + painter->setPen(QPen(color, kPenSize)); + DrawCross(painter, x_pos + kScaledOffset, y_pos, kScaledIconSize); + } + + void ResourceEventIcons::DrawCross(QPainter* painter, int x_pos, int y_pos, int icon_size) const + { + painter->drawLine(x_pos, y_pos - (icon_size / 2), x_pos + icon_size, y_pos + (icon_size / 2)); + painter->drawLine(x_pos + icon_size, y_pos - (icon_size / 2), x_pos, y_pos + (icon_size / 2)); + } + +} // namespace rmv diff --git a/source/frontend/views/snapshot/resource_event_icons.h b/source/frontend/views/snapshot/resource_event_icons.h new file mode 100644 index 0000000..75de0b3 --- /dev/null +++ b/source/frontend/views/snapshot/resource_event_icons.h @@ -0,0 +1,81 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the resource event icons. This is a helper class to draw +/// icons common to the resource timeline and the resource event table in the +/// resource details pane +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_RESOURCE_EVENT_ICONS_H_ +#define RMV_VIEWS_SNAPSHOT_RESOURCE_EVENT_ICONS_H_ + +#include +#include + +#include "models/snapshot/resource_details_model.h" + +namespace rmv +{ + class ResourceEventIcons + { + public: + /// Constructor. + ResourceEventIcons(); + + /// Destructor. + ~ResourceEventIcons(); + + /// Draw a resource icon. + /// \param painter Pointer to the Qt painter object. + /// \param x_pos The left-most position of where to start drawing the icon. + /// \param y_pos The mid point on the y-axis of where to start drawing the icon. + /// \param icon_size The size of the icon, in pixels, unscaled. + /// \param color The icon color. + /// \param shape The icon shape. + void DrawIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color, ResourceIconShape shape) const; + + private: + /// Draw a circle icon. + /// \param painter Pointer to the Qt painter object. + /// \param x_pos The left-most position of where to start drawing the icon. + /// \param y_pos The mid point on the y-axis of where to start drawing the icon. + /// \param icon_size The size of the icon, in pixels, unscaled. + /// \param color The icon color. + void DrawCircleIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color) const; + + /// Draw a triangle. + /// \param painter Pointer to the Qt painter object. + /// \param x_pos The left-most position of where to start drawing the icon. + /// \param y_pos The mid point on the y-axis of where to start drawing the icon. + /// \param icon_size The size of the icon, in pixels, unscaled. + /// \param color The icon color. + /// \param inverted Should the triangle be inverted. + void DrawTriangleIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color, bool inverted) const; + + /// Draw a square. + /// \param painter Pointer to the Qt painter object. + /// \param x_pos The left-most position of where to start drawing the icon. + /// \param y_pos The mid point on the y-axis of where to start drawing the icon. + /// \param icon_size The size of the icon, in pixels, unscaled. + /// \param color The icon color. + void DrawSquareIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color) const; + + /// Draw a cross icon. + /// \param painter Pointer to the Qt painter object. + /// \param x_pos The left-most position of where to start drawing the icon. + /// \param y_pos The mid point on the y-axis of where to start drawing the icon. + /// \param icon_size The size of the icon, in pixels, unscaled. + /// \param color The icon color. + void DrawCrossIcon(QPainter* painter, int x_pos, int y_pos, int icon_size, QColor color) const; + + /// Draw a cross. + /// \param painter Pointer to the Qt painter object. + /// \param x_pos The left-most position of where to start drawing the icon. + /// \param y_pos The mid point on the y-axis of where to start drawing the icon. + /// \param icon_size The size of the icon, in pixels, unscaled. + void DrawCross(QPainter* painter, int x_pos, int y_pos, int icon_size) const; + }; +} // namespace rmv + +#endif // RMV_VIEWS_SNAPSHOT_RESOURCE_EVENT_ICONS_H_ diff --git a/source/frontend/views/snapshot/resource_list_pane.cpp b/source/frontend/views/snapshot/resource_list_pane.cpp new file mode 100644 index 0000000..9362e9d --- /dev/null +++ b/source/frontend/views/snapshot/resource_list_pane.cpp @@ -0,0 +1,282 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Allocation List pane. +//============================================================================= + +#include "views/snapshot/resource_list_pane.h" + +#include +#include + +#include "qt_common/custom_widgets/double_slider_widget.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" +#include "rmt_data_set.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" +#include "rmt_resource_list.h" +#include "rmt_util.h" + +#include "models/message_manager.h" +#include "models/proxy_models/resource_proxy_model.h" +#include "models/resource_item_model.h" +#include "models/snapshot/resource_list_model.h" +#include "settings/rmv_settings.h" +#include "views/pane_manager.h" + +ResourceListPane::ResourceListPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::ResourceListPane) + , model_valid_(false) + , selected_resource_identifier_(0) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + model_ = new rmv::ResourceListModel(); + + model_->InitializeModel(ui_->total_resources_label_, rmv::kResourceListTotalResources, "text"); + model_->InitializeModel(ui_->total_size_label_, rmv::kResourceListTotalSize, "text"); + + model_->InitializeTableModel(ui_->resource_table_view_, 0, kResourceColumnCount); + + rmv::widget_util::InitMultiSelectComboBox(this, ui_->preferred_heap_combo_box_, rmv::text::kPreferredHeap); + rmv::widget_util::InitMultiSelectComboBox(this, ui_->resource_usage_combo_box_, rmv::text::kResourceUsage); + + preferred_heap_combo_box_model_ = new rmv::HeapComboBoxModel(); + preferred_heap_combo_box_model_->SetupHeapComboBox(ui_->preferred_heap_combo_box_); + connect(preferred_heap_combo_box_model_, &rmv::HeapComboBoxModel::FilterChanged, this, &ResourceListPane::HeapChanged); + + resource_usage_combo_box_model_ = new rmv::ResourceUsageComboBoxModel(); + resource_usage_combo_box_model_->SetupResourceComboBox(ui_->resource_usage_combo_box_); + connect(resource_usage_combo_box_model_, &rmv::ResourceUsageComboBoxModel::FilterChanged, this, &ResourceListPane::ResourceChanged); + + rmv::widget_util::InitGraphicsView(ui_->carousel_view_, ScalingManager::Get().Scaled(kCarouselItemHeight)); + + RMVCarouselConfig config = {}; + config.height = ui_->carousel_view_->height(); + config.data_type = kCarouselDataTypeRegular; + + carousel_ = new RMVCarousel(config); + ui_->carousel_view_->setScene(carousel_->Scene()); + + rmv::widget_util::InitCommonFilteringComponents(ui_->search_box_, ui_->size_slider_); + + connect(ui_->size_slider_, &DoubleSliderWidget::SpanChanged, this, &ResourceListPane::FilterBySizeSliderChanged); + 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); + + // 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); + + connect(&MessageManager::Get(), &MessageManager::ResourceSelected, this, &ResourceListPane::SelectResource); + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &ResourceListPane::OnScaleFactorChanged); +} + +ResourceListPane::~ResourceListPane() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &ResourceListPane::OnScaleFactorChanged); + + delete carousel_; + delete resource_usage_combo_box_model_; + delete preferred_heap_combo_box_model_; + delete model_; +} + +void ResourceListPane::OnScaleFactorChanged() +{ + // Carousel + ui_->carousel_view_->setFixedHeight(ScalingManager::Get().Scaled(kCarouselItemHeight)); +} + +void ResourceListPane::showEvent(QShowEvent* event) +{ + if (model_valid_ == false) + { + Refresh(); + } + QWidget::showEvent(event); +} + +void ResourceListPane::hideEvent(QHideEvent* event) +{ + QWidget::hideEvent(event); +} + +void ResourceListPane::Refresh() +{ + PopulateResourceTable(); + UpdateCarousel(); + + ResizeItems(); + + QString heap_filter_string = preferred_heap_combo_box_model_->GetFilterString(ui_->preferred_heap_combo_box_); + model_->UpdatePreferredHeapList(heap_filter_string); + QString resource_filter_string = resource_usage_combo_box_model_->GetFilterString(ui_->resource_usage_combo_box_); + model_->UpdateResourceUsageList(resource_filter_string); +} + +void ResourceListPane::OnTraceClose() +{ + preferred_heap_combo_box_model_->ResetHeapComboBox(ui_->preferred_heap_combo_box_); + resource_usage_combo_box_model_->ResetResourceComboBox(ui_->resource_usage_combo_box_); +} + +void ResourceListPane::Reset() +{ + model_->ResetModelValues(); + model_valid_ = false; + selected_resource_identifier_ = 0; + + ui_->size_slider_->SetLowerValue(0); + ui_->size_slider_->SetUpperValue(rmv::kSizeSliderRange); + ui_->search_box_->setText(""); + + carousel_->ClearData(); + carousel_->update(); +} + +void ResourceListPane::OpenSnapshot(RmtDataSnapshot* snapshot) +{ + Q_UNUSED(snapshot); + + if (this->isVisible()) + { + // This pane is visible so showEvent won't get called to update the resource table + // so update it now. + Refresh(); + } + else + { + // Indicate the model data is not valid so the table will be updated when showEvent + // is called + model_valid_ = false; + } +} + +void ResourceListPane::ResizeItems() +{ + if (carousel_ != nullptr) + { + carousel_->ResizeEvent(ui_->carousel_view_->width(), ui_->carousel_view_->height()); + } + + SetMaximumResourceTableHeight(); +} + +void ResourceListPane::resizeEvent(QResizeEvent* event) +{ + ResizeItems(); + QWidget::resizeEvent(event); +} + +void ResourceListPane::PopulateResourceTable() +{ + // create the Resource table. Only do this once when showing the pane for the first + // time for the current snapshot + // Prior to doing a table update, disable sorting since Qt is super slow about it + ui_->resource_table_view_->setSortingEnabled(false); + model_->Update(); + ui_->resource_table_view_->setSortingEnabled(true); + ui_->resource_table_view_->sortByColumn(kResourceColumnName, Qt::DescendingOrder); + ui_->resource_table_view_->horizontalHeader()->adjustSize(); + model_valid_ = true; + SelectResourceInTable(); + ScrollToSelectedResource(); +} + +void ResourceListPane::UpdateCarousel() +{ + carousel_->UpdateModel(); +} + +void ResourceListPane::SearchBoxChanged() +{ + model_->SearchBoxChanged(ui_->search_box_->text()); + SetMaximumResourceTableHeight(); +} + +void ResourceListPane::FilterBySizeSliderChanged(int min_value, int max_value) +{ + model_->FilterBySizeChanged(min_value, max_value); + SetMaximumResourceTableHeight(); +} + +void ResourceListPane::HeapChanged(bool checked) +{ + // rebuild the table depending on what the state of the combo box items is + RMT_UNUSED(checked); + + QString filter_string = preferred_heap_combo_box_model_->GetFilterString(ui_->preferred_heap_combo_box_); + model_->UpdatePreferredHeapList(filter_string); + SetMaximumResourceTableHeight(); +} + +void ResourceListPane::ResourceChanged(bool checked) +{ + // rebuild the table depending on what the state of the combo box items is + RMT_UNUSED(checked); + + QString filter_string = resource_usage_combo_box_model_->GetFilterString(ui_->resource_usage_combo_box_); + model_->UpdateResourceUsageList(filter_string); + SetMaximumResourceTableHeight(); +} + +void ResourceListPane::SelectResource(RmtResourceIdentifier resource_identifier) +{ + selected_resource_identifier_ = resource_identifier; + SelectResourceInTable(); +} + +void ResourceListPane::TableClicked(const QModelIndex& index) +{ + if (index.isValid() == true) + { + const RmtResourceIdentifier resource_identifier = model_->GetResourceProxyModel()->GetData(index.row(), kResourceColumnGlobalId); + emit MessageManager::Get().ResourceSelected(resource_identifier); + } +} + +void ResourceListPane::TableDoubleClicked(const QModelIndex& index) +{ + if (index.isValid() == true) + { + const RmtResourceIdentifier resource_identifier = model_->GetResourceProxyModel()->GetData(index.row(), kResourceColumnGlobalId); + emit MessageManager::Get().ResourceSelected(resource_identifier); + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotResourceDetails); + } +} + +void ResourceListPane::ScrollToSelectedResource() +{ + QItemSelectionModel* selected_item = ui_->resource_table_view_->selectionModel(); + if (selected_item->hasSelection()) + { + QModelIndexList item_list = selected_item->selectedRows(); + if (item_list.size() > 0) + { + // get the model index of the name column since column 0 (compare ID) is hidden and scrollTo + // doesn't appear to scroll on hidden columns + QModelIndex model_index = model_->GetResourceProxyModel()->index(item_list[0].row(), kResourceColumnName); + ui_->resource_table_view_->scrollTo(model_index, QAbstractItemView::ScrollHint::PositionAtTop); + } + } +} + +void ResourceListPane::SelectResourceInTable() +{ + if (selected_resource_identifier_ != 0) + { + // select the resource in the resource table + const QModelIndex& resource_index = model_->GetResourceProxyModel()->FindModelIndex(selected_resource_identifier_, kResourceColumnGlobalId); + if (resource_index.isValid() == true) + { + ui_->resource_table_view_->selectRow(resource_index.row()); + } + } +} \ No newline at end of file diff --git a/source/frontend/views/snapshot/resource_list_pane.h b/source/frontend/views/snapshot/resource_list_pane.h new file mode 100644 index 0000000..512c120 --- /dev/null +++ b/source/frontend/views/snapshot/resource_list_pane.h @@ -0,0 +1,126 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Allocation List pane. +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_RESOURCE_LIST_PANE_H_ +#define RMV_VIEWS_SNAPSHOT_RESOURCE_LIST_PANE_H_ + +#include "ui_resource_list_pane.h" + +#include "rmt_resource_list.h" + +#include "models/heap_combo_box_model.h" +#include "models/resource_usage_combo_box_model.h" +#include "models/snapshot/resource_list_model.h" +#include "util/widget_util.h" +#include "views/base_pane.h" +#include "views/custom_widgets/rmv_carousel.h" + +/// Class declaration. +class ResourceListPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit ResourceListPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~ResourceListPane(); + + /// Overridden show event. Fired when this pane is opened. + /// \param event the show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Overridden hide event. Fired when this pane is closed. + /// \param event the hide event object. + virtual void hideEvent(QHideEvent* event) Q_DECL_OVERRIDE; + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Clean up. + virtual void OnTraceClose() Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Open a snapshot. + /// \param snapshot The snapshot to open. + virtual void OpenSnapshot(RmtDataSnapshot* snapshot) Q_DECL_OVERRIDE; + +private slots: + /// Handle what happens when user changes the filter. + void SearchBoxChanged(); + + /// Slot to handle what happens when the 'filter by size' slider changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void FilterBySizeSliderChanged(int min_value, int max_value); + + /// Handle what happens when a checkbox in the heap dropdown is checked or unchecked. + /// \param checked Whether the checkbox is checked or unchecked. + void HeapChanged(bool checked); + + /// Handle what happens when a checkbox in the resource dropdown is checked or unchecked. + /// \param checked Whether the checkbox is checked or unchecked. + void ResourceChanged(bool checked); + + /// Resize child UI widgets when the DPI scale factor changes. + void OnScaleFactorChanged(); + + /// Handle what happens when an item in the resource table is clicked on. + /// \param index the model index of the item clicked on. + void TableClicked(const QModelIndex& index); + + /// Handle what happens when an item in the resource table is double-clicked on. + /// \param index the model index of the item clicked on. + void TableDoubleClicked(const QModelIndex& index); + + /// Select a resource on this pane. This is usually called when selecting a resource. + /// on a different pane to make sure the resource selection is propagated to all + /// interested panes. + /// \param resource_identifier the resource identifier of the resource to select. + void SelectResource(RmtResourceIdentifier resource_identifier); + + /// Slot to handle what happens after the resource list table is sorted. + /// Make sure the selected item (if there is one) is visible. + void ScrollToSelectedResource(); + +private: + /// Refresh what's visible on the UI. + void Refresh(); + + /// Resize relevant items. + void ResizeItems(); + + /// Populate the resource list table. + void PopulateResourceTable(); + + /// Update the carousel. + void UpdateCarousel(); + + /// Select the selected resource in the table and scroll to it if the resource is valid. + void SelectResourceInTable(); + + /// Helper function to set the maximum height of the table so it only contains rows with valid data. + inline void SetMaximumResourceTableHeight() + { + ui_->resource_table_view_->setMaximumHeight(rmv::widget_util::GetTableHeight(ui_->resource_table_view_, model_->GetResourceProxyModel()->rowCount())); + } + + Ui::ResourceListPane* ui_; ///< Pointer to the Qt UI design. + rmv::ResourceListModel* model_; ///< Container class for the widget models. + rmv::HeapComboBoxModel* preferred_heap_combo_box_model_; ///< The preferred heap combo box model. + rmv::ResourceUsageComboBoxModel* resource_usage_combo_box_model_; ///< The resource usage model. + RMVCarousel* carousel_; ///< the carousel on the top. + bool model_valid_; ///< Is the model data valid. + RmtResourceIdentifier selected_resource_identifier_; ///< Identifier of the selected resource. +}; + +#endif // RMV_VIEWS_SNAPSHOT_RESOURCE_LIST_PANE_H_ diff --git a/source/frontend/views/snapshot/resource_list_pane.ui b/source/frontend/views/snapshot/resource_list_pane.ui new file mode 100644 index 0000000..ddab43b --- /dev/null +++ b/source/frontend/views/snapshot/resource_list_pane.ui @@ -0,0 +1,299 @@ + + + ResourceListPane + + + + 0 + 0 + 911 + 457 + + + + + + + true + + + + + 0 + 0 + 891 + 437 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + PushButton + + + + + + + PushButton + + + + + + + + 0 + 0 + + + + Filter by size: + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + 260 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 40 + 5 + + + + QSizePolicy::MinimumExpanding + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Total resources: + + + + + + + + 0 + 0 + + + + {0} + + + + + + + + 0 + 0 + + + + | + + + + + + + + 0 + 0 + + + + Total size: + + + + + + + + 0 + 0 + + + + {0} + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + ArrowIconComboBox + QWidget +
qt_common/custom_widgets/arrow_icon_combo_box.h
+ 1 +
+ + DoubleSliderWidget + QSlider +
qt_common/custom_widgets/double_slider_widget.h
+
+ + GraphicsView + QGraphicsView +
qt_common/custom_widgets/graphics_view.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + RMVClickableTableView + ScaledTableView +
views/custom_widgets/rmv_clickable_table_view.h
+
+ + TextSearchWidget + QLineEdit +
qt_common/custom_widgets/text_search_widget.h
+
+
+ + + + +
diff --git a/source/frontend/views/snapshot/resource_overview_pane.cpp b/source/frontend/views/snapshot/resource_overview_pane.cpp new file mode 100644 index 0000000..b473cb4 --- /dev/null +++ b/source/frontend/views/snapshot/resource_overview_pane.cpp @@ -0,0 +1,440 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of the Resource Overview pane. +//============================================================================= + +#include "views/snapshot/resource_overview_pane.h" + +#include + +#include "qt_common/custom_widgets/colored_legend_scene.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" +#include "rmt_print.h" +#include "rmt_resource_list.h" + +#include "models/heap_combo_box_model.h" +#include "models/message_manager.h" +#include "models/resource_usage_combo_box_model.h" +#include "models/snapshot/resource_overview_model.h" +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" +#include "util/widget_util.h" +#include "views/custom_widgets/rmv_colored_checkbox.h" +#include "views/pane_manager.h" + +ResourceOverviewPane::ResourceOverviewPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::ResourceOverviewPane) + , selected_resource_(nullptr) + , resource_details_(nullptr) + , allocation_details_scene_(nullptr) + , slice_mode_map_{} +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + model_ = new rmv::ResourceOverviewModel(); + + model_->InitializeModel(ui_->total_available_size_value_, rmv::kResourceOverviewTotalAvailableSize, "text"); + model_->InitializeModel(ui_->total_allocated_and_used_value_, rmv::kResourceOverviewTotalAllocatedAndUsed, "text"); + model_->InitializeModel(ui_->total_allocated_and_unused_value_, rmv::kResourceOverviewTotalAllocatedAndUnused, "text"); + model_->InitializeModel(ui_->allocations_value_, rmv::kResourceOverviewAllocationCount, "text"); + model_->InitializeModel(ui_->resources_value_, rmv::kResourceOverviewResourceCount, "text"); + + rmv::widget_util::InitMultiSelectComboBox(this, ui_->preferred_heap_combo_box_, rmv::text::kPreferredHeap); + rmv::widget_util::InitMultiSelectComboBox(this, ui_->actual_heap_combo_box_, rmv::text::kActualHeap); + rmv::widget_util::InitMultiSelectComboBox(this, ui_->resource_usage_combo_box_, rmv::text::kResourceUsage); + rmv::widget_util::InitSingleSelectComboBox(this, ui_->slicing_button_one_, "", false, "Level 1: "); + rmv::widget_util::InitSingleSelectComboBox(this, ui_->slicing_button_two_, "", false, "Level 2: "); + rmv::widget_util::InitSingleSelectComboBox(this, ui_->slicing_button_three_, "", false, "Level 3: "); + + // Hide actual heap as it's not that useful ATM + ui_->actual_heap_combo_box_->hide(); + + tree_map_models_.preferred_heap_model = new rmv::HeapComboBoxModel(); + tree_map_models_.actual_heap_model = new rmv::HeapComboBoxModel(); + tree_map_models_.resource_usage_model = new rmv::ResourceUsageComboBoxModel(); + colorizer_ = new Colorizer(); + + tree_map_models_.preferred_heap_model->SetupHeapComboBox(ui_->preferred_heap_combo_box_); + connect(tree_map_models_.preferred_heap_model, &rmv::HeapComboBoxModel::FilterChanged, this, &ResourceOverviewPane::ComboFiltersChanged); + + tree_map_models_.actual_heap_model->SetupHeapComboBox(ui_->actual_heap_combo_box_); + connect(tree_map_models_.actual_heap_model, &rmv::HeapComboBoxModel::FilterChanged, this, &ResourceOverviewPane::ComboFiltersChanged); + + tree_map_models_.resource_usage_model->SetupResourceComboBox(ui_->resource_usage_combo_box_); + connect(tree_map_models_.resource_usage_model, &rmv::ResourceUsageComboBoxModel::FilterChanged, this, &ResourceOverviewPane::ComboFiltersChanged); + + // Set up a list of required coloring modes, in order + // The list is terminated with COLOR_MODE_COUNT + static const Colorizer::ColorMode mode_list[] = { + Colorizer::kColorModeResourceUsageType, + Colorizer::kColorModePreferredHeap, + Colorizer::kColorModeAllocationAge, + Colorizer::kColorModeResourceCreateAge, + Colorizer::kColorModeResourceBindAge, + Colorizer::kColorModeResourceGUID, + Colorizer::kColorModeResourceCPUMapped, + Colorizer::kColorModeNotAllPreferred, + Colorizer::kColorModeAliasing, + Colorizer::kColorModeCommitType, + Colorizer::kColorModeCount, + }; + + // Initialize the "color by" UI elements. Set up the combo box, legends and signals etc + colorizer_->Initialize(parent, ui_->color_combo_box_, ui_->legends_view_, mode_list); + + ui_->tree_map_view_->SetModels(model_, &tree_map_models_, colorizer_); + + struct SliceMapping + { + int slice_id; + QString slice_text; + }; + + static const std::vector slice_map = { + {RMVTreeMapBlocks::kSliceTypeNone, "no slicing"}, + {RMVTreeMapBlocks::kSliceTypeResourceUsageType, "slice by resource usage"}, + {RMVTreeMapBlocks::kSliceTypePreferredHeap, "slice by preferred heap"}, + {RMVTreeMapBlocks::kSliceTypeAllocationAge, "slice by allocation age"}, + {RMVTreeMapBlocks::kSliceTypeResourceCreateAge, "slice by resource create time"}, + {RMVTreeMapBlocks::kSliceTypeResourceBindAge, "slice by resource bind time"}, + {RMVTreeMapBlocks::kSliceTypeVirtualAllocation, "slice by virtual allocation"}, + //{RMVTreeMapBlocks::kSliceTypeActualHeap, "slice by actual heap"}, + {RMVTreeMapBlocks::kSliceTypeCpuMapped, "slice by CPU mapped"}, + //{RMVTreeMapBlocks::kSliceTypeResourceOwner, "slice by resource owner"}, + {RMVTreeMapBlocks::kSliceTypeInPreferredHeap, "slice by not all in preferred heap"} + //{RMVTreeMapBlocks::kSliceTypeResourceCommitType, "slice by commit type"} + }; + + int index = 0; + for (auto it = slice_map.begin(); it != slice_map.end(); ++it) + { + const QString& slice_string = (*it).slice_text; + ui_->slicing_button_one_->AddItem(slice_string); + ui_->slicing_button_two_->AddItem(slice_string); + ui_->slicing_button_three_->AddItem(slice_string); + + // add the indices to a map of combo box index to slice type + slice_mode_map_[index] = (*it).slice_id; + index++; + } + + ui_->resource_details_view_->setFrameStyle(QFrame::NoFrame); + ui_->resource_details_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_->resource_details_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_->resource_details_view_->setFixedHeight(ScalingManager::Get().Scaled(kResourceDetailsHeight)); + + allocation_details_scene_ = new QGraphicsScene(); + ui_->resource_details_view_->setScene(allocation_details_scene_); + + RMVResourceDetailsConfig config = {}; + config.width = ui_->resource_details_view_->width(); + config.height = ui_->resource_details_view_->height(); + config.resource_valid = false; + config.allocation_thumbnail = true; + config.colorizer = colorizer_; + + resource_details_ = new RMVResourceDetails(config); + allocation_details_scene_->addItem(resource_details_); + + ui_->resource_details_checkbox_->Initialize(true, rmv::kCheckboxEnableColor, Qt::black); + + rmv::widget_util::InitDoubleSlider(ui_->size_slider_); + + connect(ui_->size_slider_, &DoubleSliderWidget::SpanChanged, this, &ResourceOverviewPane::FilterBySizeSliderChanged); + + connect(ui_->resource_details_checkbox_, &RMVColoredCheckbox::Clicked, this, &ResourceOverviewPane::ToggleResourceDetails); + connect(ui_->slicing_button_one_, &ArrowIconComboBox::SelectionChanged, this, &ResourceOverviewPane::SlicingLevelChanged); + connect(ui_->slicing_button_two_, &ArrowIconComboBox::SelectionChanged, this, &ResourceOverviewPane::SlicingLevelChanged); + connect(ui_->slicing_button_three_, &ArrowIconComboBox::SelectionChanged, this, &ResourceOverviewPane::SlicingLevelChanged); + connect(ui_->tree_map_view_->BlocksWidget(), &RMVTreeMapBlocks::ResourceSelected, this, &ResourceOverviewPane::SelectedResource); + connect(ui_->tree_map_view_->BlocksWidget(), &RMVTreeMapBlocks::UnboundResourceSelected, this, &ResourceOverviewPane::SelectedUnboundResource); + connect(ui_->color_combo_box_, &ArrowIconComboBox::SelectionChanged, this, &ResourceOverviewPane::ColorModeChanged); + connect(&MessageManager::Get(), &MessageManager::ResourceSelected, this, &ResourceOverviewPane::SelectResource); + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &ResourceOverviewPane::OnScaleFactorChanged); +} + +ResourceOverviewPane::~ResourceOverviewPane() +{ + disconnect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &ResourceOverviewPane::OnScaleFactorChanged); + + delete tree_map_models_.preferred_heap_model; + delete tree_map_models_.actual_heap_model; + delete tree_map_models_.resource_usage_model; + delete colorizer_; +} + +void ResourceOverviewPane::Refresh() +{ + model_->Update(); +} + +void ResourceOverviewPane::showEvent(QShowEvent* event) +{ + ResizeItems(); + QWidget::showEvent(event); +} + +void ResourceOverviewPane::resizeEvent(QResizeEvent* event) +{ + ResizeItems(); + QWidget::resizeEvent(event); +} + +int ResourceOverviewPane::GetRowForSliceType(int slice_type) +{ + for (int i = 0; i < RMVTreeMapBlocks::SliceType::kSliceTypeCount; i++) + { + if (slice_mode_map_[i] == slice_type) + { + return i; + } + } + return 0; +} + +void ResourceOverviewPane::Reset() +{ + selected_resource_ = nullptr; + resource_details_->UpdateResource(selected_resource_); + + ui_->slicing_button_one_->SetSelectedRow(GetRowForSliceType(RMVTreeMapBlocks::kSliceTypePreferredHeap)); + ui_->slicing_button_two_->SetSelectedRow(GetRowForSliceType(RMVTreeMapBlocks::kSliceTypeVirtualAllocation)); + ui_->slicing_button_three_->SetSelectedRow(GetRowForSliceType(RMVTreeMapBlocks::kSliceTypeResourceUsageType)); + + tree_map_models_.preferred_heap_model->ResetHeapComboBox(ui_->preferred_heap_combo_box_); + tree_map_models_.actual_heap_model->ResetHeapComboBox(ui_->actual_heap_combo_box_); + tree_map_models_.resource_usage_model->ResetResourceComboBox(ui_->resource_usage_combo_box_); + + ui_->color_combo_box_->SetSelectedRow(0); + colorizer_->ApplyColorMode(); + ui_->size_slider_->SetLowerValue(0); + ui_->size_slider_->SetUpperValue(rmv::kSizeSliderRange); + + UpdateDetailsTitle(); +} + +void ResourceOverviewPane::ChangeColoring() +{ + ui_->tree_map_view_->UpdateColorCache(); + resource_details_->UpdateResource(selected_resource_); + colorizer_->UpdateLegends(); + Refresh(); +} + +void ResourceOverviewPane::OpenSnapshot(RmtDataSnapshot* snapshot) +{ + Q_UNUSED(snapshot); + + selected_resource_ = nullptr; + resource_details_->UpdateResource(selected_resource_); + UpdateDetailsTitle(); + + model_->Update(); + UpdateSlicingLevel(); + UpdateComboFilters(); + ui_->tree_map_view_->UpdateTreeMap(); +} + +void ResourceOverviewPane::ToggleResourceDetails() +{ + if (ui_->resource_details_checkbox_->isChecked()) + { + ui_->resource_details_->show(); + } + else + { + ui_->resource_details_->hide(); + } + + UpdateDetailsTitle(); +} + +void ResourceOverviewPane::UpdateComboFilters() +{ + tree_map_models_.preferred_heap_model->SetupState(ui_->preferred_heap_combo_box_); + tree_map_models_.actual_heap_model->SetupState(ui_->actual_heap_combo_box_); + tree_map_models_.resource_usage_model->SetupState(ui_->resource_usage_combo_box_); +} + +void ResourceOverviewPane::ComboFiltersChanged(bool checked) +{ + RMT_UNUSED(checked); + + UpdateComboFilters(); + + selected_resource_ = nullptr; + resource_details_->UpdateResource(selected_resource_); + UpdateDetailsTitle(); + ui_->tree_map_view_->UpdateTreeMap(); +} + +void ResourceOverviewPane::UpdateSlicingLevel() +{ + QVector slicingTypes; + + RMVTreeMapBlocks::SliceType typeLevelOne = (RMVTreeMapBlocks::SliceType)slice_mode_map_[ui_->slicing_button_one_->CurrentRow()]; + RMVTreeMapBlocks::SliceType typeLevelTwo = (RMVTreeMapBlocks::SliceType)slice_mode_map_[ui_->slicing_button_two_->CurrentRow()]; + RMVTreeMapBlocks::SliceType typeLevelThree = (RMVTreeMapBlocks::SliceType)slice_mode_map_[ui_->slicing_button_three_->CurrentRow()]; + + if (typeLevelOne || typeLevelTwo || typeLevelThree) + { + if (typeLevelOne) + { + slicingTypes.push_back(typeLevelOne); + } + if (typeLevelTwo) + { + slicingTypes.push_back(typeLevelTwo); + } + if (typeLevelThree) + { + slicingTypes.push_back(typeLevelThree); + } + } + + ui_->tree_map_view_->UpdateSliceTypes(slicingTypes); +} + +void ResourceOverviewPane::SlicingLevelChanged() +{ + UpdateSlicingLevel(); + ui_->tree_map_view_->UpdateTreeMap(); +} + +void ResourceOverviewPane::ColorModeChanged() +{ + ChangeColoring(); +} + +void ResourceOverviewPane::SelectedResource(RmtResourceIdentifier resource_identifier, bool broadcast, bool navigate_to_pane) +{ + if (broadcast == true) + { + emit MessageManager::Get().ResourceSelected(resource_identifier); + } + else + { + SelectResource(resource_identifier); + } + + if (navigate_to_pane == true) + { + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotResourceDetails); + } +} + +void ResourceOverviewPane::SelectedUnboundResource(const RmtResource* unbound_resource, bool broadcast, bool navigate_to_pane) +{ + if (broadcast == true) + { + if (unbound_resource != nullptr) + { + emit MessageManager::Get().UnboundResourceSelected(unbound_resource->bound_allocation); + } + } + SelectUnboundResource(unbound_resource); + emit MessageManager::Get().ResourceSelected(0); + + if (navigate_to_pane == true) + { + emit MessageManager::Get().NavigateToPane(rmv::kPaneSnapshotAllocationExplorer); + } +} + +void ResourceOverviewPane::SelectResource(RmtResourceIdentifier resource_identifier) +{ + if (resource_identifier == 0) + { + return; + } + + ui_->tree_map_view_->SelectResource(resource_identifier); + + const TraceManager& trace_manager = TraceManager::Get(); + RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + + if (trace_manager.DataSetValid() && open_snapshot != nullptr) + { + selected_resource_ = nullptr; + + const RmtErrorCode error_code = RmtResourceListGetResourceByResourceId(&open_snapshot->resource_list, resource_identifier, &selected_resource_); + if (error_code == RMT_OK) + { + resource_details_->UpdateResource(selected_resource_); + } + + UpdateDetailsTitle(); + } +} + +void ResourceOverviewPane::SelectUnboundResource(const RmtResource* unbound_resource) +{ + if (unbound_resource == nullptr) + { + return; + } + + resource_details_->UpdateResource(unbound_resource); + UpdateDetailsTitle(); +} + +void ResourceOverviewPane::FilterBySizeSliderChanged(int min_value, int max_value) +{ + model_->FilterBySizeChanged(min_value, max_value); + ui_->tree_map_view_->UpdateTreeMap(); +} + +void ResourceOverviewPane::UpdateDetailsTitle() +{ + if (selected_resource_ == nullptr) + { + ui_->resource_name_label_->setText("Select a resource"); + + if (ui_->resource_details_checkbox_->isChecked()) + { + ui_->resource_name_label_minimized_->setText(""); + } + else + { + ui_->resource_name_label_minimized_->setText("Select a resource"); + } + } + else + { + ui_->resource_name_label_->setText(selected_resource_->name); + + if (ui_->resource_details_checkbox_->isChecked()) + { + ui_->resource_name_label_minimized_->setText(""); + } + else + { + ui_->resource_name_label_minimized_->setText(selected_resource_->name); + } + } +} + +void ResourceOverviewPane::ResizeItems() +{ + if (allocation_details_scene_ != nullptr) + { + const QRectF scene_rect = QRectF(0, 0, ui_->resource_details_view_->width(), ui_->resource_details_view_->height()); + + allocation_details_scene_->setSceneRect(scene_rect); + + resource_details_->UpdateDimensions(ui_->resource_details_view_->width(), ui_->resource_details_view_->height()); + } +} + +void ResourceOverviewPane::OnScaleFactorChanged() +{ + ui_->resource_details_view_->setFixedHeight(ScalingManager::Get().Scaled(kResourceDetailsHeight)); +} diff --git a/source/frontend/views/snapshot/resource_overview_pane.h b/source/frontend/views/snapshot/resource_overview_pane.h new file mode 100644 index 0000000..58305e1 --- /dev/null +++ b/source/frontend/views/snapshot/resource_overview_pane.h @@ -0,0 +1,133 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Resource Overview pane. +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_RESOURCE_OVERVIEW_PANE_H_ +#define RMV_VIEWS_SNAPSHOT_RESOURCE_OVERVIEW_PANE_H_ + +#include "ui_resource_overview_pane.h" + +#include + +#include "rmt_resource_list.h" + +#include "models/snapshot/resource_overview_model.h" +#include "views/base_pane.h" +#include "views/colorizer.h" +#include "views/custom_widgets/rmv_resource_details.h" +#include "views/custom_widgets/rmv_tree_map_blocks.h" + +/// Class declaration. +class ResourceOverviewPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit ResourceOverviewPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~ResourceOverviewPane(); + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Overridden showEvent handler. + /// \param event The show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + + /// Open a snapshot. + /// \param snapshot_id snapshot to open. + virtual void OpenSnapshot(RmtDataSnapshot* snapshot) Q_DECL_OVERRIDE; + +private slots: + /// Update parts of the UI when the scale factor changes. + void OnScaleFactorChanged(); + + /// Select a resource on this pane. This is usually called when selecting a resource + /// on a different pane to make sure the resource selection is propagated to all + /// interested panes. + /// \param resource_identifier the resource identifier of the resource to select. + void SelectResource(RmtResourceIdentifier resource_identifier); + + /// Slot to handle what happens when a resource has been selected. This can be used to broadcast the resource + /// selection to any panes listening for the signal so they can also update their selected resource. + /// \param resource_identifier the resource Identifier. + /// \param broadcast If true, emit a signal to select the resource on any listening panes, otherwise call + /// SelectResource() directly. + /// \param navigate_to_pane If true, navigate to the resource details pane. + void SelectedResource(RmtResourceIdentifier resource_identifier, bool broadcast, bool navigate_to_pane); + + /// Slot to handle what happens when an unbound resource has been selected. In this case, need to use + /// the allocation. + /// \param allocation Pointer to the allocation. + /// \param broadcast If true, emit a signal to select the allocation on any listening panes. + /// \param navigate_to_pane If true, navigate to the allocation explorer pane. + void SelectedUnboundResource(const RmtResource* unbound_resource, bool broadcast, bool navigate_to_pane); + + /// Show or hide the resource details. + void ToggleResourceDetails(); + + /// Handle what happens when a checkbox in one of the filter dropdowns is checked or unchecked. + /// \param checked Whether the checkbox is checked or unchecked. + void ComboFiltersChanged(bool checked); + + /// The slicing level changed. + void SlicingLevelChanged(); + + /// Handle what happens when the color mode changes. + void ColorModeChanged(); + + /// Handle what happens when the size slider range changes. + /// \param min_value Minimum value of span. + /// \param max_value Maximum value of span. + void FilterBySizeSliderChanged(int min_value, int max_value); + +private: + /// Refresh what's visible on the UI. + void Refresh(); + + /// Select an unbound resource. + /// \param unbound_resource The unbound resource selected. + void SelectUnboundResource(const RmtResource* unbound_resource); + + /// Update the title for the details section. + void UpdateDetailsTitle(); + + /// Resize all relevant UI items. + void ResizeItems(); + + /// Get the row in the slicing combo box for a slice type. Slice types are + /// not in the same ordering as the enum and some slicing modes are disabled. + int GetRowForSliceType(int slice_type); + + /// Update the combo box filters. Read the values from the combo box UI and + /// inform the treeview model. + void UpdateComboFilters(); + + /// Update the slicing level. Read the values from the combo box UI and + /// inform the treeview model. + void UpdateSlicingLevel(); + + Ui::ResourceOverviewPane* ui_; ///< Pointer to the Qt UI design. + rmv::ResourceOverviewModel* model_; ///< Container class for the widget models. + const RmtResource* selected_resource_; ///< Pointer to selected allocation. + RMVResourceDetails* resource_details_; ///< Pointer to allocation details section at the bottom. + QGraphicsScene* allocation_details_scene_; ///< The Qt scene for allocation details at the bottom. + TreeMapModels tree_map_models_; ///< The models needed for the tree map. + Colorizer* colorizer_; ///< The colorizer used by the 'color by' combo box. + int slice_mode_map_[RMVTreeMapBlocks::SliceType::kSliceTypeCount]; ///< The mapping of a slicing combo box index to slicing mode. +}; + +#endif // RMV_VIEWS_SNAPSHOT_RESOURCE_OVERVIEW_PANE_H_ diff --git a/source/frontend/views/snapshot/resource_overview_pane.ui b/source/frontend/views/snapshot/resource_overview_pane.ui new file mode 100644 index 0000000..f76ca05 --- /dev/null +++ b/source/frontend/views/snapshot/resource_overview_pane.ui @@ -0,0 +1,567 @@ + + + ResourceOverviewPane + + + + 0 + 0 + 1318 + 981 + + + + + + + true + + + + + 0 + 0 + 1298 + 961 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + PushButton + + + + + + + PushButton + + + + + + + PushButton + + + + + + + Qt::Vertical + + + + + + + + + + + + + + Qt::Horizontal + + + + 1334 + 20 + + + + + + + + Qt::Vertical + + + + + + + PushButton + + + + + + + PushButton + + + + + + + PushButton + + + + + + + + + + + 0 + 0 + + + + Double-click a resource to view its details. + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 20 + + + + + + 0 + 0 + + + + + 75 + true + + + + Total available size + + + + + + + + 0 + 0 + + + + {0} + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Total allocated and bound + + + + + + + + 0 + 0 + + + + {0} + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Total allocated and unbound + + + + + + + + 0 + 0 + + + + {0} + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Allocations + + + + + + + + 0 + 0 + + + + {0} + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Resources + + + + + + + + 0 + 0 + + + + {0} + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Filter by size: + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 75 + true + + + + Select a resource + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 75 + true + + + + + + + + + + + Qt::Horizontal + + + + 1300 + 20 + + + + + + + + + 0 + 0 + + + + Resource details + + + + + + + + + + + + + + + ArrowIconComboBox + QPushButton +
qt_common/custom_widgets/arrow_icon_combo_box.h
+
+ + ColoredLegendGraphicsView + QGraphicsView +
qt_common/custom_widgets/colored_legend_graphics_view.h
+
+ + DoubleSliderWidget + QSlider +
qt_common/custom_widgets/double_slider_widget.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + RMVTreeMapView + QGraphicsView +
views/custom_widgets/rmv_tree_map_view.h
+
+ + RMVColoredCheckbox + QCheckBox +
views/custom_widgets/rmv_colored_checkbox.h
+
+
+ + + + +
diff --git a/source/frontend/views/snapshot/snapshot_start_pane.cpp b/source/frontend/views/snapshot/snapshot_start_pane.cpp new file mode 100644 index 0000000..b2e6ef4 --- /dev/null +++ b/source/frontend/views/snapshot/snapshot_start_pane.cpp @@ -0,0 +1,88 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Snapshot start pane. +//============================================================================= + +#include "views/snapshot/snapshot_start_pane.h" + +#include "rmt_data_snapshot.h" + +#include "qt_common/utils/scaling_manager.h" + +#include "models/message_manager.h" +#include "settings/rmv_settings.h" +#include "util/widget_util.h" +#include "views/pane_manager.h" + +static const qreal kSceneMargin = 10.0; + +SnapshotStartPane::SnapshotStartPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::SnapshotStartPane) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + ui_->graphics_view_->setFixedWidth(ScalingManager::Get().Scaled(kCircleDiameter)); + rmv::widget_util::InitGraphicsView(ui_->graphics_view_, ScalingManager::Get().Scaled(kCircleDiameter)); + + scene_ = new QGraphicsScene(); + ui_->graphics_view_->setScene(scene_); + + RMVCameraSnapshotWidgetConfig config = {}; + config.height = ui_->graphics_view_->height(); + config.width = ui_->graphics_view_->width(); + config.margin = kSceneMargin; + + config.base_color = RMVSettings::Get().GetColorSnapshotViewed(); + snapshot_widget_ = new RMVCameraSnapshotWidget(config); + + scene_->addItem(snapshot_widget_); + + connect(&ScalingManager::Get(), &ScalingManager::ScaleFactorChanged, this, &SnapshotStartPane::OnScaleFactorChanged); +} + +SnapshotStartPane::~SnapshotStartPane() +{ + delete scene_; +} + +void SnapshotStartPane::resizeEvent(QResizeEvent* event) +{ + ResizeGraphicsView(); + QWidget::resizeEvent(event); +} + +void SnapshotStartPane::ResizeGraphicsView() +{ + const qreal circle_diameter = kCircleDiameter - kSceneMargin * 2.0; + + snapshot_widget_->UpdateDimensions(circle_diameter, circle_diameter); + + QRectF scene_rect = scene_->itemsBoundingRect(); + ui_->graphics_view_->setSceneRect(scene_rect); + ui_->graphics_view_->setFixedSize(scene_rect.toRect().size()); +} + +void SnapshotStartPane::OnScaleFactorChanged() +{ + ResizeGraphicsView(); +} + +void SnapshotStartPane::Reset() +{ + snapshot_widget_->update(); +} + +void SnapshotStartPane::ChangeColoring() +{ + snapshot_widget_->UpdateBaseColor(RMVSettings::Get().GetColorSnapshotViewed()); +} + +void SnapshotStartPane::OpenSnapshot(RmtDataSnapshot* snapshot) +{ + snapshot_widget_->UpdateName(QString(snapshot->name)); +} diff --git a/source/frontend/views/snapshot/snapshot_start_pane.h b/source/frontend/views/snapshot/snapshot_start_pane.h new file mode 100644 index 0000000..712f1ab --- /dev/null +++ b/source/frontend/views/snapshot/snapshot_start_pane.h @@ -0,0 +1,58 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Snapshot start pane. +//============================================================================= + +#ifndef RMV_VIEWS_SNAPSHOT_SNAPSHOT_START_PANE_H_ +#define RMV_VIEWS_SNAPSHOT_SNAPSHOT_START_PANE_H_ + +#include "ui_snapshot_start_pane.h" + +#include + +#include "views/base_pane.h" +#include "views/custom_widgets/rmv_camera_snapshot_widget.h" + +/// Class declaration +class SnapshotStartPane : public BasePane +{ + Q_OBJECT + +public: + /// constructor. + /// \param parent The widget's parent. + explicit SnapshotStartPane(QWidget* parent = nullptr); + + /// Destructor. + virtual ~SnapshotStartPane(); + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + + /// Open a snapshot. + /// \param snapshot Pointer to the snapshot to open. + virtual void OpenSnapshot(RmtDataSnapshot* snapshot) Q_DECL_OVERRIDE; + +private slots: + /// Callback for when the DPI Scale changes. + void OnScaleFactorChanged(); + +private: + /// Resizes the GraphicsView to fit the scene. + void ResizeGraphicsView(); + + Ui::SnapshotStartPane* ui_; ///< Pointer to the Qt UI design. + QGraphicsScene* scene_; ///< Qt scene for the camera drawing. + RMVCameraSnapshotWidget* snapshot_widget_; ///< Circle with camera. +}; + +#endif // RMV_VIEWS_SNAPSHOT_SNAPSHOT_START_PANE_H_ diff --git a/source/frontend/views/snapshot/snapshot_start_pane.ui b/source/frontend/views/snapshot/snapshot_start_pane.ui new file mode 100644 index 0000000..3f98c54 --- /dev/null +++ b/source/frontend/views/snapshot/snapshot_start_pane.ui @@ -0,0 +1,183 @@ + + + SnapshotStartPane + + + + 0 + 0 + 1270 + 963 + + + + + + + true + + + + + 0 + 0 + 1246 + 939 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + 20 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + You haven't selected a snapshot! + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 700 + 185 + + + + <html><head/><body><p><span style=" color:#5a5a5a;">To view a snapshot:<br/>1. Go to the TIMELINE tab.<br/>2. Right click on the timeline and select 'add snaphot'.<br/>3. Double-click the snapshot from the timeline or the table to view it.</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + + + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+
+ + + + +
diff --git a/source/frontend/views/start/about_pane.cpp b/source/frontend/views/start/about_pane.cpp new file mode 100644 index 0000000..3c58e86 --- /dev/null +++ b/source/frontend/views/start/about_pane.cpp @@ -0,0 +1,209 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV system information about pane. +//============================================================================= + +#include "views/start/about_pane.h" + +#include +#include +#include +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "util/version.h" +#include "util/widget_util.h" +#include "views/main_window.h" + +AboutPane::AboutPane(MainWindow* parent) + : BasePane(parent) + , ui_(new Ui::AboutPane) + , check_for_updates_pending_dialog_(nullptr) + , check_for_updates_dialog_label_(nullptr) + , check_for_updates_thread_(nullptr) +{ + ui_->setupUi(this); + + // Set label text for version information + ui_->label_version_data_->setText(RMV_VERSION_STRING); + ui_->label_build_data_->setText(QString::number(RMV_BUILD_NUMBER)); + ui_->label_build_date_data_->setText(RMV_BUILD_DATE_STRING); + + // Set white background for this pane + rmv::widget_util::SetWidgetBackgroundColor(this, Qt::white); + + InitButton(ui_->open_getting_started_button_); + InitButton(ui_->open_rmv_help_button_); + InitButton(ui_->read_license_button_); + InitButton(ui_->check_for_updates_button_); + + // Hookup buttons + connect(ui_->open_getting_started_button_, &QPushButton::clicked, this, &AboutPane::OpenTraceHelp); + connect(ui_->open_rmv_help_button_, &QPushButton::clicked, this, &AboutPane::OpenRmvHelp); + connect(ui_->read_license_button_, &QPushButton::clicked, this, &AboutPane::OpenRmvLicense); + connect(ui_->check_for_updates_button_, &QPushButton::clicked, this, &AboutPane::CheckForUpdates); +} + +AboutPane::~AboutPane() +{ +} + +void AboutPane::InitButton(ScaledPushButton*& button) +{ + button->setCursor(Qt::PointingHandCursor); +} + +void AboutPane::OpenHtmlFile(const QString& html_file) +{ + // Get the file info + QFileInfo file_info(QCoreApplication::applicationDirPath() + html_file); + + // Check to see if the file is not a directory and that it exists + if (file_info.isFile() && file_info.exists()) + { + QDesktopServices::openUrl(QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + html_file)); + } + else + { + // The selected html file is missing on the disk so display a message box stating so + const QString text = rmv::text::kMissingRmvHelpFile + html_file; + QtCommon::QtUtils::ShowMessageBox(this, QMessageBox::Ok, QMessageBox::Critical, rmv::text::kMissingRmvHelpFile, text); + } +} + +void AboutPane::OpenRmvLicense() +{ + OpenHtmlFile(rmv::text::kRmvLicenseFile); +} + +void AboutPane::OpenRmvHelp() +{ + OpenHtmlFile(rmv::text::kRmvHelpFile); +} + +void AboutPane::OpenTraceHelp() +{ + OpenHtmlFile(rmv::text::kTraceHelpFile); +} + +void AboutPane::CheckForUpdates() +{ + // Don't allow checking for updates if there is already one in progress. + if (check_for_updates_thread_ == nullptr) + { + check_for_updates_thread_ = new UpdateCheck::ThreadController(this, RMV_MAJOR_VERSION, RMV_MINOR_VERSION, RMV_BUGFIX_NUMBER, RMV_BUILD_NUMBER); + + // Build dialog to display and allow user to cancel the check if desired. + if (check_for_updates_pending_dialog_ == nullptr) + { + check_for_updates_pending_dialog_ = new QDialog(this); + check_for_updates_pending_dialog_->setWindowTitle(RMV_APP_NAME RMV_BUILD_SUFFIX); + check_for_updates_pending_dialog_->setWindowFlags((check_for_updates_pending_dialog_->windowFlags() & ~Qt::WindowContextHelpButtonHint) | + Qt::MSWindowsFixedSizeDialogHint); + check_for_updates_pending_dialog_->setFixedWidth(ScalingManager::Get().Scaled(rmv::kUpdatesPendingDialogWidth)); + check_for_updates_pending_dialog_->setFixedHeight(ScalingManager::Get().Scaled(rmv::kUpdatesPendingDialogHeight)); + + QVBoxLayout* pLayout = new QVBoxLayout(); + check_for_updates_pending_dialog_->setLayout(pLayout); + check_for_updates_dialog_label_ = new QLabel(rmv::kRmvUpdateCheckCheckingForUpdates); + check_for_updates_pending_dialog_->layout()->addWidget(check_for_updates_dialog_label_); + check_for_updates_pending_dialog_->layout()->addItem(new QSpacerItem(5, 10, QSizePolicy::Minimum, QSizePolicy::Expanding)); + + // Add Cancel button to cancel the check for updates. + QDialogButtonBox* pButtonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, check_for_updates_pending_dialog_); + pButtonBox->button(QDialogButtonBox::Cancel)->setCursor(Qt::PointingHandCursor); + check_for_updates_pending_dialog_->layout()->addWidget(pButtonBox); + + // If the cancel button is pressed, signal the dialog to reject, which is similar to closing it. + connect(pButtonBox, &QDialogButtonBox::rejected, check_for_updates_pending_dialog_, &QDialog::reject); + } + + // Cancel the check for updates if the dialog is closed. + connect(check_for_updates_pending_dialog_, &QDialog::rejected, check_for_updates_thread_, &UpdateCheck::ThreadController::CancelCheckForUpdates); + + // Get notified when the check for updates has completed or was cancelled. + connect(check_for_updates_thread_, &UpdateCheck::ThreadController::CheckForUpdatesComplete, this, &AboutPane::CheckForUpdatesCompleted); + connect(check_for_updates_thread_, &UpdateCheck::ThreadController::CheckForUpdatesCancelled, this, &AboutPane::CheckForUpdatesCancelled); + + // Signal the check for updates to start. + check_for_updates_thread_->StartCheckForUpdates(rmv::kRmvUpdateCheckUrl, rmv::kRmvUpdateCheckAssetName); + + // Show the WaitCursor on the check for updates button to suggest to users that it is in-progress. + ui_->check_for_updates_button_->setCursor(Qt::WaitCursor); + + // Display the dialog. + check_for_updates_pending_dialog_->show(); + } +} + +void AboutPane::CheckForUpdatesCompleted(UpdateCheck::ThreadController* thread, const UpdateCheck::Results& update_check_results) +{ + if (update_check_results.was_check_successful && !update_check_results.update_info.is_update_available) + { + // Update the existing dialog to report that there are no updates available. + check_for_updates_dialog_label_->setText(rmv::kRmvUpdateCheckNoUpdatesAvailable); + check_for_updates_pending_dialog_->update(); + } + else + { + check_for_updates_pending_dialog_->close(); + + UpdateCheckResultsDialog* results_dialog = new UpdateCheckResultsDialog(this); + if (results_dialog != nullptr) + { + results_dialog->setAttribute(Qt::WA_DeleteOnClose); + results_dialog->setWindowFlags(results_dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); + + results_dialog->setModal(true); + results_dialog->setFixedWidth(ScalingManager::Get().Scaled(rmv::kUpdatesResultsDialogWidth)); + results_dialog->setFixedHeight(ScalingManager::Get().Scaled(rmv::kUpdatesResultsDialogHeight)); + results_dialog->SetShowTags(false); + + QDialogButtonBox* button_box = results_dialog->findChild("buttonBox"); + if (button_box != nullptr) + { + QPushButton* close_button = button_box->button(QDialogButtonBox::Close); + if (close_button != nullptr) + { + close_button->setCursor(Qt::PointingHandCursor); + } + } + + results_dialog->SetResults(update_check_results); + results_dialog->show(); + } + } + + // Restore pointing hand cursor. + ui_->check_for_updates_button_->setCursor(Qt::PointingHandCursor); + + // Delete the previous thread since it is no longer useful. + if (check_for_updates_thread_ == thread) + { + if (check_for_updates_thread_ != nullptr) + { + delete check_for_updates_thread_; + check_for_updates_thread_ = nullptr; + } + } +} + +void AboutPane::CheckForUpdatesCancelled(UpdateCheck::ThreadController* thread) +{ + // Restore pointing hand cursor. + ui_->check_for_updates_button_->setCursor(Qt::PointingHandCursor); + + // Delete the previous thread since it is no longer useful. + if (check_for_updates_thread_ == thread) + { + if (check_for_updates_thread_ != nullptr) + { + delete check_for_updates_thread_; + check_for_updates_thread_ = nullptr; + } + } +} diff --git a/source/frontend/views/start/about_pane.h b/source/frontend/views/start/about_pane.h new file mode 100644 index 0000000..866e04f --- /dev/null +++ b/source/frontend/views/start/about_pane.h @@ -0,0 +1,90 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV system information about pane. +//============================================================================= + +#ifndef RMV_VIEWS_START_ABOUT_PANE_H_ +#define RMV_VIEWS_START_ABOUT_PANE_H_ + +#include "ui_about_pane.h" + +#include +#include + +#include "qt_common/custom_widgets/scaled_push_button.h" +#include "update_check_api/source/update_check_results_dialog.h" + +#include "views/base_pane.h" + +class MainWindow; + +/// Support for RMV system information about pane. +class AboutPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit AboutPane(MainWindow* parent); + + /// Destructor. + virtual ~AboutPane(); + +private slots: + /// Open RMV help file. + /// Present the user with help regarding RMV. + void OpenRmvHelp(); + + /// Open trace help file. + /// Present the user with help about how to capture a trace with the panel. + void OpenTraceHelp(); + + /// Open RMV help file. + /// Present the user with help regarding RMV. + void OpenRmvLicense(); + /// Perform a check for updates. + /// Runs a background thread that goes online to look for updates. + void CheckForUpdates(); + + /// Callback after a check for updates has returned. + /// Displays a modal dialog box with the update information or error message. + /// \param thread The background thread that was checking for updates. + /// \param update_check_results The results of the check for updates. + void CheckForUpdatesCompleted(UpdateCheck::ThreadController* thread, const UpdateCheck::Results& update_check_results); + + /// Callback for when check for updates has returned due to being cancelled. + /// Restores the UI to allow checking for updates again. + /// \param thread The background thread that was checking for updates. + void CheckForUpdatesCancelled(UpdateCheck::ThreadController* thread); + +private: + /// Open an HTML file. + /// \param html_file the file to open. + void OpenHtmlFile(const QString& html_file); + + /// Initialize the button. + /// \param button Pointer to the push button. + void InitButton(ScaledPushButton*& button); + + Ui::AboutPane* ui_; ///< Pointer to the Qt UI design. + + /// A dialog that is displayed while the check for updates is in-progress. + /// Closing this dialog will signal the check for updates to be cancelled. + /// It will close automatically after the check for updates completes. + QDialog* check_for_updates_pending_dialog_; + + /// The label on the check for updates pending dialog. + QLabel* check_for_updates_dialog_label_; + + /// This class creates and interacts with the background thread that + /// performs the check for updates. We need to store a member variable + /// so that we can cancel the thread if needed. The thread will emit a + /// signal when the check for updates has either been cancelled or after + /// it has completed. + UpdateCheck::ThreadController* check_for_updates_thread_; +}; + +#endif // RMV_VIEWS_START_ABOUT_PANE_H_ diff --git a/source/frontend/views/start/about_pane.ui b/source/frontend/views/start/about_pane.ui new file mode 100644 index 0000000..50b645d --- /dev/null +++ b/source/frontend/views/start/about_pane.ui @@ -0,0 +1,379 @@ + + + AboutPane + + + + 0 + 0 + 719 + 413 + + + + Form + + + + 20 + + + 20 + + + 20 + + + 20 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 14 + 75 + true + + + + Radeon Memory Visualizer + + + + + + + + + + 0 + 0 + + + + + 10 + + + + Build + + + + + + + + 0 + 0 + + + + + 10 + + + + Build Date + + + + + + + + 0 + 0 + + + + + 10 + + + + Version + + + + + + + + 0 + 0 + + + + + 10 + + + + + + + + + + + + 0 + 0 + + + + + 10 + + + + + + + + + + + + 0 + 0 + + + + + 10 + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 10 + + + + Copyright © 2018-2020 Advanced Micro Devices, Inc. All rights reserved + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + 0 + + + QLayout::SetNoConstraint + + + + + + 0 + 0 + + + + + 14 + 75 + true + + + + Related actions + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + 3 + + + + + + 10 + + + + View the Radeon Memory Visualizer License + + + + + + + + 10 + + + + How to capture a trace + + + + + + + + 10 + + + + How to use the visualizer + + + + + + + + 10 + + + + Check for updates + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledPushButton + QPushButton +
qt_common/custom_widgets/scaled_push_button.h
+
+
+ + +
diff --git a/source/frontend/views/start/recent_traces_pane.cpp b/source/frontend/views/start/recent_traces_pane.cpp new file mode 100644 index 0000000..5ac6987 --- /dev/null +++ b/source/frontend/views/start/recent_traces_pane.cpp @@ -0,0 +1,141 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Recent Snapshots pane. +//============================================================================= + +#include "views/start/recent_traces_pane.h" + +#include +#include + +#include "qt_common/utils/scaling_manager.h" + +#include "models/message_manager.h" +#include "settings/rmv_settings.h" +#include "util/widget_util.h" +#include "views/main_window.h" + +const static int kRecentTraceSpacing = 20; +const static int kRecentTracePaneMargin = 10; +const static int kRecentTracesTextPixelFontSize = 14; +const static QString kRecentTracesNoTracesString = "There are no recently opened memory traces"; + +RecentTracesPane::RecentTracesPane(MainWindow* parent) + : BasePane(parent) + , ui_(new Ui::RecentTracesPane) + , main_window_(parent) + , vbox_layout_(nullptr) + , no_traces_label_(nullptr) +{ + ui_->setupUi(this); + + // Set white background for this pane + rmv::widget_util::SetWidgetBackgroundColor(this, Qt::white); + + // Set the background color + QPalette palette; + palette.setColor(QPalette::Background, Qt::GlobalColor::transparent); + ui_->main_scroll_area_->setPalette(palette); + + scroll_area_widget_contents_ = new QWidget(); + scroll_area_widget_contents_->setPalette(palette); + scroll_area_widget_contents_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + // when deleting a trace, the setupFileList signal is fired to force an update of the file list. This needs to be done + // on a queuedConnection so that any signals/slots/signal mappers for the trace file widgets have been cleaned up since + // they get recreated when setting up a new file list. + connect(this, &RecentTracesPane::FileListChanged, this, &RecentTracesPane::SetupFileList, Qt::QueuedConnection); + + SetupFileList(); +} + +RecentTracesPane::~RecentTracesPane() +{ + for (RecentTraceWidget* widget : trace_widgets_) + { + delete widget; + } + delete ui_; +} + +void RecentTracesPane::SetupFileList() +{ + const QVector& files = RMVSettings::Get().RecentFiles(); + + // Clear any previous recent trace widgets + for (RecentTraceWidget* widget : trace_widgets_) + { + delete widget; + } + trace_widgets_.clear(); + + if (no_traces_label_ != nullptr) + { + if (vbox_layout_ != nullptr) + { + vbox_layout_->removeWidget(no_traces_label_); + } + + no_traces_label_->hide(); + } + + if (vbox_layout_ != nullptr) + { + delete vbox_layout_; + } + + vbox_layout_ = new QVBoxLayout(scroll_area_widget_contents_); + vbox_layout_->setSpacing(kRecentTraceSpacing); + vbox_layout_->setContentsMargins(kRecentTracePaneMargin, kRecentTracePaneMargin, kRecentTracePaneMargin, kRecentTracePaneMargin); + + // If there are no recent traces to show, add a label stating so + if (files.size() == 0) + { + no_traces_label_ = new QLabel(kRecentTracesNoTracesString); + + // Set the fonts + QFont font; + font.setPixelSize(ScalingManager::Get().Scaled(kRecentTracesTextPixelFontSize)); + font.setBold(true); + font.setFamily(font.defaultFamily()); + no_traces_label_->setFont(font); + + // Add this label to the vertical layout + vbox_layout_->addWidget(no_traces_label_); + } + + // Create a widget for each recent file + for (int i = 0; i < files.size(); i++) + { + RecentTraceWidget* trace_widget = new RecentTraceWidget(this); + QSizePolicy policy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + trace_widget->setSizePolicy(policy); + vbox_layout_->addWidget(trace_widget); + trace_widgets_.push_back(trace_widget); + + // Set up the widget + trace_widget->SetRecentFileData(files[i]); + trace_widget->show(); + + // Trigger a trace open when the trace widget is clicked + connect(trace_widget, &RecentTraceWidget::clicked, &MessageManager::Get(), &MessageManager::OpenTrace); + connect(trace_widget, &RecentTraceWidget::clickedDelete, this, &RecentTracesPane::DeleteTrace); + connect(trace_widget, &RecentTraceWidget::clickedDelete, main_window_, &MainWindow::SetupRecentTracesMenu); + } + // Add a spacer at the bottom. The spacer item is owned by the layout so will be deleted when the layout is deleted + vbox_layout_->addSpacerItem(new QSpacerItem(10, 40, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); + vbox_layout_->setSizeConstraint(QLayout::SetMinimumSize); + ui_->main_scroll_area_->setWidget(scroll_area_widget_contents_); + + // Set the vertical scrollbar to the top + ui_->main_scroll_area_->verticalScrollBar()->setMaximum(0); +} + +void RecentTracesPane::DeleteTrace(QString path) +{ + RMVSettings::Get().RemoveRecentFile(path); + RMVSettings::Get().SaveSettings(); + emit FileListChanged(); +} diff --git a/source/frontend/views/start/recent_traces_pane.h b/source/frontend/views/start/recent_traces_pane.h new file mode 100644 index 0000000..c6b1a21 --- /dev/null +++ b/source/frontend/views/start/recent_traces_pane.h @@ -0,0 +1,59 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Recent traces pane. +//============================================================================= + +#ifndef RMV_VIEWS_START_RECENT_TRACES_PANE_H_ +#define RMV_VIEWS_START_RECENT_TRACES_PANE_H_ + +#include "ui_recent_traces_pane.h" + +#include +#include +#include + +#include "qt_common/custom_widgets/recent_trace_widget.h" + +#include "views/base_pane.h" + +class MainWindow; + +/// Class declaration. +class RecentTracesPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit RecentTracesPane(MainWindow* parent = nullptr); + + /// Destructor. + virtual ~RecentTracesPane(); + +signals: + /// Something changed the file list (either a delete or a new file added). + void FileListChanged(); + +public slots: + /// Set up the list of recent traces in the UI. + void SetupFileList(); + + /// Slot to delete a trace from the Recent traces list. Only removes it from + /// the list; doesn't actually delete the file. + /// \param path The path to the trace file. + void DeleteTrace(QString path); + +private: + Ui::RecentTracesPane* ui_; ///< Pointer to the Qt UI design. + + MainWindow* main_window_; ///< Reference to the mainwindow (parent). + QVector trace_widgets_; ///< Array of trace widgets. + QVBoxLayout* vbox_layout_; ///< The vertical layout to handle custom widgets. + QWidget* scroll_area_widget_contents_; ///< The scroll area widget contents widget. + QLabel* no_traces_label_; ///< The no traces label. +}; + +#endif // RMV_VIEWS_START_RECENT_TRACES_PANE_H_ diff --git a/source/frontend/views/start/recent_traces_pane.ui b/source/frontend/views/start/recent_traces_pane.ui new file mode 100644 index 0000000..c07d213 --- /dev/null +++ b/source/frontend/views/start/recent_traces_pane.ui @@ -0,0 +1,84 @@ + + + RecentTracesPane + + + + 0 + 0 + 400 + 300 + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 382 + 282 + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Recent traces + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+
+ + + + +
diff --git a/source/frontend/views/start/welcome_pane.cpp b/source/frontend/views/start/welcome_pane.cpp new file mode 100644 index 0000000..988bccb --- /dev/null +++ b/source/frontend/views/start/welcome_pane.cpp @@ -0,0 +1,218 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV welcome pane. +//============================================================================= + +#include "views/start/welcome_pane.h" + +#include +#include +#include +#include + +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "models/message_manager.h" +#include "settings/rmv_settings.h" +#include "util/version.h" +#include "util/widget_util.h" +#include "views/main_window.h" + +static const int kMaxRecentFilesToShow = 8; + +WelcomePane::WelcomePane(MainWindow* parent) + : BasePane(parent) + , ui_(new Ui::WelcomePane) + , main_window_(parent) +{ + ui_->setupUi(this); + + constexpr int id = qRegisterMetaType(); + Q_UNUSED(id); + + // Set white background for this pane + rmv::widget_util::SetWidgetBackgroundColor(this, Qt::white); + + SetupFileList(); + + // Set up the buttons + InitButton(ui_->open_rmv_trace_button_); + InitButton(ui_->see_more_recent_files_button_); + InitButton(ui_->open_getting_started_button_); + InitButton(ui_->open_rmv_help_button_); + + ui_->quick_link_gpu_open_->SetTitle("GPUOpen website"); + ui_->quick_link_gpu_open_->SetDescLineOne("Check out the latest development blogs, performance tips & tricks "); + ui_->quick_link_gpu_open_->SetDescLineTwo("and open source releases."); + + ui_->quick_link_profiler_->SetTitle("Explore Radeon GPU Profiler"); + ui_->quick_link_profiler_->SetDescLineOne("Find performance bottlenecks and fine tune your application"); + ui_->quick_link_profiler_->SetDescLineTwo("using Radeon GPU Profiler. Available right now at GPUOpen."); + + ui_->quick_link_analyzer_->SetTitle("Explore Radeon GPU Analyzer"); + ui_->quick_link_analyzer_->SetDescLineOne("Dig into the disassembly, resource utilization and register liveness of"); + ui_->quick_link_analyzer_->SetDescLineTwo("your shaders using RGA. Available right now at GPUOpen."); + + ui_->quick_link_sample_trace_->SetTitle("Sample trace"); + ui_->quick_link_sample_trace_->SetDescLineOne("Still got your training wheels on? Check out a sample trace to see"); + ui_->quick_link_sample_trace_->SetDescLineTwo("what we can do!"); + + // Connect buttons to slots + connect(ui_->open_rmv_trace_button_, &QPushButton::clicked, main_window_, &MainWindow::OpenTrace); + connect(ui_->see_more_recent_files_button_, &QPushButton::clicked, main_window_, &MainWindow::OpenRecentTracesPane); + connect(ui_->open_getting_started_button_, &QPushButton::clicked, this, &WelcomePane::OpenTraceHelp); + connect(ui_->open_rmv_help_button_, &QPushButton::clicked, this, &WelcomePane::OpenRmvHelp); + connect(ui_->quick_link_gpu_open_, &QPushButton::clicked, this, &WelcomePane::OpenGPUOpenURL); + connect(ui_->quick_link_profiler_, &QPushButton::clicked, this, &WelcomePane::OpenRGPURL); + connect(ui_->quick_link_analyzer_, &QPushButton::clicked, this, &WelcomePane::OpenRGAURL); + connect(ui_->quick_link_sample_trace_, &QPushButton::clicked, this, &WelcomePane::OpenSampleTrace); + connect(this, &WelcomePane::FileListChanged, this, &WelcomePane::SetupFileList, Qt::QueuedConnection); + + // Notifications are always hidden by default, and will be displayed if new notifications become available. + ui_->notifications_label_->setVisible(false); + ui_->notify_update_available_button_->setVisible(false); + + if (RMVSettings::Get().GetCheckForUpdatesOnStartup()) + { + UpdateCheck::ThreadController* background_thread = + new UpdateCheck::ThreadController(this, RMV_MAJOR_VERSION, RMV_MINOR_VERSION, RMV_BUGFIX_NUMBER, RMV_BUILD_NUMBER); + + // Get notified when the check for updates has completed. + // There is not a way in the UI to cancel this thread, so no reason to connect to its CheckForUpdatesCancelled callback. + connect(background_thread, &UpdateCheck::ThreadController::CheckForUpdatesComplete, this, &WelcomePane::NotifyOfNewVersion); + + background_thread->StartCheckForUpdates(rmv::kRmvUpdateCheckUrl, rmv::kRmvUpdateCheckAssetName); + } +} + +WelcomePane::~WelcomePane() +{ +} + +void WelcomePane::SetupFileList() +{ + const QVector& files = RMVSettings::Get().RecentFiles(); + + // Clear any previous recent trace widgets + for (RecentTraceMiniWidget* widget : trace_widgets_) + { + delete widget; + } + + trace_widgets_.clear(); + + // Create a widget for each recent file + int max_files_to_show = 0; + files.size() > kMaxRecentFilesToShow ? max_files_to_show = kMaxRecentFilesToShow : max_files_to_show = files.size(); + for (int i = 0; i < max_files_to_show; i++) + { + RecentTraceMiniWidget* trace_widget = new RecentTraceMiniWidget(ui_->recent_traces_wrapper_); + trace_widgets_.push_back(trace_widget); + + // Set up the widget + trace_widget->SetFile(files[i]); + trace_widget->show(); + + // Trigger a trace open when the trace widget is clicked + connect(trace_widget, &RecentTraceMiniWidget::clicked, &MessageManager::Get(), &MessageManager::OpenTrace); + + ui_->recent_traces_wrapper_->layout()->addWidget(trace_widget); + } + + if (files.size() > kMaxRecentFilesToShow) + { + ui_->see_more_recent_files_button_->show(); + } + else + { + ui_->see_more_recent_files_button_->hide(); + } +} + +void WelcomePane::InitButton(ScaledPushButton*& button) +{ + // Init the button + button->setCursor(Qt::PointingHandCursor); + button->setStyleSheet(kLinkButtonStylesheet); +} + +void WelcomePane::OpenHtmlFile(const QString& html_file) +{ + // Get the file info + QFileInfo file_info(QCoreApplication::applicationDirPath() + html_file); + + // Check to see if the file is not a directory and that it exists + if (file_info.isFile() && file_info.exists()) + { + QDesktopServices::openUrl(QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + html_file)); + } + else + { + // The selected html file is missing on the disk so display a message box stating so + const QString text = rmv::text::kMissingRmvHelpFile + html_file; + QtCommon::QtUtils::ShowMessageBox(this, QMessageBox::Ok, QMessageBox::Critical, rmv::text::kMissingRmvHelpFile, text); + } +} + +void WelcomePane::OpenRmvHelp() +{ + OpenHtmlFile(rmv::text::kRmvHelpFile); +} + +void WelcomePane::OpenTraceHelp() +{ + OpenHtmlFile(rmv::text::kTraceHelpFile); +} + +void WelcomePane::OpenGPUOpenURL() +{ + QDesktopServices::openUrl(QUrl("http://gpuopen.com")); +} + +void WelcomePane::OpenRGPURL() +{ + QDesktopServices::openUrl(QUrl("https://gpuopen.com/gaming-product/radeon-gpu-profiler-rgp/")); +} + +void WelcomePane::OpenRGAURL() +{ + QDesktopServices::openUrl(QUrl("http://gpuopen.com/gaming-product/radeon-gpu-analyzer-rga/")); +} + +void WelcomePane::OpenSampleTrace() +{ + MessageManager::Get().OpenTrace(QCoreApplication::applicationDirPath() + rmv::text::kSampleTraceLocation); +} + +void WelcomePane::NotifyOfNewVersion(UpdateCheck::ThreadController* thread, const UpdateCheck::Results& update_check_results) +{ + if (update_check_results.was_check_successful && update_check_results.update_info.is_update_available && !update_check_results.update_info.releases.empty()) + { + ui_->notifications_label_->setVisible(true); + ui_->notify_update_available_button_->setVisible(true); + ui_->notify_update_available_button_->SetTitle("New Version Available!"); + ui_->notify_update_available_button_->SetDescLineOne(update_check_results.update_info.releases[0].title.c_str()); + ui_->notify_update_available_button_->SetDescLineTwo("Click here for more information."); + + // This dialog will get deleted when the WelcomePane is deleted. + UpdateCheckResultsDialog* results_dialog = new UpdateCheckResultsDialog(this); + results_dialog->setWindowFlags((results_dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint) | Qt::MSWindowsFixedSizeDialogHint); + results_dialog->setFixedSize(ScalingManager::Get().Scaled(rmv::kUpdatesResultsDialogWidth), + ScalingManager::Get().Scaled(rmv::kUpdatesResultsDialogHeight)); + results_dialog->SetShowTags(false); + results_dialog->SetResults(update_check_results); + + // Connect the button so that the when it is clicked, the dialog is shown. + // This is why the dialog should not be deleted earlier - it could get opened any time. + connect(ui_->notify_update_available_button_, &QPushButton::clicked, results_dialog, &QDialog::show); + } + + // Delete the thread so that it no longer exists in the background. + if (thread != nullptr) + { + delete thread; + } +} diff --git a/source/frontend/views/start/welcome_pane.h b/source/frontend/views/start/welcome_pane.h new file mode 100644 index 0000000..975814a --- /dev/null +++ b/source/frontend/views/start/welcome_pane.h @@ -0,0 +1,86 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV welcome pane. +//============================================================================= + +#ifndef RMV_VIEWS_START_WELCOME_PANE_H_ +#define RMV_VIEWS_START_WELCOME_PANE_H_ + +#include "ui_welcome_pane.h" + +#include +#include +#include + +#include "qt_common/custom_widgets/quick_link_button_widget.h" +#include "qt_common/custom_widgets/recent_trace_mini_widget.h" +#include "qt_common/custom_widgets/scaled_push_button.h" +#include "update_check_api/source/update_check_results_dialog.h" + +#include "views/base_pane.h" + +class MainWindow; + +/// Support for RMV recently opened traces pane. +class WelcomePane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit WelcomePane(MainWindow* parent); + + /// Destructor. + virtual ~WelcomePane(); + +signals: + void FileListChanged(); + +private slots: + /// Setup the list of recent files. Called whenever the number of recent files + /// changes or whenever the list needs updating. + void SetupFileList(); + + /// Open RMV help file. + /// Present the user with help regarding RMV. + void OpenRmvHelp(); + + /// Open trace help file. + /// Present the user with help about how to capture a trace with the panel. + void OpenTraceHelp(); + + /// Open a URL to GPUOpen. + void OpenGPUOpenURL(); + + /// Open a URL to RGP. + void OpenRGPURL(); + + /// Open a URL to RGA. + void OpenRGAURL(); + + /// Open sample trace. + void OpenSampleTrace(); + + /// Notify the user that a new version is available. + /// \param thread The background thread that checked for updates. + /// \param update_check_results The results of the check for updates. + void NotifyOfNewVersion(UpdateCheck::ThreadController* thread, const UpdateCheck::Results& update_check_results); + +private: + /// Open an HTML file. + /// \\param html_file the file to open. + void OpenHtmlFile(const QString& html_file); + + /// Initialize the button. + /// \param button Pointer to the push button. + void InitButton(ScaledPushButton*& button); + + Ui::WelcomePane* ui_; ///< Pointer to the Qt UI design. + MainWindow* main_window_; ///< Reference to the mainwindow (parent). + QVector trace_widgets_; ///< Array of trace widgets. +}; + +#endif // RMV_VIEWS_START_WELCOME_PANE_H_ diff --git a/source/frontend/views/start/welcome_pane.ui b/source/frontend/views/start/welcome_pane.ui new file mode 100644 index 0000000..d2cb626 --- /dev/null +++ b/source/frontend/views/start/welcome_pane.ui @@ -0,0 +1,578 @@ + + + WelcomePane + + + + 0 + 0 + 967 + 652 + + + + Form + + + + 20 + + + 20 + + + 20 + + + 20 + + + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Hello, welcome to the Radeon Memory Visualizer! + + + + + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Start + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 10 + + + + Open a Radeon Memory trace... + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Recent + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 0 + 100 + + + + + 3 + + + QLayout::SetFixedSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 10 + + + + See more recent traces... + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + 0 + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Help + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + 3 + + + + + + 10 + + + + How to capture a trace + + + + + + + + 10 + + + + How to use the visualizer + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Quick links + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + PushButton + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + PushButton + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + PushButton + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + PushButton + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Notifications + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + PushButton + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + QuickLinkButtonWidget + QPushButton +
qt_common/custom_widgets/quick_link_button_widget.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledPushButton + QPushButton +
qt_common/custom_widgets/scaled_push_button.h
+
+
+ + +
diff --git a/source/frontend/views/timeline/device_configuration_pane.cpp b/source/frontend/views/timeline/device_configuration_pane.cpp new file mode 100644 index 0000000..f07209e --- /dev/null +++ b/source/frontend/views/timeline/device_configuration_pane.cpp @@ -0,0 +1,78 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of RMV's device configuration pane. +//============================================================================= + +#include "views/timeline/device_configuration_pane.h" + +#include "qt_common/utils/qt_util.h" + +#include "rmt_adapter_info.h" +#include "rmt_data_set.h" + +#include "models/message_manager.h" +#include "util/widget_util.h" + +DeviceConfigurationPane::DeviceConfigurationPane(QWidget* parent) + : BasePane(parent) + , ui_(new Ui::DeviceConfigurationPane) +{ + ui_->setupUi(this); + + // Set white background for this pane + rmv::widget_util::SetWidgetBackgroundColor(this, Qt::white); + + // Set mouse cursor to pointing hand cursor for various widgets + ui_->button_copy_to_clipboard_->setCursor(Qt::PointingHandCursor); + + // hide the copy to clipboard button until it's implemented + ui_->button_copy_to_clipboard_->hide(); + + // hide the CPU info for now + ui_->label_title_system_->hide(); + ui_->label_processor_brand_->hide(); + ui_->content_processor_brand_->hide(); + ui_->label_processor_speed_->hide(); + ui_->content_processor_speed_->hide(); + ui_->label_physical_cores_->hide(); + ui_->content_physical_cores_->hide(); + ui_->label_logical_cores_->hide(); + ui_->content_logical_cores_->hide(); + ui_->label_system_memory_->hide(); + ui_->content_system_memory_->hide(); + + model_ = new rmv::DeviceConfigurationModel(); + + model_->InitializeModel(ui_->content_device_name_, rmv::kDeviceConfigurationDeviceName, "text"); + model_->InitializeModel(ui_->content_device_id_, rmv::kDeviceConfigurationDeviceID, "text"); + model_->InitializeModel(ui_->content_memory_size_, rmv::kDeviceConfigurationMemorySize, "text"); + model_->InitializeModel(ui_->content_shader_core_clock_frequency_, rmv::kDeviceConfigurationShaderCoreClockFrequency, "text"); + model_->InitializeModel(ui_->content_memory_clock_frequency_, rmv::kDeviceConfigurationMemoryClockFrequency, "text"); + model_->InitializeModel(ui_->content_local_memory_bandwidth_, rmv::kDeviceConfigurationLocalMemoryBandwidth, "text"); + model_->InitializeModel(ui_->content_local_memory_type_, rmv::kDeviceConfigurationLocalMemoryType, "text"); + model_->InitializeModel(ui_->content_local_memory_bus_width_, rmv::kDeviceConfigurationLocalMemoryBusWidth, "text"); +} + +DeviceConfigurationPane::~DeviceConfigurationPane() +{ + delete ui_; + delete model_; +} + +void DeviceConfigurationPane::showEvent(QShowEvent* event) +{ + Refresh(); + QWidget::showEvent(event); +} + +void DeviceConfigurationPane::Refresh() +{ + model_->Update(); +} + +void DeviceConfigurationPane::Reset() +{ + model_->ResetModelValues(); +} diff --git a/source/frontend/views/timeline/device_configuration_pane.h b/source/frontend/views/timeline/device_configuration_pane.h new file mode 100644 index 0000000..0ab8aad --- /dev/null +++ b/source/frontend/views/timeline/device_configuration_pane.h @@ -0,0 +1,45 @@ +//============================================================================= +/// Copyright (c) 2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for RMV's device configuration pane. +//============================================================================= + +#ifndef RMV_VIEWS_TIMELINE_DEVICE_CONFIGURATION_PANE_H_ +#define RMV_VIEWS_TIMELINE_DEVICE_CONFIGURATION_PANE_H_ + +#include "ui_device_configuration_pane.h" + +#include + +#include "models/timeline/device_configuration_model.h" +#include "views/base_pane.h" + +class DeviceConfigurationPane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit DeviceConfigurationPane(QWidget* parent = nullptr); + + /// Destructor. + ~DeviceConfigurationPane(); + + /// Overridden Qt show event. Fired when this pane is opened. + /// \param event The show event object. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + +private: + /// Refresh the UI. + void Refresh(); + + Ui::DeviceConfigurationPane* ui_; ///< Pointer to the Qt UI design. + rmv::DeviceConfigurationModel* model_; ///< The model for this pane. +}; + +#endif // RMV_VIEWS_TIMELINE_DEVICE_CONFIGURATION_PANE_H_ diff --git a/source/frontend/views/timeline/device_configuration_pane.ui b/source/frontend/views/timeline/device_configuration_pane.ui new file mode 100644 index 0000000..89170cd --- /dev/null +++ b/source/frontend/views/timeline/device_configuration_pane.ui @@ -0,0 +1,839 @@ + + + DeviceConfigurationPane + + + + 0 + 0 + 502 + 767 + + + + + 75 + true + + + + Form + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 50 + false + false + + + + false + + + 0 + + + + + + true + + + Qt::AlignCenter + + + false + + + 0 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 20 + + + 0 + + + + + + 0 + 0 + + + + + 50 + false + + + + - + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + System information + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Device ID (and revision): + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Video memory type: + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + GPU information + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Shader core clock frequency: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + %d + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Video memory size: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + %d + + + + + + + + 0 + 0 + + + + + 50 + false + + + + - + + + + + + + + 0 + 0 + + + + + 50 + false + + + + - + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Clock speed: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + %d + + + + + + + + 0 + 0 + + + + + 50 + false + + + + %d + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Device name: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + - + + + + + + + + 0 + 0 + + + + + 50 + false + + + + %s + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Video memory bandwidth: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + %d + + + + + + + + 0 + 0 + + + + + 50 + false + + + + %d + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Logical cores: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + - + + + + + + + + 0 + 0 + + + + + 50 + false + + + + System memory (RAM): + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Processor name: + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Video memory clock frequency: + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + GPU memory + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Shader core + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Physical cores: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Video memory bus width: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + %d + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + 0 + + + + + + 0 + 0 + + + + + 50 + false + + + + Copy to clipboard + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledPushButton + QPushButton +
qt_common/custom_widgets/scaled_push_button.h
+
+
+ + + + +
diff --git a/source/frontend/views/timeline/keyboard_zoom_shortcuts_timeline.cpp b/source/frontend/views/timeline/keyboard_zoom_shortcuts_timeline.cpp new file mode 100644 index 0000000..1c1d415 --- /dev/null +++ b/source/frontend/views/timeline/keyboard_zoom_shortcuts_timeline.cpp @@ -0,0 +1,141 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the global keyboard shortcuts class. +//============================================================================= + +#include "views/timeline/keyboard_zoom_shortcuts_timeline.h" + +#include +#include +#include + +#include "views/timeline/timeline_pane.h" + +KeyboardZoomShortcutsTimeline::KeyboardZoomShortcutsTimeline(TimelinePane* parent_pane, QScrollBar* scroll_bar, QGraphicsView* zoom_view) + : KeyboardZoomShortcuts(scroll_bar, zoom_view) + , parent_pane_(parent_pane) + , scroll_bar_(scroll_bar) + , zoom_view_(zoom_view) +{ + SetupKeyboardZoomShortcuts(); +} + +KeyboardZoomShortcutsTimeline::~KeyboardZoomShortcutsTimeline() +{ +} + +void KeyboardZoomShortcutsTimeline::OnZoomInShortCut(bool checked) +{ + Q_UNUSED(checked); + + parent_pane_->ZoomInCustom(2, true); +} + +void KeyboardZoomShortcutsTimeline::OnZoomOutShortCut(bool checked) +{ + Q_UNUSED(checked); + + parent_pane_->ZoomOutCustom(2, true); +} + +void KeyboardZoomShortcutsTimeline::OnZoomInMoreShortCut(bool checked) +{ + Q_UNUSED(checked); + + parent_pane_->ZoomInCustom(10, true); +} + +void KeyboardZoomShortcutsTimeline::OnZoomOutMoreShortCut(bool checked) +{ + Q_UNUSED(checked); + + parent_pane_->ZoomOutCustom(10, true); +} + +void KeyboardZoomShortcutsTimeline::OnZoomInSelection(bool checked) +{ + Q_UNUSED(checked); + + emit ZoomInSelectionSignal(); +} + +void KeyboardZoomShortcutsTimeline::OnResetView(bool checked) +{ + Q_UNUSED(checked); + + emit ResetViewSignal(); +} + +void KeyboardZoomShortcutsTimeline::SetupKeyboardZoomShortcuts() +{ + // Setup actions for various keyboard shortcuts. + for (auto const& item : navigation_control_) + { + QAction* action = new QAction(parent_pane_); + action->setShortcut(item.first); + + connect(action, &QAction::triggered, this, item.second); + parent_pane_->addAction(action); + } +} + +bool KeyboardZoomShortcutsTimeline::KeyPressed(int key_code, bool is_auto_repeat) +{ + bool processed = true; + switch (key_code) + { + case Qt::Key_Space: + { + // Only enable drag if not currently holding a mouse button down and the mouse + // is over the graphics view to be dragged. + Qt::MouseButtons buttons = QApplication::mouseButtons(); + if (is_auto_repeat == false && buttons == Qt::NoButton) + { + if ((zoom_view_ && zoom_view_->underMouse())) + { + // Set graphics view to scroll mode. + zoom_view_->setDragMode(QGraphicsView::ScrollHandDrag); + } + else if (zoom_view_ == nullptr) + { + // Set cursor for event timings treeview. + parent_pane_->setCursor(QCursor(Qt::OpenHandCursor)); + } + } + } + break; + + default: + processed = false; + break; + } + return processed; +} + +bool KeyboardZoomShortcutsTimeline::KeyReleased(int key_code, bool is_auto_repeat) +{ + bool processed = true; + switch (key_code) + { + case Qt::Key_Space: + if (!is_auto_repeat) + { + if (zoom_view_ != nullptr) + { + zoom_view_->setDragMode(QGraphicsView::NoDrag); + } + else + { + parent_pane_->setCursor(Qt::ArrowCursor); + } + } + break; + + default: + processed = false; + break; + } + return processed; +} \ No newline at end of file diff --git a/source/frontend/views/timeline/keyboard_zoom_shortcuts_timeline.h b/source/frontend/views/timeline/keyboard_zoom_shortcuts_timeline.h new file mode 100644 index 0000000..53e06d9 --- /dev/null +++ b/source/frontend/views/timeline/keyboard_zoom_shortcuts_timeline.h @@ -0,0 +1,87 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the keyboard zoom shortcuts. +//============================================================================= + +#ifndef RMV_VIEWS_TIMELINE_KEYBOARD_ZOOM_SHORTCUTS_TIMELINE_H_ +#define RMV_VIEWS_TIMELINE_KEYBOARD_ZOOM_SHORTCUTS_TIMELINE_H_ + +#include +#include +#include + +#include "views/keyboard_zoom_shortcuts.h" + +class TimelinePane; + +/// Class to handle keyboard zoom shortcut keys for the Timeline pane. +class KeyboardZoomShortcutsTimeline : public KeyboardZoomShortcuts +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent_pane The parent pane. + /// \param scroll_bar The view where the zoom scrollbar is located. + /// \param zoom_view The view where the zoom shortcuts are applied. + explicit KeyboardZoomShortcutsTimeline(TimelinePane* parent_pane, QScrollBar* scroll_bar, QGraphicsView* zoom_view = nullptr); + + /// Destructor. + virtual ~KeyboardZoomShortcutsTimeline(); + + /// Handle a key press. + /// \param key_code The key code of the key pressed. + /// \param is_auto_repeat Is the key autorepeat on. + /// \return true if the key press has been processed, false otherwise. + virtual bool KeyPressed(int key_code, bool is_auto_repeat); + + /// Handle a key release. + /// \param key_code The key code of the key released. + /// \param is_auto_repeat Is the key autorepeat on. + /// \return true if the key press has been processed, false otherwise. + virtual bool KeyReleased(int key_code, bool is_auto_repeat); + +signals: + /// Signal for zoom in selection. + void ZoomInSelectionSignal(); + + /// Signal for zoom reset. + void ResetViewSignal(); + +private slots: + /// Action slot to zoom in. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomInShortCut(bool checked) Q_DECL_OVERRIDE; + + /// Action slot to zoom out. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomOutShortCut(bool checked) Q_DECL_OVERRIDE; + + /// Action slot to zoom in faster. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomInMoreShortCut(bool checked) Q_DECL_OVERRIDE; + + /// Action slot to zoom out faster. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomOutMoreShortCut(bool checked) Q_DECL_OVERRIDE; + + /// Action slot to zoom in selection. + /// \param checked Boolean to indicate if the item checked. + virtual void OnZoomInSelection(bool checked) Q_DECL_OVERRIDE; + + /// Action slot to reset the view. + /// \param checked Boolean to indicate if the item checked. + virtual void OnResetView(bool checked) Q_DECL_OVERRIDE; + +private: + /// Setup keyboard shortcuts for zooming, etc. + void SetupKeyboardZoomShortcuts(); + + TimelinePane* parent_pane_; ///< The parent UI Pane. + QScrollBar* scroll_bar_; ///< The scrollbar used for zooming. + QGraphicsView* zoom_view_; ///< The graphics view to zoom. +}; + +#endif // RMV_VIEWS_TIMELINE_KEYBOARD_ZOOM_SHORTCUTS_TIMELINE_H_ diff --git a/source/frontend/views/timeline/timeline_pane.cpp b/source/frontend/views/timeline/timeline_pane.cpp new file mode 100644 index 0000000..0940876 --- /dev/null +++ b/source/frontend/views/timeline/timeline_pane.cpp @@ -0,0 +1,732 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation of Timeline pane. +//============================================================================= + +#include "views/timeline/timeline_pane.h" + +#include +#include +#include +#include + +#include "qt_common/custom_widgets/double_slider_widget.h" +#include "qt_common/utils/qt_util.h" +#include "qt_common/utils/scaling_manager.h" + +#include "rmt_assert.h" +#include "rmt_data_snapshot.h" +#include "rmt_util.h" + +#include "models/message_manager.h" +#include "models/proxy_models/snapshot_timeline_proxy_model.h" +#include "models/snapshot_manager.h" +#include "models/timeline/snapshot_item_model.h" +#include "models/timeline/timeline_model.h" +#include "models/trace_manager.h" +#include "settings/rmv_settings.h" +#include "util/time_util.h" +#include "views/main_window.h" +#include "views/pane_manager.h" + +#ifndef _WIN32 +#include +#endif + +const static QString kRenameAction = "Rename snapshot"; +const static QString kDeleteAction = "Delete snapshot"; +const static QString kCompareAction = "Compare snapshots"; + +/// Worker class definition to do the processing of the timeline generation +/// on a separate thread. +class TimelineWorker : public rmv::BackgroundTask +{ +public: + /// Constructor. + explicit TimelineWorker(rmv::TimelineModel* model, RmtDataTimelineType timeline_type) + : BackgroundTask() + , model_(model) + , timeline_type_(timeline_type) + { + } + + /// Destructor. + ~TimelineWorker() + { + } + + /// Worker thread function. + virtual void ThreadFunc() + { + model_->GenerateTimeline(timeline_type_); + } + +private: + rmv::TimelineModel* model_; ///< Pointer to the model data + RmtDataTimelineType timeline_type_; ///< The timeline type +}; + +TimelinePane::TimelinePane(MainWindow* parent) + : BasePane(parent) + , ui_(new Ui::TimelinePane) + , main_window_(parent) + , thread_controller_(nullptr) +{ + ui_->setupUi(this); + + rmv::widget_util::ApplyStandardPaneStyle(this, ui_->main_content_, ui_->main_scroll_area_); + + // fix up the ratios of the 2 splitter regions + ui_->splitter_->setStretchFactor(0, 5); + ui_->splitter_->setStretchFactor(1, 4); + + // initialize the snapshot legends + rmv::widget_util::InitGraphicsView(ui_->snapshot_legends_view_, rmv::kColoredLegendsHeight); + rmv::widget_util::InitColorLegend(snapshot_legends_, ui_->snapshot_legends_view_); + AddSnapshotLegends(); + + model_ = new rmv::TimelineModel(); + + model_->InitializeModel(ui_->snapshot_count_label_, rmv::kTimelineSnapshotCount, "text"); + model_->InitializeTableModel(ui_->snapshot_table_view_, 0, kSnapshotTimelineColumnCount); + + // Set default columns widths appropriately so that they can show the table contents. + ui_->snapshot_table_view_->SetColumnPadding(0); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnID, 10); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnName, 11); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnTime, 10); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnVirtualAllocations, 12); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnResources, 9); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnAllocatedTotalVirtualMemory, 14); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnAllocatedBoundVirtualMemory, 14); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnAllocatedUnboundVirtualMemory, 16); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnCommittedLocal, 16); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnCommittedInvisible, 18); + ui_->snapshot_table_view_->SetColumnWidthEms(kSnapshotTimelineColumnCommittedHost, 16); + + // Allow users to resize columns if desired. + ui_->snapshot_table_view_->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Interactive); + rmv::widget_util::UpdateTablePalette(ui_->snapshot_table_view_); + + ui_->snapshot_table_view_->horizontalHeader()->setSectionsClickable(true); + ui_->snapshot_table_view_->setSortingEnabled(true); + ui_->snapshot_table_view_->sortByColumn(kSnapshotTimelineColumnTime, Qt::AscendingOrder); + ui_->snapshot_table_view_->setEditTriggers(QAbstractItemView::EditKeyPressed); + + // hide columns that we are using for sorting + ui_->snapshot_table_view_->hideColumn(kSnapshotTimelineColumnID); + + // hide the snapshot legends for now. Currently not used but maybe needed in future + ui_->snapshot_legends_controls_wrapper_->hide(); + + // initialize the timeline type combo box + colorizer_ = new TimelineColorizer(); + + // Set up a list of required timeline modes, in order. + // The list is terminated with -1 + static const RmtDataTimelineType type_list[] = {kRmtDataTimelineTypeResourceUsageVirtualSize, + kRmtDataTimelineTypeResourceUsageCount, + kRmtDataTimelineTypeVirtualMemory, + kRmtDataTimelineTypeCommitted, + // kRmtDataTimelineTypeProcess, + RmtDataTimelineType(-1)}; + + // Initialize the "color by" UI elements. Set up the combo box, legends and signals etc + colorizer_->Initialize(parent, ui_->timeline_type_combo_box_, ui_->timeline_legends_view_, type_list); + connect(ui_->timeline_type_combo_box_, &ArrowIconComboBox::SelectionChanged, this, &TimelinePane::TimelineTypeChanged); + + model_->SetTimelineType(type_list[0]); + + // allow multiple snapshots to be selected so they can be compared. + ui_->snapshot_table_view_->setSelectionMode(QAbstractItemView::ExtendedSelection); + + // Set up the zoom buttons + ZoomIconManagerConfiguration zoom_config = {}; + zoom_config.zoom_in_button = ui_->zoom_in_button_; + zoom_config.zoom_in_resource_enabled = rmv::resource::kZoomInEnabled; + zoom_config.zoom_in_resource_disabled = rmv::resource::kZoomInDisabled; + zoom_config.zoom_out_button = ui_->zoom_out_button_; + zoom_config.zoom_out_resource_enabled = rmv::resource::kZoomOutEnabled; + zoom_config.zoom_out_resource_disabled = rmv::resource::kZoomOutDisabled; + zoom_config.zoom_reset_button = ui_->zoom_reset_button_; + zoom_config.zoom_reset_resource_enabled = rmv::resource::kZoomResetEnabled; + zoom_config.zoom_reset_resource_disabled = rmv::resource::kZoomResetDisabled; + zoom_config.zoom_to_selection_button = ui_->zoom_to_selection_button_; + zoom_config.zoom_to_selection_resource_enabled = rmv::resource::kZoomToSelectionEnabled; + zoom_config.zoom_to_selection_resource_disabled = rmv::resource::kZoomToSelectionDisabled; + + zoom_icon_manager_ = new ZoomIconGroupManager(zoom_config); + + rmv::widget_util::InitCommonFilteringComponents(ui_->search_box_, ui_->size_slider_); + + // hide size slider for now + ui_->size_slider_->hide(); + ui_->size_slider_label_->hide(); + + // disable the compare button + ui_->compare_button_->setEnabled(false); + + keyboard_zoom_shortcuts_ = new KeyboardZoomShortcutsTimeline(this, ui_->timeline_view_->horizontalScrollBar(), ui_->timeline_view_); + + connect(keyboard_zoom_shortcuts_, &KeyboardZoomShortcutsTimeline::ZoomInSelectionSignal, this, &TimelinePane::ZoomInSelection); + connect(keyboard_zoom_shortcuts_, &KeyboardZoomShortcutsTimeline::ResetViewSignal, this, &TimelinePane::ZoomReset); + connect(ui_->size_slider_, &DoubleSliderWidget::SpanChanged, this, &TimelinePane::FilterBySizeSliderChanged); + connect(ui_->search_box_, &QLineEdit::textChanged, this, &TimelinePane::SearchBoxChanged); + connect(ui_->zoom_to_selection_button_, &QPushButton::pressed, this, &TimelinePane::ZoomInSelection); + connect(ui_->zoom_reset_button_, &QPushButton::pressed, this, &TimelinePane::ZoomReset); + connect(ui_->zoom_in_button_, &QPushButton::pressed, this, &TimelinePane::ZoomIn); + connect(ui_->zoom_out_button_, &QPushButton::pressed, this, &TimelinePane::ZoomOut); + connect(ui_->timeline_view_, &RMVSnapshotTimeline::UpdateSelectedDuration, this, &TimelinePane::UpdateSelectedDuration); + connect(ui_->timeline_view_, &RMVSnapshotTimeline::UpdateHoverClock, this, &TimelinePane::UpdateHoverClock); + connect(ui_->snapshot_table_view_, &RMVSnapshotTableView::SelectionChanged, this, &TimelinePane::TableSelectionChanged); + connect(ui_->snapshot_table_view_, &RMVSnapshotTableView::doubleClicked, this, &TimelinePane::TableDoubleClicked); + connect(ui_->timeline_view_, &RMVSnapshotTimeline::GenerateSnapshotAtTime, this, &TimelinePane::GenerateSnapshotAtTime); + connect(ui_->timeline_view_, &RMVSnapshotTimeline::UpdateZoomButtonsForZoomIn, this, &TimelinePane::UpdateZoomButtonsForZoomIn); + connect(ui_->timeline_view_, &RMVSnapshotTimeline::UpdateZoomButtonsForZoomOut, this, &TimelinePane::UpdateZoomButtonsForZoomOut); + connect(ui_->timeline_view_, &RMVSnapshotTimeline::UpdateZoomButtonsForZoomToSelection, this, &TimelinePane::UpdateZoomButtonsForZoomToSelection); + connect(ui_->timeline_view_->horizontalScrollBar(), &QScrollBar::valueChanged, this, &TimelinePane::ScrollBarChanged); + connect(ui_->compare_button_, &QPushButton::pressed, this, &TimelinePane::CompareSnapshots); + connect(&MessageManager::Get(), &MessageManager::SelectSnapshot, this, &TimelinePane::SelectSnapshot); + + // set up a connection between the timeline being sorted and making sure the selected event is visible + connect(model_->GetProxyModel(), &rmv::SnapshotTimelineProxyModel::layoutChanged, this, &TimelinePane::ScrollToSelectedSnapshot); +} + +TimelinePane::~TimelinePane() +{ + delete zoom_icon_manager_; + delete colorizer_; +} + +void TimelinePane::showEvent(QShowEvent* event) +{ + SwitchTimeUnits(); + ui_->compare_button_->setEnabled(false); + SelectTableRows(); + QWidget::showEvent(event); +} + +void TimelinePane::SelectTableRows() +{ + ui_->snapshot_table_view_->setSelectionMode(QAbstractItemView::MultiSelection); + const RmtSnapshotPoint* snapshot_point = SnapshotManager::Get().GetSelectedSnapshotPoint(); + if (snapshot_point != nullptr) + { + const QModelIndex& index = model_->GetProxyModel()->FindModelIndex(((uintptr_t)snapshot_point), kSnapshotTimelineColumnID); + if (index.isValid()) + { + ui_->snapshot_table_view_->selectRow(index.row()); + } + } + ui_->snapshot_table_view_->setSelectionMode(QAbstractItemView::ExtendedSelection); +} + +void TimelinePane::OnTraceLoad() +{ + model_->Update(); + + // Add memory allocation widget. Qt will take ownership of the created timeline so + // it will get deleted when it's removed from the scene. + AddTimelineGraph(); + + UpdateSnapshotMarkers(); + + model_->ValidateTimeUnits(); + + ui_->timeline_view_->SetMaxClock(model_->GetMaxTimestamp()); + SwitchTimeUnits(); + + ui_->timeline_wrapper_->show(); + ui_->snapshot_table_view_->showColumn(kSnapshotTimelineColumnTime); + + model_->UpdateMemoryGraph(ui_->timeline_view_->ViewableStartClk(), ui_->timeline_view_->ViewableEndClk()); + colorizer_->UpdateLegends(); + + UpdateTableDisplay(); +} + +void TimelinePane::UpdateSnapshotMarkers() +{ + TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid() == false) + { + return; + } + + ui_->timeline_view_->ClearSnapshotMarkers(); + + // add snapshot widgets + RmtDataSet* data_set = trace_manager.GetDataSet(); + for (int32_t current_snapshot_point_index = 0; current_snapshot_point_index < data_set->snapshot_count; current_snapshot_point_index++) + { + RmtSnapshotPoint* current_snapshot_point = &data_set->snapshots[current_snapshot_point_index]; + RMVSnapshotMarker* marker = AddSnapshot(current_snapshot_point); + RMT_UNUSED(marker); + } + const RmtSnapshotPoint* snapshot_point = SnapshotManager::Get().GetSelectedSnapshotPoint(); + ui_->timeline_view_->SelectSnapshot(snapshot_point); +} + +void TimelinePane::OnTraceClose() +{ + // reset the timeline type combo back to default + int row_index = 0; + ui_->timeline_type_combo_box_->SetSelectedRow(row_index); + RmtDataTimelineType new_timeline_type = colorizer_->ApplyColorMode(row_index); + model_->SetTimelineType(new_timeline_type); + + ui_->timeline_view_->Clear(); +} + +void TimelinePane::UpdateSelectedDuration(uint64_t duration) +{ + QString text = "-"; + + if (duration != 0) + { + text = rmv::time_util::ClockToTimeUnit(duration); + } + + ui_->selection_clock_label_->setText(text); +} + +void TimelinePane::UpdateHoverClock(uint64_t clock) +{ + ui_->hover_clock_label_->setText(rmv::time_util::ClockToTimeUnit(clock)); +} + +void TimelinePane::Reset() +{ + model_->ResetModelValues(); + SnapshotManager::Get().SetSelectedSnapshotPoint(nullptr); + + ZoomReset(); + + ui_->size_slider_->SetLowerValue(0); + ui_->size_slider_->SetUpperValue(rmv::kSizeSliderRange); + ui_->search_box_->setText(""); +} + +void TimelinePane::SwitchTimeUnits() +{ + double ratio = rmv::time_util::TimeToClockRatio(); + ui_->timeline_view_->UpdateTimeUnits(RMVSettings::Get().GetUnits(), ratio); + model_->Update(); +} + +void TimelinePane::ChangeColoring() +{ + snapshot_legends_->Clear(); + AddSnapshotLegends(); +} + +void TimelinePane::ZoomInSelection() +{ + const bool zoom = ui_->timeline_view_->ZoomInSelection(); + + if (ui_->timeline_view_->RegionSelected()) + { + UpdateZoomButtonsForZoomIn(zoom); + } +} + +void TimelinePane::ZoomReset() +{ + zoom_icon_manager_->ZoomReset(); + UpdateTimelineScrollbarContextMenu(false); + + ui_->selection_clock_label_->setText("-"); + + ui_->timeline_view_->ZoomReset(); + model_->UpdateMemoryGraph(ui_->timeline_view_->ViewableStartClk(), ui_->timeline_view_->ViewableEndClk()); +} + +void TimelinePane::ZoomIn() +{ + const bool zoom = ui_->timeline_view_->ZoomIn(2, false); + UpdateZoomButtonsForZoomIn(zoom); +} + +void TimelinePane::ZoomOut() +{ + const bool zoom = ui_->timeline_view_->ZoomOut(2, false); + UpdateZoomButtonsForZoomOut(zoom); +} + +void TimelinePane::ZoomInCustom(int zoom_rate, bool use_mouse_pos) +{ + const bool zoom = ui_->timeline_view_->ZoomIn(zoom_rate, use_mouse_pos); + UpdateZoomButtonsForZoomIn(zoom); +} + +void TimelinePane::ZoomOutCustom(int zoom_rate, bool use_mouse_pos) +{ + const bool zoom = ui_->timeline_view_->ZoomOut(zoom_rate, use_mouse_pos); + UpdateZoomButtonsForZoomOut(zoom); +} + +void TimelinePane::UpdateZoomButtonsForZoomIn(bool zoom) +{ + zoom_icon_manager_->ZoomIn(zoom); + UpdateTimelineScrollbarContextMenu(true); + + model_->UpdateMemoryGraph(ui_->timeline_view_->ViewableStartClk(), ui_->timeline_view_->ViewableEndClk()); +} + +void TimelinePane::UpdateZoomButtonsForZoomOut(bool zoom) +{ + zoom_icon_manager_->ZoomOut(zoom); + UpdateTimelineScrollbarContextMenu(zoom); + + model_->UpdateMemoryGraph(ui_->timeline_view_->ViewableStartClk(), ui_->timeline_view_->ViewableEndClk()); +} + +void TimelinePane::UpdateTimelineScrollbarContextMenu(bool shown) +{ + // hide the right-click context menu on the scrollbar if fully zoomed out. + QScrollBar* scroll_bar = ui_->timeline_view_->horizontalScrollBar(); + if (scroll_bar != nullptr) + { + if (shown == true) + { + scroll_bar->setContextMenuPolicy(Qt::DefaultContextMenu); + } + else + { + scroll_bar->setContextMenuPolicy(Qt::NoContextMenu); + } + } +} + +void TimelinePane::UpdateZoomButtonsForZoomToSelection(bool selected_region) +{ + zoom_icon_manager_->ZoomToSelection(selected_region); +} + +void TimelinePane::ScrollBarChanged() +{ + model_->UpdateMemoryGraph(ui_->timeline_view_->ViewableStartClk(), ui_->timeline_view_->ViewableEndClk()); +} + +void TimelinePane::FilterBySizeSliderChanged(int min_value, int max_value) +{ + model_->FilterBySizeChanged(min_value, max_value); +} + +void TimelinePane::SearchBoxChanged() +{ + model_->SearchBoxChanged(ui_->search_box_->text()); + SetMaximumSnapshotTableHeight(); +} + +void TimelinePane::CompareSnapshots() +{ + QItemSelectionModel* pItemSelectionModel = ui_->snapshot_table_view_->selectionModel(); + QModelIndexList selected_rows = pItemSelectionModel->selectedRows(); + + if (selected_rows.count() == 2) + { + RmtSnapshotPoint* snapshot_point_base = + reinterpret_cast(model_->GetProxyData(selected_rows[kSnapshotCompareBase].row(), kSnapshotTimelineColumnID)); + RmtSnapshotPoint* snapshot_point_diff = + reinterpret_cast(model_->GetProxyData(selected_rows[kSnapshotCompareDiff].row(), kSnapshotTimelineColumnID)); + + emit MessageManager::Get().CompareSnapshot(snapshot_point_base, snapshot_point_diff); + } +} + +void TimelinePane::SelectSnapshot(RmtSnapshotPoint* snapshot_point) +{ + ui_->timeline_view_->SelectSnapshot(snapshot_point); + + const QModelIndex& index = model_->GetProxyModel()->FindModelIndex(((uintptr_t)snapshot_point), kSnapshotTimelineColumnID); + + if (index.isValid() == true) + { + ui_->snapshot_table_view_->selectRow(index.row()); + + QItemSelectionModel* selected_item = ui_->snapshot_table_view_->selectionModel(); + if (selected_item->hasSelection()) + { + QModelIndexList selected_rows = selected_item->selectedRows(); + if (selected_rows.size() > 0) + { + ui_->compare_button_->setEnabled(selected_rows.count() == 2); + } + } + + //ui_->snapshot_table_view_->setFocus(Qt::FocusReason::OtherFocusReason); + } + SnapshotManager::Get().SetSelectedSnapshotPoint(snapshot_point); +} + +void TimelinePane::TableSelectionChanged() +{ + const QItemSelectionModel* selection_model = ui_->snapshot_table_view_->selectionModel(); + const QModelIndexList& selected_rows = selection_model->selectedRows(); + const QModelIndex current_index = selection_model->currentIndex(); + bool is_selected = selection_model->isSelected(current_index); + + if (is_selected == true) + { + if (current_index.isValid()) + { + RmtSnapshotPoint* selected_snapshot = reinterpret_cast(model_->GetProxyData(current_index.row(), kSnapshotTimelineColumnID)); + SelectSnapshot(selected_snapshot); + } + } + else + { + SnapshotManager::Get().SetSelectedSnapshotPoint(nullptr); + ui_->timeline_view_->SelectSnapshot(nullptr); + } + + // Enable the compare button if 2 entries in the table are selected + ui_->compare_button_->setEnabled(selected_rows.count() == 2); +} + +void TimelinePane::TableDoubleClicked(const QModelIndex& index) +{ + if (index.isValid()) + { + RmtSnapshotPoint* snapshot_point = reinterpret_cast(model_->GetProxyData(index.row(), kSnapshotTimelineColumnID)); + + emit MessageManager::Get().OpenSnapshot(snapshot_point); + } +} + +void TimelinePane::GenerateSnapshotAtTime(uint64_t snapshot_time) +{ + RmtSnapshotPoint* snapshot_point = model_->AddSnapshot(snapshot_time); + if (snapshot_point != nullptr) + { + ui_->timeline_view_->AddSnapshot(snapshot_point); + UpdateTableDisplay(); + } +} + +RMVTimelineGraph* TimelinePane::AddTimelineGraph() +{ + RMVTimelineGraph* new_object = ui_->timeline_view_->AddTimelineGraph(model_, colorizer_); + return new_object; +} + +RMVSnapshotMarker* TimelinePane::AddSnapshot(RmtSnapshotPoint* snapshot_point) +{ + if (snapshot_point == nullptr) + { + return nullptr; + } + + RMVSnapshotMarker* new_marker = ui_->timeline_view_->AddSnapshot(snapshot_point); + return new_marker; +} + +void TimelinePane::RemoveSnapshot(RmtSnapshotPoint* snapshot_point) +{ + const TraceManager& trace_manager = TraceManager::Get(); + if (trace_manager.DataSetValid() == false) + { + return; + } + + // if the snapshot point has a cached snapshot (i.e.: there's a chance its open, then look at closing it) + if (snapshot_point->cached_snapshot) + { + // if we're about to remove the snapshot that's open, then signal to everyone its about to vanish. + const RmtDataSnapshot* open_snapshot = trace_manager.GetOpenSnapshot(); + if (open_snapshot == snapshot_point->cached_snapshot) + { + if (open_snapshot != nullptr) + { + emit MessageManager::Get().OpenSnapshot(nullptr); + } + } + + if (trace_manager.GetComparedSnapshot(kSnapshotCompareBase) == snapshot_point->cached_snapshot || + trace_manager.GetComparedSnapshot(kSnapshotCompareDiff) == snapshot_point->cached_snapshot) + { + if (trace_manager.GetComparedSnapshot(kSnapshotCompareBase) != nullptr || trace_manager.GetComparedSnapshot(kSnapshotCompareDiff) != nullptr) + { + emit MessageManager::Get().CompareSnapshot(nullptr, nullptr); + } + } + } + + // deselect the selected snapshot if it's being removed + if (snapshot_point == SnapshotManager::Get().GetSelectedSnapshotPoint()) + { + SnapshotManager::Get().SetSelectedSnapshotPoint(nullptr); + } + + model_->RemoveSnapshot(snapshot_point); + + UpdateSnapshotMarkers(); + UpdateTableDisplay(); +} + +void TimelinePane::RenameSnapshotByIndex(int32_t snapshot_index) +{ + QModelIndex model_index = ui_->snapshot_table_view_->model()->index(snapshot_index, kSnapshotTimelineColumnName, QModelIndex()); + ui_->snapshot_table_view_->edit(model_index); +} + +void TimelinePane::keyPressEvent(QKeyEvent* event) +{ + const int key = event->key(); + + if (ui_->timeline_view_->GetResetState() == false) + { + if (keyboard_zoom_shortcuts_->KeyPressed(key, event->isAutoRepeat()) == false) + { + QWidget::keyPressEvent(event); + } + } +} + +void TimelinePane::keyReleaseEvent(QKeyEvent* event) +{ + const int key = event->key(); + + if (keyboard_zoom_shortcuts_->KeyReleased(key, event->isAutoRepeat()) == false) + { + QWidget::keyReleaseEvent(event); + } +} + +void TimelinePane::resizeEvent(QResizeEvent* event) +{ + if (model_ != nullptr) + { + model_->UpdateMemoryGraph(ui_->timeline_view_->ViewableStartClk(), ui_->timeline_view_->ViewableEndClk()); + } + QWidget::resizeEvent(event); +} + +void TimelinePane::contextMenuEvent(QContextMenuEvent* event) +{ + // Check that there are exactly two selected objects -- offer to compare them. + // Else offer to remove the snapshot. + QItemSelectionModel* item_selection_model = ui_->snapshot_table_view_->selectionModel(); + + if (!item_selection_model->hasSelection()) + { + return; + } + + // Get the number of rows in the table selected + QModelIndexList selected_rows = item_selection_model->selectedRows(); + + if (selected_rows.count() == 1) + { + // if 1 row selected, allow user to rename or delete a snapshot + QMenu menu; + QAction rename_action(kRenameAction); + QAction delete_action(kDeleteAction); + + menu.addAction(&rename_action); + menu.addAction(&delete_action); + + QAction* pAction = menu.exec(event->globalPos()); + + if (pAction != nullptr) + { + QString selection_text = pAction->text(); + if (selection_text.compare(kDeleteAction) == 0) + { + RmtSnapshotPoint* snapshot_point = reinterpret_cast(model_->GetProxyData(selected_rows[0].row(), kSnapshotTimelineColumnID)); + RemoveSnapshot(snapshot_point); + } + else if (selection_text.compare(kRenameAction) == 0) + { + const int32_t snapshot_id = selected_rows[0].row(); + RenameSnapshotByIndex(snapshot_id); + } + } + return; + } + + if (selected_rows.count() == 2) + { + // if 2 rows selected, allow user to compare snapshots + QAction action(kCompareAction); + + QMenu menu; + menu.addAction(&action); + + QAction* pAction = menu.exec(event->globalPos()); + + if (pAction != nullptr) + { + RmtSnapshotPoint* snapshot_point_base = + reinterpret_cast(model_->GetProxyData(selected_rows[kSnapshotCompareBase].row(), kSnapshotTimelineColumnID)); + RmtSnapshotPoint* snapshot_point_diff = + reinterpret_cast(model_->GetProxyData(selected_rows[kSnapshotCompareDiff].row(), kSnapshotTimelineColumnID)); + emit MessageManager::Get().CompareSnapshot(snapshot_point_base, snapshot_point_diff); + } + + return; + } +} + +void TimelinePane::TimelineTypeChanged() +{ + int index = ui_->timeline_type_combo_box_->CurrentRow(); + if (index >= 0) + { + RmtDataTimelineType new_timeline_type = colorizer_->ApplyColorMode(index); + model_->SetTimelineType(new_timeline_type); + + // start the processing thread and pass in the worker object. The thread controller will take ownership + // of the worker and delete it once it's complete + thread_controller_ = new rmv::ThreadController(main_window_, ui_->timeline_view_, new TimelineWorker(model_, new_timeline_type)); + + // when the worker thread has finished, a signal will be emitted. Wait for the signal here and update + // the UI with the newly acquired data from the worker thread + connect(thread_controller_, &rmv::ThreadController::ThreadFinished, this, &TimelinePane::TimelineWorkerThreadFinished); + } +} + +void TimelinePane::TimelineWorkerThreadFinished() +{ + disconnect(thread_controller_, &rmv::ThreadController::ThreadFinished, this, &TimelinePane::TimelineWorkerThreadFinished); + thread_controller_->deleteLater(); + thread_controller_ = nullptr; + + model_->UpdateMemoryGraph(ui_->timeline_view_->ViewableStartClk(), ui_->timeline_view_->ViewableEndClk()); + ui_->timeline_view_->viewport()->update(); + colorizer_->UpdateLegends(); +} + +void TimelinePane::ScrollToSelectedSnapshot() +{ + QItemSelectionModel* selected_item = ui_->snapshot_table_view_->selectionModel(); + if (selected_item->hasSelection()) + { + QModelIndexList item_list = selected_item->selectedRows(); + if (item_list.size() > 0) + { + // get the model index of the name column since column 0 (ID) is hidden and scrollTo + // doesn't appear to scroll on hidden columns + QModelIndex model_index = model_->GetProxyModel()->index(item_list[0].row(), kSnapshotTimelineColumnName); + ui_->snapshot_table_view_->scrollTo(model_index, QAbstractItemView::ScrollHint::PositionAtTop); + } + } +} + +void TimelinePane::UpdateTableDisplay() +{ + int index = (model_->RowCount() == 0) ? 0 : 1; + ui_->snapshot_table_valid_switch_->setCurrentIndex(index); + + SetMaximumSnapshotTableHeight(); +} + +void TimelinePane::AddSnapshotLegends() +{ + // Commented out for now but kept for reference as may be used later. + //snapshot_legends_->AddColorLegendItem(GetSnapshotTypeColor(RMV_SNAPSHOT_TYPE_LIVE), "Live snapshot"); + //snapshot_legends_->AddColorLegendItem(GetSnapshotTypeColor(RMV_SNAPSHOT_TYPE_GENERATED), "Generated snapshot"); +} diff --git a/source/frontend/views/timeline/timeline_pane.h b/source/frontend/views/timeline/timeline_pane.h new file mode 100644 index 0000000..c8a5488 --- /dev/null +++ b/source/frontend/views/timeline/timeline_pane.h @@ -0,0 +1,220 @@ +//============================================================================= +/// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header for the Timeline pane. +//============================================================================= + +#ifndef RMV_VIEWS_TIMELINE_TIMELINE_PANE_H_ +#define RMV_VIEWS_TIMELINE_TIMELINE_PANE_H_ + +#include "ui_timeline_pane.h" + +#include "qt_common/custom_widgets/colored_legend_scene.h" +#include "qt_common/custom_widgets/scaled_table_view.h" +#include "qt_common/utils/zoom_icon_group_manager.h" + +#include "rmt_data_set.h" + +#include "models/timeline/timeline_model.h" +#include "views/base_pane.h" +#include "views/custom_widgets/rmv_snapshot_marker.h" +#include "views/custom_widgets/rmv_timeline_graph.h" +#include "views/timeline_colorizer.h" +#include "views/timeline/keyboard_zoom_shortcuts_timeline.h" +#include "util/definitions.h" +#include "util/thread_controller.h" +#include "util/widget_util.h" + +class MainWindow; + +/// Class declaration for the timeline pane +class TimelinePane : public BasePane +{ + Q_OBJECT + +public: + /// Constructor. + /// \param parent The widget's parent. + explicit TimelinePane(MainWindow* parent = nullptr); + + /// Destructor. + virtual ~TimelinePane(); + + // Overridden Qt events. + + /// Overridden show event. Fired when this pane is opened. + /// \param event The event. + virtual void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; + + /// Key pressed event handler. + /// \param event The event data for this event. + virtual void keyPressEvent(QKeyEvent* event) Q_DECL_OVERRIDE; + + /// Key released event handler. + /// \param event The event data for this event. + virtual void keyReleaseEvent(QKeyEvent* event) Q_DECL_OVERRIDE; + + /// Create a context menu to add a new snapshot. + /// \param event The event. + virtual void contextMenuEvent(QContextMenuEvent* event) Q_DECL_OVERRIDE; + + /// Overridden window resize event. + /// \param event the resize event object. + virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; + + /// Refresh what's visible on the UI. + void OnTraceLoad(); + + // Handlers from BasePane + + /// Clean up. + virtual void OnTraceClose() Q_DECL_OVERRIDE; + + /// Reset UI state. + virtual void Reset() Q_DECL_OVERRIDE; + + /// Update time units. + virtual void SwitchTimeUnits() Q_DECL_OVERRIDE; + + /// Update UI coloring. + virtual void ChangeColoring() Q_DECL_OVERRIDE; + +public slots: + /// Zoom into selection box. + void ZoomInSelection(); + + /// Reset view. + void ZoomReset(); + + /// Zoom in by 2x. + void ZoomIn(); + + /// Zoom out by 2x. + void ZoomOut(); + + /// Wrapper around zoom in function. + /// \param zoom_rate The zoom rate (a value of 2 would be a x2 zoom in). + /// \param use_mouse_pos If true, zoom around the mouse position, otherwise zoom + /// around the view center position. + void ZoomInCustom(int zoom_rate, bool use_mouse_pos); + + /// Wrapper around zoom out function. + /// \param zoom_rate The zoom rate (a value of 2 would be a x2 zoom out). + /// \param use_mouse_pos If true, zoom around the mouse position, otherwise zoom + /// around the view center position. + void ZoomOutCustom(int zoom_rate, bool use_mouse_pos); + + /// Update the duration label. + /// \param duration The new duration. + void UpdateSelectedDuration(uint64_t duration); + + /// Update the hover over clock label. + /// \param clock The new timestamp in clocks. + void UpdateHoverClock(uint64_t clock); + +private slots: + /// Slot to handle what happens when the 'filter by size' slider changes. + /// \param min_value Minimum value of slider span. + /// \param max_value Maximum value of slider span. + void FilterBySizeSliderChanged(int min_value, int max_value); + + /// Handle what happens when user changes the search filter. + void SearchBoxChanged(); + + /// Slot to compare 2 snapshots via the "compare snsapshots" button. The + /// snapshots have already been selected in the snapshot table. + void CompareSnapshots(); + + /// Select a snapshot. + /// \param snapshot_point The snapshot selected. + void SelectSnapshot(RmtSnapshotPoint* snapshot_point); + + /// Highlight an entry in the snapshot table. + void TableSelectionChanged(); + + /// Handle what happens when an item in the snapshot table is double-clicked on. + /// \param index The model index of the item clicked. + void TableDoubleClicked(const QModelIndex& index); + + /// Create a new snapshot. + /// \param snapshot_time The time the snapshot was taken. + void GenerateSnapshotAtTime(uint64_t snapshot_time); + + /// Set the zoom buttons to the correct configuration after a zoom in. + /// \param zoom Flag indicating if further zoom possible, true if so. + void UpdateZoomButtonsForZoomIn(bool zoom); + + /// Set the zoom buttons to the correct configuration after a zoom out. + /// \param zoom Flag indicating if further zoom possible, true if so. + void UpdateZoomButtonsForZoomOut(bool zoom); + + /// Set the zoom to selection icon to the correct state after a region selection. + /// \param selectedRegion If true, a region is selected. + void UpdateZoomButtonsForZoomToSelection(bool selected_region); + + /// Slot to handle what happens when the scroll bar on the timeline changes. + void ScrollBarChanged(); + + /// Slot to handle what happens when the timeline type changes via the combo box. + void TimelineTypeChanged(); + + /// Slot to handle what happens when the timeline worker thread has finished. + void TimelineWorkerThreadFinished(); + + /// Slot to handle what happens after the resource list table is sorted. + /// Make sure the selected item (if there is one) is visible. + void ScrollToSelectedSnapshot(); + +private: + /// Update the snapshot markers on the graph. + void UpdateSnapshotMarkers(); + + /// Add a new allocation graph to the timeline. + /// \return pointer to a new snapshot graphics object. + RMVTimelineGraph* AddTimelineGraph(); + + /// Add a new snapshot to the timeline. + /// \param snapshot_point The snapshot point to add. + /// \return pointer to a new marker graphics object. + RMVSnapshotMarker* AddSnapshot(RmtSnapshotPoint* snapshot_point); + + /// Remove a snapshot from the timeline. + /// \param snapshot_point The snapshot point. + void RemoveSnapshot(RmtSnapshotPoint* snapshot_point); + + /// Rename a snapshot from the timeline. + /// \param snapshot_index The index of the snapshot to rename. + void RenameSnapshotByIndex(int snapshot_index); + + /// Update the snapshot table display area. If no snapshots have been created, inform the user as such. + void UpdateTableDisplay(); + + /// Select rows in the snapshots table depending on which snapshot is currently selected. + void SelectTableRows(); + + /// Add the snapshot legends to the required scene. + void AddSnapshotLegends(); + + /// Update the right-click context menu on the timeline scrollbar. By default this is shown but + /// it should be disabled if the view is fully zoomed out. + /// \param shown Should the context menu be shown. + void UpdateTimelineScrollbarContextMenu(bool shown); + + /// Helper function to set the maximum height of the table so it only contains rows with valid data. + inline void SetMaximumSnapshotTableHeight() + { + ui_->snapshot_table_view_->setMaximumHeight(rmv::widget_util::GetTableHeight(ui_->snapshot_table_view_, model_->RowCount())); + } + + Ui::TimelinePane* ui_; ///< Pointer to the Qt UI design. + ZoomIconGroupManager* zoom_icon_manager_; ///< The object responsible for the zoom icon status. + MainWindow* main_window_; ///< Reference to the mainwindow (parent). + rmv::TimelineModel* model_; ///< Container class for the widget models. + ColoredLegendScene* snapshot_legends_; ///< Snapshot legends above the timeline. + KeyboardZoomShortcutsTimeline* keyboard_zoom_shortcuts_; ///< Keyboard shortcut handler. + TimelineColorizer* colorizer_; ///< The colorizer used by the 'timeline type' combo box. + rmv::ThreadController* thread_controller_; ///< The thread for processing backend data. +}; + +#endif // RMV_VIEWS_TIMELINE_TIMELINE_PANE_H_ diff --git a/source/frontend/views/timeline/timeline_pane.ui b/source/frontend/views/timeline/timeline_pane.ui new file mode 100644 index 0000000..2fb09c7 --- /dev/null +++ b/source/frontend/views/timeline/timeline_pane.ui @@ -0,0 +1,820 @@ + + + TimelinePane + + + + 0 + 0 + 726 + 704 + + + + + + + true + + + + + 0 + 0 + 689 + 687 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + + + 0 + + + 0 + + + 8 + + + + + + 0 + 0 + + + + + 75 + true + + + + Snapshot timeline + + + + + + + + 0 + 0 + + + + Right-click on the timeline below to generate a snapshot at that moment in time. + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Compare snapshots + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + Zoom to selection + + + + + + + 20 + 20 + + + + true + + + + + + + + 0 + 0 + + + + Reset zoom + + + + + + + 20 + 20 + + + + true + + + + + + + + 0 + 0 + + + + Zoom in + + + + + + + 20 + 20 + + + + true + + + + + + + + 0 + 0 + + + + Zoom out + + + + + + + 20 + 20 + + + + true + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 10 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 10 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Selection: + + + + + + + + 0 + 0 + + + + - + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + - + + + + + + + + + + + + + + + + + + 0 + + + 8 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + :/Resources/assets/stop_128x128.png + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 12 + + + + No snapshots have been generated. + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 12 + + + + To generate a snapshot, right-click on the graph above at the time required. + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Filter by size: + + + + + + + Qt::Horizontal + + + + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 40 + 5 + + + + QSizePolicy::MinimumExpanding + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Snapshot count: + + + + + + + + 0 + 0 + + + + {0} + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + ArrowIconComboBox + QWidget +
qt_common/custom_widgets/arrow_icon_combo_box.h
+ 1 +
+ + ColoredLegendGraphicsView + QGraphicsView +
qt_common/custom_widgets/colored_legend_graphics_view.h
+
+ + DoubleSliderWidget + QSlider +
qt_common/custom_widgets/double_slider_widget.h
+
+ + ScaledLabel + QLabel +
qt_common/custom_widgets/scaled_label.h
+
+ + ScaledPushButton + QPushButton +
qt_common/custom_widgets/scaled_push_button.h
+
+ + TextSearchWidget + QLineEdit +
qt_common/custom_widgets/text_search_widget.h
+
+ + RMVSnapshotTableView + ScaledTableView +
views/custom_widgets/rmv_snapshot_table_view.h
+
+ + RMVSnapshotTimeline + QGraphicsView +
views/custom_widgets/rmv_snapshot_timeline.h
+
+
+ + + + +
diff --git a/source/frontend/views/timeline_colorizer.cpp b/source/frontend/views/timeline_colorizer.cpp new file mode 100644 index 0000000..b697cb4 --- /dev/null +++ b/source/frontend/views/timeline_colorizer.cpp @@ -0,0 +1,95 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Implementation for the timeline colorizer control. +/// The colorizer is responsible for the functionality for the coloring the +/// timeline depending on the timeline type. It sets up the timeline type +/// combo box with the required timeline types currently supported by the +/// backend and updates the timeline and the legends depending on which +/// coloring mode is required. +//============================================================================= + +#include "views/timeline_colorizer.h" + +#include + +#include "rmt_assert.h" + +#include "util/widget_util.h" + +TimelineColorizer::TimelineColorizer() + : timeline_type_(kRmtDataTimelineTypeResourceUsageVirtualSize) + , timeline_type_map_{} +{ +} + +TimelineColorizer::~TimelineColorizer() +{ +} + +void TimelineColorizer::Initialize(QWidget* parent, ArrowIconComboBox* combo_box, ColoredLegendGraphicsView* legends_view, const RmtDataTimelineType* type_list) +{ + // map of timeline type to text string. These need to match the RmtDataTimelineType struct + struct TimelineInfo + { + QString text; + ColorMode color_mode; + }; + + static std::vector type_info = {{QString("Process"), kColorModeCount}, + {QString("Page size"), kColorModeCount}, + {QString("Committed"), kColorModePreferredHeap}, + {QString("Resource usage count"), kColorModeResourceUsageType}, + {QString("Resource usage size"), kColorModeResourceUsageType}, + {QString("Paging"), kColorModeCount}, + {QString("Virtual memory heap"), kColorModePreferredHeap}}; + + // Set up the combo box. Get the title string from the first entry in mode_list + QString combo_title_string = type_info[0].text; + RMT_ASSERT(type_list != nullptr); + if (type_list != nullptr) + { + timeline_type_ = type_list[0]; + if (timeline_type_ < type_info.size()) + { + combo_title_string = type_info[timeline_type_].text; + color_mode_ = type_info[timeline_type_].color_mode; + } + } + + rmv::widget_util::InitSingleSelectComboBox(parent, combo_box, combo_title_string, false); + + // Add the required coloring modes to the combo box and the internal map + combo_box->ClearItems(); + if (type_list != nullptr) + { + int list_index = 0; + bool done = false; + do + { + int index = type_list[list_index]; + if (index > -1) + { + combo_box->AddItem(type_info[index].text); + timeline_type_map_[list_index] = static_cast(index); + color_mode_map_[list_index] = type_info[index].color_mode; + list_index++; + } + else + { + done = true; + } + } while (!done); + } + + ColorizerBase::Initialize(combo_box, legends_view); +} + +RmtDataTimelineType TimelineColorizer::ApplyColorMode(int index) +{ + timeline_type_ = timeline_type_map_[index]; + color_mode_ = color_mode_map_[index]; + + return timeline_type_; +} diff --git a/source/frontend/views/timeline_colorizer.h b/source/frontend/views/timeline_colorizer.h new file mode 100644 index 0000000..e3d9931 --- /dev/null +++ b/source/frontend/views/timeline_colorizer.h @@ -0,0 +1,55 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Header file for the timeline colorizer control. +/// The colorizer is responsible for the functionality for the coloring the +/// timeline depending on the timeline type. It sets up the timeline type +/// combo box with the required timeline types currently supported by the +/// backend and updates the timeline and the legends depending on which +/// coloring mode is required. +//============================================================================= + +#ifndef RMV_VIEWS_TIMELINE_COLORIZER_H_ +#define RMV_VIEWS_TIMELINE_COLORIZER_H_ + +#include + +#include "qt_common/custom_widgets/arrow_icon_combo_box.h" +#include "qt_common/custom_widgets/colored_legend_graphics_view.h" + +#include "rmt_format.h" +#include "rmt_resource_list.h" +#include "rmt_data_timeline.h" + +#include "views/colorizer_base.h" + +/// Class to handle control of the timeline type combo boxes and picking which colors to use. +class TimelineColorizer : public ColorizerBase +{ +public: + /// Constructor. + explicit TimelineColorizer(); + + /// Destructor. + virtual ~TimelineColorizer(); + + /// Initialize the timeline colorizer. + /// \param parent The parent pane or widget. + /// \param combo_box The 'color by' combo box to set up. + /// \param legends_view The graphics view containing the color legends. + /// \param type_list The list of timeline types required. + void Initialize(QWidget* parent, ArrowIconComboBox* combo_box, ColoredLegendGraphicsView* legends_view, const RmtDataTimelineType* type_list); + + /// Called when the combo box is selected. Update the internal data + /// based on the selection in the combo box. + /// \param index The index of the combo box item selected. + /// \return The new timeline type selected. + RmtDataTimelineType ApplyColorMode(int index); + +private: + RmtDataTimelineType timeline_type_; ///< The timeline type. + RmtDataTimelineType timeline_type_map_[kRmtDataTimelineTypeCount]; ///< The mapping of combo box index to timeline type. +}; + +#endif // RMV_VIEWS_TIMELINE_COLORIZER_H_ diff --git a/source/frontend/windows/resource.h b/source/frontend/windows/resource.h new file mode 100644 index 0000000..4884ad9 --- /dev/null +++ b/source/frontend/windows/resource.h @@ -0,0 +1,12 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by RMV.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#endif +#endif diff --git a/source/frontend/windows/rmv.rc b/source/frontend/windows/rmv.rc new file mode 100644 index 0000000..7cb6551 --- /dev/null +++ b/source/frontend/windows/rmv.rc @@ -0,0 +1,73 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "../util/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "windows.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "rmv_icon.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION RMV_MAJOR_VERSION, RMV_MINOR_VERSION, RMV_BUGFIX_NUMBER, RMV_BUILD_NUMBER + PRODUCTVERSION RMV_MAJOR_VERSION, RMV_MINOR_VERSION, RMV_BUGFIX_NUMBER, RMV_BUILD_NUMBER + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Advanced Micro Devices, Inc." + VALUE "FileDescription", RMV_APP_NAME + VALUE "FileVersion", RMV_VERSION_STRING + VALUE "InternalName", RMV_APP_NAME + VALUE "OriginalFilename", "RMV" ".exe" + VALUE "LegalCopyright", RMV_COPYRIGHT_STRING + VALUE "ProductName", RMV_APP_NAME + VALUE "ProductVersion", RMV_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// diff --git a/source/frontend/windows/rmv_icon.ico b/source/frontend/windows/rmv_icon.ico new file mode 100644 index 0000000..69fcf35 Binary files /dev/null and b/source/frontend/windows/rmv_icon.ico differ diff --git a/source/parser/CMakeLists.txt b/source/parser/CMakeLists.txt new file mode 100644 index 0000000..97e7ac2 --- /dev/null +++ b/source/parser/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.5.1) +project(RmvParser) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +include_directories(AFTER ../parser) + +IF(UNIX) + # Remove warnings for Linux in backend + add_compile_options(-std=c++11 -D_LINUX -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-sign-compare -Wno-uninitialized) +ENDIF(UNIX) + +# List of all source files. It may be possible to have the build process call cmake to update the makefiles +# only when this file has changed (ie source files have been added or removed) + +set( SOURCES + "rmt_address_helper.cpp" + "rmt_address_helper.h" + "rmt_assert.cpp" + "rmt_assert.h" + "rmt_error.h" + "rmt_file_format.h" + "rmt_file_format.cpp" + "rmt_format.h" + "rmt_parser.cpp" + "rmt_parser.h" + "rmt_platform.cpp" + "rmt_platform.h" + "rmt_print.cpp" + "rmt_print.h" + "rmt_profile.h" + "rmt_token_heap.cpp" + "rmt_token_heap.h" + "rmt_types.h" + "rmt_util.h" +) + +set( LINUX_SOURCES + "linux/safe_crt.cpp" + "linux/safe_crt.h" +) + +# specify output library name +IF(WIN32) + add_library(${PROJECT_NAME} ${SOURCES}) +ELSEIF(UNIX) + add_library(${PROJECT_NAME} ${SOURCES} ${LINUX_SOURCES}) +ENDIF(WIN32) + +set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) + +IF(WIN32) +# Create Visual Studio filters so that the source files in the project match the directory structure +foreach(source IN LISTS SOURCES) + get_filename_component(source_path "${source}" PATH) + string(REPLACE "/" "\\" source_path_msvc "${source_path}") + source_group("${source_path_msvc}" FILES "${source}") +endforeach() +ENDIF(WIN32) + diff --git a/source/parser/linux/safe_crt.cpp b/source/parser/linux/safe_crt.cpp new file mode 100644 index 0000000..b3485b3 --- /dev/null +++ b/source/parser/linux/safe_crt.cpp @@ -0,0 +1,77 @@ +//============================================================================== +/// Copyright (c) 2016-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Linux implementation of Windows safe CRT functions +//============================================================================== + +#if !defined(_WIN32) + +#include +#include +#include +#include + +#include "safe_crt.h" +#include "../parser/rmt_util.h" + +errno_t fopen_s(FILE** file, const char* filename, const char* mode) +{ + if (file == 0 || filename == 0 || mode == 0) + { + return EINVAL; + } + + *file = fopen(filename, mode); + + if (*file) + { + return errno; + } + + return 0; +} + +int sprintf_s(char* buffer, size_t size_of_buffer, const char* format, ...) +{ + int ret_val; + va_list arg_ptr; + + va_start(arg_ptr, format); + ret_val = vsnprintf(buffer, size_of_buffer, format, arg_ptr); + va_end(arg_ptr); + return ret_val; +} + +int fprintf_s(FILE* stream, const char* format, ...) +{ + int ret_val; + va_list arg_ptr; + + va_start(arg_ptr, format); + ret_val = vfprintf(stream, format, arg_ptr); + va_end(arg_ptr); + return ret_val; +} + +size_t fread_s(void* buffer, size_t buffer_size, size_t element_size, size_t count, FILE* stream) +{ + RMT_UNUSED(count); + return fread(buffer, element_size, buffer_size, stream); +} + +errno_t strcpy_s(char* destination, size_t size, const char* source) +{ + RMT_UNUSED(size); + strcpy(destination, source); + return 0; +} + +errno_t strcat_s(char* destination, size_t size, const char* source) +{ + RMT_UNUSED(size); + strcat(destination, source); + return 0; +} + +#endif // !_WIN32 diff --git a/source/parser/linux/safe_crt.h b/source/parser/linux/safe_crt.h new file mode 100644 index 0000000..3382072 --- /dev/null +++ b/source/parser/linux/safe_crt.h @@ -0,0 +1,66 @@ +//============================================================================== +/// Copyright (c) 2016-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Developer Tools Team +/// \file +/// \brief Linux definition of Windows safe CRT functions +//============================================================================== + +#ifndef RMV_PARSER_LINUX_SAFE_CRT_H_ +#define RMV_PARSER_LINUX_SAFE_CRT_H_ + +#if !defined(_WIN32) + +/// errno_t defined so that the function prototypes match the Windows function prototypes. +typedef int errno_t; + +/// fopen_s secure version of fopen. +/// \param file A pointer to the file pointer that will receive the pointer to the opened file. +/// \param filename The filename of the file to be opened. +/// \param mode Type of access permitted. +/// \return Zero if successful; an error code on failure. +errno_t fopen_s(FILE** file, const char* filename, const char* mode); + +/// sprintf_s secure version of sprintf. +/// \param buffer Storage location for output. +/// \param size_of_buffer Maximum number of characters to store. +/// \param format Format-control string. +/// \param ... Optional arguments to be formatted. +/// \return The number of characters written or -1 if an error occurred. +int sprintf_s(char* buffer, size_t size_of_buffer, const char* format, ...); + +/// fprintf_s secure version of fprintf. +/// \param stream Pointer to FILE structure. +/// \param format Format-control string. +/// \param ... Optional arguments to be formatted. +/// \return The number of bytes written, or a negative value when an output error occurs. +int fprintf_s(FILE* stream, const char* format, ...); + +/// fread_s secure version of fread. +/// \param buffer Storage location for data. +/// \param buffer_size Size of the destination buffer in bytes. +/// \param element_size Size of the item to read in bytes. +/// \param count Maximum number of items to be read. +/// \param stream Pointer to FILE structure. +/// \return The number of (whole) items that were read into the buffer, which may be less than count if a +/// read error or the end of the file is encountered before count is reached. Use the feof or ferror +/// function to distinguish an error from an end-of-file condition. If size or count is 0, fread_s +/// returns 0 and the buffer contents are unchanged. +size_t fread_s(void* buffer, size_t buffer_size, size_t element_size, size_t count, FILE* stream); + +/// strcpy_s secure version of strcpy. +/// \param destination The destination (output) string. +/// \param size The maximum number of bytes to copy. +/// \param source The source (input) string. +/// \return 0 on success, non-zero on error. +errno_t strcpy_s(char* destination, size_t size, const char* source); + +/// strcat_s secure version of strcat. +/// \param destination The destination (output) string. +/// \param size The maximum number of bytes to copy. +/// \param source The source (input) string. +/// \return 0 on success, non-zero on error. +errno_t strcat_s(char* destination, size_t size, const char* source); + +#endif // !_WIN32 + +#endif // RMV_PARSER_LINUX_SAFE_CRT_H_ diff --git a/source/parser/rmt_address_helper.cpp b/source/parser/rmt_address_helper.cpp new file mode 100644 index 0000000..fbffe43 --- /dev/null +++ b/source/parser/rmt_address_helper.cpp @@ -0,0 +1,58 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation of helper functions for working with addresses. +//============================================================================= + +#include "rmt_address_helper.h" +#include + +uint64_t RmtGetPageSize(RmtPageSize page_size) +{ + switch (page_size) + { + case kRmtPageSize4Kb: + return 4 * 1024; + case kRmtPageSize64Kb: + return 64 * 1024; + case kRmtPageSize256Kb: + return 256 * 1024; + case kRmtPageSize1Mb: + return 1024 * 1024; + case kRmtPageSize2Mb: + return 2 * 1024 * 1024; + default: + return 0; + } +} + +uint64_t RmtGetAllocationSizeInBytes(uint64_t size_in_pages, RmtPageSize page_size) +{ + const uint64_t page_size_in_bytes = RmtGetPageSize(page_size); + return size_in_pages * page_size_in_bytes; +} + +bool RmtAllocationsOverlap(RmtGpuAddress base_address1, uint64_t size_in_bytes1, RmtGpuAddress base_address2, uint64_t size_in_bytes2) +{ + // Case 1: |---2----| |----1----| + if (base_address1 > (base_address2 + size_in_bytes2)) + { + return false; + } + + // Case 2: |---1----| |----2----| + if (base_address2 > (base_address1 + size_in_bytes1)) + { + return false; + } + + // Case 3: |--------1------------| + // |----2---| + // Case 4: |-----1-----| + // |-----2-----| + // Case 5: |-----2-----| + // |-----1------| + // Case 6: |-----2-------------------| + // |----1----| + return true; +} diff --git a/source/parser/rmt_address_helper.h b/source/parser/rmt_address_helper.h new file mode 100644 index 0000000..08d550e --- /dev/null +++ b/source/parser/rmt_address_helper.h @@ -0,0 +1,44 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Helpers for working with addresses. +//============================================================================= + +#ifndef RMV_PARSER_RMT_ADDRESS_HELPER_H_ +#define RMV_PARSER_RMT_ADDRESS_HELPER_H_ + +#include "rmt_format.h" + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// Calculate the size of a page in bytes. +/// +/// @param [in] page_size An RmtPageSize defining the page size. +/// @returns +/// The page size, in bytes. +uint64_t RmtGetPageSize(RmtPageSize page_size); + +/// Calculate the size of an allocation in bytes from its pages count and page size. +/// +/// @param [in] size_in_pages The size of the allocation, in pages. +/// @param [in] page_size An RmtPageSize defining the page size. +/// @returns +/// The allocation size, in bytes. +uint64_t RmtGetAllocationSizeInBytes(uint64_t size_in_pages, RmtPageSize page_size); + +/// Check if two ranges in an address space overlap. +/// +/// @param [in] base_address1 The base address of the first allocation. +/// @param [in] size_in_bytes1 The size, in bytes, of the first allocation. +/// @param [in] base_address2 The base address of the second allocation. +/// @param [in] size_in_bytes2 The size, in bytes, of the second allocation. +/// @returns +/// If the allocations overlap, true is returned, otherwise false. +bool RmtAllocationsOverlap(RmtGpuAddress base_address1, uint64_t size_in_bytes1, RmtGpuAddress base_address2, uint64_t size_in_bytes2); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_ADDRESS_HELPER_H_ diff --git a/source/parser/rmt_assert.cpp b/source/parser/rmt_assert.cpp new file mode 100644 index 0000000..77f1e08 --- /dev/null +++ b/source/parser/rmt_assert.cpp @@ -0,0 +1,70 @@ +//============================================================================= +/// Copyright (c) 2017 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation of assert. +//============================================================================= + +#include "rmt_assert.h" +#include // for malloc() + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include // required for OutputDebugString() +#include // required for sprintf_s +#endif // #ifndef _WIN32 + +static RmtAssertCallback s_assert_callback; + +// set the printing callback function +void RmtAssertSetPrintingCallback(RmtAssertCallback callback) +{ + s_assert_callback = callback; + return; +} + +// implementation of assert reporting +bool RmtAssertReport(const char* file, int32_t line, const char* condition, const char* message) +{ + if (!file) + { + return true; + } + +#ifdef _WIN32 + // form the final assertion string and output to the TTY. + const size_t bufferSize = snprintf(NULL, 0, "%s(%d): ASSERTION FAILED. %s\n", file, line, message ? message : condition) + 1; + char* tempBuf = (char*)malloc(bufferSize); + if (!tempBuf) + { + return true; + } + + if (!message) + { + sprintf_s(tempBuf, bufferSize, "%s(%d): ASSERTION FAILED. %s\n", file, line, condition); + } + else + { + sprintf_s(tempBuf, bufferSize, "%s(%d): ASSERTION FAILED. %s\n", file, line, message); + } + + if (!s_assert_callback) + { + OutputDebugStringA(tempBuf); + } + else + { + s_assert_callback(tempBuf); + } + + // free the buffer. + free(tempBuf); + +#else + RMT_UNUSED(line); + RMT_UNUSED(condition); + RMT_UNUSED(message); +#endif + + return true; +} diff --git a/source/parser/rmt_assert.h b/source/parser/rmt_assert.h new file mode 100644 index 0000000..2c8e0f0 --- /dev/null +++ b/source/parser/rmt_assert.h @@ -0,0 +1,120 @@ +//============================================================================= +/// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Definition of assert. +//============================================================================= + +#ifndef RMV_PARSER_RMT_ASSERT_H_ +#define RMV_PARSER_RMT_ASSERT_H_ + +#include +#include +#include "rmt_util.h" + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +#ifdef _DEBUG +#ifdef _WIN32 + +#ifdef DISABLE_RMT_DEBUG_BREAK +#define RMT_DEBUG_BREAK \ + { \ + } +#else +/// Macro to force the debugger to break at this point in the code. +#define RMT_DEBUG_BREAK __debugbreak(); +#endif +#else +#define RMT_DEBUG_BREAK \ + { \ + } +#endif +#else +// don't allow debug break in release builds. +#define RMT_DEBUG_BREAK +#endif + +/// A typedef for the callback function for assert printing. +/// +/// This can be used to re-route printing of assert messages from the RMT backend +/// to another destination. For example instead of the default behaviour of printing +/// the assert messages to the debugger's TTY the message can be re-routed to a +/// MessageBox in a GUI application. +/// +/// @param [in] message The message generated by the assert. +/// +typedef void (*RmtAssertCallback)(const char* message); + +/// Function to report an assert. +/// +/// @param [in] file The name of the file as a string. +/// @param [in] line The index of the line in the file. +/// @param [in] condition The boolean condition that was tested. +/// @param [in] message The optional message to print. +/// +/// @returns +/// Always returns true. +/// +bool RmtAssertReport(const char* file, int32_t line, const char* condition, const char* msg); + +/// Provides the ability to set a callback for assert messages. +/// +/// @param [in] callback The callback function that will receive assert messages. +/// +void RmtAssertSetPrintingCallback(RmtAssertCallback callback); + +#if _DEBUG +/// Standard assert macro. +#define RMT_ASSERT(condition) \ + do \ + { \ + if (!(condition) && RmtAssertReport(__FILE__, __LINE__, #condition, NULL)) \ + RMT_DEBUG_BREAK \ + } while (0) + +/// Assert macro with message. +#define RMT_ASSERT_MESSAGE(condition, msg) \ + do \ + { \ + if (!(condition) && RmtAssertReport(__FILE__, __LINE__, #condition, msg)) \ + RMT_DEBUG_BREAK \ + } while (0) + +/// Assert macro that always fails. +#define RMT_ASSERT_FAIL(message) \ + do \ + { \ + RmtAssertReport(__FILE__, __LINE__, NULL, message); \ + RMT_DEBUG_BREAK \ + } while (0) +#else +// asserts disabled +#define RMT_ASSERT(condition) \ + do \ + { \ + RMT_UNUSED(condition); \ + } while (0) + +#define RMT_ASSERT_MESSAGE(condition, message) \ + do \ + { \ + RMT_UNUSED(condition); \ + RMT_UNUSED(message); \ + } while (0) + +#define RMT_ASSERT_FAIL(message) \ + do \ + { \ + RMT_UNUSED(message); \ + } while (0) +#endif // #if _DEBUG + +/// Simple static assert. +#define RMT_STATIC_ASSERT(condition) static_assert(condition, #condition) + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_ASSERT_H_ diff --git a/source/parser/rmt_error.h b/source/parser/rmt_error.h new file mode 100644 index 0000000..38ead2f --- /dev/null +++ b/source/parser/rmt_error.h @@ -0,0 +1,97 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Error codes. +//============================================================================= + +#ifndef RMV_PARSER_RMT_ERROR_H_ +#define RMV_PARSER_RMT_ERROR_H_ + +#include + +#ifndef _WIN32 +#include +#include +#endif + +/// The operation completed successfully. +#define RMT_OK (0) + +/// The operation failed due to an invalid pointer. +#define RMT_ERROR_INVALID_POINTER (0x80000000) + +/// The operation failed due to an invalid pointer. +#define RMT_ERROR_INVALID_ALIGNMENT (0x80000001) + +/// The operation failed due to an invalid size. +#define RMT_ERROR_INVALID_SIZE (0x80000002) + +/// The end of the file was encountered. +#define RMT_EOF (0x80000003) + +/// The operation failed because the specified path was invalid. +#define RMT_ERROR_INVALID_PATH (0x80000004) + +/// The operation failed because end of file was reached. +#define RMT_END_OF_FILE (0x80000005) + +/// The operation failed because of some malformed data. +#define RMT_ERROR_MALFORMED_DATA (0x80000006) + +/// The operation failed because a file was not open. +#define RMT_ERROR_FILE_NOT_OPEN (0x80000007) + +/// The operation failed because it ran out memory. +#define RMT_ERROR_OUT_OF_MEMORY (0x80000008) + +/// The operation failed because of an invalid page size. +#define RMT_ERROR_INVALID_PAGE_SIZE (0x80000009) + +/// The operation failed because a timestamp was out of bounds. +#define RMT_ERROR_TIMESTAMP_OUT_OF_BOUNDS (0x8000000a) + +/// The operation failed because a platform-specific function failed. +#define RMT_ERROR_PLATFORM_FUNCTION_FAILED (0x8000000b) + +/// The operation failed because an index was out of range. +#define RMT_ERROR_INDEX_OUT_OF_RANGE (0x8000000c) + +/// The operation failed because an allocation could not be found. +#define RMT_ERROR_NO_ALLOCATION_FOUND (0x8000000d) + +/// The operation failed because a resource could not be found. +#define RMT_ERROR_NO_RESOURCE_FOUND (0x8000000e) + +/// The operation failed because an address was not mapped. +#define RMT_ERROR_ADDRESS_NOT_MAPPED (0x8000000f) + +/// The operation failed because an address was already mapped. +#define RMT_ERROR_ADDRESS_ALREADY_MAPPED (0x80000010) + +/// An allocation was not found due to it being an external shared allocation. +#define RMT_ERROR_SHARED_ALLOCATION_NOT_FOUND (0x80000011) + +/// A resource was already bound to a virtual memory range. +#define RMT_ERROR_RESOURCE_ALREADY_BOUND (0x80000012) + +/// The operation failed because a file was already opened. +#define RMT_ERROR_FILE_ALREADY_OPENED (0x80000013) + +/// Helper macro to return error code y from a function when a specific condition, x, is not met. +#define RMT_RETURN_ON_ERROR(x, y) \ + if (!(x)) \ + { \ + return (y); \ + } + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// Typedef for error codes returned from functions in the RMT backend. +typedef int32_t RmtErrorCode; + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_ERROR_H_ diff --git a/source/parser/rmt_file_format.cpp b/source/parser/rmt_file_format.cpp new file mode 100644 index 0000000..ff4f404 --- /dev/null +++ b/source/parser/rmt_file_format.cpp @@ -0,0 +1,74 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation of functions for working with the RMT file format. +//============================================================================= + +#include "rmt_file_format.h" +#include "rmt_util.h" +#include // for fread + +RmtErrorCode RmtFileParserCreateFromHandle(RmtFileParser* file_parser, FILE* file_handle) +{ + RMT_RETURN_ON_ERROR(file_parser, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(file_handle, RMT_ERROR_FILE_NOT_OPEN); + + // set up the parser. + file_parser->next_chunk_offset = 0; + file_parser->file_handle = file_handle; + + // reset the file for read. + fseek(file_handle, 0L, SEEK_END); + file_parser->file_size = ftell(file_handle); + fseek(file_handle, 0, SEEK_SET); + if (file_parser->file_size == 0U) + { + return RMT_ERROR_FILE_NOT_OPEN; + } + + // read the header in, if we didn't get enough from the file than error out. + const size_t read_size = fread(&file_parser->header, 1, sizeof(RmtFileHeader), file_handle); + RMT_RETURN_ON_ERROR(read_size == sizeof(RmtFileHeader), RMT_ERROR_MALFORMED_DATA); + + // set the next chunk offset + file_parser->next_chunk_offset = file_parser->header.chunk_offset; + + // validate that the file contains the magic number + RMT_RETURN_ON_ERROR(file_parser->header.magic_number == RMT_FILE_MAGIC_NUMBER, RMT_ERROR_MALFORMED_DATA); + + return RMT_OK; +} + +RmtErrorCode RmtFileParserParseNextChunk(RmtFileParser* file_parser, RmtFileChunkHeader** parsed_chunk) +{ + RMT_RETURN_ON_ERROR(file_parser, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(parsed_chunk, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR((size_t)file_parser->next_chunk_offset < file_parser->file_size, RMT_END_OF_FILE); + *parsed_chunk = NULL; + + // read the chunk header in from the file. + fseek(file_parser->file_handle, file_parser->next_chunk_offset, 0); + const size_t read_size = fread(&file_parser->current_chunk, 1, sizeof(RmtFileChunkHeader), file_parser->file_handle); + RMT_RETURN_ON_ERROR(read_size == sizeof(RmtFileChunkHeader), RMT_ERROR_MALFORMED_DATA); + + // It is possible to get stuck in loops by malformed data in the file since nextChunkOffset is updated + // by nextChunk->sizeInButes. Harden for that outside where we validate other chunk data. + file_parser->next_chunk_offset += file_parser->current_chunk.size_in_bytes; + *parsed_chunk = &file_parser->current_chunk; + + return RMT_OK; +} + +RmtErrorCode RmtFileParserIsChunkSupported(const RmtFileChunkHeader* header) +{ + RMT_UNUSED(header); + + return RMT_OK; +} + +RmtErrorCode RmtFileParserIsFileSupported(const RmtFileHeader* header) +{ + RMT_UNUSED(header); + + return RMT_OK; +} diff --git a/source/parser/rmt_file_format.h b/source/parser/rmt_file_format.h new file mode 100644 index 0000000..ec19145 --- /dev/null +++ b/source/parser/rmt_file_format.h @@ -0,0 +1,228 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Definition of structures and functions for the RMT file format. +//============================================================================= + +#ifndef RMV_PARSER_RMT_FILE_FORMAT_H_ +#define RMV_PARSER_RMT_FILE_FORMAT_H_ + +#include "rmt_error.h" +#include "rmt_types.h" +#include + +/// Magic number for all RMT files. +#define RMT_FILE_MAGIC_NUMBER (0x494e494d) + +/// The maximum number of separate RMT streams in a file. +#define RMT_MAXIMUM_STREAMS (256) + +/// The maximum length of an adapter name. +#define RMT_MAX_ADAPTER_NAME_LENGTH (128) + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// Structure encapsulating the file header of a RMT file. +typedef struct RmtFileHeader +{ + uint32_t magic_number; ///< Magic number, always set to RMT_FILE_MAGIC_NUMBER. + uint32_t version_major; ///< The major version number of the file. + uint32_t version_minor; ///< The minor version number of the file. + uint32_t flags; ///< Bitfield of flags set with information about the file. + int32_t chunk_offset; ///< The offset in bytes to the first chunk contained in the file. + int32_t second; ///< The second in the minute that the RMT file was created. + int32_t minute; ///< The minute in the hour that the RMT file was created. + int32_t hour; ///< The hour in the day that the RMT file was created. + int32_t day_in_month; ///< The day in the month that the RMT file was created. + int32_t month; ///< The month in the year that the RMT file was created. + int32_t year; ///< The year that the RMT file was created. + int32_t day_in_week; ///< The day in the week that the RMT file was created. + int32_t day_in_year; ///< The day in the year that the RMT file was created. + int32_t is_daylight_savings; ///< Set to 1 if the time is subject to daylight savings. +} RmtFileHeader; + +/// An enumeration of all chunk types used in the file format. +typedef enum RmtFileChunkType +{ + kRmtFileChunkTypeAsicInfo = 0, ///< A chunk containing information about the ASIC on which the RMT file was generated. + kRmtFileChunkTypeApiInfo = 1, ///< A chunk containing information about the API that the application generating the RMT file was using. + kRmtFileChunkTypeSystemInfo = 2, ///< A chunk containing the description of the system on which the trace was made. + kRmtFileChunkTypeRmtData = 3, ///< A chunk containing the RMT data. + kRmtFileChunkTypeSegmentInfo = 4, ///< A chunk containing segment information for the main process. + kRmtFileChunkTypeProcessStart = 5, ///< A chunk containing process state information at the start of the RMT trace. + kRmtFileChunkTypeSnapshotInfo = 6, ///< A chunk containing snapshot info. + kRmtFileChunkTypeAdapterInfo = 7, ///< A chunk containing adapter info. + + // NOTE: Add new chunks above this. + kRmtFileChunkTypeCount ///< The number of different chunk types. +} RmtFileChunkType; + +/// An enumeration of flags about the file header. +typedef enum RmtFileChunkFileHeaderFlags +{ + kRmtFileHeaderFlagReserved = (1 << 0), ///< Get the queue timing source +} RmtFileChunkFileHeaderFlags; + +/// An enumeration of the API types. +typedef enum RmtApiType +{ + kRmtApiTypeDirectx12 = 0, ///< The trace contains data from a DirectX 12 application. + kRmtApiTypeVulkan = 1, ///< The trace contains data from a Vulkan application. + kRmtApiTypeGeneric = 2, ///< The API of the application is not known. + kRmtApiTypeOpencl = 3, ///< The API of the application is OpenCL. + kRmtApiTypeCount ///< The number of APIs supported. +} RmtApiType; + +/// A structure encapsulating a single chunk identifier. +typedef struct RmtFileChunkIdentifier +{ + union + { + struct + { + RmtFileChunkType chunk_type : 8; ///< The type of chunk. + int32_t chunk_index : 8; ///< The index of the chunk. + int32_t reserved : 16; ///< Reserved, set to 0. + }; + + uint32_t value; ///< 32bit value containing all the above fields. + }; +} RmtFileChunkIdentifier; + +/// A structure encapsulating common fields of a chunk in the RMT file format. +typedef struct RmtFileChunkHeader +{ + RmtFileChunkIdentifier chunk_identifier; ///< A unique identifier for the chunk. + int16_t version_minor; ///< The minor version of the chunk. Please see above note on ordering of minor and major version + int16_t version_major; ///< The major version of the chunk. + int32_t size_in_bytes; ///< The size of the chunk in bytes. + int32_t padding; ///< Reserved padding dword. +} RmtFileChunkHeader; + +/// A structure encapsulating information about the location of the RMT data within the RMT file itself. +typedef struct RmtFileChunkRmtData +{ + uint64_t process_id; ///< The process ID that generated this RMT data. If unknown program to 0. + uint64_t thread_id; ///< The CPU thread ID of the thread in the application that generated this RMT data. +} RmtFileChunkRmtData; + +/// A structure encapsulating system information. +typedef struct RmtFileChunkSystemInfo +{ + char vendor_id[16]; ///< For x86 CPUs this is based off the 12 character ASCII string retreived via CPUID instruction. + char processor_brand[48]; ///< For x86 CPUs this is based off the 48 byte null-terminated ASCII processor brand using CPU instruction. + uint64_t padding; ///< Padding after 48 byte string. + uint64_t + timestamp_frequency; ///< The frequency of the timestamp clock (in Hz). For windows this is the same as reported by the QueryPerformanceFrequency API. + uint32_t clock_speed; ///< The maximum clock frequency of the CPU (in MHz). + int32_t logic_cores; ///< The number of logical cores. + int32_t physical_cores; ///< The number of physical cores. + int32_t system_ram_in_mb; ///< The amount of system RAM expressed in MB. +} RmtFileChunkSystemInfo; + +/// A structure encapsulating segment info. +typedef struct RmtFileChunkSegmentInfo +{ + uint64_t base_address; ///< The physical address for the segment. + uint64_t size_in_bytes; ///< The size (in bytes) of the segment. + RmtHeapType heap_type; ///< The type of heap that the segment implements. + int32_t memory_index; ///< The memory index exposed by the Vulkan software stack. +} RmtFileChunkSegmentInfo; + +/// A structure encapsulating adapter info. +typedef struct RmtFileChunkAdapterInfo +{ + char name[RMT_MAX_ADAPTER_NAME_LENGTH]; ///< The name of the adapter as a NULL terminated string. + uint32_t pcie_family_id; ///< The PCIe family ID of the adapater. + uint32_t pcie_revision_id; ///< The PCIe revision ID of the adapter. + uint32_t device_id; ///< The PCIe device ID of the adapter. + uint32_t minimum_engine_clock; ///< The minimum engine clock (in MHz). + uint32_t maximum_engine_clock; ///< The maximum engine clock (in MHz). + uint32_t memory_type; ///< The memory type. + uint32_t memory_operations_per_clock; ///< The number of memory operations that can be performed per clock. + uint32_t memory_bus_width; ///< The width of the memory bus (in bits). + uint32_t memory_bandwidth; ///< Bandwidth of the memory system (in MB/s). + uint32_t minimum_memory_clock; ///< The minimum memory clock (in MHz). + uint32_t maximum_memory_clock; ///< The maximum memory clock (in MHz). +} RmtFileChunkAdapterInfo; + +/// A structure encapsulating snapshot info. +typedef struct RmtFileChunkSnapshotInfo +{ + uint64_t snapshot_time; ///< The time (in RMT clocks) when the snapshot was taken. + int32_t name_length_in_bytes; ///< The length of the name in bytes. + + // NOTE: The name follows this structure. +} RmtFileChunkSnapshotInfo; + +/// A structure encapsulating the state of the RMT file parser. +typedef struct RmtFileParser +{ + FILE* file_handle; ///< The file handle. + RmtFileHeader header; ///< The RMT file header read from the buffer. + RmtFileChunkHeader current_chunk; ///< Storage for a RmtFileChunkHeader structure. + int32_t next_chunk_offset; ///< The offset to the next chunk to read. + size_t file_size; ///< The size of the file. +} RmtFileParser; + +/// Create an RMT file parser from a buffer. +/// +/// @param [in, out] file_parser A pointer to a RmtFileParser structure to populate. +/// @param [in] file_handle A pointer to a FILE object. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because fileParser or fileBuffer was NULL. +/// @retval +/// RMT_ERROR_INVALID_SIZE The operation failed because fileBufferSize was not large enough to contain the RMT file header. +/// @retval +/// RMT_ERROR_MALFORMED_DATA The operation failed because the data pointed to by fileBuffer didn't begin with a valid RMT file header. +/// +RmtErrorCode RmtFileParserCreateFromHandle(RmtFileParser* file_parser, FILE* file_handle); + +/// Get a pointer to the next chunk in the file. +/// +/// @param [in, out] file_parser A pointer to a RmtFileParser structure. +/// @param [out] parsed_chunk A pointer to a RmtFileChunkHeader structure to populate with the chunk information. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because fileParser or parsedChunk was NULL. +/// @retval +/// RMT_END_OF_FILE The operation failed because the end of the buffer was reached. +/// +RmtErrorCode RmtFileParserParseNextChunk(RmtFileParser* file_parser, RmtFileChunkHeader** parsed_chunk); + +/// Check if the current chunk can be processed by this build of RMT. Only checks major versions. +/// See implementation comment for directions on how to express the versions presently supported. +/// +/// @param [in] header A pointer to a Chunk header +/// @retval +/// RMT_OK The chunk is supported. +/// @retval +/// RMT_ERROR_UNSUPPORTED_LOW_SPEC_VERSION Incompatible version +/// @retval +/// RMT_ERROR_UNSUPPORTED_HIGH_SPEC_VERSION Incompatible version +/// +RmtErrorCode RmtFileParserIsChunkSupported(const RmtFileChunkHeader* header); + +/// Check if the RMT file can be processed by this build of RMT. +/// +/// @param [in] header A pointer to a file header +/// @retval +/// RMT_OK The file is compatible. +/// @retval +/// RMT_ERROR_UNSUPPORTED_LOW_SPEC_VERSION Incompatible version +/// @retval +/// RMT_ERROR_UNSUPPORTED_HIGH_SPEC_VERSION Incompatible version +/// +RmtErrorCode RmtFileParserIsFileSupported(const RmtFileHeader* header); + +#ifdef __cplusplus +} +#endif +#endif // #ifndef RMV_PARSER_RMT_FILE_FORMAT_H_ diff --git a/source/parser/rmt_format.h b/source/parser/rmt_format.h new file mode 100644 index 0000000..4dcd0f1 --- /dev/null +++ b/source/parser/rmt_format.h @@ -0,0 +1,910 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Definitions of the RMT format. +//============================================================================= + +#ifndef RMV_PARSER_RMT_FORMAT_H_ +#define RMV_PARSER_RMT_FORMAT_H_ + +#include +#include "rmt_types.h" + +#ifndef _WIN32 +#include +#endif + +/// The maximum number of bytes it will take to hold the maximum number of pages +/// that a kRmtTokenTypePageReference token can encode. +/// +/// NOTE: The real value is 620, this is rounded up to hit a byte boundary. +#define RMT_PAGE_REF_COUNT (624 / 8) + +/// There are only 3 bits for the pool size count, so that sets the max number. +#define RMT_MAX_POOLS (1ull << 3) + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// An enumeration of all token types supported by RMT. +typedef enum RmtTokenType +{ + kRmtTokenTypeTimestamp = 0, ///< The token is a timestamp token. + kRmtTokenTypeReserved0 = 1, ///< The token is reserved + kRmtTokenTypeReserved1 = 2, ///< The token is reserved + kRmtTokenTypePageTableUpdate = 3, ///< The token is a page table update token. + kRmtTokenTypeUserdata = 4, ///< The token is a user data token. + kRmtTokenTypeMisc = 5, ///< The token is a miscellaneous token. + kRmtTokenTypeResourceReference = 6, ///< The token is a resource reference token. + kRmtTokenTypeResourceBind = 7, ///< The token is a resource bind token. + kRmtTokenTypeProcessEvent = 8, ///< The token is a process event token. + kRmtTokenTypePageReference = 9, ///< The token is a page reference token. + kRmtTokenTypeCpuMap = 10, ///< The token is a CPU map token. + kRmtTokenTypeVirtualFree = 11, ///< The token is a virtual free token. + kRmtTokenTypeVirtualAllocate = 12, ///< The token is a virtual allocation token. + kRmtTokenTypeResourceCreate = 13, ///< The token is a resource create token. + kRmtTokenTypeTimeDelta = 14, ///< The token is a time delta token. + kRmtTokenTypeResourceDestroy = 15, ///< The token is a resource destroy token. + + // Add above here. + kRmtTokenTypeCount +} RmtTokenType; + +/// An enumeration of user data types. +typedef enum RmtUserdataType +{ + kRmtUserdataTypeName = 0, ///< The user data contains a string which is relevant to the following sequence of tokens. + kRmtUserdataTypeSnapshot = 1, ///< The user data contains a string which names a specific momemnt in time. + kRmtUserdataTypeBinary = 2, ///< The user data contains a blind binary payload. + kRmtUserdataTypeReserved = 3 ///< Reserved for future expansion. +} RmtUserdataType; + +/// An enumeration of miscellaenous events. +typedef enum RmtMiscType +{ + kRmtMiscTypeSubmitGfx = 0, ///< An event occurred where work was submited to graphics. + kRmtMiscTypeSubmitCompute = 1, ///< An event occurred where work was submitted to compute. + kRmtMiscTypeSubmitCopy = 2, ///< An event occurred where work was submitted to copy. + kRmtMiscTypePresent = 3, ///< An event occurred where a present happened. + kRmtMiscTypeInvalidateRanges = 4, ///< An event occurred where host memory was invalidated. + kRmtMiscTypeFlushMappedRange = 5, ///< An event occurred where we flushed local memory. + kRmtMiscTypeTrimMemory = 6, ///< An event occurred where the host operating system asked the driver to trim resident memory. + kRmtMiscTypeReserved0 = 7, ///< Reserved for future expansion. + kRmtMiscTypeReserved1 = 8 ///< Reserved for future expansion. +} RmtMiscType; + +/// An enumeration of residency update types. +typedef enum RmtResidencyUpdateType +{ + kRmtResidencyUpdateTypeAdd = 0, ///< A reference was added to the resource. + kRmtResidencyUpdateTypeRemove = 1 ///< A reference was removed from the resource. +} RmtResidencyUpdateType; + +/// An enumeration of resource types. +typedef enum RmtResourceType +{ + kRmtResourceTypeImage = 0, ///< An image. + kRmtResourceTypeBuffer = 1, ///< A buffer. + kRmtResourceTypeGpuEvent = 2, ///< Used by the driver for finer-grained synchronization control. + kRmtResourceTypeBorderColorPalette = + 3, ///< When indexing out of bounds of an image from a shader this palette image represents the color values during texture coordinate clamping. + kRmtResourceTypeIndirectCmdGenerator = + 4, ///< The resource is used to support indirectly generating command buffers from the GPU. Used to support some variants of ExecuteIndirect. + kRmtResourceTypeMotionEstimator = 5, ///< Motion estimator video technology. + kRmtResourceTypePerfExperiment = 6, ///< Used to perform performance experiments in the driver, including SQTT, SPM and traditional, sampled counters. + kRmtResourceTypeQueryHeap = 7, ///< The resource is a heap which contains query results. + kRmtResourceTypeVideoDecoder = 8, ///< The resource is used as the source or destination for a video decoder. + kRmtResourceTypeVideoEncoder = 9, ///< The resource is used as the source or destination for a video encoder. + kRmtResourceTypeTimestamp = 10, ///< The resource is one or more timestamps. + kRmtResourceTypeHeap = 11, ///< The resource is a heap, a larger allocation which can be used for sub-allocations. + kRmtResourceTypePipeline = + 12, ///< The resource is a pipeline, i.e.: the combination of GCN/RDNA ISA code together with the state required to compile & use it. + kRmtResourceTypeDescriptorHeap = 13, ///< A specialised type of heap which holds descriptors only. [DX12 only] + kRmtResourceTypeDescriptorPool = 14, ///< A specialised pool containing multiple heaps which hold descriptors only. [Vulkan only] + kRmtResourceTypeCommandAllocator = 15, ///< A command allocator resource. + kRmtResourceTypeMiscInternal = 16, ///< A micellaneous internal type of resource. + + // add above this. + kRmtResourceTypeCount +} RmtResourceType; + +/// An enumeration of process events. +typedef enum RmtProcessEventType +{ + kRmtProcessEventTypeStart = 0, ///< A process was started. + kRmtProcessEventTypeStop = 1 ///< A process was stopped. +} RmtProcessEventType; + +/// An enumeration of all owner types. +typedef enum RmtOwnerType +{ + kRmtOwnerTypeApplication = 0, ///< The owner of the object is the application. + kRmtOwnerTypePal = 1, ///< The owner of the object is PAL. + kRmtOwnerTypeClientDriver = 2, ///< The owner of the object is the client driver. + kRmtOwnerTypeKmd = 3, ///< The owner of the object is KMD. + + // add above this. + kRmtOwnerTypeCount +} RmtOwnerType; + +/// An enumeraetion of the various commit types for a resource. +typedef enum RmtCommitType +{ + kRmtCommitTypeCommitted = 0, ///< The resource was requested to be committed. This means the resource was created with an implicit + ///< heap large enough to contain it. i.e.: The driver stack is being requested to create a full + ///< chain of memory mappings down to the physical page. + + kRmtCommitTypePlaced = 1, ///< The resource was requested to be a placed resource. This means the resource is bound to a + ///< previously allocated heap of memory. + + kRmtCommitTypeVirtual = 2, ///< The resource was requested to be a virtual resource. This means that there is no physical memory + ///< backing the resource. The resource will have its own virtual address range allocated at the time + ///< the resource is created, but that virtual address space will not be mapped to an underlaying + ///< physical address range. + + kRmtCommitTypeReserved = 3, ///< Reserved for future expansion. + + // add above this. + kRmtCommitTypeCount +} RmtCommitType; + +/// An enumeration of formats. +typedef enum RmtFormat +{ + kRmtFormatUndefined = 0, ///< Format is UNDEFINED. + kRmtFormatX1Unorm = 1, ///< Format is X1 UNORM. + kRmtFormatX1Uscaled = 2, ///< Format is X1 USCALED. + kRmtFormatX4Y4Unorm = 3, ///< Format is X4Y4 UNORM. + kRmtFormatX4Y4Uscaled = 4, ///< Format is X4Y4 USCALED. + kRmtFormatL4A4Unorm = 5, ///< Format is L4A4 UNORM. + kRmtFormatX4Y4Z4W4Unorm = 6, ///< Format is X4Y4Z4W4 UNORM. + kRmtFormatX4Y4Z4W4Uscaled = 7, ///< Format is X4Y4Z4W4 USCALED. + kRmtFormatX5Y6Z5Unorm = 8, ///< Format is X5Y6Z5 UNORM. + kRmtFormatX5Y6Z5Uscaled = 9, ///< Format is X5Y6Z5 USCALED. + kRmtFormatX5Y5Z5W1Unorm = 10, ///< Format is X5Y5Z5W1 UNORM. + kRmtFormatX5Y5Z5W1Uscaled = 11, ///< Format is X5Y5Z5W1 USCALED. + kRmtFormatX1Y5Z5W5Unorm = 12, ///< Format is X1Y5Z5W5 UNORM. + kRmtFormatX1Y5Z5W5Uscaled = 13, ///< Format is X1Y5Z5W5 USCALED. + kRmtFormatX8Xnorm = 14, ///< Format is X8 XNORM. + kRmtFormatX8Snorm = 15, ///< Format is X8 SNORM. + kRmtFormatX8Uscaled = 16, ///< Format is X8 USCALED. + kRmtFormatX8Sscaled = 17, ///< Format is X8 SSCALED. + kRmtFormatX8Uint = 18, ///< Format is X8 UINT. + kRmtFormatX8Sint = 19, ///< Format is X8 SINT. + kRmtFormatX8Srgb = 20, ///< Format is X8 SRGB. + kRmtFormatA8Unorm = 21, ///< Format is A8 UNORM. + kRmtFormatL8Unorm = 22, ///< Format is L8 UNORM. + kRmtFormatP8Uint = 23, ///< Format is P8 UINT. + kRmtFormatX8Y8Unorm = 24, ///< Format is X8Y8 UNORM. + kRmtFormatX8Y8Snorm = 25, ///< Format is X8Y8 SNORM. + kRmtFormatX8Y8Uscaled = 26, ///< Format is X8Y8 USCALED. + kRmtFormatX8Y8Sscaled = 27, ///< Format is X8Y8 SSCALED. + kRmtFormatX8Y8Uint = 28, ///< Format is X8Y8 UINT. + kRmtFormatX8Y8Sint = 29, ///< Format is X8Y8 SINT. + kRmtFormatX8Y8Srgb = 30, ///< Format is X8Y8 SRGB. + kRmtFormatL8A8Unorm = 31, ///< Format is L8A8 UNORM. + kRmtFormatX8Y8Z8W8Unorm = 32, ///< Format is X8Y8Z8W8 UNORM. + kRmtFormatX8Y8Z8W8Snorm = 33, ///< Format is X8Y8Z8W8 SNORM. + kRmtFormatX8Y8Z8W8Uscaled = 34, ///< Format is X8Y8Z8W8 USCALED. + kRmtFormatX8Y8Z8W8Sscaled = 35, ///< Format is X8Y8Z8W8 SSCALED. + kRmtFormatX8Y8Z8W8Uint = 36, ///< Format is X8Y8Z8W8 UINT. + kRmtFormatX8Y8Z8W8Sint = 37, ///< Format is X8Y8Z8W8 SINT. + kRmtFormatX8Y8Z8W8Srgb = 38, ///< Format is X8Y8Z8W8 SRGB. + kRmtFormatU8V8SnormL8W8Unorm = 39, ///< Format is U8V8 SNORM L8W8 UNORM. + kRmtFormatX10Y11Z11Float = 40, ///< Format is X10Y11Z11 FLOAT. + kRmtFormatX11Y11Z10Float = 41, ///< Format is X11Y11Z10 FLOAT. + kRmtFormatX10Y10Z10W2Unorm = 42, ///< Format is X10Y10Z10W2 UNORM. + kRmtFormatX10Y10Z10W2Snorm = 43, ///< Format is X10Y10Z10W2 SNORM. + kRmtFormatX10Y10Z10W2Uscaled = 44, ///< Format is X10Y10Z10W2 USCALED. + kRmtFormatX10Y10Z10W2Sscaled = 45, ///< Format is X10Y10Z10W2 SSCALED. + kRmtFormatX10Y10Z10W2Uint = 46, ///< Format is X10Y10Z10W2 UINT. + kRmtFormatX10Y10Z10W2Sint = 47, ///< Format is X10Y10Z10W2 SINT. + kRmtFormatX10Y10Z10W2BiasUnorm = 48, ///< Format is X10Y10Z10W2BIAS UNORM. + kRmtFormatU10V10W10SnormA2Unorm = 49, ///< Format is U10V10W10 SNORM A2 UNORM. + kRmtFormatX16Unorm = 50, ///< Format is X16 UNORM. + kRmtFormatX16Snorm = 51, ///< Format is X16 SNORM. + kRmtFormatX16Uscaled = 52, ///< Format is X16 USCALED. + kRmtFormatX16Sscaled = 53, ///< Format is X16 SSCALED. + kRmtFormatX16Uint = 54, ///< Format is X16 UINT. + kRmtFormatX16Sint = 55, ///< Format is X16 SINT. + kRmtFormatX16Float = 56, ///< Format is X16 FLOAT. + kRmtFormatL16Unorm = 57, ///< Format is L16 UNORM. + kRmtFormatX16Y16Unorm = 58, ///< Format is X16Y16 UNORM. + kRmtFormatX16Y16Snorm = 59, ///< Format is X16Y16 SNORM. + kRmtFormatX16Y16Uscaled = 60, ///< Format is X16Y16 USCALED. + kRmtFormatX16Y16Sscaled = 61, ///< Format is X16Y16 SSCALED. + kRmtFormatX16Y16Uint = 62, ///< Format is X16Y16 UINT. + kRmtFormatX16Y16Sint = 63, ///< Format is X16Y16 SINT. + kRmtFormatX16Y16Float = 64, ///< Format is X16Y16 FLOAT. + kRmtFormatX16Y16Z16W16Unorm = 65, ///< Format is X16Y16Z16W16 UNORM. + kRmtFormatX16Y16Z16W16Snorm = 66, ///< Format is X16Y16Z16W16 SNORM. + kRmtFormatX16Y16Z16W16Uscaled = 67, ///< Format is X16Y16Z16W16 USCALED. + kRmtFormatX16Y16Z16W16Sscaled = 68, ///< Format is X16Y16Z16W16 SSCALED. + kRmtFormatX16Y16Z16W16Uint = 69, ///< Format is X16Y16Z16W16 UINT. + kRmtFormatX16Y16Z16W16Sint = 70, ///< Format is X16Y16Z16W16 SINT. + kRmtFormatX16Y16Z16W16Float = 71, ///< Format is X16Y16Z16W16 FLOAT. + kRmtFormatX32Uint = 72, ///< Format is X32 UINT. + kRmtFormatX32Sint = 73, ///< Format is X32 SINT. + kRmtFormatX32Float = 74, ///< Format is X32 FLOAT. + kRmtFormatX32Y32Uint = 75, ///< Format is X32Y32 UINT. + kRmtFormatX32Y32Sint = 76, ///< Format is X32Y32 SINT. + kRmtFormatX32Y32Float = 77, ///< Format is X32Y32 FLOAT. + kRmtFormatX32Y32Z32Uint = 78, ///< Format is X32Y32Z32 UINT. + kRmtFormatX32Y32Z32Sint = 79, ///< Format is X32Y32Z32 SINT. + kRmtFormatX32Y32Z32Float = 80, ///< Format is X32Y32Z32 FLOAT. + kRmtFormatX32Y32Z32W32Uint = 81, ///< Format is X32Y32Z32W32 UINT. + kRmtFormatX32Y32Z32W32Sint = 82, ///< Format is X32Y32Z32W32 SINT. + kRmtFormatX32Y32Z32W32Float = 83, ///< Format is X32Y32Z32W32 FLOAT. + kRmtFormatD16UnormS8Uint = 84, ///< Format is D16 UNORM S8 UINT. + kRmtFormatD32UnormS8Uint = 85, ///< Format is D32 UNORM S8 UINT. + kRmtFormatX9Y9Z9E5Float = 86, ///< Format is X9Y9Z9E5 FLOAT. + kRmtFormatBC1Unorm = 87, ///< Format is BC1 UNORM. + kRmtFormatBC1Srgb = 88, ///< Format is BC1 SRGB. + kRmtFormatBC2Unorm = 89, ///< Format is BC2 UNORM. + kRmtFormatBC2Srgb = 90, ///< Format is BC2 SRGB. + kRmtFormatBC3Unorm = 91, ///< Format is BC3 UNORM. + kRmtFormatBC3Srgb = 92, ///< Format is BC3 SRGB. + kRmtFormatBC4Unorm = 93, ///< Format is BC4 UNORM. + kRmtFormatBC4Srgb = 94, ///< Format is BC4 SRGB. + kRmtFormatBC5Unorm = 95, ///< Format is BC5 UNORM. + kRmtFormatBC5Srgb = 96, ///< Format is BC5 SRGB. + kRmtFormatBC6Unorm = 97, ///< Format is BC6 UNORM. + kRmtFormatBC6Srgb = 98, ///< Format is BC6 SRGB. + kRmtFormatBC7Unorm = 99, ///< Format is BC7 UNORM. + kRmtFormatBC7Srgb = 100, ///< Format is BC7 SRGB. + kRmtFormatEtC2X8Y8Z8Unorm = 101, ///< Format is ETC2X8Y8Z8 UNORM. + kRmtFormatEtC2X8Y8Z8Srgb = 102, ///< Format is ETC2X8Y8Z8 SRGB. + kRmtFormatEtC2X8Y8Z8W1Unorm = 103, ///< Format is ETC2X8Y8Z8W1 UNORM. + kRmtFormatEtC2X8Y8Z8W1Srgb = 104, ///< Format is ETC2X8Y8Z8W1 SRGB. + kRmtFormatEtC2X8Y8Z8W8Unorm = 105, ///< Format is ETC2X8Y8Z8W8 UNORM. + kRmtFormatEtC2X8Y8Z8W8Srgb = 106, ///< Format is ETC2X8Y8Z8W8 SRGB. + kRmtFormatEtC2X11Unorm = 107, ///< Format is ETC2X11 UNORM. + kRmtFormatEtC2X11Snorm = 108, ///< Format is ETC2X11 SNORM. + kRmtFormatEtC2X11Y11Unorm = 109, ///< Format is ETC2X11Y11 UNORM. + kRmtFormatEtC2X11Y11Snorm = 110, ///< Format is ETC2X11Y11 SNORM. + kRmtFormatAstcldR4X4Unorm = 111, ///< Format is ASTCLDR4X4 UNORM. + kRmtFormatAstcldR4X4Srgb = 112, ///< Format is ASTCLDR4X4 SRGB. + kRmtFormatAstcldR5X4Unorm = 113, ///< Format is ASTCLDR5X4 UNORM. + kRmtFormatAstcldR5X4Srgb = 114, ///< Format is ASTCLDR5X4 SRGB. + kRmtFormatAstcldR5X5Unorm = 115, ///< Format is ASTCLDR5X5 UNORM. + kRmtFormatAstcldR5X5Srgb = 116, ///< Format is ASTCLDR5X5 SRGB. + kRmtFormatAstcldR6X5Unorm = 117, ///< Format is ASTCLDR6X5 UNORM. + kRmtFormatAstcldR6X5Srgb = 118, ///< Format is ASTCLDR6X5 SRGB. + kRmtFormatAstcldR6X6Unorm = 119, ///< Format is ASTCLDR6X6 UNORM. + kRmtFormatAstcldR6X6Srgb = 120, ///< Format is ASTCLDR6X6 SRGB. + kRmtFormatAstcldR8X5Unorm = 121, ///< Format is ASTCLDR8X5 UNORM. + kRmtFormatAstcldR8X5Srgb = 122, ///< Format is ASTCLDR8X5 SRGB. + kRmtFormatAstcldR8X6Unorm = 123, ///< Format is ASTCLDR8X6 UNORM. + kRmtFormatAstcldR8X6Srgb = 124, ///< Format is ASTCLDR8X6 SRGB. + kRmtFormatAstcldR8X8Unorm = 125, ///< Format is ASTCLDR8X8 UNORM. + kRmtFormatAstcldR8X8Srgb = 126, ///< Format is ASTCLDR8X8 SRGB. + kRmtFormatAstcldR10X5Unorm = 127, ///< Format is ASTCLDR10X5 UNORM. + kRmtFormatAstcldR10X5Srgb = 128, ///< Format is ASTCLDR10X5 SRGB. + kRmtFormatAstcldR10X6Unorm = 129, ///< Format is ASTCLDR10X6 UNORM. + kRmtFormatAstcldR10X6Srgb = 130, ///< Format is ASTCLDR10X6 SRGB. + kRmtFormatAstcldR10X8Unorm = 131, ///< Format is ASTCLDR10X8 UNORM. + kRmtFormatAstcldR10X10Unorm = 132, ///< Format is ASTCLDR10X10 UNORM. + kRmtFormatAstcldR12X10Unorm = 133, ///< Format is ASTCLDR12X10 UNORM. + kRmtFormatAstcldR12X10Srgb = 134, ///< Format is ASTCLDR12X10 SRGB. + kRmtFormatAstcldR12X12Unorm = 135, ///< Format is ASTCLDR12X12 UNORM. + kRmtFormatAstcldR12X12Srgb = 136, ///< Format is ASTCLDR12X12 SRGB. + kRmtFormatAstchdR4x4Float = 137, ///< Format is ASTCHDR4x4 FLOAT. + kRmtFormatAstchdR5x4Float = 138, ///< Format is ASTCHDR5x4 FLOAT. + kRmtFormatAstchdR5x5Float = 139, ///< Format is ASTCHDR5x5 FLOAT. + kRmtFormatAstchdR6x5Float = 140, ///< Format is ASTCHDR6x5 FLOAT. + kRmtFormatAstchdR6x6Float = 141, ///< Format is ASTCHDR6x6 FLOAT. + kRmtFormatAstchdR8x5Float = 142, ///< Format is ASTCHDR8x5 FLOAT. + kRmtFormatAstchdR8x6Float = 143, ///< Format is ASTCHDR8x6 FLOAT. + kRmtFormatAstchdR8x8Float = 144, ///< Format is ASTCHDR8x8 FLOAT. + kRmtFormatAstchdR10x5Float = 145, ///< Format is ASTCHDR10x5 FLOAT. + kRmtFormatAstchdR10x6Float = 146, ///< Format is ASTCHDR10x6 FLOAT. + kRmtFormatAstchdR10x8Float = 147, ///< Format is ASTCHDR10x8 FLOAT. + kRmtFormatAstchdR10x10Float = 148, ///< Format is ASTCHDR10x10 FLOAT. + kRmtFormatAstchdR12x10Float = 149, ///< Format is ASTCHDR12x10 FLOAT. + kRmtFormatAstchdR12x12Float = 150, ///< Format is ASTCHDR12x12 FLOAT. + kRmtFormatX8Y8Z8Y8Unorm = 151, ///< Format is X8Y8 Z8Y8 UNORM. + kRmtFormatX8Y8Z8Y8Uscaled = 152, ///< Format is X8Y8 Z8Y8 USCALED. + kRmtFormatY8X8Y8Z8Unorm = 153, ///< Format is Y8X8 Y8Z8 UNORM. + kRmtFormatY8X8Y8Z8Uscaled = 154, ///< Format is Y8X8 Y8Z8 USCALED. + kRmtFormatAyuv = 155, ///< Format is AYUV. + kRmtFormatUyvy = 156, ///< Format is UYVY. + kRmtFormatVyuy = 157, ///< Format is VYUY. + kRmtFormatYuY2 = 158, ///< Format is YUY2. + kRmtFormatYvY2 = 159, ///< Format is YVY2. + kRmtFormatYV12 = 160, ///< Format is YV12. + kRmtFormatNV11 = 161, ///< Format is NV11. + kRmtFormatNV12 = 162, ///< Format is NV12. + kRmtFormatNV21 = 163, ///< Format is NV21. + kRmtFormatP016 = 164, ///< Format is P016. + kRmtFormatP010 = 165, ///< Format is P010. + + // add above this. + kRmtFormatCount +} RmtFormat; + +/// An enumeration of the harware engine types +typedef enum RmtEngineType +{ + kRmtEngineTypeUniversal = 0, ///< Engine type is universal. + kRmtEngineTypeCompute = 1, ///< Engine type is compute. + kRmtEngineTypeExclusiveCompute = 2, ///< Engine type is exclusive compute. + kRmtEngineTypeDma = 3, ///< Engine type is DMA. + kRmtEngineTypeTimer = 4, ///< Engine type is timer. + kRmtEngineTypeVceEncode = 5, ///< Engine type is VCE encode. + kRmtEngineTypeUvdDecode = 6, ///< Engine type is UVD decode. + kRmtEngineTypeUvdEncode = 7, ///< Engine type is UVD encode. + kRmtEngineTypeVcnDecode = 8, ///< Engine type is VCN decode. + kRmtEngineTypeVcnEncode = 9, ///< Engine type is VCN encode. + kRmtEngineTypeHP3D = 10, ///< Engine type is HP3D. +} RmtEngineType; + +/// An enumeration of video decoder types +typedef enum RmtVideoDecoderType +{ + kRmtVideoDecoderTypeH264 = 0, ///< Decoder type H.264. + kRmtVideoDecoderTypeVC1 = 1, ///< Decoder type VC1. + kRmtVideoDecoderTypeMpeG2Idct = 2, ///< Decoder type MPEG2 IDCT. + kRmtVideoDecoderTypeMpeG2Vld = 3, ///< Decoder type MPEG2 VLD. + kRmtVideoDecoderTypeMpeG4 = 4, ///< Decoder type MPEG4. + kRmtVideoDecoderTypeWmV9 = 5, ///< Decoder type WMV9. + kRmtVideoDecoderTypeMjpeg = 6, ///< Decoder type MJPEG. + kRmtVideoDecoderTypeHvec = 7, ///< Decoder type HVEC. + kRmtVideoDecoderTypeVP9 = 8, ///< Decoder type VP9. + kRmtVideoDecoderTypeHevC10Bit = 9, ///< Decoder type HEVC 10bit. + kRmtVideoDecoderTypeVP910Bit = 10, ///< Decoder type VP 910bit. +} RmtVideoDecoderType; + +/// An enumeration of video encoder types +typedef enum RmtVideoEncoderType +{ + kRmtVideoEncoderTypeH264 = 0, ///< Encoder type H.264. + kRmtVideoEncoderTypeH265 = 1, ///< Encoder type H.265. +} RmtVideoEncoderType; + +/// An enumeration of descriptor types +typedef enum RmtDescriptorType +{ + kRmtDescriptorTypeCsvSrvUav = 0, ///< Descriptor type CSV, SRV UAV. + kRmtDescriptorTypeSampler = 1, ///< Descriptor type sampler. + kRmtDescriptorTypeRtv = 2, ///< Descriptor type RTV. + kRmtDescriptorTypeDsv = 3, ///< Descriptor type DSV. + kRmtDescriptorTypeCombinedImageSampler = 4, ///< Descriptor type combined image sampler. + kRmtDescriptorTypeSampledImage = 5, ///< Descriptor type sampled image. + kRmtDescriptorTypeStorageImage = 6, ///< Descriptor type storage image. + kRmtDescriptorTypeUniformTexelBuffer = 7, ///< Descriptor type uniform texel buffer. + kRmtDescriptorTypeStorageTexelBuffer = 8, ///< Descriptor type storage texel buffer. + kRmtDescriptorTypeUniformBuffer = 9, ///< Descriptor type uniform buffer. + kRmtDescriptorTypeStorageBuffer = 10, ///< Descriptor type storage buffer. + kRmtDescriptorTypeUniformBufferDynamic = 11, ///< Descriptor type uniform buffer dynamic. + kRmtDescriptorTypeStorageBufferDynamic = 12, ///< Descriptor type storage buffer dynamic. + kRmtDescriptorTypeInputAttachment = 13, ///< Descriptor type input attachment. + kRmtDescriptorTypeInlineUniformBlock = 14, ///< Descriptor type inline uniform block. + kRmtDescriptorTypeAccelerationStructure = 15, ///< Descriptor type acceleration structure. +} RmtDescriptorType; + +/// An structure encapsulating a descriptor pool description. +typedef struct RmtDescriptorPool +{ + RmtDescriptorType type; ///< The type of descriptor this pool holds. + uint32_t num_descriptors; ///< The number of descriptors to be allocated by this pool. +} RmtDescriptorPool; + +/// An enumeration of flag bits for command allocators. +typedef enum RmtCmdAllocatorFlagsBits +{ + kRmtCmdAllocatorAutoMemoryReuse = (1 << 0), + kRmtCmdAllocatorDisableBusyChunkTracking = (1 << 1), + kRmtCmdAllocatorThreadSafe = (1 << 2), +} RmtCmdAllocatorFlagsBits; + +/// An enumeration of image types. +typedef enum RmtImageType +{ + kRmtImageType1D = 0, ///< The iamge is 1 dimensional. + kRmtImageType2D = 1, ///< The iamge is 2 dimensional. + kRmtImageType3D = 2, ///< The iamge is 3 dimensional. + kRmtImageTypeReserved = 3 ///< Reserved for future expansion. +} RmtImageType; + +/// An enumeration of image creation flags. +typedef enum RmtImageCreationFlagBits +{ + kRmtImageCreationFlagInvariant = (1 << 0), ///< + kRmtImageCreationFlagCloneable = (1 << 1), ///< + kRmtImageCreationFlagShareable = (1 << 2), ///< Set if the image is owned by another process. + kRmtImageCreationFlagFlippable = (1 << 3), ///< + kRmtImageCreationFlagStereo = (1 << 4), ///< + kRmtImageCreationFlagCubemap = (1 << 5), ///< + kRmtImageCreationFlagPrt = (1 << 6), ///< The image is a partially resident texture. + kRmtImageCreationFlagReserved0 = (1 << 7), ///< + kRmtImageCreationFlagReadSwizzleEquations = (1 << 8), ///< + kRmtImageCreationFlagPerSubresourceInit = (1 << 9), ///< + kRmtImageCreationFlagSeparateDepthAspectRatio = (1 << 10), ///< + kRmtImageCreationFlagCopyFormatsMatch = (1 << 11), ///< + kRmtImageCreationFlagRepetitiveResolve = (1 << 12), ///< + kRmtImageCreationFlagPreferSwizzleEquations = (1 << 13), ///< + kRmtImageCreationFlagFixedTileSwizzle = (1 << 14), ///< + kRmtImageCreationFlagVideoReferenceOnly = (1 << 15), ///< + kRmtImageCreationFlagOptimalShareable = (1 << 16), ///< + kRmtImageCreationFlagSampleLocationsKnown = (1 << 17), ///< + kRmtImageCreationFlagFullResolveDestOnly = (1 << 18), ///< + kRmtImageCreationFlagExternalShared = (1 << 19), ///< Set if an image is owned by another process. +} RmtImageCreationFlagBits; + +/// An enumeration of image usage flags. +typedef enum RmtImageUsageFlagBits +{ + kRmtImageUsageFlagsShaderRead = (1 << 0), ///< Image will be read from shader (i.e., texture). + kRmtImageUsageFlagsShaderWrite = (1 << 1), ///< Image will be written from a shader (i.e., UAV). + kRmtImageUsageFlagsResolveSource = (1 << 2), ///< Image will be used as resolve source image. + kRmtImageUsageFlagsResolveDestination = (1 << 3), ///< Image will be used as resolve destination image. + kRmtImageUsageFlagsColorTarget = (1 << 4), ///< Image will be bound as a color target. + kRmtImageUsageFlagsDepthStencil = (1 << 5), ///< Image will be bound as a depth/stencil target. + kRmtImageUsageFlagsNoStencilShaderRead = + (1 + << 6), ///< Image will be neither read as stencil nor resolved on stencil aspect. Note that if RESOLVE_SOURCE bit has been set to indicate that the image could be adopted as a resolve source image and there could be stencil resolve, NO_STENCIL_SHADER_READ must be set to 0, since shader-read based stencil resolve might be performed. + kRmtImageUsageFlagsHiZNeverInvalid = + (1 + << 7), ///< Hint to PAL indicating the client will guarantee that no operations performed on this Image while it is in a decompressed state will cause Hi-Z metadata to become invalid. This allows PAL to avoid an expensive re-summarization blit in some resource barriers. + kRmtImageUsageFlagsDepthAsZ24 = + (1 << 8), ///< Use a 24-bit format for HW programming of a native 32-bit surface. If set, border color and Z-reference values are treated as Z-24. + kRmtImageUsageFlagsFirstShaderWritableMip = + (1 + << 9), ///< Only relevant if the SHADER_WRITE flag is set. Typically set to 0 so entire image is writable. If non-zero, such as an image where only level 0 is used as a color target and compute is used to generate mipmaps, PAL may be able to enable additional compression on the baseLevels which are used exclusively as color target and shader read. + kRmtImageUsageFlagsCornerSampling = + (1 + << 10), ///< Set if this image will use corner sampling in image-read scenarios. With corner sampling, the extent refers to the number of pixel corners which will be one more than the number of pixels. Border color is ignored when corner sampling is enabled. + kRmtImageUsageFlagsVrsDepth = (1 << 11) ///< Set if this depth image will be bound when VRS rendering is enabled. +} RmtImageUsageFlagBits; + +/// An enumeration of tiling types. +typedef enum RmtTilingType +{ + kRmtTilingTypeLinear = 0, ///< Image is linear. + kRmtTilingTypeOptimal = 1, ///< Image is tiled optimally for hardware. + kRmtTilingTypeStandardSwizzle = 2, ///< Imgae is tiled using API specified swizzling pattern. + kRmtTilingTypeReserved = 3 ///< Reserved for future expansion. +} RmtTilingType; + +/// An enumeration of optimization tiling modes. +typedef enum RmtTilingOptimizationMode +{ + kRmtTilingOptimizationModeBalanced = 0, ///< Tiling mode is balanced. + kRmtTilingOptimizationModeSpace = 1, ///< Tiling mode is optimized for space. + kRmtTilingOptimizationModeSpeed = 2, ///< Tiling mode is optimized for speed. + kRmtTilingOptimizationModeReserved = 3 ///< Reserved for future expansion. +} RmtTilingOptimizationMode; + +/// An enumeration of metadata modes. +typedef enum RmtMetadataMode +{ + kRmtMetadataModeDefault = 0, ///< The default metadata mode. + kRmtMetadataModeOptForTexPrefetch = 1, ///< The metadata mode is optimized for texture prefetch. + kRmtMetadataModeDisabled = 2, ///< The metadata mode is disabled. + kRmtMetadataModeReserved = 3 ///< Reserved for future expansion. +} RmtMetadataMode; + +/// An enumeration of channel swizzle values. +typedef enum RmtChannelSwizzle +{ + kRmtSwizzleZero = 0, ///< Hardwired zero value. + kRmtSwizzleOne = 1, ///< Hardwired one value. + kRmtSwizzleX = 2, ///< Read X channel from source image. + kRmtSwizzleY = 3, ///< Read Y channel from source image. + kRmtSwizzleZ = 4, ///< Read Z channel from source image. + kRmtSwizzleW = 5, ///< Read W channel from source image. + kRmtSwizzleReserved0 = 6, ///< Reserved for future expansion. + kRmtSwizzleReserved1 = 7 ///< Reserved for future expansion. +} RmtChannelSwizzle; + +/// An enumeration of all creation flags for buffers. +typedef enum RmtBufferCreationFlagBits +{ + kRmtBufferCreationFlagSparseBinding = (1 << 0), + kRmtBufferCreationFlagSparseResidency = (1 << 1), + kRmtBufferCreationFlagSparseAliasing = (1 << 2), + kRmtBufferCreationFlagProtected = (1 << 3), + kRmtBufferCreationFlagDeviceAddressCaptureReplay = (1 << 4) +} RmtBufferCreationFlagBits; + +/// An enumeration of all usage flags for buffers. +typedef enum RmtBufferUsageFlagBits +{ + kRmtBufferUsageFlagTransferSource = (1 << 0), ///< The buffer is used as a transfer source. + kRmtBufferUsageFlagTransferDestination = (1 << 1), ///< The buffer is used as a transafer destination. + kRmtBufferUsageFlagUniformTexelBuffer = (1 << 2), ///< + kRmtBufferUsageFlagStorageTexelBuffer = (1 << 3), ///< + kRmtBufferUsageFlagUniformBuffer = (1 << 4), ///< The buffer is used a uniform/constant buffer. + kRmtBufferUsageFlagStorageBuffer = (1 << 5), ///< + kRmtBufferUsageFlagIndexBuffer = (1 << 6), ///< The buffer is used as an index buffer. + kRmtBufferUsageFlagVertexBuffer = (1 << 7), ///< The buffer is used as a vertex buffer. + kRmtBufferUsageFlagIndirectBuffer = (1 << 8), ///< The buffer is used to store indirect arguments for draw calls/dispatches. + kRmtBufferUsageFlagTransformFeedbackBuffer = (1 << 9), ///< + kRmtBufferUsageFlagTransformFeedbackCounterBuffer = (1 << 10), ///< + kRmtBufferUsageFlagConditionalRendering = (1 << 11), ///< + kRmtBufferUsageFlagRayTracing = (1 << 12), ///< + kRmtBufferUsageFlagShaderDeviceAddress = (1 << 13) ///< +} RmtBufferUsageFlagBits; + +/// An enumeration of all query heap types. +typedef enum RmtQueryHeapType +{ + kRmtQueryHeapTypeOcclusion = 0, ///< The heap is used for occlusion queries. + kRmtQueryHeapTypePipelineStats = 1, ///< The heap is used for pipeline stats. + kRmtQueryHeapTypeStreamoutStats = 2, ///< The heap is used for streamout stats. + kRmtQueryHeapTypeVideoDecodeStats = 3 ///< The heap is used for video decoder stats. +} RmtQueryHeapType; + +/// An enumeration of all the pipeline stage bits. +typedef enum RmtPipelineStageBits +{ + kRmtPipelineStageMaskPs = (1 << 0), ///< The pipeline ran on the PS stage. + kRmtPipelineStageMaskHs = (1 << 1), ///< The pipeline ran on the HS stage. + kRmtPipelineStageMaskDs = (1 << 2), ///< The pipeline ran on the DS stage. + kRmtPipelineStageMaskVs = (1 << 3), ///< The pipeline ran on the VS stage. + kRmtPipelineStageMaskGs = (1 << 4), ///< The pipeline ran on the GS stage. + kRmtPipelineStageMaskCs = (1 << 5), ///< The pipeline ran on the CS stage. +} RmtPipelineStageBits; + +/// An enumeration of all pipeline creation flags. +typedef enum RmtPipelineCreateFlagBits +{ + kRmtPipelineCreateFlagInternal = (1 << 0), ///< The pipeline was created for internal use. + kRmtPipelineCreateFlagOverrideGpuHeap = (1 << 1), ///< The pipeline should override the default heap. + kRmtPipelineCreateFlagReserved0 = (1 << 2), ///< Reserved for future expansion. + kRmtPipelineCreateFlagReserved1 = (1 << 3), ///< Reserved for future expansion. + kRmtPipelineCreateFlagReserved2 = (1 << 4), ///< Reserved for future expansion. + kRmtPipelineCreateFlagReserved3 = (1 << 5), ///< Reserved for future expansion. + kRmtPipelineCreateFlagReserved4 = (1 << 6), ///< Reserved for future expansion. + kRmtPipelineCreateFlagReserved5 = (1 << 7) ///< Reserved for future expansion. +} RmtPipelineCreateFlagBits; + +/// An enumeration of flag bits for GPU events. +typedef enum RmtGpuEventFlagBits +{ + kRmtGpuEventFlagGpuOnly = (1 << 0) ///< Event is only used from the GPU. +} RmtGpuEventFlagBits; + +/// An enumeration of all internal resource types. +typedef enum RmtResourceMiscInternalType +{ +} RmtResourceMiscInternalType; + +/// A structure encapsulating the full image format. +typedef struct RmtImageFormat +{ + RmtChannelSwizzle swizzle_x; ///< The swizzle applied to the X channel. + RmtChannelSwizzle swizzle_y; ///< The swizzle applied to the Y channel. + RmtChannelSwizzle swizzle_z; ///< The swizzle applied to the Z channel. + RmtChannelSwizzle swizzle_w; ///< The swizzle applied to the W channel. + RmtFormat format; ///< The format of the channels. +} RmtImageFormat; + +/// A structure encapsulating the resource description for an kRmtResourceTypeImage. +typedef struct RmtResourceDescriptionImage +{ + uint32_t create_flags; ///< Flags describing how the image was created. + uint32_t usage_flags; ///< Flags deccribing how the image is used. + RmtImageType image_type; ///< The type of the image. + int32_t dimension_x; ///< The width of the image [1..4096]. + int32_t dimension_y; ///< The height of the image [1...4096]. + int32_t dimension_z; ///< The depth of the image [1...4096]. + RmtImageFormat format; ///< The image format. + int32_t mip_levels; ///< The number of mip-map levels the image contains. + int32_t slices; ///< The number of slices the image contains. + int32_t sample_count; ///< The number of samples. + int32_t fragment_count; ///< The number of fragments. + RmtTilingType tiling_type; ///< The tiling type used to create the image. + RmtTilingOptimizationMode tiling_optimization_mode; ///< The tiling optimization mode. + RmtMetadataMode metadata_mode; ///< The metadata mode for the image. + uint64_t max_base_alignment; ///< The alignment of the image resource. + uint64_t image_offset; ///< The offset (in bytes) from the base virtual address of the resource to the core image data. + uint64_t image_size; ///< The size (in bytes) of the core image data inside the resource. + uint64_t image_alignment; ///< The alignment of the core image data within the resources virtual address allocation. + uint64_t metadata_head_offset; ///< The offset (in bytes) from the base virtual address of the resource to the metadata header of the image. + uint64_t metadata_head_size; ///< The size (in bytes) of the metadata header inside the resource. + uint64_t metadata_head_alignment; ///< The alignment of the metadata header within the resources virtual address allocation. + uint64_t metadata_tail_offset; ///< The offset (in bytes) from the base virtual address of the resource to the metadata tail. + uint64_t metadata_tail_size; ///< The size (in bytes) of the metadata tail inside the resource. + uint64_t metadata_tail_alignment; ///< The alignment of the metadata tail within the resources virtual address allocation. + bool presentable; ///< The image is able to be presented by the display controller. + bool fullscreen; ///< The image is fullscreen, only valid when presentable is true. +} RmtResourceDescriptionImage; + +/// A structure encapsulating the resource description for an kRmtResourceTypeBuffer. +typedef struct RmtResourceDescriptionBuffer +{ + uint32_t create_flags; ///< Flags describing how the buffer was created. + uint32_t usage_flags; ///< Flags deccribing how the image is used. + uint64_t size_in_bytes; ///< The size (in bytes) of the buffer. +} RmtResourceDescriptionBuffer; + +/// A structure encapsulating the resource description for an kRmtResourceTypeGpuEvent. +typedef struct RmtResourceDescriptionGpuEvent +{ + uint32_t flags; ///< Flags describing the GPU event. +} RmtResourceDescriptionGpuEvent; + +/// A structure encapsulating the resource description for an kRmtResourceTypeBorderColorPalette. +typedef struct RmtResourceDescriptionBorderColorPalette +{ + uint32_t size_in_entries; ///< The number of entries in the palette. +} RmtResourceDescriptionBorderColorPalette; + +/// A structure encapsulating the resource description for an kRmtResourceTypePerfExperiment. +typedef struct RmtResourceDescriptionPerfExperiment +{ + uint64_t spm_size; ///< The size of the perf experiment memory used for SPM counters. + uint64_t sqtt_size; ///< The size of the perf experiment memory used for SQTT counters. + uint64_t counter_size; ///< The size of the perf experiment memory used for regular counters. +} RmtResourceDescriptionPerfExperiment; + +/// A structure encapsulating the resource description for an kRmtResourceTypeQueryHeap. +typedef struct RmtResourceDescriptionQueryHeap +{ + RmtQueryHeapType heap_type; ///< The type of the query heap. + bool enable_cpu_access; ///< Set to true if CPU access is allowed. +} RmtResourceDescriptionQueryHeap; + +/// A structure encapsulating a resource description for an kRmtResourceTypeVideoDecoder. +typedef struct RmtResourceDescriptionVideoDecoder +{ + RmtEngineType engine_type; ///< The hardware engine type. + RmtVideoDecoderType decoder_type; ///< The video decoder type. + uint32_t width; ///< The width of the video. + uint32_t height; ///< The height of the video. +} RmtResourceDescription; + +/// A structure encapsulating a resource description for an kRmtResourceTypeVideoEncoder. +typedef struct RmtResourceDescriptionVideoEncoder +{ + RmtEngineType engine_type; ///< The hardware engine type. + RmtVideoEncoderType encoder_type; ///< The video encoder type. + uint16_t width; ///< The width of the video. + uint16_t height; ///< The height of the video. + RmtImageFormat format; ///< The video image format. +} RmtResourceDescriptionVideoEncoder; + +/// A structure encapsulating a resource description for an kRmtResourceTypeDescriptorHeap. +typedef struct RmtResourceDescriptionHeap +{ + uint8_t flags; ///< The heap creation flags. + uint64_t size; ///< The size of the heap, in bytes. + RmtPageSize alignment; ///< The alignment of the heap, expressed as a page size. + uint8_t segment_index; ///< The segment index where the heap was requested to be created. +} RmtResourceDescriptionHeap; + +/// A structure encapsulating the resource description for an kRmtResourceTypePipeline. +typedef struct RmtResourceDescriptionPipeline +{ + uint64_t internal_pipeline_hash_hi; ///< Hi bits of the internal pipeline hash. + uint64_t internal_pipeline_hash_lo; ///< Lo bits of the internal pipeline hash. + uint32_t create_flags; ///< Create flags for the pipeline. + uint32_t stage_mask; ///< A bitfield representing which stages are active for this pipeline. + bool is_ngg; ///< Set to true if NGG is enabled. +} RmtResourceDescriptionPipeline; + +typedef struct RmtResourceDescriptionDescriptorHeap +{ + RmtDescriptorType descriptor_type; ///< The type of descriptors in the descriptor heap. + bool shader_visible; ///< Set to true if the heap is shader-visible. + uint8_t gpu_mask; ///< For multiple adapter mode, this is a bitmask indicating which adapters the heap applies to. + uint16_t num_descriptors; ///< The number of descriptors in the heap. +} RmtResourceDescriptionDescriptorHeap; + +typedef struct RmtResourceDescriptionDescriptorPool +{ + uint16_t max_sets; ///< Maximum number of descriptor sets that can be allocated from the pool. + uint8_t pools_count; ///< The number of pool descriptions in the pools[] array. + RmtDescriptorPool pools[RMT_MAX_POOLS]; ///< Array of pool descriptions. +} RmtResourceDescriptionDescriptorPool; + +typedef struct RmtResourceDescriptionCommandAllocator +{ + uint8_t flags; ///< Create flags for the command allocator. + RmtHeapType cmd_data_heap; ///< The preferred allocation heap for executable command data. + uint64_t + cmd_data_size; ///< The size, in bytes, of the base memory allocations the command allocator will make for executable command data. Expressed as 4Kb chunks. + uint64_t + cmd_data_suballoc_size; ///< The size, in bytes, of the chunks the command allocator will give to command buffers for executable command data. Expressed as 4Kb chunks. + RmtHeapType embed_data_heap; ///< The preferred allocation heap for embedded command data. + uint64_t + embed_data_size; ///< The size, in bytes, of the base memory allocations the command allocator will make for embedded command data. Expressed as 4Kb chunks. + uint64_t + embed_data_suballoc_size; ///< The size, in bytes, of the chunks the command allocator will give to command buffers for embedded command data. Expressed as 4Kb chunks. + RmtHeapType gpu_scratch_heap; ///< The preferred allocation heap for GPU scratch memory. + uint64_t + gpu_scratch_size; ///< The size, in bytes, of the base memory allocations the command allocator will make for GPU scratch memory. Expressed as 4Kb chunks. + uint64_t + gpu_scratch_suballoc_size; ///< The size, in bytes, of the chunks the command allocator will give to command buffers for GPU scratch memory. Expressed as 4Kb chunks. +} RmtResourceDescriptionCommandAllocator; + +/// A structure encapsulating the resource description for a miscellaneous internal resource. +typedef struct RmtResourceDescriptionMiscInternal +{ + RmtResourceMiscInternalType type; ///< The type of the miscellaneous internal resource. +} RmtResourceDescriptionMiscInternal; + +/// A structure encapsulating common fields for all RMT tokens. +typedef struct RmtTokenCommon +{ + uint64_t thread_id; ///< The thread ID that the token was emitted from. + RmtProcessId process_id; ///< The process ID that the token was emitted from. + uint64_t timestamp; ///< The timestamp (in RMT clocks) when the token was generated. + size_t offset; ///< The offset (in bytes) into the parent RMT stream. + int32_t stream_index; ///< The index of the RMT stream that the token was parsed from. +} RmtTokenCommon; + +/// A structure encapsulating a timestamp. +typedef struct RmtTokenTimestamp +{ + RmtTokenCommon common; ///< Fields common to all tokens. + uint64_t timestamp; ///< A 64bit timestamp (in RMT clocks). + uint32_t frequency; ///< CPU frequency +} RmtTimestampToken; + +/// A structure encapsulating a free of virtual memory. +typedef struct RmtTokenVirtualFree +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtGpuAddress virtual_address; ///< The virtual or physical address being freed. +} RmtTokenVirtualFree; + +/// A structure encapsulating page table updates. +typedef struct RmtTokenPageTableUpdate +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtGpuAddress virtual_address; ///< The virtual address of the allocation being mapped. + RmtGpuAddress physical_address; ///< The physical address of the allocation being mapped. + uint64_t size_in_pages; ///< The size of the mapping in pages. + RmtPageSize page_size; ///< The page size for the mapping. + bool is_unmapping; + RmtPageTableUpdateType update_type; ///< The type of the page table update. + RmtPageTableController controller; ///< The type of system controlling page table updates. +} RmtTokenPageTableUpdate; + +/// A structure encapsulating user data. +typedef struct RmtTokenUserdata +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtUserdataType userdata_type; ///< The type of the user data in the payload. + int32_t size_in_bytes; ///< The size (in bytes) of the payload. The largest we can encode is 1MB. + const void* payload; ///< The payload of the user data. + RmtResourceIdentifier resource_identifer; ///< The resource identifer, only valid when usedataType is RMT_USERDATA_TYPE_NAME. +} RmtTokenUserdata; + +/// A structure encapsulating misc data. +typedef struct RmtTokenMisc +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtMiscType type; ///< The type of miscellaneous event that occurred. +} RmtTokenMisc; + +/// A structure encapsulating a residency update. +typedef struct RmtTokenResourceReference +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtResidencyUpdateType residency_update_type; ///< The type of residency update. + RmtGpuAddress virtual_address; ///< The virtual address of the residency memory where the residency update was requested. + RmtQueue queue; ///< The queue where the reference was added or removed. +} RmtTokenResourceReference; + +/// A structure encapsulating a resource being bound to a virtual memory address range. +typedef struct RmtTokenResourceBind +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtResourceIdentifier resource_identifier; ///< A unique identifier for the resource being bound. + RmtGpuAddress virtual_address; ///< The virtual address that the resource is being bound to. + uint64_t size_in_bytes; ///< The size of the resource in bytes. + bool is_system_memory; ///< A boolean value indicates if the bind is in system memory. +} RmtTokenResourceBind; + +/// A structure encapsulating a process event. +typedef struct RmtTokenProcessEvent +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtProcessEventType event_type; ///< The process event type. +} RmtTokenProcessEvent; + +/// A structure encapsulating a page reference. +typedef struct RmtTokenPageReference +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtPageSize page_size; ///< The size of each page in pageState. + uint8_t page_state[RMT_PAGE_REF_COUNT]; ///< A bitfield of page state. +} RmtTokenPageReference; + +/// A structure encapsulating a CPU map token. +typedef struct RmtTokenCpuMap +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtGpuAddress virtual_address; ///< The virtual address that was mapped for CPU access. + bool is_unmap; ///< The map operation is an unmap +} RmtTokenCpuMap; + +/// A structure encapsulating a time delta. +typedef struct RmtTokenVirtualAllocate +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtGpuAddress virtual_address; ///< The virtual address that was allocated. + uint64_t size_in_bytes; ///< The size (in bytes) of the allocation. + RmtOwnerType owner_type; ///< The owner of the allocation. + RmtHeapType preference[4]; ///< An ordered list of heap preferences for the allocation. +} RmtTokenVirtualAllocate; + +/// A structure encapsulating a resource description. +typedef struct RmtTokenResourceCreate +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtResourceIdentifier resource_identifier; ///< A unique identifier for the resource. + RmtOwnerType owner_type; ///< The part of the software stack creating this resource. + //RmtOwnerCategory owner_category; ///< The owner category. + RmtCommitType commit_type; ///< The type of commitment reqeuired for this resource. + RmtResourceType resource_type; ///< The resource type. + + // A union of the different resource descriptions, access based on resourceType. + union + { + RmtResourceDescriptionImage image; ///< Valid when resourceType is kRmtResourceTypeImage. + RmtResourceDescriptionBuffer buffer; ///< Valid when resourceType is kRmtResourceTypeBuffer. + RmtResourceDescriptionGpuEvent gpu_event; ///< Valid when resourceType is kRmtResourceTypeGpuEvent. + RmtResourceDescriptionBorderColorPalette + border_color_palette; ///< Valid when resourceType is kRmtResourceTypeBorderColorPalette. + RmtResourceDescriptionPerfExperiment perf_experiment; ///< Valid when resourceType is kRmtResourceTypePerfExperiment. + RmtResourceDescriptionQueryHeap query_heap; ///< Valid when resourceType is kRmtResourceTypeQueryHeap. + RmtResourceDescriptionPipeline pipeline; ///< Valid when resourceType is kRmtResourceTypePipeline. + RmtResourceDescriptionVideoDecoder video_decoder; ///< Valid when resourceType is kRmtResourceTypeVideoDecoder. + RmtResourceDescriptionVideoEncoder video_encoder; ///< Valid when resourceType is kRmtResourceTypeVideoEncoder. + RmtResourceDescriptionHeap heap; ///< Valid when resourceType is kRmtResourceTypeHeap. + RmtResourceDescriptionDescriptorHeap descriptor_heap; ///< Valid when resourceType is kRmtResourceTypeDescriptorHeap. + RmtResourceDescriptionDescriptorPool descriptor_pool; ///< Valid when resourceType is kRmtResourceTypeDescriptorPool. + RmtResourceDescriptionCommandAllocator command_allocator; ///< Valid when resourceType is kRmtResourceTypeCommandAllocator. + RmtResourceDescriptionMiscInternal misc_internal; ///< Valid when resourceType is RMT_RESOURCE_TYPE_MISC_INTERNAL. + }; +} RmtTokenResourceCreate; + +/// A structure encapsulating a time delta. +typedef struct RmtTokenTimeDelta +{ + RmtTokenCommon common; ///< Fields common to all tokens. + uint64_t delta; ///< A 12bit delta (in RMT clocks). +} RmtTokenTimeDelta; + +/// A structure encapsulating a resource being unbound from a virtual memory address range. +typedef struct RmtTokenResourceDestroy +{ + RmtTokenCommon common; ///< Fields common to all tokens. + RmtResourceIdentifier resource_identifier; ///< A unique identifier for the resource being unbound. +} RmtTokenResourceUnbind; + +/// A structure encapsulating the token. +typedef struct RmtToken +{ + RmtTokenType type; ///< The type of the RMT token. + + union + { + RmtTokenCommon common; ///< Valid for any type, as all structures begin with a RmtTokenCommon structure. + RmtTimestampToken timestamp_token; ///< Valid when type is kRmtTokenTypeTimestamp. + RmtTokenVirtualFree virtual_free_token; ///< Valid when type is kRmtTokenTypeVirtualFree. + RmtTokenPageTableUpdate page_table_update_token; ///< Valid when type is kRmtTokenTypePageTableUpdate. + RmtTokenUserdata userdata_token; ///< Valid when type is kRmtTokenTypeUserdata. + RmtTokenMisc misc_token; ///< Valid when type is kRmtTokenTypeMisc. + RmtTokenResourceReference resource_reference; ///< Valid when type is kRmtTokenTypeResourceReference. + RmtTokenResourceBind resource_bind_token; ///< Valid when type is kRmtTokenTypeResourceBind. + RmtTokenProcessEvent process_event_token; ///< Valid when type is kRmtTokenTypeProcessEvent. + RmtTokenPageReference page_reference_token; ///< Valid when type is kRmtTokenTypePageReference. + RmtTokenCpuMap cpu_map_token; ///< Valid when type is kRmtTokenTypeCpuMap. + RmtTokenVirtualAllocate virtual_allocate_token; ///< Valid when type is kRmtTokenTypeVirtualAllocate. + RmtTokenResourceCreate resource_create_token; ///< Valid when type is kRmtTokenTypeResourceCreate. + RmtTokenTimeDelta time_delta_token; ///< Valid when type is kRmtTokenTypeTimeDelta. + RmtTokenResourceDestroy resource_destroy_token; ///< Valid when type is kRmtTokenTypeResourceDestroy. + }; + +} RmtToken; + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_FORMAT_H_ diff --git a/source/parser/rmt_parser.cpp b/source/parser/rmt_parser.cpp new file mode 100644 index 0000000..2e779e2 --- /dev/null +++ b/source/parser/rmt_parser.cpp @@ -0,0 +1,1323 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of functions for parsing RMT data. +//============================================================================= + +#include "rmt_parser.h" +#include "rmt_format.h" +#include "rmt_util.h" +#include "rmt_assert.h" + +// size in bytes of each token. +#define RMT_TOKEN_SIZE_TIMESTAMP (96 / 8) ///< Timestamp Token Size, in bytes +#define RMT_TOKEN_SIZE_RESERVED_0 (0 / 8) ///< Reserved_0 Token Size, in bytes +#define RMT_TOKEN_SIZE_RESERVED_1 (0 / 8) ///< Reserved_1 Token Size, in bytes +#define RMT_TOKEN_SIZE_PAGE_TABLE_UPDATE (144 / 8) ///< Page Table Update Token Size, in bytes +#define RMT_TOKEN_SIZE_USERDATA (32 / 8) ///< Userdata Token Size, in bytes +#define RMT_TOKEN_SIZE_MISC (16 / 8) ///< Misc Token Size, in bytes +#define RMT_TOKEN_SIZE_RESOURCE_REFERENCE (64 / 8) ///< Resource Reference Token Size, in bytes +#define RMT_TOKEN_SIZE_RESOURCE_BIND (136 / 8) ///< Resource Bind Token Size, in bytes +#define RMT_TOKEN_SIZE_PROCESS_EVENT (48 / 8) ///< Process Event Token Size, in bytes +#define RMT_TOKEN_SIZE_PAGE_REFERENCE (80 / 8) ///< Page Reference Token Size, in bytes +#define RMT_TOKEN_SIZE_CPU_MAP (64 / 8) ///< CPU Map Token Size, in bytes +#define RMT_TOKEN_SIZE_VIRTUAL_FREE (56 / 8) ///< Virtual Free Token Size, in bytes +#define RMT_TOKEN_SIZE_VIRTUAL_ALLOCATE (96 / 8) ///< Virtual Allocate Token Size, in bytes +#define RMT_TOKEN_SIZE_RESOURCE_CREATE (56 / 8) ///< Resource Create Token Size, in bytes +#define RMT_TOKEN_SIZE_RESOURCE_DESTROY (40 / 8) ///< Resource Destroy Token Size, in bytes + +#define IMAGE_RESOURCE_TOKEN_SIZE (304 / 8) ///< Image Resource Token Size +#define BUFFER_RESOURCE_TOKEN_SIZE (88 / 8) ///< Buffer Resource Token Size +#define GPU_EVENT_RESOURCE_TOKEN_SIZE (8 / 8) ///< GPU Event Resource Token Size +#define BORDER_COLOR_PALETTE_RESOURCE_TOKEN_SIZE (8 / 8) ///< Border Color Palette Resource Token Size +#define INDIRECT_CMD_GENERATOR_RESOURCE_TOKEN_SIZE (0 / 8) ///< Indirect Cmd Generator Resource Token Size +#define MOTION_ESTIMATOR_RESOURCE_TOKEN_SIZE (0 / 8) ///< Motion Estimator Resource Token Size +#define PERF_EXPERIMENT_RESOURCE_TOKEN_SIZE (96 / 8) ///< Perf Experiment Resource Token Size +#define QUERY_HEAP_RESOURCE_TOKEN_SIZE (8 / 8) ///< Query Heap Resource Token Size +#define VIDEO_DECODER_RESOURCE_TOKEN_SIZE (32 / 8) ///< Video Decoder Resource Token Size +#define VIDEO_ENCODER_RESOURCE_TOKEN_SIZE (48 / 8) ///< Video Encoder Resource Token Size +#define TIMESTAMP_RESOURCE_TOKEN_SIZE (0 / 8) ///< Timestamp Resource Token Size +#define HEAP_RESOURCE_TOKEN_SIZE (80 / 8) ///< Heap Resource Token Size +#define PIPELINE_RESOURCE_TOKEN_SIZE (152 / 8) ///< Pipeline Resource Token Size +#define DESCRIPTOR_HEAP_RESOURCE_TOKEN_SIZE (32 / 8) ///< Descriptor Heap Resource Token Size +#define DESCRIPTOR_POOL_RESOURCE_TOKEN_SIZE (24 / 8) ///< Descriptor Pool Resource Token Size +#define CMD_ALLOCATOR_RESOURCE_TOKEN_SIZE (352 / 8) ///< Cmd Allocator Resource Token Size +#define MISC_INTERNAL_RESOURCE_TOKEN_SIZE (8 / 8) ///< Misc Internal Resource Token Size + +#define DESCRIPTOR_POOL_DESCRIPTION_SIZE (32 / 8) ///< Descriptor Pool description size + +#define TIMESTAMP_QUANTA (32) + +// read unsigned 8 bit value from the parser's buffer. +static RmtErrorCode ReadUInt8(const RmtParser* rmt_parser, uint8_t* value, size_t offset) +{ + RMT_RETURN_ON_ERROR(rmt_parser->stream_current_offset + sizeof(uint8_t) <= rmt_parser->stream_size, RMT_EOF); + RMT_RETURN_ON_ERROR((size_t)(rmt_parser->file_buffer_offset + offset + sizeof(uint8_t)) <= (size_t)rmt_parser->file_buffer_actual_size, + RMT_ERROR_INVALID_SIZE); + + const uintptr_t base_address = (uintptr_t)rmt_parser->file_buffer + rmt_parser->file_buffer_offset + offset; + const uint8_t* base_ptr = (const uint8_t*)base_address; + *value = *base_ptr; + return RMT_OK; +} + +// read an unsigned 16bit value from the parser's buffer +static RmtErrorCode ReadUInt16(const RmtParser* rmt_parser, uint16_t* value, size_t offset) +{ + // validate that there is enough data to read from the buffers + RMT_RETURN_ON_ERROR(rmt_parser->stream_current_offset + sizeof(uint16_t) <= rmt_parser->stream_size, RMT_EOF); + RMT_RETURN_ON_ERROR((size_t)(rmt_parser->file_buffer_offset + offset + sizeof(uint16_t)) <= (size_t)rmt_parser->file_buffer_actual_size, + RMT_ERROR_INVALID_SIZE); + + const uintptr_t base_address = (uintptr_t)rmt_parser->file_buffer + rmt_parser->file_buffer_offset + offset; + const uint16_t* base_ptr = (const uint16_t*)base_address; + *value = *base_ptr; + return RMT_OK; +} + +// read an unsigned 32bit value from the parser's buffer +static RmtErrorCode ReadUInt32(const RmtParser* rmt_parser, uint32_t* value, size_t offset) +{ + // validate that there is enough data to read from the buffers + RMT_RETURN_ON_ERROR(rmt_parser->stream_current_offset + sizeof(uint32_t) <= rmt_parser->stream_size, RMT_EOF); + RMT_RETURN_ON_ERROR((size_t)(rmt_parser->file_buffer_offset + offset + sizeof(uint32_t)) <= (size_t)rmt_parser->file_buffer_actual_size, + RMT_ERROR_INVALID_SIZE); + + const uintptr_t base_address = (uintptr_t)rmt_parser->file_buffer + rmt_parser->file_buffer_offset + offset; + const uint32_t* base_ptr = (const uint32_t*)base_address; + *value = *base_ptr; + return RMT_OK; +} + +// read an unsigned 64bit value from the parser's buffer +static RmtErrorCode ReadUInt64(RmtParser* rmt_parser, uint64_t* value, size_t offset) +{ + // validate that there is enough data to read from the buffers + RMT_RETURN_ON_ERROR(rmt_parser->stream_current_offset + sizeof(uint64_t) <= rmt_parser->stream_size, RMT_EOF); + RMT_RETURN_ON_ERROR((size_t)(rmt_parser->file_buffer_offset + offset + sizeof(uint64_t)) <= (size_t)rmt_parser->file_buffer_actual_size, + RMT_ERROR_INVALID_SIZE); + + const uintptr_t base_address = (uintptr_t)rmt_parser->file_buffer + rmt_parser->file_buffer_offset + offset; + const uint8_t* base_ptr = (const uint8_t*)base_address; + *value = ((uint64_t)base_ptr[7] << 56) | ((uint64_t)base_ptr[6] << 48) | ((uint64_t)base_ptr[5] << 40) | ((uint64_t)base_ptr[4] << 32) | + ((uint64_t)base_ptr[3] << 24) | ((uint64_t)base_ptr[2] << 16) | ((uint64_t)base_ptr[1] << 8) | ((uint64_t)base_ptr[0]); + + return RMT_OK; +} + +// read an array of unsigned 8 bit value from the parser's buffer. +static RmtErrorCode ReadBytes(const RmtParser* rmt_parser, uint8_t* value, size_t offset, size_t size) +{ + RMT_RETURN_ON_ERROR(rmt_parser->stream_current_offset + (sizeof(uint8_t) * size) <= rmt_parser->stream_size, RMT_EOF); + RMT_RETURN_ON_ERROR((size_t)(rmt_parser->file_buffer_offset + offset + (sizeof(uint8_t) * size)) <= (size_t)rmt_parser->file_buffer_actual_size, + RMT_ERROR_INVALID_SIZE); + + const uintptr_t base_address = (uintptr_t)rmt_parser->file_buffer + rmt_parser->file_buffer_offset + offset; + const uint8_t* base_ptr = (const uint8_t*)base_address; + for (size_t current_byte_index = 0; current_byte_index < size; ++current_byte_index) + { + value[current_byte_index] = base_ptr[current_byte_index]; + } + + return RMT_OK; +} + +// Get the specified bits from the provided source data, up to 64 bits. +// NOTE: pDstVal will be cleared prior to the copy, any existing data will be lost. +static uint64_t ReadBitsFromBuffer(const uint8_t* buffer, size_t buffer_size, uint32_t end_bit, uint32_t start_bit) +{ + const uint32_t num_bits = end_bit - start_bit + 1; + const uint32_t start_byte = start_bit / 8; + const uint8_t start_byte_shift = (start_bit % 8); + const uint8_t start_byte_bits = (uint8_t)RMT_MINIMUM(((uint32_t)8 - start_byte_shift), num_bits); + const uint8_t start_byte_mask = ((1 << start_byte_bits) - 1); + const uint32_t end_byte = end_bit / 8; + const uint8_t end_byte_bits = (uint8_t)RMT_MINIMUM(((end_bit % 8) + 1), num_bits); + const uint8_t end_byte_mask = (1 << end_byte_bits) - 1; + const uint32_t num_bytes = end_byte - start_byte + 1; + + uint64_t ret_val = 0; + if (((start_byte + num_bytes) <= buffer_size) && (buffer != nullptr)) + { + int8_t total_bits_copied = 0; + + for (uint32_t i = 0; i < num_bytes; ++i) + { + uint32_t src_idx = (start_byte + i); + uint8_t src_mask = 0xFF; + uint8_t src_shift = 0; + uint8_t bits = 8; + if (i == 0) + { + src_mask = start_byte_mask; + bits = start_byte_bits; + src_shift = start_byte_shift; + } + else if (i == (num_bytes - 1)) + { + src_mask = end_byte_mask; + bits = end_byte_bits; + } + + // Get the source byte + uint8_t src_byte = buffer[src_idx]; + + // Mask off the target bits, in most cases this will be all of them but for the first + // or last byte it may be less + src_byte = (src_byte >> src_shift) & src_mask; + + ret_val |= (static_cast(src_byte) << total_bits_copied); + + total_bits_copied += bits; + } + } + + return ret_val; +} + +// update the parser's notion of time +static void UpdateTimeState(RmtParser* rmt_parser, const uint16_t token_header) +{ + // work out the token type. + const RmtTokenType token_type = (RmtTokenType)(token_header & 0xf); + + if (rmt_parser->seen_timestamp == 0) + { + if (token_type == kRmtTokenTypeTimestamp) + { + uint64_t timestamp = 0; + RmtErrorCode error_code = ReadUInt64(rmt_parser, ×tamp, 0); + if (error_code != RMT_OK) + { + return; + } + + uint32_t frequency = 0; + error_code = ReadUInt32(rmt_parser, &frequency, 8); + if (error_code != RMT_OK) + { + return; + } + + rmt_parser->start_timestamp = ((timestamp >> 4) * TIMESTAMP_QUANTA); + rmt_parser->current_timestamp = rmt_parser->start_timestamp; + rmt_parser->cpu_frequency = frequency; + rmt_parser->seen_timestamp = 1; + } + } + else + { + switch (token_type) + { + case kRmtTokenTypeCpuMap: + case kRmtTokenTypeVirtualFree: + case kRmtTokenTypeMisc: + case kRmtTokenTypePageTableUpdate: + case kRmtTokenTypeProcessEvent: + case kRmtTokenTypeResourceBind: + case kRmtTokenTypeResourceCreate: + case kRmtTokenTypeResourceReference: + case kRmtTokenTypeUserdata: + case kRmtTokenTypeVirtualAllocate: + { + const uint64_t delta = (((token_header >> 4) & 0xf) * TIMESTAMP_QUANTA); + rmt_parser->current_timestamp += delta; + } + break; + + case kRmtTokenTypeTimeDelta: + { + uint8_t num_delta_bytes = 0; + RmtErrorCode error_code = ReadUInt8(rmt_parser, &num_delta_bytes, 0); + if (error_code != RMT_OK) + { + return; + } + num_delta_bytes = (num_delta_bytes >> 4) & 7; + + uint64_t delta = 0; + error_code = ReadBytes(rmt_parser, (uint8_t*)&delta, 1, num_delta_bytes); + if (error_code != RMT_OK) + { + return; + } + + delta *= TIMESTAMP_QUANTA; + rmt_parser->current_timestamp += delta; + } + break; + + case kRmtTokenTypeTimestamp: + { + uint64_t timestamp = 0; + const RmtErrorCode error_code = ReadUInt64(rmt_parser, ×tamp, 0); + if (error_code != RMT_OK) + { + return; + } + timestamp = (timestamp >> 4) * TIMESTAMP_QUANTA; + + // calculate delta from start timestamp. + const uint64_t delta = timestamp; + rmt_parser->current_timestamp = delta; + } + break; + + default: + break; + } + } +} + +// Calculate the size of the resource description size from the type. +static int32_t GetResourceDescriptionSize(RmtParser* rmt_parser, RmtResourceType resource_type) +{ + switch (resource_type) + { + case kRmtResourceTypeImage: + return IMAGE_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeBuffer: + return BUFFER_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeGpuEvent: + return GPU_EVENT_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeBorderColorPalette: + return BORDER_COLOR_PALETTE_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypePerfExperiment: + return PERF_EXPERIMENT_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeQueryHeap: + return QUERY_HEAP_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeVideoDecoder: + return VIDEO_DECODER_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeVideoEncoder: + return VIDEO_ENCODER_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeHeap: + return HEAP_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypePipeline: + return PIPELINE_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeDescriptorHeap: + return DESCRIPTOR_HEAP_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeDescriptorPool: + { + uint32_t pool_size_count = 0; + const RmtErrorCode error_code = ReadUInt32(rmt_parser, &pool_size_count, RMT_TOKEN_SIZE_RESOURCE_CREATE); + RMT_UNUSED(error_code); + pool_size_count = (pool_size_count >> 16) & 0xff; + return 3 + (pool_size_count * 4); + } + case kRmtResourceTypeCommandAllocator: + return CMD_ALLOCATOR_RESOURCE_TOKEN_SIZE; + case kRmtResourceTypeMiscInternal: + return MISC_INTERNAL_RESOURCE_TOKEN_SIZE; + + // all of the the rest have no payload + default: + return 0; + } +} + +// Calculate the size of the token from the header and the data in the parser. +static int32_t GetTokenSize(RmtParser* rmt_parser, const uint16_t token_header) +{ + // work out the token type (bottom 4 bits) + const RmtTokenType token_type = (RmtTokenType)(token_header & 0xf); + + switch (token_type) + { + case kRmtTokenTypeTimestamp: + return RMT_TOKEN_SIZE_TIMESTAMP; + case kRmtTokenTypeVirtualFree: + return RMT_TOKEN_SIZE_VIRTUAL_FREE; + case kRmtTokenTypePageTableUpdate: + return RMT_TOKEN_SIZE_PAGE_TABLE_UPDATE; + case kRmtTokenTypeUserdata: + { + uint32_t payload_length = 0; + const RmtErrorCode error_code = ReadUInt32(rmt_parser, &payload_length, 0); // [31:12] + RMT_RETURN_ON_ERROR(error_code == RMT_OK, 0); + payload_length = (payload_length >> 12) & 0xfffff; + return RMT_TOKEN_SIZE_USERDATA + payload_length; + } + case kRmtTokenTypeMisc: + return RMT_TOKEN_SIZE_MISC; + case kRmtTokenTypeResourceReference: + return RMT_TOKEN_SIZE_RESOURCE_REFERENCE; + case kRmtTokenTypeResourceBind: + return RMT_TOKEN_SIZE_RESOURCE_BIND; + case kRmtTokenTypeProcessEvent: + return RMT_TOKEN_SIZE_PROCESS_EVENT; + case kRmtTokenTypePageReference: + return RMT_TOKEN_SIZE_PAGE_REFERENCE; + case kRmtTokenTypeCpuMap: + return RMT_TOKEN_SIZE_CPU_MAP; + case kRmtTokenTypeVirtualAllocate: + return RMT_TOKEN_SIZE_VIRTUAL_ALLOCATE; + case kRmtTokenTypeResourceCreate: + { + const int32_t base_size_of_resource_description = RMT_TOKEN_SIZE_RESOURCE_CREATE; + uint8_t resource_type_byte = 0; + const RmtErrorCode error_code = ReadUInt8(rmt_parser, &resource_type_byte, 6); // [53:48] + RMT_RETURN_ON_ERROR(error_code == RMT_OK, 0); + const RmtResourceType resource_type = (RmtResourceType)resource_type_byte; + const int32_t size_of_resource_description_payload = GetResourceDescriptionSize(rmt_parser, resource_type); + return base_size_of_resource_description + size_of_resource_description_payload; + } + case kRmtTokenTypeTimeDelta: + { + uint8_t num_delta_bytes = 0; + const RmtErrorCode error_code = ReadUInt8(rmt_parser, &num_delta_bytes, 0); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, 0); + num_delta_bytes = (num_delta_bytes >> 4) & 7; + return 1 + num_delta_bytes; + } + case kRmtTokenTypeResourceDestroy: + return RMT_TOKEN_SIZE_RESOURCE_DESTROY; + default: + return 1; // Advance by a byte to try to recover. + } +} + +// populate the common fields of all tokens. +static void PopulateCommonFields(RmtParser* rmt_parser, RmtTokenCommon* common) +{ + common->offset = rmt_parser->stream_start_offset + rmt_parser->stream_current_offset; + common->timestamp = rmt_parser->current_timestamp; + common->thread_id = rmt_parser->thread_id; + common->process_id = rmt_parser->process_id; // sometimes overriden. + common->stream_index = rmt_parser->stream_index; +} + +// parse a timestamp token +static RmtErrorCode ParseTimestamp(RmtParser* rmt_parser, const uint16_t token_header, RmtTimestampToken* out_timestamp_token) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_timestamp_token->common); + + // token-specific fields. + uint8_t data[RMT_TOKEN_SIZE_TIMESTAMP]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + uint64_t timestamp = ReadBitsFromBuffer(data, sizeof(data), 63, 4); + out_timestamp_token->timestamp = (timestamp >> 4) * TIMESTAMP_QUANTA; + out_timestamp_token->frequency = (uint32_t)ReadBitsFromBuffer(data, sizeof(data), 95, 64); + return RMT_OK; +} + +// parse a virtual free token +static RmtErrorCode ParseVirtualFree(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenVirtualFree* out_free_token) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_free_token->common); + + uint8_t data[RMT_TOKEN_SIZE_VIRTUAL_FREE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_free_token->virtual_address = ReadBitsFromBuffer(data, sizeof(data), 55, 8); + + return RMT_OK; +} + +// parse a page table update +static RmtErrorCode ParsePageTableUpdate(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenPageTableUpdate* out_page_table_update_token) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_page_table_update_token->common); + + // token-specific fields. + uint8_t data[RMT_TOKEN_SIZE_PAGE_TABLE_UPDATE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + const uint64_t virtual_address = ReadBitsFromBuffer(data, sizeof(data), 43, 8); + out_page_table_update_token->virtual_address = virtual_address << 12; + + const uint64_t physical_address = ReadBitsFromBuffer(data, sizeof(data), 79, 44); + out_page_table_update_token->physical_address = physical_address << 12; + + const uint64_t size_in_pages = ReadBitsFromBuffer(data, sizeof(data), 99, 80); + out_page_table_update_token->size_in_pages = size_in_pages; + const RmtPageSize page_size = (RmtPageSize)(ReadBitsFromBuffer(data, sizeof(data), 102, 100)); + out_page_table_update_token->page_size = page_size; + + const bool is_unmap = (bool)(ReadBitsFromBuffer(data, sizeof(data), 103, 103)); + out_page_table_update_token->is_unmapping = is_unmap; + + const uint64_t process_id = ReadBitsFromBuffer(data, sizeof(data), 135, 104); + out_page_table_update_token->common.process_id = process_id; // override the procID from the token. + + const RmtPageTableUpdateType update_type = (RmtPageTableUpdateType)(ReadBitsFromBuffer(data, sizeof(data), 137, 136)); + out_page_table_update_token->update_type = update_type; + + const RmtPageTableController controller = (RmtPageTableController)(ReadBitsFromBuffer(data, sizeof(data), 138, 138)); + out_page_table_update_token->controller = controller; + + return RMT_OK; +} + +// parse a user data blob +static RmtErrorCode ParseUserdata(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenUserdata* out_userdata_token) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_userdata_token->common); + + uint32_t payload_length = 0; + const RmtErrorCode error_code = ReadUInt32(rmt_parser, &payload_length, 0); // [31:12] + RMT_RETURN_ON_ERROR(error_code == RMT_OK, 0); + out_userdata_token->userdata_type = (RmtUserdataType)((payload_length & 0xf00) >> 8); + out_userdata_token->size_in_bytes = (payload_length >> 12) & 0xfffff; + out_userdata_token->payload = (void*)((uintptr_t)rmt_parser->file_buffer + rmt_parser->file_buffer_offset + sizeof(uint32_t)); + + // read the resource ID. + if (out_userdata_token->size_in_bytes > 4 && out_userdata_token->userdata_type == kRmtUserdataTypeName) + { + const uintptr_t resource_id_address = ((uintptr_t)out_userdata_token->payload) + (out_userdata_token->size_in_bytes - 4); + const uint32_t resource_id_value = *((uint32_t*)resource_id_address); + const RmtResourceIdentifier resource_identifier = (RmtResourceIdentifier)resource_id_value; + out_userdata_token->resource_identifer = resource_identifier; + } + else + { + out_userdata_token->resource_identifer = 0; + } + + return RMT_OK; +} + +// parse a misc token +static RmtErrorCode ParseMisc(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenMisc* out_misc_token) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_misc_token->common); + + uint8_t data[RMT_TOKEN_SIZE_MISC]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_UNUSED(error_code); + out_misc_token->type = (RmtMiscType)ReadBitsFromBuffer(data, sizeof(data), 11, 8); + return RMT_OK; +} + +// parse a resource reference token +static RmtErrorCode ParserResourceReference(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenResourceReference* out_residency_update) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_residency_update->common); + + uint8_t data[RMT_TOKEN_SIZE_RESOURCE_REFERENCE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_residency_update->residency_update_type = (RmtResidencyUpdateType)ReadBitsFromBuffer(data, sizeof(data), 8, 8); + out_residency_update->virtual_address = ReadBitsFromBuffer(data, sizeof(data), 56, 9); + out_residency_update->queue = ReadBitsFromBuffer(data, sizeof(data), 63, 57); + return RMT_OK; +} + +// parse a resource bind token +static RmtErrorCode ParseResourceBind(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenResourceBind* out_resource_bind) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_resource_bind->common); + + uint8_t data[RMT_TOKEN_SIZE_RESOURCE_BIND]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_resource_bind->virtual_address = ReadBitsFromBuffer(data, sizeof(data), 55, 8); + out_resource_bind->size_in_bytes = ReadBitsFromBuffer(data, sizeof(data), 99, 56); + out_resource_bind->is_system_memory = (ReadBitsFromBuffer(data, sizeof(data), 100, 100) != 0U); + out_resource_bind->resource_identifier = ReadBitsFromBuffer(data, sizeof(data), 135, 104); + return RMT_OK; +} + +// parse a process event token +static RmtErrorCode ParseProcessEvent(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenProcessEvent* out_process_event) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_process_event->common); + + uint8_t data[RMT_TOKEN_SIZE_PROCESS_EVENT]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_process_event->common.process_id = (RmtProcessId)ReadBitsFromBuffer(data, sizeof(data), 39, 8); + out_process_event->event_type = (RmtProcessEventType)ReadBitsFromBuffer(data, sizeof(data), 47, 40); + + return RMT_OK; +} + +// parse a page reference token +static RmtErrorCode ParsePageReference(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenPageReference* out_page_reference) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_page_reference->common); + + uint8_t data[RMT_TOKEN_SIZE_PAGE_REFERENCE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_UNUSED(error_code); + + out_page_reference->page_size = (RmtPageSize)ReadBitsFromBuffer(data, sizeof(data), 10, 8); + const bool is_compressed = (bool)ReadBitsFromBuffer(data, sizeof(data), 11, 11); + const uint64_t page_reference_data = ReadBitsFromBuffer(data, sizeof(data), 75, 16); + + RMT_UNUSED(is_compressed); + RMT_UNUSED(page_reference_data); + + return RMT_OK; +} + +// parse a CPU map token +static RmtErrorCode ParseCpuMap(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenCpuMap* out_cpu_map) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_cpu_map->common); + + uint8_t data[RMT_TOKEN_SIZE_CPU_MAP]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_cpu_map->virtual_address = ReadBitsFromBuffer(data, sizeof(data), 55, 8); + out_cpu_map->is_unmap = (ReadBitsFromBuffer(data, sizeof(data), 56, 56) != 0U); + + return RMT_OK; +} + +// parse a virtual allocation +static RmtErrorCode ParseVirtualAllocate(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenVirtualAllocate* out_virtual_allocate) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_virtual_allocate->common); + + uint8_t data[RMT_TOKEN_SIZE_VIRTUAL_ALLOCATE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + const uint64_t size_in_pages_minus_one = ReadBitsFromBuffer(data, sizeof(data), 31, 8); + out_virtual_allocate->size_in_bytes = ((size_in_pages_minus_one + 1) * (4 * 1024)); + out_virtual_allocate->owner_type = (RmtOwnerType)ReadBitsFromBuffer(data, sizeof(data), 33, 32); + out_virtual_allocate->virtual_address = ReadBitsFromBuffer(data, sizeof(data), 81, 34); + out_virtual_allocate->preference[0] = (RmtHeapType)ReadBitsFromBuffer(data, sizeof(data), 83, 82); + out_virtual_allocate->preference[1] = (RmtHeapType)ReadBitsFromBuffer(data, sizeof(data), 85, 84); + out_virtual_allocate->preference[2] = (RmtHeapType)ReadBitsFromBuffer(data, sizeof(data), 87, 86); + out_virtual_allocate->preference[3] = (RmtHeapType)ReadBitsFromBuffer(data, sizeof(data), 89, 88); + + // handle flattening of GART_CACHABLE and GART_USWC. + for (int32_t current_heap_index = 0; current_heap_index < 4; ++current_heap_index) + { + if (out_virtual_allocate->preference[current_heap_index] == 3) + { + out_virtual_allocate->preference[current_heap_index] = kRmtHeapTypeSystem; + } + } + return RMT_OK; +} + +// parse a image description payload. +static RmtErrorCode ParseResourceDescriptionPayloadImage(RmtParser* rmt_parser, RmtResourceDescriptionImage* out_image) +{ + uint8_t data[IMAGE_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // FLAGS [19:0] Creation flags describing how the image was created. + out_image->create_flags = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 19, 0)); + + // USAGE_FLAGS [34:20] Usage flags describing how the image is used by the application. + out_image->usage_flags = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 34, 20)); + + // TYPE [36:35] The type of the image + out_image->image_type = (RmtImageType)(ReadBitsFromBuffer(data, sizeof(data), 36, 35)); + + // DIMENSION_X [49:37] The dimension of the image in the X dimension, minus 1. + int32_t dimension = (int32_t)ReadBitsFromBuffer(data, sizeof(data), 49, 37); + out_image->dimension_x = dimension + 1; + + // DIMENSION_Y [62:50] The dimension of the image in the Y dimension, minus 1. + dimension = (int32_t)ReadBitsFromBuffer(&data[0], sizeof(data), 62, 50); + out_image->dimension_y = (int32_t)(dimension + 1); + + // DIMENSION_Z [75:63] The dimension of the image in the Z dimension, minus 1. + dimension = (int32_t)ReadBitsFromBuffer(data, sizeof(data), 75, 63); + out_image->dimension_z = (int32_t)(dimension + 1); + + // FORMAT [95:76] The format of the image. + uint64_t format = ReadBitsFromBuffer(data, sizeof(data), 95, 76); + // SWIZZLE_X [2:0] + out_image->format.swizzle_x = (RmtChannelSwizzle)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 2, 0); + // SWIZZLE_Y [5:3] + out_image->format.swizzle_y = (RmtChannelSwizzle)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 5, 3); + // SWIZZLE_Z [8:6] + out_image->format.swizzle_z = (RmtChannelSwizzle)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 8, 6); + // SWIZZLE_W [11:9] + out_image->format.swizzle_w = (RmtChannelSwizzle)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 11, 9); + // NUM_FORMAT [19:12] + out_image->format.format = (RmtFormat)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 19, 12); + + // MIPS [99:96] The number of mip-map levels in the image. + out_image->mip_levels = (int32_t)(ReadBitsFromBuffer(data, sizeof(data), 99, 96)); + + // SLICES [110:100] The number of slices in the image minus one. The maximum this can be in the range [1..2048]. + int32_t slices = (int32_t)(ReadBitsFromBuffer(data, sizeof(data), 110, 100)); + out_image->slices = slices + 1; + + // SAMPLES [113:111] The Log2(n) of the sample count for the image. + int32_t log2_samples = (int32_t)(ReadBitsFromBuffer(data, sizeof(data), 113, 111)); + out_image->sample_count = (1 << log2_samples); + + // FRAGMENTS [115:114] The Log2(n) of the fragment count for the image. + int32_t log2_fragments = (int32_t)(ReadBitsFromBuffer(data, sizeof(data), 115, 114)); + out_image->fragment_count = (1 << log2_fragments); + + // TILING_TYPE [117:116] The tiling type used for the image + out_image->tiling_type = (RmtTilingType)(ReadBitsFromBuffer(data, sizeof(data), 117, 116)); + + // TILING_OPT_MODE [119:118] The tiling optimisation mode for the image + out_image->tiling_optimization_mode = (RmtTilingOptimizationMode)(ReadBitsFromBuffer(data, sizeof(data), 119, 118)); + + // METADATA_MODE [121:120] The metadata mode for the image + out_image->metadata_mode = (RmtMetadataMode)(ReadBitsFromBuffer(&data[0], sizeof(data), 121, 120)); + + // MAX_BASE_ALIGNMENT [126:122] The alignment of the image resource. This is stored as the Log2(n) of the + // alignment, it is therefore possible to encode alignments from [1Byte..2MiB]. + uint64_t log2_alignment = ReadBitsFromBuffer(data, sizeof(data), 126, 122); + out_image->max_base_alignment = (1ULL << log2_alignment); + + // PRESENTABLE [127] This bit is set to 1 if the image is presentable. + out_image->presentable = (bool)(ReadBitsFromBuffer(data, sizeof(data), 127, 127)); + + // IMAGE_SIZE [159:128] The size of the core image data inside the resource. + out_image->image_size = ReadBitsFromBuffer(data, sizeof(data), 159, 128); + + // METADATA_OFFSET [191:160] The offset from the base virtual address of the resource to the metadata of + // the image. + out_image->metadata_tail_offset = ReadBitsFromBuffer(data, sizeof(data), 191, 160); + + // METADATA_SIZE [223:192] The size of the metadata inside the resource. + out_image->metadata_tail_size = ReadBitsFromBuffer(data, sizeof(data), 223, 192); + + // METADATA_HEADER_OFFSET [255:224] The offset from the base virtual address of the resource to the + // metadata header. + out_image->metadata_head_offset = ReadBitsFromBuffer(data, sizeof(data), 255, 224); + + // METADATA_HEADER_SIZE [287:256] The size of the metadata header inside the resource. + out_image->metadata_head_size = ReadBitsFromBuffer(data, sizeof(data), 287, 256); + + // IMAGE_ALIGN [292:288] The alignment of the core image data within the resources virtual address allocation. + // This is stored as the Log2(n) of the alignment. + log2_alignment = ReadBitsFromBuffer(data, sizeof(data), 292, 288); + out_image->image_alignment = (1ULL << log2_alignment); + + // METADATA_ALIGN [297:293] The alignment of the metadata within the resources virtual address allocation. + // This is stored as the Log2(n) of the alignment. + log2_alignment = ReadBitsFromBuffer(data, sizeof(data), 297, 293); + out_image->metadata_tail_alignment = (1ULL << log2_alignment); + + // METADATA_HEADER_ALIGN [302:298] The alignment of the metadata header within the resources virtual address + // allocation. This is stored as the Log2(n) of the alignment. + log2_alignment = ReadBitsFromBuffer(data, sizeof(data), 302, 298); + out_image->metadata_head_alignment = (1ULL << log2_alignment); + + // FULLSCREEN [303] This bit is set to 1 if the image is fullscreen presentable. + out_image->fullscreen = (bool)(ReadBitsFromBuffer(data, sizeof(data), 303, 303)); + return RMT_OK; +} + +// parse a buffer description payload. +static RmtErrorCode ParseResourceDescriptionPayloadBuffer(RmtParser* rmt_parser, RmtResourceDescriptionBuffer* out_buffer) +{ + uint8_t data[BUFFER_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // CREATE_FLAGS [7:0] The create flags for a buffer. + out_buffer->create_flags = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 7, 0)); + // USAGE_FLAGS [23:8] The usage flags for a buffer. + out_buffer->usage_flags = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 23, 8)); + // SIZE [87:24] The size in bytes of the buffer. + out_buffer->size_in_bytes = ReadBitsFromBuffer(data, sizeof(data), 87, 24); + return RMT_OK; +} + +// parse a gpu event description payload. +static RmtErrorCode ParseResourceDescriptionPayloadGpuEvent(RmtParser* rmt_parser, RmtResourceDescriptionGpuEvent* out_gpu_event) +{ + uint8_t data[GPU_EVENT_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_UNUSED(error_code); + + // FLAGS [7:0] The flags used to create the GPU event. + out_gpu_event->flags = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 7, 0)); + return RMT_OK; +} + +// parse a border palette description payload. +static RmtErrorCode ParseResourceDescriptionPayloadBorderColorPalette(RmtParser* rmt_parser, RmtResourceDescriptionBorderColorPalette* out_border_color_palette) +{ + uint8_t data[BORDER_COLOR_PALETTE_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // NUM_ENTRIES [7:0] The number of entries in the border color palette. + out_border_color_palette->size_in_entries = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 7, 0)); + return RMT_OK; +} + +// parse perf experiment description payload. +static RmtErrorCode ParseResourceDescriptionPayloadPerfExperiment(RmtParser* rmt_parser, RmtResourceDescriptionPerfExperiment* out_perf_experiment) +{ + uint8_t data[PERF_EXPERIMENT_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // SPM_SIZE [31:0] The size in bytes for the amount of memory allocated for SPM counter streaming. + out_perf_experiment->spm_size = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 31, 0)); + + // SQTT_SIZE [63:32] The size in bytes for the amount of memory allocated for SQTT data streaming. + out_perf_experiment->sqtt_size = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 63, 32)); + + // COUNTER_SIZE [95:64] The size in bytes for the amount of memory allocated for per-draw counter data. + out_perf_experiment->counter_size = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 95, 64)); + return RMT_OK; +} + +// parse query heap description payload. +static RmtErrorCode ParseResourceDescriptionPayloadQueryHeap(RmtParser* rmt_parser, RmtResourceDescriptionQueryHeap* out_query_heap) +{ + uint8_t data[QUERY_HEAP_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // TYPE [1:0] The type of the query heap. See RMT_QUERY_HEAP_TYPE. + out_query_heap->heap_type = (RmtQueryHeapType)(ReadBitsFromBuffer(data, sizeof(data), 1, 0)); + + // ENABLE_CPU_ACCESS [2] Set to 1 if CPU access is enabled. + out_query_heap->enable_cpu_access = (bool)(ReadBitsFromBuffer(data, sizeof(data), 2, 2)); + return RMT_OK; +} + +// parse video decoder description payload. +static RmtErrorCode ParseResourceDescriptionPayloadVideoDecoder(RmtParser* rmt_parser, RmtResourceDescriptionVideoDecoder* out_video_decoder) +{ + uint8_t data[VIDEO_DECODER_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // ENGINE_TYPE [3:0] The type of engine that the video decoder will run on. + out_video_decoder->engine_type = (RmtEngineType)(ReadBitsFromBuffer(data, sizeof(data), 3, 0)); + + // VIDEO_DECODER_TYPE [7:4] The type of decoder being run. + out_video_decoder->decoder_type = (RmtVideoDecoderType)(ReadBitsFromBuffer(data, sizeof(data), 7, 4)); + + // WIDTH [19:8] The width of the video minus one. + uint32_t width = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 19, 8)); + out_video_decoder->width = width + 1; + + // HEIGHT [31:20] The height of the video minus one. + uint32_t height = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 31, 20)); + out_video_decoder->height = height + 1; + return RMT_OK; +} + +// parse video encoder description payload. +static RmtErrorCode ParseResourceDescriptionPayloadVideoEncoder(RmtParser* rmt_parser, RmtResourceDescriptionVideoEncoder* out_video_encoder) +{ + uint8_t data[VIDEO_ENCODER_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // ENGINE_TYPE [3:0] The type of engine that the video encoder will run on. + out_video_encoder->engine_type = (RmtEngineType)(ReadBitsFromBuffer(data, sizeof(data), 3, 0)); + + // VIDEO_ENCODER_TYPE [4] The type of encoder being run. + out_video_encoder->encoder_type = (RmtVideoEncoderType)(ReadBitsFromBuffer(data, sizeof(data), 4, 4)); + + // WIDTH [16:5] The width of the video minus one. + uint16_t width = (uint16_t)(ReadBitsFromBuffer(data, sizeof(data), 16, 5)); + out_video_encoder->width = width + 1; + + // HEIGHT [28:17] The height of the video minus one. + uint16_t height = (uint16_t)(ReadBitsFromBuffer(data, sizeof(data), 28, 17)); + out_video_encoder->height = height + 1; + + // IMAGE_FORMAT [47:29] Image format + uint64_t format = ReadBitsFromBuffer(data, sizeof(data), 47, 29); + // SWIZZLE_X [2:0] + out_video_encoder->format.swizzle_x = (RmtChannelSwizzle)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 2, 0); + // SWIZZLE_Y [5:3] + out_video_encoder->format.swizzle_y = (RmtChannelSwizzle)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 5, 3); + // SWIZZLE_Z [8:6] + out_video_encoder->format.swizzle_z = (RmtChannelSwizzle)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 8, 6); + // SWIZZLE_W [11:9] + out_video_encoder->format.swizzle_w = (RmtChannelSwizzle)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 11, 9); + // NUM_FORMAT [19:12] + out_video_encoder->format.format = (RmtFormat)ReadBitsFromBuffer((uint8_t*)&format, sizeof(format), 19, 12); + + return RMT_OK; +} + +// parse heap description payload. +static RmtErrorCode ParseResourceDescriptionPayloadHeap(RmtParser* rmt_parser, RmtResourceDescriptionHeap* out_heap) +{ + uint8_t data[HEAP_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // FLAGS [4:0] The flags used to create the heap. + out_heap->flags = (uint8_t)(ReadBitsFromBuffer(data, sizeof(data), 4, 0)); + + // SIZE [68:5] The size of the heap in bytes. + out_heap->size = ReadBitsFromBuffer(data, sizeof(data), 68, 5); + + // ALIGNMENT [73:69] The alignment of the heap. This will always match a page size, and therefore is encoded as RmtPageSize. + out_heap->alignment = (RmtPageSize)(ReadBitsFromBuffer(data, sizeof(data), 73, 69)); + + // SEGMENT_INDEX [77:74] The segment index where the heap was requested to be created. + out_heap->segment_index = (uint8_t)(ReadBitsFromBuffer(data, sizeof(data), 77, 74)); + + return RMT_OK; +} + +// parse a pipeline. +static RmtErrorCode ParseResourceDescriptionPayloadPipeline(RmtParser* rmt_parser, RmtResourceDescriptionPipeline* out_pipeline) +{ + uint8_t data[PIPELINE_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // CREATE_FLAGS [7:0] Describes the creation flags for the pipeline. + out_pipeline->create_flags = (uint8_t)(ReadBitsFromBuffer(data, sizeof(data), 7, 0)); + + // PIPELINE_HASH [135:8] The 128bit pipeline hash of the code object. + out_pipeline->internal_pipeline_hash_hi = ReadBitsFromBuffer(data, sizeof(data), 71, 8); + out_pipeline->internal_pipeline_hash_lo = ReadBitsFromBuffer(data, sizeof(data), 135, 72); + + // Pipeline Stages [143:136]. + out_pipeline->stage_mask = (uint32_t)(ReadBitsFromBuffer(data, sizeof(data), 143, 136)); + + // IS_NGG [144] The bit is set to true if the pipeline was compiled in NGG mode. + out_pipeline->is_ngg = (bool)(ReadBitsFromBuffer(data, sizeof(data), 144, 144)); + + return RMT_OK; +} + +// parse descriptor heap experiment description payload. +static RmtErrorCode ParseResourceDescriptionPayloadDescriptorHeap(RmtParser* rmt_parser, RmtResourceDescriptionDescriptorHeap* out_descriptor_heap) +{ + uint8_t data[DESCRIPTOR_HEAP_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // TYPE [3:0] The type of descriptors in the heap. + out_descriptor_heap->descriptor_type = (RmtDescriptorType)(ReadBitsFromBuffer(data, sizeof(data), 3, 0)); + + // SHADER_VISIBLE [4] Flag indicating whether the heap is shader-visible. + out_descriptor_heap->shader_visible = (bool)(ReadBitsFromBuffer(data, sizeof(data), 4, 4)); + + // GPU_MASK [12:5] Bitmask to identify which adapters the heap applies to. + out_descriptor_heap->gpu_mask = (uint8_t)(ReadBitsFromBuffer(data, sizeof(data), 12, 5)); + + // NUM_DESCRIPTORS [28:13] The number of descriptors in the heap. + out_descriptor_heap->num_descriptors = (uint16_t)(ReadBitsFromBuffer(data, sizeof(data), 28, 13)); + + return RMT_OK; +} + +// parse descriptor pool experiment description payload. +static RmtErrorCode ParseResourceDescriptionPayloadDescriptorPool(RmtParser* rmt_parser, RmtResourceDescriptionDescriptorPool* out_descriptor_pool) +{ + uint8_t data[DESCRIPTOR_POOL_RESOURCE_TOKEN_SIZE]; + RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // MAX_SETS [11:0] Maximum number of descriptor sets that can be allocated from the pool. + out_descriptor_pool->max_sets = (uint16_t)(ReadBitsFromBuffer(data, sizeof(data), 15, 0)); + + // POOL_SIZE_COUNT [15:12] The number of pool size structs. + out_descriptor_pool->pools_count = (uint8_t)(ReadBitsFromBuffer(data, sizeof(data), 23, 16)); + + size_t offset = RMT_TOKEN_SIZE_RESOURCE_CREATE + DESCRIPTOR_POOL_RESOURCE_TOKEN_SIZE; + for (uint8_t i = 0; i < out_descriptor_pool->pools_count; ++i) + { + uint8_t pool_desc_data[DESCRIPTOR_POOL_DESCRIPTION_SIZE]; + error_code = ReadBytes(rmt_parser, pool_desc_data, offset, sizeof(pool_desc_data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // TYPE [15:0] Descriptor type this pool can hold. + out_descriptor_pool->pools[i].type = (RmtDescriptorType)(ReadBitsFromBuffer(pool_desc_data, sizeof(pool_desc_data), 15, 0)); + + // NUM_DESCRIPTORS [31:16] Number of descriptors to be allocated by this pool. + out_descriptor_pool->pools[i].num_descriptors = (RmtDescriptorType)(ReadBitsFromBuffer(pool_desc_data, sizeof(pool_desc_data), 31, 16)); + + offset += sizeof(pool_desc_data); + } + + return RMT_OK; +} + +// parse command allocator experiment description payload. +static RmtErrorCode ParseResourceDescriptionPayloadCmdAllocator(RmtParser* rmt_parser, RmtResourceDescriptionCommandAllocator* out_command_allocator) +{ + uint8_t data[CMD_ALLOCATOR_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // FLAGS [3:0] Describes the creation flags for the command allocator. + out_command_allocator->flags = (uint8_t)(ReadBitsFromBuffer(data, sizeof(data), 3, 0)); + + // CMD_DATA_PREFERRED_HEAP [7:4] The preferred allocation heap for executable command data + out_command_allocator->cmd_data_heap = (RmtHeapType)(ReadBitsFromBuffer(data, sizeof(data), 7, 4)); + + // CMD_DATA_ALLOC_SIZE [63:8] Size of the base memory allocations the command allocator will make for executable command data. Expressed as 4kB chunks + out_command_allocator->cmd_data_size = ReadBitsFromBuffer(data, sizeof(data), 63, 8); + + // CMD_DATA_SUBALLOC_SIZE [119:64] Size, in bytes, of the chunks the command allocator will give to command buffers for executable command data. Expressed as 4kB chunks + out_command_allocator->cmd_data_suballoc_size = ReadBitsFromBuffer(data, sizeof(data), 119, 64); + + // EMBEDDED_DATA_PREFERRED_HEAP [123:120] The preferred allocation heap for embedded command data + out_command_allocator->embed_data_heap = (RmtHeapType)(ReadBitsFromBuffer(data, sizeof(data), 123, 120)); + + // EMBEDDED_DATA_ALLOC_SIZE [179:124] Size, in bytes, of the base memory allocations the command allocator will make for embedded command data. Expressed as 4kB chunks + out_command_allocator->embed_data_size = ReadBitsFromBuffer(data, sizeof(data), 179, 124); + + // EMBEDDED_DATA_SUBALLOC_SIZE [235:180] Size, in bytes, of the chunks the command allocator will give to command buffers for embedded command data. Expressed as 4kB chunks + out_command_allocator->embed_data_suballoc_size = ReadBitsFromBuffer(data, sizeof(data), 235, 180); + + // GPU_SCRATCH_MEM_PREFERRED_HEAP [239:236] The preferred allocation heap for GPU scratch memory. + out_command_allocator->gpu_scratch_heap = (RmtHeapType)(ReadBitsFromBuffer(data, sizeof(data), 239, 236)); + + // GPU_SCRATCH_MEM_ALLOC_SIZE [295:240] Size, in bytes, of the base memory allocations the command allocator will make for GPU scratch memory. Expressed as 4kB chunks + out_command_allocator->gpu_scratch_size = ReadBitsFromBuffer(data, sizeof(data), 295, 240); + + // GPU_SCRATCH_MEM_SUBALLOC_SIZE [351:296] Size, in bytes, of the chunks the command allocator will give to command buffers for GPU scratch memory. Expressed as 4kB chunks + out_command_allocator->gpu_scratch_suballoc_size = ReadBitsFromBuffer(data, sizeof(data), 351, 296); + + return RMT_OK; +} + +// parse a misc internal resource. +static RmtErrorCode ParseResourceDescriptionPayloadMiscInternal(RmtParser* rmt_parser, RmtResourceDescriptionMiscInternal* out_misc_internal) +{ + uint8_t data[MISC_INTERNAL_RESOURCE_TOKEN_SIZE]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, RMT_TOKEN_SIZE_RESOURCE_CREATE, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_misc_internal->type = (RmtResourceMiscInternalType)data[0]; + return RMT_OK; +} + +// parse a resource description +static RmtErrorCode ParseResourceCreate(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenResourceCreate* out_resource_description) +{ + RMT_UNUSED(token_header); + + // common fields. + PopulateCommonFields(rmt_parser, &out_resource_description->common); + + uint8_t data[RMT_TOKEN_SIZE_RESOURCE_CREATE]; + RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_resource_description->resource_identifier = ReadBitsFromBuffer(data, sizeof(data), 39, 8); + out_resource_description->owner_type = (RmtOwnerType)ReadBitsFromBuffer(data, sizeof(data), 41, 40); + //outResourceDescription->ownerCategoryType = readBitsFromBuffer(data, sizeof(data), 45, 42); + out_resource_description->commit_type = (RmtCommitType)ReadBitsFromBuffer(data, sizeof(data), 47, 46); + out_resource_description->resource_type = (RmtResourceType)ReadBitsFromBuffer(data, sizeof(data), 53, 48); + + // Parse per-type data. + switch (out_resource_description->resource_type) + { + case kRmtResourceTypeImage: + error_code = ParseResourceDescriptionPayloadImage(rmt_parser, &out_resource_description->image); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeBuffer: + error_code = ParseResourceDescriptionPayloadBuffer(rmt_parser, &out_resource_description->buffer); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeGpuEvent: + error_code = ParseResourceDescriptionPayloadGpuEvent(rmt_parser, &out_resource_description->gpu_event); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeBorderColorPalette: + error_code = ParseResourceDescriptionPayloadBorderColorPalette(rmt_parser, &out_resource_description->border_color_palette); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypePerfExperiment: + error_code = ParseResourceDescriptionPayloadPerfExperiment(rmt_parser, &out_resource_description->perf_experiment); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeQueryHeap: + error_code = ParseResourceDescriptionPayloadQueryHeap(rmt_parser, &out_resource_description->query_heap); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeVideoDecoder: + error_code = ParseResourceDescriptionPayloadVideoDecoder(rmt_parser, &out_resource_description->video_decoder); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeVideoEncoder: + error_code = ParseResourceDescriptionPayloadVideoEncoder(rmt_parser, &out_resource_description->video_encoder); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeHeap: + error_code = ParseResourceDescriptionPayloadHeap(rmt_parser, &out_resource_description->heap); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypePipeline: + error_code = ParseResourceDescriptionPayloadPipeline(rmt_parser, &out_resource_description->pipeline); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeDescriptorHeap: + error_code = ParseResourceDescriptionPayloadDescriptorHeap(rmt_parser, &out_resource_description->descriptor_heap); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeDescriptorPool: + error_code = ParseResourceDescriptionPayloadDescriptorPool(rmt_parser, &out_resource_description->descriptor_pool); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeCommandAllocator: + error_code = ParseResourceDescriptionPayloadCmdAllocator(rmt_parser, &out_resource_description->command_allocator); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + case kRmtResourceTypeMiscInternal: + error_code = ParseResourceDescriptionPayloadMiscInternal(rmt_parser, &out_resource_description->misc_internal); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + break; + + default: + break; + } + + return RMT_OK; +} + +// parse a time delta +static RmtErrorCode ParseTimeDelta(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenTimeDelta* out_time_delta) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_time_delta->common); + + // token-specific fields. + uint8_t num_delta_bytes = 0; + RmtErrorCode error_code = ReadUInt8(rmt_parser, &num_delta_bytes, 0); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + num_delta_bytes = (num_delta_bytes >> 4) & 7; + + uint64_t delta = 0; + error_code = ReadUInt64(rmt_parser, &delta, 1); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_time_delta->delta = ((delta >> (8 - num_delta_bytes)) * TIMESTAMP_QUANTA) / 1000000; + return RMT_OK; +} + +// parse a resource destroy +static RmtErrorCode ParseResourceDestroy(RmtParser* rmt_parser, const uint16_t token_header, RmtTokenResourceDestroy* out_resource_destroy) +{ + RMT_UNUSED(token_header); + + PopulateCommonFields(rmt_parser, &out_resource_destroy->common); + + uint8_t data[kRmtTokenTypeResourceDestroy]; + const RmtErrorCode error_code = ReadBytes(rmt_parser, data, 0, sizeof(data)); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + out_resource_destroy->resource_identifier = ReadBitsFromBuffer(data, sizeof(data), 39, 8); + return RMT_OK; +} + +RmtErrorCode RmtParserInitialize(RmtParser* rmt_parser, + FILE* file_handle, + int32_t file_offset, + int32_t stream_size, + void* file_buffer, + int32_t file_buffer_size, + int32_t major_version, + int32_t minor_version, + int32_t stream_index, + uint64_t process_id, + uint64_t thread_id) +{ + RMT_RETURN_ON_ERROR(rmt_parser, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(file_handle, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR((stream_size >= 1), RMT_ERROR_INVALID_SIZE); + + rmt_parser->start_timestamp = 0; + rmt_parser->current_timestamp = 0; + rmt_parser->seen_timestamp = 0; + rmt_parser->file_handle = file_handle; + rmt_parser->stream_current_offset = 0; + rmt_parser->stream_start_offset = file_offset; + rmt_parser->stream_size = stream_size; + rmt_parser->file_buffer_offset = 0; + rmt_parser->file_buffer = file_buffer; + rmt_parser->file_buffer_size = file_buffer_size; + rmt_parser->file_buffer_actual_size = 0; + rmt_parser->major_version = major_version; + rmt_parser->minor_version = minor_version; + rmt_parser->process_id = process_id; + rmt_parser->thread_id = thread_id; + rmt_parser->stream_index = stream_index; + return RMT_OK; +} + +RmtErrorCode RmtParserAdvance(RmtParser* rmt_parser, RmtToken* out_token, RmtParserPosition* out_parser_position) +{ + RMT_RETURN_ON_ERROR(rmt_parser, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(out_token, RMT_ERROR_INVALID_POINTER); + + if (out_parser_position != nullptr) + { + out_parser_position->seen_timestamp = rmt_parser->seen_timestamp; + out_parser_position->timestamp = rmt_parser->current_timestamp; + out_parser_position->stream_start_offset = rmt_parser->stream_start_offset; + out_parser_position->stream_current_offset = rmt_parser->stream_current_offset; + out_parser_position->file_buffer_actual_size = rmt_parser->file_buffer_actual_size; + out_parser_position->file_buffer_offset = rmt_parser->file_buffer_offset; + } + + // If we have less than 64 bytes in the buffer, fetch some more data.. + if (rmt_parser->file_buffer_offset >= (rmt_parser->file_buffer_actual_size - 64)) + { + if (rmt_parser->file_buffer_actual_size == 0 || (rmt_parser->file_buffer_actual_size == rmt_parser->file_buffer_size)) + { + fseek(rmt_parser->file_handle, rmt_parser->stream_start_offset + rmt_parser->stream_current_offset, SEEK_SET); + const int32_t read_bytes = (int32_t)fread(rmt_parser->file_buffer, 1, rmt_parser->file_buffer_size, rmt_parser->file_handle); + //printf("Read %d bytes from file [%d..%d]\n", read_bytes, rmt_parser->stream_current_offset + rmt_parser->stream_start_offset, rmt_parser->stream_current_offset + rmt_parser->stream_start_offset + rmt_parser->file_buffer_size); + rmt_parser->file_buffer_actual_size = read_bytes; + rmt_parser->file_buffer_offset = 0; + } + } + + // Figure out what the token is we have to parse. + uint16_t token_header = 0; + RmtErrorCode error_code = ReadUInt16(rmt_parser, &token_header, 0); + if (error_code != RMT_OK) + { + return error_code; + } + + UpdateTimeState(rmt_parser, token_header); + + // token type encoded in [3:0] + const RmtTokenType token_type = (RmtTokenType)(token_header & 0xf); + out_token->type = token_type; + + switch (token_type) + { + case kRmtTokenTypeTimestamp: + error_code = ParseTimestamp(rmt_parser, token_header, &out_token->timestamp_token); + break; + case kRmtTokenTypePageTableUpdate: + error_code = ParsePageTableUpdate(rmt_parser, token_header, &out_token->page_table_update_token); + break; + case kRmtTokenTypeUserdata: + error_code = ParseUserdata(rmt_parser, token_header, &out_token->userdata_token); + break; + case kRmtTokenTypeMisc: + error_code = ParseMisc(rmt_parser, token_header, &out_token->misc_token); + break; + case kRmtTokenTypeResourceReference: + error_code = ParserResourceReference(rmt_parser, token_header, &out_token->resource_reference); + break; + case kRmtTokenTypeResourceBind: + error_code = ParseResourceBind(rmt_parser, token_header, &out_token->resource_bind_token); + break; + case kRmtTokenTypeProcessEvent: + error_code = ParseProcessEvent(rmt_parser, token_header, &out_token->process_event_token); + break; + case kRmtTokenTypePageReference: + error_code = ParsePageReference(rmt_parser, token_header, &out_token->page_reference_token); + break; + case kRmtTokenTypeCpuMap: + error_code = ParseCpuMap(rmt_parser, token_header, &out_token->cpu_map_token); + break; + case kRmtTokenTypeVirtualFree: + error_code = ParseVirtualFree(rmt_parser, token_header, &out_token->virtual_free_token); + break; + case kRmtTokenTypeVirtualAllocate: + error_code = ParseVirtualAllocate(rmt_parser, token_header, &out_token->virtual_allocate_token); + break; + case kRmtTokenTypeResourceCreate: + error_code = ParseResourceCreate(rmt_parser, token_header, &out_token->resource_create_token); + break; + case kRmtTokenTypeTimeDelta: + error_code = ParseTimeDelta(rmt_parser, token_header, &out_token->time_delta_token); + break; + case kRmtTokenTypeResourceDestroy: + error_code = ParseResourceDestroy(rmt_parser, token_header, &out_token->resource_destroy_token); + break; + default: + RMT_ASSERT(0); + error_code = RMT_ERROR_MALFORMED_DATA; // corrupted file. + break; + } + + // if there was an error during the parsing of the packet, then return it. + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // advance the stream by the size of the token. + const int32_t token_size = GetTokenSize(rmt_parser, token_header); + rmt_parser->stream_current_offset += token_size; + rmt_parser->file_buffer_offset += token_size; + + return RMT_OK; +} + +RmtErrorCode RmtParserSetPosition(RmtParser* rmt_parser, const RmtParserPosition* parser_position) +{ + RMT_RETURN_ON_ERROR(rmt_parser, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(parser_position, RMT_ERROR_INVALID_POINTER); + + // set the position up + rmt_parser->stream_current_offset = parser_position->stream_current_offset; + rmt_parser->current_timestamp = parser_position->timestamp; + rmt_parser->stream_start_offset = parser_position->stream_start_offset; + rmt_parser->seen_timestamp = parser_position->seen_timestamp; + rmt_parser->file_buffer_actual_size = parser_position->file_buffer_actual_size; + rmt_parser->file_buffer_offset = parser_position->file_buffer_offset; + + return RMT_OK; +} + +bool RmtParserIsCompleted(RmtParser* rmt_parser) +{ + RMT_RETURN_ON_ERROR(rmt_parser, false); + + RmtParserPosition parser_pos; + RmtToken token; + const RmtErrorCode error_code = RmtParserAdvance(rmt_parser, &token, &parser_pos); + RmtParserSetPosition(rmt_parser, &parser_pos); + + return error_code != RMT_OK; +} + +RmtErrorCode RmtParserReset(RmtParser* rmt_parser) +{ + RMT_RETURN_ON_ERROR(rmt_parser, RMT_ERROR_INVALID_POINTER); + + // state related values + rmt_parser->start_timestamp = 0; + rmt_parser->stream_current_offset = 0; + rmt_parser->seen_timestamp = 0; + + // initialize time-related values. + rmt_parser->current_timestamp = 0; + + // make sure we re-read the data from the start. + rmt_parser->file_buffer_actual_size = 0; + rmt_parser->file_buffer_offset = 0; + + return RMT_OK; +} diff --git a/source/parser/rmt_parser.h b/source/parser/rmt_parser.h new file mode 100644 index 0000000..22ba309 --- /dev/null +++ b/source/parser/rmt_parser.h @@ -0,0 +1,153 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Core parsing code for RMT data. +//============================================================================= + +#ifndef RMV_PARSER_RMT_PARSER_H_ +#define RMV_PARSER_RMT_PARSER_H_ + +#include "rmt_error.h" +#include + +typedef struct RmtToken RmtToken; + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +// call forward for use in function pointer typedef. +typedef struct RmtParser RmtParser; + +/// A callback function that rmtParseAdvance will call when it runs out of memory. +/// +/// The host code can then either provide an additional memory to the parser for it to continue +/// parsing. If the host code returns memory to the parser then it should also return RMT_OK +/// from the callback function. If the host does not wish to provide additional memory (perhaps +/// the end of the buffer has already been reached) then the host code can return +/// RMT_ERROR_OUT_OF_MEMORY from the callback to indicate that there is no more +/// more memory for the parser to consume. +/// +/// @param [in] parser A pointer to a RmtParser structure that is requesting additional memory. +/// @param [in] start_offset The offset in the current buffer where the parser is currently positioned. Any memory returned should include this data. +/// @param [out] out_rmt_buffer A pointer to the address of the buffer that parser will be updated to work with. +/// @param [out] out_rmt_buffer_size A pointer to an size_t which contains the size of the buffer pointed to by the contets of outRmtBuffer. +typedef RmtErrorCode (*RmtParserNextChunkCallbackFunc)(const RmtParser* parser, size_t start_offset, const void** out_rmt_buffer, size_t* out_rmt_buffer_size); + +/// A structure representing current position in the RMT parser. +typedef struct RmtParserPosition +{ + uint64_t timestamp; ///< The last time seen. + int32_t stream_start_offset; ///< The start position in the stream (in bytes). + int32_t stream_current_offset; ///< The current offset (in bytes) into the stream. + int32_t seen_timestamp; ///< Flag indicating if we've seen a timestamp packet in the buffer yet. + int32_t file_buffer_actual_size; ///< The size of the file buffer. + int32_t file_buffer_offset; ///< The offset into the file buffer. +} RmtParserPosition; + +/// A structure encapsulating the RMT format parser state. +typedef struct RmtParser +{ + uint64_t start_timestamp; ///< THe timestamp considered to the be the start of the trace, specified in RMT clocks. + uint64_t current_timestamp; ///< The current time in RMT clocks. + int32_t seen_timestamp; ///< Set to non-zero if we have seen a kRmtTokenTypeTimestamp while parsing. + uint32_t cpu_frequency; ///< The CPU frequency (in clock ticks per second) of the machine where the RMT data was captured. + + RmtParserNextChunkCallbackFunc next_chunk_func; ///< The function to call to request more memory to parse when we run out of tokens. + FILE* file_handle; ///< The handle to read the file. + + // offset within the stream + int32_t stream_current_offset; ///< The current offset into rmtBuffer. + int32_t stream_start_offset; ///< The starting offset into rmtBuffer. + size_t stream_size; ///< The max length to read from this stream. + + // local buffering from file. + void* file_buffer; ///< Buffer to contain reads of data from the file. + int32_t file_buffer_size; ///< The size of the file buffer. + int32_t file_buffer_offset; ///< The current offset into the file buffer. + int32_t file_buffer_actual_size; ///< The actual size of the dat in the file buffer. + + int32_t major_version; ///< The major version of the RMT format. + int32_t minor_version; ///< The minor version of the RMT format. + uint64_t thread_id; ///< The thread ID of the CPU thread in the target application where the RMT data was collected from. + uint64_t process_id; ///< THe process ID of the target application where the RMT data was collected from. + int32_t stream_index; ///< The index of the RMT stream within the RMT file. +} RmtParser; + +/// Initialize the RMT parser structure. +/// +/// @param [in] rmt_parser A pointer to a RmtParser structure. +/// @param [in] file_handle A pointer to the data set file handle. +/// @param [in] file_offset The offset into the file specified by file_handle where the file chunk is to be found. +/// @param [in] stream_size The size of the file chunk. +/// @param [in] file_buffer A pointer to the read buffer. +/// @param [in] file_buffer_size The size of the read buffer. +/// @param [in] major_version The major version number of the RmtFileChunkHeader. +/// @param [in] minor_version The minor version number of the RmtFileChunkHeader. +/// @param [in] stream_index The index of the stream. +/// @param [in] process_id The process ID corresponding to the stream. +/// @param [in] thread_id The thread ID corresponding to the stream. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because rmt_parser or file_handle being set to NULL. +/// @retval +/// RMT_ERROR_INVALID_SIZE The operation failed because the stream_size is invalid. +RmtErrorCode RmtParserInitialize(RmtParser* rmt_parser, + FILE* file_handle, + int32_t file_offset, + int32_t stream_size, + void* file_buffer, + int32_t file_buffer_size, + int32_t major_version, + int32_t minor_version, + int32_t stream_index, + uint64_t process_id, + uint64_t thread_id); + +/// Advance the RMT parser forward by a single token. +/// +/// @param [in] rmt_parser A pointer to a RmtParser structure. +/// @param [out] out_token A pointer to a RmtToken structure. +/// @param [in] out_parser_position A pointer to a RmtParserPosition structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because rmt_parser or out_parser_position being set to NULL. +RmtErrorCode RmtParserAdvance(RmtParser* rmt_parser, RmtToken* out_token, RmtParserPosition* out_parser_position); + +/// Check if the RMT parser has finished. +/// +/// @param [in] rmt_parser A pointer to a RmtParser structure. +/// +/// @returns +/// true if the parser has finished, false if not. +bool RmtParserIsCompleted(RmtParser* rmt_parser); + +/// Set the current position of the RMT buffer on the parser. +/// +/// @param [in] rmt_parser A pointer to a RmtParser structure. +/// @param [in] parser_position A pointer to a RmtParserPosition structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because rmt_parser or parser_position being set to NULL. +RmtErrorCode RmtParserSetPosition(RmtParser* rmt_parser, const RmtParserPosition* parser_position); + +/// Reset the RMT parser. +/// +/// @param [in] rmt_parser A pointer to a RmtParser structure. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because rmt_parser being set to NULL. +RmtErrorCode RmtParserReset(RmtParser* rmt_parser); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_PARSER_ diff --git a/source/parser/rmt_platform.cpp b/source/parser/rmt_platform.cpp new file mode 100644 index 0000000..e5e6b1b --- /dev/null +++ b/source/parser/rmt_platform.cpp @@ -0,0 +1,54 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of platform-specific utils. +//============================================================================= + +#include "rmt_platform.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif /* #ifdef _WIN32 */ + +static const uint64_t kOneBillion = 1000000000; + +uint64_t RmtGetClockFrequency() +{ +#ifdef _WIN32 + LARGE_INTEGER freq = {0}; + QueryPerformanceFrequency(&freq); + return (uint64_t)freq.QuadPart; +#else + // clock_gettime on Linux is always in nanoseconds + return kOneBillion; +#endif /* #ifdef _WIN32 */ +} + +uint64_t RmtGetCurrentTimestamp() +{ +#ifdef _WIN32 + LARGE_INTEGER clock_value = {0}; + QueryPerformanceCounter(&clock_value); + return (uint64_t)clock_value.QuadPart; +#else + struct timespec clock_value; + clock_gettime(CLOCK_REALTIME, &clock_value); + return (clock_value.tv_sec * kOneBillion) + clock_value.tv_nsec; +#endif /* #ifdef _WIN32 */ +} + +void RmtSleep(uint32_t timeout) +{ +#ifdef WIN32 + Sleep(timeout); +#else + timespec time; + const time_t sec = static_cast(timeout / 1000); + time.tv_sec = sec; + time.tv_nsec = (timeout - (sec * 1000)) * 1000000; + nanosleep(&time, nullptr); +#endif +} diff --git a/source/parser/rmt_platform.h b/source/parser/rmt_platform.h new file mode 100644 index 0000000..a3bcdca --- /dev/null +++ b/source/parser/rmt_platform.h @@ -0,0 +1,54 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Platform specific utilities. +//============================================================================= + +#ifndef RMV_PARSER_RMT_PLATFORM_H_ +#define RMV_PARSER_RMT_PLATFORM_H_ + +#include + +#ifdef _WIN32 +#define RMT_FORCEINLINE __forceinline +#else +#define RMT_FORCEINLINE __attribute__((always_inline)) inline +#endif // #ifdef _WIN32 + +#ifdef _WIN32 +#define RMT_UNUSED_FUNC +#else +#define RMT_UNUSED_FUNC __attribute__((unused)) +#endif + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// Utility function to get the clock frequency. +/// +/// @returns +/// The current clock frquency of the CPU. +/// +uint64_t RmtGetClockFrequency(); + +/// Utility function to get the current timestamp. +/// +/// @returns +/// The current clock frquency of the CPU. +/// +uint64_t RmtGetCurrentTimestamp(); + +/// Utility function to sleep. +/// +/// @param [in] timeout The sleep time, in milliseconds. +/// +/// @returns +/// The current clock frquency of the CPU. +/// +void RmtSleep(uint32_t timeout); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_PLATFORM_H_ diff --git a/source/parser/rmt_print.cpp b/source/parser/rmt_print.cpp new file mode 100644 index 0000000..55ef40a --- /dev/null +++ b/source/parser/rmt_print.cpp @@ -0,0 +1,940 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Implementation of printing helper functions for RMT. +//============================================================================= + +#include "rmt_print.h" +#include // for sprintf +#include // for strcat + +#ifndef _WIN32 +#include "linux/safe_crt.h" +#endif + +const char* RmtGetPageSizeNameFromPageSize(RmtPageSize page_size) +{ + switch (page_size) + { + case kRmtPageSizeUnmapped: + return "UNMAPPED"; + case kRmtPageSize4Kb: + return "4KB"; + case kRmtPageSize64Kb: + return "64KB"; + case kRmtPageSize256Kb: + return "256KB"; + case kRmtPageSize1Mb: + return "1MB"; + case kRmtPageSize2Mb: + return "2MB"; + default: + return "Unknown"; + } +} + +const char* RmtGetResourceTypeNameFromResourceType(RmtResourceType resource_type) +{ + switch (resource_type) + { + case kRmtResourceTypeImage: + return "IMAGE"; + case kRmtResourceTypeBuffer: + return "BUFFER"; + case kRmtResourceTypeGpuEvent: + return "GPU_EVENT"; + case kRmtResourceTypeBorderColorPalette: + return "BORDER_COLOR_PALETTE"; + case kRmtResourceTypeIndirectCmdGenerator: + return "INDIRECT_CMD_GENERATOR"; + case kRmtResourceTypeMotionEstimator: + return "MOTION_ESTIMATOR"; + case kRmtResourceTypePerfExperiment: + return "PERF_EXPERIMENT"; + case kRmtResourceTypeQueryHeap: + return "QUERY_HEAP"; + case kRmtResourceTypeVideoDecoder: + return "VIDEO_DECODER"; + case kRmtResourceTypeVideoEncoder: + return "VIDEO_ENCODER"; + case kRmtResourceTypeTimestamp: + return "TIMESTAMP"; + case kRmtResourceTypeHeap: + return "HEAP"; + case kRmtResourceTypePipeline: + return "PIPELINE"; + case kRmtResourceTypeDescriptorHeap: + return "DESCRIPTOR_HEAP"; + case kRmtResourceTypeDescriptorPool: + return "DESCRIPTOR_POOL"; + case kRmtResourceTypeCommandAllocator: + return "CMD_ALLOCATOR"; + case kRmtResourceTypeMiscInternal: + return "MISC_INTERNAL"; + default: + return "Unknown"; + } +} + +const char* RmtGetCommitTypeNameFromCommitType(RmtCommitType commit_type) +{ + switch (commit_type) + { + case kRmtCommitTypeCommitted: + return "COMMITTED"; + case kRmtCommitTypePlaced: + return "PLACED"; + case kRmtCommitTypeVirtual: + return "VIRTUAL"; + default: + return "Unknown"; + } +} + +const char* RmtGetOwnerTypeNameFromOwnerType(RmtOwnerType owner_type) +{ + switch (owner_type) + { + case kRmtOwnerTypeApplication: + return "Application"; + case kRmtOwnerTypePal: + return "PAL"; + case kRmtOwnerTypeClientDriver: + return "ClientDriver"; + case kRmtOwnerTypeKmd: + return "KMD"; + default: + return "Unknown"; + } +} + +const char* RmtGetMiscTypeNameFromMiscType(RmtMiscType misc_type) +{ + switch (misc_type) + { + case kRmtMiscTypeFlushMappedRange: + return "FLUSH_MAPPED_RAGE"; + case kRmtMiscTypeInvalidateRanges: + return "INVALIDATE_RANGES"; + case kRmtMiscTypePresent: + return "PRESENT"; + case kRmtMiscTypeSubmitCompute: + return "SUBMIT_COMPUTE"; + case kRmtMiscTypeSubmitCopy: + return "SUBMIT_COPY"; + case kRmtMiscTypeSubmitGfx: + return "SUBMIT_GFX"; + case kRmtMiscTypeTrimMemory: + return "TRIM_MEMORY"; + default: + return "Unknown"; + } +} + +const char* RmtGetProcessEventNameFromProcessEvent(RmtProcessEventType process_event) +{ + switch (process_event) + { + case kRmtProcessEventTypeStart: + return "START"; + case kRmtProcessEventTypeStop: + return "STOP"; + default: + return "Unknown"; + } +} + +const char* RmtGetHeapTypeNameFromHeapType(RmtHeapType heap_type) +{ + switch (heap_type) + { + case kRmtHeapTypeLocal: + return "Local"; + case kRmtHeapTypeInvisible: + return "Invisible"; + case kRmtHeapTypeSystem: + return "Host"; + case kRmtHeapTypeNone: + return "Unspecified"; + default: + return "Unknown"; + } +} + +const char* RmtGetPageTableUpdateTypeNameFromPageTableUpdateType(RmtPageTableUpdateType update_type) +{ + switch (update_type) + { + case kRmtPageTableUpdateTypeDiscard: + return "DISCARD"; + case kRmtPageTableUpdateTypeUpdate: + return "UPDATE"; + case kRmtPageTableUpdateTypeTransfer: + return "TRANSFER"; + case kRmtPageTableUpdateTypeReserved: + return "RESERVED"; + default: + return "Unknown"; + } +} + +const char* RmtGetTokenNameFromTokenType(RmtTokenType token_type) +{ + switch (token_type) + { + case kRmtTokenTypeCpuMap: + return "CPU_MAP"; + case kRmtTokenTypeMisc: + return "MISC"; + case kRmtTokenTypePageReference: + return "PAGE_REFERENCE"; + case kRmtTokenTypePageTableUpdate: + return "PAGE_TABLE_UPDATE"; + case kRmtTokenTypeProcessEvent: + return "PROCESS_EVENT"; + case kRmtTokenTypeResourceBind: + return "RESOURCE_BIND"; + case kRmtTokenTypeResourceCreate: + return "RESOURCE_CREATE"; + case kRmtTokenTypeResourceDestroy: + return "RESOURCE_DESTROY"; + case kRmtTokenTypeResourceReference: + return "RESOURCE_REFERENCE"; + case kRmtTokenTypeTimestamp: + return "TIMESTAMP"; + case kRmtTokenTypeTimeDelta: + return "TIME_DELTA"; + case kRmtTokenTypeUserdata: + return "USERDATA"; + case kRmtTokenTypeVirtualAllocate: + return "VIRTUAL_ALLOCATE"; + case kRmtTokenTypeVirtualFree: + return "VIRTUAL_FREE"; + default: + return "Unknown"; + } +} + +// get the name of a format. +const char* RmtGetFormatNameFromFormat(RmtFormat format) +{ + switch (format) + { + case kRmtFormatUndefined: + return "UNDEFINED"; + case kRmtFormatX1Unorm: + return "X1_UNORM"; + case kRmtFormatX1Uscaled: + return "X1_USCALED"; + case kRmtFormatX4Y4Unorm: + return "X4Y4_UNORM"; + case kRmtFormatX4Y4Uscaled: + return "X4Y4_USCALED"; + case kRmtFormatL4A4Unorm: + return "L4A4_UNORM"; + case kRmtFormatX4Y4Z4W4Unorm: + return "X4Y4Z4W4_UNORM"; + case kRmtFormatX4Y4Z4W4Uscaled: + return "X4Y4Z4W4_USCALED"; + case kRmtFormatX5Y6Z5Unorm: + return "X5Y6Z5_UNORM"; + case kRmtFormatX5Y6Z5Uscaled: + return "X5Y6Z5_USCALED"; + case kRmtFormatX5Y5Z5W1Unorm: + return "X5Y5Z5W1_UNORM"; + case kRmtFormatX5Y5Z5W1Uscaled: + return "X5Y5Z5W1_USCALED"; + case kRmtFormatX1Y5Z5W5Unorm: + return "X1Y5Z5W5_UNORM"; + case kRmtFormatX1Y5Z5W5Uscaled: + return "X1Y5Z5W5_USCALED"; + case kRmtFormatX8Xnorm: + return "X8_XNORM"; + case kRmtFormatX8Snorm: + return "X8_SNORM"; + case kRmtFormatX8Uscaled: + return "X8_USCALED"; + case kRmtFormatX8Sscaled: + return "X8_SSCALED"; + case kRmtFormatX8Uint: + return "X8_UINT"; + case kRmtFormatX8Sint: + return "X8_SINT"; + case kRmtFormatX8Srgb: + return "X8_SRGB"; + case kRmtFormatA8Unorm: + return "A8_UNORM"; + case kRmtFormatL8Unorm: + return "L8_UNORM"; + case kRmtFormatP8Uint: + return "P8_UINT"; + case kRmtFormatX8Y8Unorm: + return "X8Y8_UNORM"; + case kRmtFormatX8Y8Snorm: + return "X8Y8_SNORM"; + case kRmtFormatX8Y8Uscaled: + return "X8Y8_USCALED"; + case kRmtFormatX8Y8Sscaled: + return "X8Y8_SSCALED"; + case kRmtFormatX8Y8Uint: + return "X8Y8_UINT"; + case kRmtFormatX8Y8Sint: + return "X8Y8_SINT"; + case kRmtFormatX8Y8Srgb: + return "X8Y8_SRGB"; + case kRmtFormatL8A8Unorm: + return "L8A8_UNORM"; + case kRmtFormatX8Y8Z8W8Unorm: + return "X8Y8Z8W8_UNORM"; + case kRmtFormatX8Y8Z8W8Snorm: + return "X8Y8Z8W8_SNORM"; + case kRmtFormatX8Y8Z8W8Uscaled: + return "X8Y8Z8W8_USCALED"; + case kRmtFormatX8Y8Z8W8Sscaled: + return "X8Y8Z8W8_SSCALED"; + case kRmtFormatX8Y8Z8W8Uint: + return "X8Y8Z8W8_UINT"; + case kRmtFormatX8Y8Z8W8Sint: + return "X8Y8Z8W8_SINT"; + case kRmtFormatX8Y8Z8W8Srgb: + return "X8Y8Z8W8_SRGB"; + case kRmtFormatU8V8SnormL8W8Unorm: + return "U8V8_SNORM_L8W8_UNORM"; + case kRmtFormatX10Y11Z11Float: + return "X10Y11Z11_FLOAT"; + case kRmtFormatX11Y11Z10Float: + return "X11Y11Z10_FLOAT"; + case kRmtFormatX10Y10Z10W2Unorm: + return "X10Y10Z10W2_UNORM"; + case kRmtFormatX10Y10Z10W2Snorm: + return "X10Y10Z10W2_SNORM"; + case kRmtFormatX10Y10Z10W2Uscaled: + return "X10Y10Z10W2_USCALED"; + case kRmtFormatX10Y10Z10W2Sscaled: + return "X10Y10Z10W2_SSCALED"; + case kRmtFormatX10Y10Z10W2Uint: + return "X10Y10Z10W2_UINT"; + case kRmtFormatX10Y10Z10W2Sint: + return "X10Y10Z10W2_SINT"; + case kRmtFormatX10Y10Z10W2BiasUnorm: + return "X10Y10Z10W2BIAS_UNORM"; + case kRmtFormatU10V10W10SnormA2Unorm: + return "U10V10W10_SNORM_A2_UNORM"; + case kRmtFormatX16Unorm: + return "X16_UNORM"; + case kRmtFormatX16Snorm: + return "X16_SNORM"; + case kRmtFormatX16Uscaled: + return "X16_USCALED"; + case kRmtFormatX16Sscaled: + return "X16_SSCALED"; + case kRmtFormatX16Uint: + return "X16_UINT"; + case kRmtFormatX16Sint: + return "X16_SINT"; + case kRmtFormatX16Float: + return "X16_FLOAT"; + case kRmtFormatL16Unorm: + return "L16_UNORM"; + case kRmtFormatX16Y16Unorm: + return "X16Y16_UNORM"; + case kRmtFormatX16Y16Snorm: + return "X16Y16_SNORM"; + case kRmtFormatX16Y16Uscaled: + return "X16Y16_USCALED"; + case kRmtFormatX16Y16Sscaled: + return "X16Y16_SSCALED"; + case kRmtFormatX16Y16Uint: + return "X16Y16_UINT"; + case kRmtFormatX16Y16Sint: + return "X16Y16_SINT"; + case kRmtFormatX16Y16Float: + return "X16Y16_FLOAT"; + case kRmtFormatX16Y16Z16W16Unorm: + return "X16Y16Z16W16_UNORM"; + case kRmtFormatX16Y16Z16W16Snorm: + return "X16Y16Z16W16_SNORM"; + case kRmtFormatX16Y16Z16W16Uscaled: + return "X16Y16Z16W16_USCALED"; + case kRmtFormatX16Y16Z16W16Sscaled: + return "X16Y16Z16W16_SSCALED"; + case kRmtFormatX16Y16Z16W16Uint: + return "X16Y16Z16W16_UINT"; + case kRmtFormatX16Y16Z16W16Sint: + return "X16Y16Z16W16_SINT"; + case kRmtFormatX16Y16Z16W16Float: + return "X16Y16Z16W16_FLOAT"; + case kRmtFormatX32Uint: + return "X32_UINT"; + case kRmtFormatX32Sint: + return "X32_SINT"; + case kRmtFormatX32Float: + return "X32_FLOAT"; + case kRmtFormatX32Y32Uint: + return "X32Y32_UINT"; + case kRmtFormatX32Y32Sint: + return "X32Y32_SINT"; + case kRmtFormatX32Y32Float: + return "X32Y32_FLOAT"; + case kRmtFormatX32Y32Z32Uint: + return "X32Y32Z32_UINT"; + case kRmtFormatX32Y32Z32Sint: + return "X32Y32Z32_SINT"; + case kRmtFormatX32Y32Z32Float: + return "X32Y32Z32_FLOAT"; + case kRmtFormatX32Y32Z32W32Uint: + return "X32Y32Z32W32_UINT"; + case kRmtFormatX32Y32Z32W32Sint: + return "X32Y32Z32W32_SINT"; + case kRmtFormatX32Y32Z32W32Float: + return "X32Y32Z32W32_FLOAT"; + case kRmtFormatD16UnormS8Uint: + return "D16_UNORM_S8_UINT"; + case kRmtFormatD32UnormS8Uint: + return "D32_UNORM_S8_UINT"; + case kRmtFormatX9Y9Z9E5Float: + return "X9Y9Z9E5_FLOAT"; + case kRmtFormatBC1Unorm: + return "BC1_UNORM"; + case kRmtFormatBC1Srgb: + return "BC1_SRGB"; + case kRmtFormatBC2Unorm: + return "BC2_UNORM"; + case kRmtFormatBC2Srgb: + return "BC2_SRGB"; + case kRmtFormatBC3Unorm: + return "BC3_UNORM"; + case kRmtFormatBC3Srgb: + return "BC3_SRGB"; + case kRmtFormatBC4Unorm: + return "BC4_UNORM"; + case kRmtFormatBC4Srgb: + return "BC4_SRGB"; + case kRmtFormatBC5Unorm: + return "BC5_UNORM"; + case kRmtFormatBC5Srgb: + return "BC5_SRGB"; + case kRmtFormatBC6Unorm: + return "BC6_UNORM"; + case kRmtFormatBC6Srgb: + return "BC6_SRGB"; + case kRmtFormatBC7Unorm: + return "BC7_UNORM"; + case kRmtFormatBC7Srgb: + return "BC7_SRGB"; + + case kRmtFormatEtC2X8Y8Z8Unorm: + return "ETC2X8Y8Z8_UNORM"; + case kRmtFormatEtC2X8Y8Z8Srgb: + return "ETC2X8Y8Z8_SRGB"; + case kRmtFormatEtC2X8Y8Z8W1Unorm: + return "ETC2X8Y8Z8W1_UNORM"; + case kRmtFormatEtC2X8Y8Z8W1Srgb: + return "ETC2X8Y8Z8W1_SRGB"; + case kRmtFormatEtC2X8Y8Z8W8Unorm: + return "ETC2X8Y8Z8W8_UNORM"; + case kRmtFormatEtC2X8Y8Z8W8Srgb: + return "ETC2X8Y8Z8W8_SRGB"; + case kRmtFormatEtC2X11Unorm: + return "ETC2X11_UNORM"; + case kRmtFormatEtC2X11Snorm: + return "ETC2X11_SNORM"; + case kRmtFormatEtC2X11Y11Unorm: + return "ETC2X11Y11_UNORM"; + case kRmtFormatEtC2X11Y11Snorm: + return "ETC2X11Y11_SNORM"; + case kRmtFormatAstcldR4X4Unorm: + return "ASTCLDR4X4_UNORM"; + case kRmtFormatAstcldR4X4Srgb: + return "ASTCLDR4X4_SRGB"; + case kRmtFormatAstcldR5X4Unorm: + return "ASTCLDR5X4_UNORM"; + case kRmtFormatAstcldR5X4Srgb: + return "ASTCLDR5X4_SRGB"; + case kRmtFormatAstcldR5X5Unorm: + return "ASTCLDR5X5_UNORM"; + case kRmtFormatAstcldR5X5Srgb: + return "ASTCLDR5X5_SRGB"; + case kRmtFormatAstcldR6X5Unorm: + return "ASTCLDR6X5_UNORM"; + case kRmtFormatAstcldR6X5Srgb: + return "ASTCLDR6X5_SRGB"; + case kRmtFormatAstcldR6X6Unorm: + return "ASTCLDR6X6_UNORM"; + case kRmtFormatAstcldR6X6Srgb: + return "ASTCLDR6X6_SRGB"; + case kRmtFormatAstcldR8X5Unorm: + return "ASTCLDR8X5_UNORM"; + case kRmtFormatAstcldR8X5Srgb: + return "ASTCLDR8X5_SRGB"; + case kRmtFormatAstcldR8X6Unorm: + return "ASTCLDR8X6_UNORM"; + case kRmtFormatAstcldR8X6Srgb: + return "ASTCLDR8X6_SRGB"; + case kRmtFormatAstcldR8X8Unorm: + return "ASTCLDR8X8_UNORM"; + case kRmtFormatAstcldR8X8Srgb: + return "ASTCLDR8X8_SRGB"; + case kRmtFormatAstcldR10X5Unorm: + return "ASTCLDR10X5_UNORM"; + case kRmtFormatAstcldR10X5Srgb: + return "ASTCLDR10X5_SRGB"; + case kRmtFormatAstcldR10X6Unorm: + return "ASTCLDR10X6_UNORM"; + case kRmtFormatAstcldR10X6Srgb: + return "ASTCLDR10X6_SRGB"; + case kRmtFormatAstcldR10X8Unorm: + return "ASTCLDR10X8_UNORM"; + case kRmtFormatAstcldR10X10Unorm: + return "ASTCLDR10X10_UNORM"; + case kRmtFormatAstcldR12X10Unorm: + return "ASTCLDR12X10_UNORM"; + case kRmtFormatAstcldR12X10Srgb: + return "ASTCLDR12X10_SRGB"; + case kRmtFormatAstcldR12X12Unorm: + return "ASTCLDR12X12_UNORM"; + case kRmtFormatAstcldR12X12Srgb: + return "ASTCLDR12X12_SRGB"; + case kRmtFormatAstchdR4x4Float: + return "ASTCHDR4x4_FLOAT"; + case kRmtFormatAstchdR5x4Float: + return "ASTCHDR5x4_FLOAT"; + case kRmtFormatAstchdR5x5Float: + return "ASTCHDR5x5_FLOAT"; + case kRmtFormatAstchdR6x5Float: + return "ASTCHDR6x5_FLOAT"; + case kRmtFormatAstchdR6x6Float: + return "ASTCHDR6x6_FLOAT"; + case kRmtFormatAstchdR8x5Float: + return "ASTCHDR8x5_FLOAT"; + case kRmtFormatAstchdR8x6Float: + return "ASTCHDR8x6_FLOAT"; + case kRmtFormatAstchdR8x8Float: + return "ASTCHDR8x8_FLOAT"; + case kRmtFormatAstchdR10x5Float: + return "ASTCHDR10x5_FLOAT"; + case kRmtFormatAstchdR10x6Float: + return "ASTCHDR10x6_FLOAT"; + case kRmtFormatAstchdR10x8Float: + return "ASTCHDR10x8_FLOAT"; + case kRmtFormatAstchdR10x10Float: + return "ASTCHDR10x10_FLOAT"; + case kRmtFormatAstchdR12x10Float: + return "ASTCHDR12x10_FLOAT"; + case kRmtFormatAstchdR12x12Float: + return "ASTCHDR12x12_FLOAT"; + case kRmtFormatX8Y8Z8Y8Unorm: + return "X8Y8_Z8Y8_UNORM"; + case kRmtFormatX8Y8Z8Y8Uscaled: + return "X8Y8_Z8Y8_USCALED"; + case kRmtFormatY8X8Y8Z8Unorm: + return "Y8X8_Y8Z8_UNORM"; + case kRmtFormatY8X8Y8Z8Uscaled: + return "Y8X8_Y8Z8_USCALED"; + case kRmtFormatAyuv: + return "AYUV"; + case kRmtFormatUyvy: + return "UYVY"; + case kRmtFormatVyuy: + return "VYUY"; + case kRmtFormatYuY2: + return "YUY2"; + case kRmtFormatYvY2: + return "YVY2"; + case kRmtFormatYV12: + return "YV12"; + case kRmtFormatNV11: + return "NV11"; + case kRmtFormatNV12: + return "NV12"; + case kRmtFormatNV21: + return "NV21"; + case kRmtFormatP016: + return "P016"; + case kRmtFormatP010: + return "P010"; + + default: + return "Unknown"; + } +} + +const char* RmtGetChannelSwizzleNameFromChannelSwizzle(RmtChannelSwizzle channel_swizzle) +{ + switch (channel_swizzle) + { + case kRmtSwizzleZero: + return "0"; + case kRmtSwizzleOne: + return "1"; + case kRmtSwizzleX: + return "X"; + case kRmtSwizzleY: + return "Y"; + case kRmtSwizzleZ: + return "Z"; + case kRmtSwizzleW: + return "W"; + default: + return "Unknown"; + } +} + +const char* RmtGetSwizzlePatternFromImageFormat(const RmtImageFormat* image_format, char* out_string, int32_t max_length) +{ + sprintf_s(out_string, + max_length, + "%s%s%s%s", + RmtGetChannelSwizzleNameFromChannelSwizzle(image_format->swizzle_x), + RmtGetChannelSwizzleNameFromChannelSwizzle(image_format->swizzle_y), + RmtGetChannelSwizzleNameFromChannelSwizzle(image_format->swizzle_z), + RmtGetChannelSwizzleNameFromChannelSwizzle(image_format->swizzle_w)); + return out_string; +} + +const char* RmtGetTilingNameFromTilingType(RmtTilingType tiling_type) +{ + switch (tiling_type) + { + case kRmtTilingTypeLinear: + return "Linear"; + case kRmtTilingTypeOptimal: + return "Optimal"; + case kRmtTilingTypeStandardSwizzle: + return "Standard Swizzle"; + default: + return "Unknown"; + } +} + +const char* RmtGetImageTypeNameFromImageType(RmtImageType image_type) +{ + switch (image_type) + { + case kRmtImageType1D: + return "1D"; + case kRmtImageType2D: + return "2D"; + case kRmtImageType3D: + return "3D"; + default: + return "Unknown"; + } +} + +const char* RmtGetTilingOptimizationModeNameFromTilingOptimizationMode(RmtTilingOptimizationMode tiling_optimization_mode) +{ + switch (tiling_optimization_mode) + { + case kRmtTilingOptimizationModeBalanced: + return "Balanced"; + case kRmtTilingOptimizationModeSpace: + return "Space"; + case kRmtTilingOptimizationModeSpeed: + return "Speed"; + default: + return "Unknown"; + } +} + +/// Get the image creation flag text based on the bitfield parameter +static const char* GetImageCreationNameFromImageCreationFlagBits(int32_t bitfield) +{ + switch (bitfield) + { + case kRmtImageCreationFlagInvariant: + return "INVARIANT"; + case kRmtImageCreationFlagCloneable: + return "CLONEABLE"; + case kRmtImageCreationFlagShareable: + return "SHAREABLE"; + case kRmtImageCreationFlagFlippable: + return "FLIPPABLE"; + case kRmtImageCreationFlagStereo: + return "STEREO"; + case kRmtImageCreationFlagCubemap: + return "CUBEMAP"; + case kRmtImageCreationFlagPrt: + return "PRT"; + case kRmtImageCreationFlagReserved0: + return "RESERVED_0"; + case kRmtImageCreationFlagReadSwizzleEquations: + return "READ_SWIZZLE_EQUATIONS"; + case kRmtImageCreationFlagPerSubresourceInit: + return "PER_SUBRESOURCE_INIT"; + case kRmtImageCreationFlagSeparateDepthAspectRatio: + return "SEPARATE_DEPTH_ASPECT_RATIO"; + case kRmtImageCreationFlagCopyFormatsMatch: + return "COPY_FORMATS_MATCH"; + case kRmtImageCreationFlagRepetitiveResolve: + return "REPETITIVE_RESOLVE"; + case kRmtImageCreationFlagPreferSwizzleEquations: + return "PREFER_SWIZZLE_EQUATIONS"; + case kRmtImageCreationFlagFixedTileSwizzle: + return "FIXED_TILE_SWIZZLE"; + case kRmtImageCreationFlagVideoReferenceOnly: + return "VIDEO_REFERENCE_ONLY"; + case kRmtImageCreationFlagOptimalShareable: + return "OPTIMAL_SHAREABLE"; + case kRmtImageCreationFlagSampleLocationsKnown: + return "SAMPLE_LOCATIONS_KNOWN"; + case kRmtImageCreationFlagFullResolveDestOnly: + return "FULL_RESOLVE_DEST_ONLY"; + case kRmtImageCreationFlagExternalShared: + return "EXTERNAL_SHARED"; + default: + return ""; + } +} + +/// Get the image usage flag text based on the bitfield parameter +static const char* GetImageUsageNameFromImageUsageFlagBits(int32_t bitfield) +{ + switch (bitfield) + { + case kRmtImageUsageFlagsShaderRead: + return "SHADER_READ"; + case kRmtImageUsageFlagsShaderWrite: + return "SHADER_WRITE"; + case kRmtImageUsageFlagsResolveSource: + return "RESOLVE_SOURCE"; + case kRmtImageUsageFlagsResolveDestination: + return "RESOLVE_DESTINATION"; + case kRmtImageUsageFlagsColorTarget: + return "COLOR_TARGET"; + case kRmtImageUsageFlagsDepthStencil: + return "DEPTH_STENCIL"; + case kRmtImageUsageFlagsNoStencilShaderRead: + return "NO_STENCIL_SHADER_READ"; + case kRmtImageUsageFlagsHiZNeverInvalid: + return "HI_Z_NEVER_INVALID"; + case kRmtImageUsageFlagsDepthAsZ24: + return "DEPTH_AS_Z24"; + case kRmtImageUsageFlagsFirstShaderWritableMip: + return "FIRST_SHADER_WRITABLE_MIP"; + case kRmtImageUsageFlagsCornerSampling: + return "CORNER_SAMPLING"; + case kRmtImageUsageFlagsVrsDepth: + return "VRS_DEPTH"; + default: + return ""; + } +} + +/// Get the buffer creation flag text based on the bitfield parameter +static const char* GetBufferCreationNameFromBufferCreationFlagBits(int32_t bitfield) +{ + switch (bitfield) + { + case kRmtBufferCreationFlagSparseBinding: + return "SPARSE_BINDING"; + case kRmtBufferCreationFlagSparseResidency: + return "SPARSE_RESIDENCY"; + case kRmtBufferCreationFlagSparseAliasing: + return "SPARSE_ALIASING"; + case kRmtBufferCreationFlagProtected: + return "PROTECTED"; + case kRmtBufferCreationFlagDeviceAddressCaptureReplay: + return "DEVICE_ADDRESS_CAPTURE_REPLAY"; + default: + return ""; + } +} + +/// Get the buffer usage flag text based on the bitfield parameter +static const char* GetBufferUsageNameFromBufferUsageFlagBits(int32_t bitfield) +{ + switch (bitfield) + { + case kRmtBufferUsageFlagTransferSource: + return "TRANSFER_SOURCE"; + case kRmtBufferUsageFlagTransferDestination: + return "TRANSFER_DESTINATION"; + case kRmtBufferUsageFlagUniformTexelBuffer: + return "UNIFORM_TEXEL_BUFFER"; + case kRmtBufferUsageFlagStorageTexelBuffer: + return "STORAGE_TEXEL_BUFFER"; + case kRmtBufferUsageFlagUniformBuffer: + return "UNIFORM_BUFFER"; + case kRmtBufferUsageFlagStorageBuffer: + return "STORAGE_BUFFER"; + case kRmtBufferUsageFlagIndexBuffer: + return "INDEX_BUFFER"; + case kRmtBufferUsageFlagVertexBuffer: + return "VERTEX_BUFFER"; + case kRmtBufferUsageFlagIndirectBuffer: + return "INDIRECT_BUFFER"; + case kRmtBufferUsageFlagTransformFeedbackBuffer: + return "TRANSFORM_FEEDBACK_BUFFER"; + case kRmtBufferUsageFlagTransformFeedbackCounterBuffer: + return "TRANSFORM_FEEDBACK_COUNTER_BUFFER"; + case kRmtBufferUsageFlagConditionalRendering: + return "CONDITIONAL_RENDERING"; + case kRmtBufferUsageFlagRayTracing: + return "RAY_TRACING"; + case kRmtBufferUsageFlagShaderDeviceAddress: + return "SHADER_DEVICE_ADDRESS"; + default: + return ""; + } +} + +/// Get the GPU event flag text based on the bitfield parameter +static const char* GetGpuEventNameFromGpuEventFlagBits(int32_t bitfield) +{ + switch (bitfield) + { + case kRmtGpuEventFlagGpuOnly: + return "GPU_ONLY"; + default: + return ""; + } +} + +/// Get the pipeline creation flag text based on the bitfield parameter +static const char* GetPipelineCreationNameFromPipelineCreationFlagBits(int32_t bitfield) +{ + switch (bitfield) + { + case kRmtPipelineCreateFlagInternal: + return "CLIENT_INTERNAL"; + case kRmtPipelineCreateFlagOverrideGpuHeap: + return "OVERRIDE_GPU_HEAP"; + case kRmtPipelineCreateFlagReserved0: + return "RESERVED_0"; + case kRmtPipelineCreateFlagReserved1: + return "RESERVED_1"; + case kRmtPipelineCreateFlagReserved2: + return "RESERVED_2"; + case kRmtPipelineCreateFlagReserved3: + return "RESERVED_3"; + case kRmtPipelineCreateFlagReserved4: + return "RESERVED_4"; + case kRmtPipelineCreateFlagReserved5: + return "RESERVED_5"; + default: + return ""; + } +} + +/// Get the command allocator flag text based on the bitfield parameter +static const char* GetCmdAllocatorNameFromCmdAllocatorFlagBits(int32_t bitfield) +{ + switch (bitfield) + { + case kRmtCmdAllocatorAutoMemoryReuse: + return "AUTO_MEMORY_REUSE"; + case kRmtCmdAllocatorDisableBusyChunkTracking: + return "DISABLE_BUSY_CHUNK_TRACKING"; + case kRmtCmdAllocatorThreadSafe: + return "THREAD_SAFE"; + default: + return ""; + } +} + +/// Get the pipeline stage flag text based on the bitfield parameter +static const char* GetPipelineStageNameFromPipelineStageBits(int32_t bitfield) +{ + switch (bitfield) + { + case kRmtPipelineStageMaskPs: + return "PS"; + case kRmtPipelineStageMaskHs: + return "HS"; + case kRmtPipelineStageMaskDs: + return "DS"; + case kRmtPipelineStageMaskVs: + return "VS"; + case kRmtPipelineStageMaskGs: + return "GS"; + case kRmtPipelineStageMaskCs: + return "CS"; + default: + return ""; + } +} + +/// Get a text string based on the flags passed in. Each flag is separated by a '|' +/// If there are no text strings corresponding to the flags, the raw value is shown +/// The raw flags value is shown after the flag text string in parentheses +/// @param [in] flags The buffer usage flags to convert to text +/// @param [out] flag_text String to accept the flag text +/// @param [in] text_length The length of the flag text string +/// @param [in] function Pointer to a function to get the text from a bitfield. +static void GetFlagsNameFromFlags(int32_t flags, char* flag_text, int text_length, const char* (*function)(int32_t)) +{ + int32_t mask = 0x01; + const int32_t size = sizeof(int32_t) * 8; + + flag_text[0] = '\0'; + + int flag_count = 0; + for (int i = 0; i < size; i++) + { + if ((flags & mask) != 0) + { + if (flag_count > 0) + { + strcat_s(flag_text, text_length, " | "); + } + const char* flag_name = function(mask); + strcat_s(flag_text, text_length, flag_name); + flag_count++; + } + mask <<= 1; + } + + if (flag_count == 0) + { + // no flags text set so display "None" + sprintf_s(flag_text, text_length, "None"); + } + else + { + // append the value of the flags after the text + char value_text[32]; + sprintf_s(value_text, 32, " (%d)", flags); + strcat_s(flag_text, text_length, value_text); + } +} + +void RmtGetImageCreationNameFromImageCreationFlags(int32_t flags, char* flag_text, int text_length) +{ + GetFlagsNameFromFlags(flags, flag_text, text_length, &GetImageCreationNameFromImageCreationFlagBits); +} + +void RmtGetImageUsageNameFromImageUsageFlags(int32_t flags, char* flag_text, int text_length) +{ + GetFlagsNameFromFlags(flags, flag_text, text_length, &GetImageUsageNameFromImageUsageFlagBits); +} + +void RmtGetBufferCreationNameFromBufferCreationFlags(int32_t flags, char* flag_text, int text_length) +{ + GetFlagsNameFromFlags(flags, flag_text, text_length, &GetBufferCreationNameFromBufferCreationFlagBits); +} + +void RmtGetBufferUsageNameFromBufferUsageFlags(int32_t flags, char* flag_text, int text_length) +{ + GetFlagsNameFromFlags(flags, flag_text, text_length, &GetBufferUsageNameFromBufferUsageFlagBits); +} + +void RmtGetGpuEventNameFromGpuEventFlags(int32_t flags, char* flag_text, int text_length) +{ + GetFlagsNameFromFlags(flags, flag_text, text_length, &GetGpuEventNameFromGpuEventFlagBits); +} + +void RmtGetPipelineCreationNameFromPipelineCreationFlags(int32_t flags, char* flag_text, int text_length) +{ + GetFlagsNameFromFlags(flags, flag_text, text_length, &GetPipelineCreationNameFromPipelineCreationFlagBits); +} + +void RmtGetCmdAllocatorNameFromCmdAllocatorFlags(int32_t flags, char* flag_text, int text_length) +{ + GetFlagsNameFromFlags(flags, flag_text, text_length, &GetCmdAllocatorNameFromCmdAllocatorFlagBits); +} + +void RmtGetPipelineStageNameFromPipelineStageFlags(int32_t flags, char* flag_text, int text_length) +{ + GetFlagsNameFromFlags(flags, flag_text, text_length, &GetPipelineStageNameFromPipelineStageBits); +} diff --git a/source/parser/rmt_print.h b/source/parser/rmt_print.h new file mode 100644 index 0000000..431728f --- /dev/null +++ b/source/parser/rmt_print.h @@ -0,0 +1,175 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Printing helper functions for RMT. +//============================================================================= + +#ifndef RMV_PARSER_RMT_PRINT_H_ +#define RMV_PARSER_RMT_PRINT_H_ + +#include "rmt_error.h" +#include "rmt_format.h" + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// Get the page size as a string from the page size ID. +/// +/// @param [in] page_size An RmtPageSize defining the page size. +/// @returns +/// Pointer to the page size string. +const char* RmtGetPageSizeNameFromPageSize(RmtPageSize page_size); + +/// Get the resource type as a string from the resource type ID. +/// +/// @param [in] resource_type An RmtResourceType defining the resource type. +/// @returns +/// Pointer to the resource type string. +const char* RmtGetResourceTypeNameFromResourceType(RmtResourceType resource_type); + +/// Get the commit type as a string from the commit type ID. +/// +/// @param [in] commit_type An RmtCommitType defining the commit type. +/// @returns +/// Pointer to the commit type string. +const char* RmtGetCommitTypeNameFromCommitType(RmtCommitType commit_type); + +/// Get the owner type as a string from the owner type ID. +/// +/// @param [in] owner_type An RmtOwnerType defining the owner type. +/// @returns +/// Pointer to the owner type string. +const char* RmtGetOwnerTypeNameFromOwnerType(RmtOwnerType owner_type); + +/// Get the miscellaneous type as a string from the miscellaneous type ID. +/// +/// @param [in] misc_type An RmtMiscType defining the miscellaneous type. +/// @returns +/// Pointer to the miscellaneous type string. +const char* RmtGetMiscTypeNameFromMiscType(RmtMiscType misc_type); + +/// Get the process event type as a string from the process event type ID. +/// +/// @param [in] process_event An RmtProcessEventType defining the process event type. +/// @returns +/// Pointer to the process event type string. +const char* RmtGetProcessEventNameFromProcessEvent(RmtProcessEventType process_event); + +/// Get the heap type as a string from the heap type ID. +/// +/// @param [in] heap_type An RmtHeapType defining the heap type. +/// @returns +/// Pointer to the heap type string. +const char* RmtGetHeapTypeNameFromHeapType(RmtHeapType heap_type); + +/// Get the page table update type as a string from the page table update type ID. +/// +/// @param [in] update_type An RmtPageTableUpdateType defining the page table update type. +/// @returns +/// Pointer to the page table update type string. +const char* RmtGetPageTableUpdateTypeNameFromPageTableUpdateType(RmtPageTableUpdateType update_type); + +/// Get the token type as a string from the token type ID. +/// +/// @param [in] token_type An RmtTokenType defining the token type. +/// @returns +/// Pointer to the token type string. +const char* RmtGetTokenNameFromTokenType(RmtTokenType token_type); + +/// Get the format as a string from the format ID. +/// +/// @param [in] format An RmtFormat defining the format. +/// @returns +/// Pointer to the format string. +const char* RmtGetFormatNameFromFormat(RmtFormat format); + +/// Get the channel swizzle name as a string from the channel swizzle struct. +/// +/// @param [in] channel_swizzle An RmtChannelSwizzle defining the token type. +/// @returns +/// Pointer to the channel swizzle string. +const char* RmtGetChannelSwizzleNameFromChannelSwizzle(RmtChannelSwizzle channel_swizzle); + +/// Get swizzle pattern name as a string from the image format struct. +/// +/// @param [in] format An RmtImageFormat defining the image format. +/// @param [out] out_string A pointer to a string to receive the name string. +/// @param [in] max_length The length of out_string, in bytes. +/// @returns +/// Pointer to the swizzel pattern string (the same pointer as out_string). +const char* RmtGetSwizzlePatternFromImageFormat(const RmtImageFormat* format, char* out_string, int32_t max_length); + +/// Get the tiling type as a string from the tiling type ID. +/// +/// @param [in] tiling_type An RmtTilingType defining the tiling type. +/// @returns +/// Pointer to the tiling type string. +const char* RmtGetTilingNameFromTilingType(RmtTilingType tiling_type); + +/// Get the image type as a string from the image type ID. +/// +/// @param [in] image_type An RmtImageType defining the image type. +/// @returns +/// Pointer to the image type string. +const char* RmtGetImageTypeNameFromImageType(RmtImageType image_type); + +/// Get the tiling optimization mode as a string from the tiling optimization mode ID. +/// +/// @param [in] tiling_optimization_mode An RmtTilingOptimizationMode defining the tiling optimization mode. +/// @returns +/// Pointer to the tiling optimization mode string. +const char* RmtGetTilingOptimizationModeNameFromTilingOptimizationMode(RmtTilingOptimizationMode tiling_optimization_mode); + +/// Get the image creation flags. +/// @param [in] flags The image creation flags to convert to text. +/// @param [out] flag_text String to accept the flag text. +/// @param [in] text_length The length of the flag text string. +void RmtGetImageCreationNameFromImageCreationFlags(int32_t flags, char* flag_text, int text_length); + +/// Get the image usage flags. +/// @param [in] flags The image usage flags to convert to text. +/// @param [out] flag_text String to accept the flag text. +/// @param [in] text_length The length of the flag text string. +void RmtGetImageUsageNameFromImageUsageFlags(int32_t flags, char* flag_text, int text_length); + +/// Get the buffer creation flags. +/// @param [in] flags The buffer creation flags to convert to text. +/// @param [out] flag_text String to accept the flag text. +/// @param [in] text_length The length of the flag text string. +void RmtGetBufferCreationNameFromBufferCreationFlags(int32_t flags, char* flag_text, int text_length); + +/// Get the buffer usage flags. +/// @param [in] flags The buffer usage flags to convert to text. +/// @param [out] flag_text String to accept the flag text. +/// @param [in] text_length The length of the flag text string. +void RmtGetBufferUsageNameFromBufferUsageFlags(int32_t flags, char* flag_text, int text_length); + +/// Get the GPU event flags. +/// @param [in] flags The GPU event flags to convert to text. +/// @param [out] flag_text String to accept the flag text. +/// @param [in] text_length The length of the flag text string. +void RmtGetGpuEventNameFromGpuEventFlags(int32_t flags, char* flag_text, int text_length); + +/// Get the pipeline creation flags. +/// @param [in] flags The pipeline creation flags to convert to text. +/// @param [out] flag_text String to accept the flag text. +/// @param [in] text_length The length of the flag text string. +void RmtGetPipelineCreationNameFromPipelineCreationFlags(int32_t flags, char* flag_text, int text_length); + +/// Get the command allocator flags. +/// @param [in] flags The command allocator flags to convert to text. +/// @param [out] flag_text String to accept the flag text. +/// @param [in] text_length The length of the flag text string. +void RmtGetCmdAllocatorNameFromCmdAllocatorFlags(int32_t flags, char* flag_text, int text_length); + +/// Get the pipeline stage flags. +/// @param [in] flags The pipeline stage flags to convert to text. +/// @param [out] flag_text String to accept the flag text. +/// @param [in] text_length The length of the flag text string. +void RmtGetPipelineStageNameFromPipelineStageFlags(int32_t flags, char* flag_text, int text_length); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_PRINT_H_ diff --git a/source/parser/rmt_profile.h b/source/parser/rmt_profile.h new file mode 100644 index 0000000..da5abeb --- /dev/null +++ b/source/parser/rmt_profile.h @@ -0,0 +1,27 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \file +/// \brief Profile utils. +//============================================================================= + +#ifndef RMV_PARSER_RMT_PROFILE_H_ +#define RMV_PARSER_RMT_PROFILE_H_ + +#include "rmt_platform.h" +#include + +#define RMT_PROFILE_START(name) \ + const uint64_t name##___freq_value = rmtGetClockFrequency(); \ + const uint64_t name##___strt_value = rmtGetCurrentTimestamp(); + +#define RMT_PROFILE_STOP(name) \ + do \ + { \ + const uint64_t name##___stop_value = rmtGetCurrentTimestamp(); \ + const uint64_t name##___delta = name##___stop_value - name##___strt_value; \ + const uint64_t name##___micro = name##___delta * 1000000; \ + const uint64_t name##___final = name##___micro / name##___freq_value; \ + printf("%" PRIu64 "ms\n", (name##___final / 1000)); \ + } while (0) + +#endif // #ifndef RMV_PARSER_RMT_PROFILE_H_ diff --git a/source/parser/rmt_token_heap.cpp b/source/parser/rmt_token_heap.cpp new file mode 100644 index 0000000..07ec284 --- /dev/null +++ b/source/parser/rmt_token_heap.cpp @@ -0,0 +1,398 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Implementation of a priority queue data structure. +//============================================================================= + +#include "rmt_token_heap.h" +#include "rmt_platform.h" +#include "rmt_format.h" +#include "rmt_parser.h" +#include "rmt_assert.h" +#include // for memcpy() + +// NOTE: if its a KMD stream, bias the timestamp backwards. The reason for this +// is to compensate for the latency of the data output from KMD being shorter +// than that of the UMD. This gives rise to KMD tokens sometimes arriving for PTE +// tokens before the corresponding VA is recorded as being allocated by the UMD. +// by biasing it backwards we get a more accurate accounting of mapped memory +// per VA (and per process) at the expense of some accuracy of when memory is mapped. +// this seems like a reaonable tradeoff as unless the user generates a snapshot +// inside this buffer offset (relative to the start of a VA) it should be benign. +#define KMD_TIMESTAMP_BIAS (0) + +// helper function to compare to RmtToken timestamp values. +static RMT_FORCEINLINE bool ElementCompareLess(const RmtToken* a, const RmtToken* b) +{ + return a->common.timestamp <= b->common.timestamp; +} + +// helper function to compare to RmtToken timestamp values. +#ifdef VALIDATE_HEAP +static RMT_FORCEINLINE bool elementCompareLessByIndex(const RmtStreamMerger* token_heap, int32_t a, int32_t b) +{ + return token_heap->tokens[a]->common.timestamp <= token_heap->tokens[b]->common.timestamp; +} +#endif // VALIDATE_HEAP + +// heap function to swap two tokens +static RMT_FORCEINLINE void ElementSwap(RmtToken** a, RmtToken** b) +{ + RmtToken* temp = *a; + *a = *b; + *b = temp; +} + +#ifdef VALIDATE_HEAP +static bool validateHeap(RmtStreamMerger* token_heap, int32_t element_index) +{ + if (!token_heap) + { + return false; + } + + if ((size_t)element_index >= token_heap->current_size) + { + return true; + } + + const int32_t left_element_index = (element_index << 1) + 1; + const int32_t right_element_index = (element_index << 1) + 2; + + if ((size_t)left_element_index < token_heap->current_size && !elementCompareLessByIndex(token_heap, element_index, left_element_index)) + { + return false; + } + + if ((size_t)right_element_index < token_heap->current_size && !elementCompareLessByIndex(token_heap, element_index, right_element_index)) + { + return false; + } + + return validateHeap(token_heap, left_element_index) && validateHeap(token_heap, right_element_index); +} +#endif // VALIDATE_HEAP + +// helper function to move an element to its correct place in the heap from the bttom of the heap. +static void ElementMoveUp(RmtStreamMerger* token_heap, size_t element_index) +{ + if (element_index == 0U) + { + return; + } + + size_t parent_index = (element_index - 1) >> 1; + + while (element_index > 0 && ElementCompareLess(token_heap->tokens[element_index], token_heap->tokens[parent_index])) + { + ElementSwap(&token_heap->tokens[parent_index], &token_heap->tokens[element_index]); + element_index = parent_index; + parent_index = (element_index - 1) >> 1; + } +} + +// helper function to move an element to its correct place from the top of the heap. +static void ElementMoveDown(RmtStreamMerger* token_heap, size_t element_index) +{ + if (element_index >= token_heap->current_size) + { + return; + } + + RmtToken** current_element = &token_heap->tokens[element_index]; + + // traverse the heap until we hit the bottom. + for (;;) + { + const size_t left_child_index = (element_index << 1) + 1; + const size_t right_child_index = (element_index << 1) + 2; + + // get the token values. + RmtToken** smallest_element = &token_heap->tokens[left_child_index]; + element_index = left_child_index; + + RmtToken** right_element = &token_heap->tokens[right_child_index]; + + if (right_child_index < token_heap->current_size && ElementCompareLess(*right_element, *smallest_element)) + { + smallest_element = &token_heap->tokens[right_child_index]; + element_index = right_child_index; + } + + if (left_child_index >= token_heap->current_size || ElementCompareLess(*current_element, *smallest_element)) + { + break; + } + + ElementSwap(smallest_element, current_element); + current_element = smallest_element; + } +} + +static RmtErrorCode Peek(RmtStreamMerger* token_heap, RmtToken* out_token) +{ + RMT_ASSERT(token_heap); + RMT_ASSERT(out_token); + + memcpy(out_token, (const void*)token_heap->tokens[0], sizeof(RmtToken)); + return RMT_OK; +} + +static RmtErrorCode Poll(RmtStreamMerger* token_heap, RmtToken* out_token) +{ + RMT_ASSERT(token_heap); + RMT_ASSERT(out_token); + + Peek(token_heap, out_token); + + // remove the element + if (token_heap->current_size == 0) + { + return RMT_OK; + } + + ElementSwap(&token_heap->tokens[0], &token_heap->tokens[token_heap->current_size - 1]); + token_heap->current_size--; + ElementMoveDown(token_heap, 0); + +#ifdef VALIDATE_HEAP + RMT_ASSERT(validateHeap(token_heap, 0)); +#endif // #ifdef VALIDATE_HEAP + + return RMT_OK; +} + +static bool IsFull(const RmtStreamMerger* token_heap) +{ + RMT_ASSERT(token_heap); + return (token_heap->current_size == RMT_MAXIMUM_STREAMS); +} + +static RmtErrorCode Insert(RmtStreamMerger* token_heap, RmtToken* token) +{ + RMT_ASSERT(token_heap); + RMT_ASSERT(token); + RMT_RETURN_ON_ERROR(token_heap, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(token, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(!IsFull(token_heap), RMT_ERROR_OUT_OF_MEMORY); + + token_heap->tokens[token_heap->current_size] = token; + ElementMoveUp(token_heap, token_heap->current_size); + token_heap->current_size++; + +#ifdef VALIDATE_HEAP + RMT_ASSERT(validateHeap(token_heap, 0)); +#endif // #ifdef VALIDATE_HEAP + return RMT_OK; +} + +RmtErrorCode RmtStreamMergerInitialize(RmtStreamMerger* token_heap, RmtParser* stream_parsers, int32_t stream_parser_count) +{ + RMT_RETURN_ON_ERROR(token_heap, RMT_ERROR_INVALID_POINTER); + RMT_RETURN_ON_ERROR(stream_parser_count, RMT_ERROR_INVALID_SIZE); + RMT_RETURN_ON_ERROR(stream_parser_count < RMT_MAXIMUM_STREAMS, RMT_ERROR_INVALID_SIZE); + + token_heap->parser_count = stream_parser_count; + token_heap->current_size = 0; + token_heap->parsers = stream_parsers; + token_heap->minimum_start_timestamp = UINT64_MAX; + + RmtStreamMergerReset(token_heap); + return RMT_OK; +} + +RmtErrorCode RmtStreamMergerReset(RmtStreamMerger* token_heap) +{ + RMT_RETURN_ON_ERROR(token_heap, RMT_ERROR_INVALID_POINTER); + + token_heap->current_size = 0; + + for (int32_t current_rmt_stream_index = 0; current_rmt_stream_index < token_heap->parser_count; ++current_rmt_stream_index) + { + // reset each parser. + RmtErrorCode error_code = RmtParserReset(&token_heap->parsers[current_rmt_stream_index]); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // insert first token of each parser + RmtToken* current_token = &token_heap->buffer[current_rmt_stream_index]; + error_code = RmtParserAdvance(&token_heap->parsers[current_rmt_stream_index], current_token, NULL); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // NOTE: Only apply biasing of the KMD tokens in the advance, its unlikely to + // cause a problem for the first token out of the trap, and avoids the issue + // of the start time going negative due to the biasing. + + error_code = Insert(token_heap, current_token); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // track the minimum timestamp. + token_heap->minimum_start_timestamp = RMT_MINIMUM(token_heap->minimum_start_timestamp, current_token->common.timestamp); + } + + // reset the resource count for deterministic IDs. + if (token_heap->allocator != NULL) + { + token_heap->allocator->resource_count = 0; + } + + return RMT_OK; +} + +bool RmtStreamMergerIsEmpty(const RmtStreamMerger* token_heap) +{ + RMT_ASSERT(token_heap); + return (token_heap->current_size == 0); +} + +// recursive function to find a node by base driver ID +static ResourceIdMapNode* FindResourceNode(ResourceIdMapNode* root, uint64_t base_driver_id) +{ + if (root == nullptr) + { + return NULL; + } + + if (root->base_driver_id == base_driver_id) + { + return root; + } + + if (base_driver_id < root->base_driver_id) + { + return FindResourceNode(root->left, base_driver_id); + } + + return FindResourceNode(root->right, base_driver_id); +} + +// recursive function to insert a new node, returns the newly added node +static ResourceIdMapNode* InsertNode(ResourceIdMapAllocator* allocator, + ResourceIdMapNode* node, + uint64_t base_driver_id, + RmtResourceIdentifier* new_resource_id) +{ + if ((node == nullptr) && (allocator != nullptr)) + { + // create a new node + ResourceIdMapNode* new_node = allocator->AllocNewNode(base_driver_id); + RMT_ASSERT(new_node != nullptr); + if (new_resource_id != nullptr) + { + (*new_resource_id) = new_node->unique_id; + } + + return new_node; + } + + if (base_driver_id < node->base_driver_id) + { + node->left = InsertNode(allocator, node->left, base_driver_id, new_resource_id); + } + else if (base_driver_id > node->base_driver_id) + { + node->right = InsertNode(allocator, node->right, base_driver_id, new_resource_id); + } + else + { + // In this case, it means we're replacing a driver resource Id with a new unique Id + node->unique_id = allocator->GenUniqueId(base_driver_id); + if (new_resource_id != nullptr) + { + (*new_resource_id) = node->unique_id; + } + } + + return node; +} + +static uint64_t HashId(uint64_t base_driver_id) +{ + return Fnv1aHash(reinterpret_cast(&base_driver_id), sizeof(base_driver_id)); +} + +RmtErrorCode RmtStreamMergerAdvance(RmtStreamMerger* token_heap, RmtToken* out_token) +{ + if (RmtStreamMergerIsEmpty(token_heap)) + { + return RMT_ERROR_OUT_OF_MEMORY; + } + + // grab the next token from the heap. + RmtErrorCode error_code = Poll(token_heap, out_token); + RMT_ASSERT(error_code == RMT_OK); + RMT_RETURN_ON_ERROR(error_code == RMT_OK, error_code); + + // rebase against the minimum timestamp seen on all heaps. + out_token->common.timestamp -= token_heap->minimum_start_timestamp; + if (out_token->common.stream_index == 1) + { + out_token->common.timestamp += KMD_TIMESTAMP_BIAS; + } + + if (token_heap->allocator != nullptr) + { + switch (out_token->type) + { + case kRmtTokenTypeResourceCreate: + { + // When we see a new resource create, we want to create a new map node which will generate a unique resource ID based on our driver + // provided ID. + uint64_t base_driver_id = HashId(out_token->resource_create_token.resource_identifier); + RmtResourceIdentifier unique_id = 0; + token_heap->map_root = InsertNode(token_heap->allocator, token_heap->map_root, base_driver_id, &unique_id); + out_token->resource_create_token.resource_identifier = unique_id; + break; + } + case kRmtTokenTypeResourceBind: + { + uint64_t base_driver_id = HashId(out_token->resource_bind_token.resource_identifier); + ResourceIdMapNode* node = FindResourceNode(token_heap->map_root, base_driver_id); + out_token->resource_bind_token.resource_identifier = node != nullptr ? node->unique_id : base_driver_id; + break; + } + case kRmtTokenTypeResourceDestroy: + { + uint64_t base_driver_id = HashId(out_token->resource_destroy_token.resource_identifier); + ResourceIdMapNode* node = FindResourceNode(token_heap->map_root, base_driver_id); + out_token->resource_destroy_token.resource_identifier = node != nullptr ? node->unique_id : base_driver_id; + break; + } + + case kRmtTokenTypeUserdata: + { + if (out_token->userdata_token.userdata_type != kRmtUserdataTypeName) + { + break; + } + + const uint64_t base_driver_id = HashId(out_token->userdata_token.resource_identifer); + ResourceIdMapNode* node = FindResourceNode(token_heap->map_root, base_driver_id); + out_token->userdata_token.resource_identifer = node != nullptr ? node->unique_id : base_driver_id; + break; + } + + default: + // Do nothing for other token types + break; + } + } + + // now get the next token (if there is one) from the stream we just processed a token from, this + // will ensure there is always 1 token from each stream with outstanding tokens available in the + // heap for consideration. + RmtToken* next_token_from_stream = &token_heap->buffer[out_token->common.stream_index]; + error_code = RmtParserAdvance(&token_heap->parsers[out_token->common.stream_index], next_token_from_stream, NULL); + if (error_code == RMT_OK) + { + error_code = Insert(token_heap, next_token_from_stream); + RMT_ASSERT(error_code == RMT_OK); + } + + // EOF is a valid error code, as that's just a stream ending, anything else is probably bad. + if (error_code == RMT_EOF) + { + return RMT_OK; + } + + return error_code; +} diff --git a/source/parser/rmt_token_heap.h b/source/parser/rmt_token_heap.h new file mode 100644 index 0000000..f848ba6 --- /dev/null +++ b/source/parser/rmt_token_heap.h @@ -0,0 +1,122 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief A priority queue data structure for RmtToken structures. +//============================================================================= + +#ifndef RMV_PARSER_RMT_TOKEN_HEAP_H_ +#define RMV_PARSER_RMT_TOKEN_HEAP_H_ + +#include "rmt_error.h" +#include "rmt_file_format.h" +#include "rmt_format.h" +#include "rmt_util.h" + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +typedef struct RmtToken RmtToken; +typedef struct RmtParser RmtParser; + +/// A structure for fast lookup of unique resource ID based on a driver provided ID. +typedef struct ResourceIdMapNode +{ + uint64_t base_driver_id; + RmtResourceIdentifier unique_id; + ResourceIdMapNode* left; + ResourceIdMapNode* right; +} ResourceIdMapNode; + +/// A structure wrapping an allocation used to contain ResourceIdMapNodes +typedef struct ResourceIdMapAllocator +{ + void* allocation_base; + size_t allocation_size; + size_t bytes_used; + void* curr_offset; + uint32_t resource_count; + + RmtResourceIdentifier GenUniqueId(uint64_t base_driver_id) + { + return ((base_driver_id & 0xFFFFFFFF) << 32) | (resource_count++ & 0xFFFFFFFF); + } + + // Allocates a new node from the underlying allocation, if successful the new node will be initialized + ResourceIdMapNode* AllocNewNode(uint64_t base_driver_id) + { + ResourceIdMapNode* new_node = nullptr; + if ((bytes_used + sizeof(ResourceIdMapNode)) <= allocation_size) + { + curr_offset = static_cast(allocation_base) + bytes_used; + new_node = static_cast(curr_offset); + bytes_used += sizeof(ResourceIdMapNode); + + new_node->base_driver_id = base_driver_id; + new_node->unique_id = GenUniqueId(base_driver_id); + new_node->left = nullptr; + new_node->right = nullptr; + } + return new_node; + } +} ResourceIdMapAllocator; + +/// A structure encapsulating a priority queue data structure. +typedef struct RmtStreamMerger +{ + RmtParser* parsers; ///< A pointers to an array of RmtParser structures. + int32_t parser_count; ///< The number of parsers. + RmtToken buffer[RMT_MAXIMUM_STREAMS]; ///< An array of pointers to RmtToken structures, one per stream. + RmtToken* tokens[RMT_MAXIMUM_STREAMS]; ///< An array of pointers to structures in buffer, the pointers are organised in min-heap order. + size_t current_size; ///< The current number of token pointers used in the heap. + uint64_t minimum_start_timestamp; ///< The minimum start timestamp. + ResourceIdMapAllocator* allocator; ///< Allocator for a resource ID map, used to lookup unique ID based on driver provided resource ID. + ResourceIdMapNode* map_root; ///< Root of the resource ID mapping tree. +} RmtStreamMerger; + +/// Initialize the stream merger. +/// +/// @param [in] token_heap An RmtStreamMerger structure defining the stream merger. +/// @param [in] stream_parsers An RmtParser structure defining the stream parsers. +/// @param [in] stream_parser_count The number of stream parsers. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because token_heap was NULL. +/// @retval +/// RMT_ERROR_INVALID_SIZE The operation failed because stream_parser_count was an invalid size. +RmtErrorCode RmtStreamMergerInitialize(RmtStreamMerger* token_heap, RmtParser* stream_parsers, int32_t stream_parser_count); + +/// Clear the heap. +/// +/// @param [in] token_heap An RmtStreamMerger structure defining the stream merger. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_INVALID_POINTER The operation failed because token_heap was NULL. +RmtErrorCode RmtStreamMergerReset(RmtStreamMerger* token_heap); + +/// Return true if the heap is empty. +/// +/// @param [in] token_heap An RmtStreamMerger structure defining the stream merger. +/// @returns +/// true if stream merger is empty, false if not. +bool RmtStreamMergerIsEmpty(const RmtStreamMerger* token_heap); + +/// Get the next token from the stream merger. +/// +/// @param [in] token_heap An RmtStreamMerger structure defining the stream merger. +/// @param [out] out_token An RmtToken structure receiving the token. +/// +/// @retval +/// RMT_OK The operation completed successfully. +/// @retval +/// RMT_ERROR_OUT_OF_MEMORY The operation failed because token_heap is empty. +RmtErrorCode RmtStreamMergerAdvance(RmtStreamMerger* token_heap, RmtToken* out_token); + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_TOKEN_HEAP_H_ diff --git a/source/parser/rmt_types.h b/source/parser/rmt_types.h new file mode 100644 index 0000000..4794b9b --- /dev/null +++ b/source/parser/rmt_types.h @@ -0,0 +1,83 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author +/// \brief Definition of types used throughout the RMT code. +//============================================================================= + +#ifndef RMV_PARSER_RMT_TYPES_H_ +#define RMV_PARSER_RMT_TYPES_H_ + +#include + +#ifdef __cpluplus +extern "C" { +#endif // #ifdef __cplusplus + +/// An enumeration of different address maps. +typedef enum RmtAddressMapType +{ + kRmtAddressMapTypeVirtual = 0, ///< The virtual address map. + kRmtAddressMapTypePhysical = 1 ///< The physical address map. +} RmtAddressMapType; + +/// An enumeration of the different page sizes. +typedef enum RmtPageSize +{ + kRmtPageSizeUnmapped = 0, ///< The memory was unmapped. + kRmtPageSize4Kb = 1, ///< 4KiB page size. + kRmtPageSize64Kb = 2, ///< 64KiB page size. + kRmtPageSize256Kb = 3, ///< 256KiB page size. + kRmtPageSize1Mb = 4, ///< 1MB page size. + kRmtPageSize2Mb = 5, ///< 2MB page size. + kRmtPageSizeReserved0 = 6, ///< Reserved for future expansion. + kRmtPageSizeReserved1 = 7, ///< Reserved for future expansion. + + // add above this + kRmtPageSizeCount +} RmtPageSize; + +/// An enumeration of the heap types. +typedef enum RmtHeapType +{ + kRmtHeapTypeUnknown = -1, ///< The heap is not known. + kRmtHeapTypeLocal = 0, ///< The heap in local memory. This is mapped on the CPU. + kRmtHeapTypeInvisible = 1, ///< The heap in local memory. This is not mappable on the CPU. + kRmtHeapTypeSystem = + 2, ///< The heap is in host memory. Uncached Speculative Write Combine - it is intended for write-only data on the CPU side, it does not use the CPU cache. + kRmtHeapTypeNone = 3, ///< There is no heap used. + + kRmtHeapTypeCount +} RmtHeapType; + +/// The type of page table update operation. +typedef enum RmtPageTableUpdateType +{ + kRmtPageTableUpdateTypeDiscard = 0, ///< The physical page table entry is being discarded. + kRmtPageTableUpdateTypeUpdate = 1, ///< The physical page table entry is a regular page table mapping. + kRmtPageTableUpdateTypeTransfer = 2, ///< The physical page table entry is some memory being transfered from one pool to another. + kRmtPageTableUpdateTypeReserved = 3 ///< Reserved for future expansion. +} RmtPageTableUpdateType; + +/// The type of system driving page table updates. +typedef enum RmtPageTableController +{ + kRmtPageTableControllerOperatingSystem = 0, ///< Decisions about page table updating strategy are controlled by the OS. + kRmtPageTableControllerKmd = 1 ///< Decisions about page table updating strategy are controlled by KMD. +} RmtPageTableController; + +/// A typedef for a resource identifier. +typedef uint64_t RmtResourceIdentifier; + +/// A typedef for a queue. +typedef uint64_t RmtQueue; + +/// A typedef for an address. +typedef uint64_t RmtGpuAddress; + +/// A typedef for a process ID. +typedef uint64_t RmtProcessId; + +#ifdef __cpluplus +} +#endif // #ifdef __cplusplus +#endif // #ifndef RMV_PARSER_RMT_TYPES_H_ diff --git a/source/parser/rmt_util.h b/source/parser/rmt_util.h new file mode 100644 index 0000000..6f27aa7 --- /dev/null +++ b/source/parser/rmt_util.h @@ -0,0 +1,69 @@ +//============================================================================= +/// Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. +/// \author AMD Game Engineering Group +/// \brief Utility macro definitions. +//============================================================================= + +#ifndef RMV_PARSER_RMT_UTIL_H_ +#define RMV_PARSER_RMT_UTIL_H_ + +#include "rmt_types.h" + +/// Helper macro to avoid warnings about unused variables. +#define RMT_UNUSED(x) ((void)(x)) + +/// Helper macro to align an integer to the specified power of 2 boundary +#define RMT_ALIGN_UP(x, y) (((x) + ((y)-1)) & ~((y)-1)) + +/// Helper macro to check if a value is aligned. +#define RMT_IS_ALIGNED(x) (((x) != 0) && ((x) & ((x)-1))) + +/// Helper macro to stringify a value. +#define RMT_STR(s) RMT_XSTR(s) +#define RMT_XSTR(s) #s + +/// Helper macro to forward declare a structure. +#define RMT_FORWARD_DECLARE(x) typedef struct x x + +/// Helper macro to return the maximum of two values. +#define RMT_MAXIMUM(x, y) (((x) > (y)) ? (x) : (y)) + +/// Helper macro to return the minimum of two values. +#define RMT_MINIMUM(x, y) (((x) < (y)) ? (x) : (y)) + +/// Helper macro to do safe free on a pointer. +#define RMT_SAFE_FREE(x) \ + if (x) \ + free(x) + +/// Helper macro to return the abs of an integer value. +#define RMT_ABSOLUTE(x) (((x) < 0) ? (-(x)) : (x)) + +/// Helper macro to return sign of a value. +#define RMT_SIGN(x) (((x) < 0) ? -1 : 1) + +/// Helper macro to work out the number of elements in an array. +#define RMT_ARRAY_ELEMENTS(x) (int32_t)((sizeof(x) / sizeof(0 [x])) / ((size_t)(!(sizeof(x) % sizeof(0 [x]))))) + +/// Inline FNV1a hashing function. See (http://www.isthe.com/chongo/tech/comp/fnv/) +static inline uint32_t Fnv1aHash(const uint8_t* data, size_t data_size) +{ + uint32_t hash = 0; + + if (data != nullptr) + { + static constexpr uint32_t kFnvPrime = 16777619U; + static constexpr uint32_t kFnvOffset = 2166136261U; + + hash = kFnvOffset; + + for (uint32_t i = 0; i < data_size; i++) + { + hash ^= static_cast(data[i]); + hash *= kFnvPrime; + } + } + return hash; +} + +#endif // #ifndef RMV_PARSER_RMT_UTIL_H_ diff --git a/source/third_party/pevents/LICENSE b/source/third_party/pevents/LICENSE new file mode 100644 index 0000000..8a92787 --- /dev/null +++ b/source/third_party/pevents/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2011 - 2015 by NeoSmart Technologies + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/source/third_party/pevents/README b/source/third_party/pevents/README new file mode 100644 index 0000000..193fc4e --- /dev/null +++ b/source/third_party/pevents/README @@ -0,0 +1,79 @@ +pevents is a cross-platform C++ library meant to provide an +implementation of the WIN32 events for POSIX systems. pevents is built +on pthreads and provides *most* of the functionality of both manual- +and auto-reset events on Windows, most-notably including simultaneous +waits on multiple events (à la WaitForMultipleObjects). pevents is +also free of spurious wakeups - returns from waits are guaranteed +correct. + +pevents also doubles as a thin, sane wrapper for CreateEvent() & co. on +Windows, meaning you can use pevents directly in your cross-platform +code without #ifdefs for Windows/pthreads. + +pevents is developed and maintained by Mahmoud Al-Qudsi + of NeoSmart Technologies + +While POSIX condition variables (pthread_cond_t) and WIN32 events both +provide the essential building blocks of the synchronization primitives +required to write multithreaded code with signaling, the nature of the +differences between the two have lent their way towards creating +different synchronization and multithreaded-programming paradigms. + +Developers accustomed to WIN32 events might have a hard time +transitioning to condition variables; pevents aims to ease the +transition for Windows developers looking to write multithreaded code on +*nix by providing a familiar synchronization primitive that will allow +them to duplicate the essential features of WIN32 auto/manual-reset +events. + +As mentioned earlier, pevents provides most of the functionality of +WIN32 events. The only features not included are only named events and +support for security attributes. + +Usage: + +pevents comes with two APIs: one along the lines of WIN32 functions and +the other for those more comfortable with pthread functions. They are +identical underneath the hood. + +WIN32-style pevents API: + + neosmart_event_t CreateEvent(bool manualReset, bool initialState); + + int DestroyEvent(neosmart_event_t event); + + int WaitForEvent(neosmart_event_t event, uint64_t milliseconds); + + int WaitForMultipleEvents(neosmart_event_t *events, int count, + bool waitAll, uint64_t milliseconds); + + int WaitForMultipleEvents(neosmart_event_t *events, int count, + bool waitAll, uint64_t milliseconds, int &index); + + int SetEvent(neosmart_event_t event); + + int ResetEvent(neosmart_event_t event); + + int PulseEvent(neosmart_event_t event); + +All the code is contained within pevents.cpp and pevents.h. You should +include these two files in your project as needed. All functions are in +the neosmart namespace. (Windows users should use win32.cpp instead of +pevents.cpp) + +Compilation options: + +The following preprocessor definitions may be defined (-DOPTION) at +compile time to enable different features. + +WFMO: Enables WFMO support in pevents. It is recommended to only compile +with WFMO support if you are taking advantage of the +WaitForMultipleEvents function, as it adds a (small) overhead to all +event objects. + +PULSE: Enables the PulseEvent function. PulseEvent() on Windows is +fundamentally broken and should not be relied upon — it will almost +never do what you think you're doing when you call it. pevents includes +this function only to make porting existing (flawed) code from WIN32 to +*nix platforms easier, and this function is not compiled into pevents by +default. diff --git a/source/third_party/pevents/pevents.cpp b/source/third_party/pevents/pevents.cpp new file mode 100644 index 0000000..b8c2296 --- /dev/null +++ b/source/third_party/pevents/pevents.cpp @@ -0,0 +1,557 @@ +/* + * WIN32 Events for POSIX + * Author: Mahmoud Al-Qudsi + * Copyright (C) 2011 - 2015 by NeoSmart Technologies + * This code is released under the terms of the MIT License +*/ + +#ifndef _WIN32 + +#include "pevents.h" +#include +#include +#include +#include +#ifdef WFMO +#include +#include +#endif + +#define UNUSED(x) ((void)(x)) + +namespace neosmart +{ +#ifdef WFMO + //Each call to WaitForMultipleObjects initializes a neosmart_wfmo_t object which tracks + //the progress of the caller's multi-object wait and dispatches responses accordingly. + //One neosmart_wfmo_t struct is shared for all events in a single WFMO call + struct neosmart_wfmo_t_ + { + pthread_mutex_t Mutex; + pthread_cond_t CVariable; + int RefCount; + union + { + int FiredEvent; //WFSO + int EventsLeft; //WFMO + } Status; + bool WaitAll; + bool StillWaiting; + + void Destroy() + { + pthread_mutex_destroy(&Mutex); + pthread_cond_destroy(&CVariable); + } + }; + typedef neosmart_wfmo_t_ *neosmart_wfmo_t; + + //A neosmart_wfmo_info_t object is registered with each event waited on in a WFMO + //This reference to neosmart_wfmo_t_ is how the event knows whom to notify when triggered + struct neosmart_wfmo_info_t_ + { + neosmart_wfmo_t Waiter; + int WaitIndex; + }; + typedef neosmart_wfmo_info_t_ *neosmart_wfmo_info_t; +#endif + + //The basic event structure, passed to the caller as an opaque pointer when creating events + struct neosmart_event_t_ + { + pthread_cond_t CVariable; + pthread_mutex_t Mutex; + bool AutoReset; + bool State; +#ifdef WFMO + std::deque RegisteredWaits; +#endif + }; + +#ifdef WFMO + bool RemoveExpiredWaitHelper(neosmart_wfmo_info_t_ wait) + { + int result = pthread_mutex_trylock(&wait.Waiter->Mutex); + + if (result == EBUSY) + { + return false; + } + + assert(result == 0); + + if (wait.Waiter->StillWaiting == false) + { + --wait.Waiter->RefCount; + assert(wait.Waiter->RefCount >= 0); + if (wait.Waiter->RefCount == 0) + { + wait.Waiter->Destroy(); + delete wait.Waiter; + } + else + { + result = pthread_mutex_unlock(&wait.Waiter->Mutex); + assert(result == 0); + } + + return true; + } + + result = pthread_mutex_unlock(&wait.Waiter->Mutex); + assert(result == 0); + + return false; + } +#endif + + neosmart_event_t CreateEvent(bool manualReset, bool initialState) + { + neosmart_event_t event = new neosmart_event_t_; + + int result = pthread_cond_init(&event->CVariable, 0); + UNUSED(result); + assert(result == 0); + + result = pthread_mutex_init(&event->Mutex, 0); + assert(result == 0); + + event->State = false; + event->AutoReset = !manualReset; + + if (initialState) + { + result = SetEvent(event); + assert(result == 0); + } + + return event; + } + + int UnlockedWaitForEvent(neosmart_event_t event, uint64_t milliseconds) + { + int result = 0; + if (!event->State) + { + //Zero-timeout event state check optimization + if (milliseconds == 0) + { + return WAIT_TIMEOUT; + } + + timespec ts; + if (milliseconds != (uint64_t) -1) + { + timeval tv; + gettimeofday(&tv, NULL); + + uint64_t nanoseconds = ((uint64_t) tv.tv_sec) * 1000 * 1000 * 1000 + milliseconds * 1000 * 1000 + ((uint64_t) tv.tv_usec) * 1000; + + ts.tv_sec = nanoseconds / 1000 / 1000 / 1000; + ts.tv_nsec = (nanoseconds - ((uint64_t) ts.tv_sec) * 1000 * 1000 * 1000); + } + + do + { + //Regardless of whether it's an auto-reset or manual-reset event: + //wait to obtain the event, then lock anyone else out + if (milliseconds != (uint64_t) -1) + { + result = pthread_cond_timedwait(&event->CVariable, &event->Mutex, &ts); + } + else + { + result = pthread_cond_wait(&event->CVariable, &event->Mutex); + } + } while (result == 0 && !event->State); + + if (result == 0 && event->AutoReset) + { + //We've only accquired the event if the wait succeeded + event->State = false; + } + } + else if (event->AutoReset) + { + //It's an auto-reset event that's currently available; + //we need to stop anyone else from using it + result = 0; + event->State = false; + } + //Else we're trying to obtain a manual reset event with a signaled state; + //don't do anything + + return result; + } + + int WaitForEvent(neosmart_event_t event, uint64_t milliseconds) + { + int tempResult; + if (milliseconds == 0) + { + tempResult = pthread_mutex_trylock(&event->Mutex); + if (tempResult == EBUSY) + { + return WAIT_TIMEOUT; + } + } + else + { + tempResult = pthread_mutex_lock(&event->Mutex); + } + + assert(tempResult == 0); + + int result = UnlockedWaitForEvent(event, milliseconds); + + tempResult = pthread_mutex_unlock(&event->Mutex); + assert(tempResult == 0); + + return result; + } + +#ifdef WFMO + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds) + { + int unused; + return WaitForMultipleEvents(events, count, waitAll, milliseconds, unused); + } + + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds, int &waitIndex) + { + neosmart_wfmo_t wfmo = new neosmart_wfmo_t_; + + int result = 0; + int tempResult = pthread_mutex_init(&wfmo->Mutex, 0); + assert(tempResult == 0); + + tempResult = pthread_cond_init(&wfmo->CVariable, 0); + assert(tempResult == 0); + + neosmart_wfmo_info_t_ waitInfo; + waitInfo.Waiter = wfmo; + waitInfo.WaitIndex = -1; + + wfmo->WaitAll = waitAll; + wfmo->StillWaiting = true; + wfmo->RefCount = 1; + + if (waitAll) + { + wfmo->Status.EventsLeft = count; + } + else + { + wfmo->Status.FiredEvent = -1; + } + + tempResult = pthread_mutex_lock(&wfmo->Mutex); + assert(tempResult == 0); + + bool done = false; + waitIndex = -1; + + for (int i = 0; i < count; ++i) + { + waitInfo.WaitIndex = i; + + //Must not release lock until RegisteredWait is potentially added + tempResult = pthread_mutex_lock(&events[i]->Mutex); + assert(tempResult == 0); + + //Before adding this wait to the list of registered waits, let's clean up old, expired waits while we have the event lock anyway + events[i]->RegisteredWaits.erase(std::remove_if (events[i]->RegisteredWaits.begin(), events[i]->RegisteredWaits.end(), RemoveExpiredWaitHelper), events[i]->RegisteredWaits.end()); + + if (UnlockedWaitForEvent(events[i], 0) == 0) + { + tempResult = pthread_mutex_unlock(&events[i]->Mutex); + assert(tempResult == 0); + + if (waitAll) + { + --wfmo->Status.EventsLeft; + assert(wfmo->Status.EventsLeft >= 0); + } + else + { + wfmo->Status.FiredEvent = i; + waitIndex = i; + done = true; + break; + } + } + else + { + events[i]->RegisteredWaits.push_back(waitInfo); + ++wfmo->RefCount; + + tempResult = pthread_mutex_unlock(&events[i]->Mutex); + assert(tempResult == 0); + } + } + + timespec ts; + if (!done) + { + if (milliseconds == 0) + { + result = WAIT_TIMEOUT; + done = true; + } + else if (milliseconds != (uint64_t) -1) + { + timeval tv; + gettimeofday(&tv, NULL); + + uint64_t nanoseconds = ((uint64_t) tv.tv_sec) * 1000 * 1000 * 1000 + milliseconds * 1000 * 1000 + ((uint64_t) tv.tv_usec) * 1000; + + ts.tv_sec = nanoseconds / 1000 / 1000 / 1000; + ts.tv_nsec = (nanoseconds - ((uint64_t) ts.tv_sec) * 1000 * 1000 * 1000); + } + } + + while (!done) + { + //One (or more) of the events we're monitoring has been triggered? + + //If we're waiting for all events, assume we're done and check if there's an event that hasn't fired + //But if we're waiting for just one event, assume we're not done until we find a fired event + done = (waitAll && wfmo->Status.EventsLeft == 0) || (!waitAll && wfmo->Status.FiredEvent != -1); + + if (!done) + { + if (milliseconds != (uint64_t) -1) + { + result = pthread_cond_timedwait(&wfmo->CVariable, &wfmo->Mutex, &ts); + } + else + { + result = pthread_cond_wait(&wfmo->CVariable, &wfmo->Mutex); + } + + if (result != 0) + { + break; + } + } + } + + waitIndex = wfmo->Status.FiredEvent; + wfmo->StillWaiting = false; + + --wfmo->RefCount; + assert(wfmo->RefCount >= 0); + if (wfmo->RefCount == 0) + { + wfmo->Destroy(); + delete wfmo; + } + else + { + tempResult = pthread_mutex_unlock(&wfmo->Mutex); + assert(tempResult == 0); + } + + return result; + } +#endif + + int DestroyEvent(neosmart_event_t event) + { + int result = 0; + UNUSED(result); + +#ifdef WFMO + result = pthread_mutex_lock(&event->Mutex); + assert(result == 0); + event->RegisteredWaits.erase(std::remove_if (event->RegisteredWaits.begin(), event->RegisteredWaits.end(), RemoveExpiredWaitHelper), event->RegisteredWaits.end()); + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); +#endif + + result = pthread_cond_destroy(&event->CVariable); + assert(result == 0); + + result = pthread_mutex_destroy(&event->Mutex); + assert(result == 0); + + delete event; + + return 0; + } + + int SetEvent(neosmart_event_t event) + { + int result = pthread_mutex_lock(&event->Mutex); + UNUSED(result); + assert(result == 0); + + event->State = true; + + //Depending on the event type, we either trigger everyone or only one + if (event->AutoReset) + { +#ifdef WFMO + while (!event->RegisteredWaits.empty()) + { + neosmart_wfmo_info_t i = &event->RegisteredWaits.front(); + + result = pthread_mutex_lock(&i->Waiter->Mutex); + assert(result == 0); + + --i->Waiter->RefCount; + assert(i->Waiter->RefCount >= 0); + if (!i->Waiter->StillWaiting) + { + if (i->Waiter->RefCount == 0) + { + i->Waiter->Destroy(); + delete i->Waiter; + } + else + { + result = pthread_mutex_unlock(&i->Waiter->Mutex); + assert(result == 0); + } + event->RegisteredWaits.pop_front(); + continue; + } + + event->State = false; + + if (i->Waiter->WaitAll) + { + --i->Waiter->Status.EventsLeft; + assert(i->Waiter->Status.EventsLeft >= 0); + //We technically should do i->Waiter->StillWaiting = Waiter->Status.EventsLeft != 0 + //but the only time it'll be equal to zero is if we're the last event, so no one + //else will be checking the StillWaiting flag. We're good to go without it. + } + else + { + i->Waiter->Status.FiredEvent = i->WaitIndex; + i->Waiter->StillWaiting = false; + } + + result = pthread_mutex_unlock(&i->Waiter->Mutex); + assert(result == 0); + + result = pthread_cond_signal(&i->Waiter->CVariable); + assert(result == 0); + + event->RegisteredWaits.pop_front(); + + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); + + return 0; + } +#endif + //event->State can be false if compiled with WFMO support + if (event->State) + { + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); + + result = pthread_cond_signal(&event->CVariable); + assert(result == 0); + + return 0; + } + } + else + { +#ifdef WFMO + for (size_t i = 0; i < event->RegisteredWaits.size(); ++i) + { + neosmart_wfmo_info_t info = &event->RegisteredWaits[i]; + + result = pthread_mutex_lock(&info->Waiter->Mutex); + assert(result == 0); + + --info->Waiter->RefCount; + assert(info->Waiter->RefCount >= 0); + + if (!info->Waiter->StillWaiting) + { + if (info->Waiter->RefCount == 0) + { + info->Waiter->Destroy(); + delete info->Waiter; + } + else + { + result = pthread_mutex_unlock(&info->Waiter->Mutex); + assert(result == 0); + } + continue; + } + + if (info->Waiter->WaitAll) + { + --info->Waiter->Status.EventsLeft; + assert(info->Waiter->Status.EventsLeft >= 0); + //We technically should do i->Waiter->StillWaiting = Waiter->Status.EventsLeft != 0 + //but the only time it'll be equal to zero is if we're the last event, so no one + //else will be checking the StillWaiting flag. We're good to go without it. + } + else + { + info->Waiter->Status.FiredEvent = info->WaitIndex; + info->Waiter->StillWaiting = false; + } + + result = pthread_mutex_unlock(&info->Waiter->Mutex); + assert(result == 0); + + result = pthread_cond_signal(&info->Waiter->CVariable); + assert(result == 0); + } + event->RegisteredWaits.clear(); +#endif + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); + + result = pthread_cond_broadcast(&event->CVariable); + assert(result == 0); + } + + return 0; + } + + int ResetEvent(neosmart_event_t event) + { + int result = pthread_mutex_lock(&event->Mutex); + UNUSED(result); + assert(result == 0); + + event->State = false; + + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); + + return 0; + } + +#ifdef PULSE + int PulseEvent(neosmart_event_t event) + { + //This may look like it's a horribly inefficient kludge with the sole intention of reducing code duplication, + //but in reality this is what any PulseEvent() implementation must look like. The only overhead (function + //calls aside, which your compiler will likely optimize away, anyway), is if only WFMO auto-reset waits are active + //there will be overhead to unnecessarily obtain the event mutex for ResetEvent() after. In all other cases (being + //no pending waits, WFMO manual-reset waits, or any WFSO waits), the event mutex must first be released for the + //waiting thread to resume action prior to locking the mutex again in order to set the event state to unsignaled, + //or else the waiting threads will loop back into a wait (due to checks for spurious CVariable wakeups). + + int result = SetEvent(event); + assert(result == 0); + result = ResetEvent(event); + assert(result == 0); + + return 0; + } +#endif +} + +#endif //_WIN32 diff --git a/source/third_party/pevents/pevents.h b/source/third_party/pevents/pevents.h new file mode 100644 index 0000000..bf05163 --- /dev/null +++ b/source/third_party/pevents/pevents.h @@ -0,0 +1,42 @@ +/* + * WIN32 Events for POSIX + * Author: Mahmoud Al-Qudsi + * Copyright (C) 2011 - 2015 by NeoSmart Technologies + * This code is released under the terms of the MIT License +*/ + +#pragma once + +#if defined(_WIN32) && !defined(CreateEvent) +#error Must include Windows.h prior to including pevents.h! +#endif +#ifndef WAIT_TIMEOUT +#include +#define WAIT_TIMEOUT ETIMEDOUT +#endif + +#include + +namespace neosmart +{ + //Type declarations + struct neosmart_event_t_; + typedef neosmart_event_t_ * neosmart_event_t; + + //WIN32-style functions + neosmart_event_t CreateEvent(bool manualReset = false, bool initialState = false); + int DestroyEvent(neosmart_event_t event); + int WaitForEvent(neosmart_event_t event, uint64_t milliseconds = -1); + int SetEvent(neosmart_event_t event); + int ResetEvent(neosmart_event_t event); +#ifdef WFMO + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds); + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds, int &index); +#endif +#ifdef PULSE + int PulseEvent(neosmart_event_t event); +#endif + + //POSIX-style functions + //TBD +} diff --git a/source/third_party/pevents/sample.cpp b/source/third_party/pevents/sample.cpp new file mode 100644 index 0000000..6a5b74f --- /dev/null +++ b/source/third_party/pevents/sample.cpp @@ -0,0 +1,131 @@ +/* + * WIN32 Events for POSIX + * Author: Mahmoud Al-Qudsi + * Copyright (C) 2011 - 2015 by NeoSmart Technologies + * This code is released under the terms of the MIT License +*/ + +#include +#include +#include +#include +#include +#include +#include +//On Windows, you must include Winbase.h/Synchapi.h/Windows.h before pevents.h +#ifdef _WIN32 +#include +#endif +#include "pevents.h" + +using namespace neosmart; +using namespace std; + +neosmart_event_t events[3]; //letters, numbers, abort +std::atomic interrupted { false }; +char letter = '?'; +int number = -1; + +char lastChar = '\0'; +int lastNum = -1; + +void intHandler(int sig) { + interrupted = true; + //unfortunately you can't use SetEvent here because posix signal handlers + //shouldn't use any non-reentrant code (like printf) + //on x86/x64, std::atomic is just a fancy way of doing a memory + //barrier and nothing more, so it is safe +} + +void letters() +{ + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dis(0, 3000); + for (uint32_t i = 0; WaitForEvent(events[2], dis(gen)) == WAIT_TIMEOUT; ++i) + { + letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i%26]; + SetEvent(events[0]); + } +} + +void numbers() +{ + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dis(0, 3000); + for (uint32_t i = 0; WaitForEvent(events[2], dis(gen)) == WAIT_TIMEOUT; ++i) + { + number = i; + SetEvent(events[1]); + } +} + +int main() +{ + events[0] = CreateEvent(); //letters auto-reset event + events[1] = CreateEvent(); //numbers auto-reset event + events[2] = CreateEvent(true); //abort manual-reset event + + //after the abort event has been created + struct sigaction act = {0}; + act.sa_handler = intHandler; //trigger abort on ctrl+c + sigaction(SIGINT, &act, NULL); + + std::thread thread1(letters); + std::thread thread2(numbers); + + for (uint32_t i = 0; lastChar != 'Z'; ++i) + { + if (interrupted) + { + printf("Interrupt triggered.. Aborting!\n"); + break; + } + + int index = -1; + int result = WaitForMultipleEvents(events, 2, false, -1, index); + + if (result == WAIT_TIMEOUT) + { + cout << "Timeout!" << endl; + } + else if (result != 0) + { + cout << "Error in wait!" << endl; + } + else if (index == 0) + { + assert(lastChar != letter); + cout << letter << endl; + lastChar = letter; + } + else if (index == 1) + { + assert(lastNum != number); + cout << number << endl; + lastNum = number; + } + else + { + cout << "ERROR! Unexpected index: " << index << endl; + exit(-1); + } + } + + //You can't just DestroyEvent() and exit - it'll segfault + //That's because letters() and numbers() will call SetEvent on a destroyed event + //You must *never* call SetEvent/ResetEvent on a destroyed event! + //So we set an abort event and wait for the helper threads to exit + + //Set the abort + SetEvent(events[2]); + + thread1.join(); + thread2.join(); + + for (auto event : events) + { + DestroyEvent(event); + } +} diff --git a/source/third_party/pevents/win32.cpp b/source/third_party/pevents/win32.cpp new file mode 100644 index 0000000..b8c786e --- /dev/null +++ b/source/third_party/pevents/win32.cpp @@ -0,0 +1,135 @@ +/* + * WIN32 Events for POSIX + * Author: Mahmoud Al-Qudsi + * Copyright (C) 2011 - 2015 by NeoSmart Technologies + * This code is released under the terms of the MIT License +*/ + +#ifdef _WIN32 + +#include +#include "pevents.h" + +namespace neosmart +{ + neosmart_event_t CreateEvent(bool manualReset, bool initialState) + { + return static_cast(::CreateEvent(NULL, manualReset, initialState, NULL)); + } + + int DestroyEvent(neosmart_event_t event) + { + HANDLE handle = static_cast(event); + return CloseHandle(handle) ? 0 : GetLastError(); + } + + int WaitForEvent(neosmart_event_t event, uint64_t milliseconds) + { + uint32_t result = 0; + HANDLE handle = static_cast(event); + + //WaitForSingleObject(Ex) and WaitForMultipleObjects(Ex) only support 32-bit timeout + if (milliseconds == ((uint64_t) -1) || (milliseconds >> 32) == 0) + { + result = WaitForSingleObject(handle, static_cast(milliseconds)); + } + else + { + //Cannot wait for 0xFFFFFFFF because that means infinity to WIN32 + uint32_t waitUnit = (INFINITE - 1); + uint64_t rounds = milliseconds / waitUnit; + uint32_t remainder = milliseconds % waitUnit; + + uint32_t result = WaitForSingleObject(handle, remainder); + while (result == WAIT_TIMEOUT && rounds-- != 0) + { + result = WaitForSingleObject(handle, waitUnit); + } + } + + if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED) + { + //We must swallow WAIT_ABANDONED because there is no such equivalent on *nix + return 0; + } + + if (result == WAIT_TIMEOUT) + { + return WAIT_TIMEOUT; + } + + return GetLastError(); + } + + int SetEvent(neosmart_event_t event) + { + HANDLE handle = static_cast(event); + return ::SetEvent(handle) ? 0 : GetLastError(); + } + + int ResetEvent(neosmart_event_t event) + { + HANDLE handle = static_cast(event); + return ::ResetEvent(handle) ? 0 : GetLastError(); + } + +#ifdef WFMO + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds) + { + int index = 0; + return WaitForMultipleEvents(events, count, waitAll, milliseconds, index); + } + + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds, int &index) + { + HANDLE *handles = reinterpret_cast(events); + uint32_t result = 0; + + //WaitForSingleObject(Ex) and WaitForMultipleObjects(Ex) only support 32-bit timeout + if (milliseconds == ((uint64_t) -1) || (milliseconds >> 32) == 0) + { + result = WaitForMultipleObjects(count, handles, waitAll, static_cast(milliseconds)); + } + else + { + //Cannot wait for 0xFFFFFFFF because that means infinity to WIN32 + uint32_t waitUnit = (INFINITE - 1); + uint64_t rounds = milliseconds / waitUnit; + uint32_t remainder = milliseconds % waitUnit; + + uint32_t result = WaitForMultipleObjects(count, handles, waitAll, remainder); + while (result == WAIT_TIMEOUT && rounds-- != 0) + { + result = WaitForMultipleObjects(count, handles, waitAll, waitUnit); + } + } + + if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + count) + { + index = result - WAIT_OBJECT_0; + return 0; + } + else if (result >= WAIT_ABANDONED_0 && result < WAIT_ABANDONED_0 + count) + { + index = result - WAIT_ABANDONED_0; + return 0; + } + + if (result == WAIT_FAILED) + { + return GetLastError(); + } + return result; + } +#endif + +#ifdef PULSE + int PulseEvent(neosmart_event_t event) + { + HANDLE handle = static_cast(event); + return ::PulseEvent(handle) ? 0 : GetLastError(); + } +#endif +} + +#endif //_WIN32