blob: 9e666d2a4f04763de862e6364a75f01c618f4473 [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_skia.h"
#import <Metal/Metal.h>
#import <QuartzCore/QuartzCore.h>
#include <utility>
#include "flutter/common/graphics/persistent_cache.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/platform/darwin/cf_utils.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
#include "flutter/fml/trace_event.h"
#include "flutter/shell/gpu/gpu_surface_metal_delegate.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkColorType.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSize.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkSurfaceProps.h"
#include "third_party/skia/include/gpu/GpuTypes.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
#include "third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSurface.h"
#include "third_party/skia/include/gpu/ganesh/mtl/GrMtlTypes.h"
#include "third_party/skia/include/ports/SkCFObject.h"
static_assert(!__has_feature(objc_arc), "ARC must be disabled.");
namespace flutter {
namespace {
sk_sp<SkSurface> CreateSurfaceFromMetalTexture(GrDirectContext* context,
id<MTLTexture> texture,
GrSurfaceOrigin origin,
MsaaSampleCount sample_cnt,
SkColorType color_type,
sk_sp<SkColorSpace> color_space,
const SkSurfaceProps* props,
SkSurfaces::TextureReleaseProc release_proc,
SkSurface::ReleaseContext release_context) {
GrMtlTextureInfo info;
info.fTexture.reset([texture retain]);
GrBackendTexture backend_texture =
GrBackendTextures::MakeMtl(texture.width, texture.height, skgpu::Mipmapped::kNo, info);
return SkSurfaces::WrapBackendTexture(
context, backend_texture, origin, static_cast<int>(sample_cnt), color_type,
std::move(color_space), props, release_proc, release_context);
}
} // namespace
GPUSurfaceMetalSkia::GPUSurfaceMetalSkia(GPUSurfaceMetalDelegate* delegate,
sk_sp<GrDirectContext> context,
MsaaSampleCount msaa_samples,
bool render_to_surface)
: delegate_(delegate),
render_target_type_(delegate->GetRenderTargetType()),
context_(std::move(context)),
msaa_samples_(msaa_samples),
render_to_surface_(render_to_surface) {
// If this preference is explicitly set, we allow for disabling partial repaint.
NSNumber* disablePartialRepaint =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTDisablePartialRepaint"];
if (disablePartialRepaint != nil) {
disable_partial_repaint_ = disablePartialRepaint.boolValue;
}
}
GPUSurfaceMetalSkia::~GPUSurfaceMetalSkia() = default;
// |Surface|
bool GPUSurfaceMetalSkia::IsValid() {
return context_ != nullptr;
}
void GPUSurfaceMetalSkia::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> GPUSurfaceMetalSkia::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, SurfaceFrame::FramebufferInfo(),
[](const SurfaceFrame& surface_frame, DlCanvas* canvas) { return true; }, frame_size);
}
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> GPUSurfaceMetalSkia::AcquireFrameFromCAMetalLayer(
const SkISize& frame_info) {
auto layer = delegate_->GetCAMetalLayer(frame_info);
if (!layer) {
FML_LOG(ERROR) << "Invalid CAMetalLayer given by the embedder.";
return nullptr;
}
auto* mtl_layer = (CAMetalLayer*)layer;
// Get the drawable eagerly, we will need texture object to identify target framebuffer
fml::scoped_nsprotocol<id<CAMetalDrawable>> drawable(
reinterpret_cast<id<CAMetalDrawable>>([[mtl_layer nextDrawable] retain]));
if (!drawable.get()) {
FML_LOG(ERROR) << "Could not obtain drawable from the metal layer.";
return nullptr;
}
auto surface = CreateSurfaceFromMetalTexture(context_.get(), drawable.get().texture,
kTopLeft_GrSurfaceOrigin, // origin
msaa_samples_, // sample count
kBGRA_8888_SkColorType, // color type
nullptr, // colorspace
nullptr, // surface properties
nullptr, // release proc
nullptr // release context
);
if (!surface) {
FML_LOG(ERROR) << "Could not create the SkSurface from the CAMetalLayer.";
return nullptr;
}
auto submit_callback = [this, drawable](const SurfaceFrame& surface_frame,
DlCanvas* canvas) -> bool {
TRACE_EVENT0("flutter", "GPUSurfaceMetal::Submit");
if (canvas == nullptr) {
FML_DLOG(ERROR) << "Canvas not available.";
return false;
}
{
TRACE_EVENT0("flutter", "SkCanvas::Flush");
canvas->Flush();
}
if (!disable_partial_repaint_) {
uintptr_t texture = reinterpret_cast<uintptr_t>(drawable.get().texture);
for (auto& entry : damage_) {
if (entry.first != texture) {
// Accumulate damage for other framebuffers
if (surface_frame.submit_info().frame_damage) {
entry.second.join(*surface_frame.submit_info().frame_damage);
}
}
}
// Reset accumulated damage for current framebuffer
damage_[texture] = SkIRect::MakeEmpty();
}
return delegate_->PresentDrawable(drawable);
};
SurfaceFrame::FramebufferInfo framebuffer_info;
framebuffer_info.supports_readback = true;
if (!disable_partial_repaint_) {
// Provide accumulated damage to rasterizer (area in current framebuffer that lags behind
// front buffer)
uintptr_t texture = reinterpret_cast<uintptr_t>(drawable.get().texture);
auto i = damage_.find(texture);
if (i != damage_.end()) {
framebuffer_info.existing_damage = i->second;
}
framebuffer_info.supports_partial_repaint = true;
}
return std::make_unique<SurfaceFrame>(std::move(surface), framebuffer_info, submit_callback,
frame_info);
}
std::unique_ptr<SurfaceFrame> GPUSurfaceMetalSkia::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;
}
sk_sp<SkSurface> surface = CreateSurfaceFromMetalTexture(
context_.get(), mtl_texture, kTopLeft_GrSurfaceOrigin, msaa_samples_, kBGRA_8888_SkColorType,
nullptr, nullptr, static_cast<SkSurfaces::TextureReleaseProc>(texture.destruction_callback),
texture.destruction_context);
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, DlCanvas* canvas) -> bool {
TRACE_EVENT0("flutter", "GPUSurfaceMetal::PresentTexture");
if (canvas == nullptr) {
FML_DLOG(ERROR) << "Canvas not available.";
return false;
}
{
TRACE_EVENT0("flutter", "SkCanvas::Flush");
canvas->Flush();
}
return delegate->PresentTexture(texture);
};
SurfaceFrame::FramebufferInfo framebuffer_info;
framebuffer_info.supports_readback = true;
return std::make_unique<SurfaceFrame>(std::move(surface), framebuffer_info, submit_callback,
frame_info);
}
// |Surface|
SkMatrix GPUSurfaceMetalSkia::GetRootTransformation() const {
// This backend does not currently support root surface transformations. Just
// return identity.
return {};
}
// |Surface|
GrDirectContext* GPUSurfaceMetalSkia::GetContext() {
return context_.get();
}
// |Surface|
std::unique_ptr<GLContextResult> GPUSurfaceMetalSkia::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 GPUSurfaceMetalSkia::AllowsDrawingWhenGpuDisabled() const {
return delegate_->AllowsDrawingWhenGpuDisabled();
}
} // namespace flutter