Sign in to follow this  

[SlimDX] Multi-Monitor Multi-Window fullscreen troubles

This topic is 2102 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, after having tried to implement the feature to switch multiple .NET Form windows to fullscreen mode on different monitors simultaneously without success, I've now ported the DirectX SDK MultiMon10 sample to SlimDX to see if this is an issue caused by my code or if it could be caused by using Forms + DXGI or a bug in SlimDX.
(I get the exact same behaviour for the ported sample code as it happens in my engine)

Here's the code of the ported MultiMon10 sample, I'm using D3D11 here but I also tried it with D3D10, it didn't make a difference for me.
I tried to resemble the original sample's code as close as possible, but only using C# & SlimDX ... the only thing that I added for debugging purposes is a Debug output message when the WM_SIZE message is dispatched to a window, since this is what is an indication for an ongoing fullscreen transition.

The output for the original C++ DxSDK sample looks like follows on my 2 monitor workstation:
[CODE]
resize: #0 1680 x 1050
resize: #1 1920 x 1080
resize: #0 834 x 497
resize: #1 954 x 512
[/CODE]
... the first two resize messages are the fullscreen switch ... the other two are caused by closing the application and therefore leaving the fullscreen modes again.

The debug messages for the C# / SlimDX port of the sample look like follows:
[CODE]
resize: #0 1680 x 1050
resize: #1 1920 x 1080
resize: #0 834 x 497
resize: #1 954 x 512
resize: #0 1680 x 1050
resize: #1 1920 x 1080
resize: #0 834 x 497
resize: #1 954 x 512
[/CODE]
... as you can see after the first fullscreen resize messages DXGI seems to fall back to windowed mode again and tried to switch to fullscreen for another time until it finally falls back to windowed and stays like that.

My first suspicion was that this could be caused by some window focus events that could occur after the fullscreen mode has been entered, but I debugged this and it seems that the same happens for the C++ version as well, also I tried several methods to avoid the windows to gain/lose focus, but the issue stayed the same.

Does anyone have an idea what could be causing this ?
Could this be a problem of using .NET Forms instead of plain Win32 Windows ?
Or should it work with .NET Forms no matter what, and this might be a SlimDX bug ?

PS: I'll try to use PInvoke to create plain Win32 windows tomorrow to try confirm that this is related to .NET Forms, I'll update the status of my investigations tomorrow.

Thanks for any hints in the meantime

