1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "flutter/shell/gpu/gpu_surface_metal.h" 6 7#include <QuartzCore/CAMetalLayer.h> 8 9#include "third_party/skia/include/core/SkSurface.h" 10#include "third_party/skia/include/gpu/GrBackendSurface.h" 11#include "third_party/skia/include/ports/SkCFObject.h" 12 13namespace flutter { 14 15GPUSurfaceMetal::GPUSurfaceMetal(fml::scoped_nsobject<CAMetalLayer> layer) 16 : layer_(std::move(layer)) { 17 if (!layer_) { 18 FML_LOG(ERROR) << "Could not create metal surface because of invalid layer."; 19 return; 20 } 21 22 layer.get().pixelFormat = MTLPixelFormatBGRA8Unorm; 23 24 auto metal_device = fml::scoped_nsprotocol<id<MTLDevice>>([layer_.get().device retain]); 25 auto metal_queue = fml::scoped_nsprotocol<id<MTLCommandQueue>>([metal_device newCommandQueue]); 26 27 if (!metal_device || !metal_queue) { 28 FML_LOG(ERROR) << "Could not create metal device or queue."; 29 return; 30 } 31 32 command_queue_ = metal_queue; 33 34 // The context creation routine accepts arguments using transfer semantics. 35 auto context = GrContext::MakeMetal(metal_device.release(), metal_queue.release()); 36 if (!context) { 37 FML_LOG(ERROR) << "Could not create Skia metal context."; 38 return; 39 } 40 41 context_ = context; 42} 43 44GPUSurfaceMetal::~GPUSurfaceMetal() = default; 45 46// |Surface| 47bool GPUSurfaceMetal::IsValid() { 48 return layer_ && context_ && command_queue_; 49} 50 51// |Surface| 52std::unique_ptr<SurfaceFrame> GPUSurfaceMetal::AcquireFrame(const SkISize& size) { 53 if (!IsValid()) { 54 FML_LOG(ERROR) << "Metal surface was invalid."; 55 return nullptr; 56 } 57 58 if (size.isEmpty()) { 59 FML_LOG(ERROR) << "Metal surface was asked for an empty frame."; 60 return nullptr; 61 } 62 63 const auto bounds = layer_.get().bounds.size; 64 if (bounds.width <= 0.0 || bounds.height <= 0.0) { 65 FML_LOG(ERROR) << "Metal layer bounds were invalid."; 66 return nullptr; 67 } 68 69 const auto scale = layer_.get().contentsScale; 70 71 auto next_drawable = fml::scoped_nsprotocol<id<CAMetalDrawable>>([[layer_ nextDrawable] retain]); 72 if (!next_drawable) { 73 FML_LOG(ERROR) << "Could not acquire next metal drawable."; 74 return nullptr; 75 } 76 77 auto metal_texture = fml::scoped_nsprotocol<id<MTLTexture>>([next_drawable.get().texture retain]); 78 if (!metal_texture) { 79 FML_LOG(ERROR) << "Could not acquire metal texture from drawable."; 80 return nullptr; 81 } 82 83 GrMtlTextureInfo metal_texture_info; 84 metal_texture_info.fTexture.reset(SkCFSafeRetain(metal_texture.get())); 85 86 GrBackendRenderTarget metal_render_target(bounds.width * scale, // width 87 bounds.height * scale, // height 88 1, // sample count 89 metal_texture_info // metal texture info 90 ); 91 92 auto command_buffer = 93 fml::scoped_nsprotocol<id<MTLCommandBuffer>>([[command_queue_.get() commandBuffer] retain]); 94 95 SkSurface::RenderTargetReleaseProc release_proc = [](SkSurface::ReleaseContext context) { 96 [reinterpret_cast<id>(context) release]; 97 }; 98 99 auto surface = 100 SkSurface::MakeFromBackendRenderTarget(context_.get(), // context 101 metal_render_target, // backend render target 102 kTopLeft_GrSurfaceOrigin, // origin 103 kBGRA_8888_SkColorType, // color type 104 nullptr, // colorspace 105 nullptr, // surface properties 106 release_proc, // release proc 107 metal_texture.release() // release context (texture) 108 ); 109 110 if (!surface) { 111 FML_LOG(ERROR) << "Could not create the SkSurface from the metal texture."; 112 return nullptr; 113 } 114 115 auto submit_callback = [drawable = next_drawable, command_buffer]( 116 const SurfaceFrame& surface_frame, SkCanvas* canvas) -> bool { 117 canvas->flush(); 118 [command_buffer.get() presentDrawable:drawable.get()]; 119 [command_buffer.get() commit]; 120 return true; 121 }; 122 123 return std::make_unique<SurfaceFrame>(std::move(surface), submit_callback); 124} 125 126// |Surface| 127SkMatrix GPUSurfaceMetal::GetRootTransformation() const { 128 // This backend does not currently support root surface transformations. Just 129 // return identity. 130 SkMatrix matrix; 131 matrix.reset(); 132 return matrix; 133} 134 135// |Surface| 136GrContext* GPUSurfaceMetal::GetContext() { 137 return context_.get(); 138} 139 140// |Surface| 141bool GPUSurfaceMetal::MakeRenderContextCurrent() { 142 // This backend has no such concept. 143 return true; 144} 145 146} // namespace flutter 147