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>
This commit is contained in:
Hal Canary 2019-09-09 17:03:38 -04:00 committed by Skia Commit-Bot
parent d3d13af110
commit 4119443532
8 changed files with 402 additions and 9 deletions

View File

@ -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"
}
}

View File

@ -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.

View File

@ -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

View File

@ -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];
}

View File

@ -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

View File

@ -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

View File

@ -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]));
}
}

View File

@ -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',