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