diff --git a/doc.go b/doc.go index 1367f74c0..7fd8116d7 100644 --- a/doc.go +++ b/doc.go @@ -65,13 +65,19 @@ // to dump all the internal images. This is valid only when the build tag // 'ebitendebug' is specified. This works only on desktops. // +// `EBITEN_GRAPHICS_LIBRARY` environment variable specifies the graphics library. +// If the specified graphics library is not available, RunGame returns an error. +// This can take one of the following value: +// +// "auto": Ebiten chooses the graphics library automatically. This is the default value. +// "opengl": OpenGL, OpenGL ES, or WebGL. +// "metal": Metal. This works only on macOS or iOS. +// // Build tags // // `ebitendebug` outputs a log of graphics commands. This is useful to know what happens in Ebiten. In general, the // number of graphics commands affects the performance of your game. // -// `ebitengl` forces to use OpenGL in any environments. -// // `ebitenwebgl1` forces to use WebGL 1 on browsers. // // `ebitensinglethread` disables Ebiten's thread safety to unlock maximum performance. If you use this you will have diff --git a/internal/ui/graphics.go b/internal/ui/graphics.go index 2657a2e0a..2ea50dddb 100644 --- a/internal/ui/graphics.go +++ b/internal/ui/graphics.go @@ -15,9 +15,42 @@ package ui import ( + "fmt" + "os" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" ) +type graphicsDriverGetter interface { + getAuto() graphicsdriver.Graphics + getOpenGL() graphicsdriver.Graphics + getMetal() graphicsdriver.Graphics +} + +func chooseGraphicsDriver(getter graphicsDriverGetter) (graphicsdriver.Graphics, error) { + const envName = "EBITEN_GRAPHICS_LIBRARY" + + switch env := os.Getenv(envName); env { + case "", "auto": + if g := getter.getAuto(); g != nil { + return g, nil + } + return nil, fmt.Errorf("ui: no graphics library is available") + case "opengl": + if g := getter.getOpenGL(); g != nil { + return g, nil + } + return nil, fmt.Errorf("ui: %s=%s is specified but OpenGL is not available", envName, env) + case "metal": + if g := getter.getMetal(); g != nil { + return g, nil + } + return nil, fmt.Errorf("ui: %s=%s is specified but Metal is not available", envName, env) + default: + return nil, fmt.Errorf("ui: an unsupported graphics library is specified: %s", env) + } +} + func GraphicsDriverForTesting() graphicsdriver.Graphics { return theUI.graphicsDriver } diff --git a/internal/ui/graphics_opengl.go b/internal/ui/graphics_opengl.go deleted file mode 100644 index 6ed81a887..000000000 --- a/internal/ui/graphics_opengl.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 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. - -//go:build !darwin || ebitencbackend || ebitengl -// +build !darwin ebitencbackend ebitengl - -package ui - -import ( - "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" - "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" -) - -func graphicsDriver() graphicsdriver.Graphics { - return opengl.Get() -} diff --git a/internal/ui/graphics_darwin.go b/internal/ui/ui_android.go similarity index 67% rename from internal/ui/graphics_darwin.go rename to internal/ui/ui_android.go index 6aa1a1d1d..bb3f75545 100644 --- a/internal/ui/graphics_darwin.go +++ b/internal/ui/ui_android.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Ebiten Authors +// Copyright 2022 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. @@ -12,20 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !ebitengl && !ebitencbackend -// +build !ebitengl,!ebitencbackend - package ui import ( "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" - "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" ) -func graphicsDriver() graphicsdriver.Graphics { - if g := metal.Get(); g != nil { - return g - } +type graphicsDriverGetterImpl struct { + gomobileBuild bool +} + +func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { return opengl.Get() } + +func (*graphicsDriverGetterImpl) getOpenGL() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getMetal() graphicsdriver.Graphics { + return nil +} diff --git a/internal/ui/ui_cbackend.go b/internal/ui/ui_cbackend.go index 21eb53ab8..d73149050 100644 --- a/internal/ui/ui_cbackend.go +++ b/internal/ui/ui_cbackend.go @@ -22,8 +22,23 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/cbackend" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" ) +type graphicsDriverGetterImpl struct{} + +func (*graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getOpenGL() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getMetal() graphicsdriver.Graphics { + return nil +} + const deviceScaleFactor = 1 func init() { @@ -39,7 +54,11 @@ type userInterfaceImpl struct { func (u *userInterfaceImpl) Run(game Game) error { u.context = newContextImpl(game) - u.graphicsDriver = graphicsDriver() + g, err := chooseGraphicsDriver(&graphicsDriverGetterImpl{}) + if err != nil { + return err + } + u.graphicsDriver = g cbackend.InitializeGame() for { cbackend.BeginFrame() diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 644fc59eb..ae93b3f5d 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -811,7 +811,11 @@ event: } func (u *userInterfaceImpl) init() error { - u.graphicsDriver = graphicsDriver() + g, err := chooseGraphicsDriver(&graphicsDriverGetterImpl{}) + if err != nil { + return err + } + u.graphicsDriver = g if u.graphicsDriver.IsGL() { glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI) glfw.WindowHint(glfw.ContextVersionMajor, 2) diff --git a/internal/ui/ui_glfw_darwin.go b/internal/ui/ui_glfw_darwin.go index bd5624a62..e2aec0ede 100644 --- a/internal/ui/ui_glfw_darwin.go +++ b/internal/ui/ui_glfw_darwin.go @@ -227,8 +227,28 @@ import "C" import ( "github.com/hajimehoshi/ebiten/v2/internal/glfw" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" ) +type graphicsDriverGetterImpl struct{} + +func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { + if m := g.getMetal(); m != nil { + return m + } + return g.getOpenGL() +} + +func (*graphicsDriverGetterImpl) getOpenGL() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getMetal() graphicsdriver.Graphics { + return metal.Get() +} + // clearVideoModeScaleCache must be called from the main thread. func clearVideoModeScaleCache() {} diff --git a/internal/ui/ui_glfw_unix.go b/internal/ui/ui_glfw_unix.go index 28aac9f62..91bd4d5e3 100644 --- a/internal/ui/ui_glfw_unix.go +++ b/internal/ui/ui_glfw_unix.go @@ -22,12 +22,29 @@ import ( "math" "runtime" - "github.com/hajimehoshi/ebiten/v2/internal/glfw" "github.com/jezek/xgb" "github.com/jezek/xgb/randr" "github.com/jezek/xgb/xproto" + + "github.com/hajimehoshi/ebiten/v2/internal/glfw" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" ) +type graphicsDriverGetterImpl struct{} + +func (*graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getOpenGL() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getMetal() graphicsdriver.Graphics { + return nil +} + type videoModeScaleCacheKey struct{ X, Y int } var videoModeScaleCache = map[videoModeScaleCacheKey]float64{} diff --git a/internal/ui/ui_glfw_windows.go b/internal/ui/ui_glfw_windows.go index 2ddc72bb2..54031d7ef 100644 --- a/internal/ui/ui_glfw_windows.go +++ b/internal/ui/ui_glfw_windows.go @@ -25,8 +25,24 @@ import ( "golang.org/x/sys/windows" "github.com/hajimehoshi/ebiten/v2/internal/glfw" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" ) +type graphicsDriverGetterImpl struct{} + +func (*graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getOpenGL() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getMetal() graphicsdriver.Graphics { + return nil +} + const ( smCyCaption = 4 monitorDefaultToNearest = 2 diff --git a/internal/ui/ui_ios.go b/internal/ui/ui_ios.go index beb701b50..38285d0bc 100644 --- a/internal/ui/ui_ios.go +++ b/internal/ui/ui_ios.go @@ -17,6 +17,36 @@ package ui +import ( + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" +) + +type graphicsDriverGetterImpl struct { + gomobileBuild bool +} + +func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { + if m := g.getMetal(); m != nil { + return m + } + return g.getOpenGL() +} + +func (*graphicsDriverGetterImpl) getOpenGL() graphicsdriver.Graphics { + return opengl.Get() +} + +func (g *graphicsDriverGetterImpl) getMetal() graphicsdriver.Graphics { + // When gomobile-build is used, GL functions must be called via + // gl.Context so that they are called on the appropriate thread. + if g.gomobileBuild { + return nil + } + return metal.Get() +} + func SetUIView(uiview uintptr) { // This function should be called only when the graphics library is Metal. if g, ok := theUI.graphicsDriver.(interface{ SetUIView(uintptr) }); ok { diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index c8fe8b86d..6e1f36cdc 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -21,9 +21,24 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/devicescale" "github.com/hajimehoshi/ebiten/v2/internal/gamepad" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" "github.com/hajimehoshi/ebiten/v2/internal/hooks" ) +type graphicsDriverGetterImpl struct{} + +func (*graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getOpenGL() graphicsdriver.Graphics { + return opengl.Get() +} + +func (*graphicsDriverGetterImpl) getMetal() graphicsdriver.Graphics { + return nil +} + var ( stringNone = js.ValueOf("none") stringTransparent = js.ValueOf("transparent") @@ -309,7 +324,6 @@ func (u *userInterfaceImpl) needsUpdate() bool { func (u *userInterfaceImpl) loop(game Game) <-chan error { u.context = newContextImpl(game) - u.graphicsDriver = graphicsDriver() errCh := make(chan error, 1) reqStopAudioCh := make(chan struct{}) @@ -591,6 +605,11 @@ func (u *userInterfaceImpl) Run(game Game) error { } } u.running = true + g, err := chooseGraphicsDriver(&graphicsDriverGetterImpl{}) + if err != nil { + return err + } + u.graphicsDriver = g return <-u.loop(game) } diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index 79d0a03fd..2839c960a 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -270,17 +270,18 @@ func (u *userInterfaceImpl) run(game Game, mainloop bool) (err error) { }() u.context = newContextImpl(game) + g, err := chooseGraphicsDriver(&graphicsDriverGetterImpl{ + gomobileBuild: mainloop, + }) + if err != nil { + return err + } + u.graphicsDriver = g if mainloop { - // When mainloop is true, gomobile-build is used. In this case, GL functions must be called via - // gl.Context so that they are called on the appropriate thread. - g := opengl.Get() - u.graphicsDriver = g - ctx := <-glContextCh - g.SetGomobileGLContext(ctx) + g.(*opengl.Graphics).SetGomobileGLContext(ctx) } else { - u.graphicsDriver = graphicsDriver() u.t = thread.NewOSThread() graphicscommand.SetRenderingThread(u.t) }