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:
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()
.
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);
}