DirectX11 Swapchain和窗口失去全屏状态

时间:2015-10-20 10:17:39

标签: directx-11 sharpdx dxgi

我只是偶然发现了这个令人烦恼的行为,同时在示例程序中添加了全屏支持。

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

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

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

如果这很有用,我也会在Windows 8.1上运行。

我已经尝试更改WindowAssociationFlags和SwapChainFlags,两者都没有成功,与使用FlipSequential而不是Discard相同

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 ++示例(刚刚从Microsoft获取DirectX11基础教程并添加了全屏切换),这导致了相同的行为,因此这不是SharpDX特定的问题。

我查看了消息循环,一旦发生这种情况,首先将全屏模式更改回窗口,然后收到WM_DISPLAYCHANGE消息。

3 个答案:

答案 0 :(得分: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相同的操作,并且无边框窗口的大小与屏幕相匹配,但您将显示模式更改为系统默认值以外的其他模式。这通常被称为“假全屏”。退出应用程序时,显示模式会更改回来。
  4. 1具有我们刚刚描述的多任务和焦点的所有问题。 2和3允许系统通知和其他弹出窗口显示在游戏上而不强制模式切换。在多显示器设置中,2和3也可以更好地工作,您可以在一个显示器上玩游戏并在另一个显示器上使用其他应用程序。对于多任务处理,大多数人更喜欢具有框架边框的经典窗户。

      

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

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

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

      

    使用上面的全屏独占模式(1)的主要原因是尝试获得后退缓冲区/前缓冲区的“翻转”而不是“blit”。对于大多数现代系统而言,这是一个可忽略的性能差异。经历使用它的痛苦的另一个原因是SLI / Crossfire多GPU渲染转到单个显示器。真正使该场景工作需要进行许多其他优化,而且它非常适合。您应该查找供应商优化指南以获取详细信息。

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

    请参阅DXGI OverviewDirectX Graphics Infrastructure (DXGI): Best Practices

答案 1 :(得分:2)

经过多次尝试和试验,以下是我使用的不同解决方法,没有一个是理想的,但都比模式更改更好。

1 /将光标强制在全屏窗口的中间,使用键盘快捷键再次获得控制权。 这并不理想,因为在我们的部分运行期间我们无法做任何事情,但至少可以防止意外的灾难点击&#34;。它也不会阻止键盘交互。

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

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

不再适用于Windows 8,但在我的使用案例中,因为我工作的大多数媒体公司仍然使用Windows 7,因此它至少可以保留5到10年的有效解决方案。

4 /强制DX11窗口在前景

基本上不断调用SetForegroundWindow以避免另一个窗口聚焦。

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

由于在我的应用程序中我可以访问演示文稿时,我使用以下例程(之前调用Present)

- 获取前景窗口句柄(使用GetForegroundWindow),如果前景句柄是我们的全屏窗口,只需像往常一样调用Present。

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

- 如果我们的前景窗口与监视器重叠,则验证: 调用GetWindowRect以获取边界,并与监视器位置执行交集。

或者,使用DXGI_PRESENT_TEST标志在交换链上调用Present。如果窗口重叠,则当前调用将返回DXGI_STATUS_OCCLUDED

如果窗口重叠,请隐藏它或将其移动到另一个监视器中(任何地方都不要重叠): ShowWindowSetWindowPos非常适合此任务。

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

答案 2 :(得分:0)

有一种方法可以防止DXGI在您的过程失去焦点时自动退出全屏模式,尽管我必须警告,这有点骇人听闻。 基本上DXGI会调用GetForegroundWindow()并检查返回的窗口是否属于您。 如果不是,它将关闭全屏模式。 因此,如果您将此函数挂钩/重定向到自己的替换项,则始终返回您的窗口(无论它是否具有焦点)-这样就可以完成工作。

这是执行此操作的简单代码。它是针对64位模式的,并且假定您永远不需要调用实函数,因此它只需使用跳转指令覆盖您的替换就可以覆盖其开始:

HWND WINAPI get_our_window()
{
    return our_window;
}
void disable_automatic_leaving_fullscreen_on_lost_focus()
{
    // get the address of GetForegroundWindow
    char *p = (char *)GetProcAddress(GetModuleHandleA("user32.dll"), "GetForegroundWindow");

    // make the function code writable
    DWORD old;
    VirtualProtect(p, 12, PAGE_EXECUTE_WRITECOPY, &old);

    // overwrite the function start:
    // mov rax, <address_of_GetOurWindow>
    p[0] = 0x48, p[1] = 0xB8, *(void **)(p + 2) = (void *)get_our_window;
    // jmp rax
    p[10] = 0xFF, p[11] = 0xE0;
}

此代码仅用于演示。 如果您需要保留调用true函数的功能,则必须以其他更复杂的方式对其进行挂钩,但这是一个单独的主题