[Impeller] implemented golden image tests for opengles (flutter/engine#50146)

fixes https://github.com/flutter/flutter/issues/142354

This sets up impeller_golden_tests to run with ANGLE.

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
gaaclarke
2024-01-31 12:49:40 -08:00
committed by GitHub
parent 6ae0666bb7
commit c35dbe26fc
10 changed files with 149 additions and 76 deletions

View File

@@ -159,6 +159,7 @@
"--prebuilt-dart-sdk",
"--build-embedder-examples",
"--enable-impeller-vulkan",
"--enable-impeller-opengles",
"--use-glfw-swiftshader"
],
"name": "host_release",

View File

@@ -84,6 +84,8 @@ if (is_mac) {
"//flutter/impeller/aiks:aiks_unittests_golden",
"//flutter/impeller/fixtures",
"//flutter/third_party/swiftshader",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/googletest:gtest",
]
}

View File

@@ -55,47 +55,33 @@ const std::unique_ptr<PlaygroundImpl>& GetSharedVulkanPlayground(
}
} // namespace
#define IMP_AIKSTEST(name) \
"impeller_Play_AiksTest_" #name "_Metal", \
"impeller_Play_AiksTest_" #name "_OpenGLES", \
"impeller_Play_AiksTest_" #name "_Vulkan"
// If you add a new playground test to the aiks unittests and you do not want it
// to also be a golden test, then add the test name here.
static const std::vector<std::string> kSkipTests = {
"impeller_Play_AiksTest_CanDrawPaintMultipleTimesInteractive_Metal",
"impeller_Play_AiksTest_CanDrawPaintMultipleTimesInteractive_Vulkan",
"impeller_Play_AiksTest_CanRenderLinearGradientManyColorsUnevenStops_Metal",
"impeller_Play_AiksTest_CanRenderLinearGradientManyColorsUnevenStops_"
"Vulkan",
"impeller_Play_AiksTest_CanRenderRadialGradient_Metal",
"impeller_Play_AiksTest_CanRenderRadialGradient_Vulkan",
"impeller_Play_AiksTest_CanRenderRadialGradientManyColors_Metal",
"impeller_Play_AiksTest_CanRenderRadialGradientManyColors_Vulkan",
"impeller_Play_AiksTest_CanRenderBackdropBlurInteractive_Metal",
"impeller_Play_AiksTest_CanRenderBackdropBlurInteractive_Vulkan",
"impeller_Play_AiksTest_ClippedBlurFilterRendersCorrectlyInteractive_Metal",
"impeller_Play_AiksTest_ClippedBlurFilterRendersCorrectlyInteractive_"
"Vulkan",
"impeller_Play_AiksTest_CoverageOriginShouldBeAccountedForInSubpasses_"
"Metal",
"impeller_Play_AiksTest_CoverageOriginShouldBeAccountedForInSubpasses_"
"Vulkan",
"impeller_Play_AiksTest_GaussianBlurRotatedAndClippedInteractive_Metal",
"impeller_Play_AiksTest_GaussianBlurRotatedAndClippedInteractive_Vulkan",
"impeller_Play_AiksTest_GradientStrokesRenderCorrectly_Metal",
"impeller_Play_AiksTest_GradientStrokesRenderCorrectly_Vulkan",
"impeller_Play_AiksTest_ColorWheel_Metal",
"impeller_Play_AiksTest_ColorWheel_Vulkan",
"impeller_Play_AiksTest_SceneColorSource_Metal",
"impeller_Play_AiksTest_SceneColorSource_Vulkan",
"impeller_Play_AiksTest_SolidStrokesRenderCorrectly_Metal",
"impeller_Play_AiksTest_SolidStrokesRenderCorrectly_Vulkan",
"impeller_Play_AiksTest_TextFrameSubpixelAlignment_Metal",
"impeller_Play_AiksTest_TextFrameSubpixelAlignment_Vulkan",
IMP_AIKSTEST(CanDrawPaintMultipleTimesInteractive),
IMP_AIKSTEST(CanRenderLinearGradientManyColorsUnevenStops),
IMP_AIKSTEST(CanRenderRadialGradient),
IMP_AIKSTEST(CanRenderRadialGradientManyColors),
IMP_AIKSTEST(CanRenderBackdropBlurInteractive),
IMP_AIKSTEST(ClippedBlurFilterRendersCorrectlyInteractive),
IMP_AIKSTEST(CoverageOriginShouldBeAccountedForInSubpasses),
IMP_AIKSTEST(GaussianBlurRotatedAndClippedInteractive),
IMP_AIKSTEST(GradientStrokesRenderCorrectly),
IMP_AIKSTEST(ColorWheel),
IMP_AIKSTEST(SceneColorSource),
IMP_AIKSTEST(SolidStrokesRenderCorrectly),
IMP_AIKSTEST(TextFrameSubpixelAlignment),
// TextRotated is flakey and we can't seem to get it to stabilize on Skia
// Gold.
"impeller_Play_AiksTest_TextRotated_Metal",
"impeller_Play_AiksTest_TextRotated_Vulkan",
IMP_AIKSTEST(TextRotated),
// Runtime stage based tests get confused with a Metal context.
"impeller_Play_AiksTest_CanRenderClippedRuntimeEffects_Vulkan",
"impeller_Play_AiksTest_CaptureContext_Metal",
"impeller_Play_AiksTest_CaptureContext_Vulkan",
IMP_AIKSTEST(CaptureContext),
};
static const std::vector<std::string> kVulkanDenyValidationTests = {
@@ -143,6 +129,7 @@ bool ShouldTestHaveVulkanValidations() {
struct GoldenPlaygroundTest::GoldenPlaygroundTestImpl {
std::unique_ptr<PlaygroundImpl> test_vulkan_playground;
std::unique_ptr<PlaygroundImpl> test_opengl_playground;
std::unique_ptr<testing::Screenshotter> screenshotter;
ISize window_size = ISize{1024, 768};
};
@@ -172,13 +159,29 @@ void GoldenPlaygroundTest::SetUp() {
std::filesystem::path icd_path = target_path / "vk_swiftshader_icd.json";
setenv("VK_ICD_FILENAMES", icd_path.c_str(), 1);
if (GetBackend() != PlaygroundBackend::kMetal &&
GetBackend() != PlaygroundBackend::kVulkan) {
GTEST_SKIP_("GoldenPlaygroundTest doesn't support this backend type.");
return;
}
bool enable_vulkan_validations = ShouldTestHaveVulkanValidations();
switch (GetParam()) {
case PlaygroundBackend::kMetal:
pimpl_->screenshotter = std::make_unique<testing::MetalScreenshotter>();
break;
case PlaygroundBackend::kVulkan: {
const std::unique_ptr<PlaygroundImpl>& playground =
GetSharedVulkanPlayground(enable_vulkan_validations);
pimpl_->screenshotter =
std::make_unique<testing::VulkanScreenshotter>(playground);
break;
}
case PlaygroundBackend::kOpenGLES: {
FML_CHECK(::glfwInit() == GLFW_TRUE);
PlaygroundSwitches playground_switches;
playground_switches.use_angle = true;
pimpl_->test_opengl_playground = PlaygroundImpl::Create(
PlaygroundBackend::kOpenGLES, playground_switches);
pimpl_->screenshotter = std::make_unique<testing::VulkanScreenshotter>(
pimpl_->test_opengl_playground);
break;
}
}
if (GetParam() == PlaygroundBackend::kMetal) {
pimpl_->screenshotter = std::make_unique<testing::MetalScreenshotter>();
} else if (GetParam() == PlaygroundBackend::kVulkan) {
@@ -272,8 +275,8 @@ std::shared_ptr<Context> GoldenPlaygroundTest::MakeContext() const {
pimpl_->test_vulkan_playground);
return pimpl_->test_vulkan_playground->GetContext();
} else {
FML_CHECK(false);
return nullptr;
/// On OpenGL we create a context for each test.
return GetContext();
}
}

View File

@@ -6,8 +6,6 @@
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/impeller/golden_tests/metal_screenshot.h"
#include "impeller/renderer/backend/vulkan/surface_context_vk.h"
#include "impeller/renderer/backend/vulkan/texture_vk.h"
#define GLFW_INCLUDE_NONE
#include "third_party/glfw/include/GLFW/glfw3.h"
@@ -15,6 +13,15 @@ namespace impeller {
namespace testing {
namespace {
using CGContextPtr = std::unique_ptr<std::remove_pointer<CGContextRef>::type,
decltype(&CGContextRelease)>;
using CGImagePtr = std::unique_ptr<std::remove_pointer<CGImageRef>::type,
decltype(&CGImageRelease)>;
using CGColorSpacePtr =
std::unique_ptr<std::remove_pointer<CGColorSpaceRef>::type,
decltype(&CGColorSpaceRelease)>;
std::unique_ptr<Screenshot> ReadTexture(
const std::shared_ptr<Context>& surface_context,
const std::shared_ptr<Texture>& texture) {
@@ -49,21 +56,45 @@ std::unique_ptr<Screenshot> ReadTexture(
// TODO(gaaclarke): Replace CoreImage requirement with something
// crossplatform.
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
CGColorSpacePtr color_space(CGColorSpaceCreateDeviceRGB(),
&CGColorSpaceRelease);
CGBitmapInfo bitmap_info =
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little;
CGContextRef context = CGBitmapContextCreate(
device_buffer->OnGetContents(), texture->GetSize().width,
texture->GetSize().height,
/*bitsPerComponent=*/8,
/*bytesPerRow=*/texture->GetTextureDescriptor().GetBytesPerRow(),
color_space, bitmap_info);
texture->GetTextureDescriptor().format == PixelFormat::kB8G8R8A8UNormInt
? kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little
: kCGImageAlphaPremultipliedLast;
CGContextPtr context(
CGBitmapContextCreate(
device_buffer->OnGetContents(), texture->GetSize().width,
texture->GetSize().height,
/*bitsPerComponent=*/8,
/*bytesPerRow=*/texture->GetTextureDescriptor().GetBytesPerRow(),
color_space.get(), bitmap_info),
&CGContextRelease);
FML_CHECK(context);
CGImageRef image_ref = CGBitmapContextCreateImage(context);
FML_CHECK(image_ref);
CGContextRelease(context);
CGColorSpaceRelease(color_space);
return std::make_unique<MetalScreenshot>(image_ref);
CGImagePtr image(CGBitmapContextCreateImage(context.get()), &CGImageRelease);
FML_CHECK(image);
// TODO(tbd): Perform the flip at the blit stage to avoid this slow
// copy.
if (texture->GetYCoordScale() == -1) {
CGContextPtr flipped_context(
CGBitmapContextCreate(
nullptr, texture->GetSize().width, texture->GetSize().height,
/*bitsPerComponent=*/8,
/*bytesPerRow=*/0, color_space.get(), bitmap_info),
&CGContextRelease);
CGContextTranslateCTM(flipped_context.get(), 0, texture->GetSize().height);
CGContextScaleCTM(flipped_context.get(), 1.0, -1.0);
CGContextDrawImage(
flipped_context.get(),
CGRectMake(0, 0, texture->GetSize().width, texture->GetSize().height),
image.get());
CGImagePtr flipped_image(CGBitmapContextCreateImage(flipped_context.get()),
&CGImageRelease);
image.swap(flipped_image);
}
return std::make_unique<MetalScreenshot>(image.release());
}
} // namespace
@@ -84,8 +115,6 @@ std::unique_ptr<Screenshot> VulkanScreenshotter::MakeScreenshot(
aiks_context,
ISize(size.width * content_scale.x, size.height * content_scale.y));
std::shared_ptr<Texture> texture = image->GetTexture();
FML_CHECK(aiks_context.GetContext()->GetBackendType() ==
Context::BackendType::kVulkan);
return ReadTexture(aiks_context.GetContext(), texture);
}

View File

@@ -4,6 +4,12 @@
#include "impeller/playground/backend/gles/playground_impl_gles.h"
#define IMPELLER_PLAYGROUND_SUPPORTS_ANGLE FML_OS_MACOSX
#if IMPELLER_PLAYGROUND_SUPPORTS_ANGLE
#include <dlfcn.h>
#endif
#define GLFW_INCLUDE_NONE
#include "third_party/glfw/include/GLFW/glfw3.h"
@@ -58,12 +64,26 @@ void PlaygroundImplGLES::DestroyWindowHandle(WindowHandle handle) {
PlaygroundImplGLES::PlaygroundImplGLES(PlaygroundSwitches switches)
: PlaygroundImpl(switches),
handle_(nullptr, &DestroyWindowHandle),
worker_(std::shared_ptr<ReactorWorker>(new ReactorWorker())) {
worker_(std::shared_ptr<ReactorWorker>(new ReactorWorker())),
use_angle_(switches.use_angle) {
if (use_angle_) {
#if IMPELLER_PLAYGROUND_SUPPORTS_ANGLE
angle_glesv2_ = dlopen("libGLESv2.dylib", RTLD_LAZY);
#endif
FML_CHECK(angle_glesv2_ != nullptr);
}
::glfwDefaultWindowHints();
#if FML_OS_MACOSX
// ES Profiles are not supported on Mac.
::glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
if (use_angle_) {
::glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
::glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
::glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
::glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
} else {
::glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
}
#else // FML_OS_MACOSX
::glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
::glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
@@ -113,9 +133,18 @@ ShaderLibraryMappingsForPlayground() {
// |PlaygroundImpl|
std::shared_ptr<Context> PlaygroundImplGLES::GetContext() const {
auto resolver = [](const char* name) -> void* {
return reinterpret_cast<void*>(::glfwGetProcAddress(name));
};
auto resolver = use_angle_ ? [](const char* name) -> void* {
void* symbol = nullptr;
#if IMPELLER_PLAYGROUND_SUPPORTS_ANGLE
void* angle_glesv2 = dlopen("libGLESv2.dylib", RTLD_LAZY);
symbol = dlsym(angle_glesv2, name);
#endif
FML_CHECK(symbol);
return symbol;
}
: [](const char* name) -> void* {
return reinterpret_cast<void*>(::glfwGetProcAddress(name));
};
auto gl = std::make_unique<ProcTableGLES>(resolver);
if (!gl->IsValid()) {
FML_LOG(ERROR) << "Proc table when creating a playground was invalid.";

View File

@@ -26,6 +26,8 @@ class PlaygroundImplGLES final : public PlaygroundImpl {
using UniqueHandle = std::unique_ptr<void, decltype(&DestroyWindowHandle)>;
UniqueHandle handle_;
std::shared_ptr<ReactorWorker> worker_;
const bool use_angle_;
void* angle_glesv2_;
// |PlaygroundImpl|
std::shared_ptr<Context> GetContext() const override;

View File

@@ -20,6 +20,7 @@ struct PlaygroundSwitches {
// rendered in the playground.
std::optional<std::chrono::milliseconds> timeout;
bool enable_vulkan_validation = false;
bool use_angle = false;
PlaygroundSwitches();

View File

@@ -641,11 +641,7 @@ template("_impeller_shaders_gles") {
if (impeller_enable_metal || impeller_enable_vulkan) {
intermediates_subdir = "gles"
}
if (is_mac) {
shader_target_flags = [ "--opengl-desktop" ]
} else {
shader_target_flags = [ "--opengl-es" ]
}
shader_target_flags = [ "--opengl-es" ]
defines = [ "IMPELLER_TARGET_OPENGLES" ]
}

View File

@@ -518,12 +518,16 @@ def run_cc_tests(build_dir, executable_filter, coverage, capture_core_dump):
'METAL_DEBUG_ERROR_MODE': '0', # Enables metal validation.
'METAL_DEVICE_WRAPPER_TYPE': '1', # Enables metal validation.
})
mac_impeller_unittests_flags = shuffle_flags + [
'--enable_vulkan_validation',
'--gtest_filter=-*OpenGLES' # These are covered in the golden tests.
]
# Impeller tests are only supported on macOS for now.
run_engine_executable(
build_dir,
'impeller_unittests',
executable_filter,
shuffle_flags + ['--enable_vulkan_validation'],
mac_impeller_unittests_flags,
coverage=coverage,
extra_env=extra_env,
# TODO(https://github.com/flutter/flutter/issues/123733): Remove this allowlist.
@@ -558,7 +562,10 @@ def run_cc_tests(build_dir, executable_filter, coverage, capture_core_dump):
build_dir,
'impeller_dart_unittests',
executable_filter,
shuffle_flags + ['--enable_vulkan_validation'],
shuffle_flags + [
'--enable_vulkan_validation',
'--gtest_filter=-*OpenGLES', # TODO(tbd)
],
coverage=coverage,
extra_env=extra_env,
)
@@ -1107,7 +1114,7 @@ def run_impeller_golden_tests(build_dir: str):
'golden_tests_harvester'
)
with tempfile.TemporaryDirectory(prefix='impeller_golden') as temp_dir:
run_cmd([tests_path, '--working_dir=%s' % temp_dir])
run_cmd([tests_path, '--working_dir=%s' % temp_dir], cwd=build_dir)
with DirectoryChange(harvester_path):
run_cmd(['dart', 'pub', 'get'])
bin_path = Path('.').joinpath('bin'

View File

@@ -717,9 +717,8 @@ def to_gn_args(args):
# gen_snapshot, but the build defines otherwise make it look like the build is
# for a host Windows build and make GN think we will be building ANGLE.
# Angle is not used on Mac hosts as there are no tests for the OpenGL backend.
if (is_host_build(args) and
gn_args['host_os'] != 'mac') or (args.target_os == 'android' and
get_host_os() == 'win'):
if is_host_build(args) or (args.target_os == 'android' and
get_host_os() == 'win'):
# Do not build unnecessary parts of the ANGLE tree.
gn_args['angle_build_all'] = False
gn_args['angle_has_astc_encoder'] = False
@@ -728,6 +727,10 @@ def to_gn_args(args):
# https://github.com/flutter/flutter/issues/114107
if get_host_os() == 'win':
gn_args['angle_force_context_check_every_call'] = True
if get_host_os() == 'mac':
gn_args['angle_enable_metal'] = True
gn_args['angle_enable_gl'] = False
gn_args['angle_enable_vulkan'] = False
# ANGLE and SwiftShader share build flags to enable X11 and Wayland,
# but we only need these enabled for SwiftShader.