【学习DirectX12(2)】初始化——第二部分

解析 Command-line 参数

当应用程序执行的时候,ParseCommandLineArguments函数可以使用命令行参数来重载一些全局定义的变量。

void ParseCommandLineArguments()
{
    int argc;
    wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
 
    for (size_t i = 0; i < argc; ++i)
    {
        if (::wcscmp(argv[i], L"-w") == 0 || ::wcscmp(argv[i], L"--width") == 0)
        {
            g_ClientWidth = ::wcstol(argv[++i], nullptr, 10);
        }
        if (::wcscmp(argv[i], L"-h") == 0 || ::wcscmp(argv[i], L"--height") == 0)
        {
            g_ClientHeight = ::wcstol(argv[++i], nullptr, 10);
        }
        if (::wcscmp(argv[i], L"-warp") == 0 || ::wcscmp(argv[i], L"--warp") == 0)
        {
            g_UseWarp = true;
        }
    }
 
    // Free memory allocated by CommandLineToArgvW
    ::LocalFree(argv);
}

你可能注意到有些函数的前缀是::操作符,这是用于定义全局中的系统函数。

下面这个表描述了应用程序支持的命令行参数。

参数描述
-w--width渲染窗口的宽(像素)
-h--height渲染窗口的高(像素)
-warp--warp为创建device使用 Windows Advanced Rasterization Platform (WARP) 

启用 Direct3D 12 调试层

在使用DXGI或D3D API之前,调试层应当首先被启用。在创建ID3D12Deviece后启用调试层会让device被移除。

void EnableDebugLayer()
{
#if defined(_DEBUG)
    // Always enable the debug layer before doing anything DX12 related
    // so all possible errors generated while creating DX12 objects
    // are caught by the debug layer.
    ComPtr<ID3D12Debug> debugInterface;
    ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugInterface)));
    debugInterface->EnableDebugLayer();
#endif
}

IID_PPV_ARGS宏用于接收接口指针,supplying the IID value of the requested interface automatically based on the type of the interface pointer used.

常见的接收接口指针的语法包括下面两个参数。

  • 一个 [in] 参数,类型通常为 REFIID,用于指定接收接口的IID
  • 一个 [out] 参数, 类型通常为 void**,用于接收接口指针.

这个宏将会基于接口类型来计算IID,用于防止接口指针与IID不匹配。

注册窗口类 

在创建窗口实例之前,必须先注册窗口类。当应用程序停止后,窗口类会被自动注销。

void RegisterWindowClass( HINSTANCE hInst, const wchar_t* windowClassName )
{
    // Register a window class for creating our render window with.
    WNDCLASSEXW windowClass = {};
 
    windowClass.cbSize = sizeof(WNDCLASSEX);
    windowClass.style = CS_HREDRAW | CS_VREDRAW;
    windowClass.lpfnWndProc = &WndProc;
    windowClass.cbClsExtra = 0;
    windowClass.cbWndExtra = 0;
    windowClass.hInstance = hInst;
    windowClass.hIcon = ::LoadIcon(hInst, NULL);
    windowClass.hCursor = ::LoadCursor(NULL, IDC_ARROW);
    windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    windowClass.lpszMenuName = NULL;
    windowClass.lpszClassName = windowClassName;
    windowClass.hIconSm = ::LoadIcon(hInst, NULL);
 
    static ATOM atom = ::RegisterClassExW(&windowClass);
    assert(atom > 0);
}

The RegisterClassEx function takes a pointer to a WNDCLASSEX structure as its only argument.

The WNDCLASSEX structure has the following definition [17]:

