diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/.gitignore b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/.gitignore new file mode 100644 index 000000000..96e03d03f --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/.gitignore @@ -0,0 +1,12 @@ +.vs/ + +Debug/ +Release/ + +x64/ + +packages/ + +Generated Files/ + +AppPackages/ diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.cpp b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.cpp new file mode 100644 index 000000000..624d86804 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.cpp @@ -0,0 +1,57 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#include "pch.h" +#include "AnimationPage.h" +#if __has_include("AnimationPage.g.cpp") +#include "AnimationPage.g.cpp" +#endif + +using namespace winrt; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls::Primitives; + +namespace winrt::DynamicRefreshRateTool::implementation +{ + const float DEFAULT_ROTATION_SPEED = 3600; + + AnimationPage::AnimationPage() + { + InitializeComponent(); + myStoryBoard().Begin(); + } + + void AnimationPage::Button_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e) + { + auto buttonClicked = sender.try_as(); + + for (auto button : { Button1(), Button2(), Button3() }) { + if ((buttonClicked == button) != button.IsChecked().GetBoolean()) { + button.OnToggle(); + } + } + + if (Button1().IsChecked().GetBoolean()) { + RotationAnimation().To(DEFAULT_ROTATION_SPEED / 2); + } + + if (Button2().IsChecked().GetBoolean()) { + RotationAnimation().To(DEFAULT_ROTATION_SPEED); + } + + if (Button3().IsChecked().GetBoolean()) { + RotationAnimation().To(DEFAULT_ROTATION_SPEED * 2); + } + } +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.h b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.h new file mode 100644 index 000000000..bdd410e83 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.h @@ -0,0 +1,34 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#pragma once + +#include "AnimationPage.g.h" + +namespace winrt::DynamicRefreshRateTool::implementation +{ + struct AnimationPage : AnimationPageT + { + AnimationPage(); + + void Button_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); + }; +} + +namespace winrt::DynamicRefreshRateTool::factory_implementation +{ + struct AnimationPage : AnimationPageT + { + }; +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.idl b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.idl new file mode 100644 index 000000000..2a498bda0 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.idl @@ -0,0 +1,22 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +namespace DynamicRefreshRateTool +{ + [default_interface] + runtimeclass AnimationPage : Microsoft.UI.Xaml.Controls.Page + { + AnimationPage(); + } +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.xaml b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.xaml new file mode 100644 index 000000000..aad6bede1 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/AnimationPage.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x0.5 Speed + x1 Speed + x2 Speed + + + + + + + + + + diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.idl b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.idl new file mode 100644 index 000000000..3a52f73ac --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.idl @@ -0,0 +1,17 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +namespace DynamicRefreshRateTool +{ +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml new file mode 100644 index 000000000..d849f1495 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + #20A0A0A0 + #20A0A0A0 + + + + + + + + + + + + + + + + + diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml.cpp b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml.cpp new file mode 100644 index 000000000..1f835f0c7 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml.cpp @@ -0,0 +1,67 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#include "pch.h" + +#include "App.xaml.h" +#include "MainWindow.xaml.h" + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Microsoft::UI::Xaml::Navigation; +using namespace DynamicRefreshRateTool; +using namespace DynamicRefreshRateTool::implementation; +using namespace Windows::UI::ViewManagement; + +/// +/// Initializes the singleton application object. This is the first line of authored code +/// executed, and as such is the logical equivalent of main() or WinMain(). +/// +App::App() +{ + InitializeComponent(); + +#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION + UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e) + { + if (IsDebuggerPresent()) + { + auto errorMessage = e.Message(); + __debugbreak(); + } + }); +#endif +} + +/// +/// Invoked when the application is launched normally by the end user. Other entry points +/// will be used such as when the application is launched to open a specific file. +/// +/// Details about the launch request and process. +void App::OnLaunched(LaunchActivatedEventArgs const&) +{ + window = make(); + + // Set default window size on startup + HWND hwnd{ nullptr }; + window.try_as()->get_WindowHandle(&hwnd); + auto winid = winrt::Microsoft::UI::GetWindowIdFromWindow(hwnd); + auto appWindow = winrt::Microsoft::UI::Windowing::AppWindow::GetFromWindowId(winid); + double scale = GetDpiForWindow(hwnd) / 96.0; + appWindow.Resize({ static_cast(800 * scale), static_cast(600 * scale) }); + + window.Activate(); +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml.h b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml.h new file mode 100644 index 000000000..e5f45e356 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/App.xaml.h @@ -0,0 +1,30 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#pragma once + +#include "App.xaml.g.h" + +namespace winrt::DynamicRefreshRateTool::implementation +{ + struct App : AppT + { + App(); + + void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&); + + private: + winrt::Microsoft::UI::Xaml::Window window{ nullptr }; + }; +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/LockScreenLogo.scale-200.png b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 000000000..dcfd9bf7b Binary files /dev/null and b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/LockScreenLogo.scale-200.png differ diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/SplashScreen.scale-200.png b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/SplashScreen.scale-200.png new file mode 100644 index 000000000..2adfbd745 Binary files /dev/null and b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/SplashScreen.scale-200.png differ diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square150x150Logo.scale-200.png b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 000000000..e8bde6352 Binary files /dev/null and b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square150x150Logo.scale-200.png differ diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square44x44Logo.scale-200.png b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 000000000..b11f0951f Binary files /dev/null and b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square44x44Logo.scale-200.png differ diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 000000000..b0bf1927d Binary files /dev/null and b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/StoreLogo.png b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/StoreLogo.png new file mode 100644 index 000000000..8a55cd73c Binary files /dev/null and b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/StoreLogo.png differ diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Wide310x150Logo.scale-200.png b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 000000000..bae3ec61a Binary files /dev/null and b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Assets/Wide310x150Logo.scale-200.png differ diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.cpp b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.cpp new file mode 100644 index 000000000..6edc04d62 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.cpp @@ -0,0 +1,161 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#include "pch.h" +#include "ChartPage.h" +#if __has_include("ChartPage.g.cpp") +#include "ChartPage.g.cpp" +#endif + +using namespace winrt; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Windows::Foundation; + +namespace winrt::DynamicRefreshRateTool::implementation +{ + ChartPage::ChartPage() + { + InitializeComponent(); + } + + float ChartPage::QpcTimeToXCoordinate(int64_t qpcTime) + { + if (m_fpsHistory.empty()) + { + return 0; + } + + float width = static_cast(FpsCanvas().ActualWidth()); + float relativeTime = static_cast(m_fpsHistory.back().tick - qpcTime) / RefreshRateMeter::Instance().GetFrequency(); + return width - relativeTime * width / m_displayHistorySeconds; + } + + float ChartPage::FpsToYCoordinate(float fps) + { + float height = static_cast(FpsCanvas().ActualHeight()); + return height - fps / m_maxFps / 1.3f * height; + } + + void ChartPage::RefreshChart() + { + + m_fpsHistory = RefreshRateMeter::Instance().GetRecentHistory(0, m_displayHistorySeconds * RefreshRateMeter::Instance().GetFrequency(), m_aggregationSize); + + if (m_fpsHistory.empty()) + { + return; + } + + m_maxFps *= 0.95; + for (auto& fpsValue : m_fpsHistory) + { + m_maxFps = max(m_maxFps, fpsValue.refreshRate); + } + + RefreshDashedLines(); + + auto points = FpsChart().Points(); + + points.Clear(); + + Point firstPoint = { 0, FpsToYCoordinate(m_fpsHistory[0].refreshRate) }; + points.Append(firstPoint); + + for (auto& fpsValue : m_fpsHistory) + { + points.Append({ QpcTimeToXCoordinate(fpsValue.tick), FpsToYCoordinate(fpsValue.refreshRate) }); + } + + points.Append({ (float)FpsCanvas().ActualWidth(), FpsToYCoordinate(m_fpsHistory.back().refreshRate) }); + + points.Append({ (float)FpsCanvas().ActualWidth() + 10, (float)FpsCanvas().ActualHeight() + 10 }); + points.Append({ -10, (float)FpsCanvas().ActualHeight() + 10 }); + + RefreshTooltip(); + } + + void ChartPage::RefreshDashedLines() + { + DashLine().Points().Clear(); + + bool leftToRight = 0; + for (float level : {60.0f, 120.0f, 240.0f}) + { + float y = FpsToYCoordinate(level); + + if (y < 0) continue; + + DashLine().Points().Append({ leftToRight ? -10.0f : (float)FpsCanvas().ActualWidth() + 10.0f, y }); + DashLine().Points().Append({ !leftToRight ? -10.0f : (float)FpsCanvas().ActualWidth() + 10.0f, y }); + + leftToRight = !leftToRight; + } + } + + void ChartPage::RefreshTooltip() + { + // Place tooltip offscreen + FpsCanvas().SetLeft(FpsTooltip(), -1000); + FpsCanvas().SetLeft(FpsIndicator(), -1000); + + if (!m_mousePosition.has_value()) + { + return; + } + + float nearestDistance = 1e9; + RefreshRateMeter::DataPoint nearestFpsValue = { 0, 0 }; + + for (auto fpsValue : m_fpsHistory) + { + float distance = fabs(QpcTimeToXCoordinate(fpsValue.tick) - m_mousePosition->X); + if (distance < nearestDistance) + { + nearestDistance = distance; + nearestFpsValue = fpsValue; + } + } + + // Place tooltip to new coordinbates + float y = FpsToYCoordinate(nearestFpsValue.refreshRate); + FpsCanvas().SetLeft(FpsTooltip(), m_mousePosition->X); + FpsCanvas().SetTop(FpsTooltip(), y - 30); + FpsCanvas().SetLeft(FpsIndicator(), m_mousePosition->X - 3); + FpsCanvas().SetTop(FpsIndicator(), y - 3); + FpsTooltipText().Text(winrt::to_hstring(RefreshRateMeter::RefreshRateToString(nearestFpsValue.refreshRate)) + winrt::to_hstring(" FPS")); + } + + void ChartPage::FpsCanvas_PointerMoved(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e) + { + m_mousePosition = e.GetCurrentPoint(*this).Position(); + RefreshTooltip(); + } + + void ChartPage::FpsCanvas_PointerExited(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e) + { + m_mousePosition = std::nullopt; + RefreshTooltip(); + } + + void ChartPage::Slider_ValueChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e) + { + m_displayHistorySeconds = e.NewValue(); + } + + void ChartPage::Slider_ValueChanged_1(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e) + { + m_aggregationSize = e.NewValue(); + } +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.h b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.h new file mode 100644 index 000000000..bc2635f0f --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.h @@ -0,0 +1,67 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#pragma once + +#include "ChartPage.g.h" + +namespace winrt::DynamicRefreshRateTool::implementation +{ + struct ChartPage : ChartPageT + { + ChartPage(); + + // Mouse events over canvas. + void FpsCanvas_PointerExited(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e); + void FpsCanvas_PointerMoved(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e); + + // Sliders events. + void Slider_ValueChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e); + void Slider_ValueChanged_1(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e); + + void RefreshChart(); + + private: + + // Helper functions to convert fps data values to coordinates on canvas. + float QpcTimeToXCoordinate(int64_t fpsValue); + float FpsToYCoordinate(float fps); + + // Methods that refresh canvas content. + void RefreshDashedLines(); + void RefreshTooltip(); + + // Timer that awakes to refresh chart. + std::future m_monitorFuture; + bool running = true; + + // Optional mouse position. Set to nullopt if mouse is not over the canvas. + std::optional m_mousePosition; + + // Latest fps data history and query parameters that can be tweaked in UI. + std::vector m_fpsHistory; + float m_displayHistorySeconds = 16.0f; + int m_aggregationSize = 1; + + // Maximal fps value in recorded history. + float m_maxFps = 60.0f; + }; +} + +namespace winrt::DynamicRefreshRateTool::factory_implementation +{ + struct ChartPage : ChartPageT + { + }; +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.idl b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.idl new file mode 100644 index 000000000..90100404a --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.idl @@ -0,0 +1,24 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +namespace DynamicRefreshRateTool +{ + [default_interface] + runtimeclass ChartPage : Microsoft.UI.Xaml.Controls.Page + { + ChartPage(); + + void RefreshChart(); + } +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.xaml b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.xaml new file mode 100644 index 000000000..4aaeebd26 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/ChartPage.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + FPS: 60.0 + + + + + + + Displayed FPS History (Seconds) + + + + Smoothing (Number of frames to average) + + + + + diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.sln b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.sln new file mode 100644 index 000000000..800b7b615 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32802.440 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DynamicRefreshRateTool", "DynamicRefreshRateTool.vcxproj", "{FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|arm64 = Debug|arm64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|arm64 = Release|arm64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|arm64.ActiveCfg = Debug|arm64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|arm64.Build.0 = Debug|arm64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|arm64.Deploy.0 = Debug|arm64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|x64.ActiveCfg = Debug|x64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|x64.Build.0 = Debug|x64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|x64.Deploy.0 = Debug|x64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|x86.ActiveCfg = Debug|Win32 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|x86.Build.0 = Debug|Win32 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Debug|x86.Deploy.0 = Debug|Win32 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|arm64.ActiveCfg = Release|arm64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|arm64.Build.0 = Release|arm64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|arm64.Deploy.0 = Release|arm64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|x64.ActiveCfg = Release|x64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|x64.Build.0 = Release|x64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|x64.Deploy.0 = Release|x64 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|x86.ActiveCfg = Release|Win32 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|x86.Build.0 = Release|Win32 + {FDBC6BD5-F2E8-46BC-89D7-324B16E872B2}.Release|x86.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {46A26428-CD90-4D63-8567-EBDE8ED66261} + EndGlobalSection +EndGlobal diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.vcxproj b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.vcxproj new file mode 100644 index 000000000..6b083f094 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.vcxproj @@ -0,0 +1,238 @@ + + + + + + + true + true + true + {fdbc6bd5-f2e8-46bc-89d7-324b16e872b2} + DynamicRefreshRateTool + DynamicRefreshRateTool + + $(RootNamespace) + en-US + 16.0 + false + true + Windows Store + 10.0 + 10.0 + 10.0.17763.0 + true + true + + + + + Debug + Win32 + + + Debug + x64 + + + Debug + arm64 + + + Release + Win32 + + + Release + x64 + + + Release + arm64 + + + + Application + v143 + v142 + Unicode + true + + + true + true + + + false + true + false + + + + + + + + + False + False + False + True + Never + 0 + True + SHA256 + testcertbeta.pfx + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + Designer + + + + + + + + AnimationPage.xaml + Code + + + ChartPage.xaml + Code + + + + App.xaml + + + MainWindow.xaml + + + + + + + + Designer + + + Designer + + + + + + AnimationPage.xaml + Code + + + ChartPage.xaml + Code + + + Create + + + App.xaml + + + MainWindow.xaml + + + + + + + + AnimationPage.xaml + Code + + + Code + App.xaml + + + ChartPage.xaml + Code + + + Code + MainWindow.xaml + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.vcxproj.filters b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.vcxproj.filters new file mode 100644 index 000000000..df3cb64ed --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/DynamicRefreshRateTool.vcxproj.filters @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + {fdbc6bd5-f2e8-46bc-89d7-324b16e872b2} + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.idl b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.idl new file mode 100644 index 000000000..3f02e69e0 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.idl @@ -0,0 +1,22 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +namespace DynamicRefreshRateTool +{ + [default_interface] + runtimeclass MainWindow : Microsoft.UI.Xaml.Window + { + MainWindow(); + } +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml new file mode 100644 index 000000000..802c7378d --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Folder is not selected + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml.cpp b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml.cpp new file mode 100644 index 000000000..4d7123dc9 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml.cpp @@ -0,0 +1,132 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#include "pch.h" +#include "MainWindow.xaml.h" +#if __has_include("MainWindow.g.cpp") +#include "MainWindow.g.cpp" +#endif + +using namespace winrt; +using namespace Microsoft::UI::Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace winrt::DynamicRefreshRateTool::implementation +{ + + MainWindow::MainWindow() + { + InitializeComponent(); + + ExtendsContentIntoTitleBar(true); + SetTitleBar(AppTitleBar()); + + m_updateFuture = std::async(std::launch::async | std::launch::deferred, + [this]() + { + while (running) + { + std::this_thread::sleep_for(std::chrono::milliseconds(75)); + DispatcherQueue().TryEnqueue( + [this]() + { + FpsText().Text(winrt::to_hstring(RefreshRateMeter::RefreshRateToString(RefreshRateMeter::Instance().GetCurrentRefreshRate()))); + if (Chart().Visibility() == Visibility::Visible) + { + Chart().RefreshChart(); + } + } + ); + } + } + ); + + Closed( + [&](auto, auto) + { + this->running = false; + m_updateFuture.wait(); + } + ); + } + + void MainWindow::Page_KeyUp(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) + { + if (e.Key() == winrt::Windows::System::VirtualKey::B) + { + BoostToggleSwitch().IsOn(!BoostToggleSwitch().IsOn()); + } + if (e.Key() == winrt::Windows::System::VirtualKey::L && LoggingToggleSwitch().IsEnabled()) + { + LoggingToggleSwitch().IsOn(!LoggingToggleSwitch().IsOn()); + } + } + + winrt::Windows::Foundation::IAsyncAction MainWindow::ChooseFolder_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e) + { + Windows::Storage::Pickers::FolderPicker picker; + picker.ViewMode(Windows::Storage::Pickers::PickerViewMode::List); + picker.SuggestedStartLocation(Windows::Storage::Pickers::PickerLocationId::ComputerFolder); + picker.as()->Initialize(GetActiveWindow()); + + + if (auto loggingFolder = co_await picker.PickSingleFolderAsync()) + { + m_loggingFolder = loggingFolder; + FolderPath().Text(m_loggingFolder.Path()); + FolderPath().Visibility(Visibility::Visible); + FolderNotSelected().Visibility(Visibility::Collapsed); + LoggingToggleSwitch().IsEnabled(true); + } + + } + + void MainWindow::BoostToggled(IInspectable const& sender, RoutedEventArgs const& args) + { + DCompositionBoostCompositorClock(BoostToggleSwitch().IsOn()); + if (IsLogging()) + { + m_logger->BoostStateChanged(BoostToggleSwitch().IsOn()); + } + } + + void MainWindow::StartLogging() + { + m_logger.emplace(std::wstring(m_loggingFolder.Path().c_str())); + } + + void MainWindow::StopLogging() + { + m_logger = std::nullopt; + } + + bool MainWindow::IsLogging() + { + return LoggingToggleSwitch().IsOn() && m_logger; + } + + void MainWindow::LoggingToggleSwitch_Toggled(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e) + { + if (LoggingToggleSwitch().IsOn()) + { + StartLogging(); + } + else + { + StopLogging(); + } + } +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml.h b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml.h new file mode 100644 index 000000000..2f5bb265c --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/MainWindow.xaml.h @@ -0,0 +1,54 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#pragma once + +#include "MainWindow.g.h" + +namespace winrt::DynamicRefreshRateTool::implementation +{ + struct MainWindow : MainWindowT + { + MainWindow(); + + void BoostToggled(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args); + + void Page_KeyUp(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e); + + winrt::Windows::Foundation::IAsyncAction ChooseFolder_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); + + void LoggingToggleSwitch_Toggled(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); + + private: + + void StartLogging(); + + void StopLogging(); + + bool IsLogging(); + + std::optional m_logger; + winrt::Windows::Storage::StorageFolder m_loggingFolder = nullptr; + + std::future m_updateFuture; + bool running = true; + }; +} + +namespace winrt::DynamicRefreshRateTool::factory_implementation +{ + struct MainWindow : MainWindowT + { + }; +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/PRIVACY.md b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/PRIVACY.md new file mode 100644 index 000000000..567a70531 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/PRIVACY.md @@ -0,0 +1,3 @@ +# Dynamic Refresh Rate Tool Privacy + +The Dynamic Refresh Rate Tool application does not collect, store, or transmit any personal information. diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Package.appxmanifest b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Package.appxmanifest new file mode 100644 index 000000000..ef56e7912 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/Package.appxmanifest @@ -0,0 +1,48 @@ + + + + + + + + DynamicRefreshRateTool + microsoft + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/README.md b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/README.md new file mode 100644 index 000000000..6c7d04cb8 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/README.md @@ -0,0 +1,54 @@ +--- +page_type: sample +languages: +- cpp +products: +- windows +- windows-api-win32 +- windows-app-sdk +- DXGI +name: DynamicRefreshRateTool sample +urlFragment: DynamicRefreshRateTool-sample +description: Demonstrates how to use APIs related to the Dynamic Refresh Rate feature with WinUI3 applications. +extendedZipContent: +- path: LICENSE +- target: LICENSE +--- + +# DynamicRefreshRateTool sample + +This sample application demonstrates how a **WinUI3** app can interact with the Windows 11 feature [Dynamic Refresh Rate](https://devblogs.microsoft.com/directx/dynamic-refresh-rate/) through various system APIs. It uses these APIs to both demonstrate their functionality and provide a helpful and easy-to-use framework for validating the functionality of Dynamic Refresh Rate on supported devices, both programatically and visually. The main purpose will be to illustrate when the refresh rate of a system changes by going higher and lower. We refer to this functionality as *boosting* and *unboosting.* You can read more about how this feature works in our documentation for the [Compositor Clock API](https://docs.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock'). + +## APIs Used + +**WinUI3** controls are used to build the main user interface of this application, including its windows and buttons. For the animation shown in the animations tab, we use **Microsoft.UI.Composition**. In this iteration of **Microsoft.UI.Composition**, the app's visual scene is self-composed into a single **DXGI SwapChain**. Due to the design of Dynamic Refresh Rate, DXGI SwapChains are mapped to a [virtualized refresh rate](https://docs.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock) by default and are not able to perceive changing vblank cadences of present timings. In build 22502, Windows 11 introduced [DXGIDisableVBlankVirtualization](https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_6/nf-dxgi1_6-dxgidisablevblankvirtualization) which DynamicRefreshRateTool uses to disable the virtualization for the application before the main SwapChain is created, allowing the SwapChain to observe changes in its [IDXGIOutput::WaitForVBlank](https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgioutput-waitforvblank) and present timing as the refresh rate changes from 60Hz to 120Hz and back. We expect many applications that are SwapChain-based will use this method to be able to perceive the Dynamic Refresh Rate. + +The buttons that are used to request boost use the [DCompositionBoostCompositorClock](https://docs.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-dcompositionboostcompositorclock) function with a boolean value to indicate whether the application is requesting the system boost or rescinding the request, which we call boosting or unboosting. + +The Statistics tab showcases usage of [Frame statistics](https://docs.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-dcompositiongetstatistics) through DComposition-based statistics API. + +## Dynamic Refresh Rate behavior on different Windows 11 versions + +While using this sample, you may find that boosting may occur at different times when your application does not request it. That is because there is global logic in Windows that will *auto-boost*. In Windows 11 builds before [preview 22557](https://blogs.windows.com/windows-insider/2022/02/16/announcing-windows-11-insider-preview-build-22557/), auto-boosting occurs when GPU Accelerated Ink is drawn, such as by using [DelegatedInkTrailVisual ](https://docs.microsoft.com/en-us/uwp/api/windows.ui.composition.delegatedinktrailvisual?view=winrt-22621) as well as when an application uses [InteractionTracker](https://docs.microsoft.com/en-us/uwp/api/windows.ui.composition.interactions.interactiontracker?view=winrt-22621) to drive animations. After 22557 [Windows will auto-boost for a short duration when it receives pointing input](https://blogs.windows.com/windows-insider/2022/02/16/announcing-windows-11-insider-preview-build-22557/#:~:text=We%E2%80%99re%20expanding%20Dynamic,then%20restart%20Edge.). + +## Prerequisites + +* See [System requirements for Windows app development](https://docs.microsoft.com/windows/apps/windows-app-sdk/system-requirements). +* Make sure that your development environment is set up correctly—see [Install tools for developing apps for Windows 10 and Windows 11](https://docs.microsoft.com/windows/apps/windows-app-sdk/set-up-your-development-environment). + + +## Building and running the sample + +* Open the solution file (`.sln`) in Visual Studio. +* Press Ctrl+Shift+B, or select **Build** \> **Build Solution**. + +In the project properties, select the "Debug" tab, and set the Debugger type for both "Application process" and "Background +task process" to "Native Only". + +## Related Links + +- [Windows App SDK](https://docs.microsoft.com/windows/apps/windows-app-sdk/) +- [Compositor Clock](https://docs.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock) +- [Dynamic Refresh Rate](https://devblogs.microsoft.com/directx/dynamic-refresh-rate/) +- [DXGIDisableVBlankVirtualization](https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_6/nf-dxgi1_6-dxgidisablevblankvirtualization) +- [Windows 11 preview 22557](https://blogs.windows.com/windows-insider/2022/02/16/announcing-windows-11-insider-preview-build-22557/) diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateLogger.cpp b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateLogger.cpp new file mode 100644 index 000000000..c434ccf22 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateLogger.cpp @@ -0,0 +1,114 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#include "pch.h" + +using namespace winrt::DynamicRefreshRateTool; +using namespace std::chrono; + +std::wstring GetFullPathForLoggingFile(std::wstring folderPath) +{ + std::time_t t = std::time(nullptr); + std::tm tm; + ::localtime_s(&tm, &t); + std::stringstream stream; + stream << std::put_time(&tm, "%d-%m-%Y %H-%M-%S"); + auto str = stream.str(); + return folderPath + L"\\" + L"FPS Monitor " + std::wstring(str.begin(), str.end()) + L".txt"; +} + +RefreshRateLogger::RefreshRateLogger(std::wstring folderPath) :m_out(GetFullPathForLoggingFile(folderPath)) +{ + WriteLog("Logging started."); + + m_loggerFuture = std::async(std::launch::async | std::launch::deferred, + [this]() + { + int64_t startingFrom = RefreshRateMeter::Instance().GetCurrentTick(); + + int levels = 4; + int fpsLevels[] = { 0, 60, 120, 240 }; + int fpsLevelsFrames[] = { 0, 0, 0, 0, 0 }; + int prevLevel = 0; + int total = 0; + + while (!m_stop) + { + auto history = RefreshRateMeter::Instance().GetHistoryStartingFrom(startingFrom); + for (auto& data : history) + { + int curLevel = 0; + total++; + for (int i = 1; i < levels; i++) + { + if (fabs(fpsLevels[i] - data.refreshRate) < 10) + { + curLevel = i; + break; + } + } + + fpsLevelsFrames[curLevel]++; + + if (curLevel != 0) + { + if (curLevel != prevLevel) + { + WriteLog("Running at " + std::to_string(fpsLevels[curLevel]) + "hz"); + } + + prevLevel = curLevel; + } + } + + if (!history.empty()) + { + startingFrom = history.back().tick; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + + WriteLog("Logging finished."); + WriteLog("Total number of frames captured: " + std::to_string(total)); + for (int i = 1; i < 4; i++) + { + WriteLog("Number of frames at " + std::to_string(fpsLevels[i]) + "hz: " + std::to_string(fpsLevelsFrames[i]) + " (~" + std::to_string(fpsLevelsFrames[i] * 100 / total) + "%)"); + } + WriteLog("Other frames: " + std::to_string(fpsLevelsFrames[0]) + " (~" + std::to_string(fpsLevelsFrames[0] * 100 / total) + "%)"); + } + ); +} + +RefreshRateLogger::~RefreshRateLogger() +{ + m_stop = true; + m_loggerFuture.wait(); +} + +void RefreshRateLogger::BoostStateChanged(bool enabled) +{ + WriteLog(enabled ? "Boost enabled." : "Boost disabled."); +} + +void RefreshRateLogger::WriteLog(std::string message) +{ + std::lock_guard lock(m_mutex); + + std::time_t t = std::time(nullptr); + std::tm tm; + ::localtime_s(&tm, &t); + + m_out << "[" << std::put_time(&tm, "%d-%m-%Y %H:%M:%S") << "] " << message << std::endl; +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateLogger.h b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateLogger.h new file mode 100644 index 000000000..a79e8481c --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateLogger.h @@ -0,0 +1,39 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#pragma once +#include "pch.h" + +namespace winrt::DynamicRefreshRateTool { + + class RefreshRateLogger { + public: + RefreshRateLogger(std::wstring folderPath); + + ~RefreshRateLogger(); + + void BoostStateChanged(bool enabled); + + private: + + void WriteLog(std::string message); + + std::ofstream m_out; + std::mutex m_mutex; + + bool m_stop = false; + std::future m_loggerFuture; + }; + +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateMeter.cpp b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateMeter.cpp new file mode 100644 index 000000000..a666749d9 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateMeter.cpp @@ -0,0 +1,185 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#include "pch.h" + +using namespace winrt; +using namespace winrt::DynamicRefreshRateTool; + +RefreshRateMeter::RefreshRateMeter() +{ + LARGE_INTEGER frequency = {}; + QueryPerformanceFrequency(&frequency); + m_frequency = frequency.QuadPart; + + // Start monitor thread. + m_monitorFuture = std::async(std::launch::async | std::launch::deferred, [this] { RefreshRateTrackingThread(); }); +} + +RefreshRateMeter::~RefreshRateMeter() +{ + m_shouldStopFpsCalculation = true; + m_monitorFuture.wait(); +} + +float RefreshRateMeter::GetCurrentRefreshRate() const +{ + if (m_deltaHistory.empty()) + { + // If there is no history yet - return default 60 FPS. + return 60.0; + } + + return static_cast(m_frequency) / m_deltaHistory.back().second; +} + +int64_t RefreshRateMeter::GetLastFrameDeltaTicks() const +{ + if (m_deltaHistory.empty()) + { + // If there is no history yet - return default 1/60 delta. + return m_frequency / 60; + } + + return m_deltaHistory.back().second; +} + +int64_t RefreshRateMeter::GetFrequency() const +{ + return m_frequency; +} + +int64_t RefreshRateMeter::GetCurrentTick() const +{ + LARGE_INTEGER currentTime; + QueryPerformanceCounter(¤tTime); + return currentTime.QuadPart; +} + +RefreshRateMeter& RefreshRateMeter::Instance() +{ + static RefreshRateMeter RefreshRateMeter; + return RefreshRateMeter; +} + +std::vector RefreshRateMeter::GetRecentHistory(int64_t offsetTicks, int64_t historyLengthTicks, int aggregationSize, int keepEach) const +{ + if (m_deltaHistory.empty()) + { + return {}; + } + + std::lock_guard guard(m_mutex); + + int lastFrameIndex = static_cast(m_deltaHistory.size()) - 1; + for (int64_t accumulatedTotal = 0; lastFrameIndex >= 0 && accumulatedTotal + m_deltaHistory[lastFrameIndex].second <= offsetTicks; lastFrameIndex--) { + accumulatedTotal += m_deltaHistory[lastFrameIndex].second; + } + + if (lastFrameIndex < 0) + { + return {}; + } + + int firstFrameIndex = lastFrameIndex; + for (int64_t accumulatedTotal = 0; firstFrameIndex >= 0 && accumulatedTotal + m_deltaHistory[firstFrameIndex].second <= historyLengthTicks; firstFrameIndex--) { + accumulatedTotal += m_deltaHistory[firstFrameIndex].second; + } + + if (firstFrameIndex < 0) + { + firstFrameIndex = 0; + } + + std::vector res; + + int64_t aggregatedDelta = 0; + int64_t aggregatedFramesNumber = 0; + + for (int i = lastFrameIndex; i > lastFrameIndex - aggregationSize + 1 && i >= 0; i--) + { + aggregatedDelta += m_deltaHistory[i].second; + aggregatedFramesNumber++; + } + + for (int i = lastFrameIndex; i >= firstFrameIndex; i--) + { + if (i - aggregationSize + 1 >= 0) + { + aggregatedDelta += m_deltaHistory[i - aggregationSize + 1].second; + aggregatedFramesNumber++; + } + + if ((m_currentFrame - (m_deltaHistory.size() - i - 1)) % keepEach == 0) + { + float refreshRate = static_cast(aggregatedFramesNumber * m_frequency) / aggregatedDelta; + res.push_back(DataPoint{ m_deltaHistory[i].first, aggregatedDelta, aggregatedFramesNumber, refreshRate }); + } + + aggregatedDelta -= m_deltaHistory[i].second; + aggregatedFramesNumber--; + } + + std::reverse(res.begin(), res.end()); + + return res; +} + +std::vector RefreshRateMeter::GetHistoryStartingFrom(int64_t startingFrom) const +{ + std::lock_guard guard(m_mutex); + std::vector res; + for (int i = (int)m_deltaHistory.size() - 1; i >= 0 && m_deltaHistory[i].first > startingFrom; i--) + { + res.push_back(DataPoint{ m_deltaHistory[i].first, m_deltaHistory[i].second, 1, static_cast(m_frequency) / m_deltaHistory[i].second }); + } + + std::reverse(res.begin(), res.end()); + + return res; +} + +std::string RefreshRateMeter::RefreshRateToString(float fpsValue) +{ + std::stringstream stream; + // One digit fixed precision (like "60.0"). + stream << std::fixed << std::setprecision(1) << fpsValue; + return stream.str(); +} + +void RefreshRateMeter::RefreshRateTrackingThread() +{ + LARGE_INTEGER prevTime = {}; + + while (!m_shouldStopFpsCalculation) + { + DCompositionWaitForCompositorClock(0, nullptr, INFINITE); + LARGE_INTEGER currentTime{}; + QueryPerformanceCounter(¤tTime); + + if (prevTime.QuadPart != 0) + { + std::lock_guard guard(m_mutex); + m_deltaHistory.push_back({ currentTime.QuadPart, currentTime.QuadPart - prevTime.QuadPart }); + + if (m_deltaHistory.size() > MAX_HISTORY_SIZE) + { + m_deltaHistory.pop_front(); + } + } + + prevTime = currentTime; + m_currentFrame++; + } +} diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateMeter.h b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateMeter.h new file mode 100644 index 000000000..93be55926 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/RefreshRateMeter.h @@ -0,0 +1,83 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#pragma once +#include "pch.h" + +namespace winrt::DynamicRefreshRateTool { + + /** + * Helper for monitoring dcomp refresh rate (fps) and capturing its history. + * Can provide current refresh rate or refresh rate at some previous point in time. + */ + class RefreshRateMeter { + public: + RefreshRateMeter(); + + ~RefreshRateMeter(); + + // Global instance. + static RefreshRateMeter& Instance(); + + // Get current refresh rate. + float GetCurrentRefreshRate() const; + + // Get last frame delta in ticks. + int64_t GetLastFrameDeltaTicks() const; + + // Get timer frequency, or ticks per second. + int64_t GetFrequency() const; + + // Get current tick. + int64_t GetCurrentTick() const; + + // History data point. + struct DataPoint { + // QPC tick - start of the segment. + int64_t tick; + // Number of QPC ticks - duration of the segment. + int64_t deltaTicks; + // Number of frames in the segment. + int64_t framesNumber; + // Average refresh rate in the segment. + float refreshRate; + }; + + std::vector GetRecentHistory(int64_t offsetTicks, int64_t historyLengthTicks, int aggregationSize = 1, int keepEach = 1) const; + + std::vector GetHistoryStartingFrom(int64_t startingFrom) const; + + // Helper that converts refresh rate float value to a readable string. + static std::string RefreshRateToString(float fpsValue); + + private: + void RefreshRateTrackingThread(); + + int64_t m_frequency = 0; + uint64_t m_currentFrame = 0; + + // Up to 10 minutes of history at 60 fps rate + const size_t MAX_HISTORY_SIZE = 60 * 60 * 10; + // List of pairs (start frame QPC tick, frame duration QPC ticks) for the last ~10 minutes + std::deque> m_deltaHistory; + + // Future that owns monitor thread. + std::future m_monitorFuture; + + // Flag to stop the monitor thread. + bool m_shouldStopFpsCalculation = false; + + mutable std::mutex m_mutex; + }; +} \ No newline at end of file diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/app.manifest b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/app.manifest new file mode 100644 index 000000000..6988d07bc --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/app.manifest @@ -0,0 +1,15 @@ + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + + diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/packages.config b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/packages.config new file mode 100644 index 000000000..a69e1c743 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/pch.cpp b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/pch.cpp new file mode 100644 index 000000000..17aa0db4f --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/pch.cpp @@ -0,0 +1,15 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#include "pch.h" diff --git a/Samples/Composition/DynamicRefreshRateTool/cpp-winui/pch.h b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/pch.h new file mode 100644 index 000000000..1e9930d92 --- /dev/null +++ b/Samples/Composition/DynamicRefreshRateTool/cpp-winui/pch.h @@ -0,0 +1,73 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// 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. +// +//********************************************************* + +#pragma once +#include +#include +#include +#include + +#include +#pragma comment( lib, "dcomp" ) +#pragma comment( lib, "Dxgi" ) + +// Undefine GetCurrentTime macro to prevent +// conflict with Storyboard::GetCurrentTime +#undef GetCurrentTime + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "RefreshRateLogger.h" +#include "RefreshRateMeter.h" + +#include +#include +#include