[Windows] Allow apps to prefer low power GPUs (#162490)

…and make ANGLE backend to choose low-power GPU when this flag is
provided.

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Mateusz Przybylski 2025-02-04 16:39:28 -08:00 committed by GitHub
parent b0d95460e4
commit 6ea438f296
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 175 additions and 16 deletions

View File

@ -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<FlutterDesktopGpuPreference>(project.gpu_preference());
const std::vector<std::string>& entrypoint_args =
project.dart_entrypoint_arguments();

View File

@ -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<std::string> dart_entrypoint_arguments_;
// The preference for GPU to be used by flutter engine.
GpuPreference gpu_preference_ = GpuPreference::NoPreference;
};
} // namespace flutter

View File

@ -14,19 +14,19 @@ namespace egl {
int Manager::instance_count_ = 0;
std::unique_ptr<Manager> Manager::Create() {
std::unique_ptr<Manager> Manager::Create(GpuPreference gpu_preference) {
std::unique_ptr<Manager> 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> 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<EGLint>(luid.has_value() ? luid->HighPart : 0),
EGL_PLATFORM_ANGLE_D3D_LUID_LOW_ANGLE,
static_cast<EGLint>(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<const EGLint*> display_attributes_configs = {
d3d11_display_attributes,
d3d11_fl_9_3_display_attributes,
d3d11_warp_display_attributes,
};
std::vector<const EGLint*> 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<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
@ -296,5 +333,33 @@ Context* Manager::resource_context() const {
return resource_context_.get();
}
std::optional<LUID> Manager::GetLowPowerGpuLuid() {
Microsoft::WRL::ComPtr<IDXGIFactory1> factory1 = nullptr;
Microsoft::WRL::ComPtr<IDXGIFactory6> factory6 = nullptr;
Microsoft::WRL::ComPtr<IDXGIAdapter1> 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

View File

@ -14,9 +14,12 @@
// Windows platform specific includes
#include <d3d11.h>
#include <dxgi.h>
#include <dxgi1_6.h>
#include <windows.h>
#include <wrl/client.h>
#include <memory>
#include <optional>
#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<Manager> Create();
static std::unique_ptr<Manager> 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<LUID> 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();

View File

@ -29,6 +29,9 @@ FlutterProjectBundle::FlutterProjectBundle(
std::string(properties.dart_entrypoint_argv[i]));
}
gpu_preference_ =
static_cast<FlutterGpuPreference>(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())) {

View File

@ -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<std::string> engine_switches_;
// App's GPU preference.
FlutterGpuPreference gpu_preference_;
};
} // namespace flutter

View File

@ -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<egl::GpuPreference>(project_->gpu_preference()));
window_proc_delegate_manager_ = std::make_unique<WindowProcDelegateManager>();
window_proc_delegate_manager_->RegisterTopLevelWindowProcDelegate(
[](HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar, void* user_data,

View File

@ -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<egl::WindowSurface>
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> 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<IDXGIAdapter> 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();

View File

@ -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 ==========

View File

@ -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<flutter::egl::WindowSurface>,
CreateWindowSurface,

View File

@ -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;
}

View File

@ -61,6 +61,8 @@ class WindowsConfigBuilder {
// Adds an argument to the Dart entrypoint arguments List<String>.
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<std::string> dart_entrypoint_arguments_;
FlutterDesktopGpuPreference gpu_preference_ =
FlutterDesktopGpuPreference::NoPreference;
FML_DISALLOW_COPY_AND_ASSIGN(WindowsConfigBuilder);
};