mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
ebiten: add RunGameOptions.StrictContextRestration
This reverts commit a30f075896
.
This change adds a new option StrictContextRestration to make the
restoration optional.
Closes #3083
This commit is contained in:
parent
935e7a6d5d
commit
35f4884a74
@ -25,10 +25,10 @@ import javax.microedition.khronos.egl.EGLConfig;
|
||||
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 {
|
||||
|
||||
@ -63,6 +63,10 @@ class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
|
||||
onceSurfaceCreated_ = true;
|
||||
return;
|
||||
}
|
||||
if (hasStrictContextRestoration()) {
|
||||
Ebitenmobileview.onContextLost();
|
||||
return;
|
||||
}
|
||||
contextLost_ = true;
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
@ -77,6 +81,8 @@ class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean strictContextRestoration_ = false;
|
||||
|
||||
public EbitenSurfaceView(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
@ -90,9 +96,11 @@ class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
|
||||
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) {
|
||||
@ -114,6 +122,16 @@ class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
#import "Ebitenmobileview.objc.h"
|
||||
|
||||
@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderRequester, EbitenmobileviewSetGameNotifier>
|
||||
@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderer, EbitenmobileviewSetGameNotifier>
|
||||
@end
|
||||
|
||||
@implementation {{.PrefixUpper}}EbitenViewController {
|
||||
@ -149,7 +149,7 @@
|
||||
|
||||
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];
|
||||
@ -364,6 +364,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setStrictContextRestoration:(BOOL)strictContextRestoration {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
- (void)setExplicitRenderingMode:(BOOL)explicitRendering {
|
||||
@synchronized(self) {
|
||||
explicitRendering_ = explicitRendering;
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -15,17 +15,42 @@
|
||||
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.
|
||||
@ -33,16 +58,12 @@ 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.
|
||||
@ -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
|
||||
}
|
||||
@ -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 {
|
||||
@ -243,6 +267,8 @@ func (i *images) restore(graphicsDriver graphicsdriver.Graphics) error {
|
||||
}
|
||||
}
|
||||
|
||||
i.contextLost.Store(false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 (
|
||||
@ -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
|
||||
}
|
||||
@ -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 {
|
||||
@ -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) {
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
@ -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) {
|
||||
|
37
run.go
37
run.go
@ -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.
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user