From 41194435324b19c20c8ea992d324399f6923b459 Mon Sep 17 00:00:00 2001 From: Hal Canary Date: Mon, 9 Sep 2019 17:03:38 -0400 Subject: [PATCH] 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 Reviewed-by: Jim Van Verth --- experimental/skottie_ios/BUILD.gn | 45 ++++++ experimental/skottie_ios/README.md | 29 ++++ experimental/skottie_ios/SkMetalViewBridge.h | 19 +++ experimental/skottie_ios/SkMetalViewBridge.mm | 53 +++++++ experimental/skottie_ios/SkottieMtkView.h | 19 +++ experimental/skottie_ios/SkottieMtkView.mm | 95 ++++++++++++ experimental/skottie_ios/main.mm | 141 ++++++++++++++++++ infra/bots/compile.isolate | 10 +- 8 files changed, 402 insertions(+), 9 deletions(-) create mode 100644 experimental/skottie_ios/BUILD.gn create mode 100644 experimental/skottie_ios/README.md create mode 100644 experimental/skottie_ios/SkMetalViewBridge.h create mode 100644 experimental/skottie_ios/SkMetalViewBridge.mm create mode 100644 experimental/skottie_ios/SkottieMtkView.h create mode 100644 experimental/skottie_ios/SkottieMtkView.mm create mode 100644 experimental/skottie_ios/main.mm diff --git a/experimental/skottie_ios/BUILD.gn b/experimental/skottie_ios/BUILD.gn new file mode 100644 index 0000000000..af1efa68c4 --- /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 0000000000..fcf3489fcf --- /dev/null +++ b/experimental/skottie_ios/README.md @@ -0,0 +1,29 @@ +##`skottie_ios` + +How to compile: + + cd $SKIA_ROOT_DIRECTORY + + cat >> BUILD.gn < out/ios_arm64_mtl/args.gn < + +class SkSurface; +class GrContext; +class GrContextOptions; +template class sk_sp; + +sk_sp SkMtkViewToSurface(MTKView*, GrContext*); + +sk_sp SkMetalDeviceToGrContext(id, 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 0000000000..aabf2ff43f --- /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 +#import + +sk_sp 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 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 SkMetalDeviceToGrContext(id 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 0000000000..33b7384a8f --- /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 +#import + +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 0000000000..3c71e52644 --- /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 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 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 0000000000..73ea26d5d2 --- /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 +#import +#import + +static UIStackView* make_skottie_stack(CGFloat width, + id metalDevice, + GrContext* grContext) { + UIStackView* stack = [[UIStackView alloc] init]; + [stack setAxis:UILayoutConstraintAxisVertical]; + [stack setDistribution:UIStackViewDistributionEqualSpacing]; + + NSBundle* mainBundle = [NSBundle mainBundle]; + NSArray* 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 metalDevice; + @property (strong) UIStackView* stackView; +@end + +@implementation AppViewController { + sk_sp 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* 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 +@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 508a541549..85accb86e2 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',