6

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

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

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

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

另外,如果这有用,我正在 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 消息)。

4

3 回答 3

5

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

使用单个监视器,只要您的应用程序窗口大小可以填满显示屏,它就可以正常工作。用户不能使用鼠标来改变窗口的焦点,它需要像 ALT+TAB 这样的东西来切换焦点。

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

此外,在 Windows Vista 或更高版本上,“独占”模式的概念是一种错觉:GPU 始终是共享的。无论是全屏还是窗口交换链,“焦点”应用程序都会获得优先权。

对于 Windows 桌面应用程序,您有三种选择来获得全屏风格体验:

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

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

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

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

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

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

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

请参阅DXGI 概述DirectX 图形基础结构 (DXGI):最佳实践

于 2015-10-20T16:43:59.063 回答
3

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

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

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。如果窗口重叠,Present 调用将返回 DXGI_STATUS_OCCLUDED

如果一个窗口重叠,要么隐藏它,要么将它移动到另一个监视器中(任何地方,这样它就不会重叠): ShowWindowSetWindowPos非常适合这个任务。

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

于 2016-09-21T10:12:44.307 回答
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;
}

此代码仅用于演示。如果你需要保留调用真正函数的能力,那么你必须以一种不同的、更复杂的方式来挂钩它,但这是一个单独的主题

于 2020-02-05T14:00:11.343 回答