2015-10-20 134 views
6

我只是偶然发现了这个小恼人的行为,同时在示例程序中添加了全屏支持。DirectX11 Swapchain和窗口失去全屏状态

创建一个全屏窗口可以工作,但只要我在包含全屏窗口的输出中移动任何窗口(来自另一个应用程序),窗口就会自动切换回窗口。

有什么办法来防止这种行为(所以全屏窗口不会回到窗口)?

作为参考,这是一个小的独立示例(因此可以轻松地复制问题)。

此外,如果这是有用的,我在Windows 8.1上运行。

我已经试图改变WindowAssociationFlags和SwapChainFlags,都没有成功,像使用的,而不是丢弃FlipSequential

SharpDX.DXGI.Factory2 factory = new SharpDX.DXGI.Factory2(); 
SharpDX.DXGI.Adapter adapter = factory.GetAdapter(0); 

var renderForm1 = new RenderForm("Form 1"); 
factory.MakeWindowAssociation(renderForm1.Handle, SharpDX.DXGI.WindowAssociationFlags.IgnoreAll); 

Device device = new Device(adapter, DeviceCreationFlags.BgraSupport); 

SharpDX.DXGI.SwapChainDescription sd = new SharpDX.DXGI.SwapChainDescription() 
{ 
    BufferCount = 2, 
    ModeDescription = new SharpDX.DXGI.ModeDescription(0, 0, new SharpDX.DXGI.Rational(50, 1), SharpDX.DXGI.Format.R8G8B8A8_UNorm), 
    IsWindowed = true, 
    OutputHandle = renderForm1.Handle, 
    SampleDescription = new SharpDX.DXGI.SampleDescription(1,0), 
    SwapEffect = SharpDX.DXGI.SwapEffect.Discard, 
    Usage = SharpDX.DXGI.Usage.RenderTargetOutput, 
    Flags = SharpDX.DXGI.SwapChainFlags.None 
}; 

var swapChain1 = new SharpDX.DXGI.SwapChain(factory, device, sd); 

renderForm1.Left = 1922; //Just hardcoded here to move window to second screen 
renderForm1.Width = 1920; 
renderForm1.Height = 1080; 
renderForm1.FormBorderStyle = FormBorderStyle.None; 

swapChain1.SetFullscreenState(true, null); 
swapChain1.ResizeBuffers(2, 1920, 1080, SharpDX.DXGI.Format.R8G8B8A8_UNorm, SharpDX.DXGI.SwapChainFlags.AllowModeSwitch); 

var resource = Texture2D.FromSwapChain<Texture2D>(swapChain1, 0); 
var renderView = new RenderTargetView(device, resource); 

RenderLoop.Run(renderForm1,() => 
{ 
    device.ImmediateContext.ClearRenderTargetView(renderView, new SharpDX.Color4(1, 0, 0, 1)); 
    swapChain1.Present(1, SharpDX.DXGI.PresentFlags.None); 
}); 

编辑: 我也尝试了C++的样品(刚刚拍摄的DirectX11基础教程微软和添加全屏切换),这导致了相同的行为,所以这不是SharpDX的特定问题。

我查看了消息循环,一旦发生这种情况,第一个全屏模式将变回窗口状态,并且我收到一条WM_DISPLAYCHANGE消息)。

回答

3

这听起来像预期的行为。如果您有全屏“独占”模式交换链并且关联的窗口失去焦点,系统会自动将应用程序从全屏模式切换回窗口模式,设计为

对于单个显示器,只要您的应用程序的窗口大小足以填满显示屏,它就会工作。用户不能使用鼠标来改变窗口的焦点,而且它需要像ALT + TAB那样来切换焦点。

对于多个显示器,这是一个真正的问题。如果您在另一台显示器上单击另一个窗口,应用程序将失去焦点,并且全屏模式会再次切换。还有一些限制会阻止您在多台显示器上设置全屏“独占”模式。

此外,在Windows Vista或更高版本中,'独占'模式的概念是一种幻想:无论如何GPU总是共享的。无论是全屏还是窗口化交换链,“重点”应用都会获得优先权。

你为一个完整的画面风格体验三种选择一个Windows桌面应用程序:

  1. 使用传统的全屏幕“独占”模式,大小以填充显示,与设置显示模式沿窗这可能不是用户通常为Windows设置的内容。这里你有IsWindowed = false
  2. 您设置窗口大小以填满整个显示屏(即最大化)。您可以使用窗口样式来确保该窗口没有框架,从而获得全屏风格的体验(WS_POPUP)。这里您有IsWindowed = true,您应该确保设置DXGI_MWA_NO_ALT_ENTER以避免DXGI尝试带您使用1个案例。
  3. 您可以使用与IsWindowed = true一样的2和无边界窗口大小来匹配屏幕,但是您将显示模式更改为系统默认值以外的值。这通常被称为“假全屏”。每次退出应用程序时,显示模式都会改回。