typedef struct tagWNDCLASSEXW {
    UINT        cbSize;
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCWSTR     lpszMenuName;
    LPCWSTR     lpszClassName;
    HICON       hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW;

每个成员的定义如下

  • UINT cbSize: 这个结构体的字节大小,设置为 sizeof(WNDCLASSEXW).
  • UINT style: 类样式。比如 CS_HREDRAW 指定了当窗口的宽度改变时需要重新绘制整个窗口, CS_VREDRAW则是当高度改变时重新绘制整个窗口。
  • WNDPROC lpfnWndProc: 窗口消息处理程序,用于处理在这个类下实例化出来的窗口所接收到的消息。可默认也可自定义。
  • int cbClsExtra: 给这个窗口类结构体额外分配的字节。我们暂时不需要所以设置为0.
  • int cpWndExtra: 给这个窗口实例额外分配的字节。我们暂时不需要所以设置为0.
  • HINSTANCE hInstance: A handle to the instance that contains the window procedure for the class. This module instance handle is passed to the WinMain function which will be shown later.
  • HICON hIcon: 类图标的handle。你可以使用 LoadIcon来加载自定义图标,或者是 NULL (or nullptr) 来使用默认图标。
  • HCURSOR hCursor: 类鼠标的handle。我们将使用默认鼠标,也就是使用函数 LoadCursor( nullptr, IDC_ARROW ).
  • HBRUSH hbrBackground: 背景笔刷的颜色,包括以下几种。
    • COLOR_ACTIVEBORDER
    • COLOR_ACTIVECAPTION
    • COLOR_APPWORKSPACE
    • COLOR_BACKGROUND
    • COLOR_BTNFACE
    • COLOR_BTNSHADOW
    • COLOR_BTNTEXT
    • COLOR_CAPTIONTEXT
    • COLOR_GRAYTEXT
    • COLOR_HIGHLIGHT
    • COLOR_HIGHLIGHTTEXT
    • COLOR_INACTIVEBORDER
    • COLOR_INACTIVECAPTION
    • COLOR_MENU
    • COLOR_MENUTEXT
    • COLOR_SCROLLBAR
    • COLOR_WINDOW
    • COLOR_WINDOWFRAME
    • COLOR_WINDOWTEXT
  • LPCWSTR lpszMenuName: Pointer to a null-terminated character string that specifies the resource name of the class menu, as the name appears in the resource file. If this member is NULL, windows belonging to this class have no default menu.
  • LPCWSTR lpszClassName: 实例化窗口要使用一个窗口类,这就是那个类的名字。
  • HICON hIconSm: 窗口类使用的小图标。

创建窗口实例

窗口将被创建在显示器的中心。注意不要让窗口的一部分创建到显示器外面去了。

HWND CreateWindow(const wchar_t* windowClassName, HINSTANCE hInst,
    const wchar_t* windowTitle, uint32_t width, uint32_t height)
{
    int screenWidth = ::GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = ::GetSystemMetrics(SM_CYSCREEN);
 
    RECT windowRect = { 0, 0, static_cast<LONG>(width), static_cast<LONG>(height) };
    ::AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE);
 
    int windowWidth = windowRect.right - windowRect.left;
    int windowHeight = windowRect.bottom - windowRect.top;
 
    // Center the window within the screen. Clamp to 0, 0 for the top-left corner.
    int windowX = std::max<int>(0, (screenWidth - windowWidth) / 2);
    int windowY = std::max<int>(0, (screenHeight - windowHeight) / 2);
}

GetSystemMetrics很简单,返回屏幕的宽高。为了计算期望的窗口大小,使用函数AdjustWindowRect。WS_OVERLAPPEDWINDOW说明这个窗口可以被最小化,最大化,以及边框很薄。

The top-left corner point of the window is computed on lines 158-159 so that the window appears in the center of the screen. The window position should be clamped to (0,0)(0,0) to prevent the window from being positioned offscreen.

当知道了窗口的维度,就可以创建窗口了。

HWND hWnd = ::CreateWindowExW(
        NULL,
        windowClassName,
        windowTitle,
        WS_OVERLAPPEDWINDOW,
        windowX,
        windowY,
        windowWidth,
        windowHeight,
        NULL,
        NULL,
        hInst,
        nullptr
    );
 
    assert(hWnd && "Failed to create window");
 
    return hWnd;
}

