| // 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" |
| |
| #include <QuartzCore/CAMetalLayer.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" |
| |
| namespace flutter { |
| |
| GPUSurfaceMetal::GPUSurfaceMetal(fml::scoped_nsobject<CAMetalLayer> layer) |
| : layer_(std::move(layer)) { |
| if (!layer_) { |
| FML_LOG(ERROR) << "Could not create metal surface because of invalid layer."; |
| return; |
| } |
| |
| layer.get().pixelFormat = MTLPixelFormatBGRA8Unorm; |
| |
| auto metal_device = fml::scoped_nsprotocol<id<MTLDevice>>([layer_.get().device retain]); |
| auto metal_queue = fml::scoped_nsprotocol<id<MTLCommandQueue>>([metal_device newCommandQueue]); |
| |
| if (!metal_device || !metal_queue) { |
| FML_LOG(ERROR) << "Could not create metal device or queue."; |
| return; |
| } |
| |
| command_queue_ = metal_queue; |
| |
| // The context creation routine accepts arguments using transfer semantics. |
| auto context = GrContext::MakeMetal(metal_device.release(), metal_queue.release()); |
| if (!context) { |
| FML_LOG(ERROR) << "Could not create Skia metal context."; |
| return; |
| } |
| |
| context_ = context; |
| } |
| |
| GPUSurfaceMetal::~GPUSurfaceMetal() = default; |
| |
| // |Surface| |
| bool GPUSurfaceMetal::IsValid() { |
| return layer_ && context_ && command_queue_; |
| } |
| |
| // |Surface| |
| std::unique_ptr<SurfaceFrame> GPUSurfaceMetal::AcquireFrame(const SkISize& size) { |
| if (!IsValid()) { |
| FML_LOG(ERROR) << "Metal surface was invalid."; |
| return nullptr; |
| } |
| |
| if (size.isEmpty()) { |
| FML_LOG(ERROR) << "Metal surface was asked for an empty frame."; |
| return nullptr; |
| } |
| |
| const auto bounds = layer_.get().bounds.size; |
| if (bounds.width <= 0.0 || bounds.height <= 0.0) { |
| FML_LOG(ERROR) << "Metal layer bounds were invalid."; |
| return nullptr; |
| } |
| |
| const auto scale = layer_.get().contentsScale; |
| |
| auto next_drawable = fml::scoped_nsprotocol<id<CAMetalDrawable>>([[layer_ nextDrawable] retain]); |
| if (!next_drawable) { |
| FML_LOG(ERROR) << "Could not acquire next metal drawable."; |
| return nullptr; |
| } |
| |
| auto metal_texture = fml::scoped_nsprotocol<id<MTLTexture>>([next_drawable.get().texture retain]); |
| if (!metal_texture) { |
| FML_LOG(ERROR) << "Could not acquire metal texture from drawable."; |
| return nullptr; |
| } |
| |
| GrMtlTextureInfo metal_texture_info; |
| metal_texture_info.fTexture.reset(SkCFSafeRetain(metal_texture.get())); |
| |
| GrBackendRenderTarget metal_render_target(bounds.width * scale, // width |
| bounds.height * scale, // height |
| 1, // sample count |
| metal_texture_info // metal texture info |
| ); |
| |
| auto command_buffer = |
| fml::scoped_nsprotocol<id<MTLCommandBuffer>>([[command_queue_.get() commandBuffer] retain]); |
| |
| SkSurface::RenderTargetReleaseProc release_proc = [](SkSurface::ReleaseContext context) { |
| [reinterpret_cast<id>(context) release]; |
| }; |
| |
| auto surface = |
| SkSurface::MakeFromBackendRenderTarget(context_.get(), // context |
| metal_render_target, // backend render target |
| kTopLeft_GrSurfaceOrigin, // origin |
| kBGRA_8888_SkColorType, // color type |
| nullptr, // colorspace |
| nullptr, // surface properties |
| release_proc, // release proc |
| metal_texture.release() // release context (texture) |
| ); |
| |
| if (!surface) { |
| FML_LOG(ERROR) << "Could not create the SkSurface from the metal texture."; |
| return nullptr; |
| } |
| |
| auto submit_callback = [drawable = next_drawable, command_buffer]( |
| const SurfaceFrame& surface_frame, SkCanvas* canvas) -> bool { |
| canvas->flush(); |
| [command_buffer.get() presentDrawable:drawable.get()]; |
| [command_buffer.get() commit]; |
| return true; |
| }; |
| |
| return std::make_unique<SurfaceFrame>(std::move(surface), submit_callback); |
| } |
| |
| // |Surface| |
| SkMatrix GPUSurfaceMetal::GetRootTransformation() const { |
| // This backend does not currently support root surface transformations. Just |
| // return identity. |
| SkMatrix matrix; |
| matrix.reset(); |
| return matrix; |
| } |
| |
| // |Surface| |
| GrContext* GPUSurfaceMetal::GetContext() { |
| return context_.get(); |
| } |
| |
| // |Surface| |
| bool GPUSurfaceMetal::MakeRenderContextCurrent() { |
| // This backend has no such concept. |
| return true; |
| } |
| |
| } // namespace flutter |