Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions shell/gpu/gpu_surface_metal_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ typedef void* GPUCAMetalLayerHandle;
// expected to be id<MTLTexture>
typedef const void* GPUMTLTextureHandle;

typedef void (*GPUMTLDestructionCallback)(void* /* destruction_context */);

struct GPUMTLTextureInfo {
int64_t texture_id;
GPUMTLTextureHandle texture;
GPUMTLDestructionCallback destruction_callback;
void* destruction_context;
};

enum class MTLRenderTargetType { kMTLTexture, kCAMetalLayer };
Expand Down
21 changes: 13 additions & 8 deletions shell/gpu/gpu_surface_metal_skia.mm
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
MsaaSampleCount sample_cnt,
SkColorType color_type,
sk_sp<SkColorSpace> color_space,
const SkSurfaceProps* props) {
const SkSurfaceProps* props,
SkSurface::TextureReleaseProc release_proc,
SkSurface::ReleaseContext release_context) {
GrMtlTextureInfo info;
info.fTexture.reset([texture retain]);
GrBackendTexture backend_texture(texture.width, texture.height, GrMipmapped::kNo, info);
return SkSurface::MakeFromBackendTexture(context, backend_texture, origin,
static_cast<int>(sample_cnt), color_type,
std::move(color_space), props);
return SkSurface::MakeFromBackendTexture(
context, backend_texture, origin, static_cast<int>(sample_cnt), color_type,
std::move(color_space), props, release_proc, release_context);
}
} // namespace

Expand Down Expand Up @@ -137,7 +139,9 @@
msaa_samples_, // sample count
kBGRA_8888_SkColorType, // color type
nullptr, // colorspace
nullptr // surface properties
nullptr, // surface properties
nullptr, // release proc
nullptr // release context
);

if (!surface) {
Expand Down Expand Up @@ -203,9 +207,10 @@
return nullptr;
}

sk_sp<SkSurface> surface =
CreateSurfaceFromMetalTexture(context_.get(), mtl_texture, kTopLeft_GrSurfaceOrigin,
msaa_samples_, kBGRA_8888_SkColorType, nullptr, nullptr);
sk_sp<SkSurface> surface = CreateSurfaceFromMetalTexture(
context_.get(), mtl_texture, kTopLeft_GrSurfaceOrigin, msaa_samples_, kBGRA_8888_SkColorType,
nullptr, nullptr, static_cast<SkSurface::TextureReleaseProc>(texture.destruction_callback),
texture.destruction_context);

if (!surface) {
FML_LOG(ERROR) << "Could not create the SkSurface from the metal texture.";
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/embedder/embedder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ InferMetalPlatformViewCreationCallback(
FlutterMetalTexture metal_texture = ptr(user_data, &frame_info);
texture_info.texture_id = metal_texture.texture_id;
texture_info.texture = metal_texture.texture;
texture_info.destruction_callback = metal_texture.destruction_callback;
texture_info.destruction_context = metal_texture.user_data;
return texture_info;
};

Expand Down
19 changes: 19 additions & 0 deletions shell/platform/embedder/fixtures/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,25 @@ void can_composite_platform_views_with_platform_layer_on_bottom() {
PlatformDispatcher.instance.scheduleFrame();
}

@pragma('vm:external-name', 'SignalBeginFrame')
external void signalBeginFrame();

@pragma('vm:entry-point')
void texture_destruction_callback_called_without_custom_compositor() async {
PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
Color red = Color.fromARGB(127, 255, 0, 0);
Size size = Size(50.0, 150.0);
SceneBuilder builder = SceneBuilder();
builder.pushOffset(0.0, 0.0);
builder.addPicture(
Offset(10.0, 10.0), CreateColoredBox(red, size)); // red - flutter
builder.pop();
PlatformDispatcher.instance.views.first.render(builder.build());
};
PlatformDispatcher.instance.scheduleFrame();
}


@pragma('vm:entry-point')
void can_render_scene_without_custom_compositor() {
PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
Expand Down
14 changes: 13 additions & 1 deletion shell/platform/embedder/tests/embedder_test_context_metal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,22 @@ bool EmbedderTestContextMetal::PopulateExternalTexture(
}
}

TestMetalContext::TextureInfo EmbedderTestContextMetal::GetTextureInfo() {
return metal_surface_->GetTextureInfo();
}

void EmbedderTestContextMetal::SetNextDrawableCallback(
NextDrawableCallback next_drawable_callback) {
next_drawable_callback_ = std::move(next_drawable_callback);
}

