Windows桌面应用程序(1-1-2-1st) 使用DirectX设备资源

了解Microsoft DirectX图形基础结构(DXGI)在Windows Store DirectX游戏中的作用。 DXGI是一组用于配置和管理低级图形和图形适配器资源的API。没有它,你将无法将游戏的图形绘制到窗口中。
想想DXGI:直接访问GPU并管理其资源,你必须有一种方法来描述你的应用程序。您需要的关于GPU的最重要信息是绘制像素的位置,以便将这些像素发送到屏幕。通常这被称为”后台缓冲区”——在GPU内存中的一个位置,您可以在其中绘制像素,然后将其”翻转”或”交换”并在刷新信号上发送到屏幕。 DXGI允许您获取该位置以及使用该缓冲区的手段(称为交换链,因为它是一个可交换缓冲区链,允许多个缓冲策略)。
为此,您需要访问写入交换链和窗口的句柄,该句柄将显示交换链的当前后台缓冲区。您还需要连接这两者,以确保操作系统在您请求时使用后台缓冲区的内容刷新窗口。
绘制到屏幕的整个过程如下:

  • 获取您的应用程序的CoreWindow
  • 获取Direct3D设备和上下文的接口。
  • 创建交换链来在CoreWindow中显示你的渲染图像。
  • 为绘图创建一个渲染目标,并用像素填充它。
  • 呈现交换链!

为您的应用程序创建一个窗口

我们需要做的第一件事是创建一个窗口。首先,通过填充一个WNDCLASS实例来创建一个窗口类,然后使用RegisterClass注册它。窗口类包含窗口的基本属性,包括它使用的图标,静态消息处理函数(稍后详细介绍)和窗口类的唯一名称。

if(m_hInstance==NULL) 
    m_hInstance=(HINSTANCE)GetModuleHandle(NULL);
HICON hIcon=NULL;
WCHAR szExePath[MAX_PATH];
GetModuleFileName(NULL,szExePath,MAX_PATH);
// If the icon is NULL, then use the first one found in the exe
if(hIcon==NULL)
    hIcon=ExtractIcon(m_hInstance,szExePath,0); 
// Register the windows class
WNDCLASS wndClass;
wndClass.style=CS_DBLCLKS;
wndClass.lpfnWndProc=MainClass::StaticWindowProc;
wndClass.cbClsExtra=0;
wndClass.cbWndExtra=0;
wndClass.hInstance=m_hInstance;
wndClass.hIcon=hIcon;
wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
wndClass.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
wndClass.lpszMenuName=NULL;
wndClass.lpszClassName=m_windowClassName.c_str();
if(!RegisterClass(&wndClass)){
    DWORD dwError=GetLastError();
    if(dwError!=ERROR_CLASS_ALREADY_EXISTS)
        return HRESULT_FROM_WIN32(dwError);
}

接下来,你创建窗口。我们还需要提供窗口的大小信息和我们刚刚创建的窗口类的名称。 当你调用CreateWindow的时候,你会得到一个不透明的指向HWND窗口的指针;您需要保留HWND指针,并在任何时候需要引用窗口时使用它,包括销毁或重新创建它,以及(尤其重要的)创建用于在窗口中绘制的DXGI交换链。

m_rc;
int x=CW_USEDEFAULT;
int y=CW_USEDEFAULT;
// No menu in this example.
m_hMenu=NULL;
// This example uses a non-resizable 640 by 480 viewport for simplicity.
int nDefaultWidth=640;
int nDefaultHeight=480;
SetRect(&m_rc,0,0,nDefaultWidth,nDefaultHeight);        
AdjustWindowRect(
    &m_rc,
    WS_OVERLAPPEDWINDOW,
    (m_hMenu!=NULL)?true:false
);
// Create the window for our viewport.
m_hWnd=CreateWindow(
    m_windowClassName.c_str(),
    L"Cube11",
    WS_OVERLAPPEDWINDOW,
    x,y,
    (m_rc.right-m_rc.left),(m_rc.bottom-m_rc.top),
    0,
    m_hMenu,
    m_hInstance,
    0
);
if(m_hWnd==NULL){
    DWORD dwError=GetLastError();
    return HRESULT_FROM_WIN32(dwError);
}

