Reland: [Impeller] add a configuration option that allows defering all PSO construction until needed. (#165622)

The cost of bootstapping the initial PSOs can regress cold startup time
for customer money. As an experiment, attempt to defer PSO construction
to skia like.

---------

Co-authored-by: Aaron Clarke <aaclarke@google.com>
Co-authored-by: gaaclarke <30870216+gaaclarke@users.noreply.github.com>
This commit is contained in:
Jonah Williams 2025-03-21 18:34:05 -07:00 committed by GitHub
parent a4b982738e
commit 31ff6497f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 424 additions and 211 deletions

View File

@ -2730,6 +2730,17 @@ targets:
["devicelab", "android", "linux"]
task_name: flutter_gallery__start_up
# linux mokey benchmark
- name: Linux_mokey flutter_gallery_lazy__start_up
recipe: devicelab/devicelab_drone
presubmit: false
bringup: true
timeout: 60
properties:
tags: >
["devicelab", "android", "linux"]
task_name: flutter_gallery_lazy__start_up
# linux mokey benchmark
- name: Linux_mokey flutter_gallery__start_up_delayed
recipe: devicelab/devicelab_drone

View File

@ -45,6 +45,7 @@
/dev/devicelab/bin/tasks/flutter_gallery__image_cache_memory.dart @jtmcdole @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery__memory_nav.dart @jtmcdole @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery__start_up.dart @jtmcdole @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery_lazy__start_up.dart @jtmcdole @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery__start_up_delayed.dart @jtmcdole @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery__transition_perf.dart @jtmcdole @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e.dart @jtmcdole @flutter/engine

View File

@ -0,0 +1,12 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createFlutterGalleryStartupTest(enableLazyShaderMode: true));
}

View File