1所有问题都有我们刚才描述的多任务和焦点问题。 2和3允许系统通知和其他弹出窗口显示在游戏中,而不是强制模式切换。 2和3在多显示器设置中也可以更好地工作,您可以在一台显示器上播放游戏,并在另一台显示器上使用其他应用程序。对于多任务处理的大多数人来说,喜欢具有边框边框的经典窗口风格。

Windows Store UWP全屏模式的概念基本上与上面的2相同。您无法使用UWP更改显示模式。

调试全屏设置非常具有挑战性。使用多个监视器,2和3可以在另一个屏幕上与调试器一起工作。对于真正的全屏独占模式,真正唯一的选择是使用另一台PC的远程调试。

1和3的另一个问题是,您可以将显示模式设置为不会与显示器同步,从而使用户无法使用UI且无法退出系统。理想情况下,通过正确的驱动程序设置,DXGI枚举列表不包含不受支持的模式,但它是需要注意的。因此,用于选择显示模式的用户界面应该有一个超时时间,并且如果显示模式在将来的某个时间点未能同步,则应该确保有一种合理的方法来用键盘中止应用程序。使用现有的显示模式,就像我们在上面2中做的那样,始终是最安全的选择。

使用上述全屏独占模式(1)的主要原因是试图获得后置缓冲器/前置缓冲器的'翻转'而不是'blit'。对于大多数现代系统来说,这是微不足道的性能差异。经历使用它的痛苦的另一个原因是SLI/Crossfire多GPU渲染转向单个显示器。还需要其他一些优化才能真正实现该场景的运行,并且它非常适合。您应该查看详细信息的供应商优化指南。

大多数现代游戏默认使用假全屏而不是全屏“独家”模式。它们提供了使用真正的窗口模式的能力,因为许多用户希望能够在播放时进行多任务(如在线查看提示,使用即时消息或外部语音聊天等)。希望支持针对SLI/Crossfire调谐的高性能游戏的AAA Windows桌面游戏将提供全屏“独家”模式,但这需要一些工作才能充分发挥作用,并且需要比DXGI代码更多的工作。

DXGI OverviewDirectX Graphics Infrastructure (DXGI): Best Practices

+0

感谢您的好奇心(思考windows8.1或windows 10),你会遇到同样的行为使用DirectX9全屏(不是我打算回滚到它,但好奇它)吗? – catflier

+0

我认为在模拟Direct3D 9全屏模式方面存在一些怪癖 - 请记住,“丢失的设备”方案在Windows Vista +上并不存在,因此此行为也被模拟 - 但我相信它基本上同样的情况。 –

+0

默认情况下,在dx9中它实际上更糟糕,因为只要它失去了焦点,它就会隐藏自身,但至少它可以很容易被欺骗,所以现在似乎唯一的方法将是共享资源回到它... – catflier

2

多次尝试和考验后,这里是我用了不同的解决方法,但并不理想,但一切都弄好比获得一个模式改变更好。

1 /强制光标在全屏窗口的中间,用键盘快捷键重新获得控制权。 这并不理想,因为我们的部分运行时我们无法做任何事情,但至少可以防止意外的“灾难点击”。它也不妨碍键盘交互。

2 /使用具有共享纹理的DX9渲染器。DX9 Swapchain可以将其父窗口设置为桌面,因此在移动到其他位置时不会失去焦点。 将焦点放在顶部的窗口显示移动时可见的小边框,但这比失去一切更容易接受。 没有将来的证据,但猜测将保持实际一段时间。

3 /留在Windows 7和禁用DWM服务:

在Windows 8不工作了,但在我的使用情况,因为大多数媒体公司我工作仍然在Windows 7中,它一直是一个有效的解决方案至少5至10年。

4 /强制DX11窗口上前景

基本上连续地调用SetForegroundWindow避免另一个窗口取得焦点。

5 /在演示级别阻止模式切换。

由于我的应用我介绍的时候出现,我用下面的程序(之前叫至今)访问

- 获得前台窗口句柄(使用GetForegroundWindow),如果前景手柄是我们的全屏窗口,只是打电话像平常一样。

如果前景句柄不是我们的全屏窗口,请执行以下操作。请注意,不需要可视性检查,因为即使是不可见的重叠窗口也会导致全屏丢失! (严重的是,这太糟糕了......)

- 如果我们的前景窗口与显示器重叠,请进行验证: 请致电GetWindowRect获取边界,并与显示器位置进行交集。

或者,使用DXGI_PRESENT_TEST标志调用交换链上的存在。如果窗口是重叠的,当前呼叫将返回DXGI_STATUS_OCCLUDED

如果窗口重叠,或者隐藏,或在另一台显示器移动它(任何地方,所以它不重叠): ShowWindowSetWindowPos是aperfect适合这个任务。

在循环中重复该测试呈现调用,直到它不返回被遮挡的状态(这很重要,因为Windows可能没有立即处理消息);一旦遮挡标志消失,像平常一样调用Present。