56df7910b0
Motivation: make is easier to add multiple views sharing a GrContext. - Split configure_mtk_view() from to_context() - AppViewDelegate no longer owns GrContext - Split out config_paint() and draw_example() fns to delineate example code from useful code. - AppViewDelegate no longer responsible for initializing itself. - AppViewController owns MTLDevice and GrContext. No-Try: true Change-Id: I608da76c7818b262c0bfbe52dd431f3007e86e2d Reviewed-on: https://skia-review.googlesource.com/c/skia/+/240201 Reviewed-by: Jim Van Verth <jvanverth@google.com> Commit-Queue: Hal Canary <halcanary@google.com>
221 lines
8.0 KiB
Plaintext
221 lines
8.0 KiB
Plaintext
// 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]));
|
|
}
|
|
}
|