我正在试验 Windows 10、Direct3D 11 和 IDXGISwapChain::Present() 函数。当我在全屏模式下调用 Present 函数时,它会将命令排入队列并且直到大约第 4 次调用才会阻塞,这是预期的行为。
但是,当我在窗口模式下调用相同的函数时,它会在第一次调用和每个后续调用时阻塞。
我还尝试将 IDXGIDevice1::SetMaximumFrameLatency 函数设置为不同的值,但在窗口模式下,该函数似乎被忽略了。在全屏模式下,该功能可以正常工作。
我想知道是否有办法绕过 Present 函数在每次调用时阻塞,同时保持窗口模式。
这是我用来对上述问题进行单元测试的 C++ 代码:
#include <windows.h>
#include <windowsx.h>
#include <d3d11_1.h>
#include <wrl.h>
#include <dxgi1_2.h>
#include <iostream>
#include <chrono>
#include <stdio.h>
#include <list>
#pragma comment (lib, "d3d11.lib")
#define SCREEN_WIDTH 2560
#define SCREEN_HEIGHT 1440
using namespace std;
using chrono::milliseconds;
using chrono::high_resolution_clock;
using chrono::duration_cast;
using chrono::duration;
using Microsoft::WRL::ComPtr;
ComPtr<IDXGISwapChain> swapchain;
ComPtr<ID3D11Device> dev;
ComPtr<ID3D11DeviceContext> devcon;
ID3D11RenderTargetView* backbuffer;
list<milliseconds> times;
void initConsole()
{
AllocConsole();
FILE* pstdin, * pstdout, * pstderr;
freopen_s(&pstdin, "CONIN$", "r", stdin);
freopen_s(&pstdout, "CONOUT$", "w", stdout);
freopen_s(&pstderr, "CONOUT$", "w", stderr);
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
void InitD3D(HWND hWnd)
{
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 2;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferDesc.Width = SCREEN_WIDTH;
scd.BufferDesc.Height = SCREEN_HEIGHT;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.OutputWindow = hWnd;
scd.SampleDesc.Count = 1;
scd.Windowed = true;
scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &scd, &swapchain, &dev, nullptr, &devcon);
//swapchain->SetFullscreenState(true, nullptr);
ComPtr<ID3D11Texture2D> pBackBuffer;
swapchain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
dev->CreateRenderTargetView(pBackBuffer.Get(), nullptr, &backbuffer);
devcon->OMSetRenderTargets(1, &backbuffer, nullptr);
D3D11_VIEWPORT viewport = {};
viewport.Width = SCREEN_WIDTH;
viewport.Height = SCREEN_HEIGHT;
devcon->RSSetViewports(1, &viewport);
}
void RenderFrame(void)
{
float color[] = { 0.0f, 0.2f, 0.4f, 1.0f };
devcon->ClearRenderTargetView(backbuffer, color);
auto t1 = high_resolution_clock::now();
swapchain->Present(1, 0);
auto t2 = high_resolution_clock::now();
auto time = duration_cast<milliseconds>(t2 - t1);
times.push_back(time);
}
void CleanD3D(void)
{
swapchain->SetFullscreenState(false, nullptr);
backbuffer->Release();
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
initConsole();
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
RECT wr = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
auto hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our First Direct3D Program", WS_OVERLAPPEDWINDOW, 0, 0, wr.right - wr.left, wr.bottom - wr.top, nullptr, nullptr, hInstance, nullptr);
ShowWindow(hWnd, nCmdShow);
InitD3D(hWnd);
MSG msg;
for (int i = 0; i < 20; ++i)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT) {
break;
}
}
RenderFrame();
}
CleanD3D();
for (auto time : times)
{
cout << time << " ";
}
cin.ignore();
return 0;
}
如果你运行上面的代码,它会输出:
4ms 8ms 15ms 16ms 14ms 16ms 14ms 16ms 15ms 16ms 16ms 16ms 16ms 16ms 16ms 16ms 16ms 15ms 16ms 16ms
但是,如果您取消注释 SetFullscreenState 行,则会输出:
1ms 0ms 0ms 13ms 16ms 49ms 33ms 50ms 16ms 16ms 16ms 16ms 16ms 16ms 16ms 16ms 16ms 15ms 16ms 16ms
在窗口化的情况下,它会从第一次调用开始阻塞。在全屏情况下,它会从第四次调用开始阻塞。
编辑:添加了示例代码。