[CODE]
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using SlimDX;
using SlimDX.DXGI;
using SlimDX.Direct3D11;
using SlimDX.Windows;
using Device = SlimDX.Direct3D11.Device;
using Resource = SlimDX.Direct3D11.Resource;
namespace MultiMon11
{
[Flags]
enum WindowStyles : uint
{
WS_OVERLAPPED = 0x00000000,
WS_POPUP = 0x80000000,
WS_CHILD = 0x40000000,
WS_MINIMIZE = 0x20000000,
WS_VISIBLE = 0x10000000,
WS_DISABLED = 0x08000000,
WS_CLIPSIBLINGS = 0x04000000,
WS_CLIPCHILDREN = 0x02000000,
WS_MAXIMIZE = 0x01000000,
WS_BORDER = 0x00800000,
WS_DLGFRAME = 0x00400000,
WS_VSCROLL = 0x00200000,
WS_HSCROLL = 0x00100000,
WS_SYSMENU = 0x00080000,
WS_THICKFRAME = 0x00040000,
WS_GROUP = 0x00020000,
WS_TABSTOP = 0x00010000,
WS_MINIMIZEBOX = 0x00020000,
WS_MAXIMIZEBOX = 0x00010000,
WS_CAPTION = WS_BORDER | WS_DLGFRAME,
WS_TILED = WS_OVERLAPPED,
WS_ICONIC = WS_MINIMIZE,
WS_SIZEBOX = WS_THICKFRAME,
WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW,
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
WS_CHILDWINDOW = WS_CHILD,
//Extended Window Styles
WS_EX_DLGMODALFRAME = 0x00000001,
WS_EX_NOPARENTNOTIFY = 0x00000004,
WS_EX_TOPMOST = 0x00000008,
WS_EX_ACCEPTFILES = 0x00000010,
WS_EX_TRANSPARENT = 0x00000020,
//#if(WINVER >= 0x0400)
WS_EX_MDICHILD = 0x00000040,
WS_EX_TOOLWINDOW = 0x00000080,
WS_EX_WINDOWEDGE = 0x00000100,
WS_EX_CLIENTEDGE = 0x00000200,
WS_EX_CONTEXTHELP = 0x00000400,
WS_EX_RIGHT = 0x00001000,
WS_EX_LEFT = 0x00000000,
WS_EX_RTLREADING = 0x00002000,
WS_EX_LTRREADING = 0x00000000,
WS_EX_LEFTSCROLLBAR = 0x00004000,
WS_EX_RIGHTSCROLLBAR = 0x00000000,
WS_EX_CONTROLPARENT = 0x00010000,
WS_EX_STATICEDGE = 0x00020000,
WS_EX_APPWINDOW = 0x00040000,
WS_EX_OVERLAPPEDWINDOW = (WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE),
WS_EX_PALETTEWINDOW = (WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST),
//#endif /* WINVER >= 0x0400 */
//#if(WIN32WINNT >= 0x0500)
WS_EX_LAYERED = 0x00080000,
//#endif /* WIN32WINNT >= 0x0500 */
//#if(WINVER >= 0x0500)
WS_EX_NOINHERITLAYOUT = 0x00100000, // Disable inheritence of mirroring by children
WS_EX_LAYOUTRTL = 0x00400000, // Right to left mirroring
//#endif /* WINVER >= 0x0500 */
//#if(WIN32WINNT >= 0x0500)
WS_EX_COMPOSITED = 0x02000000,
WS_EX_NOACTIVATE = 0x08000000
//#endif /* WIN32WINNT >= 0x0500 */
}
class DEVICE_OBJECT
{
public int Ordinal;
public Device pDevice;
};
class WINDOW_OBJECT
{
public IntPtr hWnd;
public Adapter pAdapter;
public Output pOutput;
public SwapChain pSwapChain;
public DEVICE_OBJECT pDeviceObj;
public RenderTargetView pRenderTargetView;
public DepthStencilView pDepthStencilView;
public int Width;
public int Height;
public AdapterDescription AdapterDesc;
public OutputDescription OutputDesc;
public FormBase Form;
};
class ADAPTER_OBJECT
{
public Adapter pDXGIAdapter;
public List<Output> DXGIOutputArray = new List<Output>();
};
public class FormBase : RenderForm
{
public const uint WM_SIZE = 0x0005;
public static int InitialWidth;
public static int InitialHeight;
public static int InitialX;
public static int InitialY;
public FormBase()
{
WmSize = (f, lo, hi) =>
{
for (int i = 0; i < Program.g_WindowObjects.Count; i++)
{
WINDOW_OBJECT pObj = Program.g_WindowObjects.ElementAt(i);
if (Handle == pObj.hWnd)
{
// Cleanup the views
pObj.pRenderTargetView.Dispose();
pObj.pDepthStencilView.Dispose();
//RECT rcCurrentClient;
//GetClientRect(hWnd, &rcCurrentClient);
Rectangle rcCurrentClient = ClientRectangle;
SwapChainDescription Desc = pObj.pSwapChain.Description;
pObj.pSwapChain.ResizeBuffers(Desc.BufferCount,
rcCurrentClient.Right, // passing in 0 here will automatically calculate the size from the client rect
rcCurrentClient.Bottom, // passing in 0 here will automatically calculate the size from the client rect
Desc.ModeDescription.Format,
SwapChainFlags.None);
pObj.Width = rcCurrentClient.Right;
pObj.Height = rcCurrentClient.Bottom;
// recreate the views
Program.CreateViewsForWindowObject(pObj);
}
}
};
}
protected override CreateParams CreateParams
{
get
{
CreateParams ret = base.CreateParams;
ret.Style = (int)(WindowStyles.WS_OVERLAPPEDWINDOW & ~(WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_THICKFRAME));
ret.X = InitialX;
ret.Y = InitialY;
ret.Width = InitialWidth;
ret.Height = InitialHeight;
return ret;
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_SIZE && WmSize != null)
{
int lo = m.LParam.ToInt32() & 0xffff;
int hi = m.LParam.ToInt32() >> 16;
WmSize(this, lo, hi);
System.Diagnostics.Debug.WriteLine("resize: #{0} {1} x {2}", WindowIndex, lo, hi);
return;
}
base.WndProc(ref m);
}
public int WindowIndex;
public Action<FormBase, int, int> WmSize { get; set; }
}
class Program
{
static void EnumerateAdapters()
{
int ac = g_pDXGIFactory.GetAdapterCount();
for (int i = 0; i < ac; i++)
{
ADAPTER_OBJECT pAdapterObj = new ADAPTER_OBJECT();
pAdapterObj.pDXGIAdapter = g_pDXGIFactory.GetAdapter(i);
// get the description of the adapter
AdapterDescription AdapterDesc = pAdapterObj.pDXGIAdapter.Description;
// Enumerate outputs for this adapter
EnumerateOutputs(pAdapterObj);
// add the adapter to the list
if (pAdapterObj.DXGIOutputArray.Count > 0)
g_AdapterArray.Add(pAdapterObj);
}
}
static List<ADAPTER_OBJECT> g_AdapterArray = new List<ADAPTER_OBJECT>();
static void EnumerateOutputs(ADAPTER_OBJECT pAdapterObj)
{
int oc = pAdapterObj.pDXGIAdapter.GetOutputCount();
for (int i = 0; i < oc; i++)
{
Output pOutput = pAdapterObj.pDXGIAdapter.GetOutput(i);
// get the description
OutputDescription OutputDesc = pOutput.Description;
// only add outputs that are attached to the desktop
// TODO: AttachedToDesktop seems to be always 0
/*if( !OutputDesc.AttachedToDesktop )
{
pOutput.Release();
continue;
}*/
pAdapterObj.DXGIOutputArray.Add(pOutput);
}
}
private static bool g_bFullscreen = false;
static void CreateMonitorWindows()
{
for (int a = 0; a < g_AdapterArray.Count; a++)
{
ADAPTER_OBJECT pAdapter = g_AdapterArray.ElementAt(a);
for (int o = 0; o < pAdapter.DXGIOutputArray.Count; o++)
{
Output pOutput = pAdapter.DXGIOutputArray.ElementAt(o);
OutputDescription OutputDesc = pOutput.Description;
int X = OutputDesc.DesktopBounds.Left;
int Y = OutputDesc.DesktopBounds.Top;
int Width = OutputDesc.DesktopBounds.Right - X;
int Height = OutputDesc.DesktopBounds.Bottom - Y;
WINDOW_OBJECT pWindowObj = new WINDOW_OBJECT();
if (g_bFullscreen)
{
//pWindowObj.hWnd = CreateWindow( g_szWindowClass,
// g_szWindowedName,
// WS_POPUP,
// X,
// Y,
// Width,
// Height,
// NULL,
// 0,
// g_WindowClass.hInstance,
// NULL );
}
else
{
X += 100;
Y += 100;
Width /= 2;
Height /= 2;
FormBase.InitialX = X;
FormBase.InitialY = Y;
FormBase.InitialWidth = Width;
FormBase.InitialHeight = Height;

FormBase form = new FormBase();
form.WindowIndex = o;
form.Show();
pWindowObj.hWnd = form.Handle;
pWindowObj.Form = form;
//DWORD dwStyle = WS_OVERLAPPEDWINDOW & ~( WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME );
//pWindowObj.hWnd = CreateWindow( g_szWindowClass,
// g_szWindowedName,
// dwStyle,
// X,
// Y,
// Width,
// Height,
// NULL,
// 0,
// g_WindowClass.hInstance,
// NULL );
}
// set width and height
pWindowObj.Width = Width;
pWindowObj.Height = Height;
// add this to the window object array
g_WindowObjects.Add(pWindowObj);
}
}
}
static List<DEVICE_OBJECT> g_DeviceArray = new List<DEVICE_OBJECT>();
public static List<WINDOW_OBJECT> g_WindowObjects = new List<WINDOW_OBJECT>();
static void CreateDevicePerAdapter(DriverType DriverType = DriverType.Hardware)
{
int iWindowObj = 0;
for (int a = 0; a < g_AdapterArray.Count; a++)
{
ADAPTER_OBJECT pAdapterObj = g_AdapterArray.ElementAt(a);
Adapter pAdapter = null;
if (DriverType.Hardware == DriverType)
pAdapter = pAdapterObj.pDXGIAdapter;
// Create a device for this
Device pd3dDevice = new Device(pAdapter, DeviceCreationFlags.Debug);
DEVICE_OBJECT pDeviceObj = new DEVICE_OBJECT();
pDeviceObj.pDevice = pd3dDevice;
// add the device
pDeviceObj.Ordinal = g_DeviceArray.Count;
g_DeviceArray.Add(pDeviceObj);
// Init stuff needed for the device
//OnD3D10CreateDevice( pDeviceObj );
// go through the outputs and set the device, adapter, and output
for (int o = 0; o < pAdapterObj.DXGIOutputArray.Count; o++)
{
Output pOutput = pAdapterObj.DXGIOutputArray.ElementAt(o);
WINDOW_OBJECT pWindowObj = g_WindowObjects.ElementAt(iWindowObj);
pWindowObj.pDeviceObj = pDeviceObj;
pWindowObj.pAdapter = pAdapter;
pWindowObj.pOutput = pOutput;
iWindowObj++;
}
}
}
static void CreateSwapChainPerOutput()
{
for (int i = 0; i < g_WindowObjects.Count; i++)
{
WINDOW_OBJECT pWindowObj = g_WindowObjects.ElementAt(i);
// get the dxgi device
SlimDX.DXGI.Device pDXGIDevice = new SlimDX.DXGI.Device(pWindowObj.pDeviceObj.pDevice);
// create a swap chain
SwapChainDescription SwapChainDesc = new SwapChainDescription();
ModeDescription BufferDesc = new ModeDescription();
BufferDesc.Width = pWindowObj.Width;
BufferDesc.Height = pWindowObj.Height;
BufferDesc.RefreshRate = new Rational(60, 1);
BufferDesc.Format = Format.R8G8B8A8_UNorm;
BufferDesc.ScanlineOrdering = DisplayModeScanlineOrdering.Unspecified;
BufferDesc.Scaling = DisplayModeScaling.Unspecified;
SwapChainDesc.ModeDescription = BufferDesc;
SwapChainDesc.SampleDescription = new SampleDescription(1, 0);
SwapChainDesc.Usage = Usage.RenderTargetOutput;
SwapChainDesc.BufferCount = 3;
SwapChainDesc.OutputHandle = pWindowObj.hWnd;
SwapChainDesc.IsWindowed = (g_bFullscreen == false);
SwapChainDesc.SwapEffect = SwapEffect.Discard;
SwapChainDesc.Flags = SwapChainFlags.None;
pWindowObj.pSwapChain = new SwapChain(g_pDXGIFactory, pDXGIDevice, SwapChainDesc);
pDXGIDevice.Dispose();
pDXGIDevice = null;
CreateViewsForWindowObject(pWindowObj);
}
}
public static void CreateViewsForWindowObject(WINDOW_OBJECT pWindowObj)
{
// get the backbuffer
Texture2D pBackBuffer = null;
pBackBuffer = Resource.FromSwapChain<Texture2D>(pWindowObj.pSwapChain, 0);
// get the backbuffer desc
Texture2DDescription BBDesc = pBackBuffer.Description;
// create the render target view
RenderTargetViewDescription RTVDesc = new RenderTargetViewDescription();
RTVDesc.Format = BBDesc.Format;
RTVDesc.Dimension = RenderTargetViewDimension.Texture2D;
RTVDesc.MipSlice = 0;
pWindowObj.pRenderTargetView = new RenderTargetView(pWindowObj.pDeviceObj.pDevice, pBackBuffer, RTVDesc);
pBackBuffer.Dispose();
pBackBuffer = null;
// Create depth stencil texture
Texture2D pDepthStencil = null;
Texture2DDescription descDepth = new Texture2DDescription();
descDepth.Width = pWindowObj.Width;
descDepth.Height = pWindowObj.Height;
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = Format.D16_UNorm;
descDepth.SampleDescription = new SampleDescription(1, 0);
descDepth.Usage = ResourceUsage.Default;
descDepth.BindFlags = BindFlags.DepthStencil;
descDepth.CpuAccessFlags = CpuAccessFlags.None;
descDepth.OptionFlags = ResourceOptionFlags.None;
pDepthStencil = new Texture2D(pWindowObj.pDeviceObj.pDevice, descDepth);
// Create the depth stencil view
DepthStencilViewDescription descDSV = new DepthStencilViewDescription();
descDSV.Format = descDepth.Format;
descDSV.Dimension = DepthStencilViewDimension.Texture2D;
descDSV.MipSlice = 0;
pWindowObj.pDepthStencilView = new DepthStencilView(pWindowObj.pDeviceObj.pDevice, pDepthStencil, descDSV);
pDepthStencil.Dispose();
// get various information
if (pWindowObj.pAdapter != null)
pWindowObj.AdapterDesc = pWindowObj.pAdapter.Description;
if (pWindowObj.pOutput != null)
pWindowObj.OutputDesc = pWindowObj.pOutput.Description;
}
private static Factory g_pDXGIFactory;
static Color4[] colors = new[]
{
new Color4(1,0,0),
new Color4(0,1,0),
new Color4(0,0,1)
};
static void RenderToAllMonitors()
{
// Clear them all
for (int w = 0; w < g_WindowObjects.Count; w++)
{
WINDOW_OBJECT pWindowObj = g_WindowObjects.ElementAt(w);
// set the render target
pWindowObj.pDeviceObj.pDevice.ImmediateContext.OutputMerger.SetTargets(pWindowObj.pDepthStencilView, pWindowObj.pRenderTargetView);
// set the viewport
Viewport Viewport = new Viewport();
Viewport.X = 0;
Viewport.Y = 0;
Viewport.Width = pWindowObj.Width;
Viewport.Height = pWindowObj.Height;
Viewport.MinZ = 0.0f;
Viewport.MaxZ = 1.0f;
pWindowObj.pDeviceObj.pDevice.ImmediateContext.Rasterizer.SetViewports(Viewport);
// Call the render function
pWindowObj.pDeviceObj.pDevice.ImmediateContext.ClearRenderTargetView(pWindowObj.pRenderTargetView, colors[w]);
}
}
static void PresentToAllMonitors()
{
for (int w = 0; w < g_WindowObjects.Count; w++)
{
WINDOW_OBJECT pWindowObj = g_WindowObjects.ElementAt(w);
pWindowObj.pSwapChain.Present(0, 0);
}
}
static void Main(string[] args)
{
g_pDXGIFactory = new Factory();
EnumerateAdapters();
CreateMonitorWindows();
CreateDevicePerAdapter();
CreateSwapChainPerOutput();
MessagePump.Run(g_WindowObjects.First().Form, () =>
{
RenderToAllMonitors();
PresentToAllMonitors();
});
}
}
}
[/CODE]

