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:
parent
7543587ee2
commit
118df7cf62
@ -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",
|
||||
|
11
public.bzl
11
public.bzl
@ -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 = [
|
||||
|
@ -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"
|
||||
}
|
||||
|
16
tools/skottie_ios_app/GrContextHolder.h
Normal file
16
tools/skottie_ios_app/GrContextHolder.h
Normal 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
|
24
tools/skottie_ios_app/GrContextHolder.mm
Normal file
24
tools/skottie_ios_app/GrContextHolder.mm
Normal 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
|
@ -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.
|
||||
|
@ -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
|
@ -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*);
|
||||
|
@ -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,
|
||||
|
@ -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
|
20
tools/skottie_ios_app/SkiaContext.h
Normal file
20
tools/skottie_ios_app/SkiaContext.h
Normal 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
|
9
tools/skottie_ios_app/SkiaContext.mm
Normal file
9
tools/skottie_ios_app/SkiaContext.mm
Normal 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
|
146
tools/skottie_ios_app/SkiaGLContext.mm
Normal file
146
tools/skottie_ios_app/SkiaGLContext.mm
Normal 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]; }
|
117
tools/skottie_ios_app/SkiaMetalContext.mm
Normal file
117
tools/skottie_ios_app/SkiaMetalContext.mm
Normal 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]; }
|
66
tools/skottie_ios_app/SkiaUIContext.mm
Normal file
66
tools/skottie_ios_app/SkiaUIContext.mm
Normal 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]; }
|
21
tools/skottie_ios_app/SkiaViewController.h
Normal file
21
tools/skottie_ios_app/SkiaViewController.h
Normal 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
|
16
tools/skottie_ios_app/SkiaViewController.mm
Normal file
16
tools/skottie_ios_app/SkiaViewController.mm
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
170
tools/skottie_ios_app/SkottieViewController.mm
Normal file
170
tools/skottie_ios_app/SkottieViewController.mm
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user