stride3d / stride

Stride Game Engine (formerly Xenko)
https://stride3d.net
MIT License
6.33k stars 917 forks source link

Exception when switching window from fullscreen to windowed mode #1493

Open RedKorshun opened 1 year ago

RedKorshun commented 1 year ago

Release Type: Official Release

Version: 4.1.0.1734

Platform: Windows

Describe the bug Exception during switching to windowed mode.

To Reproduce Steps to reproduce the behavior:

  1. Create a new game project using default settings
  2. Add a SyncScript asset with the following code:
    
    using Stride.Engine;
    using Stride.Input;

public class FullScreenToggler : SyncScript { public override void Update() { if (Input.Keyboard.IsKeyDown(Keys.F)) { Game.Window.IsFullscreen = !Game.Window.IsFullscreen; } } }


3. Add a _ScriptComponent_ with the script from **step 2** to the Camera (or any other) entity.
4. Run the game using _**F5**_ hotkey (or in any other way).
5. Toggle fullscreen mode using the _**"F"**_ or _**"Alt+Enter"**_ hotkey.
6. Toggle windowed mode using exactly _**"F"**_ hotkey (handled by the script from **step 2**).

**Expected behavior**
Game window is switched to windowed mode like it was before **step 5** execution.

**Screenshots**
![ContentSerializationExceptions](https://user-images.githubusercontent.com/35571766/183230361-e8ef5eba-5d73-451b-874f-d06d6765d325.png)

**Log and callstacks**
- [ReproducingExceptionInfo.txt](https://github.com/stride3d/stride/files/9274255/ReproducingExceptionInfo.txt)
[ReproducingAfterExceptionConsole.log](https://github.com/stride3d/stride/files/9274277/ReproducingAfterExceptionConsole.log) - console log after you click on continue button (see screenshot above).
- [RealProjectExceptionInfo.txt](https://github.com/stride3d/stride/files/9274256/RealProjectExceptionInfo.txt)

**Additional context**
1. Using the _**"Alt+Enter"**_ hotkey  in **step 6** (for switching back from fullscreen mode to windowed) works as expected.
2. Even an empty scene with the only empty entity on it which has the _ScriptComponent_ with the script from **step 2** works as described.
3. There are also [similar issues](https://github.com/stride3d/stride/issues?q=is%3Aissue+is%3Aopen+label%3Abug+fullscreen), but they all belongs to a different kind of exception.
RedKorshun commented 1 year ago

Since "Alt+Enter" is working as expected this may not be a big issue, but there is a common practice to add a fullscreen toggler in game video options and it won't work there.

manio143 commented 1 year ago

I'll try to repro this later, but the exceptions you're showing are related to serialization which should have nothing to do with changing window mode, which either should be very worrying or there's something else going on here.

mmujic2 commented 1 year ago

I'm not so sure if related, but I get a very similar exception when I try to change window size while in fullscreen mode (different asset but same serializer can't be found).

manio143 commented 1 year ago

Ok, so I was able to repro this easily. Basically whenever things change for the GraphicsDevice such that it has to recreate everything it calls ResumeManager.OnReload which invokes reloading of all visual assets.

The exception message is thrown in the Reload callback of TextureContentSerializer L45, which performs a Load<Image> on the asset associated to the graphicsResource, but that graphics resource is of type Texture, which causes ContentManager to fail to retrieve it from cache of loaded objects and then try to deserialize it from the raw data, which fails again because there's no serializer compatible for a Texture that would return an Image.

While the underlying serialized data of a texture can be deserialized as an Image it seems that the way Reload callbacks are set up isn't taking all the details into account and would require deeper investigation to understand why this is happening.

The other issue is why this full reload happens in the first place. Looks like there may be an exception thrown during resizing from FullScreen in GraphicsDeviceManager.ChangeOrCreateDevice L1053. (Error 0x80004005 is a type of unspecified Windows error code)

External component has thrown an exception.
   at System.Runtime.InteropServices.Marshal.Release(IntPtr pUnk)
   at SharpDX.ComObject.SharpDX.IUnknown.Release()
   at SharpDX.ComObject.Dispose(Boolean disposing)
   at SharpDX.DisposeBase.CheckAndDispose(Boolean disposing)
   at Stride.Graphics.SwapChainGraphicsPresenter.OnDestroyed() in C:\stride\sources\engine\Stride.Graphics\Direct3D\SwapChainGraphicsPresenter.Direct3D.cs:line 184
   at Stride.Graphics.SwapChainGraphicsPresenter.set_IsFullScreen(Boolean value) in C:\stride\sources\engine\Stride.Graphics\Direct3D\SwapChainGraphicsPresenter.Direct3D.cs:line 116
   at Stride.Games.GraphicsDeviceManager.ChangeOrCreateDevice(Boolean forceCreate) in C:\stride\sources\engine\Stride.Games\GraphicsDeviceManager.cs:line 1053
RedKorshun commented 1 year ago

@manio143 , thanks for a quick feedback. I wonder if the Alt+Enter hotkey uses a different code, because it causes no exceptions. If so, is this the expected behavior?

manio143 commented 1 year ago

I was able to trace the Alt+Enter behavior to GameForm.cs and seems to be doing the exactly same thing, except in a different moment in the game loop (before calling any of the game's systems, it happens in WindowsMessageLoop that process OS messages before calling Game.Tick() to process another frame) - this may be a better moment to modify the graphics device, but I'm not fully sure why.

artems37rus commented 7 months ago

It'll work if you try it like this.

if (Input.Keyboard.IsKeyDown(Keys.E))
{
    if (Game.Window.IsFullscreen)
    {
        Game.Window.Visible = false;
        Game.Window.IsFullscreen = false;
        Game.Window.Visible = true;
    }
    else
    {
        Game.Window.SetSize(new Int2(1920, 1080));
        Game.Window.PreferredFullscreenSize = new Int2(1920, 1080);
        Game.Window.IsFullscreen = true;
    }
}
mmujic2 commented 7 months ago

It'll work if you try it like this.

if (Input.Keyboard.IsKeyDown(Keys.E))
{
    if (Game.Window.IsFullscreen)
    {
        Game.Window.Visible = false;
        Game.Window.IsFullscreen = false;
        Game.Window.Visible = true;
    }
    else
    {
        Game.Window.SetSize(new Int2(1920, 1080));
        Game.Window.PreferredFullscreenSize = new Int2(1920, 1080);
        Game.Window.IsFullscreen = true;
    }
}

That's a nice workaround, tested it myself and it works like a charm. I'll leave one more note here: when changing resolution in fullscreen, these two ways worked for me:

// First way. I prefer this one because it causes less flickering
Game.Window.Visible = false;

Game.Window.SetSize(resolution);
Game.Window.PreferredFullscreenSize = resolution;

Game.Window.IsFullscreen = true;
Game.Window.Visible = true;

// Second way (switching to windowed and back to fullscreen)
gameSettings.Window.Visible = false;
gameSettings.Window.IsFullscreen = false;
gameSettings.Window.Visible = true;

gameSettings.Window.SetSize(resolution);
gameSettings.Window.PreferredFullscreenSize = resolution;
gameSettings.Window.IsFullscreen = true;
Doprez commented 1 month ago

I have literally never seen this before from C#... When I have a breakpoint, the IsFullScreen changes properly to false which does not cause a crash.

When I make a conditional breakpoint to only trigger when the value is true it shows that GameWindow.IsFullscreen is set to true when it should be false.... https://github.com/Doprez/stride/blob/0e053a3b8873f49e9e0e5450a3dfad368a6ed042/sources/engine/Stride.Games/GameWindow.cs#L196

This seems to stem from my SyncScript class which is attempting to change the window similar to the original post

if (Input.IsKeyPressed(Keys.Tab))
{
    Game.Window.IsFullscreen = false
}

Is there a threading issue here or something? I dont understand why the bool would be so defiant lol.

Doprez commented 1 month ago

Ok so it seems like the property is being called twice for some weird reason and when it immediately goes from false to true in the same call seems to be where the crash happens.

image

Its a bit weird to step through, if you have a breakpoint that doesnt check if the property is being set to true then that breakpoint will act as iff the user clicked out of the window which disables fullscreen successfully and doesnt crash.