@ -270,11 +270,13 @@ TaskFunction createOpenPayScrollPerfTest({bool measureCpuGpu = true}) {
TaskFunction createFlutterGalleryStartupTest({
String target = 'lib/main.dart',
Map<String, String>? runEnvironment,
bool enableLazyShaderMode = false,
}) {
return StartupTest(
'${flutterDirectory.path}/dev/integration_tests/flutter_gallery',
target: target,
runEnvironment: runEnvironment,
enableLazyShaderMode: enableLazyShaderMode,
).run;
}
@ -840,6 +842,17 @@ void _addVulkanGPUTracingToManifest(String testDirectory) {
_addMetadataToManifest(testDirectory, keyPairs);
}
/// Opens the file at testDirectory + 'android/app/src/main/AndroidManifest.xml'
/// <meta-data
/// android:name="io.flutter.embedding.android.ImpellerShaderMode"
/// android:value="lazy" />
void _addLazyShaderMode(String testDirectory) {
final List<(String, String)> keyPairs = <(String, String)>[
('io.flutter.embedding.android.ImpellerLazyShaderInitialization', 'true'),
];
_addMetadataToManifest(testDirectory, keyPairs);
}
/// Opens the file at testDirectory + 'android/app/src/main/AndroidManifest.xml'
/// and adds the following entry to the application.
/// <meta-data
@ -881,10 +894,12 @@ class StartupTest {
this.reportMetrics = true,
this.target = 'lib/main.dart',
this.runEnvironment,
this.enableLazyShaderMode = false,
});
final String testDirectory;
final bool reportMetrics;
final bool enableLazyShaderMode;
final String target;
final Map<String, String>? runEnvironment;
@ -895,144 +910,155 @@ class StartupTest {
const int iterations = 5;
final List<Map<String, dynamic>> results = <Map<String, dynamic>>[];
section('Building application');
String? applicationBinaryPath;
switch (deviceOperatingSystem) {
case DeviceOperatingSystem.android:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm,android-arm64',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.androidArm:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.androidArm64:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm64',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.fake:
case DeviceOperatingSystem.fuchsia:
case DeviceOperatingSystem.linux:
break;
case DeviceOperatingSystem.ios:
case DeviceOperatingSystem.macos:
await flutter(
'build',
options: <String>[
if (deviceOperatingSystem == DeviceOperatingSystem.ios) 'ios' else 'macos',
'-v',
'--profile',
'--target=$target',
if (deviceOperatingSystem == DeviceOperatingSystem.ios) '--no-publish-port',
],
);
final String buildRoot = path.join(testDirectory, 'build');
applicationBinaryPath = _findDarwinAppInBuildDirectory(buildRoot);
case DeviceOperatingSystem.windows:
await flutter(
'build',
options: <String>['windows', '-v', '--profile', '--target=$target'],
);
final String basename = path.basename(testDirectory);
final String arch = Abi.current() == Abi.windowsX64 ? 'x64' : 'arm64';
applicationBinaryPath = path.join(
testDirectory,
'build',
'windows',
arch,
'runner',
'Profile',
'$basename.exe',
);
if (enableLazyShaderMode) {
_addLazyShaderMode(testDirectory);
}
const int maxFailures = 3;
int currentFailures = 0;
for (int i = 0; i < iterations; i += 1) {
// Startup should not take more than a few minutes. After 10 minutes,
// take a screenshot to help debug.
final Timer timer = Timer(const Duration(minutes: 10), () async {
print('Startup not completed within 10 minutes. Taking a screenshot...');
await _flutterScreenshot(
device.deviceId,
'screenshot_startup_${DateTime.now().toLocal().toIso8601String()}.png',
);
});
final int result = await flutter(
'run',
options: <String>[
'--no-android-gradle-daemon',
'--no-publish-port',
'--verbose',
'--profile',
'--trace-startup',
'--target=$target',
'-d',
device.deviceId,
if (applicationBinaryPath != null) '--use-application-binary=$applicationBinaryPath',
],
environment: runEnvironment,
canFail: true,
);
timer.cancel();
if (result == 0) {
final Map<String, dynamic> data =
json.decode(
file(
'${testOutputDirectory(testDirectory)}/start_up_info.json',
).readAsStringSync(),
)
as Map<String, dynamic>;
results.add(data);
} else {
currentFailures += 1;
await _flutterScreenshot(
device.deviceId,
'screenshot_startup_failure_$currentFailures.png',
);
i -= 1;
if (currentFailures == maxFailures) {
return TaskResult.failure('Application failed to start $maxFailures times');
}
try {
section('Building application');
String? applicationBinaryPath;
switch (deviceOperatingSystem) {
case DeviceOperatingSystem.android:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm,android-arm64',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.androidArm:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.androidArm64:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm64',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.fake:
case DeviceOperatingSystem.fuchsia:
case DeviceOperatingSystem.linux:
break;
case DeviceOperatingSystem.ios:
case DeviceOperatingSystem.macos:
await flutter(
'build',
options: <String>[
if (deviceOperatingSystem == DeviceOperatingSystem.ios) 'ios' else 'macos',
'-v',
'--profile',
'--target=$target',
if (deviceOperatingSystem == DeviceOperatingSystem.ios) '--no-publish-port',
],
);
final String buildRoot = path.join(testDirectory, 'build');
applicationBinaryPath = _findDarwinAppInBuildDirectory(buildRoot);
case DeviceOperatingSystem.windows:
await flutter(
'build',
options: <String>['windows', '-v', '--profile', '--target=$target'],
);
final String basename = path.basename(testDirectory);
final String arch = Abi.current() == Abi.windowsX64 ? 'x64' : 'arm64';
applicationBinaryPath = path.join(
testDirectory,
'build',
'windows',
arch,
'runner',
'Profile',
'$basename.exe',
);
}
await device.uninstallApp();
const int maxFailures = 3;
int currentFailures = 0;
for (int i = 0; i < iterations; i += 1) {
// Startup should not take more than a few minutes. After 10 minutes,
// take a screenshot to help debug.
final Timer timer = Timer(const Duration(minutes: 10), () async {
print('Startup not completed within 10 minutes. Taking a screenshot...');
await _flutterScreenshot(
device.deviceId,
'screenshot_startup_${DateTime.now().toLocal().toIso8601String()}.png',
);
});
final int result = await flutter(
'run',
options: <String>[
'--no-android-gradle-daemon',
'--no-publish-port',
'--verbose',
'--profile',
'--trace-startup',
'--target=$target',
'-d',
device.deviceId,
if (applicationBinaryPath != null) '--use-application-binary=$applicationBinaryPath',
],
environment: runEnvironment,
canFail: true,
);
timer.cancel();
if (result == 0) {
final Map<String, dynamic> data =
json.decode(
file(
'${testOutputDirectory(testDirectory)}/start_up_info.json',
).readAsStringSync(),
)
as Map<String, dynamic>;
results.add(data);
} else {
currentFailures += 1;
await _flutterScreenshot(
device.deviceId,
'screenshot_startup_failure_$currentFailures.png',
);
i -= 1;
if (currentFailures == maxFailures) {
return TaskResult.failure('Application failed to start $maxFailures times');
}
}
await device.uninstallApp();
}
final Map<String, dynamic> averageResults = _average(results, iterations);
if (!reportMetrics) {
return TaskResult.success(averageResults);
}
return TaskResult.success(
averageResults,
benchmarkScoreKeys: <String>[
'timeToFirstFrameMicros',
'timeToFirstFrameRasterizedMicros',
],
);
} finally {
await _resetManifest(testDirectory);
}
final Map<String, dynamic> averageResults = _average(results, iterations);
if (!reportMetrics) {
return TaskResult.success(averageResults);
}
return TaskResult.success(
averageResults,
benchmarkScoreKeys: <String>['timeToFirstFrameMicros', 'timeToFirstFrameRasterizedMicros'],
);
});
}
@ -1180,6 +1206,7 @@ class PerfTest {
this.disablePartialRepaint = false,
this.enableMergedPlatformThread = false,
this.enableSurfaceControl = false,
this.enableLazyShaderMode = false,
this.createPlatforms = const <String>[],
}) : _resultFilename = resultFilename;
@ -1202,6 +1229,7 @@ class PerfTest {
this.disablePartialRepaint = false,
this.enableMergedPlatformThread = false,
this.enableSurfaceControl = false,
this.enableLazyShaderMode = false,
this.createPlatforms = const <String>[],
}) : saveTraceFile = false,
timelineFileName = null,
@ -1261,6 +1289,9 @@ class PerfTest {
/// Whether to enable SurfaceControl swapchain.
final bool enableSurfaceControl;
/// Whether to defer construction of all PSO objects in the Impeller backend.
final bool enableLazyShaderMode;
/// Number of seconds to time out the test after, allowing debug callbacks to run.
final int? timeoutSeconds;
@ -1359,6 +1390,9 @@ class PerfTest {
if (enableSurfaceControl) {
_addSurfaceControlSupportToManifest(testDirectory);
}
if (enableLazyShaderMode) {
_addLazyShaderMode(testDirectory);
}
}
if (disablePartialRepaint || enableMergedPlatformThread) {
changedPlist = true;

View File

@ -41560,6 +41560,7 @@ ORIGIN: ../../../flutter/impeller/base/backend_cast.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/base/comparable.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/base/comparable.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/base/config.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/base/flags.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/base/mask.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/base/promise.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/base/promise.h + ../../../flutter/LICENSE
@ -44532,6 +44533,7 @@ FILE: ../../../flutter/impeller/base/backend_cast.h
FILE: ../../../flutter/impeller/base/comparable.cc
FILE: ../../../flutter/impeller/base/comparable.h
FILE: ../../../flutter/impeller/base/config.h
FILE: ../../../flutter/impeller/base/flags.h
FILE: ../../../flutter/impeller/base/mask.h
FILE: ../../../flutter/impeller/base/promise.cc
FILE: ../../../flutter/impeller/base/promise.h

View File

@ -228,6 +228,9 @@ struct Settings {
// Enable android surface control swapchains where supported.
bool enable_surface_control = false;
// Whether to lazily initialize impeller PSO state.
bool impeller_enable_lazy_shader_mode = false;
// Log a warning during shell initialization if Impeller is not enabled.
bool warn_on_impeller_opt_out = false;

View File

@ -14,6 +14,7 @@ impeller_component("base") {
"comparable.cc",
"comparable.h",
"config.h",
"flags.h",
"mask.h",
"promise.cc",
"promise.h",

View File

@ -0,0 +1,16 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_IMPELLER_BASE_FLAGS_H_
#define FLUTTER_IMPELLER_BASE_FLAGS_H_
namespace impeller {
struct Flags {
/// Whether to defer PSO construction until first use. Usage Will introduce
/// raster jank.
bool lazy_shader_mode = false;
};
} // namespace impeller
#endif // FLUTTER_IMPELLER_BASE_FLAGS_H_

View File

@ -216,7 +216,6 @@ void ContentContextOptions::ApplyToPipelineDescriptor(
}
desc.SetPrimitiveType(primitive_type);
desc.SetPolygonMode(wireframe ? PolygonMode::kLine : PolygonMode::kFill);
}
@ -283,11 +282,13 @@ ContentContext::ContentContext(
desc.format = PixelFormat::kR8G8B8A8UNormInt;
desc.size = ISize{1, 1};
empty_texture_ = GetContext()->GetResourceAllocator()->CreateTexture(desc);
auto data = Color::BlackTransparent().ToR8G8B8A8();
auto cmd_buffer = GetContext()->CreateCommandBuffer();
auto blit_pass = cmd_buffer->CreateBlitPass();
auto& host_buffer = GetTransientsBuffer();
auto buffer_view = host_buffer.Emplace(data);
std::array<uint8_t, 4> data = Color::BlackTransparent().ToR8G8B8A8();
std::shared_ptr<CommandBuffer> cmd_buffer =
GetContext()->CreateCommandBuffer();
std::shared_ptr<BlitPass> blit_pass = cmd_buffer->CreateBlitPass();
HostBuffer& host_buffer = GetTransientsBuffer();
BufferView buffer_view = host_buffer.Emplace(data);
blit_pass->AddCopy(buffer_view, empty_texture_);
if (!blit_pass->EncodeCommands() || !GetContext()
@ -383,9 +384,14 @@ ContentContext::ContentContext(
}
clip_pipeline_descriptor->SetColorAttachmentDescriptors(
std::move(clip_color_attachments));
clip_pipelines_.SetDefault(
options,
std::make_unique<ClipPipeline>(*context_, clip_pipeline_descriptor));
if (GetContext()->GetFlags().lazy_shader_mode) {
clip_pipelines_.SetDefaultDescriptor(clip_pipeline_descriptor);
clip_pipelines_.SetDefault(options, nullptr);
} else {
clip_pipelines_.SetDefault(
options,
std::make_unique<ClipPipeline>(*context_, clip_pipeline_descriptor));
}
texture_downsample_pipelines_.CreateDefault(
*context_, options_no_msaa_no_depth_stencil);
rrect_blur_pipelines_.CreateDefault(*context_, options_trianglestrip);
@ -671,7 +677,9 @@ void ContentContext::ClearCachedRuntimeEffectPipeline(
}
void ContentContext::InitializeCommonlyUsedShadersIfNeeded() const {
TRACE_EVENT0("flutter", "InitializeCommonlyUsedShadersIfNeeded");
if (GetContext()->GetFlags().lazy_shader_mode) {
return;
}
GetContext()->InitializeCommonlyUsedShadersIfNeeded();
}

View File

@ -9,6 +9,7 @@
#include <memory>
#include <optional>
#include <unordered_map>
#include <utility>
#include "flutter/fml/logging.h"
#include "flutter/fml/status_or.h"
@ -521,7 +522,13 @@ class ContentContext {
void SetDefault(const ContentContextOptions& options,
std::unique_ptr<PipelineHandleT> pipeline) {
default_options_ = options;
Set(options, std::move(pipeline));
if (pipeline) {
Set(options, std::move(pipeline));
}
}
void SetDefaultDescriptor(std::optional<PipelineDescriptor> desc) {
desc_ = std::move(desc);
}
void CreateDefault(const Context& context,
@ -534,7 +541,13 @@ class ContentContext {
return;
}
options.ApplyToPipelineDescriptor(*desc);
SetDefault(options, std::make_unique<PipelineHandleT>(context, desc));
desc_ = desc;
if (context.GetFlags().lazy_shader_mode) {
SetDefault(options, nullptr);
} else {
SetDefault(options, std::make_unique<PipelineHandleT>(context, desc_,
/*async=*/true));
}
}
PipelineHandleT* Get(const ContentContextOptions& options) const {
@ -547,16 +560,29 @@ class ContentContext {
return nullptr;
}
PipelineHandleT* GetDefault() const {
bool IsDefault(const ContentContextOptions& opts) {
return default_options_.has_value() &&
opts.ToKey() == default_options_.value().ToKey();
}
PipelineHandleT* GetDefault(const Context& context) {
if (!default_options_.has_value()) {
return nullptr;
}
PipelineHandleT* result = Get(default_options_.value());
if (result != nullptr) {
return result;
}
SetDefault(
default_options_.value(),
std::make_unique<PipelineHandleT>(context, desc_, /*async=*/false));
return Get(default_options_.value());
}
size_t GetPipelineCount() const { return pipelines_.size(); }
private:
std::optional<PipelineDescriptor> desc_;
std::optional<ContentContextOptions> default_options_;
std::vector<std::pair<uint64_t, std::unique_ptr<PipelineHandleT>>>
pipelines_;
@ -688,7 +714,10 @@ class ContentContext {
return found;
}
RenderPipelineHandleT* default_handle = container.GetDefault();
RenderPipelineHandleT* default_handle = container.GetDefault(*GetContext());
if (container.IsDefault(opts)) {
return default_handle;
}
// The default must always be initialized in the constructor.
FML_CHECK(default_handle != nullptr);

View File

@ -134,7 +134,7 @@ std::shared_ptr<Context> PlaygroundImplGLES::GetContext() const {
}
auto context = ContextGLES::Create(
std::move(gl), ShaderLibraryMappingsForPlayground(), true);
Flags{}, std::move(gl), ShaderLibraryMappingsForPlayground(), true);
if (!context) {
FML_LOG(ERROR) << "Could not create context.";
return nullptr;

View File

@ -77,8 +77,8 @@ PlaygroundImplMTL::PlaygroundImplMTL(PlaygroundSwitches switches)
}
auto context = ContextMTL::Create(
ShaderLibraryMappingsForPlayground(), is_gpu_disabled_sync_switch_,
"Playground Library",
impeller::Flags{}, ShaderLibraryMappingsForPlayground(),
is_gpu_disabled_sync_switch_, "Playground Library",
switches.enable_wide_gamut
? std::optional<PixelFormat>(PixelFormat::kB10G10R10A10XR)
: std::nullopt);

View File

@ -96,7 +96,7 @@ PlaygroundImplVK::PlaygroundImplVK(PlaygroundSwitches switches)
context_settings.fatal_missing_validations =
switches_.enable_vulkan_validation;
auto context_vk = ContextVK::Create(std::move(context_settings));
auto context_vk = ContextVK::Create(Flags{}, std::move(context_settings));
if (!context_vk || !context_vk->IsValid()) {
VALIDATION_LOG << "Could not create Vulkan context in the playground.";
return;

View File

@ -19,17 +19,20 @@
namespace impeller {
std::shared_ptr<ContextGLES> ContextGLES::Create(
const Flags& flags,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries,
bool enable_gpu_tracing) {
return std::shared_ptr<ContextGLES>(
new ContextGLES(std::move(gl), shader_libraries, enable_gpu_tracing));
return std::shared_ptr<ContextGLES>(new ContextGLES(
flags, std::move(gl), shader_libraries, enable_gpu_tracing));
}
ContextGLES::ContextGLES(
const Flags& flags,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_mappings,
bool enable_gpu_tracing) {
bool enable_gpu_tracing)
: Context(flags) {
reactor_ = std::make_shared<ReactorGLES>(std::move(gl));
if (!reactor_->IsValid()) {
VALIDATION_LOG << "Could not create valid reactor.";

View File

@ -25,6 +25,7 @@ class ContextGLES final : public Context,
public std::enable_shared_from_this<ContextGLES> {
public:
static std::shared_ptr<ContextGLES> Create(
const Flags& flags,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries,
bool enable_gpu_tracing);
@ -60,6 +61,7 @@ class ContextGLES final : public Context,
bool is_valid_ = false;
ContextGLES(
const Flags& flags,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries,
bool enable_gpu_tracing);

View File

@ -67,16 +67,19 @@ class ContextMTL final : public Context,
public std::enable_shared_from_this<ContextMTL> {
public:
static std::shared_ptr<ContextMTL> Create(
const Flags& flags,
const std::vector<std::string>& shader_library_paths,
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch);
static std::shared_ptr<ContextMTL> Create(
const Flags& flags,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
const std::string& label,
std::optional<PixelFormat> pixel_format_override = std::nullopt);
static std::shared_ptr<ContextMTL> Create(
const Flags& flags,
id<MTLDevice> device,
id<MTLCommandQueue> command_queue,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
@ -178,7 +181,8 @@ class ContextMTL final : public Context,
#endif // IMPELLER_DEBUG
bool is_valid_ = false;
ContextMTL(id<MTLDevice> device,
ContextMTL(const Flags& flags,
id<MTLDevice> device,
id<MTLCommandQueue> command_queue,
NSArray<id<MTLLibrary>>* shader_libraries,
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,

View File

@ -86,12 +86,14 @@ static std::unique_ptr<Capabilities> InferMetalCapabilities(
}
ContextMTL::ContextMTL(
const Flags& flags,
id<MTLDevice> device,
id<MTLCommandQueue> command_queue,
NSArray<id<MTLLibrary>>* shader_libraries,
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
std::optional<PixelFormat> pixel_format_override)
: device_(device),
: Context(flags),
device_(device),
command_queue_(command_queue),
is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) {
// Validate device.
@ -235,6 +237,7 @@ static id<MTLCommandQueue> CreateMetalCommandQueue(id<MTLDevice> device) {
}
std::shared_ptr<ContextMTL> ContextMTL::Create(
const Flags& flags,
const std::vector<std::string>& shader_library_paths,
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) {
auto device = CreateMetalDevice();
@ -243,7 +246,7 @@ std::shared_ptr<ContextMTL> ContextMTL::Create(
return nullptr;
}
auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
device, command_queue,
flags, device, command_queue,
MTLShaderLibraryFromFilePaths(device, shader_library_paths),
std::move(is_gpu_disabled_sync_switch)));
if (!context->IsValid()) {
@ -254,6 +257,7 @@ std::shared_ptr<ContextMTL> ContextMTL::Create(
}
std::shared_ptr<ContextMTL> ContextMTL::Create(
const Flags& flags,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
const std::string& library_label,
@ -264,7 +268,7 @@ std::shared_ptr<ContextMTL> ContextMTL::Create(
return nullptr;
}
auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
device, command_queue,
flags, device, command_queue,
MTLShaderLibraryFromFileData(device, shader_libraries_data,
library_label),
std::move(is_gpu_disabled_sync_switch), pixel_format_override));
@ -276,13 +280,14 @@ std::shared_ptr<ContextMTL> ContextMTL::Create(
}
std::shared_ptr<ContextMTL> ContextMTL::Create(
const Flags& flags,
id<MTLDevice> device,
id<MTLCommandQueue> command_queue,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
const std::string& library_label) {
auto context = std::shared_ptr<ContextMTL>(
new ContextMTL(device, command_queue,
new ContextMTL(flags, device, command_queue,
MTLShaderLibraryFromFileData(device, shader_libraries_data,
library_label),
std::move(is_gpu_disabled_sync_switch)));

View File

@ -34,7 +34,7 @@ std::shared_ptr<Context> CreateContext() {
settings.enable_gpu_tracing = false;
settings.enable_surface_control = false;
return ContextVK::Create(std::move(settings));
return ContextVK::Create(impeller::Flags{}, std::move(settings));
}
TEST(AndroidVulkanTest, CanImportRGBA) {

View File

@ -102,8 +102,9 @@ static std::optional<QueueIndexVK> PickQueue(const vk::PhysicalDevice& device,
return std::nullopt;
}
std::shared_ptr<ContextVK> ContextVK::Create(Settings settings) {
auto context = std::shared_ptr<ContextVK>(new ContextVK());
std::shared_ptr<ContextVK> ContextVK::Create(const Flags& flags,
Settings settings) {
auto context = std::shared_ptr<ContextVK>(new ContextVK(flags));
context->Setup(std::move(settings));
if (!context->IsValid()) {
return nullptr;
@ -127,7 +128,8 @@ uint64_t CalculateHash(void* ptr) {
}
} // namespace
ContextVK::ContextVK() : hash_(CalculateHash(this)) {}
ContextVK::ContextVK(const Flags& flags)
: Context(flags), hash_(CalculateHash(this)) {}
ContextVK::~ContextVK() {
if (device_holder_ && device_holder_->device) {
@ -150,16 +152,6 @@ void ContextVK::Setup(Settings settings) {
raster_message_loop_ = fml::ConcurrentMessageLoop::Create(
ChooseThreadCountForWorkers(std::thread::hardware_concurrency()));
raster_message_loop_->PostTaskToAllWorkers([]() {
// Currently we only use the worker task pool for small parts of a frame
// workload, if this changes this setting may need to be adjusted.
fml::RequestAffinity(fml::CpuAffinity::kNotPerformance);
#ifdef FML_OS_ANDROID
if (::setpriority(PRIO_PROCESS, gettid(), -5) != 0) {
VALIDATION_LOG << "Failed to set Workers task runner priority";
}
#endif // FML_OS_ANDROID
});
auto& dispatcher = VULKAN_HPP_DEFAULT_DISPATCHER;
dispatcher.init(settings.proc_address_callback);

View File

@ -98,7 +98,8 @@ class ContextVK final : public Context,
/// Visible for testing.
static size_t ChooseThreadCountForWorkers(size_t hardware_concurrency);
static std::shared_ptr<ContextVK> Create(Settings settings);
static std::shared_ptr<ContextVK> Create(const Flags& flags,
Settings settings);
uint64_t GetHash() const { return hash_; }
@ -300,7 +301,7 @@ class ContextVK final : public Context,
bool is_valid_ = false;
ContextVK();
explicit ContextVK(const Flags& flags);
void Setup(Settings settings);

View File

@ -14,7 +14,7 @@
namespace impeller {
SurfaceContextVK::SurfaceContextVK(const std::shared_ptr<ContextVK>& parent)
: parent_(parent) {}
: Context(parent->GetFlags()), parent_(parent) {}
SurfaceContextVK::~SurfaceContextVK() = default;

View File

@ -955,7 +955,8 @@ std::shared_ptr<ContextVK> MockVulkanContextBuilder::Build() {
g_format_properties_callback = format_properties_callback_;
g_physical_device_properties_callback = physical_properties_callback_;
settings.embedder_data = embedder_data_;
std::shared_ptr<ContextVK> result = ContextVK::Create(std::move(settings));
std::shared_ptr<ContextVK> result =
ContextVK::Create(Flags{}, std::move(settings));
return result;
}

View File

@ -10,7 +10,7 @@ namespace impeller {
Context::~Context() = default;
Context::Context() = default;
Context::Context(const Flags& flags) : flags_(flags) {}
bool Context::UpdateOffscreenLayerPixelFormat(PixelFormat format) {
return false;

View File

@ -9,6 +9,7 @@
#include <string>
#include "fml/closure.h"
#include "impeller/base/flags.h"
#include "impeller/core/allocator.h"
#include "impeller/core/formats.h"
#include "impeller/renderer/capabilities.h"
@ -245,9 +246,12 @@ class Context {
/// @brief Submit the command buffer that renders to the onscreen surface.
virtual bool SubmitOnscreen(std::shared_ptr<CommandBuffer> cmd_buffer);
protected:
Context();
const Flags& GetFlags() const { return flags_; }
protected:
explicit Context(const Flags& flags);
Flags flags_;
std::vector<std::function<void()>> per_frame_task_;
private:

View File

@ -23,13 +23,15 @@ Pipeline<T>::~Pipeline() = default;
PipelineFuture<PipelineDescriptor> CreatePipelineFuture(
const Context& context,
std::optional<PipelineDescriptor> desc) {
std::optional<PipelineDescriptor> desc,
bool async) {
if (!context.IsValid()) {
return {desc, RealizedFuture<std::shared_ptr<Pipeline<PipelineDescriptor>>>(
nullptr)};
}
return context.GetPipelineLibrary()->GetPipeline(std::move(desc));
return context.GetPipelineLibrary()->GetPipeline(std::move(desc),
/*async=*/async);
}
PipelineFuture<ComputePipelineDescriptor> CreatePipelineFuture(

View File

@ -90,9 +90,18 @@ using PipelineRef = raw_ptr<Pipeline<PipelineDescriptor>>;
extern template class Pipeline<PipelineDescriptor>;
extern template class Pipeline<ComputePipelineDescriptor>;
/// @brief Create a pipeline for the given descriptor.
///
/// If `async` is true, the compilation is performed on a worker thread. The
/// returned future will complete once that work is done. If `async` is false,
/// the work is done on the current thread.
///
/// It is more performant to set async to false than to spawn a
/// worker and immediately block on the future completion.
PipelineFuture<PipelineDescriptor> CreatePipelineFuture(
const Context& context,
std::optional<PipelineDescriptor> desc);
std::optional<PipelineDescriptor> desc,
bool async = true);
PipelineFuture<ComputePipelineDescriptor> CreatePipelineFuture(
const Context& context,
@ -116,14 +125,17 @@ class RenderPipelineHandle {
using FragmentShader = FragmentShader_;
using Builder = PipelineBuilder<VertexShader, FragmentShader>;
explicit RenderPipelineHandle(const Context& context)
explicit RenderPipelineHandle(const Context& context, bool async = true)
: RenderPipelineHandle(CreatePipelineFuture(
context,
Builder::MakeDefaultPipelineDescriptor(context))) {}
Builder::MakeDefaultPipelineDescriptor(context),
async)) {}
explicit RenderPipelineHandle(const Context& context,
std::optional<PipelineDescriptor> desc)
: RenderPipelineHandle(CreatePipelineFuture(context, desc)) {}
std::optional<PipelineDescriptor> desc,
bool async = true)
: RenderPipelineHandle(
CreatePipelineFuture(context, desc, /*async=*/async)) {}
explicit RenderPipelineHandle(PipelineFuture<PipelineDescriptor> future)
: pipeline_future_(std::move(future)) {}

View File

@ -140,6 +140,8 @@ class MockCommandBuffer : public CommandBuffer {
class MockImpellerContext : public Context {
public:
MockImpellerContext() : Context(Flags{}) {}
MOCK_METHOD(Context::BackendType, GetBackendType, (), (const, override));
MOCK_METHOD(std::string, DescribeGpuModel, (), (const, override));

View File

@ -27,8 +27,8 @@ ScopedObject<Context> ContextGLES::Create(
impeller_framebuffer_blend_shaders_gles_data,
impeller_framebuffer_blend_shaders_gles_length),
};
auto impeller_context = impeller::ContextGLES::Create(std::move(proc_table),
shader_mappings, false);
auto impeller_context = impeller::ContextGLES::Create(
Flags{}, std::move(proc_table), shader_mappings, false);
if (!impeller_context) {
VALIDATION_LOG << "Could not create Impeller context.";
return {};

View File

@ -28,9 +28,9 @@ CreateShaderLibraryMappings() {
ScopedObject<Context> ContextMTL::Create() {
auto impeller_context =
impeller::ContextMTL::Create(CreateShaderLibraryMappings(), //
std::make_shared<fml::SyncSwitch>(), //
"Impeller" //
impeller::ContextMTL::Create(Flags{}, CreateShaderLibraryMappings(), //
std::make_shared<fml::SyncSwitch>(), //
"Impeller" //
);
if (!impeller_context) {
VALIDATION_LOG << "Could not create Impeller context.";

View File

@ -53,8 +53,8 @@ ScopedObject<Context> ContextVK::Create(const Settings& settings) {
impeller_settings.enable_validation = true;
sContextVKProcAddressCallback = settings.instance_proc_address_callback;
impeller_settings.proc_address_callback = ContextVKGetInstanceProcAddress;
auto impeller_context =
impeller::ContextVK::Create(std::move(impeller_settings));
auto impeller_context = impeller::ContextVK::Create(
impeller::Flags{}, std::move(impeller_settings));
sContextVKProcAddressCallback = nullptr;
if (!impeller_context) {
VALIDATION_LOG << "Could not create Impeller context.";

View File

@ -40,7 +40,7 @@ namespace impeller {
class TestImpellerContext : public impeller::Context {
public:
TestImpellerContext() = default;
TestImpellerContext() : impeller::Context(impeller::Flags{}) {}
BackendType GetBackendType() const override { return BackendType::kMetal; }

View File

@ -61,7 +61,7 @@ ShellTestPlatformViewGL::ShellTestPlatformViewGL(
return;
}
impeller_context_ = impeller::ContextGLES::Create(
std::move(gl), ShaderLibraryMappings(), true);
impeller::Flags{}, std::move(gl), ShaderLibraryMappings(), true);
}
}

View File

@ -532,6 +532,9 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) {
settings.merged_platform_ui_thread = !command_line.HasOption(
FlagForSwitch(Switch::DisableMergedPlatformUIThread));
settings.impeller_enable_lazy_shader_mode =
command_line.HasOption(FlagForSwitch(Switch::ImpellerLazyShaderMode));
return settings;
}

View File

@ -300,6 +300,10 @@ DEF_SWITCH(DisableMergedPlatformUIThread,
DEF_SWITCH(EnableAndroidSurfaceControl,
"enable-surface-control",
"Enable the SurfaceControl backed swapchain when supported.")
DEF_SWITCH(ImpellerLazyShaderMode,
"impeller-lazy-shader-mode",
"Whether to defer initialization of all required PSOs for the "
"Impeller backend. Defaults to false.")
DEF_SWITCHES_END
void PrintUsage(const std::string& executable_name);

View File

@ -54,7 +54,8 @@ static std::shared_ptr<impeller::ContextMTL> CreateImpellerContext() {
impeller_framebuffer_blend_shaders_length),
};
auto sync_switch = std::make_shared<fml::SyncSwitch>(false);
return impeller::ContextMTL::Create(shader_mappings, sync_switch, "Impeller Library");
return impeller::ContextMTL::Create(impeller::Flags{}, shader_mappings, sync_switch,
"Impeller Library");
}
TEST(GPUSurfaceMetalImpeller, InvalidImpellerContextCreatesCausesSurfaceToBeInvalid) {

View File

@ -79,7 +79,7 @@ static std::shared_ptr<impeller::Context> CreateImpellerContext(
};
auto context = impeller::ContextGLES::Create(
std::move(proc_table),
impeller::Flags{}, std::move(proc_table),
is_gles3 ? gles3_shader_mappings : gles2_shader_mappings,
enable_gpu_tracing);
if (!context) {

View File

@ -36,7 +36,7 @@ TaskRunners MakeTaskRunners(const std::string& thread_label,
class TestImpellerContext : public impeller::Context {
public:
TestImpellerContext() {}
TestImpellerContext() : Context(impeller::Flags{}) {}
~TestImpellerContext() {}

View File

@ -49,7 +49,11 @@ static std::shared_ptr<impeller::Context> CreateImpellerContext(
settings.enable_gpu_tracing = p_settings.enable_gpu_tracing;
settings.enable_surface_control = p_settings.enable_surface_control;
auto context = impeller::ContextVK::Create(std::move(settings));
auto context = impeller::ContextVK::Create(
impeller::Flags{
.lazy_shader_mode = p_settings.enable_lazy_shader_mode,
},
std::move(settings));
if (!p_settings.quiet) {
if (context && impeller::CapabilitiesVK::Cast(*context->GetCapabilities())

View File

@ -7,6 +7,7 @@
#include "flutter/fml/concurrent_message_loop.h"
#include "flutter/fml/macros.h"
#include "flutter/impeller/display_list/aiks_context.h"
#include "flutter/impeller/renderer/backend/vulkan/surface_context_vk.h"
#include "flutter/shell/platform/android/android_context_vk_impeller.h"
#include "flutter/shell/platform/android/surface/android_native_window.h"

View File

@ -25,6 +25,7 @@ class AndroidContext {
bool enable_validation = false;
bool enable_gpu_tracing = false;
bool enable_surface_control = false;
bool enable_lazy_shader_mode = false;
bool quiet = false;
};

View File

@ -51,6 +51,8 @@ public class FlutterLoader {
"io.flutter.embedding.android.DisableMergedPlatformUIThread";
private static final String ENABLE_SURFACE_CONTROL =
"io.flutter.embedding.android.EnableSurfaceControl";
private static final String IMPELLER_LAZY_SHADER_MODE =
"io.flutter.embedding.android.ImpellerLazyShaderInitialization";
/**
* Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's
@ -377,6 +379,9 @@ public class FlutterLoader {
if (backend != null) {
shellArgs.add("--impeller-backend=" + backend);
}
if (metaData.getBoolean(IMPELLER_LAZY_SHADER_MODE)) {
shellArgs.add("--impeller-lazy-shader-mode");
}
}
final String leakVM = isLeakVM(metaData) ? "true" : "false";

View File

@ -26,6 +26,7 @@
#include "flutter/shell/platform/android/surface_texture_external_texture_gl_skia.h"
#include "flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.h"
#include "fml/logging.h"
#include "impeller/display_list/aiks_context.h"
#if IMPELLER_ENABLE_VULKAN // b/258506856 for why this is behind an if
#include "flutter/shell/platform/android/android_surface_vk_impeller.h"
#include "flutter/shell/platform/android/image_external_texture_vk_impeller.h"
@ -52,14 +53,19 @@ AndroidContext::ContextSettings CreateContextSettings(
settings.enable_gpu_tracing = p_settings.enable_vulkan_gpu_tracing;
settings.enable_validation = p_settings.enable_vulkan_validation;
settings.enable_surface_control = p_settings.enable_surface_control;
settings.enable_lazy_shader_mode =
p_settings.impeller_enable_lazy_shader_mode;
return settings;
}
} // namespace
AndroidSurfaceFactoryImpl::AndroidSurfaceFactoryImpl(
const std::shared_ptr<AndroidContext>& context,
bool enable_impeller)
: android_context_(context), enable_impeller_(enable_impeller) {}
bool enable_impeller,
bool lazy_shader_mode)
: android_context_(context),
enable_impeller_(enable_impeller),
lazy_shader_mode_(lazy_shader_mode) {}
AndroidSurfaceFactoryImpl::~AndroidSurfaceFactoryImpl() = default;
@ -132,8 +138,10 @@ PlatformViewAndroid::PlatformViewAndroid(
FML_CHECK(android_context_->IsValid())
<< "Could not create surface from invalid Android context.";
surface_factory_ = std::make_shared<AndroidSurfaceFactoryImpl>(
android_context_, //
delegate.OnPlatformViewGetSettings().enable_impeller //
android_context_, //
delegate.OnPlatformViewGetSettings().enable_impeller, //
delegate.OnPlatformViewGetSettings()
.impeller_enable_lazy_shader_mode //
);
android_surface_ = surface_factory_->CreateSurface();
android_use_new_platform_view_ =

View File

@ -26,7 +26,8 @@ namespace flutter {
class AndroidSurfaceFactoryImpl : public AndroidSurfaceFactory {
public:
AndroidSurfaceFactoryImpl(const std::shared_ptr<AndroidContext>& context,
bool enable_impeller);
bool enable_impeller,
bool lazy_shader_mode);
~AndroidSurfaceFactoryImpl() override;
@ -35,6 +36,7 @@ class AndroidSurfaceFactoryImpl : public AndroidSurfaceFactory {
private:
const std::shared_ptr<AndroidContext>& android_context_;
const bool enable_impeller_;
const bool lazy_shader_mode_;
};
class PlatformViewAndroid final : public PlatformView {

View File

@ -315,6 +315,35 @@ public class FlutterLoaderTest {
assertTrue(arguments.contains(disabledControlArg));
}
@Test
public void itSetsShaderInitModeFromMetaData() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
Bundle metaData = new Bundle();
metaData.putBoolean("io.flutter.embedding.android.ImpellerLazyShaderInitialization", true);
ctx.getApplicationInfo().metaData = metaData;
FlutterLoader.Settings settings = new FlutterLoader.Settings();
assertFalse(flutterLoader.initialized());
flutterLoader.startInitialization(ctx, settings);
flutterLoader.ensureInitializationComplete(ctx, null);
shadowOf(getMainLooper()).idle();
final String shaderModeArg = "--impeller-lazy-shader-mode";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(shaderModeArg));
}
@Test
@TargetApi(API_LEVELS.API_23)
@Config(sdk = API_LEVELS.API_23)

View File

@ -25,8 +25,8 @@ static std::shared_ptr<impeller::ContextMTL> CreateImpellerContext(
std::make_shared<fml::NonOwnedMapping>(impeller_framebuffer_blend_shaders_data,
impeller_framebuffer_blend_shaders_length),
};
return impeller::ContextMTL::Create(shader_mappings, is_gpu_disabled_sync_switch,
"Impeller Library");
return impeller::ContextMTL::Create(impeller::Flags{}, shader_mappings,
is_gpu_disabled_sync_switch, "Impeller Library");
}
@implementation FlutterDarwinContextMetalImpeller

View File

@ -41,7 +41,8 @@ static std::shared_ptr<impeller::ContextMTL> CreateImpellerContext() {
impeller_framebuffer_blend_shaders_length),
};
auto sync_switch = std::make_shared<fml::SyncSwitch>(false);
return impeller::ContextMTL::Create(shader_mappings, sync_switch, "Impeller Library");
return impeller::ContextMTL::Create(impeller::Flags{}, shader_mappings, sync_switch,
"Impeller Library");
}
@interface TestExternalTexture : NSObject <FlutterTexture>

View File

@ -6,6 +6,7 @@
#include <utility>
#include "impeller/display_list/aiks_context.h"
#include "impeller/entity/gles/entity_shaders_gles.h"
#include "impeller/entity/gles/framebuffer_blend_shaders_gles.h"
#include "impeller/entity/gles/modern_shaders_gles.h"
@ -80,7 +81,8 @@ EmbedderSurfaceGLImpeller::EmbedderSurfaceGLImpeller(
}
impeller_context_ = impeller::ContextGLES::Create(
std::move(gl), shader_mappings, /*enable_gpu_tracing=*/false);
impeller::Flags{}, std::move(gl), shader_mappings,
/*enable_gpu_tracing=*/false);
if (!impeller_context_) {
FML_LOG(ERROR) << "Could not create Impeller context.";

View File

@ -41,6 +41,7 @@ EmbedderSurfaceMetalImpeller::EmbedderSurfaceMetalImpeller(
impeller_framebuffer_blend_shaders_length),
};
context_ = impeller::ContextMTL::Create(
impeller::Flags{},
(__bridge id<MTLDevice>)device, // device
(__bridge id<MTLCommandQueue>)command_queue, // command_queue
shader_mappings, // shader_libraries_data

View File

@ -10,6 +10,7 @@
#include "flutter/impeller/entity/vk/framebuffer_blend_shaders_vk.h"
#include "flutter/impeller/entity/vk/modern_shaders_vk.h"
#include "flutter/shell/gpu/gpu_surface_vulkan.h"
#include "impeller/display_list/aiks_context.h"
#include "impeller/renderer/backend/vulkan/context_vk.h"
#include "include/gpu/ganesh/GrDirectContext.h"
#include "shell/gpu/gpu_surface_vulkan_impeller.h"
@ -70,7 +71,8 @@ EmbedderSurfaceVulkanImpeller::EmbedderSurfaceVulkanImpeller(
}
settings.embedder_data = data;
context_ = impeller::ContextVK::Create(std::move(settings));
context_ =
impeller::ContextVK::Create(impeller::Flags{}, std::move(settings));
if (!context_) {
FML_LOG(ERROR) << "Failed to initialize Vulkan Context.";
return;

View File

@ -35,6 +35,7 @@
#if ALLOW_IMPELLER
#include <vulkan/vulkan.h> // nogncheck
#include "impeller/display_list/aiks_context.h" // nogncheck
#include "impeller/entity/vk/entity_shaders_vk.h" // nogncheck
#include "impeller/entity/vk/framebuffer_blend_shaders_vk.h" // nogncheck
#include "impeller/entity/vk/modern_shaders_vk.h" // nogncheck
@ -74,7 +75,10 @@ bool ImpellerVulkanContextHolder::Initialize(bool enable_validation) {
context_settings.cache_directory = fml::paths::GetCachesDirectory();
context_settings.enable_validation = enable_validation;
context = impeller::ContextVK::Create(std::move(context_settings));
context = impeller::ContextVK::Create(
// Enable lazy shader mode for faster test execution as most tests
// will never render anything at all.
impeller::Flags{.lazy_shader_mode = true}, std::move(context_settings));
if (!context || !context->IsValid()) {
VALIDATION_LOG << "Could not create Vulkan context.";
return false;