FlutterMetalTexture EmbedderTestContextMetal::GetNextDrawable(
const FlutterFrameInfo* frame_info) {
auto texture_info = metal_surface_->GetTextureInfo();
if (next_drawable_callback_ != nullptr) {
return next_drawable_callback_(frame_info);
}

auto texture_info = metal_surface_->GetTextureInfo();
FlutterMetalTexture texture;
texture.struct_size = sizeof(FlutterMetalTexture);
texture.texture_id = texture_info.texture_id;
Expand Down
10 changes: 10 additions & 0 deletions shell/platform/embedder/tests/embedder_test_context_metal.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class EmbedderTestContextMetal : public EmbedderTestContext {
size_t h,
FlutterMetalExternalTexture* output)>;

using NextDrawableCallback =
std::function<FlutterMetalTexture(const FlutterFrameInfo* frame_info)>;

explicit EmbedderTestContextMetal(std::string assets_path = "");

~EmbedderTestContextMetal() override;
Expand All @@ -45,6 +48,12 @@ class EmbedderTestContextMetal : public EmbedderTestContext {

TestMetalContext* GetTestMetalContext();

// Returns the TextureInfo for the test Metal surface.
TestMetalContext::TextureInfo GetTextureInfo();

// Override the default handling for GetNextDrawable.
void SetNextDrawableCallback(NextDrawableCallback next_drawable_callback);

FlutterMetalTexture GetNextDrawable(const FlutterFrameInfo* frame_info);

private:
Expand All @@ -56,6 +65,7 @@ class EmbedderTestContextMetal : public EmbedderTestContext {
std::unique_ptr<TestMetalContext> metal_context_;
std::unique_ptr<TestMetalSurface> metal_surface_;
size_t present_count_ = 0;
NextDrawableCallback next_drawable_callback_ = nullptr;

void SetupSurface(SkISize surface_size) override;

Expand Down
43 changes: 43 additions & 0 deletions shell/platform/embedder/tests/embedder_unittests_metal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,49 @@ GrBackendTexture backend_texture(texture_size.width(), texture_size.height(), Gr
ASSERT_TRUE(ImageMatchesFixture("scene_without_custom_compositor.png", rendered_scene));
}

TEST_F(EmbedderTest, TextureDestructionCallbackCalledWithoutCustomCompositorMetal) {
EmbedderTestContextMetal& context = reinterpret_cast<EmbedderTestContextMetal&>(
GetEmbedderContext(EmbedderTestContextType::kMetalContext));
EmbedderConfigBuilder builder(context);
builder.SetMetalRendererConfig(SkISize::Make(800, 600));
builder.SetDartEntrypoint("texture_destruction_callback_called_without_custom_compositor");

struct CollectContext {
int collect_count = 0;
fml::AutoResetWaitableEvent latch;
};

auto collect_context = std::make_unique<CollectContext>();
context.SetNextDrawableCallback([&context, &collect_context](const FlutterFrameInfo* frame_info) {
auto texture_info = context.GetTextureInfo();
FlutterMetalTexture texture;
texture.struct_size = sizeof(FlutterMetalTexture);
texture.texture_id = texture_info.texture_id;
texture.texture = reinterpret_cast<FlutterMetalTextureHandle>(texture_info.texture);
texture.user_data = collect_context.get();
texture.destruction_callback = [](void* user_data) {
CollectContext* callback_collect_context = reinterpret_cast<CollectContext*>(user_data);
callback_collect_context->collect_count++;
callback_collect_context->latch.Signal();
Copy link
Member Author

@cbracken cbracken Dec 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latching/signalling here is a bit dirty since the test either passes or hangs until it times out and fails.

What I'm really trying to capture here is the moment where one complete frame has been drawn and the associated surface/texture collected.

Initially I tried to do a countdown latch on onBeginFrame. On the second call, we expect that the first surface has been disposed. This actually works pretty well except that I haven't found a nice mechanism to guarantee that we actually get the second frame before everything shuts down.

I wonder if I could use a Completer on the dart side in onDraw in the dart code and schedule the frame, wait on it, then schedule the next frame and wait on that. Will give it a shot and report back here. Any ideas appreciated though :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we have other spots where this pattern if followed. The hanging case is handled by the per test case watchdog which only allows a test to run for a small amount of time.

};
return texture;
});

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());

collect_context->latch.Wait();
EXPECT_EQ(collect_context->collect_count, 1);
}

TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneMetal) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext);

Expand Down