cmd/ebitenmobile: Use Metal on iOS

The emulators still use OpenGL.

Fixes #737
This commit is contained in:
Hajime Hoshi 2019-09-03 04:08:58 +09:00
parent 17d7a33676
commit 3c976eae02
13 changed files with 293 additions and 113 deletions

View File

@ -133,21 +133,39 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
// +build ios
#import <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR
#define EBITEN_METAL 0
#else
#define EBITEN_METAL 1
#endif
#import <stdint.h>
#import <UIKit/UIKit.h>
#import <GLKit/GLkit.h>
#import "Ebitenmobileview.objc.h"
@interface {{.PrefixUpper}}EbitenViewController : UIViewController
@end
@implementation {{.PrefixUpper}}EbitenViewController {
UIView* metalView_;
GLKView* glkView_;
bool started_;
bool active_;
bool error_;
}
- (UIView*)metalView {
if (!metalView_) {
metalView_ = [[UIView alloc] init];
metalView_.multipleTouchEnabled = YES;
}
return metalView_;
}
- (GLKView*)glkView {
if (!glkView_) {
glkView_ = [[GLKView alloc] init];
@ -166,6 +184,10 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
started_ = true;
}
#if EBITEN_METAL
[self.view addSubview: self.metalView];
EbitenmobileviewSetUIView((uintptr_t)(self.metalView));
#else
self.glkView.delegate = (id<GLKViewDelegate>)(self);
[self.view addSubview: self.glkView];
@ -173,7 +195,8 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
[self glkView].context = context;
[EAGLContext setCurrentContext:context];
#endif
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
@ -186,8 +209,12 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
}
- (void)setViewRect:(long)x y:(long)y width:(long)width height:(long)height {
CGRect glkViewRect = CGRectMake(x, y, width, height);
[[self glkView] setFrame:glkViewRect];
CGRect viewRect = CGRectMake(x, y, width, height);
#if EBITEN_METAL
[[self metalView] setFrame:viewRect];
#else
[[self glkView] setFrame:viewRect];
#endif
}
- (void)didReceiveMemoryWarning {
@ -202,23 +229,31 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
return;
}
#if EBITEN_METAL
[self updateEbiten];
#else
[[self glkView] setNeedsDisplay];
#endif
}
}
- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {
@synchronized(self) {
if (error_) {
return;
}
NSError* err = nil;
EbitenmobileviewUpdate(&err);
if (err != nil) {
[self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)
withObject:err
waitUntilDone:NO];
error_ = true;
}
[self updateEbiten];
}
}
- (void)updateEbiten {
if (error_) {
return;
}
NSError* err = nil;
EbitenmobileviewUpdate(&err);
if (err != nil) {
[self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)
withObject:err
waitUntilDone:NO];
error_ = true;
}
}
@ -228,9 +263,15 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
- (void)updateTouches:(NSSet*)touches {
for (UITouch* touch in touches) {
#if EBITEN_METAL
if (touch.view != [self metalView]) {
continue;
}
#else
if (touch.view != [self glkView]) {
continue;
}
#endif
CGPoint location = [touch locationInView:touch.view];
EbitenmobileviewUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y);
}
@ -252,8 +293,7 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
[self updateTouches:touches];
}
- (void)suspendGame
{
- (void)suspendGame {
NSAssert(started_, @"suspendGame msut not be called before viewDidLoad is called");
@synchronized(self) {
@ -261,8 +301,7 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
}
}
- (void)resumeGame
{
- (void)resumeGame {
NSAssert(started_, @"resumeGame msut not be called before viewDidLoad is called");
@synchronized(self) {

File diff suppressed because one or more lines are too long

32
graphicsdriver_ios.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin,ios,arm darwin,ios,arm64
package ebiten
import (
"fmt"
"github.com/hajimehoshi/ebiten/internal/driver"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl"
)
func graphicsDriver() driver.Graphics {
if _, err := mtl.CreateSystemDefaultDevice(); err != nil {
panic(fmt.Sprintf("ebiten: mtl.CreateSystemDefaultDevice failed on iOS: %v", err))
}
return metal.Get()
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build android freebsd ios js linux windows
// +build android darwin,ios,386 darwin,ios,amd64 freebsd js linux windows
// As the Go playground tries to compile this with CGO_ENABLED=0 and GOOS=linux, check Cgo on build tags.

View File

@ -344,6 +344,11 @@ func (d *Driver) SetWindow(window uintptr) {
})
}
func (d *Driver) SetUIView(uiview uintptr) {
// TODO: Should this be called on the main thread?
d.view.setUIView(uiview)
}
func (d *Driver) SetVertices(vertices []float32, indices []uint16) {
d.t.Call(func() error {
if d.vb != (mtl.Buffer{}) {
@ -352,8 +357,8 @@ func (d *Driver) SetVertices(vertices []float32, indices []uint16) {
if d.ib != (mtl.Buffer{}) {
d.ib.Release()
}
d.vb = d.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&vertices[0]), unsafe.Sizeof(vertices[0])*uintptr(len(vertices)), mtl.ResourceStorageModeManaged)
d.ib = d.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&indices[0]), unsafe.Sizeof(indices[0])*uintptr(len(indices)), mtl.ResourceStorageModeManaged)
d.vb = d.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&vertices[0]), unsafe.Sizeof(vertices[0])*uintptr(len(vertices)), resourceStorageMode)
d.ib = d.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&indices[0]), unsafe.Sizeof(indices[0])*uintptr(len(indices)), resourceStorageMode)
return nil
})
}
@ -404,11 +409,8 @@ func (d *Driver) NewImage(width, height int) (driver.Image, error) {
PixelFormat: mtl.PixelFormatRGBA8UNorm,
Width: graphics.InternalImageSize(width),
Height: graphics.InternalImageSize(height),
StorageMode: mtl.StorageModeManaged,
// MTLTextureUsageRenderTarget might cause a problematic render result. Not sure the reason.
// Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget
Usage: mtl.TextureUsageShaderRead,
StorageMode: storageMode,
Usage: textureUsage,
}
var t mtl.Texture
d.t.Call(func() error {

View File

@ -0,0 +1,81 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
package metal
import (
"sync"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ca"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl"
)
type view struct {
window uintptr
uiview uintptr
device mtl.Device
ml ca.MetalLayer
once sync.Once
}
func (v *view) setDrawableSize(width, height int) {
v.ml.SetDrawableSize(width, height)
}
func (v *view) getMTLDevice() mtl.Device {
return v.device
}
func (v *view) setDisplaySyncEnabled(enabled bool) {
// TODO: Now SetVsyncEnabled is called only from the main thread, and d.t.Run is not available since
// recursive function call via Run is forbidden.
// Fix this to use d.t.Run to avoid confusion.
v.ml.SetDisplaySyncEnabled(enabled)
}
func (v *view) colorPixelFormat() mtl.PixelFormat {
return v.ml.PixelFormat()
}
func (v *view) reset() error {
var err error
v.device, err = mtl.CreateSystemDefaultDevice()
if err != nil {
return err
}
v.ml = ca.MakeMetalLayer()
v.ml.SetDevice(v.device)
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat
//
// The pixel format for a Metal layer must be MTLPixelFormatBGRA8Unorm,
// MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or
// MTLPixelFormatBGRA10_XR_sRGB.
v.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
v.ml.SetMaximumDrawableCount(3)
return nil
}
func (v *view) drawable() ca.MetalDrawable {
d, err := v.ml.NextDrawable()
if err != nil {
// Drawable is nil. This can happen at the initial state. Let's wait and see.
return ca.MetalDrawable{}
}
return d
}

View File

@ -17,46 +17,49 @@
package metal
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework UIKit
//
// #import <UIKit/UIKit.h>
//
// static void addSublayer(void* view, void* sublayer) {
// CALayer* layer = ((UIView*)view).layer;
// [layer addSublayer:(CALayer*)sublayer];
// }
//
// static void setFrame(void* cametal, void* uiview) {
// CGSize size = ((UIView*)uiview).frame.size;
// ((CALayer*)cametal).frame = CGRectMake(0, 0, size.width, size.height);
// }
import "C"
import (
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ca"
"unsafe"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl"
)
type view struct {
}
func (v *view) setWindow(window uintptr) {
panic("metal: setWindow cannot be called on iOS")
panic("metal: setWindow is not available on iOS")
}
func (v *view) setDrawableSize(width, height int) {
// Do nothing
}
func (v *view) getMTLDevice() mtl.Device {
// TODO: Implement this
return mtl.Device{}
}
func (v *view) setDisplaySyncEnabled(enabled bool) {
// Do nothing
}
func (v *view) colorPixelFormat() mtl.PixelFormat {
// TODO: Implement this
return 0
}
func (v *view) reset() error {
// Do nothing
return nil
func (v *view) setUIView(uiview uintptr) {
v.uiview = uiview
}
func (v *view) update() {
// Do nothing
v.once.Do(func() {
if v.ml.Layer() == nil {
panic("metal: CAMetalLayer is not initialized yet")
}
C.addSublayer(unsafe.Pointer(v.uiview), v.ml.Layer())
})
C.setFrame(v.ml.Layer(), unsafe.Pointer(v.uiview))
}
func (v *view) drawable() ca.MetalDrawable {
// TODO: Implemnt this
return ca.MetalDrawable{}
}
const (
textureUsage = mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget
storageMode = mtl.StorageModeShared
resourceStorageMode = mtl.ResourceStorageModeShared
)

View File

@ -18,58 +18,16 @@
package metal
import (
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ca"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/mtl"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal/ns"
)
type view struct {
window uintptr
device mtl.Device
ml ca.MetalLayer
}
func (v *view) setWindow(window uintptr) {
v.window = window
}
func (v *view) setDrawableSize(width, height int) {
v.ml.SetDrawableSize(width, height)
}
func (v *view) getMTLDevice() mtl.Device {
return v.device
}
func (v *view) setDisplaySyncEnabled(enabled bool) {
// TODO: Now SetVsyncEnabled is called only from the main thread, and d.t.Run is not available since
// recursive function call via Run is forbidden.
// Fix this to use d.t.Run to avoid confusion.
v.ml.SetDisplaySyncEnabled(enabled)
}
func (v *view) colorPixelFormat() mtl.PixelFormat {
return v.ml.PixelFormat()
}
func (v *view) reset() error {
var err error
v.device, err = mtl.CreateSystemDefaultDevice()
if err != nil {
return err
}
v.ml = ca.MakeMetalLayer()
v.ml.SetDevice(v.device)
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat
//
// The pixel format for a Metal layer must be MTLPixelFormatBGRA8Unorm,
// MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or
// MTLPixelFormatBGRA10_XR_sRGB.
v.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
v.ml.SetMaximumDrawableCount(3)
return nil
func (v *view) setUIView(uiview uintptr) {
panic("metal: setUIView is not available on macOS")
}
func (v *view) update() {
@ -79,11 +37,10 @@ func (v *view) update() {
cocoaWindow.ContentView().SetWantsLayer(true)
}
func (v *view) drawable() ca.MetalDrawable {
d, err := v.ml.NextDrawable()
if err != nil {
// Drawable is nil. This can happen at the initial state. Let's wait and see.
return ca.MetalDrawable{}
}
return d
}
const (
// MTLTextureUsageRenderTarget might cause a problematic render result. Not sure the reason.
textureUsage = mtl.TextureUsageShaderRead
storageMode = mtl.StorageModeManaged
resourceStorageMode = mtl.ResourceStorageModeManaged
)

View File

@ -34,6 +34,7 @@ import (
"github.com/hajimehoshi/ebiten/internal/devicescale"
"github.com/hajimehoshi/ebiten/internal/driver"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl"
"github.com/hajimehoshi/ebiten/internal/thread"
)
var (
@ -61,7 +62,14 @@ func (u *UserInterface) Render() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
<-renderEndCh
cancel()
if u.t != nil {
u.t.Call(func() error {
cancel()
return nil
})
} else {
cancel()
}
}()
if u.graphics.IsGL() {
@ -79,9 +87,9 @@ func (u *UserInterface) Render() {
}
}
return
} else {
u.t.Loop(ctx)
}
// TODO: Create and run the thread loop like the GLFW driver does.
}
type UserInterface struct {
@ -99,6 +107,7 @@ type UserInterface struct {
input Input
t *thread.Thread
glWorker gl.Worker
m sync.RWMutex
@ -211,6 +220,9 @@ func (u *UserInterface) run(width, height int, scale float64, title string, cont
ctx, u.glWorker = gl.NewContext()
}
graphics.(*opengl.Driver).SetMobileGLContext(ctx)
} else {
u.t = thread.New()
graphics.SetThread(u.t)
}
// Force to set the screen size

View File

@ -81,3 +81,7 @@ func UpdateTouchesOnAndroid(action int, id int, x, y int) {
func UpdateTouchesOnIOS(phase int, ptr int64, x, y int) {
updateTouchesOnIOSImpl(phase, ptr, x, y)
}
func SetUIView(uiview int64) {
setUIView(uintptr(uiview))
}

View File

@ -0,0 +1,25 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin,ios,arm darwin,ios,arm64
package ebitenmobileview
import (
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/metal"
)
func setUIView(uiview uintptr) {
metal.Get().SetUIView(uiview)
}

View File

@ -0,0 +1,22 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin,ios,!arm
// +build darwin,ios,!arm64
package ebitenmobileview
func setUIView(uiview uintptr) {
panic("ebitenmobileview: setUIView is not available on this platform")
}

View File

@ -36,3 +36,6 @@ func updateTouchesOnAndroid(action int, id int, x, y int) {
func updateTouchesOnIOSImpl(phase int, ptr int64, x, y int) {
}
func setUIView(uiview uintptr) {
}