From 6d06b01cae408a1652b8a85e64415f3037a3c9ef Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Thu, 19 May 2016 23:37:58 +0900 Subject: [PATCH] gomobile bind works --- examples/mobile/main.go | 11 ++-- graphicscontext.go | 17 +++++ image.go | 4 -- internal/graphics/opengl/context_mobile.go | 35 ++++++---- internal/loop/run.go | 3 + internal/ui/ui.go | 1 + internal/ui/ui_glfw.go | 8 +-- internal/ui/ui_js.go | 11 ++-- internal/ui/ui_mobile.go | 77 ++++++++++++++++++++-- mobile/run.go | 26 +++----- 10 files changed, 138 insertions(+), 55 deletions(-) diff --git a/examples/mobile/main.go b/examples/mobile/main.go index 10fe674e7..62ba07c97 100644 --- a/examples/mobile/main.go +++ b/examples/mobile/main.go @@ -17,8 +17,6 @@ package mobile import ( - _ "image/jpeg" - "log" "math" "github.com/hajimehoshi/ebiten" @@ -49,11 +47,16 @@ func update(screen *ebiten.Image) error { return nil } -func Start() { +func Start() error { var err error gophersImage, _, err = common.AssetImage("gophers.jpg", ebiten.FilterNearest) if err != nil { - log.Fatal(err) + return err } mobile.Start(update, screenWidth, screenHeight, 2, "Mobile (Ebiten Demo)") + return nil +} + +func Render() error { + return mobile.Render() } diff --git a/graphicscontext.go b/graphicscontext.go index 5881c5b97..7dbf33718 100644 --- a/graphicscontext.go +++ b/graphicscontext.go @@ -14,6 +14,11 @@ package ebiten +import ( + "github.com/hajimehoshi/ebiten/internal/graphics" + "github.com/hajimehoshi/ebiten/internal/ui" +) + func newGraphicsContext(f func(*Image) error) *graphicsContext { return &graphicsContext{ f: f, @@ -25,6 +30,7 @@ type graphicsContext struct { screen *Image defaultRenderTarget *Image screenScale int + imageTasksDone bool } func (c *graphicsContext) SetSize(screenWidth, screenHeight, screenScale int) error { @@ -49,6 +55,17 @@ func (c *graphicsContext) SetSize(screenWidth, screenHeight, screenScale int) er } func (c *graphicsContext) Update() error { + if !c.imageTasksDone { + if err := graphics.Initialize(ui.GLContext()); err != nil { + return err + } + // This execution is called here because we can say actual GL function calls + // should be done here (especailly on mobiles). + if err := theDelayedImageTasks.exec(); err != nil { + return err + } + c.imageTasksDone = true + } if err := c.screen.Clear(); err != nil { return err } diff --git a/image.go b/image.go index 155d71c9f..91f4d435f 100644 --- a/image.go +++ b/image.go @@ -561,10 +561,6 @@ func newImageWithZeroFramebuffer(width, height int) (*Image, error) { if err != nil { return nil, err } - // When this is called, OpenGL context should exist. - if err := theDelayedImageTasks.exec(); err != nil { - return nil, err - } return img, nil } diff --git a/internal/graphics/opengl/context_mobile.go b/internal/graphics/opengl/context_mobile.go index 1129a3637..1e0782567 100644 --- a/internal/graphics/opengl/context_mobile.go +++ b/internal/graphics/opengl/context_mobile.go @@ -41,10 +41,12 @@ func (p Program) id() programID { } type context struct { - gl mgl.Context + gl mgl.Context + worker mgl.Worker + initialized chan struct{} } -func NewContext() *Context { +func NewContext() (*Context, error) { c := &Context{ Nearest: mgl.NEAREST, Linear: mgl.LINEAR, @@ -65,21 +67,28 @@ func NewContext() *Context { locationCache: newLocationCache(), lastCompositeMode: CompositeModeUnknown, } - return c + c.gl, c.worker = mgl.NewContext() + c.initialized = make(chan struct{}) + go func() { + // GL calls will just enqueue an task to the worker. + // Since the worker is not avaialbe, this enqueuing should be done + // in a goroutine. + + // Textures' pixel formats are alpha premultiplied. + c.gl.Enable(mgl.BLEND) + c.BlendFunc(CompositeModeSourceOver) + close(c.initialized) + }() + return c, nil } -func (c *Context) SetContext(gl mgl.Context) { - c.gl = gl - if gl == nil { - return - } - // Textures' pixel formats are alpha premultiplied. - gl.Enable(mgl.BLEND) - c.BlendFunc(CompositeModeSourceOver) +func (c *Context) WaitUntilInitializingDone() { + // TODO: Call this function at an approriate place + <-c.initialized } -func (c *Context) IsGLContextNil() bool { - return c.gl == nil +func (c *Context) Worker() mgl.Worker { + return c.worker } func (c *Context) BlendFunc(mode CompositeMode) { diff --git a/internal/loop/run.go b/internal/loop/run.go index a458339a8..7597d52bc 100644 --- a/internal/loop/run.go +++ b/internal/loop/run.go @@ -150,6 +150,9 @@ func Run(g GraphicsContext, width, height, scale int, title string, fps int) err beforeForUpdate += int64(tt) * int64(time.Second) / int64(fps) frames++ } + if err := ui.CurrentUI().FinishRendering(); err != nil { + return err + } // Calc the current FPS. if time.Second <= time.Duration(n2-beforeForFPS) { diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 81e6b0b33..b563127eb 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -18,6 +18,7 @@ type UserInterface interface { Start(width, height, scale int, title string) error Update() (interface{}, error) SwapBuffers() error + FinishRendering() error Terminate() error ScreenScale() int SetScreenSize(width, height int) bool diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 9fe9a0fdf..f9dd287ff 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -23,7 +23,6 @@ import ( "time" "github.com/go-gl/glfw/v3.1/glfw" - "github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/graphics/opengl" ) @@ -87,9 +86,6 @@ func initialize() (*opengl.Context, error) { if err := u.context.Init(); err != nil { return nil, err } - if err := graphics.Initialize(u.context); err != nil { - return nil, err - } return u.context, nil } @@ -262,6 +258,10 @@ func (u *userInterface) swapBuffers() { }) } +func (u *userInterface) FinishRendering() error { + return nil +} + func (u *userInterface) setScreenSize(width, height, scale int) bool { if u.width == width && u.height == height && u.scale == scale { return false diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index 073d4cc8a..022de465f 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -20,7 +20,6 @@ import ( "strconv" "github.com/gopherjs/gopherjs/js" - "github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/graphics/opengl" ) @@ -109,6 +108,10 @@ func (u *userInterface) SwapBuffers() error { return nil } +func (u *userInterface) FinishRendering() error { + return nil +} + func initialize() (*opengl.Context, error) { // Do nothing in node.js. if js.Global.Get("require") != js.Undefined { @@ -116,9 +119,6 @@ func initialize() (*opengl.Context, error) { if err != nil { return nil, err } - if err := graphics.Initialize(c); err != nil { - return nil, err - } return c, nil } @@ -226,9 +226,6 @@ func initialize() (*opengl.Context, error) { if err != nil { return nil, err } - if err := graphics.Initialize(c); err != nil { - return nil, err - } return c, nil } diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index 40485378e..179543067 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -17,26 +17,68 @@ package ui import ( + "errors" + "github.com/hajimehoshi/ebiten/internal/graphics/opengl" ) func initialize() (*opengl.Context, error) { - // TODO: Implement - return nil, nil + return opengl.NewContext() } func Main() error { + return errors.New("ui: don't call this: use RunWithoutMainLoop instead of Run") +} + +func Render(chError <-chan error) error { + if chError == nil { + return errors.New("ui: chError must not be nil") + } + // TODO: Check this is called on the rendering thread + chRender <- struct{}{} + worker := glContext.Worker() +loop: + for { + select { + case err := <-chError: + return err + case <-worker.WorkAvailable(): + worker.DoWork() + case <-chRenderEnd: + break loop + } + } return nil } type userInterface struct { + width int + height int + scale int + sizeChanged bool + render chan struct{} + renderEnd chan struct{} } +var ( + chRender = make(chan struct{}) + chRenderEnd = make(chan struct{}) + currentUI = &userInterface{ + sizeChanged: true, + render: chRender, + renderEnd: chRenderEnd, + } +) + func CurrentUI() UserInterface { - return nil + return currentUI } func (u *userInterface) Start(width, height, scale int, title string) error { + u.width = width + u.height = height + u.scale = scale + // title is ignored? return nil } @@ -45,21 +87,46 @@ func (u *userInterface) Terminate() error { } func (u *userInterface) Update() (interface{}, error) { - return nil, nil + // TODO: Need lock? + if u.sizeChanged { + u.sizeChanged = false + e := ScreenSizeEvent{ + Width: u.width, + Height: u.height, + Scale: u.scale, + ActualScale: u.actualScreenScale(), + } + return e, nil + } + select { + case <-u.render: + return RenderEvent{}, nil + } } func (u *userInterface) SwapBuffers() error { return nil } +func (u *userInterface) FinishRendering() error { + u.renderEnd <- struct{}{} + return nil +} + func (u *userInterface) SetScreenSize(width, height int) bool { + // TODO: Implement return false } func (u *userInterface) SetScreenScale(scale int) bool { + // TODO: Implement return false } func (u *userInterface) ScreenScale() int { - return 1 + return u.scale +} + +func (u *userInterface) actualScreenScale() int { + return u.scale } diff --git a/mobile/run.go b/mobile/run.go index c91b64493..df64db1e8 100644 --- a/mobile/run.go +++ b/mobile/run.go @@ -15,6 +15,7 @@ package mobile import ( + "errors" "runtime" "github.com/hajimehoshi/ebiten" @@ -28,16 +29,6 @@ var chError <-chan error // Different from ebiten.Run, this invokes only the game loop and not the main (UI) loop. func Start(f func(*ebiten.Image) error, width, height, scale int, title string) { chError = ebiten.RunWithoutMainLoop(f, width, height, scale, title) - return -} - -func LastErrorString() string { - select { - case err := <-chError: - return err.Error() - default: - return "" - } } func SetScreenSize(width, height int) { @@ -48,13 +39,12 @@ func SetScreenScale(scale int) { ui.CurrentUI().SetScreenScale(scale) } -func Render() { +func Render() error { runtime.LockOSThread() - // TODO: Implement this - /*select { - case <-workAvailable: - DoWork() - case <-done: - return - }*/ + defer runtime.UnlockOSThread() + + if chError == nil { + return errors.New("mobile: chError must not be nil: Start is not called yet?") + } + return ui.Render(chError) }