CreateWindowExW结构体如下

HWND WINAPI CreateWindowExW(
    _In_ DWORD dwExStyle,
    _In_opt_ LPCWSTR lpClassName,
    _In_opt_ LPCWSTR lpWindowName,
    _In_ DWORD dwStyle,
    _In_ int X,
    _In_ int Y,
    _In_ int nWidth,
    _In_ int nHeight,
    _In_opt_ HWND hWndParent,
    _In_opt_ HMENU hMenu,
    _In_opt_ HINSTANCE hInstance,
    _In_opt_ LPVOID lpParam
);

每个成员变量分别是

  • DWORD dwExStyle:窗口的扩展样式 Extended Window Styles
  • LPCWSTR lpClassName: 使用 RegisterClass or RegisterClassEx 注册的窗口内。
  • LPCWSTR lpWindowName: 窗口的名字,如果有标题框的话,窗口的名字将显示出来。
  • DWORD dwStyle: 窗口的标准样式 window style values.
  • 接下来四个参数是窗口的位置和大小,可以设置为 CW_USEDEFAULT。
  • HWND hWndParent:父窗口的handle
  • HMENU hMenu:目录的handle,取决于窗口的样式,可以为NULL。
  • HINSTANCE hInstance: 窗口模组的handle,
  • LPVOID lpParam: 将要使用 CREATESTRUCT 结构来通过窗口传递的数据的指针 (lpCreateParams member) 。

窗口被创建成功但是没有展示出来。因为还要创建和初始化Device和指令队列。

查询DirectX 12 Adapter

在创建Device之前,必须现在用户的电脑上找到兼容的adapter,因此使用GetAdapter函数。

ComPtr<IDXGIAdapter4> GetAdapter(bool useWarp)
{
    ComPtr<IDXGIFactory4> dxgiFactory;
    UINT createFactoryFlags = 0;
#if defined(_DEBUG)
    createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
#endif
 
    ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory)));
}

但在查询可用的适配器之前,还需要创建DXGI工厂。启用DXGI_CREATE_FACTORY_DEBUG以便捕获错误。但在要发售的产品上不能用这个标签。

ComPtr<IDXGIAdapter1> dxgiAdapter1;
ComPtr<IDXGIAdapter4> dxgiAdapter4;
 
if (useWarp)
{
    ThrowIfFailed(dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&dxgiAdapter1)));
    ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4));
}

In the case that a WARP device should be used, the IDXGIFactory4::EnumWarpAdapter method can be used to directly create the WARP adapter.

这种情况下应该使用WARP device,因此使用IDXGIFactory4::EnumWarpAdapter来创建WARP适配器,这个方法需要一个IDXGIAdapter1接口的指针,但GetAdapter函数返回的是IDXGIAdapter4的指针,因此要使用ComPtr::As来将其cast到正确的类型。当不使用ComPtr的时候,应该使用QueryInterface方法来查询正确的COM物体。

在COM物体上使用 static_cast 既不安全也不可靠。

else
    {
        SIZE_T maxDedicatedVideoMemory = 0;
        for (UINT i = 0; dxgiFactory->EnumAdapters1(i, &dxgiAdapter1) != DXGI_ERROR_NOT_FOUND; ++i)
        {
            DXGI_ADAPTER_DESC1 dxgiAdapterDesc1;
            dxgiAdapter1->GetDesc1(&dxgiAdapterDesc1);
 
            // Check to see if the adapter can create a D3D12 device without actually 
            // creating it. The adapter with the largest dedicated video memory
            // is favored.
            if ((dxgiAdapterDesc1.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 &&
                SUCCEEDED(D3D12CreateDevice(dxgiAdapter1.Get(), 
                    D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr)) && 
                dxgiAdapterDesc1.DedicatedVideoMemory > maxDedicatedVideoMemory )
            {
                maxDedicatedVideoMemory = dxgiAdapterDesc1.DedicatedVideoMemory;
                ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4));
            }
        }
    }
 
    return dxgiAdapter4;
}

