blob: 296edf7b5723e237cb79ae22841fb1f3c2037511 [file] [log] [blame]
// 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.
#define FML_USED_ON_EMBEDDER
#include <string>
#include <vector>
#import <Metal/Metal.h>
#include "embedder.h"
#include "flutter/fml/synchronization/count_down_latch.h"
#include "flutter/shell/platform/embedder/tests/embedder_assertions.h"
#include "flutter/shell/platform/embedder/tests/embedder_config_builder.h"
#include "flutter/shell/platform/embedder/tests/embedder_test.h"
#include "flutter/shell/platform/embedder/tests/embedder_test_context_metal.h"
#include "flutter/shell/platform/embedder/tests/embedder_unittests_util.h"
#include "flutter/testing/assertions_skia.h"
#include "flutter/testing/testing.h"
// CREATE_NATIVE_ENTRY is leaky by design
// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
namespace flutter {
namespace testing {
using EmbedderTest = testing::EmbedderTest;
TEST_F(EmbedderTest, CanRenderGradientWithMetal) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext);
EmbedderConfigBuilder builder(context);
builder.SetDartEntrypoint("render_gradient");
builder.SetMetalRendererConfig(SkISize::Make(800, 600));
auto rendered_scene = context.GetNextSceneImage();
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
// Send a window metrics events so frames may be scheduled.
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(event);
event.width = 800;
event.height = 600;
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess);
// TODO (https://github.com/flutter/flutter/issues/73590): re-enable once
// we are able to figure out why this fails on the bots.
// ASSERT_TRUE(ImageMatchesFixture("gradient_metal.png", rendered_scene));
}
static sk_sp<SkSurface> GetSurfaceFromTexture(const sk_sp<GrDirectContext>& skia_context,
SkISize texture_size,
void* texture) {
GrMtlTextureInfo info;
info.fTexture.reset([(id<MTLTexture>)texture retain]);
GrBackendTexture backend_texture(texture_size.width(), texture_size.height(), GrMipmapped::kNo,
info);
return SkSurface::MakeFromBackendTexture(skia_context.get(), backend_texture,
kTopLeft_GrSurfaceOrigin, 1, kBGRA_8888_SkColorType,
nullptr, nullptr);
}
TEST_F(EmbedderTest, ExternalTextureMetal) {
EmbedderTestContextMetal& context = reinterpret_cast<EmbedderTestContextMetal&>(
GetEmbedderContext(EmbedderTestContextType::kMetalContext));
const auto texture_size = SkISize::Make(800, 600);
const int64_t texture_id = 1;
TestMetalContext* metal_context = context.GetTestMetalContext();
TestMetalContext::TextureInfo texture_info = metal_context->CreateMetalTexture(texture_size);
sk_sp<SkSurface> surface =
GetSurfaceFromTexture(metal_context->GetSkiaContext(), texture_size, texture_info.texture);
auto canvas = surface->getCanvas();
canvas->clear(SK_ColorRED);
metal_context->GetSkiaContext()->flushAndSubmit();
std::vector<FlutterMetalTextureHandle> textures{texture_info.texture};
context.SetExternalTextureCallback(
[&](int64_t id, size_t w, size_t h, FlutterMetalExternalTexture* output) {
EXPECT_TRUE(w == texture_size.width());
EXPECT_TRUE(h == texture_size.height());
EXPECT_TRUE(texture_id == id);
output->num_textures = 1;
output->height = h;
output->width = w;
output->pixel_format = FlutterMetalExternalTexturePixelFormat::kRGBA;
output->textures = textures.data();
return true;
});
EmbedderConfigBuilder builder(context);
builder.SetDartEntrypoint("render_texture");
builder.SetMetalRendererConfig(texture_size);
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
ASSERT_EQ(FlutterEngineRegisterExternalTexture(engine.get(), texture_id), kSuccess);
auto rendered_scene = context.GetNextSceneImage();
// Send a window metrics events so frames may be scheduled.
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(event);
event.width = texture_size.width();
event.height = texture_size.height();
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess);
ASSERT_TRUE(ImageMatchesFixture("external_texture_metal.png", rendered_scene));
}
TEST_F(EmbedderTest, MetalCompositorMustBeAbleToRenderPlatformViews) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext);
EmbedderConfigBuilder builder(context);
builder.SetMetalRendererConfig(SkISize::Make(800, 600));
builder.SetCompositor();
builder.SetDartEntrypoint("can_composite_platform_views");
builder.SetRenderTargetType(EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture);
fml::CountDownLatch latch(3);
context.GetCompositor().SetNextPresentCallback(
[&](const FlutterLayer** layers, size_t layers_count) {
ASSERT_EQ(layers_count, 3u);
{
FlutterBackingStore backing_store = *layers[0]->backing_store;
backing_store.struct_size = sizeof(backing_store);
backing_store.type = kFlutterBackingStoreTypeMetal;
backing_store.did_update = true;
FlutterLayer layer = {};
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypeBackingStore;
layer.backing_store = &backing_store;
layer.size = FlutterSizeMake(800.0, 600.0);
layer.offset = FlutterPointMake(0, 0);
ASSERT_EQ(*layers[0], layer);
}
{
FlutterPlatformView platform_view = *layers[1]->platform_view;
platform_view.struct_size = sizeof(platform_view);
platform_view.identifier = 42;
FlutterLayer layer = {};
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypePlatformView;
layer.platform_view = &platform_view;
layer.size = FlutterSizeMake(123.0, 456.0);
layer.offset = FlutterPointMake(1.0, 2.0);
ASSERT_EQ(*layers[1], layer);
}
{
FlutterBackingStore backing_store = *layers[2]->backing_store;
backing_store.struct_size = sizeof(backing_store);
backing_store.type = kFlutterBackingStoreTypeMetal;
backing_store.did_update = true;
FlutterLayer layer = {};
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypeBackingStore;
layer.backing_store = &backing_store;
layer.size = FlutterSizeMake(800.0, 600.0);
layer.offset = FlutterPointMake(0.0, 0.0);
ASSERT_EQ(*layers[2], layer);
}
latch.CountDown();
});
context.AddNativeCallback(
"SignalNativeTest",
CREATE_NATIVE_ENTRY([&latch](Dart_NativeArguments args) { latch.CountDown(); }));
auto engine = builder.LaunchEngine();
// Send a window metrics events so frames may be scheduled.
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(event);
event.width = 800;
event.height = 600;
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess);
ASSERT_TRUE(engine.is_valid());
latch.Wait();
}
TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorMetal) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext);
EmbedderConfigBuilder builder(context);
builder.SetDartEntrypoint("can_render_scene_without_custom_compositor");
builder.SetMetalRendererConfig(SkISize::Make(800, 600));
auto rendered_scene = context.GetNextSceneImage();
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
// Send a window metrics events so frames may be scheduled.
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(event);
event.width = 800;
event.height = 600;
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess);
ASSERT_TRUE(ImageMatchesFixture("scene_without_custom_compositor.png", rendered_scene));
}
TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneMetal) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext);
EmbedderConfigBuilder builder(context);
builder.SetMetalRendererConfig(SkISize::Make(800, 600));
builder.SetCompositor();
builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene");
builder.SetRenderTargetType(EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture);
fml::CountDownLatch latch(5);
auto scene_image = context.GetNextSceneImage();
context.GetCompositor().SetNextPresentCallback(
[&](const FlutterLayer** layers, size_t layers_count) {
ASSERT_EQ(layers_count, 5u);
// Layer Root
{
FlutterBackingStore backing_store = *layers[0]->backing_store;
backing_store.type = kFlutterBackingStoreTypeMetal;
backing_store.did_update = true;
FlutterLayer layer = {};
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypeBackingStore;
layer.backing_store = &backing_store;
layer.size = FlutterSizeMake(800.0, 600.0);
layer.offset = FlutterPointMake(0.0, 0.0);
ASSERT_EQ(*layers[0], layer);
}
// Layer 1
{
FlutterPlatformView platform_view = *layers[1]->platform_view;
platform_view.struct_size = sizeof(platform_view);
platform_view.identifier = 1;
FlutterLayer layer = {};
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypePlatformView;
layer.platform_view = &platform_view;
layer.size = FlutterSizeMake(50.0, 150.0);
layer.offset = FlutterPointMake(20.0, 20.0);
ASSERT_EQ(*layers[1], layer);
}
// Layer 2
{
FlutterBackingStore backing_store = *layers[2]->backing_store;
backing_store.type = kFlutterBackingStoreTypeMetal;
backing_store.did_update = true;
FlutterLayer layer = {};
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypeBackingStore;
layer.backing_store = &backing_store;
layer.size = FlutterSizeMake(800.0, 600.0);
layer.offset = FlutterPointMake(0.0, 0.0);
ASSERT_EQ(*layers[2], layer);
}
// Layer 3
{
FlutterPlatformView platform_view = *layers[3]->platform_view;
platform_view.struct_size = sizeof(platform_view);
platform_view.identifier = 2;
FlutterLayer layer = {};
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypePlatformView;
layer.platform_view = &platform_view;
layer.size = FlutterSizeMake(50.0, 150.0);
layer.offset = FlutterPointMake(40.0, 40.0);
ASSERT_EQ(*layers[3], layer);
}
// Layer 4
{
FlutterBackingStore backing_store = *layers[4]->backing_store;
backing_store.type = kFlutterBackingStoreTypeMetal;
backing_store.did_update = true;
FlutterLayer layer = {};
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypeBackingStore;
layer.backing_store = &backing_store;
layer.size = FlutterSizeMake(800.0, 600.0);
layer.offset = FlutterPointMake(0.0, 0.0);
ASSERT_EQ(*layers[4], layer);
}
latch.CountDown();
});
context.GetCompositor().SetPlatformViewRendererCallback(
[&](const FlutterLayer& layer, GrDirectContext* context) -> sk_sp<SkImage> {
auto surface = CreateRenderSurface(layer, context);
auto canvas = surface->getCanvas();
FML_CHECK(canvas != nullptr);
switch (layer.platform_view->identifier) {
case 1: {
SkPaint paint;
// See dart test for total order.
paint.setColor(SK_ColorGREEN);
paint.setAlpha(127);
const auto& rect = SkRect::MakeWH(layer.size.width, layer.size.height);
canvas->drawRect(rect, paint);
latch.CountDown();
} break;
case 2: {
SkPaint paint;
// See dart test for total order.
paint.setColor(SK_ColorMAGENTA);
paint.setAlpha(127);
const auto& rect = SkRect::MakeWH(layer.size.width, layer.size.height);
canvas->drawRect(rect, paint);
latch.CountDown();
} break;
default:
// Asked to render an unknown platform view.
FML_CHECK(false) << "Test was asked to composite an unknown platform view.";
}
return surface->makeImageSnapshot();
});
context.AddNativeCallback(
"SignalNativeTest",
CREATE_NATIVE_ENTRY([&latch](Dart_NativeArguments args) { latch.CountDown(); }));
auto engine = builder.LaunchEngine();
// Send a window metrics events so frames may be scheduled.
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(event);
// Flutter still thinks it is 800 x 600. Only the root surface is rotated.
event.width = 800;
event.height = 600;
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess);
ASSERT_TRUE(engine.is_valid());
latch.Wait();
ASSERT_TRUE(ImageMatchesFixture("compositor.png", scene_image));
}
TEST_F(EmbedderTest, CreateInvalidBackingstoreMetalTexture) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext);
EmbedderConfigBuilder builder(context);
builder.SetMetalRendererConfig(SkISize::Make(800, 600));
builder.SetCompositor();
builder.SetRenderTargetType(EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture);
builder.SetDartEntrypoint("invalid_backingstore");
class TestCollectOnce {
public:
// Collect() should only be called once
void Collect() {
ASSERT_FALSE(collected_);
collected_ = true;
}
private:
bool collected_ = false;
};
fml::AutoResetWaitableEvent latch;
builder.GetCompositor().create_backing_store_callback =
[](const FlutterBackingStoreConfig* config, //
FlutterBackingStore* backing_store_out, //
void* user_data //
) {
backing_store_out->type = kFlutterBackingStoreTypeMetal;
// Deliberately set this to be invalid
backing_store_out->user_data = nullptr;
backing_store_out->metal.texture.texture = 0;
backing_store_out->metal.struct_size = sizeof(FlutterMetalBackingStore);
backing_store_out->metal.texture.user_data = new TestCollectOnce();
backing_store_out->metal.texture.destruction_callback = [](void* user_data) {
reinterpret_cast<TestCollectOnce*>(user_data)->Collect();
};
return true;
};
context.AddNativeCallback(
"SignalNativeTest",
CREATE_NATIVE_ENTRY([&latch](Dart_NativeArguments args) { latch.Signal(); }));
auto engine = builder.LaunchEngine();
// Send a window metrics events so frames may be scheduled.
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(event);
event.width = 800;
event.height = 600;
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess);
ASSERT_TRUE(engine.is_valid());
latch.Wait();
}
TEST_F(EmbedderTest, ExternalTextureMetalRefreshedTooOften) {
EmbedderTestContextMetal& context = reinterpret_cast<EmbedderTestContextMetal&>(
GetEmbedderContext(EmbedderTestContextType::kMetalContext));
TestMetalContext* metal_context = context.GetTestMetalContext();
auto metal_texture = metal_context->CreateMetalTexture(SkISize::Make(100, 100));
std::vector<FlutterMetalTextureHandle> textures{metal_texture.texture};
bool resolve_called = false;
EmbedderExternalTextureMetal::ExternalTextureCallback callback([&](int64_t id, size_t, size_t) {
resolve_called = true;
auto res = std::make_unique<FlutterMetalExternalTexture>();
res->struct_size = sizeof(FlutterMetalExternalTexture);
res->width = res->height = 100;
res->pixel_format = FlutterMetalExternalTexturePixelFormat::kRGBA;
res->textures = textures.data();
res->num_textures = 1;
return res;
});
EmbedderExternalTextureMetal texture(1, callback);
auto surface = TestMetalSurface::Create(*metal_context, SkISize::Make(100, 100));
auto skia_surface = surface->GetSurface();
auto canvas = skia_surface->getCanvas();
Texture* texture_ = &texture;
Texture::PaintContext ctx{
.canvas = canvas,
.gr_context = surface->GetGrContext().get(),
};
texture_->Paint(ctx, SkRect::MakeXYWH(0, 0, 100, 100), false,
SkSamplingOptions(SkFilterMode::kLinear));
EXPECT_TRUE(resolve_called);
resolve_called = false;
texture_->Paint(ctx, SkRect::MakeXYWH(0, 0, 100, 100), false,
SkSamplingOptions(SkFilterMode::kLinear));
EXPECT_FALSE(resolve_called);
texture_->MarkNewFrameAvailable();
texture_->Paint(ctx, SkRect::MakeXYWH(0, 0, 100, 100), false,
SkSamplingOptions(SkFilterMode::kLinear));
EXPECT_TRUE(resolve_called);
}
} // namespace testing
} // namespace flutter
// NOLINTEND(clang-analyzer-core.StackAddressEscape)