diff --git a/engine/src/flutter/common/settings.h b/engine/src/flutter/common/settings.h index 6180528c650..fc1000df543 100644 --- a/engine/src/flutter/common/settings.h +++ b/engine/src/flutter/common/settings.h @@ -298,6 +298,13 @@ struct Settings { /// on the clock used by the Dart timeline APIs. This timestamp is used /// to log a timeline event that tracks the latency of engine startup. std::chrono::microseconds engine_start_timestamp = {}; + + /// The minimum number of samples to require in multipsampled anti-aliasing. + /// + /// Setting this value to 0 or 1 disables MSAA. + /// If it is not 0 or 1, it must be one of 2, 4, 8, or 16. However, if the + /// GPU does not support the requested sampling value, MSAA will be disabled. + uint8_t msaa_samples = 0; }; } // namespace flutter diff --git a/engine/src/flutter/shell/common/switches.cc b/engine/src/flutter/shell/common/switches.cc index 853c3e88ea2..25c5da36dc9 100644 --- a/engine/src/flutter/shell/common/switches.cc +++ b/engine/src/flutter/shell/common/switches.cc @@ -469,6 +469,28 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { &old_gen_heap_size); settings.old_gen_heap_size = std::stoi(old_gen_heap_size); } + + if (command_line.HasOption(FlagForSwitch(Switch::MsaaSamples))) { + std::string msaa_samples; + command_line.GetOptionValue(FlagForSwitch(Switch::MsaaSamples), + &msaa_samples); + if (msaa_samples == "0") { + settings.msaa_samples = 0; + } else if (msaa_samples == "1") { + settings.msaa_samples = 1; + } else if (msaa_samples == "2") { + settings.msaa_samples = 2; + } else if (msaa_samples == "4") { + settings.msaa_samples = 4; + } else if (msaa_samples == "8") { + settings.msaa_samples = 8; + } else if (msaa_samples == "16") { + settings.msaa_samples = 16; + } else { + FML_DLOG(ERROR) << "Invalid value for --msaa-samples: '" << msaa_samples + << "' (expected 0, 1, 2, 4, 8, or 16)."; + } + } return settings; } diff --git a/engine/src/flutter/shell/common/switches.h b/engine/src/flutter/shell/common/switches.h index 42043d39d0f..b5f64cef5e7 100644 --- a/engine/src/flutter/shell/common/switches.h +++ b/engine/src/flutter/shell/common/switches.h @@ -232,6 +232,13 @@ DEF_SWITCH(LeakVM, "When the last shell shuts down, the shared VM is leaked by default " "(the leak_vm in VM settings is true). To clean up the leak VM, set " "this value to false.") +DEF_SWITCH( + MsaaSamples, + "msaa-samples", + "The minimum number of samples to require for multisampled anti-aliasing. " + "Setting this value to 0 or 1 disables MSAA. If it is not 0 or 1, it must " + "be one of 2, 4, 8, or 16. However, if the GPU does not support the " + "requested sampling value, MSAA will be disabled.") DEF_SWITCHES_END void PrintUsage(const std::string& executable_name); diff --git a/engine/src/flutter/shell/common/switches_unittests.cc b/engine/src/flutter/shell/common/switches_unittests.cc index 3b0dcce0722..c919e873dff 100644 --- a/engine/src/flutter/shell/common/switches_unittests.cc +++ b/engine/src/flutter/shell/common/switches_unittests.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + #include "flutter/common/settings.h" #include "flutter/fml/command_line.h" #include "flutter/shell/common/switches.h" @@ -56,5 +58,23 @@ TEST(SwitchesTest, RouteParsedFlag) { EXPECT_TRUE(settings.route.empty()); } +TEST(SwitchesTest, MsaaSamples) { + for (int samples : {0, 1, 2, 4, 8, 16}) { + fml::CommandLine command_line = fml::CommandLineFromInitializerList( + {"command", ("--msaa-samples=" + std::to_string(samples)).c_str()}); + Settings settings = SettingsFromCommandLine(command_line); + EXPECT_EQ(settings.msaa_samples, samples); + } + fml::CommandLine command_line = + fml::CommandLineFromInitializerList({"command", "--msaa-samples=3"}); + Settings settings = SettingsFromCommandLine(command_line); + EXPECT_EQ(settings.msaa_samples, 0); + + command_line = + fml::CommandLineFromInitializerList({"command", "--msaa-samples=foobar"}); + settings = SettingsFromCommandLine(command_line); + EXPECT_EQ(settings.msaa_samples, 0); +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/shell/gpu/gpu_surface_metal_skia.mm b/engine/src/flutter/shell/gpu/gpu_surface_metal_skia.mm index 99271510acb..8dc1e614140 100644 --- a/engine/src/flutter/shell/gpu/gpu_surface_metal_skia.mm +++ b/engine/src/flutter/shell/gpu/gpu_surface_metal_skia.mm @@ -112,6 +112,17 @@ std::unique_ptr GPUSurfaceMetalSkia::AcquireFrameFromCAMetalLayer( return nullptr; } + // TODO(dnfield): Enable MSAA behind a flag if the settings say to. + // https://github.com/flutter/flutter/issues/100392 + // Rough outline of steps needed: + // Create memoryless texture + // auto texture = drawable.get().texture; + // MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: + // texture.pixelFormat width: texture.width height: texture.height: mipmapped: texture.mipmapped]; + // desc.sampleCount = 4; + // Type should be multisample, count 4 or 8 (4 always supported) + // Give the new texture to skia in this call, using the same sampleCount value + // Resolve texture auto surface = CreateSurfaceFromMetalTexture(context_.get(), drawable.get().texture, kTopLeft_GrSurfaceOrigin, // origin 1, // sample count diff --git a/engine/src/flutter/shell/platform/android/android_context_gl.cc b/engine/src/flutter/shell/platform/android/android_context_gl.cc index a04c333abe1..bf7f0fb4dcf 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl.cc +++ b/engine/src/flutter/shell/platform/android/android_context_gl.cc @@ -73,7 +73,9 @@ static EGLResult CreateContext(EGLDisplay display, return {context != EGL_NO_CONTEXT, context}; } -static EGLResult ChooseEGLConfiguration(EGLDisplay display) { +static EGLResult ChooseEGLConfiguration(EGLDisplay display, + uint8_t msaa_samples) { + EGLint sample_buffers = msaa_samples > 1 ? 1 : 0; EGLint attributes[] = { // clang-format off EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, @@ -84,6 +86,8 @@ static EGLResult ChooseEGLConfiguration(EGLDisplay display) { EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 8, + EGL_SAMPLES, static_cast(msaa_samples), + EGL_SAMPLE_BUFFERS, sample_buffers, EGL_NONE, // termination sentinel // clang-format on }; @@ -292,7 +296,8 @@ SkISize AndroidEGLSurface::GetSize() const { AndroidContextGL::AndroidContextGL( AndroidRenderingAPI rendering_api, fml::RefPtr environment, - const TaskRunners& task_runners) + const TaskRunners& task_runners, + uint8_t msaa_samples) : AndroidContext(AndroidRenderingAPI::kOpenGLES), environment_(environment), config_(nullptr), @@ -305,7 +310,8 @@ AndroidContextGL::AndroidContextGL( bool success = false; // Choose a valid configuration. - std::tie(success, config_) = ChooseEGLConfiguration(environment_->Display()); + std::tie(success, config_) = + ChooseEGLConfiguration(environment_->Display(), msaa_samples); if (!success) { FML_LOG(ERROR) << "Could not choose an EGL configuration."; LogLastEGLError(); diff --git a/engine/src/flutter/shell/platform/android/android_context_gl.h b/engine/src/flutter/shell/platform/android/android_context_gl.h index 38f17280744..4bd4e1886e9 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl.h +++ b/engine/src/flutter/shell/platform/android/android_context_gl.h @@ -98,7 +98,8 @@ class AndroidContextGL : public AndroidContext { public: AndroidContextGL(AndroidRenderingAPI rendering_api, fml::RefPtr environment, - const TaskRunners& taskRunners); + const TaskRunners& taskRunners, + uint8_t msaa_samples); ~AndroidContextGL(); @@ -151,6 +152,11 @@ class AndroidContextGL : public AndroidContext { /// EGLContext CreateNewContext() const; + //---------------------------------------------------------------------------- + /// @brief The EGLConfig for this context. + /// + EGLConfig Config() const { return config_; } + private: fml::RefPtr environment_; EGLConfig config_; diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_unittests.cc b/engine/src/flutter/shell/platform/android/android_context_gl_unittests.cc index 9b376bc8482..fd327187a21 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl_unittests.cc +++ b/engine/src/flutter/shell/platform/android/android_context_gl_unittests.cc @@ -86,7 +86,7 @@ TEST(AndroidContextGl, Create) { ThreadHost::Type::UI | ThreadHost::Type::RASTER | ThreadHost::Type::IO)); TaskRunners task_runners = MakeTaskRunners(thread_label, thread_host); auto context = std::make_unique( - AndroidRenderingAPI::kOpenGLES, environment, task_runners); + AndroidRenderingAPI::kOpenGLES, environment, task_runners, 0); context->SetMainSkiaContext(main_context); EXPECT_NE(context.get(), nullptr); context.reset(); @@ -107,7 +107,7 @@ TEST(AndroidContextGl, CreateSingleThread) { TaskRunners(thread_label, platform_runner, platform_runner, platform_runner, platform_runner); auto context = std::make_unique( - AndroidRenderingAPI::kOpenGLES, environment, task_runners); + AndroidRenderingAPI::kOpenGLES, environment, task_runners, 0); context->SetMainSkiaContext(main_context); EXPECT_NE(context.get(), nullptr); context.reset(); @@ -126,7 +126,7 @@ TEST(AndroidSurfaceGL, CreateSnapshopSurfaceWhenOnscreenSurfaceIsNotNull) { ThreadHost::Type::UI | ThreadHost::Type::RASTER | ThreadHost::Type::IO)); TaskRunners task_runners = MakeTaskRunners(thread_label, thread_host); auto android_context = std::make_shared( - AndroidRenderingAPI::kOpenGLES, environment, task_runners); + AndroidRenderingAPI::kOpenGLES, environment, task_runners, 0); auto jni = std::make_shared(); auto android_surface = std::make_unique(android_context, jni); @@ -154,7 +154,7 @@ TEST(AndroidSurfaceGL, CreateSnapshopSurfaceWhenOnscreenSurfaceIsNull) { ThreadHost thread_host(host_config); TaskRunners task_runners = MakeTaskRunners(thread_label, thread_host); auto android_context = std::make_shared( - AndroidRenderingAPI::kOpenGLES, environment, task_runners); + AndroidRenderingAPI::kOpenGLES, environment, task_runners, 0); auto jni = std::make_shared(); auto android_surface = std::make_unique(android_context, jni); @@ -162,6 +162,28 @@ TEST(AndroidSurfaceGL, CreateSnapshopSurfaceWhenOnscreenSurfaceIsNull) { android_surface->CreateSnapshotSurface(); EXPECT_NE(android_surface->GetOnscreenSurface(), nullptr); } + +TEST(AndroidContextGl, MSAAx4) { + GrMockOptions main_context_options; + sk_sp main_context = + GrDirectContext::MakeMock(&main_context_options); + auto environment = fml::MakeRefCounted(); + std::string thread_label = + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + + ThreadHost thread_host(ThreadHost::ThreadHostConfig( + thread_label, + ThreadHost::Type::UI | ThreadHost::Type::RASTER | ThreadHost::Type::IO)); + TaskRunners task_runners = MakeTaskRunners(thread_label, thread_host); + auto context = std::make_unique( + AndroidRenderingAPI::kOpenGLES, environment, task_runners, 4); + context->SetMainSkiaContext(main_context); + + EGLint sample_count; + eglGetConfigAttrib(environment->Display(), context->Config(), EGL_SAMPLES, + &sample_count); + EXPECT_EQ(sample_count, 4); +} } // namespace android } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder.cc b/engine/src/flutter/shell/platform/android/android_shell_holder.cc index a78a9b8e752..b1b2a048f50 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder.cc +++ b/engine/src/flutter/shell/platform/android/android_shell_holder.cc @@ -113,7 +113,8 @@ AndroidShellHolder::AndroidShellHolder( shell.GetTaskRunners(), // task runners jni_facade, // JNI interop shell.GetSettings() - .enable_software_rendering // use software rendering + .enable_software_rendering, // use software rendering + shell.GetSettings().msaa_samples // msaa sample count ); weak_platform_view = platform_view_android->GetWeakPtr(); std::vector> displays; diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java index 7b861171a1b..a9b9a382a74 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java @@ -56,6 +56,8 @@ public class FlutterShellArgs { public static final String ARG_OBSERVATORY_PORT = "--observatory-port="; public static final String ARG_KEY_DART_FLAGS = "dart-flags"; public static final String ARG_DART_FLAGS = "--dart-flags"; + public static final String ARG_KEY_MSAA_SAMPLES = "msaa-samples"; + public static final String ARG_MSAA_SAMPLES = "--msaa-samples"; @NonNull public static FlutterShellArgs fromIntent(@NonNull Intent intent) { @@ -116,6 +118,10 @@ public class FlutterShellArgs { if (intent.getBooleanExtra(ARG_KEY_VERBOSE_LOGGING, false)) { args.add(ARG_VERBOSE_LOGGING); } + final int msaaSamples = intent.getIntExtra("msaa-samples", 0); + if (msaaSamples > 1) { + args.add("--msaa-samples=" + Integer.toString(msaaSamples)); + } // NOTE: all flags provided with this argument are subject to filtering // based on a a list of allowed flags in shell/common/switches.cc. If any diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.cc b/engine/src/flutter/shell/platform/android/platform_view_android.cc index f259c73b495..63c4b42d5e1 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android.cc @@ -46,25 +46,28 @@ std::unique_ptr AndroidSurfaceFactoryImpl::CreateSurface() { static std::shared_ptr CreateAndroidContext( bool use_software_rendering, - const flutter::TaskRunners task_runners) { + const flutter::TaskRunners task_runners, + uint8_t msaa_samples) { if (use_software_rendering) { return std::make_shared(AndroidRenderingAPI::kSoftware); } return std::make_unique( AndroidRenderingAPI::kOpenGLES, - fml::MakeRefCounted(), task_runners); + fml::MakeRefCounted(), task_runners, msaa_samples); } PlatformViewAndroid::PlatformViewAndroid( PlatformView::Delegate& delegate, flutter::TaskRunners task_runners, std::shared_ptr jni_facade, - bool use_software_rendering) - : PlatformViewAndroid( - delegate, - std::move(task_runners), - std::move(jni_facade), - CreateAndroidContext(use_software_rendering, task_runners)) {} + bool use_software_rendering, + uint8_t msaa_samples) + : PlatformViewAndroid(delegate, + std::move(task_runners), + std::move(jni_facade), + CreateAndroidContext(use_software_rendering, + task_runners, + msaa_samples)) {} PlatformViewAndroid::PlatformViewAndroid( PlatformView::Delegate& delegate, diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.h b/engine/src/flutter/shell/platform/android/platform_view_android.h index 0ff654f0fba..1226af1681f 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.h +++ b/engine/src/flutter/shell/platform/android/platform_view_android.h @@ -45,7 +45,8 @@ class PlatformViewAndroid final : public PlatformView { PlatformViewAndroid(PlatformView::Delegate& delegate, flutter::TaskRunners task_runners, std::shared_ptr jni_facade, - bool use_software_rendering); + bool use_software_rendering, + uint8_t msaa_samples); //---------------------------------------------------------------------------- /// @brief Creates a new PlatformViewAndroid but using an existing diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java index 954b857a89f..334b88f73aa 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java @@ -31,4 +31,27 @@ public class FlutterShellArgsTest { assertTrue(argValues.contains("--dart-flags=--observe --no-hot --no-pub")); assertTrue(argValues.contains("--trace-skia-allowlist=skia.a,skia.b")); } + + @Test + public void itHandles4xMsaaFlag() { + Intent intent = new Intent(); + intent.putExtra("msaa-samples", 4); + + FlutterShellArgs args = FlutterShellArgs.fromIntent(intent); + HashSet argValues = new HashSet(Arrays.asList(args.toArray())); + + assertEquals(1, argValues.size()); + assertTrue(argValues.contains("--msaa-samples=4")); + } + + @Test + public void itHandles1xMsaaFlag() { + Intent intent = new Intent(); + intent.putExtra("msaa-samples", 1); + + FlutterShellArgs args = FlutterShellArgs.fromIntent(intent); + HashSet argValues = new HashSet(Arrays.asList(args.toArray())); + + assertEquals(0, argValues.size()); + } } diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc index 693f6f69bcf..9eda0ffe10a 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder.cc @@ -718,10 +718,12 @@ static sk_sp MakeSkSurfaceFromBackingStore( context, // context backend_texture, // back-end texture kTopLeft_GrSurfaceOrigin, // surface origin - 1, // sample count - kBGRA_8888_SkColorType, // color type - nullptr, // color space - &surface_properties, // surface properties + // TODO(dnfield): Update this when embedders support MSAA, see + // https://github.com/flutter/flutter/issues/100392 + 1, // sample count + kBGRA_8888_SkColorType, // color type + nullptr, // color space + &surface_properties, // surface properties static_cast( metal->texture.destruction_callback), // release proc metal->texture.user_data // release context diff --git a/engine/src/flutter/shell/platform/windows/angle_surface_manager.cc b/engine/src/flutter/shell/platform/windows/angle_surface_manager.cc index 28d41cfc631..336aff0fff0 100644 --- a/engine/src/flutter/shell/platform/windows/angle_surface_manager.cc +++ b/engine/src/flutter/shell/platform/windows/angle_surface_manager.cc @@ -75,6 +75,9 @@ bool AngleSurfaceManager::InitializeEGL( } bool AngleSurfaceManager::Initialize() { + // TODO(dnfield): Enable MSAA here, see similar code in android_context_gl.cc + // Will need to plumb in argument from project bundle for sampling rate. + // https://github.com/flutter/flutter/issues/100392 const EGLint config_attributes[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8,