当不使用WARP适配器时,就会使用DXGI工厂来查询硬件适配器。IDXGIFactory1::EnumAdapters1将会查询可用的GPU。如果适配器ID大于或等于可用的适配器数量时就会返回DXGI_ERROR_NOT_FOUND

如果只考虑硬件适配器,那么就不应该使用 DXGI_ADAPTER_FLAG_SOFTWARE 标签。

为了确保IDXGIFactory1::EnumAdapters1返回的适配器与DX12兼容,会使用D3D12CreateDevice创造一个device,如果这个函数返回S_OK,说明这个适配器确实和DX12兼容。通常来说,DX12喜欢更大显存的适配器。

如果找到了合适的GPU适配器,那么将会创建真正的DX12 device。

创建 DirectX 12 Device

DX12 device用于创建资源,比如纹理和缓存,指令列,指令队列,fence,heaps等。DX12 device不会直接用于发布绘制或dispatch指令。DX12 device可用被认为是一个memory context用于追踪GPU显存上的分配。摧毁DX12 device将会让所有通过这个device分配下的资源都变得无效,然后调试层就会发出警示信息。

ComPtr<ID3D12Device2> CreateDevice(ComPtr<IDXGIAdapter4> adapter)
{
    ComPtr<ID3D12Device2> d3d12Device2;
    ThrowIfFailed(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&d3d12Device2)));

你在之前的GetAdapter函数哪里已经看见D3D12CreateDevice了。这里,真正的device被创建好后将被存储在d3d12Device2参数里。

HRESULT WINAPI D3D12CreateDevice(
  _In_opt_  IUnknown          *pAdapter,
            D3D_FEATURE_LEVEL MinimumFeatureLevel,
  _In_      REFIID            riid,
  _Out_opt_ void              **ppDevice
);

这个结构体有如下参数

  • IUnknown *pAdapter:创建device时的适配器的指针。使用NULL来使用默认适配器,which is the first adapter that is enumerated by IDXGIFactory1::EnumAdapters.
  • D3D_FEATURE_LEVEL MinimumFeatureLevel: The minimum D3D_FEATURE_LEVEL required for successful device creation.
  • REFIID riid: Device接口的globally unique identifier (GUID) for the device interface. This parameter, and ppDevice, can be addressed with the single macro IID_PPV_ARGS.
  • void **ppDevice: A pointer to a memory block that receives a pointer to the device.

为了让开发者能够修复由调试层抛出的错误,dx12 device提供了ID3D12InfoQueue接口,用于断点和过滤需要的信息。

  // Enable debug messages in debug mode.
#if defined(_DEBUG)
    ComPtr<ID3D12InfoQueue> pInfoQueue;
    if (SUCCEEDED(d3d12Device2.As(&pInfoQueue)))
    {
        pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
        pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
        pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);

ID3D12InfoQueue接口使用ID3D12Device使用ComPtr::As查询获得

The ID3D12InfoQueue::SetBreakOnSeverity method sets a message severity level to break on (while the application is attached to a debugger) when a message with that severity level passes through the storage filter. The D3D12_MESSAGE_SEVERITY_ERROR and the D3D12_MESSAGE_SEVERITY_WARNING messages are generated if an error or warning is generated by the debug layer. The D3D12_MESSAGE_SEVERITY_CORRUPTION message is generated if a memory corruption occurs.

由于要消除所有的警告问题不切实际,因此可用用过滤器来筛选重要的信息。

