diff --git a/engine/src/flutter/shell/platform/windows/client_wrapper/flutter_engine.cc b/engine/src/flutter/shell/platform/windows/client_wrapper/flutter_engine.cc index 7860947aa0..bff78cef81 100644 --- a/engine/src/flutter/shell/platform/windows/client_wrapper/flutter_engine.cc +++ b/engine/src/flutter/shell/platform/windows/client_wrapper/flutter_engine.cc @@ -17,6 +17,8 @@ FlutterEngine::FlutterEngine(const DartProject& project) { c_engine_properties.icu_data_path = project.icu_data_path().c_str(); c_engine_properties.aot_library_path = project.aot_library_path().c_str(); c_engine_properties.dart_entrypoint = project.dart_entrypoint().c_str(); + c_engine_properties.gpu_preference = + static_cast(project.gpu_preference()); const std::vector& entrypoint_args = project.dart_entrypoint_arguments(); diff --git a/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/dart_project.h b/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/dart_project.h index 903cfc45e9..7cbb48d9f5 100644 --- a/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/dart_project.h +++ b/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/dart_project.h @@ -10,6 +10,16 @@ namespace flutter { +// Configures how the Flutter engine selects a GPU. +enum class GpuPreference { + // No preference. + NoPreference, + // Prefer energy efficiency over performance, such as an integrated GPU. + // This falls back to a high performance GPU if no low power GPU is + // available. + LowPowerPreference, +}; + // A set of Flutter and Dart assets used to initialize a Flutter engine. class DartProject { public: @@ -71,6 +81,15 @@ class DartProject { return dart_entrypoint_arguments_; } + // Sets the GPU usage preference for flutter engine. + void set_gpu_preference(GpuPreference gpu_preference) { + gpu_preference_ = gpu_preference; + } + + // Returns the project's GPU preference. + // Defaults to NoPreference. + GpuPreference gpu_preference() const { return gpu_preference_; } + private: // Accessors for internals are private, so that they can be changed if more // flexible options for project structures are needed later without it @@ -95,6 +114,8 @@ class DartProject { std::string dart_entrypoint_; // The list of arguments to pass through to the Dart entrypoint. std::vector dart_entrypoint_arguments_; + // The preference for GPU to be used by flutter engine. + GpuPreference gpu_preference_ = GpuPreference::NoPreference; }; } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/egl/manager.cc b/engine/src/flutter/shell/platform/windows/egl/manager.cc index dee75a47df..7584fad1e9 100644 --- a/engine/src/flutter/shell/platform/windows/egl/manager.cc +++ b/engine/src/flutter/shell/platform/windows/egl/manager.cc @@ -14,19 +14,19 @@ namespace egl { int Manager::instance_count_ = 0; -std::unique_ptr Manager::Create() { +std::unique_ptr Manager::Create(GpuPreference gpu_preference) { std::unique_ptr manager; - manager.reset(new Manager()); + manager.reset(new Manager(gpu_preference)); if (!manager->IsValid()) { return nullptr; } return std::move(manager); } -Manager::Manager() { +Manager::Manager(GpuPreference gpu_preference) { ++instance_count_; - if (!InitializeDisplay()) { + if (!InitializeDisplay(gpu_preference)) { return; } @@ -46,7 +46,40 @@ Manager::~Manager() { --instance_count_; } -bool Manager::InitializeDisplay() { +bool Manager::InitializeDisplay(GpuPreference gpu_preference) { + // If the request for a low power GPU is provided, + // we will attempt to select GPU explicitly, via ANGLE extension + // that allows to specify the GPU to use via LUID. + std::optional luid = std::nullopt; + if (gpu_preference == GpuPreference::LowPowerPreference) { + luid = GetLowPowerGpuLuid(); + } + + // These are preferred display attributes and request ANGLE's D3D11 + // renderer (use only in case of valid LUID returned from above). + const EGLint d3d11_display_attributes_with_luid[] = { + EGL_PLATFORM_ANGLE_TYPE_ANGLE, + EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + + // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that will + // enable ANGLE to automatically call the IDXGIDevice3::Trim method on + // behalf of the application when it gets suspended. + EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, + EGL_TRUE, + + // This extension allows angle to render directly on a D3D swapchain + // in the correct orientation on D3D11. + EGL_EXPERIMENTAL_PRESENT_PATH_ANGLE, + EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE, + + // Specify the LUID of the GPU to use. + EGL_PLATFORM_ANGLE_D3D_LUID_HIGH_ANGLE, + static_cast(luid.has_value() ? luid->HighPart : 0), + EGL_PLATFORM_ANGLE_D3D_LUID_LOW_ANGLE, + static_cast(luid.has_value() ? luid->LowPart : 0), + EGL_NONE, + }; + // These are preferred display attributes and request ANGLE's D3D11 // renderer. eglInitialize will only succeed with these attributes if the // hardware supports D3D11 Feature Level 10_0+. @@ -92,11 +125,15 @@ bool Manager::InitializeDisplay() { EGL_NONE, }; - std::vector display_attributes_configs = { - d3d11_display_attributes, - d3d11_fl_9_3_display_attributes, - d3d11_warp_display_attributes, - }; + std::vector display_attributes_configs; + + if (luid) { + // If LUID value is present, obtain an adapter with that luid. + display_attributes_configs.push_back(d3d11_display_attributes_with_luid); + } + display_attributes_configs.push_back(d3d11_display_attributes); + display_attributes_configs.push_back(d3d11_fl_9_3_display_attributes); + display_attributes_configs.push_back(d3d11_warp_display_attributes); PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT = reinterpret_cast( @@ -296,5 +333,33 @@ Context* Manager::resource_context() const { return resource_context_.get(); } +std::optional Manager::GetLowPowerGpuLuid() { + Microsoft::WRL::ComPtr factory1 = nullptr; + Microsoft::WRL::ComPtr factory6 = nullptr; + Microsoft::WRL::ComPtr adapter = nullptr; + HRESULT hr = ::CreateDXGIFactory1(IID_PPV_ARGS(&factory1)); + if (FAILED(hr)) { + return std::nullopt; + } + hr = factory1->QueryInterface(IID_PPV_ARGS(&factory6)); + if (FAILED(hr)) { + // No support for IDXGIFactory6, so we will not use the selected GPU. + // We will follow with the default ANGLE selection. + return std::nullopt; + } + hr = factory6->EnumAdapterByGpuPreference( + 0, DXGI_GPU_PREFERENCE_MINIMUM_POWER, IID_PPV_ARGS(&adapter)); + if (FAILED(hr) || adapter == nullptr) { + return std::nullopt; + } + // Get the LUID of the adapter. + DXGI_ADAPTER_DESC desc; + hr = adapter->GetDesc(&desc); + if (FAILED(hr)) { + return std::nullopt; + } + return std::make_optional(desc.AdapterLuid); +} + } // namespace egl } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/egl/manager.h b/engine/src/flutter/shell/platform/windows/egl/manager.h index b2fb506773..f0ef9106a2 100644 --- a/engine/src/flutter/shell/platform/windows/egl/manager.h +++ b/engine/src/flutter/shell/platform/windows/egl/manager.h @@ -14,9 +14,12 @@ // Windows platform specific includes #include +#include +#include #include #include #include +#include #include "flutter/fml/macros.h" #include "flutter/shell/platform/windows/egl/context.h" @@ -26,11 +29,16 @@ namespace flutter { namespace egl { +enum class GpuPreference { + NoPreference, + LowPowerPreference, +}; + // A manager for initializing ANGLE correctly and using it to create and // destroy surfaces class Manager { public: - static std::unique_ptr Create(); + static std::unique_ptr Create(GpuPreference gpu_preference); virtual ~Manager(); @@ -71,17 +79,19 @@ class Manager { // Get the EGL context used for async texture uploads. virtual Context* resource_context() const; + static std::optional GetLowPowerGpuLuid(); + protected: // Creates a new surface manager retaining reference to the passed-in target // for the lifetime of the manager. - explicit Manager(); + explicit Manager(GpuPreference gpu_preference); private: // Number of active instances of Manager static int instance_count_; // Initialize the EGL display. - bool InitializeDisplay(); + bool InitializeDisplay(GpuPreference gpu_preference); // Initialize the EGL configs. bool InitializeConfig(); diff --git a/engine/src/flutter/shell/platform/windows/flutter_project_bundle.cc b/engine/src/flutter/shell/platform/windows/flutter_project_bundle.cc index aab3239ce8..9048f0e155 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_project_bundle.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_project_bundle.cc @@ -29,6 +29,9 @@ FlutterProjectBundle::FlutterProjectBundle( std::string(properties.dart_entrypoint_argv[i])); } + gpu_preference_ = + static_cast(properties.gpu_preference); + // Resolve any relative paths. if (assets_path_.is_relative() || icu_path_.is_relative() || (!aot_library_path_.empty() && aot_library_path_.is_relative())) { diff --git a/engine/src/flutter/shell/platform/windows/flutter_project_bundle.h b/engine/src/flutter/shell/platform/windows/flutter_project_bundle.h index 09770b5c09..da1f25b4fd 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_project_bundle.h +++ b/engine/src/flutter/shell/platform/windows/flutter_project_bundle.h @@ -17,6 +17,11 @@ namespace flutter { using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, FlutterEngineCollectAOTDataFnPtr>; +enum class FlutterGpuPreference { + NoPreference, + LowPowerPreference, +}; + // The data associated with a Flutter project needed to run it in an engine. class FlutterProjectBundle { public: @@ -59,6 +64,9 @@ class FlutterProjectBundle { return dart_entrypoint_arguments_; } + // Returns the app's GPU preference. + FlutterGpuPreference gpu_preference() const { return gpu_preference_; } + private: std::filesystem::path assets_path_; std::filesystem::path icu_path_; @@ -74,6 +82,9 @@ class FlutterProjectBundle { // Engine switches. std::vector engine_switches_; + + // App's GPU preference. + FlutterGpuPreference gpu_preference_; }; } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc index d08591a10b..6ea0928181 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc @@ -194,7 +194,8 @@ FlutterWindowsEngine::FlutterWindowsEngine( enable_impeller_ = std::find(switches.begin(), switches.end(), "--enable-impeller=true") != switches.end(); - egl_manager_ = egl::Manager::Create(); + egl_manager_ = egl::Manager::Create( + static_cast(project_->gpu_preference())); window_proc_delegate_manager_ = std::make_unique(); window_proc_delegate_manager_->RegisterTopLevelWindowProcDelegate( [](HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar, void* user_data, diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_unittests.cc index 72ffc7a6bf..97a9d0dbe2 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_unittests.cc @@ -31,7 +31,7 @@ namespace { // An EGL manager that initializes EGL but fails to create surfaces. class HalfBrokenEGLManager : public egl::Manager { public: - HalfBrokenEGLManager() : egl::Manager() {} + HalfBrokenEGLManager() : egl::Manager(egl::GpuPreference::NoPreference) {} std::unique_ptr CreateWindowSurface(HWND hwnd, size_t width, size_t height) override { @@ -422,6 +422,28 @@ TEST_F(WindowsTest, GetGraphicsAdapter) { ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc))); } +TEST_F(WindowsTest, GetGraphicsAdapterWithLowPowerPreference) { + std::optional luid = egl::Manager::GetLowPowerGpuLuid(); + if (!luid) { + GTEST_SKIP() << "Not able to find low power GPU, nothing to check."; + } + + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetGpuPreference(FlutterDesktopGpuPreference::LowPowerPreference); + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); + auto view = FlutterDesktopViewControllerGetView(controller.get()); + + Microsoft::WRL::ComPtr dxgi_adapter; + dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view); + ASSERT_NE(dxgi_adapter, nullptr); + DXGI_ADAPTER_DESC desc{}; + ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc))); + ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart); + ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart); +} + // Implicit view has the implicit view ID. TEST_F(WindowsTest, PluginRegistrarGetImplicitView) { auto& context = GetContext(); diff --git a/engine/src/flutter/shell/platform/windows/public/flutter_windows.h b/engine/src/flutter/shell/platform/windows/public/flutter_windows.h index 80d78766f9..8e8a5f8994 100644 --- a/engine/src/flutter/shell/platform/windows/public/flutter_windows.h +++ b/engine/src/flutter/shell/platform/windows/public/flutter_windows.h @@ -35,6 +35,16 @@ typedef struct FlutterDesktopEngine* FlutterDesktopEngineRef; // The unique identifier for a view. typedef int64_t FlutterDesktopViewId; +// Configures how the Flutter engine selects a GPU. +typedef enum { + // No preference. + NoPreference, + // Prefer energy efficiency over performance, such as an integrated GPU. + // This falls back to a high performance GPU if no low power GPU is + // available. + LowPowerPreference, +} FlutterDesktopGpuPreference; + // Properties for configuring a Flutter engine instance. typedef struct { // The path to the flutter_assets folder for the application to be run. @@ -68,6 +78,8 @@ typedef struct { // to FlutterDesktopEngineCreate. const char** dart_entrypoint_argv; + // GPU choice preference + FlutterDesktopGpuPreference gpu_preference; } FlutterDesktopEngineProperties; // ========== View Controller ========== diff --git a/engine/src/flutter/shell/platform/windows/testing/egl/mock_manager.h b/engine/src/flutter/shell/platform/windows/testing/egl/mock_manager.h index aa3fb48c87..1cc9f728d4 100644 --- a/engine/src/flutter/shell/platform/windows/testing/egl/mock_manager.h +++ b/engine/src/flutter/shell/platform/windows/testing/egl/mock_manager.h @@ -16,7 +16,7 @@ namespace egl { /// Mock for the |Manager| base class. class MockManager : public flutter::egl::Manager { public: - MockManager() : Manager() {} + MockManager() : Manager(flutter::egl::GpuPreference::NoPreference) {} MOCK_METHOD(std::unique_ptr, CreateWindowSurface, diff --git a/engine/src/flutter/shell/platform/windows/testing/windows_test_config_builder.cc b/engine/src/flutter/shell/platform/windows/testing/windows_test_config_builder.cc index 2395b5ed52..6456b9add5 100644 --- a/engine/src/flutter/shell/platform/windows/testing/windows_test_config_builder.cc +++ b/engine/src/flutter/shell/platform/windows/testing/windows_test_config_builder.cc @@ -38,6 +38,11 @@ void WindowsConfigBuilder::AddDartEntrypointArgument(std::string_view arg) { dart_entrypoint_arguments_.emplace_back(std::move(arg)); } +void WindowsConfigBuilder::SetGpuPreference( + FlutterDesktopGpuPreference gpu_preference) { + gpu_preference_ = gpu_preference; +} + FlutterDesktopEngineProperties WindowsConfigBuilder::GetEngineProperties() const { FlutterDesktopEngineProperties engine_properties = {}; @@ -63,6 +68,8 @@ FlutterDesktopEngineProperties WindowsConfigBuilder::GetEngineProperties() engine_properties.dart_entrypoint_argc = 0; } + engine_properties.gpu_preference = gpu_preference_; + return engine_properties; } diff --git a/engine/src/flutter/shell/platform/windows/testing/windows_test_config_builder.h b/engine/src/flutter/shell/platform/windows/testing/windows_test_config_builder.h index 1b54b5e009..5cfb87af0b 100644 --- a/engine/src/flutter/shell/platform/windows/testing/windows_test_config_builder.h +++ b/engine/src/flutter/shell/platform/windows/testing/windows_test_config_builder.h @@ -61,6 +61,8 @@ class WindowsConfigBuilder { // Adds an argument to the Dart entrypoint arguments List. void AddDartEntrypointArgument(std::string_view arg); + void SetGpuPreference(FlutterDesktopGpuPreference gpu_preference); + // Returns a configured and initialized engine. EnginePtr InitializeEngine() const; @@ -85,6 +87,9 @@ class WindowsConfigBuilder { std::string dart_entrypoint_; std::vector dart_entrypoint_arguments_; + FlutterDesktopGpuPreference gpu_preference_ = + FlutterDesktopGpuPreference::NoPreference; + FML_DISALLOW_COPY_AND_ASSIGN(WindowsConfigBuilder); };