experimental/skottie_ios: Skottie iOS/Metal App
To use, see instructions in experimental/skottie_ios/README.md .
No-Try: true
Change-Id: I4fb71576c5e38c7776d14561930b8c2598cfb48f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/240284
Commit-Queue: Hal Canary <halcanary@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/experimental/skottie_ios/BUILD.gn b/experimental/skottie_ios/BUILD.gn
new file mode 100644
index 0000000..af1efa6
--- /dev/null
+++ b/experimental/skottie_ios/BUILD.gn
@@ -0,0 +1,45 @@
+# Copyright 2019 Google LLC.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("../../gn/ios.gni")
+
+if (is_ios && skia_use_metal) {
+ ios_app_bundle("skottie_ios") {
+ sources = [
+ "SkMetalViewBridge.h",
+ "SkMetalViewBridge.mm",
+ "SkottieMtkView.h",
+ "SkottieMtkView.mm",
+ "main.mm",
+ ]
+ data_sources = [
+ "../../resources/skottie/skottie-3d-rotation-order.json",
+ "../../resources/skottie/skottie-camera-parent-3.json",
+ "../../resources/skottie/skottie-gradient-ramp.json",
+ "../../resources/skottie/skottie-linear-wipe-effect.json",
+ "../../resources/skottie/skottie-text-animator-1.json",
+ "../../resources/skottie/skottie-text-animator-2.json",
+ "../../resources/skottie/skottie-text-animator-3.json",
+ "../../resources/skottie/skottie-text-animator-4.json",
+ "../../resources/skottie/skottie-text-animator-5.json",
+ "../../resources/skottie/skottie-text-animator-8.json",
+ "../../resources/skottie/skottie-transform-effect.json",
+ "../../resources/skottie/skottie_sample_2.json",
+ ]
+ deps = [
+ "../..:skia",
+ "../../modules/skottie",
+ ]
+ cflags_objcc = [
+ "-std=c++14",
+ "-w",
+ ]
+ libs = [
+ "Metal.framework",
+ "MetalKit.framework",
+ "UIKit.framework",
+ ]
+ launchscreen = "../../platform_tools/ios/app/LaunchScreen.storyboard"
+ }
+}
diff --git a/experimental/skottie_ios/README.md b/experimental/skottie_ios/README.md
new file mode 100644
index 0000000..fcf3489
--- /dev/null
+++ b/experimental/skottie_ios/README.md
@@ -0,0 +1,29 @@
+##`skottie_ios`
+
+How to compile:
+
+ cd $SKIA_ROOT_DIRECTORY
+
+ cat >> BUILD.gn <<EOM
+ if (is_ios && skia_use_metal) {
+ group("skottie_ios") {
+ deps = [ "experimental/skottie_ios" ]
+ }
+ }
+ EOM
+
+ mkdir -p out/ios_arm64_mtl
+ cat > out/ios_arm64_mtl/args.gn <<EOM
+ target_os="ios"
+ target_cpu="arm64"
+ skia_use_metal=true
+ skia_use_expat=false
+ skia_enable_pdf=false
+ EOM
+
+ tools/git-sync-deps
+ bin/gn gen out/ios_arm64_mtl
+ ninja -C out/ios_arm64_mtl skottie_ios
+
+Then install the `out/ios_arm64_mtl/skottie_ios.app` bundle.
+
diff --git a/experimental/skottie_ios/SkMetalViewBridge.h b/experimental/skottie_ios/SkMetalViewBridge.h
new file mode 100644
index 0000000..9fb88a4
--- /dev/null
+++ b/experimental/skottie_ios/SkMetalViewBridge.h
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#ifndef SkMetalViewBridge_DEFINED
+#define SkMetalViewBridge_DEFINED
+
+#import <MetalKit/MetalKit.h>
+
+class SkSurface;
+class GrContext;
+class GrContextOptions;
+template <typename T> class sk_sp;
+
+sk_sp<SkSurface> SkMtkViewToSurface(MTKView*, GrContext*);
+
+sk_sp<GrContext> SkMetalDeviceToGrContext(id<MTLDevice>, const GrContextOptions&);
+
+void SkMtkViewConfigForSkia(MTKView*);
+
+#endif // SkMetalViewBridge_DEFINED
diff --git a/experimental/skottie_ios/SkMetalViewBridge.mm b/experimental/skottie_ios/SkMetalViewBridge.mm
new file mode 100644
index 0000000..aabf2ff
--- /dev/null
+++ b/experimental/skottie_ios/SkMetalViewBridge.mm
@@ -0,0 +1,53 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "experimental/skottie_ios/SkMetalViewBridge.h"
+
+#include "include/core/SkSurface.h"
+#include "include/gpu/GrBackendSurface.h"
+#include "include/gpu/GrContext.h"
+#include "include/gpu/GrContextOptions.h"
+#include "include/gpu/mtl/GrMtlTypes.h"
+
+#import <Metal/Metal.h>
+#import <MetalKit/MetalKit.h>
+
+sk_sp<SkSurface> SkMtkViewToSurface(MTKView* mtkView, GrContext* grContext) {
+ if (![[mtkView currentDrawable] texture] ||
+ !grContext ||
+ MTLPixelFormatDepth32Float_Stencil8 != [mtkView depthStencilPixelFormat] ||
+ MTLPixelFormatBGRA8Unorm != [mtkView colorPixelFormat]) {
+ return nullptr;
+ }
+ const SkColorType colorType = kBGRA_8888_SkColorType; // MTLPixelFormatBGRA8Unorm
+ sk_sp<SkColorSpace> colorSpace = nullptr; // MTLPixelFormatBGRA8Unorm
+ const GrSurfaceOrigin origin = kTopLeft_GrSurfaceOrigin;
+ const SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
+ int sampleCount = (int)[mtkView sampleCount];
+ CGSize size = [mtkView drawableSize];
+ int width = (int)size.width;
+ int height = (int)size.height;
+
+ GrMtlTextureInfo fbInfo;
+ fbInfo.fTexture.retain((__bridge const void*)([[mtkView currentDrawable] texture]));
+ if (sampleCount == 1) {
+ GrBackendRenderTarget backendRT(width, height, 1, fbInfo);
+ return SkSurface::MakeFromBackendRenderTarget(grContext, backendRT, origin,
+ colorType, colorSpace, &surfaceProps);
+ } else {
+ GrBackendTexture backendTexture(width, height, GrMipMapped::kNo, fbInfo);
+ return SkSurface::MakeFromBackendTexture(grContext, backendTexture, origin, sampleCount,
+ colorType, colorSpace, &surfaceProps);
+ }
+}
+
+sk_sp<GrContext> SkMetalDeviceToGrContext(id<MTLDevice> device, const GrContextOptions& opts) {
+ return GrContext::MakeMetal((void*)device,
+ (void*)[device newCommandQueue], opts);
+}
+
+void SkMtkViewConfigForSkia(MTKView* mtkView) {
+ [mtkView setDepthStencilPixelFormat:MTLPixelFormatDepth32Float_Stencil8];
+ [mtkView setColorPixelFormat:MTLPixelFormatBGRA8Unorm];
+ [mtkView setSampleCount:1];
+}
diff --git a/experimental/skottie_ios/SkottieMtkView.h b/experimental/skottie_ios/SkottieMtkView.h
new file mode 100644
index 0000000..33b7384
--- /dev/null
+++ b/experimental/skottie_ios/SkottieMtkView.h
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#ifndef SkottieMtkView_DEFINED
+#define SkottieMtkView_DEFINED
+
+#import <MetalKit/MetalKit.h>
+#import <UIKit/UIKit.h>
+
+class GrContext;
+
+@interface SkottieMtkView : MTKView
+@property (assign) GrContext* grContext; // non-owning pointer.
+- (void)drawRect:(CGRect)rect;
+- (BOOL)loadAnimation:(NSData*)d;
+- (CGSize)size;
+- (BOOL)togglePaused;
+@end
+
+#endif // SkottieMtkView_DEFINED
diff --git a/experimental/skottie_ios/SkottieMtkView.mm b/experimental/skottie_ios/SkottieMtkView.mm
new file mode 100644
index 0000000..3c71e52
--- /dev/null
+++ b/experimental/skottie_ios/SkottieMtkView.mm
@@ -0,0 +1,95 @@
+ //Copyright 2019 Google LLC.
+ //Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "experimental/skottie_ios/SkottieMtkView.h"
+
+#include "include/core/SkCanvas.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkSurface.h"
+#include "include/core/SkTime.h"
+
+#include "modules/skottie/include/Skottie.h"
+
+#include "experimental/skottie_ios/SkMetalViewBridge.h"
+
+@implementation SkottieMtkView {
+ sk_sp<skottie::Animation> fAnimation; // owner
+ CGSize fSize;
+ double fStartTime;
+ double fTime;
+ SkMatrix fMatrix;
+ SkRect fAnimRect;
+ bool fPaused;
+}
+
+-(void)dealloc {
+ fAnimation = nullptr;
+ [super dealloc];
+}
+
+- (void)drawRect:(CGRect)rect {
+ [super drawRect:rect];
+ // TODO(halcanary): Use the rect and the InvalidationController to speed up rendering.
+ if (!fAnimation || ![[self currentDrawable] texture] || ![self grContext]) {
+ return;
+ }
+ CGSize size = [self drawableSize];
+ if (size.width != fSize.width || size.height != fSize.height) {
+ float aw = fAnimRect.right(),
+ ah = fAnimRect.bottom();
+ if (aw > 0 && ah > 0) {
+ float scale = std::min(size.width / aw, size.height / ah);
+ fMatrix.setScaleTranslate(scale,
+ scale,
+ ((float)size.width - aw * scale) * 0.5f,
+ ((float)size.height - ah * scale) * 0.5f);
+ } else {
+ fMatrix = SkMatrix();
+ }
+ fSize = size;
+ }
+ SkPaint whitePaint(SkColors::kWhite);
+ if (!fPaused) {
+ fTime = SkTime::GetNSecs();
+ fAnimation->seekFrameTime(std::fmod(1e-9 * (fTime - fStartTime),
+ fAnimation->duration()), nullptr);
+ }
+ sk_sp<SkSurface> surface = SkMtkViewToSurface(self, [self grContext]);
+ if (!surface) {
+ NSLog(@"error: no sksurface");
+ return;
+ }
+ SkCanvas* canvas = surface->getCanvas();
+ canvas->concat(fMatrix);
+ canvas->drawRect(fAnimRect, whitePaint);
+ fAnimation->render(canvas);
+ surface->flush();
+ surface = nullptr;
+ [[self currentDrawable] present];
+}
+
+- (BOOL)loadAnimation:(NSData*) data {
+ skottie::Animation::Builder builder;
+ fAnimation = builder.make((const char*)[data bytes], (size_t)[data length]);
+ fTime = fStartTime = SkTime::GetNSecs();
+ fSize = {0, 0};
+ fAnimRect = fAnimation ? SkRect::MakeSize(fAnimation->size()) : SkRect{0, 0, 0, 0};
+ return fAnimation != nullptr;
+}
+
+- (CGSize)size {
+ if (fAnimation) {
+ const SkSize& s = fAnimation->size();
+ return {(CGFloat)s.width(), (CGFloat)s.height()};
+ }
+ return {0, 0};
+}
+
+- (BOOL)togglePaused {
+ fPaused = !fPaused;
+ if (!fPaused) {
+ fStartTime += (SkTime::GetNSecs() - fTime);
+ }
+ return fPaused;
+}
+@end
diff --git a/experimental/skottie_ios/main.mm b/experimental/skottie_ios/main.mm
new file mode 100644
index 0000000..73ea26d
--- /dev/null
+++ b/experimental/skottie_ios/main.mm
@@ -0,0 +1,141 @@
+// Copyright 2019 Google LLC.
+// Use of this source cofcee is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "experimental/skottie_ios/SkMetalViewBridge.h"
+#include "experimental/skottie_ios/SkottieMtkView.h"
+
+#include "include/gpu/GrContext.h"
+#include "include/gpu/GrContextOptions.h"
+
+#import <Metal/Metal.h>
+#import <MetalKit/MetalKit.h>
+#import <UIKit/UIKit.h>
+
+static UIStackView* make_skottie_stack(CGFloat width,
+ id<MTLDevice> metalDevice,
+ GrContext* grContext) {
+ UIStackView* stack = [[UIStackView alloc] init];
+ [stack setAxis:UILayoutConstraintAxisVertical];
+ [stack setDistribution:UIStackViewDistributionEqualSpacing];
+
+ NSBundle* mainBundle = [NSBundle mainBundle];
+ NSArray<NSString*>* paths = [mainBundle pathsForResourcesOfType:@"json"
+ inDirectory:nil];
+ constexpr CGFloat kSpacing = 2;
+ CGFloat totalHeight = kSpacing;
+ for (NSUInteger i = 0; i < [paths count]; ++i) {
+ NSString* path = [paths objectAtIndex:i];
+ NSData* content = [NSData dataWithContentsOfFile:path];
+ if (!content) {
+ NSLog(@"'%@' not found", path);
+ continue;
+ }
+ SkottieMtkView* skottieView = [[SkottieMtkView alloc] init];
+ if (![skottieView loadAnimation:content]) {
+ continue;
+ }
+ [skottieView setDevice:metalDevice];
+ [skottieView setGrContext:grContext];
+ SkMtkViewConfigForSkia(skottieView);
+ CGSize animSize = [skottieView size];
+ CGFloat height = animSize.width ? (width * animSize.height / animSize.width) : 0;
+ [skottieView setFrame:{{0, 0}, {width, height}}];
+ [skottieView setPreferredFramesPerSecond:30];
+ [[[skottieView heightAnchor] constraintEqualToConstant:height] setActive:true];
+ [[[skottieView widthAnchor] constraintEqualToConstant:width] setActive:true];
+ [stack addArrangedSubview:skottieView];
+ totalHeight += height + kSpacing;
+ }
+ [stack setFrame:{{0, 0}, {width, totalHeight}}];
+ return stack;
+}
+
+@interface AppViewController : UIViewController
+ @property (strong) id<MTLDevice> metalDevice;
+ @property (strong) UIStackView* stackView;
+@end
+
+@implementation AppViewController {
+ sk_sp<GrContext> fGrContext;
+}
+
+- (void)dealloc {
+ fGrContext = nullptr;
+ [super dealloc];
+}
+
+- (void)loadView {
+ [self setView:[[UIView alloc] init]];
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ if (!fGrContext) {
+ [self setMetalDevice:MTLCreateSystemDefaultDevice()];
+ if(![self metalDevice]) {
+ NSLog(@"Metal is not supported on this device");
+ return;
+ }
+ GrContextOptions grContextOptions; // set different options here.
+ fGrContext = SkMetalDeviceToGrContext([self metalDevice], grContextOptions);
+ }
+
+ [self setStackView:make_skottie_stack([[UIScreen mainScreen] bounds].size.width,
+ [self metalDevice], fGrContext.get())];
+
+ CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
+ CGSize mainScreenSize = [[UIScreen mainScreen] bounds].size;
+ CGRect scrollViewBounds = {{0, statusBarHeight},
+ {mainScreenSize.width, mainScreenSize.height - statusBarHeight}};
+ UIScrollView* scrollView = [[UIScrollView alloc] initWithFrame:scrollViewBounds];
+ [scrollView setContentSize:[[self stackView] frame].size];
+ [scrollView addSubview:[self stackView]];
+ [scrollView setBackgroundColor:[UIColor blackColor]];
+
+ UIView* mainView = [self view];
+ [mainView setBounds:{{0, 0}, mainScreenSize}];
+ [mainView setBackgroundColor:[UIColor whiteColor]];
+ [mainView addSubview:scrollView];
+
+ UITapGestureRecognizer* tapGestureRecognizer = [[UITapGestureRecognizer alloc] init];
+ [tapGestureRecognizer addTarget:self action:@selector(handleTap:)];
+ [mainView addGestureRecognizer:tapGestureRecognizer];
+}
+
+- (void)handleTap:(UIGestureRecognizer*)sender {
+ if (![sender state] == UIGestureRecognizerStateEnded) {
+ return;
+ }
+ NSArray<UIView*>* subviews = [[self stackView] subviews];
+ for (NSUInteger i = 0; i < [subviews count]; ++i) {
+ UIView* subview = [subviews objectAtIndex:i];
+ if (![subview isKindOfClass:[SkottieMtkView class]]) {
+ continue;
+ }
+ SkottieMtkView* skottieView = (SkottieMtkView*)subview;
+ BOOL paused = [skottieView togglePaused];
+ [skottieView setEnableSetNeedsDisplay:paused];
+ [skottieView setPaused:paused];
+ }
+}
+@end
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+@property (strong, nonatomic) UIWindow* window;
+@end
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication*)app didFinishLaunchingWithOptions:(NSDictionary*)ops {
+ [self setWindow:[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]];
+ [[self window] setRootViewController:[[AppViewController alloc] init]];
+ [[self window] makeKeyAndVisible];
+ return YES;
+}
+@end
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/infra/bots/compile.isolate b/infra/bots/compile.isolate
index 508a541..85accb8 100644
--- a/infra/bots/compile.isolate
+++ b/infra/bots/compile.isolate
@@ -17,15 +17,7 @@
'../../dm',
'../../docs/examples',
'../../example',
- '../../experimental/Networking/SkSockets.cpp',
- '../../experimental/Networking/SkSockets.h',
- '../../experimental/c-api-example/skia-c-example.c',
- '../../experimental/ffmpeg',
- '../../experimental/minimal_ios_mtl_skia_app/BUILD.gn',
- '../../experimental/svg/model',
- '../../experimental/tools/coreGraphicsPdf2png.cpp',
- '../../experimental/wasm-skp-debugger/debugger_bindings.cpp',
- '../../experimental/xform',
+ '../../experimental',
'../../fuzz',
'../../gm',
'../../gn',