Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FPS is lower and irregular in fullscreen (macOS) #3054

Open
1 of 11 tasks
venning opened this issue Aug 3, 2024 · 7 comments
Open
1 of 11 tasks

FPS is lower and irregular in fullscreen (macOS) #3054

venning opened this issue Aug 3, 2024 · 7 comments

Comments

@venning
Copy link

venning commented Aug 3, 2024

Ebitengine Version

2.7.8

Operating System

  • Windows
  • macOS
  • Linux
  • FreeBSD
  • OpenBSD
  • Android
  • iOS
  • Nintendo Switch
  • PlayStation 5
  • Xbox
  • Web Browsers

Go Version (go version)

go1.22.2 darwin/arm64

What steps will reproduce the problem?

Run the following and toggle fullscreen by pressing [F]. It just measures the system time between Update calls (using SyncWithFPS) and calculates the effective FPS.

package main

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
)

var (
	w, h = 800, 600
	then = time.Now().UnixNano()
	cols, rows = 10, 30
	msgs = make([]string, cols*rows)
	next = 0 // index of next msg to write
	x0, y0, xStroke = 10, 60, 80
	graphicsLibrary string
	paused bool
)

func addMsg(s string) {
	msgs[next] = s
	next++
	if next == len(msgs) {
		next = 0
	}
	msgs[next] = "" // so we can track new msgs after looping
}

func getCol(c int) string {
	return strings.Join(msgs[c*rows:(c+1)*rows], "\n")
}

type Game struct {}

func (g *Game) Update() error {
	if !paused {
		now := time.Now().UnixNano()
		tps := 1_000_000_000/float64(now-then)
		then = now
		// EDIT: updated to align tps >1000 since those results were a bit "hidden"
		addMsg(fmt.Sprintf("%6.1f", tps))
	}

	if inpututil.IsKeyJustPressed(ebiten.KeyF) {
		ebiten.SetFullscreen(!ebiten.IsFullscreen())
		addMsg("TOGGLED FS")
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyQ) {
		return ebiten.Termination
	}
	if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
		paused = !paused
		if paused {
			addMsg("PAUSED")
		}
	}
	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
	if graphicsLibrary == "" {
		debugInfo := &ebiten.DebugInfo{}
		ebiten.ReadDebugInfo(debugInfo)
		graphicsLibrary = debugInfo.GraphicsLibrary.String()
	}
	msg := fmt.Sprintf("ebiten.ActualFPS():  %5.1f  (%s)\n\n", ebiten.ActualFPS(), graphicsLibrary)
	msg += "Press [F] to toggle fullscreen    Press [Q] to quit    Press [Space] to pause/unpause recording"
	ebitenutil.DebugPrint(screen, msg)

	for c := range cols {
		ebitenutil.DebugPrintAt(screen, getCol(c), x0+c*xStroke, y0)
	}
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return w, h
}

func main() {
	// I'm using these settings, so for consistency:
	ebiten.SetTPS(ebiten.SyncWithFPS)
	op := &ebiten.RunGameOptions{}
	op.SingleThread = true

//	op.GraphicsLibrary = ebiten.GraphicsLibraryMetal
//	op.GraphicsLibrary = ebiten.GraphicsLibraryOpenGL

	ebiten.SetWindowSize(w, h)
	if err := ebiten.RunGameWithOptions(&Game{}, op); err != nil && err != ebiten.Termination {
        log.Fatal(err)
    }
}

What is the expected result?

My laptop monitor is 120 Hz. While not in fullscreen, Ebitengine runs at ~120 FPS and the values of the effective FPS (as established by the above code) is roughly 120 FPS for each frame. This is as I would expect.

While in fullscreen, the FPS drops to ~90 (though sometimes as low as 77, I'm not sure why). Moreover, the effective timing of frames oscillates between ~120 FPS and ~60 FPS (which would average to 90). This is unexpected.

What happens instead?

This is not fullscreen:

Screenshot 2024-08-03 at 11 56 56 AM

This is fullscreen:

Screenshot 2024-08-03 at 11 57 55 AM

(Note, the printed FPS at the top of the images is affected by me taking the screenshots themselves.)

Anything else you feel useful to add?

I'm running these under Metal. The OpenGL version of this is wildly irregular; which, I assume, is why Metal is the default. However, OpenGL maintains ~120 FPS in fullscreen.

My guess is that MacOS is limiting the application to 90 FPS for its own reasons, but Ebitengine is reading the refresh rate at 120 Hz and its Draw call timing calculation is compensating; though I really don't understand enough of Ebitengine's internals to feel confident in that.


MacBook Pro
14-inch, 2021
Apple M1 Pro
32 GB
Ventura 13.1

@hajimehoshi
Copy link
Owner

I'll take a look, but I am not sure this is fixable.

@hajimehoshi
Copy link
Owner

hajimehoshi commented Aug 3, 2024

		now := time.Now().UnixNano()
		tps := 1_000_000_000/float64(now-then)
		then = now
		addMsg(fmt.Sprintf("%5.1f", tps))

This measures TPS (ticks per second), not FPS (frames per second). Ticks can be flaky so that you can expect Update is called 60 times per second on average. This is expected.

EDIT: You used SyncWithFPS, so this measures FPS. I'm sorry.

@hajimehoshi
Copy link
Owner

hajimehoshi commented Aug 3, 2024

I tested this on fullscreen (MacBook M3 Pro with power adapter):

image

Draw timings are pretty flaky and unstable, and as I said, I am not sure this is fixable. On average, FPS is stable as 120, so unfortunately I failed to reproduce your case.

@venning
Copy link
Author

venning commented Aug 3, 2024

I am not sure this is fixable.

Okay, I understand.

Can you keep this issue open? I would be interested in revisiting this if and when I upgrade operating systems or machines.

The more I use Ebitengine, the more I appreciate how weird operating systems make rendering.

@hajimehoshi
Copy link
Owner

Can you keep this issue open? I would be interested in revisiting this if and when I upgrade operating systems or machines.

Sure. I don't set a milestone, but I'm fine keeping this open.

The more I use Ebitengine, the more I appreciate how weird operating systems make rendering.

Thanks! Yeah, Ebitengine does a lot of things to handle weird things under the hood...

@gimby
Copy link

gimby commented Aug 14, 2024

As a side note, This reminds me of the troubles that the developers of the Zed editor went through, the performance characteristics were wildly different on the different ARM processors. https://zed.dev/blog/120fps

@venning
Copy link
Author

venning commented Aug 22, 2024

(I'm recording this here more for posterity, as I will likely revisit this issue in the future. I don't expect this comment will result in any immediate change in behavior or understanding.)

@hajimehoshi Revisiting this, I studied your results a little more closely. (As a reminder: my computer was somewhat alternating between 120 FPS and 60 FPS to average to something around 90 FPS, but your computer seemed to be maintaining 120 FPS.)

However, I just now noticed that your computer seems to somewhat replicate what my computer was doing with the alternating 120 and 60, but occasionally your computer would have a tick-frame that happened so quickly it registered as >1000 FPS, which resulted in the average being 120. Presumably, this was Ebitengine attempting to compensate for the occasional 60 FPS "lag".

Is there some reason your computer would do that while mine would not? I imagine it's likely either just a difference between M1 and M3, or a difference between our operating system versions (I'm assuming you weren't on Ventura 13.1).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants