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 35f4884
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 56 deletions.
26 changes: 22 additions & 4 deletions cmd/ebitenmobile/_files/EbitenSurfaceView.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
import javax.microedition.khronos.opengles.GL10;

import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
import {{.JavaPkg}}.ebitenmobileview.RenderRequester;
import {{.JavaPkg}}.ebitenmobileview.Renderer;
import {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;

class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
class EbitenSurfaceView extends GLSurfaceView implements Renderer {

private class EbitenRenderer implements GLSurfaceView.Renderer {

Expand Down 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,9 +96,11 @@ 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);

Ebitenmobileview.setRenderer(this);
}

private void onErrorOnGameUpdate(Exception e) {
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
8 changes: 6 additions & 2 deletions cmd/ebitenmobile/_files/EbitenViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

#import "Ebitenmobileview.objc.h"

@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderRequester, EbitenmobileviewSetGameNotifier>
@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderer, EbitenmobileviewSetGameNotifier>
@end

@implementation {{.PrefixUpper}}EbitenViewController {
Expand Down Expand Up @@ -149,7 +149,7 @@ - (void)initRenderer {

displayLink_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
EbitenmobileviewSetRenderRequester(self);
EbitenmobileviewSetRenderer(self);

// Run the loop. This will never return.
[[NSRunLoop currentRunLoop] run];
Expand Down Expand Up @@ -364,6 +364,10 @@ - (void)resumeGame {
}
}

- (void)setStrictContextRestoration:(BOOL)strictContextRestoration {
// Do nothing.
}

- (void)setExplicitRenderingMode:(BOOL)explicitRendering {
@synchronized(self) {
explicitRendering_ = explicitRendering;
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
30 changes: 20 additions & 10 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 @@ -97,8 +98,11 @@ type userInterfaceImpl struct {
inputState InputState
touches []TouchForInput

fpsMode atomic.Int32
renderRequester RenderRequester
fpsMode atomic.Int32
renderer Renderer

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.renderer.SetStrictContextRestoration(u.strictContextRestoration)

for {
if err := u.update(); err != nil {
Expand Down Expand Up @@ -239,10 +248,10 @@ func (u *UserInterface) SetFPSMode(mode FPSModeType) {
}

func (u *UserInterface) updateExplicitRenderingModeIfNeeded(fpsMode FPSModeType) {
if u.renderRequester == nil {
if u.renderer == nil {
return
}
u.renderRequester.SetExplicitRenderingMode(fpsMode == FPSModeVsyncOffMinimum)
u.renderer.SetExplicitRenderingMode(fpsMode == FPSModeVsyncOffMinimum)
}

func (u *UserInterface) readInputState(inputState *InputState) {
Expand Down Expand Up @@ -297,23 +306,24 @@ func (u *UserInterface) Monitor() *Monitor {
func (u *UserInterface) UpdateInput(keys map[Key]struct{}, runes []rune, touches []TouchForInput) {
u.updateInputStateFromOutside(keys, runes, touches)
if FPSModeType(u.fpsMode.Load()) == FPSModeVsyncOffMinimum {
u.renderRequester.RequestRenderIfNeeded()
u.renderer.RequestRenderIfNeeded()
}
}

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

func (u *UserInterface) SetRenderRequester(renderRequester RenderRequester) {
u.renderRequester = renderRequester
func (u *UserInterface) SetRenderer(renderer Renderer) {
u.renderer = renderer
u.updateExplicitRenderingModeIfNeeded(FPSModeType(u.fpsMode.Load()))
}

func (u *UserInterface) ScheduleFrame() {
if u.renderRequester != nil && FPSModeType(u.fpsMode.Load()) == FPSModeVsyncOffMinimum {
u.renderRequester.RequestRenderIfNeeded()
if u.renderer != nil && FPSModeType(u.fpsMode.Load()) == FPSModeVsyncOffMinimum {
u.renderer.RequestRenderIfNeeded()
}
}

Expand Down
13 changes: 9 additions & 4 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,16 +113,20 @@ func Resume() error {
return ui.Get().SetForeground(true)
}

func OnContextLost() {
restorable.OnContextLost()
}

func DeviceScale() float64 {
return ui.Get().Monitor().DeviceScaleFactor()
}

type RenderRequester interface {
ui.RenderRequester
type Renderer interface {
ui.Renderer
}

func SetRenderRequester(renderRequester RenderRequester) {
ui.Get().SetRenderRequester(renderRequester)
func SetRenderer(renderer Renderer) {
ui.Get().SetRenderer(renderer)
}

func SetSetGameNotifier(setGameNotifier SetGameNotifier) {
Expand Down
Loading

0 comments on commit 35f4884

Please sign in to comment.