[Impeller] adds vulkan golden images (flutter/engine#49849)

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

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
gaaclarke
2024-01-19 14:44:38 -08:00
committed by GitHub
parent 07034d9f1d
commit cbcce599fc
19 changed files with 303 additions and 35 deletions

View File

@@ -5317,6 +5317,10 @@ ORIGIN: ../../../flutter/impeller/golden_tests/metal_screenshot.h + ../../../flu
ORIGIN: ../../../flutter/impeller/golden_tests/metal_screenshot.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests/metal_screenshotter.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests/metal_screenshotter.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests/screenshot.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests/screenshotter.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests/vulkan_screenshotter.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests/vulkan_screenshotter.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests/working_directory.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests/working_directory.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart + ../../../flutter/LICENSE
@@ -8155,6 +8159,10 @@ FILE: ../../../flutter/impeller/golden_tests/metal_screenshot.h
FILE: ../../../flutter/impeller/golden_tests/metal_screenshot.mm
FILE: ../../../flutter/impeller/golden_tests/metal_screenshotter.h
FILE: ../../../flutter/impeller/golden_tests/metal_screenshotter.mm
FILE: ../../../flutter/impeller/golden_tests/screenshot.h
FILE: ../../../flutter/impeller/golden_tests/screenshotter.h
FILE: ../../../flutter/impeller/golden_tests/vulkan_screenshotter.h
FILE: ../../../flutter/impeller/golden_tests/vulkan_screenshotter.mm
FILE: ../../../flutter/impeller/golden_tests/working_directory.cc
FILE: ../../../flutter/impeller/golden_tests/working_directory.h
FILE: ../../../flutter/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart

View File

@@ -58,10 +58,9 @@ std::shared_ptr<DlSurfaceInstance> DlMetalSurfaceProvider::MakeOffscreenSurface(
}
class DlMetalPixelData : public DlPixelData {
using MetalScreenshot = impeller::testing::MetalScreenshot;
public:
explicit DlMetalPixelData(std::unique_ptr<MetalScreenshot> screenshot)
explicit DlMetalPixelData(
std::unique_ptr<impeller::testing::Screenshot> screenshot)
: screenshot_(std::move(screenshot)),
addr_(reinterpret_cast<const uint32_t*>(screenshot_->GetBytes())),
ints_per_row_(screenshot_->GetBytesPerRow() / 4) {
@@ -79,7 +78,7 @@ class DlMetalPixelData : public DlPixelData {
}
private:
std::unique_ptr<MetalScreenshot> screenshot_;
std::unique_ptr<impeller::testing::Screenshot> screenshot_;
const uint32_t* addr_;
const uint32_t ints_per_row_;
};

View File

@@ -3816,6 +3816,8 @@ TEST_P(AiksTest, GaussianBlurMipMapNestedLayer) {
std::max(it->texture->GetTextureDescriptor().mip_count, max_mip_count);
}
EXPECT_EQ(max_mip_count, blur_required_mip_count);
// The log is FML_DLOG, so only check in debug builds.
#ifndef NDEBUG
if (GetParam() == PlaygroundBackend::kMetal) {
EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
std::string::npos);
@@ -3823,6 +3825,7 @@ TEST_P(AiksTest, GaussianBlurMipMapNestedLayer) {
EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
std::string::npos);
}
#endif
}
TEST_P(AiksTest, GaussianBlurMipMapImageFilter) {
@@ -3849,6 +3852,8 @@ TEST_P(AiksTest, GaussianBlurMipMapImageFilter) {
std::max(it->texture->GetTextureDescriptor().mip_count, max_mip_count);
}
EXPECT_EQ(max_mip_count, blur_required_mip_count);
// The log is FML_DLOG, so only check in debug builds.
#ifndef NDEBUG
if (GetParam() == PlaygroundBackend::kMetal) {
EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
std::string::npos);
@@ -3856,6 +3861,7 @@ TEST_P(AiksTest, GaussianBlurMipMapImageFilter) {
EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
std::string::npos);
}
#endif
}
} // namespace testing

View File

