Skip to content

Commit

Permalink
ebiten: Add FPSModeType, FPSMode, SetFPSMode, and ScheduleFrame
Browse files Browse the repository at this point in the history
This change adds these APIs:

  * type FPSModeType
  * func FPSMode
  * func SetFPSMode
  * func ScheduleFrame

and deprecates these APIs:

  * func SetVsyncEnabled
  * func IsVsyncEnabled

Closes hajimehoshi#1556
  • Loading branch information
hajimehoshi committed Jul 24, 2021
1 parent cf8aa0a commit 1706d94
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 42 deletions.
62 changes: 53 additions & 9 deletions cmd/ebitenmobile/gobind.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,17 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
#import "Ebitenmobileview.objc.h"
@interface {{.PrefixUpper}}EbitenViewController : UIViewController
@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderRequester>
@end
@implementation {{.PrefixUpper}}EbitenViewController {
UIView* metalView_;
GLKView* glkView_;
bool started_;
bool active_;
bool error_;
UIView* metalView_;
GLKView* glkView_;
bool started_;
bool active_;
bool error_;
CADisplayLink* displayLink_;
bool explicitRendering_;
}
- (UIView*)metalView {
Expand Down Expand Up @@ -214,8 +216,9 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
[EAGLContext setCurrentContext:context];
#endif
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
displayLink_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
EbitenmobileviewSetRenderRequester(self);
}
- (void)viewWillLayoutSubviews {
Expand Down Expand Up @@ -251,6 +254,10 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
#else
[[self glkView] setNeedsDisplay];
#endif
if (explicitRendering_) {
[displayLink_ setPaused:YES];
}
}
}
Expand Down Expand Up @@ -336,6 +343,25 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
}
}
- (void)setExplicitRenderingMode:(BOOL)explicitRendering {
@synchronized(self) {
explicitRendering_ = explicitRendering;
if (explicitRendering_) {
[displayLink_ setPaused:YES];
}
}
}
- (void)requestRenderIfNeeded {
@synchronized(self) {
if (explicitRendering_) {
// Resume the callback temporarily.
// This is paused again soon in drawFrame.
[displayLink_ setPaused:NO];
}
}
}
@end
`

Expand Down Expand Up @@ -745,9 +771,10 @@ import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
import {{.JavaPkg}}.ebitenmobileview.RenderRequester;
import {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;
class EbitenSurfaceView extends GLSurfaceView {
class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
private class EbitenRenderer implements GLSurfaceView.Renderer {
Expand Down Expand Up @@ -795,10 +822,27 @@ class EbitenSurfaceView extends GLSurfaceView {
setEGLContextClientVersion(2);
setEGLConfigChooser(8, 8, 8, 8, 0, 0);
setRenderer(new EbitenRenderer());
Ebitenmobileview.setRenderRequester(this);
}
private void onErrorOnGameUpdate(Exception e) {
((EbitenView)getParent()).onErrorOnGameUpdate(e);
}
@Override
public synchronized void setExplicitRenderingMode(boolean explictRendering) {
if (explictRendering) {
setRenderMode(RENDERMODE_WHEN_DIRTY);
} else {
setRenderMode(RENDERMODE_CONTINUOUSLY);
}
}
@Override
public synchronized void requestRenderIfNeeded() {
if (getRenderMode() == RENDERMODE_WHEN_DIRTY) {
requestRender();
}
}
}
`
2 changes: 1 addition & 1 deletion cmd/ebitenmobile/gobind.src.go

Large diffs are not rendered by default.