Share this post


Link to post
Share on other sites
[size=5][Update][/size]

As said I've now tried to create the windows in the exact same way the DX sample does it by using PInvoke for all necessary W32 calls ... the behaviour stays exactly the same as when using .NET Forms.
So I guess the issue might be related to either SlimDX or the .NET runtime is doing something which causes problems ?!

I'm kind of lost here right now, what else could I try to narrow the problem down ?

Share this post


Link to post
Share on other sites
Turns out that the only thing that was different between the original code and my SlimDX port was at fault for the issue [img]http://public.gamedev.net//public/style_emoticons/default/wink.png[/img]

To run the message loop for the application I simply used the SlimDX message pump on the first window...

[CODE]
MessagePump.Run(g_WindowObjects.First().Form, () =>
{
RenderToAllMonitors();
PresentToAllMonitors();
});
[/CODE]

... but this seems to have interfered with something that DXGI is doing during the fullscreen transition of this window.
I replaced the above code with a separate message loop (just like in the original sample code) and after that change it worked as expected.

I've put the whole code of the port to my bitbucket account so if anyone happens to need something like that, feel free to go there and grab it.

[url="https://bitbucket.org/stoneMcClane/slimdx_multimon11"]https://bitbucket.or...imdx_multimon11[/url]

Share this post


Link to post
Share on other sites

This topic is 2102 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this