blob: 273a9498549eb930aac74f7dc79bcf2331547ae8 [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* mtkView, GrContext* grContext) {
if (!grContext || mtkView == nil) {
return nullptr;
}
id<CAMetalDrawable> drawable = mtkView.currentDrawable;
CGSize size = mtkView.drawableSize;
int sampleCount = (int)mtkView.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(id<MTLDevice> device, const GrContextOptions& opts) {
if (device == nil) {
return nullptr;
}
id<MTLCommandQueue> queue = [device newCommandQueue];
return GrContext::MakeMetal((__bridge void*)device, (__bridge void*)queue, opts);
}
static void configure_mtk_view(MTKView* mtkView) {
mtkView.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
mtkView.sampleCount = 1;
}
////////////////////////////////////////////////////////////////////////////////
static void config_paint(SkPaint* paint) {
if (!paint->getShader()) {
// Perform as much work as possible before creating surface.
SkColor4f colors[2] = {SkColors::kBlack, SkColors::kWhite};
SkPoint points[2] = {{0, -1024}, {0, 1024}};
paint->setShader(SkGradientShader::MakeLinear(points, colors, nullptr, nullptr, 2,
SkTileMode::kClamp, 0, nullptr));
}
}
static void draw_example(SkSurface* surface, const SkPaint& paint, double rotation) {
SkCanvas* canvas = surface->getCanvas();
canvas->translate(surface->width() * 0.5f, surface->height() * 0.5f);
canvas->rotate(rotation);
canvas->drawPaint(paint);
}
////////////////////////////////////////////////////////////////////////////////
@interface AppViewDelegate : NSObject <MTKViewDelegate> {
@public
GrContext* fGrContext;
SkPaint fPaint;
}
@end
@implementation AppViewDelegate
- (void)drawInMTKView:(nonnull MTKView *)view {
if (!fGrContext || view == nil) {
NSLog(@"error: no context");
return;
}
// Do as much as possible before calling to_surface()
config_paint(&fPaint);
float rotation = (float)(180 * 1e-9 * SkTime::GetNSecs());
// Create surface:
sk_sp<SkSurface> surface = to_surface(view, fGrContext);
if (!surface) {
NSLog(@"error: no sksurface");
return;
}
draw_example(surface.get(), fPaint, rotation);
// 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 {
id<MTLDevice> fMtlDevice;
sk_sp<GrContext> fGrContext;
}
@end
@implementation AppViewController
- (void)loadView {
self.view = [[MTKView alloc] initWithFrame:[[UIScreen mainScreen] bounds] device:nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
if (!fGrContext) {
fMtlDevice = MTLCreateSystemDefaultDevice();
GrContextOptions grContextOptions; // set different options here.
fGrContext = to_context(fMtlDevice, grContextOptions);
}
MTKView* mtkView = (MTKView*)self.view;
mtkView.device = fMtlDevice;
mtkView.backgroundColor = UIColor.blackColor;
configure_mtk_view(mtkView);
if(!self.view || !mtkView.device) {
NSLog(@"Metal is not supported on this device");
self.view = [[UIView alloc] initWithFrame:self.view.frame];
return;
}
AppViewDelegate* viewDelegate = [[AppViewDelegate alloc] init];
viewDelegate->fGrContext = fGrContext.get();
[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]));
}
}