Skip to content

Commit

Permalink
ebiten: add RunGameOptions.StrictContextRestration
Browse files Browse the repository at this point in the history
This reverts commit a30f075.

This change adds a new option StrictContextRestration to make the
restoration optional.

Closes #3083
  • Loading branch information
hajimehoshi committed Sep 7, 2024
1 parent 935e7a6 commit 9e54eca
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 37 deletions.
20 changes: 19 additions & 1 deletion cmd/ebitenmobile/_files/EbitenSurfaceView.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
onceSurfaceCreated_ = true;
return;
}
if (hasStrictContextRestoration()) {
Ebitenmobileview.onContextLost();
return;
}
contextLost_ = true;
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
Expand All @@ -77,6 +81,8 @@ public void onSurfaceChanged(GL10 gl, int width, int height) {
}
}

private boolean strictContextRestoration_ = false;

public EbitenSurfaceView(Context context) {
super(context);
initialize();
Expand All @@ -90,8 +96,10 @@ public EbitenSurfaceView(Context context, AttributeSet attrs) {
private void initialize() {
setEGLContextClientVersion(3);
setEGLConfigChooser(8, 8, 8, 8, 0, 0);
// setRenderer must be called before setRenderRequester.
// Or, the application crashes.
setRenderer(new EbitenRenderer());
setPreserveEGLContextOnPause(true);

Ebitenmobileview.setRenderRequester(this);
}

Expand All @@ -114,6 +122,16 @@ public synchronized void setExplicitRenderingMode(boolean explicitRendering) {
}
}

@Override
public synchronized void setStrictContextRestoration(boolean strictContextRestoration) {
strictContextRestoration_ = strictContextRestoration;
setPreserveEGLContextOnPause(!strictContextRestoration);
}

private synchronized boolean hasStrictContextRestoration() {
return strictContextRestoration_;
}

@Override
public synchronized void requestRenderIfNeeded() {
if (getRenderMode() == RENDERMODE_WHEN_DIRTY) {
Expand Down
5 changes: 5 additions & 0 deletions internal/restorable/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
)

// EnableRestoringForTesting forces to enable restoring for testing.
func EnableRestoringForTesting() {
forceRestoring = true
}

func ResolveStaleImages(graphicsDriver graphicsdriver.Graphics) error {
return resolveStaleImages(graphicsDriver, false)
}
Expand Down
67 changes: 49 additions & 18 deletions internal/restorable/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,55 @@
package restorable

import (
"image"
"runtime"
"sync"
"sync/atomic"

"github.com/hajimehoshi/ebiten/v2/internal/debug"
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
)

// forceRestoring reports whether restoring forcely happens or not.
// forceRestoring reports whether restoring forcibly happens or not.
// This is used only for testing.
var forceRestoring = false

// disabled indicates that restoring is disabled or not.
// Restoring is enabled by default for some platforms like Android for safety.
// Before SetGame, it is not possible to determine whether restoring is needed or not.
var disabled atomic.Bool

var disabledOnce sync.Once

// Disable disables restoring.
func Disable() {
disabled.Store(true)
}

// needsRestoring reports whether restoring process works or not.
func needsRestoring() bool {
return forceRestoring
if forceRestoring {
return true
}
// TODO: If Vulkan is introduced, restoring might not be needed.
if runtime.GOOS == "android" {
return !disabled.Load()
}
return false
}

// AlwaysReadPixelsFromGPU reports whether ReadPixels always reads pixels from GPU or not.
func AlwaysReadPixelsFromGPU() bool {
return !needsRestoring()
}

// EnableRestoringForTesting forces to enable restoring for testing.
func EnableRestoringForTesting() {
forceRestoring = true
}

// images is a set of Image objects.
type images struct {
images map[*Image]struct{}
shaders map[*Shader]struct{}
lastTarget *Image
images map[*Image]struct{}
shaders map[*Shader]struct{}
lastTarget *Image
contextLost atomic.Bool
}

// theImages represents the images for the current process.
Expand All @@ -66,6 +87,15 @@ func SwapBuffers(graphicsDriver graphicsdriver.Graphics) error {
// resolveStaleImages flushes the queued draw commands and resolves all stale images.
// If endFrame is true, the current screen might be used to present when flushing the commands.
func resolveStaleImages(graphicsDriver graphicsdriver.Graphics, endFrame bool) error {
// When Disable is called, all the images data should be evicted once.
if disabled.Load() {
disabledOnce.Do(func() {
for img := range theImages.images {
img.makeStale(image.Rectangle{})
}
})
}

if err := graphicscommand.FlushCommands(graphicsDriver, endFrame); err != nil {
return err
}
Expand All @@ -83,14 +113,8 @@ func RestoreIfNeeded(graphicsDriver graphicsdriver.Graphics) error {
return nil
}

if !forceRestoring {
var r bool

// TODO: Detect context lost explicitly on Android.

if !r {
return nil
}
if !forceRestoring && !theImages.contextLost.Load() {
return nil
}

if err := graphicscommand.ResetGraphicsDriverState(graphicsDriver); err != nil {
Expand Down Expand Up @@ -243,6 +267,8 @@ func (i *images) restore(graphicsDriver graphicsdriver.Graphics) error {
}
}

i.contextLost.Store(false)

return nil
}

Expand All @@ -258,3 +284,8 @@ func InitializeGraphicsDriverState(graphicsDriver graphicsdriver.Graphics) error
func MaxImageSize(graphicsDriver graphicsdriver.Graphics) int {
return graphicscommand.MaxImageSize(graphicsDriver)
}

// OnContextLost is called when the context lost is detected in an explicit way.
func OnContextLost() {
theImages.contextLost.Store(true)
}
19 changes: 10 additions & 9 deletions internal/ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,16 @@ func (u *UserInterface) dumpImages(dir string) (string, error) {
}

type RunOptions struct {
GraphicsLibrary GraphicsLibrary
InitUnfocused bool
ScreenTransparent bool
SkipTaskbar bool
SingleThread bool
DisableHiDPI bool
ColorSpace graphicsdriver.ColorSpace
X11ClassName string
X11InstanceName string
GraphicsLibrary GraphicsLibrary
InitUnfocused bool
ScreenTransparent bool
SkipTaskbar bool
SingleThread bool
DisableHiDPI bool
ColorSpace graphicsdriver.ColorSpace
X11ClassName string
X11InstanceName string
StrictContextRestoration bool
}

// InitialWindowPosition returns the position for centering the given second width/height pair within the first width/height pair.
Expand Down
10 changes: 10 additions & 0 deletions internal/ui/ui_mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/hook"
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
)

var (
Expand Down Expand Up @@ -100,6 +101,9 @@ type userInterfaceImpl struct {
fpsMode atomic.Int32
renderRequester RenderRequester

strictContextRestoration bool
strictContextRestorationOnce sync.Once

m sync.RWMutex
}

Expand Down Expand Up @@ -152,6 +156,11 @@ func (u *UserInterface) runMobile(game Game, options *RunOptions) (err error) {
u.graphicsDriver = g
u.setGraphicsLibrary(lib)
close(u.graphicsLibraryInitCh)
u.strictContextRestoration = options.StrictContextRestoration
if !u.strictContextRestoration {
restorable.Disable()
}
u.renderRequester.SetStrictContextRestoration(u.strictContextRestoration)

for {
if err := u.update(); err != nil {
Expand Down Expand Up @@ -303,6 +312,7 @@ func (u *UserInterface) UpdateInput(keys map[Key]struct{}, runes []rune, touches

type RenderRequester interface {
SetExplicitRenderingMode(explicitRendering bool)
SetStrictContextRestoration(strictContextRestoration bool)
RequestRenderIfNeeded()
}

Expand Down
5 changes: 5 additions & 0 deletions mobile/ebitenmobileview/mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"sync"

"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)

Expand Down Expand Up @@ -112,6 +113,10 @@ func Resume() error {
return ui.Get().SetForeground(true)
}

func OnContextLost() {
restorable.OnContextLost()
}

func DeviceScale() float64 {
return ui.Get().Monitor().DeviceScaleFactor()
}
Expand Down
37 changes: 28 additions & 9 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,24 @@ type RunGameOptions struct {

// X11InstanceName is an instance name in the ICCCM WM_CLASS window property.
X11InstanceName string

// StrictContextRestration indicates whether the context lost should be restored strictly by Ebitengine or not.
//
// StrictContextRestration is available only on Android. Otherwise, StrictContextRestration is ignored.
//
// When StrictContextRestration is false, Ebitengine tries to rely on the OS to restore the context.
// In Android, Ebitengien uses `GLSurfaceView`'s `setPreserveEGLContextOnPause(true)`.
// This works in most cases, but it is still possible that the context is lost in some minor cases.
// With StrictContextRestration false, the activity's launch mode should be singleInstance,
// or the activity no longer works correctly after the context is lost.
//
// When StrictContextRestration is true, Ebitengine tries to restore the context more strictly.
// This is useful when you want to restore the context in any case.
// However, this might cause a performance issue since Ebitengine tries to keep all the information
// to restore the context.
//
// The default (zero) value is false.
StrictContextRestration bool
}

// RunGameWithOptions starts the main loop and runs the game with the specified options.
Expand Down Expand Up @@ -716,15 +734,16 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions {
options.X11InstanceName = defaultX11InstanceName
}
return &ui.RunOptions{
GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary),
InitUnfocused: options.InitUnfocused,
ScreenTransparent: options.ScreenTransparent,
SkipTaskbar: options.SkipTaskbar,
SingleThread: options.SingleThread,
DisableHiDPI: options.DisableHiDPI,
ColorSpace: graphicsdriver.ColorSpace(options.ColorSpace),
X11ClassName: options.X11ClassName,
X11InstanceName: options.X11InstanceName,
GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary),
InitUnfocused: options.InitUnfocused,
ScreenTransparent: options.ScreenTransparent,
SkipTaskbar: options.SkipTaskbar,
SingleThread: options.SingleThread,
DisableHiDPI: options.DisableHiDPI,
ColorSpace: graphicsdriver.ColorSpace(options.ColorSpace),
X11ClassName: options.X11ClassName,
X11InstanceName: options.X11InstanceName,
StrictContextRestoration: options.StrictContextRestration,
}
}

Expand Down

0 comments on commit 9e54eca

Please sign in to comment.