1// Copyright 2020 Google LLC. 2// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. 3 4#include "tools/skottie_ios_app/SkiaContext.h" 5 6#include "include/core/SkSurface.h" 7#include "include/gpu/ganesh/GrDirectContext.h" 8#include "include/gpu/ganesh/mtl/GrMtlBackendContext.h" 9#include "include/gpu/ganesh/mtl/GrMtlDirectContext.h" 10#include "tools/skottie_ios_app/SkMetalViewBridge.h" 11 12#import <Metal/Metal.h> 13#import <MetalKit/MetalKit.h> 14#import <UIKit/UIKit.h> 15 16// A UIView that uses a Metal-backed SkSurface to draw. 17@interface SkiaMtkView : MTKView 18 @property (strong) SkiaViewController* controller; 19 20 // Override of the MTKView interface. Uses Skia+Metal to draw. 21 - (void)drawRect:(CGRect)rect; 22 23 // Required initializer. 24 - (instancetype)initWithFrame:(CGRect)frameRect 25 device:(id<MTLDevice>)device 26 queue:(id<MTLCommandQueue>)queue 27 grDevice:(GrDirectContext*)dContext; 28@end 29 30@implementation SkiaMtkView { 31 id<MTLCommandQueue> fQueue; 32 GrDirectContext* fDContext; 33} 34 35- (instancetype)initWithFrame:(CGRect)frameRect 36 device:(id<MTLDevice>)mtlDevice 37 queue:(id<MTLCommandQueue>)queue 38 grDevice:(GrDirectContext*)dContext { 39 self = [super initWithFrame:frameRect device:mtlDevice]; 40 fQueue = queue; 41 fDContext = dContext; 42 SkMtkViewConfigForSkia(self); 43 return self; 44} 45 46- (void)drawRect:(CGRect)rect { 47 [super drawRect:rect]; 48 // TODO(halcanary): Use the rect and the InvalidationController to speed up rendering. 49 SkiaViewController* viewController = [self controller]; 50 if (!viewController || ![[self currentDrawable] texture] || !fDContext) { 51 return; 52 } 53 CGSize size = [self drawableSize]; 54 sk_sp<SkSurface> surface = SkMtkViewToSurface(self, fDContext); 55 if (!surface) { 56 NSLog(@"error: no sksurface"); 57 return; 58 } 59 [viewController draw:rect toCanvas:surface->getCanvas() atSize:size]; 60 fDContext->flushAndSubmit(surface.get()); 61 surface = nullptr; 62 63 id<MTLCommandBuffer> commandBuffer = [fQueue commandBuffer]; 64 [commandBuffer presentDrawable:[self currentDrawable]]; 65 [commandBuffer commit]; 66 67 bool paused = [viewController isPaused]; 68 [self setEnableSetNeedsDisplay:paused]; 69 [self setPaused:paused]; 70} 71@end 72 73@interface SkiaMetalContext : SkiaContext 74 @property (strong) id<MTLDevice> metalDevice; 75 @property (strong) id<MTLCommandQueue> metalQueue; 76 - (instancetype) init; 77 - (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame; 78 - (SkiaViewController*) getViewController:(UIView*)view; 79@end 80 81@implementation SkiaMetalContext { 82 sk_sp<GrDirectContext> fDContext; 83} 84 85- (instancetype) init { 86 self = [super init]; 87 [self setMetalDevice:MTLCreateSystemDefaultDevice()]; 88 if(![self metalDevice]) { 89 NSLog(@"Metal is not supported on this device"); 90 return nil; 91 } 92 [self setMetalQueue:[[self metalDevice] newCommandQueue]]; 93 GrMtlBackendContext backendContext = {}; 94 backendContext.fDevice.reset((__bridge void*)[self metalDevice]); 95 backendContext.fQueue.reset((__bridge void*)[self metalQueue]); 96 fDContext = GrDirectContexts::MakeMetal(backendContext, GrContextOptions()); 97 98 if (!fDContext) { 99 NSLog(@"GrDirectContexts::MakeMetal failed"); 100 return nil; 101 } 102 return self; 103} 104 105- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame { 106 SkiaMtkView* skiaView = [[SkiaMtkView alloc] initWithFrame:frame 107 device:[self metalDevice] 108 queue:[self metalQueue] 109 grDevice:fDContext.get()]; 110 [skiaView setPreferredFramesPerSecond:30]; 111 [skiaView setController:vc]; 112 return skiaView; 113} 114 115- (SkiaViewController*) getViewController:(UIView*)view { 116 return [view isKindOfClass:[SkiaMtkView class]] ? [(SkiaMtkView*)view controller] : nil; 117} 118@end 119 120SkiaContext* MakeSkiaMetalContext() { return [[SkiaMetalContext alloc] init]; } 121