@@ -49,6 +49,10 @@ if (is_mac) {
"metal_screenshot.mm",
"metal_screenshotter.h",
"metal_screenshotter.mm",
"screenshot.h",
"screenshotter.h",
"vulkan_screenshotter.h",
"vulkan_screenshotter.mm",
]
deps = [
@@ -56,6 +60,7 @@ if (is_mac) {
"//flutter/impeller/display_list",
"//flutter/impeller/playground",
"//flutter/impeller/renderer/backend/metal:metal",
"//flutter/impeller/renderer/backend/vulkan",
]
}

View File

@@ -11,6 +11,7 @@
#include "flutter/impeller/aiks/picture.h"
#include "flutter/impeller/golden_tests/golden_digest.h"
#include "flutter/impeller/golden_tests/metal_screenshotter.h"
#include "flutter/impeller/golden_tests/vulkan_screenshotter.h"
#include "impeller/typographer/backends/skia/typographer_context_skia.h"
#include "impeller/typographer/typographer_context.h"
@@ -57,6 +58,9 @@ static const std::vector<std::string> kSkipTests = {
"impeller_Play_AiksTest_CanRenderClippedRuntimeEffects_Vulkan",
"impeller_Play_AiksTest_CaptureContext_Metal",
"impeller_Play_AiksTest_CaptureContext_Vulkan",
// TODO(https://github.com/flutter/flutter/issues/141891): This tests
// crashes on vulkan and needs to be fixed.
"impeller_Play_AiksTest_DrawPaintTransformsBounds_Vulkan",
};
namespace {
@@ -77,7 +81,7 @@ std::string GetGoldenFilename() {
return GetTestName() + ".png";
}
bool SaveScreenshot(std::unique_ptr<testing::MetalScreenshot> screenshot) {
bool SaveScreenshot(std::unique_ptr<testing::Screenshot> screenshot) {
if (!screenshot || !screenshot->GetBytes()) {
return false;
}
@@ -91,15 +95,19 @@ bool SaveScreenshot(std::unique_ptr<testing::MetalScreenshot> screenshot) {
} // namespace
struct GoldenPlaygroundTest::GoldenPlaygroundTestImpl {
GoldenPlaygroundTestImpl()
: screenshotter(new testing::MetalScreenshotter()) {}
std::unique_ptr<testing::MetalScreenshotter> screenshotter;
std::unique_ptr<testing::Screenshotter> screenshotter;
ISize window_size = ISize{1024, 768};
};
GoldenPlaygroundTest::GoldenPlaygroundTest()
: typographer_context_(TypographerContextSkia::Make()),
pimpl_(new GoldenPlaygroundTest::GoldenPlaygroundTestImpl()) {}
pimpl_(new GoldenPlaygroundTest::GoldenPlaygroundTestImpl()) {
if (GetParam() == PlaygroundBackend::kMetal) {
pimpl_->screenshotter = std::make_unique<testing::MetalScreenshotter>();
} else if (GetParam() == PlaygroundBackend::kVulkan) {
pimpl_->screenshotter = std::make_unique<testing::VulkanScreenshotter>();
}
}
GoldenPlaygroundTest::~GoldenPlaygroundTest() = default;
@@ -122,7 +130,8 @@ 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) {
if (GetBackend() != PlaygroundBackend::kMetal &&
GetBackend() != PlaygroundBackend::kVulkan) {
GTEST_SKIP_("GoldenPlaygroundTest doesn't support this backend type.");
return;
}
@@ -157,7 +166,7 @@ bool GoldenPlaygroundTest::OpenPlaygroundHere(
AiksContext renderer(GetContext(), typographer_context_);
std::optional<Picture> picture;
std::unique_ptr<testing::MetalScreenshot> screenshot;
std::unique_ptr<testing::Screenshot> screenshot;
for (int i = 0; i < 2; ++i) {
picture = callback(renderer);
if (!picture.has_value()) {

View File

@@ -34,7 +34,7 @@ std::string GetGoldenFilename() {
return GetTestName() + ".png";
}
bool SaveScreenshot(std::unique_ptr<MetalScreenshot> screenshot) {
bool SaveScreenshot(std::unique_ptr<Screenshot> screenshot) {
if (!screenshot || !screenshot->GetBytes()) {
return false;
}

View File

@@ -5,6 +5,8 @@
#ifndef FLUTTER_IMPELLER_GOLDEN_TESTS_METAL_SCREENSHOT_H_
#define FLUTTER_IMPELLER_GOLDEN_TESTS_METAL_SCREENSHOT_H_
#include "flutter/impeller/golden_tests/screenshot.h"
#include <CoreFoundation/CoreFoundation.h>
#include <CoreImage/CoreImage.h>
#include <string>
@@ -15,23 +17,23 @@ namespace impeller {
namespace testing {
/// A screenshot that was produced from `MetalScreenshotter`.
class MetalScreenshot {
class MetalScreenshot : public Screenshot {
public:
explicit MetalScreenshot(CGImageRef cgImage);
~MetalScreenshot();
const UInt8* GetBytes() const;
const uint8_t* GetBytes() const override;
size_t GetHeight() const;
size_t GetHeight() const override;
size_t GetWidth() const;
size_t GetWidth() const override;
size_t GetBytesPerRow() const;
size_t GetBytesPerRow() const override;
bool WriteToPNG(const std::string& path) const;
bool WriteToPNG(const std::string& path) const override;
private:
friend class MetalScreenshotter;
explicit MetalScreenshot(CGImageRef cgImage);
MetalScreenshot(const MetalScreenshot&) = delete;
MetalScreenshot& operator=(const MetalScreenshot&) = delete;

View File

@@ -17,7 +17,7 @@ MetalScreenshot::~MetalScreenshot() {
CGImageRelease(cg_image_);
}
const UInt8* MetalScreenshot::GetBytes() const {
const uint8_t* MetalScreenshot::GetBytes() const {
return CFDataGetBytePtr(pixel_data_);
}

View File

@@ -8,6 +8,7 @@
#include "flutter/fml/macros.h"
#include "flutter/impeller/aiks/picture.h"
#include "flutter/impeller/golden_tests/metal_screenshot.h"
#include "flutter/impeller/golden_tests/screenshotter.h"
#include "flutter/impeller/playground/playground_impl.h"
namespace impeller {
@@ -15,17 +16,17 @@ namespace testing {
/// Converts `Picture`s and `DisplayList`s to `MetalScreenshot`s with the
/// playground backend.
class MetalScreenshotter {
class MetalScreenshotter : public Screenshotter {
public:
MetalScreenshotter();
std::unique_ptr<MetalScreenshot> MakeScreenshot(AiksContext& aiks_context,
const Picture& picture,
const ISize& size = {300,
300},
bool scale_content = true);
std::unique_ptr<Screenshot> MakeScreenshot(
AiksContext& aiks_context,
const Picture& picture,
const ISize& size = {300, 300},
bool scale_content = true) override;
PlaygroundImpl& GetPlayground() { return *playground_; }
PlaygroundImpl& GetPlayground() override { return *playground_; }
private:
std::unique_ptr<PlaygroundImpl> playground_;

View File

@@ -19,7 +19,7 @@ MetalScreenshotter::MetalScreenshotter() {
PlaygroundImpl::Create(PlaygroundBackend::kMetal, PlaygroundSwitches{});
}
std::unique_ptr<MetalScreenshot> MetalScreenshotter::MakeScreenshot(
std::unique_ptr<Screenshot> MetalScreenshotter::MakeScreenshot(
AiksContext& aiks_context,
const Picture& picture,
const ISize& size,

View File

@@ -0,0 +1,40 @@
// 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_GOLDEN_TESTS_SCREENSHOT_H_
#define FLUTTER_IMPELLER_GOLDEN_TESTS_SCREENSHOT_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
namespace impeller {
namespace testing {
class Screenshot {
public:
virtual ~Screenshot() = default;
/// Access raw data of the screenshot.
virtual const uint8_t* GetBytes() const = 0;
/// Returns the height of the image in pixels.
virtual size_t GetHeight() const = 0;
/// Returns the width of the image in pixels.
virtual size_t GetWidth() const = 0;
/// Returns number of bytes required to represent one row of the raw image.
virtual size_t GetBytesPerRow() const = 0;
/// Synchronously write the screenshot to disk as a PNG at `path`. Returns
/// `true` if it succeeded.
virtual bool WriteToPNG(const std::string& path) const = 0;
};
} // namespace testing
} // namespace impeller
#endif // FLUTTER_IMPELLER_GOLDEN_TESTS_SCREENSHOT_H_

View File

@@ -0,0 +1,34 @@
// 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_GOLDEN_TESTS_SCREENSHOTTER_H_
#define FLUTTER_IMPELLER_GOLDEN_TESTS_SCREENSHOTTER_H_
#include "flutter/fml/macros.h"
#include "flutter/impeller/aiks/picture.h"
#include "flutter/impeller/golden_tests/screenshot.h"
#include "flutter/impeller/playground/playground_impl.h"
namespace impeller {
namespace testing {
/// Converts `Picture`s and `DisplayList`s to `MetalScreenshot`s with the
/// playground backend.
class Screenshotter {
public:
virtual ~Screenshotter() = default;
virtual std::unique_ptr<Screenshot> MakeScreenshot(
AiksContext& aiks_context,
const Picture& picture,
const ISize& size = {300, 300},
bool scale_content = true) = 0;
virtual PlaygroundImpl& GetPlayground() = 0;
};
} // namespace testing
} // namespace impeller
#endif // FLUTTER_IMPELLER_GOLDEN_TESTS_SCREENSHOTTER_H_

View File

@@ -0,0 +1,38 @@
// 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_GOLDEN_TESTS_VULKAN_SCREENSHOTTER_H_
#define FLUTTER_IMPELLER_GOLDEN_TESTS_VULKAN_SCREENSHOTTER_H_
#include "flutter/fml/macros.h"
#include "flutter/impeller/aiks/picture.h"
#include "flutter/impeller/golden_tests/metal_screenshot.h"
#include "flutter/impeller/golden_tests/screenshotter.h"
#include "flutter/impeller/playground/playground_impl.h"
namespace impeller {
namespace testing {
/// Converts `Picture`s and `DisplayList`s to `MetalScreenshot`s with the
/// playground backend.
class VulkanScreenshotter : public Screenshotter {
public:
VulkanScreenshotter();
std::unique_ptr<Screenshot> MakeScreenshot(
AiksContext& aiks_context,
const Picture& picture,
const ISize& size = {300, 300},
bool scale_content = true) override;
PlaygroundImpl& GetPlayground() override { return *playground_; }
private:
std::unique_ptr<PlaygroundImpl> playground_;
};
} // namespace testing
} // namespace impeller
#endif // FLUTTER_IMPELLER_GOLDEN_TESTS_VULKAN_SCREENSHOTTER_H_

View File

@@ -0,0 +1,90 @@
// 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.
#include "flutter/impeller/golden_tests/vulkan_screenshotter.h"
#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"
namespace impeller {
namespace testing {
namespace {
std::unique_ptr<Screenshot> ReadTexture(
const std::shared_ptr<Context>& surface_context,
const std::shared_ptr<Texture>& texture) {
DeviceBufferDescriptor buffer_desc;
buffer_desc.storage_mode = StorageMode::kHostVisible;
buffer_desc.size =
texture->GetTextureDescriptor().GetByteSizeOfBaseMipLevel();
std::shared_ptr<DeviceBuffer> device_buffer =
surface_context->GetResourceAllocator()->CreateBuffer(buffer_desc);
FML_CHECK(device_buffer);
auto command_buffer = surface_context->CreateCommandBuffer();
auto blit_pass = command_buffer->CreateBlitPass();
bool success = blit_pass->AddCopy(texture, device_buffer);
FML_CHECK(success);
success = blit_pass->EncodeCommands(surface_context->GetResourceAllocator());
FML_CHECK(success);
fml::AutoResetWaitableEvent latch;
success =
command_buffer->SubmitCommands([&latch](CommandBuffer::Status status) {
FML_CHECK(status == CommandBuffer::Status::kCompleted);
latch.Signal();
});
FML_CHECK(success);
latch.Wait();
// TODO(gaaclarke): Replace CoreImage requirement with something
// crossplatform.
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
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);
FML_CHECK(context);
CGImageRef image_ref = CGBitmapContextCreateImage(context);
FML_CHECK(image_ref);
CGContextRelease(context);
CGColorSpaceRelease(color_space);
return std::make_unique<MetalScreenshot>(image_ref);
}
} // namespace
VulkanScreenshotter::VulkanScreenshotter() {
FML_CHECK(::glfwInit() == GLFW_TRUE);
playground_ =
PlaygroundImpl::Create(PlaygroundBackend::kVulkan, PlaygroundSwitches{});
}
std::unique_ptr<Screenshot> VulkanScreenshotter::MakeScreenshot(
AiksContext& aiks_context,
const Picture& picture,
const ISize& size,
bool scale_content) {
Vector2 content_scale =
scale_content ? playground_->GetContentScale() : Vector2{1, 1};
std::shared_ptr<Image> image = picture.ToImage(
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);
}
} // namespace testing
} // namespace impeller

View File

@@ -175,8 +175,39 @@ void PlaygroundImplVK::InitGlobalVulkanInstance() {
application_info.setPEngineName("PlaygroundImplVK");
application_info.setPApplicationName("PlaygroundImplVK");
auto instance_result =
vk::createInstanceUnique(vk::InstanceCreateInfo({}, &application_info));
auto caps = std::shared_ptr<CapabilitiesVK>(
new CapabilitiesVK(/*enable_validations=*/true));
FML_DCHECK(caps->IsValid());
std::optional<std::vector<std::string>> enabled_layers =
caps->GetEnabledLayers();
std::optional<std::vector<std::string>> enabled_extensions =
caps->GetEnabledInstanceExtensions();
FML_DCHECK(enabled_layers.has_value() && enabled_extensions.has_value());
std::vector<const char*> enabled_layers_c;
std::vector<const char*> enabled_extensions_c;
if (enabled_layers.has_value()) {
for (const auto& layer : enabled_layers.value()) {
enabled_layers_c.push_back(layer.c_str());
}
}
if (enabled_extensions.has_value()) {
for (const auto& ext : enabled_extensions.value()) {
enabled_extensions_c.push_back(ext.c_str());
}
}
vk::InstanceCreateFlags instance_flags = {};
instance_flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
vk::InstanceCreateInfo instance_info;
instance_info.setPEnabledLayerNames(enabled_layers_c);
instance_info.setPEnabledExtensionNames(enabled_extensions_c);
instance_info.setPApplicationInfo(&application_info);
instance_info.setFlags(instance_flags);
auto instance_result = vk::createInstanceUnique(instance_info);
FML_CHECK(instance_result.result == vk::Result::eSuccess)
<< "Unable to initialize global Vulkan instance";
global_instance_ = std::move(instance_result.value);

View File

@@ -224,7 +224,6 @@ constexpr vk::Filter ToVKSamplerMinMagFilter(MinMagFilter filter) {
}
constexpr vk::SamplerMipmapMode ToVKSamplerMipmapMode(MipFilter filter) {
vk::SamplerCreateInfo sampler_info;
switch (filter) {
case MipFilter::kNearest:
return vk::SamplerMipmapMode::eNearest;

View File

@@ -115,4 +115,8 @@ vk::UniqueSurfaceKHR SurfaceContextVK::CreateAndroidSurface(
#endif // FML_OS_ANDROID
const vk::Device& SurfaceContextVK::GetDevice() const {
return parent_->GetDevice();
}
} // namespace impeller

View File

@@ -76,6 +76,8 @@ class SurfaceContextVK : public Context,
vk::UniqueSurfaceKHR CreateAndroidSurface(ANativeWindow* window) const;
#endif // FML_OS_ANDROID
const vk::Device& GetDevice() const;
private:
std::shared_ptr<ContextVK> parent_;
std::shared_ptr<SwapchainVK> swapchain_;

View File

@@ -35,6 +35,9 @@ class TextureVK final : public Texture, public BackendCast<TextureVK, Texture> {
std::shared_ptr<const TextureSourceVK> GetTextureSource() const;
// |Texture|
ISize GetSize() const override;
private:
std::weak_ptr<Context> context_;
std::shared_ptr<TextureSourceVK> source_;
@@ -54,9 +57,6 @@ class TextureVK final : public Texture, public BackendCast<TextureVK, Texture> {
// |Texture|
bool IsValid() const override;
// |Texture|
ISize GetSize() const override;
TextureVK(const TextureVK&) = delete;
TextureVK& operator=(const TextureVK&) = delete;