blob: d3ec678d8bb5ef705fc0e917201499bb2b4d824e [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.
#include "flutter/shell/gpu/gpu_surface_metal.h"
#import <Metal/Metal.h>
#include "flutter/common/graphics/persistent_cache.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/platform/darwin/cf_utils.h"
#include "flutter/fml/trace_event.h"
#include "flutter/shell/gpu/gpu_surface_metal_delegate.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/ports/SkCFObject.h"
static_assert(!__has_feature(objc_arc), "ARC must be disabled.");
namespace flutter {
GPUSurfaceMetal::GPUSurfaceMetal(GPUSurfaceMetalDelegate* delegate,
sk_sp<GrDirectContext> context,
bool render_to_surface)
: delegate_(delegate),
render_target_type_(delegate->GetRenderTargetType()),
context_(std::move(context)),
render_to_surface_(render_to_surface) {}
GPUSurfaceMetal::~GPUSurfaceMetal() {
ReleaseUnusedDrawableIfNecessary();
}
// |Surface|
bool GPUSurfaceMetal::IsValid() {
return context_ != nullptr;
}
void GPUSurfaceMetal::PrecompileKnownSkSLsIfNecessary() {
auto* current_context = GetContext();
if (current_context == precompiled_sksl_context_) {
// Known SkSLs have already been prepared in this context.
return;
}
precompiled_sksl_context_ = current_context;
flutter::PersistentCache::GetCacheForProcess()->PrecompileKnownSkSLs(precompiled_sksl_context_);
}
// |Surface|
std::unique_ptr<SurfaceFrame> GPUSurfaceMetal::AcquireFrame(const SkISize& frame_size) {
if (!IsValid()) {
FML_LOG(ERROR) << "Metal surface was invalid.";
return nullptr;
}
if (frame_size.isEmpty()) {
FML_LOG(ERROR) << "Metal surface was asked for an empty frame.";
return nullptr;
}
if (!render_to_surface_) {
return std::make_unique<SurfaceFrame>(
nullptr, true, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { return true; });
}
PrecompileKnownSkSLsIfNecessary();
switch (render_target_type_) {
case MTLRenderTargetType::kCAMetalLayer:
return AcquireFrameFromCAMetalLayer(frame_size);
case MTLRenderTargetType::kMTLTexture:
return AcquireFrameFromMTLTexture(frame_size);
default:
FML_CHECK(false) << "Unknown MTLRenderTargetType type.";
}
return nullptr;
}
std::unique_ptr<SurfaceFrame> GPUSurfaceMetal::AcquireFrameFromCAMetalLayer(
const SkISize& frame_info) {
auto layer = delegate_->GetCAMetalLayer(frame_info);
if (!layer) {
FML_LOG(ERROR) << "Invalid CAMetalLayer given by the embedder.";
return nullptr;
}
ReleaseUnusedDrawableIfNecessary();
sk_sp<SkSurface> surface =
SkSurface::MakeFromCAMetalLayer(context_.get(), // context
layer, // layer
kTopLeft_GrSurfaceOrigin, // origin
1, // sample count
kBGRA_8888_SkColorType, // color type
nullptr, // colorspace
nullptr, // surface properties
&next_drawable_ // drawable (transfer out)
);
if (!surface) {
FML_LOG(ERROR) << "Could not create the SkSurface from the CAMetalLayer.";
return nullptr;
}
auto submit_callback = [this](const SurfaceFrame& surface_frame, SkCanvas* canvas) -> bool {
TRACE_EVENT0("flutter", "GPUSurfaceMetal::Submit");
if (canvas == nullptr) {
FML_DLOG(ERROR) << "Canvas not available.";
return false;
}
canvas->flush();
GrMTLHandle drawable = next_drawable_;
if (!drawable) {
FML_DLOG(ERROR) << "Unable to obtain a metal drawable.";
return false;
}
return delegate_->PresentDrawable(drawable);
};
return std::make_unique<SurfaceFrame>(std::move(surface), true, submit_callback);
}
std::unique_ptr<SurfaceFrame> GPUSurfaceMetal::AcquireFrameFromMTLTexture(
const SkISize& frame_info) {
GPUMTLTextureInfo texture = delegate_->GetMTLTexture(frame_info);
id<MTLTexture> mtl_texture = (id<MTLTexture>)(texture.texture);
if (!mtl_texture) {
FML_LOG(ERROR) << "Invalid MTLTexture given by the embedder.";
return nullptr;
}
GrMtlTextureInfo info;
info.fTexture.reset([mtl_texture retain]);
GrBackendTexture backend_texture(frame_info.width(), frame_info.height(), GrMipmapped::kNo, info);
sk_sp<SkSurface> surface =
SkSurface::MakeFromBackendTexture(context_.get(), backend_texture, kTopLeft_GrSurfaceOrigin,
1, kBGRA_8888_SkColorType, nullptr, nullptr);
if (!surface) {
FML_LOG(ERROR) << "Could not create the SkSurface from the metal texture.";
return nullptr;
}
auto submit_callback = [texture = texture, delegate = delegate_](
const SurfaceFrame& surface_frame, SkCanvas* canvas) -> bool {
TRACE_EVENT0("flutter", "GPUSurfaceMetal::PresentTexture");
if (canvas == nullptr) {
FML_DLOG(ERROR) << "Canvas not available.";
return false;
}
canvas->flush();
return delegate->PresentTexture(texture);
};
return std::make_unique<SurfaceFrame>(std::move(surface), true, submit_callback);
}
// |Surface|
SkMatrix GPUSurfaceMetal::GetRootTransformation() const {
// This backend does not currently support root surface transformations. Just
// return identity.
return {};
}
// |Surface|
GrDirectContext* GPUSurfaceMetal::GetContext() {
return context_.get();
}
// |Surface|
std::unique_ptr<GLContextResult> GPUSurfaceMetal::MakeRenderContextCurrent() {
// A context may either be necessary to render to the surface or to snapshot an offscreen
// surface. Either way, SkSL precompilation must be attempted.
PrecompileKnownSkSLsIfNecessary();
// This backend has no such concept.
return std::make_unique<GLContextDefaultResult>(true);
}
bool GPUSurfaceMetal::AllowsDrawingWhenGpuDisabled() const {
return delegate_->AllowsDrawingWhenGpuDisabled();
}
void GPUSurfaceMetal::ReleaseUnusedDrawableIfNecessary() {
// If the previous surface frame was not submitted before a new one is acquired, the old drawable
// needs to be released. An RAII wrapper may not be used because this needs to interoperate with
// Skia APIs.
if (next_drawable_ == nullptr) {
return;
}
CFRelease(next_drawable_);
next_drawable_ = nullptr;
}
} // namespace flutter