Search code examples
c#fullscreendirectx-11sharpdx

Setting SharpDX.Windows.RenderForm.IsFullscreen before RenderLoop.Run corrupts window


I observed a weird and buggy behavior in SharpDX when implementing fullscreen and resolution switching code. Whenever I set RenderForm.IsFullscreen to true before I call RenderLoop.Run(), and return to windowed mode later on, the window is corrupted:

Corrupted SharpDX window Notice how it's missing an icon, and my desktop background and the console shine through. Also, I can't click on it, any click is redirected to the underlying windows. Even while the RenderLoop is still run clearing the background color to dark blue, just a grayish and too small area is filled. (I can workaround this as a user by maximizing the corrupted window via the taskbar button. Simply resizing doesn't help.)

Here's my complete test code:

using System;
using System.Drawing;
using System.Windows.Forms;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Windows;

internal class Program
{
    private static RenderForm                _window;
    private static SharpDX.Direct3D11.Device _device;
    private static DeviceContext             _deviceContext;
    private static SwapChain                 _swapChain;
    private static RenderTargetView          _renderTargetView;

    private static bool Fullscreen
    {
        get
        {
            return _window.IsFullscreen;
        }
        set
        {
            // Do not resize multiple times by preventing the resized event.
            _window.UserResized -= _window_UserResized;

            _window.IsFullscreen = value;

            _swapChain.SetFullscreenState(_window.IsFullscreen, null);
            // Resize the window when returning to windowed mode to fit the most recent resolution.
            if (!_window.IsFullscreen)
            {
                _window.ClientSize = _resolution;
            }

            // Allow new resize events.
            _window.UserResized += _window_UserResized;
        }
    }

    private static Size _resolution;
    private static Size Resolution
    {
        get
        {
            return _resolution;
        }
        set
        {
            _resolution = value;
            Debug.WriteLine("Setting resolution: " + _resolution.ToString());

            // Do not resize multiple times by preventing the resized event.
            _window.UserResized -= _window_UserResized;

            // Resize the window in windowed mode.
            if (!_window.IsFullscreen)
            {
                _window.ClientSize = _resolution;
            }

            // Dispose existing objects.
            Utilities.Dispose(ref _renderTargetView);

            // Resize the back buffer.
            _swapChain.ResizeBuffers(0, _resolution.Width, _resolution.Height, Format.R8G8B8A8_UNorm, SwapChainFlags.AllowModeSwitch);

            // Create the new and resized render target and set it.
            using (Texture2D backBuffer = _swapChain.GetBackBuffer<Texture2D>(0))
            {
                _renderTargetView = new RenderTargetView(_device, backBuffer);
            }
            _deviceContext.OutputMerger.SetRenderTargets(_renderTargetView);

            // Resize the swap chain buffers to set the fullscreen resolution.
            ModeDescription bufferDescription = new ModeDescription()
            {
                Width = _resolution.Width,
                Height = _resolution.Height,
                RefreshRate = new Rational(60, 1),
                Format = Format.R8G8B8A8_UNorm
            };
            _swapChain.ResizeTarget(ref bufferDescription);

            // Allow new resize events.
            _window.UserResized += _window_UserResized;
        }
    }

    private static void Main(string[] args)
    {
        _window = new RenderForm();
        _window.KeyDown += _window_KeyDown;
        _window.IsFullscreen = true;

        // Set default resolution.
        _resolution = new Size(800, 600);

        // Describe the swap chain buffer mode.
        ModeDescription bufferDescription = new ModeDescription()
        {
            Width = _resolution.Width,
            Height = _resolution.Height,
            RefreshRate = new Rational(60, 1),
            Format = Format.R8G8B8A8_UNorm
        };
        // Describe the swap chain.
        SwapChainDescription swapChainDescription = new SwapChainDescription()
        {
            ModeDescription = bufferDescription,
            SampleDescription = new SampleDescription(1, 0),
            Usage = Usage.RenderTargetOutput,
            BufferCount = 1,
            OutputHandle = _window.Handle,
            IsWindowed = !_window.IsFullscreen,
            Flags = SwapChainFlags.AllowModeSwitch // Allows other fullscreen resolutions than native one.
        };
        // Create the device with the swap chain.
        SharpDX.Direct3D11.Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, swapChainDescription, out _device, out _swapChain);
        _deviceContext = _device.ImmediateContext;

        // Set the resolution to run the code which resizes the internal buffers.
        Resolution = _resolution;

        _window.UserResized += _window_UserResized;
        RenderLoop.Run(_window, Loop);
    }

    private static void Loop()
    {
        _deviceContext.ClearRenderTargetView(_renderTargetView, new Color4(0.4f, 0.5f, 0.6f, 1f));
        _swapChain.Present(1, 0);
    }

    private static void _window_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.F)
        {
            Fullscreen = !Fullscreen;
        }
    }

    private static void _window_UserResized(object sender, EventArgs e)
    {
        Resolution = _window.ClientSize;
    }
}

I can rewrite the code to remember the fullscreen setting as a boolean and not use _window.IsFullscreen directly, then check if I'm in the first iteration of the rendering loop and set the fullscreen state in there by checking the boolean, but that's just awkward.

When I do so, the window animates onto the desktop as if it was newly created. It looks like SharpDX messes up window styles here.

Is this a SharpDX bug or am I doing something wrong? I did not find information that I should not set IsFullscreen to true before calling RenderLoop.Run().


Solution

  • Analyzing the window styles set, the corrupted window was missing WS_VISIBLE.

    So, I simply forgot to Show() the render form. It turned out I have to do this even before analyzing any D3D stuff, or the window would become corrupted again.

    Here's the fixed Main() method:

    private static void Main(string[] args)
    {
        _window = new RenderForm();
        _window.KeyDown += _window_KeyDown;
        _window.IsFullscreen = true;
        _window.Show(); // Do not forget this or window styles may break
    
        // Set default resolution.
        _resolution = new Size(800, 600);
    
        // Describe the swap chain buffer mode.
        ModeDescription bufferDescription = new ModeDescription()
        {
            Width = _resolution.Width,
            Height = _resolution.Height,
            RefreshRate = new Rational(60, 1),
            Format = Format.R8G8B8A8_UNorm
        };
        // Describe the swap chain.
        SwapChainDescription swapChainDescription = new SwapChainDescription()
        {
            ModeDescription = bufferDescription,
            SampleDescription = new SampleDescription(1, 0),
            Usage = Usage.RenderTargetOutput,
            BufferCount = 1,
            OutputHandle = _window.Handle,
            IsWindowed = !_fullscreen,
            Flags = SwapChainFlags.AllowModeSwitch // Allows other fullscreen resolutions than native one.
        };
        // Create the device with the swap chain.
        SharpDX.Direct3D11.Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None,
            swapChainDescription, out _device, out _swapChain);
        _deviceContext = _device.ImmediateContext;
    
        // Set the resolution to run the code which resizes the internal buffers.
        Resolution = _resolution;
    
        RenderLoop.Run(_window, Loop);
    }