// Suppress whole categories of messages
        //D3D12_MESSAGE_CATEGORY Categories[] = {};
 
        // Suppress messages based on their severity level
        D3D12_MESSAGE_SEVERITY Severities[] =
        {
            D3D12_MESSAGE_SEVERITY_INFO
        };
 
        // Suppress individual messages by their ID
        D3D12_MESSAGE_ID DenyIds[] = {
            D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE,   // I'm really not sure how to avoid this message.
            D3D12_MESSAGE_ID_MAP_INVALID_NULLRANGE,                         // This warning occurs when using capture frame while graphics debugging.
            D3D12_MESSAGE_ID_UNMAP_INVALID_NULLRANGE,                       // This warning occurs when using capture frame while graphics debugging.
        };
 
        D3D12_INFO_QUEUE_FILTER NewFilter = {};
        //NewFilter.DenyList.NumCategories = _countof(Categories);
        //NewFilter.DenyList.pCategoryList = Categories;
        NewFilter.DenyList.NumSeverities = _countof(Severities);
        NewFilter.DenyList.pSeverityList = Severities;
        NewFilter.DenyList.NumIDs = _countof(DenyIds);
        NewFilter.DenyList.pIDList = DenyIds;
 
        ThrowIfFailed(pInfoQueue->PushStorageFilter(&NewFilter));
    }
#endif
 
    return d3d12Device2;
}

Since D3D12_MESSAGE_SEVERITY_INFO message severity is for information only, info messages are supressed.

以下的警告信息由于 message ID而被忽视

  • CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE:当你要使用ClearColor来清楚RenderTargetView的时候会发出此警告。
  • MAP_INVALID_NULLRANGE and UNMAP_INVALID_NULLRANGE: 当使用Visual Studio中集成的图形调试器捕获帧时,会出现这些警告。由于我认为此错误永远不会在调试器中修复,因此最好只是忽略此警告。

创建指令队列

使用 CreateCommandQueue 来创建指令队列

ComPtr<ID3D12CommandQueue> CreateCommandQueue(ComPtr<ID3D12Device2> device, D3D12_COMMAND_LIST_TYPE type )
{
    ComPtr<ID3D12CommandQueue> d3d12CommandQueue;
 
    D3D12_COMMAND_QUEUE_DESC desc = {};
    desc.Type =     type;
    desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
    desc.Flags =    D3D12_COMMAND_QUEUE_FLAG_NONE;
    desc.NodeMask = 0;
 
    ThrowIfFailed(device->CreateCommandQueue(&desc, IID_PPV_ARGS(&d3d12CommandQueue)));
 
    return d3d12CommandQueue;
}

其中用到了以下结构体

typedef struct D3D12_COMMAND_QUEUE_DESC {
  D3D12_COMMAND_LIST_TYPE   Type;
  INT                       Priority;
  D3D12_COMMAND_QUEUE_FLAGS Flags;
  UINT                      NodeMask;
} D3D12_COMMAND_QUEUE_DESC;

 

检查防撕裂

显示器刷新速率变量 (NVidia's G-Sync and AMD's FreeSync) 需要被正确设置以用于防撕裂,即 "vsync-off[19].

 为了创建可用支持不同显示器刷新速率的应用程序,必须支持DXGI_FEATURE_PRESENT_ALLOW_TEARINGDXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING,而且当交换链的同步间隔为0时必须设置DXGI_PRESENT_ALLOW_TEARING

 Windows Display Driver Model (WDDM) 目前的版本是 2.1. 为了检查用户电脑是否支持防撕裂,所以要用 IDXGIFactory5::CheckFeatureSupport 方法。

bool CheckTearingSupport()
{
    BOOL allowTearing = FALSE;
 
    // Rather than create the DXGI 1.5 factory interface directly, we create the
    // DXGI 1.4 interface and query for the 1.5 interface. This is to enable the 
    // graphics debugging tools which will not support the 1.5 factory interface 
    // until a future update.
    ComPtr<IDXGIFactory4> factory4;
    if (SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&factory4))))
    {
        ComPtr<IDXGIFactory5> factory5;
        if (SUCCEEDED(factory4.As(&factory5)))
        {
            if (FAILED(factory5->CheckFeatureSupport(
                DXGI_FEATURE_PRESENT_ALLOW_TEARING, 
                &allowTearing, sizeof(allowTearing))))
            {
                allowTearing = FALSE;
            }
        }
    }
 
    return allowTearing == TRUE;
}