Windows桌面应用程序模型在Windows消息循环中包含一个钩子。您需要通过编写一个”StaticWindowProc”函数来处理窗口事件,从而将主程序循环从此钩子中取出。这必须是一个静态函数,因为Windows将在任何类实例的上下文之外调用它。这是一个非常简单的静态消息处理函数的例子。

LRESULT CALLBACK MainClass::StaticWindowProc(
    HWND hWnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
){
    switch(uMsg){
        case WM_CLOSE:
            {
                HMENU hMenu;
                hMenu=GetMenu(hWnd);
                if(hMenu!=NULL)
                    DestroyMenu(hMenu);
                DestroyWindow(hWnd);
                UnregisterClass(
                    m_windowClassName.c_str(),
                    m_hInstance
                );
                return 0;
            }
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

这个简单的例子只检查程序退出条件:WM_CLOSE,请求关闭窗口时发送,WM_DESTROY,窗口实际上从屏幕上移除后发送。 完整的产品应用程序也需要处理其他窗口事件——有关窗口事件的完整列表,请参阅窗口通知
主程序循环本身需要通过允许Windows有机会运行静态消息处理来确认Windows消息。通过分叉行为来帮助程序有效地运行:每个迭代应该选择处理新的Windows消息(如果可用的话),并且如果没有消息在队列中,则它应该呈现新的帧。 这是一个非常简单的例子:

bool bGotMsg;
MSG msg;
msg.message=WM_NULL;
PeekMessage(&msg,NULL,0U,0U,PM_NOREMOVE);
while WM_QUIT!=msg.message){
    // Process window events.
    // Use PeekMessage() so we can use idle time to render the scene. 
    bGotMsg=(PeekMessage(&msg,NULL,0U,0U,PM_REMOVE)!=0);
    if(bGotMsg){
        // Translate and dispatch the message
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else{
        // Update the scene.
        renderer->Update();
        // Render frames during idle time (when no messages are waiting).
        renderer->Render();
        // Present the frame to the screen.
        deviceResources->Present();
    }
}

获取Direct3D设备和上下文的接口

使用Direct3D的第一步是获取Direct3D硬件(GPU)的接口,表示为ID3D11DeviceID3D11DeviceContext的实例。前者是GPU资源的虚拟表示,后者是渲染管线和进程的设备无关抽象。这里有一个简单的方法来思考它:ID3D11Device包含您经常调用的图形方法,通常在任何渲染发生之前,获取和配置开始绘制像素所需的一组资源。另一方面,ID3D11DeviceContext包含每帧调用的方法:加载缓冲区,视图和其他资源,更改output-merger和rasterizer状态,管理着色器以及绘制通过状态和着色器传递这些资源的结果。
这个过程中有一个非常重要的部分:设置功能级别。功能级别告诉DirectX您的应用程序支持的最低硬件级别,其中D3D_FEATURE_LEVEL_9_1为最低功能集,D3D_FEATURE_LEVEL_11_1为最高级。如果您想要覆盖最广泛的受众群体,您应该支持9_1。花一些时间阅读Direct3D功能级别,并为自己评估您希望游戏支持的最小和最大功能级别,并了解您选择的含义。
获取Direct3D设备和设备上下文的引用(指针),并将它们作为类级变量存储在DeviceResources实例上(如ComPtr智能指针)。每当需要访问Direct3D设备或设备上下文时,请使用这些引用。

D3D_FEATURE_LEVEL levels[]={
    D3D_FEATURE_LEVEL_9_1,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_11_1
};
// This flag adds support for surfaces with a color-channel ordering different
// from the API default. It is required for compatibility with Direct2D.
UINT deviceFlags=D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(DEBUG) || defined(_DEBUG)
deviceFlags|=D3D11_CREATE_DEVICE_DEBUG;
#endif
// Create the Direct3D 11 API device object and a corresponding context.
Microsoft::WRL::ComPtr<ID3D11Device> device;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
hr=D3D11CreateDevice(
    nullptr, // Specify nullptr to use the default adapter.
    D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver.
    0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    deviceFlags, // Set debug and Direct2D compatibility flags.
    levels, // List of feature levels this app can support.
    ARRAYSIZE(levels), // Size of the list above.
    D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
    &device, // Returns the Direct3D device created.
    &m_featureLevel, // Returns feature level of device created.
    &context // Returns the device immediate context.
);
if(FAILED(hr)){
    // Handle device interface creation failure if it occurs.
    // For example, reduce the feature level requirement, or fail over 
    // to WARP rendering.
}
// Store pointers to the Direct3D 11.1 API device and immediate context.
device.As(&m_pd3dDevice);
context.As(&m_pd3dDeviceContext);

创建交换链

好的:你有一个窗口可以绘制,并且你有一个接口来发送数据并给GPU发送命令。现在让我们看看如何把它们结合在一起。
首先,您告诉DXGI什么值用于交换链的属性。使用DXGI_SWAP_CHAIN_DESC结构执行此操作。六个领域对桌面应用程序尤为重要:

  • Windowed:表示交换链是全屏还是剪切到窗口。将其设置为TRUE可将交换链置于之前创建的窗口中。
  • BufferUsage:将其设置为DXGI_USAGE_RENDER_TARGET_OUTPUT。这表明交换链将是一个绘图表面,允许您将其用作Direct3D渲染目标。
  • SwapEffect:将其设置为DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL。
  • Format:DXGI_FORMAT_B8G8R8A8_UNORM格式指定32位颜色:三个RGB颜色通道中的每一个为8位,alpha通道为8位。
  • BufferCount:将其设置为2以实现传统的双缓冲行为,以避免撕裂。如果您的图形内容需要多个监视器刷新周期来渲染单个帧(例如,60 Hz,阈值大于16 ms),请将缓冲区计数设置为3。
  • SampleDesc:该字段控制多重采样。对于翻转模型交换链,将Count设置为1并将Quality设置为0。(要使用翻转模型交换链进行多重采样,请在单独的多重采样渲染目标上绘制,然后在提交之前将该目标解析为交换链。示例代码在Windows应用商店应用程序的多重采样中提供。

在为交换链指定配置之后,必须使用创建Direct3D设备(和设备上下文)的相同DXGI工厂来创建交换链。
简写:
获取您之前创建的ID3D11Device参考。将其上传到IDXGIDevice3(如果尚未),然后调用IDXGIDevice::GetAdapter来获取DXGI适配器。通过调用IDXGIFactory2::GetParentIDXGIFactory2IDXGIObject继承)获取该适配器的父工厂——现在,您可以使用该工厂通过调用CreateSwapChainForHwnd来创建交换链,如以下代码示例所示。

DXGI_SWAP_CHAIN_DESC desc;
ZeroMemory(&desc,sizeof(DXGI_SWAP_CHAIN_DESC));
desc.Windowed=TRUE; // Sets the initial state of full-screen mode.
desc.BufferCount=2;
desc.BufferDesc.Format=DXGI_FORMAT_B8G8R8A8_UNORM;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.SampleDesc.Count=1; //multisampling setting
desc.SampleDesc.Quality=0; //vendor-specific flag
desc.SwapEffect=DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.OutputWindow=hWnd;
// Create the DXGI device object to use in other factories, such as Direct2D.
Microsoft::WRL::ComPtr<IDXGIDevice3> dxgiDevice;
m_pd3dDevice.As(&dxgiDevice);
// Create swap chain.
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
Microsoft::WRL::ComPtr<IDXGIFactory> factory;
hr=dxgiDevice->GetAdapter(&adapter);
if(SUCCEEDED(hr)){
    adapter->GetParent(IID_PPV_ARGS(&factory));
    hr=factory->CreateSwapChain(
        m_pd3dDevice.Get(),
        &desc,
        &m_pDXGISwapChain
    );
}

如果刚刚开始,最好使用此处显示的配置。现在,如果您已经熟悉以前版本的DirectX,那么您可能会问:”为什么我们不能同时创建设备和交换链,而不是在所有这些类中走回去?答案是效率:交换链是Direct3D设备资源,设备资源与创建它们的特定Direct3D设备绑定。如果使用新的交换链创建新设备,则必须使用新的Direct3D设备重新创建所有设备资源。因此,通过使用相同的工厂创建交换链(如上所示),您可以重新创建交换链,并继续使用已经加载的Direct3D设备资源!
现在,您已经从操作系统获得了一个窗口,访问GPU及其资源的方式以及用于显示渲染结果的交换链。剩下的就是把所有东西连接在一起!

创建绘图的渲染目标

着色器管道需要一个资源来绘制像素。创建此资源的最简单方法是将ID3D11Texture2D资源定义为像素着色器要绘制的后备缓冲区,然后将该纹理读入交换链。
为此,您可以创建一个渲染目标视图。在Direct3D中,视图是访问特定资源的一种方式。在这种情况下,视图使像素着色器可以在完成每像素操作时写入纹理。
我们来看看这个代码。在交换链上设置DXGI_USAGE_RENDER_TARGET_OUTPUT时,可以将底层Direct3D资源用作绘图表面。所以为了得到我们的渲染目标视图,我们只需要从交换链获得后台缓冲区,并创建绑定到后台缓冲区资源的渲染目标视图。

hr=m_pDXGISwapChain->GetBuffer(
    0,
    __uuidof(ID3D11Texture2D),
    (void**)&m_pBackBuffer
);
hr=m_pd3dDevice->CreateRenderTargetView(
    m_pBackBuffer.Get(),
    nullptr,
    m_pRenderTarget.GetAddressOf()
);
m_pBackBuffer->GetDesc(&m_bbDesc);

也创建一个深度模板缓冲区。深度模板缓冲区只是ID3D11Texture2D资源的一种特殊形式,通常用于根据场景中物体与相机之间的距离确定哪些像素在光栅化过程中具有优先级。深度模板缓冲区也可用于模板效果,其中在光栅化过程中特定像素被丢弃或忽略。该缓冲区必须与渲染目标大小相同。请注意,您无法读取或渲染帧缓冲深度——模板纹理,因为它在最终栅格化之前和期间仅由着色器管道使用。
还要为ID3D11DepthStencilView创建深度模板缓冲区的视图。该视图告诉着色器管道如何解释底层的ID3D11Texture2D资源——因此,如果您不提供此视图,则不会执行每像素深度测试,并且场景中的对象至少可能看起来有点内向外!

CD3D11_TEXTURE2D_DESC depthStencilDesc(
    DXGI_FORMAT_D24_UNORM_S8_UINT,
    static_cast<UINT>(m_bbDesc.Width),
    static_cast<UINT>(m_bbDesc.Height),
    1, // This depth stencil view has only one texture.
    1, // Use a single mipmap level.
    D3D11_BIND_DEPTH_STENCIL
);
m_pd3dDevice->CreateTexture2D(
    &depthStencilDesc,
    nullptr,
    &m_pDepthStencil
);
CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
m_pd3dDevice->CreateDepthStencilView(
    m_pDepthStencil.Get(),
    &depthStencilViewDesc,
    &m_pDepthStencilView
);

最后一步是创建一个视口。这定义了屏幕上显示的后台缓冲区的可见矩形;您可以通过更改视口的参数来更改显示在屏幕上的缓冲区的部分。在全屏交换链的情况下,这个代码的目标是整个窗口大小或屏幕分辨率。为了好玩,改变提供的坐标值并观察结果。

ZeroMemory(&m_viewport,sizeof(D3D11_VIEWPORT));
m_viewport.Height=(float)m_bbDesc.Height;
m_viewport.Width=(float)m_bbDesc.Width;
m_viewport.MinDepth=0;
m_viewport.MaxDepth=1;
m_pd3dDeviceContext->RSSetViewports(
    1,
    &m_viewport
);

这就是你从无到有在窗口中绘制像素的过程!从开始的时候开始,熟悉DirectX(通过DXGI)如何管理开始绘制像素所需的核心资源是个不错的主意。接下来,您将看到图形管道的结构;请参阅了解DirectX应用程序模板的渲染管道

相关话题

接下来
使用着色器和着色器资源


原文链接:Work with DirectX device resources

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值