skottie_ios_app: Add OpenGL.

- Consolidate Skottie logic into SkottieViewController class that no longer
    knows about which backend we use.

  - Abstract out SkiaViewController interdace which SkottieViewController
    implements.

  - Create three classes SkiaGLView, SkiaUIView, and SkiaMtkView, which all
    accept SkiaViewController objects but override GLKView, UIView, and
    MTKView.

  - SkAnimationDraw and SkTimeKeeper now SkiaViewController
    implementation details, no longer shared in headers.

Cq-Include-Trybots: skia/skia.primary:Build-Mac-Clang-arm64-Debug-iOS_Metal
Cq-Include-Trybots: skia/skia.primary:Build-Mac-Clang-arm64-Debug-iOS
Change-Id: I96ff2911d63da7d5327c81f91996b2a1b12c4419
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/261178
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
This commit is contained in:
Hal Canary 2019-12-18 16:26:19 -05:00 committed by Skia Commit-Bot
parent 7543587ee2
commit 118df7cf62
23 changed files with 727 additions and 465 deletions

View File

@ -7,6 +7,8 @@ import("../../gn/ios.gni")
if (is_ios && skia_use_metal) {
ios_app_bundle("minimal_ios_mtl_skia_app") {
sources = [
"../../tools/skottie_ios_app/GrContextHolder.h",
"../../tools/skottie_ios_app/GrContextHolder.mm",
"../../tools/skottie_ios_app/SkMetalViewBridge.h",
"../../tools/skottie_ios_app/SkMetalViewBridge.mm",
"main.mm",

View File

@ -794,13 +794,16 @@ SKSHAPER_PRIMITIVE_SRCS = [
################################################################################
SKOTTIE_IOS_LIB_SRCS = [
"tools/skottie_ios_app/SkAnimationDraw.h",
"tools/skottie_ios_app/SkTimeKeeper.h",
"tools/skottie_ios_app/SkottieUIView.mm",
"tools/skottie_ios_app/SkiaContext.mm",
"tools/skottie_ios_app/SkiaUIContext.mm",
"tools/skottie_ios_app/SkiaViewController.mm",
"tools/skottie_ios_app/SkottieViewController.mm",
]
SKOTTIE_IOS_LIB_HDRS = [
"tools/skottie_ios_app/SkottieUIView.h",
"tools/skottie_ios_app/SkiaContext.h",
"tools/skottie_ios_app/SkiaViewController.h",
"tools/skottie_ios_app/SkottieViewController.h",
]
SKOTTIE_IOS_LIB_SDK_FRAMEWORKS = [

View File

@ -12,22 +12,24 @@ if (is_ios) {
}
ios_app_bundle("skottie_example") {
sources = [
"SkAnimationDraw.h",
"SkTimeKeeper.h",
"SkiaContext.h",
"SkiaContext.mm",
"SkiaViewController.h",
"SkiaViewController.mm",
"SkottieViewController.h",
"SkottieViewController.mm",
"main.mm",
]
if (skia_use_metal) {
if (skia_enable_gpu && skia_use_metal) {
sources += [
"SkMetalViewBridge.h",
"SkMetalViewBridge.mm",
"SkottieMtkView.h",
"SkottieMtkView.mm",
"SkiaMetalContext.mm",
]
} else if (skia_enable_gpu && skia_use_gl) {
sources += [ "SkiaGLContext.mm" ]
} else {
sources += [
"SkottieUIView.h",
"SkottieUIView.mm",
]
sources += [ "SkiaUIContext.mm" ]
}
data_sources = [
"../../resources/skottie/skottie-3d-rotation-order.json",
@ -52,14 +54,21 @@ if (is_ios) {
"-w",
]
libs = [
"UIKit.framework",
"CoreFoundation.framework",
"Foundation.framework",
"QuartzCore.framework",
"UIKit.framework",
]
if (skia_use_metal) {
if (skia_enable_gpu && skia_use_metal) {
libs += [
"Metal.framework",
"MetalKit.framework",
]
} else if (skia_enable_gpu && skia_use_gl) {
libs += [
"GLKit.framework",
"OpenGLES.framework",
]
}
launchscreen = "../../platform_tools/ios/app/LaunchScreen.storyboard"
}

View File

@ -0,0 +1,16 @@
// 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 GrContextHolder_DEFINED
#define GrContextHolder_DEFINED
#include <memory>
class GrContext;
// A struct to take ownership of a GrContext.
struct GrContextRelease { void operator()(GrContext*); };
using GrContextHolder = std::unique_ptr<GrContext, GrContextRelease>;
// Wrapper around GrContext::MakeGL
GrContextHolder SkMakeGLContext();
#endif // GrContextHolder_DEFINED

View File

@ -0,0 +1,24 @@
// 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 "tools/skottie_ios_app/GrContextHolder.h"
#include "include/core/SkTypes.h"
#if SK_SUPPORT_GPU
#include "include/gpu/GrContext.h"
#include "include/gpu/GrContextOptions.h"
#include "include/gpu/gl/GrGLInterface.h"
GrContextHolder SkMakeGLContext() {
return GrContextHolder(GrContext::MakeGL(nullptr, GrContextOptions()).release());
}
void GrContextRelease::operator()(GrContext* ptr) { SkSafeUnref(ptr); }
#else
void GrContextRelease::operator()(GrContext*) { SkASSERT(false); }
#endif

View File

@ -34,9 +34,32 @@ How to compile for the CPU backend:
skia_enable_gpu=false
skia_enable_pdf=false
skia_use_expat=false
EOM
tools/git-sync-deps
bin/gn gen out/ios_arm64_cpu
ninja -C out/ios_arm64_cpu skottie_example
Then install the `out/ios_arm64_cpu/skottie_example.app` bundle.
## OpenGL
How to compile for the OpenGL backend:
cd $SKIA_ROOT_DIRECTORY
mkdir -p out/ios_arm64_gl
cat > out/ios_arm64_gl/args.gn <<EOM
target_cpu="arm64"
target_os="ios"
skia_enable_gpu=true
skia_use_metal=false
skia_enable_pdf=false
skia_use_expat=false
EOM
tools/git-sync-deps
bin/gn gen out/ios_arm64_gl
ninja -C out/ios_arm64_gl skottie_example
Then install the `out/ios_arm64_gl/skottie_example.app` bundle.

View File

@ -1,61 +0,0 @@
// 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 SkAnimationDraw_DEFINED
#define SkAnimationDraw_DEFINED
#include "include/core/SkCanvas.h"
#include "include/core/SkPaint.h"
#include "modules/skottie/include/Skottie.h"
class SkAnimationDraw {
public:
SkAnimationDraw() = default;
~SkAnimationDraw() = default;
explicit operator bool() const { return fAnimation != nullptr; }
void draw(SkSize size, SkCanvas* canvas) {
if (size.width() != fSize.width() || size.height() != fSize.height()) {
// Cache the current matrix; change only if size changes.
if (fAnimationSize.width() > 0 && fAnimationSize.height() > 0) {
float scale = std::min(size.width() / fAnimationSize.width(),
size.height() / fAnimationSize.height());
fMatrix.setScaleTranslate(
scale, scale,
(size.width() - fAnimationSize.width() * scale) * 0.5f,
(size.height() - fAnimationSize.height() * scale) * 0.5f);
} else {
fMatrix = SkMatrix();
}
fSize = size;
}
canvas->concat(fMatrix);
SkRect rect = {0, 0, fAnimationSize.width(), fAnimationSize.height()};
canvas->drawRect(rect, SkPaint(SkColors::kWhite));
fAnimation->render(canvas);
}
void load(const void* data, size_t length) {
skottie::Animation::Builder builder;
fAnimation = builder.make((const char*)data, (size_t)length);
fSize = {0, 0};
fAnimationSize = fAnimation ? fAnimation->size() : SkSize{0, 0};
}
void seek(double time) { if (fAnimation) { fAnimation->seekFrameTime(time, nullptr); } }
float duration() { return fAnimation ? fAnimation->duration() : 0; }
SkSize size() { return fAnimationSize; }
private:
sk_sp<skottie::Animation> fAnimation; // owner
SkSize fSize;
SkSize fAnimationSize;
SkMatrix fMatrix;
SkAnimationDraw(const SkAnimationDraw&) = delete;
SkAnimationDraw& operator=(const SkAnimationDraw&) = delete;
};
#endif // SkAnimationDraw_DEFINED

View File

@ -3,20 +3,17 @@
#ifndef SkMetalViewBridge_DEFINED
#define SkMetalViewBridge_DEFINED
#include "tools/skottie_ios_app/GrContextHolder.h"
#import <MetalKit/MetalKit.h>
#include <memory>
class SkSurface;
class GrContext;
template <typename T> class sk_sp;
sk_sp<SkSurface> SkMtkViewToSurface(MTKView*, GrContext*);
struct GrContextRelease { void operator()(GrContext*); };
using GrContextHolder = std::unique_ptr<GrContext, GrContextRelease>;
GrContextHolder SkMetalDeviceToGrContext(id<MTLDevice>, id<MTLCommandQueue>);
void SkMtkViewConfigForSkia(MTKView*);

View File

@ -29,8 +29,6 @@ sk_sp<SkSurface> SkMtkViewToSurface(MTKView* mtkView, GrContext* grContext) {
colorType, colorSpace, &surfaceProps);
}
void GrContextRelease::operator()(GrContext* ptr) { SkSafeUnref(ptr); }
GrContextHolder SkMetalDeviceToGrContext(id<MTLDevice> device, id<MTLCommandQueue> queue) {
GrContextOptions grContextOptions; // set different options here.
return GrContextHolder(GrContext::MakeMetal((__bridge void*)device,

View File

@ -1,66 +0,0 @@
// 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 SkTimeKeeper_DEFINED
#define SkTimeKeeper_DEFINED
#include "include/core/SkTime.h"
#include <cmath>
class SkTimeKeeper {
private:
double fStartTime = 0; // used when running
float fAnimationMoment = 0; // when paused.
float fDuration = 0;
bool fPaused = false;
bool fStopAtEnd = false;
public:
void setStopAtEnd(bool s) { fStopAtEnd = s; }
float currentTime() {
if (0 == fDuration) {
return 0;
}
if (fPaused) {
return fAnimationMoment;
}
double time = 1e-9 * (SkTime::GetNSecs() - fStartTime);
if (fStopAtEnd && time >= fDuration) {
fPaused = true;
fAnimationMoment = fDuration;
return fAnimationMoment;
}
return std::fmod(time, fDuration);
}
void setDuration(float d) {
fDuration = d;
fStartTime = SkTime::GetNSecs();
fAnimationMoment = 0;
}
bool paused() const { return fPaused; }
float duration() const { return fDuration; }
void seek(float seconds) {
if (fPaused) {
fAnimationMoment = std::fmod(seconds, fDuration);
} else {
fStartTime = SkTime::GetNSecs() - 1e9 * seconds;
}
}
void togglePaused() {
if (fPaused) {
double offset = (fAnimationMoment >= fDuration) ? 0 : -1e9 * fAnimationMoment;
fStartTime = SkTime::GetNSecs() + offset;
fPaused = false;
} else {
fAnimationMoment = this->currentTime();
fPaused = true;
}
}
};
#endif // SkTimeKeeper_DEFINED

View File

@ -0,0 +1,20 @@
// Copyright 2020 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#ifndef SkiaContext_DEFINED
#define SkiaContext_DEFINED
#include "tools/skottie_ios_app/SkottieViewController.h"
#import <UIKit/UIKit.h>
@interface SkiaContext : NSObject
- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame;
- (SkiaViewController*) getViewController:(UIView*)view;
@end
SkiaContext* MakeSkiaMetalContext();
SkiaContext* MakeSkiaGLContext();
SkiaContext* MakeSkiaUIContext();
#endif // SkiaContext_DEFINED

View File

@ -0,0 +1,9 @@
// Copyright 2020 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include "tools/skottie_ios_app/SkiaContext.h"
@implementation SkiaContext
- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame { return nil; }
- (SkiaViewController*) getViewController:(UIView*)view { return nil; }
@end

View File

@ -0,0 +1,146 @@
// Copyright 2020 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include "tools/skottie_ios_app/SkiaContext.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTime.h"
#include "include/gpu/GrBackendSurface.h"
#include "include/gpu/GrContext.h"
#include "include/gpu/gl/GrGLInterface.h"
#include "include/gpu/gl/GrGLTypes.h"
#import <GLKit/GLKit.h>
#import <UIKit/UIKit.h>
#import <OpenGLES/ES3/gl.h>
#include <CoreFoundation/CoreFoundation.h>
static void configure_glkview_for_skia(GLKView* view) {
[view setDrawableColorFormat:GLKViewDrawableColorFormatRGBA8888];
[view setDrawableDepthFormat:GLKViewDrawableDepthFormat24];
[view setDrawableStencilFormat:GLKViewDrawableStencilFormat8];
}
static sk_sp<SkSurface> make_gl_surface(GrContext* grContext, int width, int height) {
static constexpr int kStencilBits = 8;
static constexpr int kSampleCount = 1;
static const SkSurfaceProps surfaceProps = SkSurfaceProps::kLegacyFontHost_InitType;
if (!grContext || width <= 0 || height <= 0) {
return nullptr;
}
GLint fboid = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboid);
return SkSurface::MakeFromBackendRenderTarget(
grContext,
GrBackendRenderTarget(width,
height,
kSampleCount,
kStencilBits,
GrGLFramebufferInfo{(GrGLuint)fboid, GL_RGBA8}),
kBottomLeft_GrSurfaceOrigin,
kRGBA_8888_SkColorType,
nullptr,
&surfaceProps);
}
// A UIView that uses a GL-backed SkSurface to draw.
@interface SkiaGLView : GLKView
@property (strong) SkiaViewController* controller;
// Override of the UIView interface.
- (void)drawRect:(CGRect)rect;
// Required initializer.
- (instancetype)initWithFrame:(CGRect)frame
withEAGLContext:(EAGLContext*)eaglContext
withGrContext:(GrContext*)grContext;
@end
@implementation SkiaGLView {
GrContext* fGrContext;
}
- (instancetype)initWithFrame:(CGRect)frame
withEAGLContext:(EAGLContext*)eaglContext
withGrContext:(GrContext*)grContext {
self = [super initWithFrame:frame context:eaglContext];
fGrContext = grContext;
configure_glkview_for_skia(self);
return self;
}
- (void)drawRect:(CGRect)rect {
SkiaViewController* viewController = [self controller];
static constexpr double kFrameRate = 1.0 / 30.0;
double next = [viewController isPaused] ? 0 : kFrameRate + SkTime::GetNSecs() * 1e-9;
[super drawRect:rect];
int width = (int)[self drawableWidth],
height = (int)[self drawableHeight];
if (!(fGrContext)) {
NSLog(@"Error: grContext missing.\n");
return;
}
if (sk_sp<SkSurface> surface = make_gl_surface(fGrContext, width, height)) {
[viewController draw:rect
toCanvas:(surface->getCanvas())
atSize:CGSize{(CGFloat)width, (CGFloat)height}];
surface->flush();
}
if (next) {
[NSTimer scheduledTimerWithTimeInterval:std::max(0.0, next - SkTime::GetNSecs() * 1e-9)
target:self
selector:@selector(setNeedsDisplay)
userInfo:nil
repeats:NO];
}
}
@end
@interface SkiaGLContext : SkiaContext
@property (strong) EAGLContext* eaglContext;
- (instancetype) init;
- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame;
- (SkiaViewController*) getViewController:(UIView*)view;
@end
@implementation SkiaGLContext {
sk_sp<GrContext> fGrContext;
}
- (instancetype) init {
self = [super init];
[self setEaglContext:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]];
if (![self eaglContext]) {
NSLog(@"Falling back to GLES2.\n");
[self setEaglContext:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]];
}
if (![self eaglContext]) {
NSLog(@"[[EAGLContext alloc] initWithAPI:...] failed");
return nil;
}
EAGLContext* oldContext = [EAGLContext currentContext];
[EAGLContext setCurrentContext:[self eaglContext]];
fGrContext = GrContext::MakeGL(nullptr, GrContextOptions());
[EAGLContext setCurrentContext:oldContext];
if (!fGrContext) {
NSLog(@"GrContext::MakeGL failed");
return nil;
}
return self;
}
- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame {
SkiaGLView* skiaView = [[SkiaGLView alloc] initWithFrame:frame
withEAGLContext:[self eaglContext]
withGrContext:fGrContext.get()];
[skiaView setController:vc];
return skiaView;
}
- (SkiaViewController*) getViewController:(UIView*)view {
return [view isKindOfClass:[SkiaGLView class]] ? [view controller] : nil;
}
@end
SkiaContext* MakeSkiaGLContext() { return [[SkiaGLContext alloc] init]; }

View File

@ -0,0 +1,117 @@
// Copyright 2020 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include "tools/skottie_ios_app/SkiaContext.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrContext.h"
#include "tools/skottie_ios_app/SkMetalViewBridge.h"
#import <Metal/Metal.h>
#import <MetalKit/MetalKit.h>
#import <UIKit/UIKit.h>
// A UIView that uses a Metal-backed SkSurface to draw.
@interface SkiaMtkView : MTKView
@property (strong) SkiaViewController* controller;
// Override of the MTKView interface. Uses Skia+Metal to draw.
- (void)drawRect:(CGRect)rect;
// Required initializer.
- (instancetype)initWithFrame:(CGRect)frameRect
device:(id<MTLDevice>)device
queue:(id<MTLCommandQueue>)queue
grDevice:(GrContext*)grContext;
@end
@implementation SkiaMtkView {
id<MTLCommandQueue> fQueue;
GrContext* fGrContext;
}
- (instancetype)initWithFrame:(CGRect)frameRect
device:(id<MTLDevice>)mtlDevice
queue:(id<MTLCommandQueue>)queue
grDevice:(GrContext*)grContext {
self = [super initWithFrame:frameRect device:mtlDevice];
fQueue = queue;
fGrContext = grContext;
SkMtkViewConfigForSkia(self);
return self;
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
// TODO(halcanary): Use the rect and the InvalidationController to speed up rendering.
SkiaViewController* viewController = [self controller];
if (!viewController || ![[self currentDrawable] texture] || !fGrContext) {
return;
}
CGSize size = [self drawableSize];
sk_sp<SkSurface> surface = SkMtkViewToSurface(self, fGrContext);
if (!surface) {
NSLog(@"error: no sksurface");
return;
}
[viewController draw:rect toCanvas:surface->getCanvas() atSize:size];
surface->flush();
surface = nullptr;
id<MTLCommandBuffer> commandBuffer = [fQueue commandBuffer];
[commandBuffer presentDrawable:[self currentDrawable]];
[commandBuffer commit];
bool paused = [viewController isPaused];
[self setEnableSetNeedsDisplay:paused];
[self setPaused:paused];
}
@end
@interface SkiaMetalContext : SkiaContext
@property (strong) id<MTLDevice> metalDevice;
@property (strong) id<MTLCommandQueue> metalQueue;
- (instancetype) init;
- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame;
- (SkiaViewController*) getViewController:(UIView*)view;
@end
@implementation SkiaMetalContext {
sk_sp<GrContext> fGrContext;
}
- (instancetype) init {
self = [super init];
[self setMetalDevice:MTLCreateSystemDefaultDevice()];
if(![self metalDevice]) {
NSLog(@"Metal is not supported on this device");
return nil;
}
[self setMetalQueue:[[self metalDevice] newCommandQueue]];
fGrContext = GrContext::MakeMetal((__bridge void*)[self metalDevice],
(__bridge void*)[self metalQueue],
GrContextOptions());
if (!fGrContext) {
NSLog(@"GrContext::MakeMetal failed");
return nil;
}
return self;
}
- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame {
SkiaMtkView* skiaView = [[SkiaMtkView alloc] initWithFrame:frame
device:[self metalDevice]
queue:[self metalQueue]
grDevice:fGrContext.get()];
[skiaView setPreferredFramesPerSecond:30];
[skiaView setController:vc];
return skiaView;
}
- (SkiaViewController*) getViewController:(UIView*)view {
return [view isKindOfClass:[SkiaMtkView class]] ? [view controller] : nil;
}
@end
SkiaContext* MakeSkiaMetalContext() { return [[SkiaMetalContext alloc] init]; }

View File

@ -0,0 +1,66 @@
// Copyright 2020 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include "tools/skottie_ios_app/SkiaContext.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkTime.h"
#include "include/utils/mac/SkCGUtils.h"
#import <UIKit/UIKit.h>
// A UIView that uses a CPU-backed SkSurface to draw.
@interface SkiaUIView : UIView
@property (strong) SkiaViewController* controller;
// Override of the UIView interface.
- (void)drawRect:(CGRect)rect;
@end
@implementation SkiaUIView {
SkBitmap fBackBuffer;
}
- (void)drawRect:(CGRect)rect {
SkiaViewController* viewController = [self controller];
static constexpr double kFrameRate = 1.0 / 30.0;
double next = [viewController isPaused] ? 0 : kFrameRate + SkTime::GetNSecs() * 1e-9;
[super drawRect:rect];
CGSize size = [self bounds].size;
SkISize iSize = {(int)size.width, (int)size.height};
if (fBackBuffer.drawsNothing() || iSize != fBackBuffer.dimensions()) {
fBackBuffer.allocN32Pixels(iSize.fWidth, iSize.fHeight);
}
fBackBuffer.eraseColor(SK_ColorTRANSPARENT);
{
SkCanvas canvas(fBackBuffer);
[viewController draw:rect toCanvas:&canvas atSize:size];
}
SkCGDrawBitmap(UIGraphicsGetCurrentContext(), fBackBuffer, 0, 0);
if (next) {
[NSTimer scheduledTimerWithTimeInterval:std::max(0.0, next - SkTime::GetNSecs() * 1e-9)
target:self
selector:@selector(setNeedsDisplay)
userInfo:nil
repeats:NO];
}
}
@end
@interface SkiaUIContext : SkiaContext
- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame;
- (SkiaViewController*) getViewController:(UIView*)view;
@end
@implementation SkiaUIContext
- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame {
SkiaUIView* skiaView = [[SkiaUIView alloc] initWithFrame:frame];
[skiaView setController:vc];
return skiaView;
}
- (SkiaViewController*) getViewController:(UIView*)view {
return [view isKindOfClass:[SkiaUIView class]] ? [view controller] : nil;
}
@end
SkiaContext* MakeSkiaUIContext() { return [[SkiaUIContext alloc] init]; }

View File

@ -0,0 +1,21 @@
// 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 SkiaViewController_DEFINED
#define SkiaViewController_DEFINED
class SkCanvas;
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
// An interface that draws to a Skia canvas.
@interface SkiaViewController : NSObject
- (void)draw:(CGRect)rect toCanvas:(SkCanvas*)canvas atSize:(CGSize)size;
// Return the current paused state. Implementations should override.
- (bool)isPaused;
// Change the paused state. Implementations should override.
- (void)togglePaused;
@end
#endif // SkiaViewController_DEFINED

View File

@ -0,0 +1,16 @@
// 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 "tools/skottie_ios_app/SkiaViewController.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
@implementation SkiaViewController {}
- (bool)isPaused { return false; }
- (void)togglePaused {}
- (void)draw:(CGRect)rect toCanvas:(SkCanvas*)canvas atSize:(CGSize)size {
// Fill with pink.
canvas->clear(SkColorSetARGB(255, 255, 192, 203));
}
@end

View File

@ -1,49 +0,0 @@
// 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
// Must be set to a Metal-backed GrContext in order to draw.
// e.g.: use SkMetalDeviceToGrContext().
@property (assign) GrContext* grContext; // non-owning pointer.
// Must be set to a valid MTLCommandQueue. Will be used to present.
@property (assign) id<MTLCommandQueue> queue; // non-owning pointer.
// When set, pauses at end of loop.
- (void)setStopAtEnd:(BOOL)stop;
// Override of the MTKView interface. Uses Skia+Skottie+Metal to draw.
- (void)drawRect:(CGRect)rect;
// Load an animation from a Lottie JSON file. Returns Yes on success.
- (BOOL)loadAnimation:(NSData*)d;
// Jump to the specified location in the animation.
- (void)seek:(float)seconds;
// Toggle paused mode. Return paused state.
- (BOOL)togglePaused;
// Return the current paused state.
- (BOOL)isPaused;
// Return the default size of the Lottie animation.
- (CGSize)size;
// Return the length of the animation loop.
- (float)animationDurationSeconds;
// Return the current position in the animation in seconds (between zero and
// animationDurationSeconds).
- (float)currentTime;
@end
#endif // SkottieMtkView_DEFINED

View File

@ -1,75 +0,0 @@
//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 "tools/skottie_ios_app/SkottieMtkView.h"
#include "tools/skottie_ios_app/SkAnimationDraw.h"
#include "tools/skottie_ios_app/SkTimeKeeper.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 "tools/skottie_ios_app/SkMetalViewBridge.h"
@implementation SkottieMtkView {
SkAnimationDraw fDraw;
SkTimeKeeper fClock;
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
// TODO(halcanary): Use the rect and the InvalidationController to speed up rendering.
if (!fDraw || ![[self currentDrawable] texture] || ![self grContext]) {
return;
}
CGSize size = [self drawableSize];
if (!fClock.paused()) {
fDraw.seek(fClock.currentTime());
}
sk_sp<SkSurface> surface = SkMtkViewToSurface(self, [self grContext]);
if (!surface) {
NSLog(@"error: no sksurface");
return;
}
fDraw.draw(SkSize{(float)size.width, (float)size.height}, surface->getCanvas());
surface->flush();
surface = nullptr;
id<MTLCommandBuffer> commandBuffer = [[self queue] commandBuffer];
[commandBuffer presentDrawable:[self currentDrawable]];
[commandBuffer commit];
}
- (BOOL)loadAnimation:(NSData*) data {
fDraw.load((const void*)[data bytes], (size_t)[data length]);
fClock.setDuration(fDraw.duration());
return (BOOL)fDraw;
}
- (void)setStopAtEnd:stop{ fClock.setStopAtEnd(stop); }
- (float)animationDurationSeconds { return fClock.duration(); }
- (float)currentTime { return fDraw ? fClock.currentTime() : 0; }
- (void)seek:(float)seconds {
if (fDraw) {
fClock.seek(seconds);
[self setNeedsDisplay];
}
}
- (CGSize)size { return {(CGFloat)fDraw.size().width(), (CGFloat)fDraw.size().height()}; }
- (BOOL)togglePaused {
fClock.togglePaused();
[self setNeedsDisplay];
return fClock.paused();
}
- (BOOL)isPaused { return fClock.paused(); }
@end

View File

@ -1,82 +0,0 @@
// 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 "tools/skottie_ios_app/SkottieUIView.h"
#include "tools/skottie_ios_app/SkAnimationDraw.h"
#include "tools/skottie_ios_app/SkTimeKeeper.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPaint.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTime.h"
#include "include/utils/mac/SkCGUtils.h"
#include "modules/skottie/include/Skottie.h"
@implementation SkottieUIView {
SkAnimationDraw fDraw;
SkTimeKeeper fClock;
SkBitmap fBackBuffer;
}
- (void)drawRect:(CGRect)rect {
double next = fClock.paused() ? 0 : (1.0 / 30.0) + SkTime::GetNSecs() * 1e-9;
[super drawRect:rect];
// TODO(halcanary): Use the rect and the InvalidationController to speed up rendering.
if (rect.size.width > 0 && rect.size.height > 0 && fDraw) {
CGSize size = [self bounds].size;
SkISize iSize = {(int)size.width, (int)size.height};
if (fBackBuffer.drawsNothing() || iSize != fBackBuffer.dimensions()) {
fBackBuffer.allocN32Pixels(iSize.fWidth, iSize.fHeight);
}
fBackBuffer.eraseColor(SK_ColorTRANSPARENT);
{
SkCanvas canvas(fBackBuffer);
if (!fClock.paused()) {
fDraw.seek(fClock.currentTime());
}
fDraw.draw({(float)size.width, (float)size.height}, &canvas);
}
SkCGDrawBitmap(UIGraphicsGetCurrentContext(), fBackBuffer, 0, 0);
}
if (next) {
[NSTimer scheduledTimerWithTimeInterval:std::max(0.0, next - SkTime::GetNSecs() * 1e-9)
target:self
selector:@selector(setNeedsDisplay)
userInfo:nil
repeats:NO];
}
}
- (BOOL)loadAnimation:(NSData*) data {
fDraw.load((const void*)[data bytes], (size_t)[data length]);
fClock.setDuration(fDraw.duration());
[self setNeedsDisplay];
return (bool)fDraw;
}
- (void)setStopAtEnd:(BOOL)stop{ fClock.setStopAtEnd(stop); }
- (float)animationDurationSeconds { return fClock.duration(); }
- (float)currentTime { return fDraw ? fClock.currentTime() : 0; }
- (void)seek:(float)seconds {
if (fDraw) {
fClock.seek(seconds);
[self setNeedsDisplay];
}
}
- (CGSize)size { return {(CGFloat)fDraw.size().width(), (CGFloat)fDraw.size().height()}; }
- (BOOL)togglePaused {
fClock.togglePaused();
[self setNeedsDisplay];
return fClock.paused();
}
- (BOOL)isPaused { return fClock.paused(); }
@end

View File

@ -1,31 +1,30 @@
// 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 SkottieUIView_DEFINED
#define SkottieUIView_DEFINED
#ifndef SkottieViewController_DEFINED
#define SkottieViewController_DEFINED
#include "tools/skottie_ios_app/SkiaViewController.h"
#import <UIKit/UIKit.h>
class GrContext;
// An abstraction of the Skottie module into Obective-C.
@interface SkottieViewController : SkiaViewController
- (void)draw:(CGRect)rect toCanvas:(SkCanvas*)canvas atSize:(CGSize)size;
@interface SkottieUIView : UIView
// Return the current paused state.
- (bool)isPaused;
// When set, pauses at end of loop.
- (void)setStopAtEnd:(BOOL)stop;
// Override of the UIView interface. Uses Skia+Skottie+Metal to draw.
- (void)drawRect:(CGRect)rect;
- (void)setStopAtEnd:(bool)stop;
// Load an animation from a Lottie JSON file. Returns Yes on success.
- (BOOL)loadAnimation:(NSData*)d;
- (bool)loadAnimation:(NSData*)d;
// Jump to the specified location in the animation.
- (void)seek:(float)seconds;
// Toggle paused mode. Return paused state.
- (BOOL)togglePaused;
// Return the current paused state.
- (BOOL)isPaused;
- (bool)togglePaused;
// Return the default size of the Lottie animation.
- (CGSize)size;
@ -38,4 +37,4 @@ class GrContext;
- (float)currentTime;
@end
#endif // SkottieUIView_DEFINED
#endif // SkottieViewController_DEFINED

View File

@ -0,0 +1,170 @@
// 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 "tools/skottie_ios_app/SkottieViewController.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 <cmath>
////////////////////////////////////////////////////////////////////////////////
class SkAnimationDraw {
public:
SkAnimationDraw() = default;
~SkAnimationDraw() = default;
explicit operator bool() const { return fAnimation != nullptr; }
void draw(SkSize size, SkCanvas* canvas) {
if (size.width() != fSize.width() || size.height() != fSize.height()) {
// Cache the current matrix; change only if size changes.
if (fAnimationSize.width() > 0 && fAnimationSize.height() > 0) {
float scale = std::min(size.width() / fAnimationSize.width(),
size.height() / fAnimationSize.height());
fMatrix.setScaleTranslate(
scale, scale,
(size.width() - fAnimationSize.width() * scale) * 0.5f,
(size.height() - fAnimationSize.height() * scale) * 0.5f);
} else {
fMatrix = SkMatrix();
}
fSize = size;
}
canvas->concat(fMatrix);
SkRect rect = {0, 0, fAnimationSize.width(), fAnimationSize.height()};
canvas->drawRect(rect, SkPaint(SkColors::kWhite));
fAnimation->render(canvas);
}
void load(const void* data, size_t length) {
skottie::Animation::Builder builder;
fAnimation = builder.make((const char*)data, (size_t)length);
fSize = {0, 0};
fAnimationSize = fAnimation ? fAnimation->size() : SkSize{0, 0};
}
void seek(double time) { if (fAnimation) { fAnimation->seekFrameTime(time, nullptr); } }
float duration() { return fAnimation ? fAnimation->duration() : 0; }
SkSize size() { return fAnimationSize; }
private:
sk_sp<skottie::Animation> fAnimation; // owner
SkSize fSize;
SkSize fAnimationSize;
SkMatrix fMatrix;
SkAnimationDraw(const SkAnimationDraw&) = delete;
SkAnimationDraw& operator=(const SkAnimationDraw&) = delete;
};
////////////////////////////////////////////////////////////////////////////////
class SkTimeKeeper {
private:
double fStartTime = 0; // used when running
float fAnimationMoment = 0; // when paused.
float fDuration = 0;
bool fPaused = false;
bool fStopAtEnd = false;
public:
void setStopAtEnd(bool s) { fStopAtEnd = s; }
float currentTime() {
if (0 == fDuration) {
return 0;
}
if (fPaused) {
return fAnimationMoment;
}
double time = 1e-9 * (SkTime::GetNSecs() - fStartTime);
if (fStopAtEnd && time >= fDuration) {
fPaused = true;
fAnimationMoment = fDuration;
return fAnimationMoment;
}
return std::fmod(time, fDuration);
}
void setDuration(float d) {
fDuration = d;
fStartTime = SkTime::GetNSecs();
fAnimationMoment = 0;
}
bool paused() const { return fPaused; }
float duration() const { return fDuration; }
void seek(float seconds) {
if (fPaused) {
fAnimationMoment = std::fmod(seconds, fDuration);
} else {
fStartTime = SkTime::GetNSecs() - 1e9 * seconds;
}
}
void togglePaused() {
if (fPaused) {
double offset = (fAnimationMoment >= fDuration) ? 0 : -1e9 * fAnimationMoment;
fStartTime = SkTime::GetNSecs() + offset;
fPaused = false;
} else {
fAnimationMoment = this->currentTime();
fPaused = true;
}
}
};
////////////////////////////////////////////////////////////////////////////////
@implementation SkottieViewController {
SkAnimationDraw fDraw;
SkTimeKeeper fClock;
}
- (bool)loadAnimation:(NSData*) data {
fDraw.load((const void*)[data bytes], (size_t)[data length]);
fClock.setDuration(fDraw.duration());
return (bool)fDraw;
}
- (void)setStopAtEnd:(bool)stop { fClock.setStopAtEnd(stop); }
- (float)animationDurationSeconds { return fClock.duration(); }
- (float)currentTime { return fDraw ? fClock.currentTime() : 0; }
- (void)seek:(float)seconds {
if (fDraw) {
fClock.seek(seconds);
}
}
- (CGSize)size { return {(CGFloat)fDraw.size().width(), (CGFloat)fDraw.size().height()}; }
- (bool)togglePaused {
fClock.togglePaused();
return fClock.paused();
}
- (bool)isPaused { return fClock.paused(); }
- (void)draw:(CGRect)rect toCanvas:(SkCanvas*)canvas atSize:(CGSize)size {
// TODO(halcanary): Use the rect and the InvalidationController to speed up rendering.
if (rect.size.width > 0 && rect.size.height > 0 && fDraw && canvas) {
if (!fClock.paused()) {
fDraw.seek(fClock.currentTime());
}
fDraw.draw(SkSize{(float)size.width, (float)size.height}, canvas);
}
}
@end

View File

@ -1,29 +1,43 @@
// 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 "tools/skottie_ios_app/SkiaContext.h"
#include "tools/skottie_ios_app/SkottieViewController.h"
#include "include/core/SkTypes.h"
#ifdef SK_METAL
#include "tools/skottie_ios_app/SkMetalViewBridge.h"
#include "tools/skottie_ios_app/SkottieMtkView.h"
#import <Metal/Metal.h>
#import <MetalKit/MetalKit.h>
#else
#include "tools/skottie_ios_app/SkottieUIView.h"
#endif
#import <UIKit/UIKit.h>
#include <cstdlib>
@interface AppViewController : UIViewController
@property (strong) SkiaContext* skiaContext;
@property (strong) UIStackView* stackView;
@end
@implementation AppViewController
- (void)loadView {
[self setView:[[UIView alloc] init]];
}
- (void)viewDidLoad {
[super viewDidLoad];
if (![self skiaContext]) {
#if (SK_SUPPORT_GPU && defined(SK_METAL) && !defined(SK_BUILD_FOR_GOOGLE3))
[self setSkiaContext:MakeSkiaMetalContext()];
#elif (SK_SUPPORT_GPU && defined(SK_GL) && !defined(SK_BUILD_FOR_GOOGLE3))
[self setSkiaContext:MakeSkiaGLContext()];
#else
[self setSkiaContext:MakeSkiaUIContext()];
#endif
if (![self skiaContext]) {
NSLog(@"abort: failed to make skia context.");
std::abort();
}
}
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
#ifdef SK_METAL
static UIStackView* make_skottie_stack(CGFloat width,
id<MTLDevice> metalDevice,
id<MTLCommandQueue> metalQueue,
GrContext* grContext) {
#else
static UIStackView* make_skottie_stack(CGFloat width) {
#endif
UIStackView* stack = [[UIStackView alloc] init];
[stack setAxis:UILayoutConstraintAxisVertical];
[stack setDistribution:UIStackViewDistributionEqualSpacing];
@ -40,83 +54,40 @@ static UIStackView* make_skottie_stack(CGFloat width) {
NSLog(@"'%@' not found", path);
continue;
}
#ifdef SK_METAL
SkottieMtkView* skottieView = [[SkottieMtkView alloc] init];
#else
SkottieUIView* skottieView = [[SkottieUIView alloc] init];
#endif
if (![skottieView loadAnimation:content]) {
SkottieViewController* controller = [[SkottieViewController alloc] init];
if (![controller loadAnimation:content]) {
continue;
}
#ifdef SK_METAL
[skottieView setDevice:metalDevice];
[skottieView setQueue:metalQueue];
[skottieView setGrContext:grContext];
SkMtkViewConfigForSkia(skottieView);
[skottieView setPreferredFramesPerSecond:30];
#endif
CGSize animSize = [skottieView size];
CGFloat height = animSize.width ? (width * animSize.height / animSize.width) : 0;
[skottieView setFrame:{{0, 0}, {width, height}}];
[[[skottieView heightAnchor] constraintEqualToConstant:height] setActive:true];
[[[skottieView widthAnchor] constraintEqualToConstant:width] setActive:true];
[stack addArrangedSubview:skottieView];
CGSize animSize = [controller size];
CGFloat height = animSize.width ? (screenWidth * animSize.height / animSize.width) : 0;
CGRect frame = {{0, 0}, {screenWidth, height}};
UIView* skiaView = [[self skiaContext] makeViewWithController:controller withFrame:frame];
[[[skiaView heightAnchor] constraintEqualToConstant:height] setActive:true];
[[[skiaView widthAnchor] constraintEqualToConstant:screenWidth] setActive:true];
[skiaView setNeedsDisplay];
[stack addArrangedSubview:skiaView];
totalHeight += height + kSpacing;
}
[stack setFrame:{{0, 0}, {width, totalHeight}}];
return stack;
}
@interface AppViewController : UIViewController
#ifdef SK_METAL
@property (strong) id<MTLDevice> metalDevice;
@property (strong) id<MTLCommandQueue> metalQueue;
#endif
@property (strong) UIStackView* stackView;
@end
@implementation AppViewController {
#ifdef SK_METAL
GrContextHolder fGrContext;
#endif
}
- (void)loadView {
[self setView:[[UIView alloc] init]];
}
- (void)viewDidLoad {
#ifdef SK_METAL
[super viewDidLoad];
if (!fGrContext) {
[self setMetalDevice:MTLCreateSystemDefaultDevice()];
if(![self metalDevice]) {
NSLog(@"Metal is not supported on this device");
return;
}
[self setMetalQueue:[[self metalDevice] newCommandQueue]];
fGrContext = SkMetalDeviceToGrContext([self metalDevice], [self metalQueue]);
}
[self setStackView:make_skottie_stack([[UIScreen mainScreen] bounds].size.width,
[self metalDevice], [self metalQueue], fGrContext.get())];
#else
[self setStackView:make_skottie_stack([[UIScreen mainScreen] bounds].size.width)];
#endif
[stack setFrame:{{0, 0}, {screenWidth, totalHeight}}];
[stack setNeedsDisplay];
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 setContentSize:[stack frame].size];
[scrollView addSubview:stack];
[scrollView setBackgroundColor:[UIColor blackColor]];
[scrollView setNeedsDisplay];
[self setStackView:stack];
UIView* mainView = [self view];
[mainView setBounds:{{0, 0}, mainScreenSize}];
[mainView setBackgroundColor:[UIColor whiteColor]];
[mainView addSubview:scrollView];
[mainView setNeedsDisplay];
UITapGestureRecognizer* tapGestureRecognizer = [[UITapGestureRecognizer alloc] init];
[tapGestureRecognizer addTarget:self action:@selector(handleTap:)];
@ -129,29 +100,17 @@ static UIStackView* make_skottie_stack(CGFloat width) {
}
NSArray<UIView*>* subviews = [[self stackView] subviews];
for (NSUInteger i = 0; i < [subviews count]; ++i) {
UIView* subview = [subviews objectAtIndex:i];
#ifdef SK_METAL
if (![subview isKindOfClass:[SkottieMtkView class]]) {
continue;
UIView* uIView = [subviews objectAtIndex:i];
if (SkiaViewController* controller = [[self skiaContext] getViewController:uIView]) {
[controller togglePaused];
[uIView setNeedsDisplay];
}
SkottieMtkView* skottieView = (SkottieMtkView*)subview;
#else
if (![subview isKindOfClass:[SkottieUIView class]]) {
continue;
}
SkottieUIView* skottieView = (SkottieUIView*)subview;
#endif
BOOL paused = [skottieView togglePaused];
#ifdef SK_METAL
[skottieView setEnableSetNeedsDisplay:paused];
[skottieView setPaused:paused];
#endif
}
}
@end
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow* window;
@property (strong, nonatomic) UIWindow* window;
@end
@implementation AppDelegate