blob: a3ab3dcbfc747a797f31df5afd6e8fdbc1a72a1e [file] [log] [blame]
// Copyright 2019 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
// This is an example of a minimal iOS application that uses Skia to draw to
// a Metal drawable.
// Much of this code is copied from the default application created by XCode.
#include "include/core/SkCanvas.h"
#include "include/core/SkPaint.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTime.h"
#include "include/effects/SkGradientShader.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>
#import <UIKit/UIKit.h>
////////////////////////////////////////////////////////////////////////////////
static sk_sp<SkSurface> to_surface(MTKView* view, GrContext* grContext) {
if (!grContext || view == nil) {
return nullptr;
}
id<CAMetalDrawable> drawable = view.currentDrawable;
CGSize size = view.drawableSize;
int sampleCount = (int)view.sampleCount;
int width = (int)size.width;
int height = (int)size.height;
GrMtlTextureInfo fbInfo;
fbInfo.fTexture.retain((__bridge const void*)(drawable.texture));
sk_sp<SkColorSpace> colorSpace = nullptr;
const SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
if (sampleCount == 1) {
GrBackendRenderTarget backendRT(width, height, 1, fbInfo);
return SkSurface::MakeFromBackendRenderTarget(grContext, backendRT,
kTopLeft_GrSurfaceOrigin,
kBGRA_8888_SkColorType,
colorSpace, &surfaceProps);
} else {
GrBackendTexture backendTexture(width, height, GrMipMapped::kNo, fbInfo);
return SkSurface::MakeFromBackendTexture(
grContext, backendTexture, kTopLeft_GrSurfaceOrigin, sampleCount,
kBGRA_8888_SkColorType, colorSpace, &surfaceProps);
}
}
static sk_sp<GrContext> to_context(MTKView* view) {
// Configure view:
view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
view.sampleCount = 1;
GrContextOptions defaultOpts; // set different options here.
id<MTLCommandQueue> commandQueue = [view.device newCommandQueue];
// Create long-lived GrContext:
return GrContext::MakeMetal((__bridge void*)view.device,
(__bridge void*)(commandQueue),
defaultOpts);
}
////////////////////////////////////////////////////////////////////////////////
@interface AppViewDelegate : NSObject <MTKViewDelegate>
-(nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)view;
@end
@implementation AppViewDelegate {
sk_sp<GrContext> fGrContext;
SkPaint fPaint;
}
-(nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)view {
self = [super init];
if (view.device == nil) {
view.device = MTLCreateSystemDefaultDevice();
}
fGrContext = to_context(view);
view.delegate = self;
return self;
}
- (void)drawInMTKView:(nonnull MTKView *)view {
if (!fGrContext || view == nil) {
NSLog(@"error: no context");
return;
}
if (!fPaint.getShader()) {
// Perform as much work as possible before creating surface.
SkColor4f colors[2] = {SkColors::kGreen, SkColors::kMagenta};
SkPoint points[2] = {{0, -1024}, {0, 1024}};
fPaint.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, nullptr, 2,
SkTileMode::kClamp, 0, nullptr));
}
float time = (float)(180 * 1e-9 * SkTime::GetNSecs());
// Create surface:
int width = (int)view.drawableSize.width;
int height = (int)view.drawableSize.height;
sk_sp<SkSurface> surface = to_surface(view, fGrContext.get());
if (!surface) {
NSLog(@"error: no sksurface");
return;
}
SkCanvas* c = surface->getCanvas();
c->translate(width * 0.5f, height * 0.5f);
c->rotate(time);
c->drawPaint(fPaint);
// Must flush *and* present for this to work!
surface->flush();
[view.currentDrawable present];
}
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
// change anything on size change?
}
@end
////////////////////////////////////////////////////////////////////////////////
@interface AppViewController : UIViewController
@end
@implementation AppViewController
- (void)loadView {
self.view = [[MTKView alloc] initWithFrame:[[UIScreen mainScreen] bounds] device:nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
MTKView *mtkView = (MTKView *)self.view;
mtkView.device = MTLCreateSystemDefaultDevice();
mtkView.backgroundColor = UIColor.blackColor;
if(!mtkView.device)
{
NSLog(@"Metal is not supported on this device");
self.view = [[UIView alloc] initWithFrame:self.view.frame];
return;
}
AppViewDelegate * viewDelegate = [[AppViewDelegate alloc] initWithMetalKitView:mtkView];
[viewDelegate mtkView:mtkView drawableSizeWillChange:mtkView.bounds.size];
mtkView.delegate = viewDelegate;
}
@end
////////////////////////////////////////////////////////////////////////////////
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.frame = [UIScreen mainScreen].bounds;
self.window.rootViewController = [[AppViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive
// state. This can occur for certain types of temporary interruptions (such
// as an incoming phone call or SMS message) or when the user quits the
// application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate
// graphics rendering callbacks. Games should use this method to pause the
// game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate
// timers, and store enough application state information to restore your
// application to its current state in case it is terminated later.
// If your application supports background execution, this method is called
// instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active
// state; here you can undo many of the changes made on entering the
// background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the
// application was inactive. If the application was previously in the
// background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if
// appropriate. See also applicationDidEnterBackground:.
}
@end
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}