25 changes: 17 additions & 8 deletions examples/windowsize/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (g *game) Update() error {
fullscreen := ebiten.IsFullscreen()
runnableOnUnfocused := ebiten.IsRunnableOnUnfocused()
cursorMode := ebiten.CursorMode()
vsyncEnabled := ebiten.IsVsyncEnabled()
fpsMode := ebiten.FPSMode()
tps := ebiten.MaxTPS()
decorated := ebiten.IsWindowDecorated()
positionX, positionY := ebiten.WindowPosition()
Expand Down Expand Up @@ -205,7 +205,14 @@ func (g *game) Update() error {
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyV) {
vsyncEnabled = !vsyncEnabled
switch fpsMode {
case ebiten.FPSModeVsyncOn:
fpsMode = ebiten.FPSModeVsyncOffMaximum
case ebiten.FPSModeVsyncOffMaximum:
fpsMode = ebiten.FPSModeVsyncOffMinimum
case ebiten.FPSModeVsyncOffMinimum:
fpsMode = ebiten.FPSModeVsyncOn
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyT) {
switch tps {
Expand Down Expand Up @@ -249,10 +256,10 @@ func (g *game) Update() error {
ebiten.SetRunnableOnUnfocused(runnableOnUnfocused)
ebiten.SetCursorMode(cursorMode)

// Set vsync enabled only when this is needed.
// This makes a bug around vsync initialization more explicit (#1364).
if vsyncEnabled != ebiten.IsVsyncEnabled() {
ebiten.SetVsyncEnabled(vsyncEnabled)
// Set FPS mode enabled only when this is needed.
// This makes a bug around FPS mode initialization more explicit (#1364).
if fpsMode != ebiten.FPSMode() {
ebiten.SetFPSMode(fpsMode)
}
ebiten.SetMaxTPS(tps)
ebiten.SetWindowDecorated(decorated)
Expand Down Expand Up @@ -323,7 +330,7 @@ func (g *game) Draw(screen *ebiten.Image) {
[U] Switch the runnable-on-unfocused state
[C] Switch the cursor mode (visible, hidden, or captured)
[I] Change the window icon (only for desktops)
[V] Switch vsync
[V] Switch the FPS mode
[T] Switch TPS (ticks per second)
[D] Switch the window decoration (only for desktops)
[L] Switch the window floating state (only for desktops)
Expand Down Expand Up @@ -403,7 +410,9 @@ func main() {
ebiten.SetWindowResizable(true)
ebiten.MaximizeWindow()
}
ebiten.SetVsyncEnabled(*flagVsync)
if !*flagVsync {
ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMaximum)
}
if *flagAutoAdjusting {
ebiten.SetWindowResizable(true)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/driver/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type UI interface {

FPSMode() FPSMode
SetFPSMode(mode FPSMode)
ScheduleFrame()

IsScreenTransparent() bool
SetScreenTransparent(transparent bool)
Expand Down Expand Up @@ -104,4 +105,5 @@ type FPSMode int
const (
FPSModeVsyncOn FPSMode = iota
FPSModeVsyncOffMaximum
FPSModeVsyncOffMinimum
)
8 changes: 8 additions & 0 deletions internal/glfw/glfw_notwindows.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ func PollEvents() {
glfw.PollEvents()
}

func PostEmptyEvent() {
glfw.PostEmptyEvent()
}

func SetMonitorCallback(cbfun func(monitor *Monitor, event PeripheralEvent)) {
var gcb func(monitor *glfw.Monitor, event glfw.PeripheralEvent)
if cbfun != nil {
Expand All @@ -360,6 +364,10 @@ func UpdateGamepadMappings(mapping string) bool {
return glfw.UpdateGamepadMappings(mapping)
}

func WaitEvents() {
glfw.WaitEvents()
}

func WindowHint(target Hint, hint int) {
glfw.WindowHint(glfw.Hint(target), hint)
}
10 changes: 10 additions & 0 deletions internal/glfw/glfw_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,11 @@ func PollEvents() {
panicErrorExceptForInvalidValue()
}

func PostEmptyEvent() {
glfwDLL.call("glfwPostEmptyEvent")
panicError()
}

func SetMonitorCallback(cbfun func(monitor *Monitor, event PeripheralEvent)) {
var gcb uintptr
if cbfun != nil {
Expand Down Expand Up @@ -554,6 +559,11 @@ func UpdateGamepadMappings(mapping string) bool {
return r == True
}

func WaitEvents() {
glfwDLL.call("glfwWaitEvents")
panicError()
}

func WindowHint(target Hint, hint int) {
glfwDLL.call("glfwWindowHint", uintptr(target), uintptr(hint))
panicError()
Expand Down
17 changes: 15 additions & 2 deletions internal/uidriver/glfw/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,15 @@ func (u *UserInterface) FPSMode() driver.FPSMode {
return v
}

func (u *UserInterface) ScheduleFrame() {
if !u.isRunning() {
return
}
// As the main thread can be blocked, do not check the current FPS mode.
// PostEmptyEvent is concurrent safe.
glfw.PostEmptyEvent()
}

func (u *UserInterface) CursorMode() driver.CursorMode {
if !u.isRunning() {
return u.getInitCursorMode()
Expand Down Expand Up @@ -964,8 +973,12 @@ func (u *UserInterface) update() (float64, float64, bool, error) {

outsideWidth, outsideHeight, outsideSizeChanged := u.updateSize()

// TODO: Updating the input can be skipped when clock.Update returns 0 (#1367).
glfw.PollEvents()
if u.fpsMode != driver.FPSModeVsyncOffMinimum {
// TODO: Updating the input can be skipped when clock.Update returns 0 (#1367).
glfw.PollEvents()
} else {
glfw.WaitEvents()
}
u.input.update(u.window, u.context)

for !u.isRunnableOnUnfocused() && u.window.GetAttrib(glfw.Focused) == 0 && !u.window.ShouldClose() {
Expand Down
2 changes: 2 additions & 0 deletions internal/uidriver/js/input_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,8 @@ func (i *Input) updateFromEvent(e js.Value) {
case t.Equal(stringTouchstart) || t.Equal(stringTouchend) || t.Equal(stringTouchmove):
i.updateTouchesFromEvent(e)
}

i.ui.forceUpdateOnMinimumFPSMode()
}

func (i *Input) setMouseCursorFromEvent(e js.Value) {
Expand Down
50 changes: 42 additions & 8 deletions internal/uidriver/js/ui_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ func driverCursorShapeToCSSCursor(cursor driver.CursorShape) string {
type UserInterface struct {
runnableOnUnfocused bool
fpsMode driver.FPSMode
renderingScheduled bool
running bool
initFocused bool
cursorMode driver.CursorMode
cursorPrevMode driver.CursorMode
cursorShape driver.CursorShape
onceUpdateCalled bool

sizeChanged bool

Expand Down Expand Up @@ -146,6 +148,10 @@ func (u *UserInterface) FPSMode() driver.FPSMode {
return u.fpsMode
}

func (u *UserInterface) ScheduleFrame() {
u.renderingScheduled = true
}

func (u *UserInterface) CursorMode() driver.CursorMode {
if !canvas.Truthy() {
return driver.CursorModeHidden
Expand Down Expand Up @@ -281,6 +287,20 @@ func (u *UserInterface) updateImpl(force bool) error {
return nil
}

func (u *UserInterface) needsUpdate() bool {
if u.fpsMode != driver.FPSModeVsyncOffMinimum {
return true
}
if !u.onceUpdateCalled {
return true
}
if u.renderingScheduled {
return true
}
// TODO: Watch the gamepad state?
return false
}

func (u *UserInterface) loop(context driver.UIContext) <-chan error {
u.context = context

Expand All @@ -290,17 +310,24 @@ func (u *UserInterface) loop(context driver.UIContext) <-chan error {

var cf js.Func
f := func() {
if err := u.update(); err != nil {
close(reqStopAudioCh)
<-resStopAudioCh

errCh <- err
return
if u.needsUpdate() {
u.onceUpdateCalled = true
u.renderingScheduled = false
if err := u.update(); err != nil {
close(reqStopAudioCh)
<-resStopAudioCh

errCh <- err
return
}
}
if u.fpsMode == driver.FPSModeVsyncOn {
switch u.fpsMode {
case driver.FPSModeVsyncOn:
requestAnimationFrame.Invoke(cf)
} else {
case driver.FPSModeVsyncOffMaximum:
setTimeout.Invoke(cf, 0)
case driver.FPSModeVsyncOffMinimum:
requestAnimationFrame.Invoke(cf)
}
}

Expand Down Expand Up @@ -540,6 +567,13 @@ func setCanvasEventHandlers(v js.Value) {
}))
}

func (u *UserInterface) forceUpdateOnMinimumFPSMode() {
if u.fpsMode != driver.FPSModeVsyncOffMinimum {
return
}
u.updateImpl(true)
}

func (u *UserInterface) Run(context driver.UIContext) error {
if u.initFocused && window.Truthy() {
// Do not focus the canvas when the current document is in an iframe.
Expand Down
Loading

0 comments on commit 1706d94

Please sign in to comment.