结构体如下:

HRESULT CheckFeatureSupport(
            DXGI_FEATURE Feature,
  [in, out] void         *pFeatureSupportData,
            UINT         FeatureSupportDataSize
);

 

  • DXGI_FEATURE Feature: 用户电脑是否支持某些特性的查询。包括
  • void *pFeatureSupportData: 支持特性的数据的缓存的指针
  • UINT FeatureSupportDataSize:  pFeatureSupportData的大小,字节。

创建交换链

交换链至少储存两个buffer,一个用于渲染的backbuffer,一个用于显示到屏幕上的frontbuffer。Windows8和DXGI 1.2引入的flip 展示方法为此提供了性能加速。

交换链可以创建两种效果。

为了使用vsync-off时到达最高帧率,必须使用第二个标签。而第一个标签可能会导致延迟,因为没有多余的缓存空间可让下一次back buffer渲染能继续进行。

创建交换链使用如下代码:

ComPtr<IDXGISwapChain4> CreateSwapChain(HWND hWnd, 
    ComPtr<ID3D12CommandQueue> commandQueue, 
    uint32_t width, uint32_t height, uint32_t bufferCount )
{
    ComPtr<IDXGISwapChain4> dxgiSwapChain4;
    ComPtr<IDXGIFactory4> dxgiFactory4;
    UINT createFactoryFlags = 0;
#if defined(_DEBUG)
    createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
#endif
 
    ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory4)));

 我们首先会创建DXGI factory,这与GetAdapter函数中相似。

DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = width;
swapChainDesc.Height = height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.Stereo = FALSE;
swapChainDesc.SampleDesc = { 1, 0 };
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = bufferCount;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
// It is recommended to always allow tearing if tearing support is available.
swapChainDesc.Flags = CheckTearingSupport() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;

 DXGI_SWAP_CHAIN_DESC1 结构体如下:

typedef struct _DXGI_SWAP_CHAIN_DESC1 {
  UINT             Width;
  UINT             Height;
  DXGI_FORMAT      Format;
  BOOL             Stereo;
  DXGI_SAMPLE_DESC SampleDesc;
  DXGI_USAGE       BufferUsage;
  UINT             BufferCount;
  DXGI_SCALING     Scaling;
  DXGI_SWAP_EFFECT SwapEffect;
  DXGI_ALPHA_MODE  AlphaMode;
  UINT             Flags;
} DXGI_SWAP_CHAIN_DESC1;

 

当指定了desc后,就可以创建交换链了

ComPtr<IDXGISwapChain1> swapChain1;
    ThrowIfFailed(dxgiFactory4->CreateSwapChainForHwnd(
        commandQueue.Get(),
        hWnd,
        &swapChainDesc,
        nullptr,
        nullptr,
        &swapChain1));
 
    // Disable the Alt+Enter fullscreen toggle feature. Switching to fullscreen
    // will be handled manually.
    ThrowIfFailed(dxgiFactory4->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER));
 
    ThrowIfFailed(swapChain1.As(&dxgiSwapChain4));
 
    return dxgiSwapChain4;
}

 

HRESULT CreateSwapChainForHwnd(
  [in]                 IUnknown                        *pDevice,
  [in]                 HWND                            hWnd,
  [in]           const DXGI_SWAP_CHAIN_DESC1           *pDesc,
  [in, optional] const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc,
  [in, optional]       IDXGIOutput                     *pRestrictToOutput,
  [out]                IDXGISwapChain1                 **ppSwapChain
);

 

为了防止按下ALT+ENTER键后变成全屏模式, IDXGIFactory::MakeWindowAssociation 方法需要使用 DXGI_MWA_NO_ALT_ENTER标签。

然后交换链使用 ComPtr::As 变成IDXGISwapChain4 类型接口,并且返回调用函数。

为了渲染交换链的back buffer,每个back buffer必须有个Render Target View,或者叫descriptor heap。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值