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

Low FPS in windowed mode when G-Sync is enabled #1467

Closed
vzx opened this issue Jan 20, 2021 · 23 comments
Closed

Low FPS in windowed mode when G-Sync is enabled #1467

vzx opened this issue Jan 20, 2021 · 23 comments

Comments

@vzx
Copy link

vzx commented Jan 20, 2021

I'm exploring Ebiten to play around with to create a small game. So far I have been checking out some of the examples and "following along" a little (check the example out, get some ideas, then write something similar in my own project). The project I have currently loads a PNG image and draws it, you can move it around with the arrow keys (translate x/y) and it continuously rotates (every update degrees are incremented, and the image is rotate in the draw).

What I experience however is that it is very slow in windowed mode. The TPS is at ~60, which is great, but the FPS is around ~25-40. This is both with my own project, but also with some of the examples (notable, the rotate example: https://github.com/hajimehoshi/ebiten/blob/master/examples/rotate/main.go).

My computer is relatively modern, with an Intel 6700k CPU, 8GB of RAM, and a GTX 1080 graphics card, and naturally other games run fine on it. The rotate example runs at only 640x480 in a window. My own project runs at 1920x1080 in a window too (my screen is a G-Sync 2560x1440 165Hz display).

My development environment is kind of odd, in the sense that I develop inside of WSL (Windows Subsystem for Linux) and so basically I cross-compile in Linux for Windows, and then run the Windows executable. But I would find it hard to believe that this could have any impact on the performance.

When I set it to fullscreen and the window size to 2560x1440 (my display's native resolution) it works fine however. But as you can imagine it's nicer to be able to play around and check new things out in a smaller windowed mode.

Edit: I played around with the G-Sync settings, and found out this problem occurs when:

  • G-Sync is enabled in both fullscreen and windowed mode (see picture below)
  • V-Sync is enabled in Ebiten

When G-Sync is switched to only fullscreen or disabled entirely, the problem goes away. Also if it's left enabled, but V-Sync is disabled in Ebiten, it also runs fine.

I changed the issue title accordingly.

image

@vzx vzx changed the title Very low FPS (~25) in windowed mode on a modern computer using Windows with the basic examples Low FPS in windowed when G-Sync is enabled Jan 20, 2021
@vzx vzx changed the title Low FPS in windowed when G-Sync is enabled Low FPS in windowed mod when G-Sync is enabled Jan 20, 2021
@vzx vzx changed the title Low FPS in windowed mod when G-Sync is enabled Low FPS in windowed mode when G-Sync is enabled Jan 20, 2021
@hajimehoshi
Copy link
Owner

Thank you for reporting! What about the examples on browsers like https://ebiten.org/examples/sprites.html ?

My development environment is kind of odd, in the sense that I develop inside of WSL (Windows Subsystem for Linux) and so basically I cross-compile in Linux for Windows, and then run the Windows executable.

So you compiled your game with GOOS=windows and execute it on Windows, right? When you run the executable, do you execute it via WSL, or without WSL?

@vzx
Copy link
Author

vzx commented Jan 20, 2021

Thanks for getting back to me! The browser examples run just fine. I also compiled my own little playground project to WebAssembly and ran it in the browser, also fine (very cool by the way, that I can run a game written in Go in the browser!).

So it really looks related to the very specific case when G-Sync is enabled in the Nvidia control panel for Windowed mode, and the game is running in Windowed mode, and V-Sync is enabled in the game.

So you compiled your game with GOOS=windows and execute it on Windows, right? When you run the executable, do you execute it via WSL, or without WSL?

During playing around, I ran GOOS=windows go run main.go to start the game each time. However I also tried a GOOS=windows go build main.go and then ran the resulting .exe in Windows (just by navigating to the folder and opening it from the explorer).

After I found out it's related to the G-Sync settings, I am convinced that the development setup with WSL is not related to it though. :)

@hajimehoshi
Copy link
Owner

Thank you for the information!

Unfortunately I'm not familiar with G-sync and don't have a machine with that video card. Then I don't have an immediate solution so far. I'll investigate about G-sync, but any insights are always welcome.

@vzx
Copy link
Author

vzx commented Jan 20, 2021

Unfortunately I'm not familiar with G-sync and don't have a machine with that video card. Then I don't have an immediate solution so far. I'll investigate about G-sync, but any insights are always welcome.

Ah, G-Sync is a type of adaptive refresh rate technology by Nvidia, similar to FreeSync by AMD (there's a bit of a long history there). It is a feature implemented in certain displays (usually ones with a higher refresh rate, like 144Hz, 165Hz, 240Hz etc). What it does, is change the refresh rate of the display to the frame rate of the game (or other graphics application) that is running. In that sense, it is kind of the inverse of V-Sync.

As you know, with V-Sync, if the game would normally be running at let's say 100 fps, but the display is only 60Hz, then the game will be restricted to run at 60 fps instead.

With G-Sync, the game runs at whatever frame rate it needs to, and the refresh rate of the display changes to match it. So if you computer is not good enough to run a game well, and it runs at only 50fps, the display will change to 50Hz. Conversely, if the game runs very well and goes up to 100fps, the display will run at 100Hz. The primary benefit of this is prevent tearing, which can happen quite often when a game's frame rate does not match a display's refresh rate (and can be particularly apparent when a game runs at a low frame rate).

The way I understand it, is that for G-Sync to work, typically also V-Sync has to be enabled, to facilitate a kind of feedback loop between the game's frame rate and the display's refresh rate, and to ensure that for example the game will still not refresh faster than the display's maximum refresh rate (eg. not run at more than 144fps on a 144Hz display). But this is also where my knowledge on the subject ends. :)

@hajimehoshi
Copy link
Owner

Looks like the same problem as #1043

@vzx
Copy link
Author

vzx commented Jan 20, 2021

Ah I see, that could well be indeed! Do you know if a related issue is already reported in the GLFW project?

@hajimehoshi
Copy link
Owner

Do you know if a related issue is already reported in the GLFW project?

No, I've not found one yet.

I'd appreciate if you could test github.com/go-gl/example's gl21-cube. This might be difficult to set up since this requires C-compiler (GCC/MinGW) for Windows, so it's ok if you find it hard. If gl21-cube works well with G-sync, we can say this is an issue in Ebiten.

@vzx
Copy link
Author

vzx commented Jan 22, 2021

Sorry for the late reply @hajimehoshi but I needed some time to get gl21-cube to compile and work (so far I had only set up my Go development environment in WSL, not Windows itself). Also I needed some time to figure out how to enabled V-Sync using go-gl/glfw and how to make it display the frame rate.

When I run the gl21-cube I experience no issues at all, neither with V-Sync on nor off. During all my testing however I also failed to reproduce the issue with Ebiten a few times: it would just run smoothly at my display's max refresh rate those times. So it is a flaky issue for sure, not reproducible each time. Though so far I've not been able to get the issue with gl21-cube even once...

Definitely a weird one. I was really expecting gl21-cube to show the same issue!

@hajimehoshi
Copy link
Owner

Ah sorry, I forgot gl21-cube didn't v-sync by default. Thank you for testing!

So,

  • gl21-cube with v-sync works with G-sync in 100%
  • Ebiten (with v-sync) doesn't work with G-sync, but sometimes works.

Is that correct?

@vzx
Copy link
Author

vzx commented Jan 23, 2021

Ah sorry, I forgot gl21-cube didn't v-sync by default. Thank you for testing!

No problem, I had a lot of fun playing around with GLFW and learning about it!

I did yet more tests, trying out some different scenarios (I had a feeling that maybe it mattered which app was open behind the game window). Also I compiled my Ebiten playground app natively on Windows to exclude the possibility that it might be related somehow to cross-compiling in WSL, but it made no difference. Also did some more attempts to reproduce it using gl21-cube, but without luck.

I did notice a few times that the behaviour can change even while running. So for example, it would start with 165fps for a while, then drop to 20-30fps for another while, and a bit later again go back to 165fps.

So you are correct, indeed.

  • gl21-cube with V-Sync and G-Sync, no issues at all
  • Ebiten with V-Sync and G-Sync, sometimes works, sometimes drops to low framerates

@HaBaLeS
Copy link

HaBaLeS commented Jan 31, 2021

I have the same issue with very low FPS/TPS when using a Dual monitor setup on Linux.
Laptop display FHD 144hz and external Monitor WQHD 144hz

it does not matter if i have V-Sync on/off. FPS fluctuates heavily 20-30. TPS fluctuates 40-60. I am rendering only 1 sprite. If i turn dual-screen off, i get a steady FPS/TPS 144/60

@hajimehoshi hajimehoshi added this to the v2.2.0 milestone Mar 24, 2021
@hajimehoshi
Copy link
Owner

Thank you for reporting, all!

When I want to try G-sync, would it be enough to just have a G-sync display, or should I get a specific GPU at the same time? Thanks.

@vzx
Copy link
Author

vzx commented Mar 28, 2021

@hajimehoshi G-Sync (and the open alternative, FreeSync) is implemented as an interaction between GPU and display. So unfortunately to use it, you would need a graphics card which supports it connected to a display which also supports it.

There is a chance however that this bug could also be reproducible using an AMD graphics card with FreeSync but unfortunately I don't have the hardware available to test that.

I have no idea if integrated graphics chips support adaptive sync technologies, and I have also no clue about support on notebooks...

Nowadays, Nvidia GPUs of the 10-series (GTX 1060, GTX 1070, etc) and newer also support enabling G-Sync when using a FreeSync display. But at the same time displays which have just G-Sync are not supported by GPUs which support only FreeSync (such as AMD GPUs) since that is a proprietary technology.

@hajimehoshi
Copy link
Owner

hajimehoshi commented Apr 24, 2021

When I run the gl21-cube I experience no issues at all, neither with V-Sync on nor off. During all my testing however I also failed to reproduce the issue with Ebiten a few times: it would just run smoothly at my display's max refresh rate those times. So it is a flaky issue for sure, not reproducible each time. Though so far I've not been able to get the issue with gl21-cube even once...

I realized that gl21-cube doesn't call glfw.SwapInterval(1). Could anyone test gl21-cube with calling glfw.SwapInterval(1) to enable vsync explictly? If FPS doesn't reach the display's refresh rate, I think Windows' OpenGL driver has an issue.

EDIT: This was already tested at #1467 (comment) ?

@hajimehoshi
Copy link
Owner

hajimehoshi commented Apr 25, 2021

OK, I'm considering to buy a machine and a monitor for G-sync

  1. A machine with GeForce® GTX 1650 GDDR6 (Mouse Computer) (about $800)
  2. A display (Asus VG258QR-J) (about $250)

I hope this combination would work for G-sync 🤔

EDIT: https://www.asus.com/sg/News/xQd6zEtf4qNYmDkZ/

ASUS today announced VG278QR, VG258QR and VG248QG, three certified NVIDIA® G-SYNC™ Compatible gaming monitors that deliver an excellent variable refresh rate (VRR) experience on NVIDIA GeForce® GTX 10-Series, GeForce GTX 16-Series and GeForce RTX 20-Series graphics cards.

@hajimehoshi
Copy link
Owner

hajimehoshi commented Apr 25, 2021

https://github.com/glfw/glfw/blob/33cd8b865d9289cfbcf3d95e6e68e4050b94fcd3/src/wgl_context.c#L320-L366

The special logic is used in the windowed mode on Windows for glfwSwapInterval and glfwSwapBuffers, but I have no idea about this.

EDIT: glfw/glfw@8309e0e

EDIT2: Found this logic for DWM is necessary. Without this, vsync doesn't work in the windowed mode. Hmm, probably we'd have to use DirectX...?

@hajimehoshi
Copy link
Owner

hajimehoshi commented Apr 25, 2021

We have experimented go-gl/example/gl21-cube w/ vsync and Ebiten applications, and apparently the same problem occurred (Thank you, @tokoroten!) This means that Ebiten is not to blame, and probably we would have to switch from OpenGL to DirectX to fix this issue completely... (This is just a guess, and we're not sure.)

Experiment

Created a modified gl21-cube (gl21-cube-vsync.exe)

We have created a modified go-gl application go-gl/example/gl21-cube with this patch in order to enable vsync and see FPS:

diff --git a/gl21-cube/cube.go b/gl21-cube/cube.go
index 1b4bbb0..5847305 100644
--- a/gl21-cube/cube.go
+++ b/gl21-cube/cube.go
@@ -11,13 +11,20 @@ import (
        "image/draw"
        _ "image/png"
        "log"
-       "os"
        "runtime"
 
        "github.com/go-gl/gl/v2.1/gl"
        "github.com/go-gl/glfw/v3.3/glfw"
+
+       _ "embed"
+       "bytes"
+       "fmt"
+       "time"
 )
 
+//go:embed square.png
+var squarePng []byte
+
 var (
        texture   uint32
        rotationX float32
@@ -50,23 +57,29 @@ func main() {
                panic(err)
        }
 
-       texture = newTexture("square.png")
+       texture = newTexture(squarePng)
        defer gl.DeleteTextures(1, &texture)
 
+       glfw.SwapInterval(1)
+       now := time.Now()
+
        setupScene()
+       var count int
        for !window.ShouldClose() {
                drawScene()
                window.SwapBuffers()
                glfw.PollEvents()
+               count++
+               if now2 := time.Now(); now2.Sub(now) >= time.Second {
+                       fmt.Printf("FPS: %d\n", count)
+                       count = 0
+                       now = now2
+               }
        }
 }
 
-func newTexture(file string) uint32 {
-       imgFile, err := os.Open(file)
-       if err != nil {
-               log.Fatalf("texture %q not found on disk: %v\n", file, err)
-       }
-       img, _, err := image.Decode(imgFile)
+func newTexture(bs []byte) uint32 {
+       img, _, err := image.Decode(bytes.NewReader(bs))
        if err != nil {
                panic(err)
        }
@@ -208,7 +221,7 @@ func drawScene() {
 }

// Set the working directory to the root of Go package, so that its assets can be accessed.
-func init() {
+/*func init() {
        dir, err := importPathToDir("github.com/go-gl/example/gl21-cube")
        if err != nil {
                log.Fatalln("Unable to find Go package in your GOPATH, it's needed to load assets:", err)
@@ -217,7 +230,7 @@ func init() {
        if err != nil {
                log.Panicln("os.Chdir:", err)
        }
-}
+}*/
 
 // importPathToDir resolves the absolute path from importPath.
 // There doesn't need to be a valid Go package inside that import path,
diff --git a/go.mod b/go.mod
index 7ba97c5..7b3346d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/go-gl/example
 
-go 1.14
+go 1.16
 
 require (
        github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7

Created a modified examples/rotate (rotate-vsync.exe)

We have created a modified Ebiten example rotate with this patch in order to see FPS:

diff --git a/examples/rotate/main.go b/examples/rotate/main.go
index 8e8a083a..6cef1319 100644
--- a/examples/rotate/main.go
+++ b/examples/rotate/main.go
@@ -22,6 +22,7 @@ import (
        _ "image/jpeg"
        "log"
        "math"
+       "fmt"
 
        "github.com/hajimehoshi/ebiten/v2"
        "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
@@ -42,6 +43,9 @@ type Game struct {
 
 func (g *Game) Update() error {
        g.count++
+       if g.count % 60 == 0 {
+               fmt.Printf("FPS: %d\n", int(ebiten.CurrentFPS()))
+       }
        return nil
 }

Vsync is on by default.

Result

We executed both on a Windows machine (GeForce RTX 3070) and a display (VG27A / 144 Hz) that can enable G-Sync.

  1. If G-Sync is off, both application could keep about 144 FPS
  2. If G-Sync is on, both application showed about 144 FPS first, but when the window was moved by dragging, the FPS dropped to 40 FPS or so.

So regardless of Ebiten usage, the FPS drop happens with Go/GLFW applications. We are not sure this could happen without OpenGL.

@hajimehoshi
Copy link
Owner

@hajimehoshi
Copy link
Owner

Now Ebiten introduced the DirectX driver. Could anyone test this?

go get github.com/hajimehoshi/ebiten/v2@79e93d3b125148dd7d191316716d7262ab0bd324

Thanks,

@vzx
Copy link
Author

vzx commented Mar 30, 2022

Thanks for the in-depth investigations and work on this issue! I would like to test, but unfortunately I am unable to. Since raising this issue, I have swapped components and no longer have a G-Sync compatible GPU nor display (now I'm using AMD hardware, using Freesync). On top of that, I have unfortunately since also abandoned the project I was working on so I have no test case ready any more (so also I don't know if the same issue occurred with AMD/Freesync). :(

I hope someone else will be able to test this!

@hajimehoshi hajimehoshi modified the milestones: v2.3.0, v2.4.0 Apr 7, 2022
@hajimehoshi
Copy link
Owner

I've not confirmed this fixed yet, but let me close this. Let's revisit this if this is not fixed yet.

@hak33m16
Copy link

@hajimehoshi It looks like this may still be an issue for those of us with G-sync cards/displays

I followed the basic tutorial setup here for installing ebiten which got me v2.3.2: https://ebiten.org/documents/install.html
And am using the hello world application here: https://ebiten.org/tour/hello_world.html

I used go run main.go on my Windows machine, and notice significant choppiness when moving the window around

The linked issue made it seem like it'll use DirectX by default on Windows, and I couldn't find any documentation specifying otherwise, so maybe I'm mistaken and it's still using OpenGL? If there's anything I can do to help narrow this down, please let me know

@hajimehoshi
Copy link
Owner

Thanks.

notice significant choppiness when moving the window around

When you don't move the windows, does the application work as expected?

I couldn't find any documentation specifying otherwise

Specify an environment variable EBITEN_GRAPHICS_LIBRARY: https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2#hdr-Environment_variables

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

4 participants