Direct3D11基础知识

这是记录我学习DirectX一系列文章,目测需要掌握一些基本的Win32知识,当然C++是必须的

摘要

在本篇中,我会介绍创建最小DirectX应用程序所需要的一些元素,每个DX程序都必须具有这些元素才成正常运行。这些元素包括一个用来显示DX的窗口和一个DX相关设备,然后我们就可以在窗口上通过DX渲染图形了。

窗口相关

我们通过vs构建c++窗口程序
图片说明
后面的我就不再多说了。如果对Win32构建桌面程序不太了解,可以上网查资料进行学习,我们目前差不多只要了解到回调函数就可以了

DX相关

现在我们拥有一个正在显示的窗口,然后我们需要初始化D3D。D3D初始化阶段首先需要创建D3D设备,D3D设备上下文DXGI交换链
D3D设备(ID3D11Device)用来执行渲染和资源创建,它包含了各种创建各种资源的方法,最常用的类有:资源类(ID3D11Resource,包含纹理和缓冲),视图类以及着色器。
D3D设备上下文(ID3D11DeviceContext)用来负责渲染工作,他可以看作是一个渲染管线,他需要绑定来自D3D设备创建的各种资源,视图类和着色器才能正常执行渲染到渲染目标(一切供渲染引擎绘话图形的缓冲皆称为渲染目标)。
DXGI交换链(IDXGISwapChain)用来负责获取展示渲染内容的窗口的缓冲区,它缓存了一个或者多个表面(2D纹理),这些表面都可以称为缓冲区(Buffer)。主要是前端和后端。前缓冲区保存向用户展示的内容,后缓冲区则是我们主要进行的渲染的场所,我们将它通过合适的手段成为渲染管线将绘制的渲染目标。一旦渲染完成,交换链将通过交换两个缓冲区来展示后缓冲区。这样后缓冲区就变成了前缓冲区,反之亦然。
初始化它们在后边,我先讲下渲染一般流程。

  1. 创建D3D设备,D3D设备上下文和DXGI交换链
  2. 创建一个渲染目标视图(RenderTargetView),并将其绑定到渲染管道上。
  3. 渲染之前的最后一件事就是初始化视口。视口就是用来映射剪辑空间坐标。
  4. 渲染场景。
  5. 交换链交换缓冲区。

以上,就是一般渲染流程。现在,我开始介绍如何执行上述步骤:

D3D设备和D3D设备上下文的创建

D3D11CreateDevice函数--创建D3D设备和D3D设备上下文

HRESULT WINAPI D3D11CreateDevice(
    IDXGIAdapter* pAdapter,         // [In_Opt]适配器
    D3D_DRIVER_TYPE DriverType,     // [In]驱动类型
    HMODULE Software,               // [In_Opt]若上面为D3D_DRIVER_TYPE_SOFTWARE则这里需要提供程序模块
    UINT Flags,                     // [In]使用D3D11_CREATE_DEVICE_FLAG枚举类型
    D3D_FEATURE_LEVEL* pFeatureLevels,  // [In_Opt]若为nullptr则为默认特性等级,否则需要提供特性等级数组
    UINT FeatureLevels,             // [In]特性等级数组的元素数目
    UINT SDKVersion,                // [In]SDK版本,默认D3D11_SDK_VERSION
    ID3D11Device** ppDevice,        // [Out_Opt]输出D3D设备
    D3D_FEATURE_LEVEL* pFeatureLevel,   // [Out_Opt]输出当前应用D3D特性等级
    ID3D11DeviceContext** ppImmediateContext //[Out_Opt]输出D3D设备上下文
 );
  1. pAdapter(适配器),可以看作是显卡的一层封装,通过它我们设置选择哪个显卡。一般设置为nullptr,这样可以由上层驱动替我们决定使用哪个显卡,我们也可到NVIDIA控制面板来设置设置当前程序使用哪个显卡。
  2. DriverType指定驱动类型,不过通常大多数我们选择D3D_DRIVER_HADEWARE,以享受硬件加速带来的收益。
  3. Software是实现软光栅器的DLL的句柄。如果DriverTypeD3D_DRIVER_TYPE_SOFTWARE,则Software不得为NULL
  4. Flags对应的是D3D11_CREATE_DEVICE_FLAG枚举值,如果需要D3D设备调试的话(在Debug模式下),可以指定D3D11_CREATE_DEVICE_DEBUG枚举值,指定该值后,可以在程序出现异常的时候观察调试输出窗口的信息。该值可以按位进行OR运算。例如:
    UINT flags = 0;
    #if _DEBUG
     flags |= D3D11_CREATE_DEVICE_DEBUG;
    #endif // DEBUG
  5. pFeatureLevels是一个特征等级数组,通过函数内部进行查询以检测所支持的特征等级。如果pFeatureLevelnullptr,则函数使用如下特征等级数组:
{
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1,
};

可以看出如果想创建D3D_FEATURE_LEVEL_11_1设备,必须显式提供包含D3D_FEATURE_LEVEL_11_1D3D_FEATURE_LEVEL数组。例如:

D3D_FEATURE_LEVEL featureLevels[] = 
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1
    };

但是如果系统不支持D3D 11.1,则D3D11CreateDevice会立刻返回一个E_INVALIDARG。如果这样,你需要降低特征等级。
在Win10, Win8.x 或 Win7 SP1且安装了KB2670838补丁的系统都支持Direct3D 11.1的API

  1. FeatureLevelspFeatureLevels中元素数量。

  2. SDKVersion默认使用D3D11_SDK_VERSION

  3. ppDevice``ppImmediateContext分别是获取对应对象的指针。pFeatureLevel是获取特征等级数组的第一个元素。如果不需要的话就输入nullptr

  4. 示例代码:

    HRESULT hr = S_OK;
    
    // D3D11_CREATE_DEVICE_FLAG枚举类型值
    UINT flags = 0;
    #if _DEBUG
    flags |= D3D11_CREATE_DEVICE_DEBUG;
    #endif // DEBUG
    
    // 特性等级数组
    D3D_FEATURE_LEVEL featureLevels[] = 
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1
    };
    
    UINT numFeatureLevel = ARRAYSIZE(featureLevels);
    
    D3D_FEATURE_LEVEL featureLevel;
    
    hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL, flags,
        featureLevels, numFeatureLevel, D3D11_SDK_VERSION, &pD3D11Device, &featureLevel,
        &pImmediateContext);
    
    if (hr == E_INVALIDARG)
    {
        // Direct3D 11.0不识别D3D_FEATURE_11_1,所以我们可以取第二个元素试试
        hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL, flags,
            &featureLevels[1], numFeatureLevel - 1, D3D11_SDK_VERSION, &pD3D11Device, &featureLevel,
            &pImmediateContext);
    }
    // 检测是否创建成功
    if (FAILED(hr))
    {
        MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
        return hr;
    }
    
    // 检测是否支持特征等级11.0或11.0
    if (featureLevel != D3D_FEATURE_LEVEL_11_1 && featureLevel != D3D_FEATURE_LEVEL_11_0)
    {
        MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);
        return false;
    }

DXGI交换链初始化

我们需要先了解D3D和DXGI各版本的对应关系,这很重要
图片说明
DXGI的交换链的创建需要通过IDXGIFactory::CreateSwapChain方法进行,但是如果要创建D3D 11.1对应的交换链,则需要通过IDXGIFactory::CreateSwapChainForHwnd进行。
所以,我们想要得到交换链,必须先获取包含IDXGIFactory接口的对象

获取包含IDXGIFactory接口的对象

一开始我们在创建D3D设备的时候使用的是默认的显卡适配器IDXGIAdapter。所以创建出来的D3D设备本身就实现了IDXGIDevice接口,通过该对象,我们可以获取到当前所用的显卡适配器IDXGIAdapter对象,这样我们再通过查询它的父级找到是哪个IDXGIFactory枚举出来的适配器。所以得到IDXGIFactory接口实例代码如下:

IDXGIFactory1 *pFactory1 = nullptr;
    {
        IDXGIDevice* pDevice = nullptr;
        hr = g_pD3D11Device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**> (&pDevice));
        if (SUCCEEDED(hr))
        {
            IDXGIAdapter* pAdapter = nullptr;
            hr = pDevice->GetAdapter(&pAdapter);
            if (SUCCEEDED(hr))
            {
                pAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&pFactory1));
                pAdapter->Release();
            }
            pDevice->Release();
        }
    }

我们也发现,每得到一节接口对象,用完后要手动释放,这样是不是太麻烦了。于是,我们引入Comptr智能指针,所以上述代码就简化成这样了:

ComPtr<IDXGIDevice>dxgiDevice;
ComPtr<IDXGIAdapter>dxgiAdapter;
ComPtr<IDXGIFactory1>dxgiFactory1;
ComPtr<IDXGIFactory2>dxgiFactory2;

if (SUCCEEDED(g_pD3D11Device.As(&dxgiDevice)))
{
    if (SUCCEEDED(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())))
    {
        hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf()));                        
    }
}

if (FAILED(hr))
    return hr;

如果他支持D3D 11.1的话,我们就可以通过下面代码拿到DXGI 1.2的对象

dxgiFactory1.As(&dxgiFactory2);

IDXGIFactory::CreateSwapChainForHwnd——D3D 11.1创建交换链

如果是D3D 11.1的话,我们需要先填充DXGI_SWAP_CHAIN_DESC1DXGI_SWAP_CHAIN_FULLSCREEN_DESC这两个结构体

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_FLAG枚举类型
} DXGI_SWAP_CHAIN_DESC1;

typedef struct DXGI_SAMPLE_DESC
{
    UINT Count;                     // MSAA采样数
    UINT Quality;                   // MSAA质量等级
} DXGI_SAMPLE_DESC;

typedef struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC
{
    DXGI_RATIONAL RefreshRate;                  // 刷新率
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;  // 忽略
    DXGI_MODE_SCALING Scaling;                  // 忽略
    BOOL Windowed;                              // 是否窗口化
} DXGI_SWAP_CHAIN_FULLSCREEN_DESC;

typedef struct DXGI_RATIONAL
{
    UINT Numerator;                 // 刷新率分子
    UINT Denominator;               // 刷新率分母
} DXGI_RATIONAL;

填充玩交换链描述符后就可以构建交换链,一个简单的示例如下:

// dx 11.1 or later
        DXGI_SWAP_CHAIN_DESC1 sd = {};
        sd.Width = 400;
        sd.Height = 600;
        sd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.BufferCount = 1;

        hr = dxgiFactory2->CreateSwapChainForHwnd(g_pD3D11Device.Get(), g_hWnd, &sd, nullptr, nullptr, g_pSwapChain1.GetAddressOf());

        if (SUCCEEDED(hr))
        {
            hr = g_pSwapChain1.As(&g_pSwapChain);
        }

        if (SUCCEEDED(hr))
        {
            // 注意我们上面因为没有填充DXGI_SWAP_CHAIN_FULLSCREEN_DESC结构体,所以我们要禁用ALT+ENTER与全屏的关联。
            dxgiFactory2->MakeWindowAssociation(g_hWnd, DXGI_MWA_NO_ALT_ENTER);
        }

上面可以注意到我不进得到了g_pSwapChain1,还通过它得到了g_pSwapChain。其实这两个接口对象指的都是同一个对象,只是前者比后者多实现了一些额外的接口,我们在后续一般使用g_pSwapChain就够了。

为渲染管道设置渲染目标

通过DXGI交换链得到原始内存

由于我们之前创建好的交换链已经包含一个后缓冲区了,我们可以通过IDXGI::GetBuffer()方法直接获取到后缓冲区的ID3D11Texture2D接口。

HRESULT IDXGISwapChain::GetBuffer( 
    UINT Buffer,        // [In]缓冲区索引号,从0到BufferCount - 1
    REFIID riid,        // [In]缓冲区的接口类型ID
    void **ppSurface    // [Out]获取到的缓冲区
);

为原始内存创建渲染目标视图

通常渲染目标是一个二维纹理,但它可能会绑定其他类型的资源。我们需要将得到的原始内存绑定到渲染目标视图上。使用ID3D11Device::CreateRenderTargetView方法来创建。

HRESULT ID3D11Device::CreateRenderTargetView( 
    ID3D11Resource *pResource,                      // [In]待绑定到渲染目标视图的资源
    const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,     // [In]为NULl时则为选择默认渲染目标视图
    ID3D11RenderTargetView **ppRTView);             // [Out]获取渲染目标视图

为渲染管线的输出合并阶段设置渲染目标

我们可以通过渲染上下文,在渲染管线的输出合并阶段设置渲染目标。可以通过ID3D11DeviceContent::OMSetRenderTargets方法实现。这样就可以确保管道呈现(Present)的输出被写入了后台缓冲区。

void ID3D11DeviceContext::OMSetRenderTargets( 
    UINT NumViews,                                      // [In] 视图数目
    ID3D11RenderTargetView *const *ppRenderTargetViews, // [In] 渲染目标视图数组
    ID3D11DepthStencilView *pDepthStencilView) = 0;     // [In] 深度/模板视图

下面给出简单实现

// 得到原始内存
ComPtr<ID3D11RenderTargetView> g_pRenderTargetView;
ComPtr<ID3D11Texture2D> pBackBuffer;
g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(pBackBuffer.GetAddressOf()));

// 创建渲染目标视图
g_pD3D11Device->CreateRenderTargetView(pBackBuffer.Get(), nullptr, g_pRenderTargetView.GetAddressOf());

// 为渲染管道设置渲染目标
g_pImmediateContext->OMSetRenderTargets(1, g_pRenderTargetView.GetAddressOf(), nullptr);

初始化视口

最终我们还需决定将整个视图输出到窗口特定的范围。我们需要使用D3D11_VIEWPORT来描述视口,然后通过ID3D11DeviceContext::RSSetViewports方法来设置1个或者多个视口

typedef struct D3D11_VIEWPORT
{
    FLOAT TopLeftX;     // 屏幕左上角起始位置X
    FLOAT TopLeftY;     // 屏幕左上角起始位置Y
    FLOAT Width;        // 宽度
    FLOAT Height;       // 高度
    FLOAT MinDepth;     // 最小深度,必须为0.0f
    FLOAT MaxDepth;     // 最大深度,必须为1.0f
}   D3D11_VIEWPORT;
void ID3D11DeviceContext::RSSetViewports(
    UINT  NumViewports,                     // 视口数目
    const D3D11_VIEWPORT *pViewports);      // 视口数组

简单实例

D3D11_VIEWPORT vp ;
vp.Width = static_cast<float>(width);
vp.Height = static_cast<float>(height);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;

g_pImmediateContext->RSSetViewports(1, &vp);

渲染场景并呈现

每一次渲染之前,我们需要清理一遍渲染目标视图绑定的缓冲区

void ID3D11DeviceContext::ClearRenderTargetView(
    ID3D11RenderTargetView *pRenderTargetView,  // [In]渲染目标视图
    const FLOAT  ColorRGBA[4]);                 // [In]指定覆盖颜色

然后我们让DXGI交换前后缓冲区以呈现输出。

HRESULT ID3D11DeviceContext::Present( 
    UINT SyncInterval,  // [In]通常为0
    UINT Flags);        // [In]通常为0

示例代码

if (g_pImmediateContext&&g_pSwapChain)
    {
        static float blue[4] = { 0.0f, 0.0f, 1.0f, 1.0f };  // RGBA = (0,0,255,255)
        g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView.Get(), blue);
        g_pSwapChain->Present(0, 0);
    }

最终的绘制效果应该如下
。。。。。。。
。。。。。
图片说明
报错了,,原来是我忘记引入静态库了
在程序最上方加入

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")

对了,最好把

#pragma comment(lib, "D3DCompiler.lib")
#pragma comment(lib, "winmm.lib")

这也加上,为后续做准备。
最终绘制效果如下
图片说明

程序最后的清理

因为我们使用了智能指针,所以我们只需要通过ID3D11DeviceContext::ClearState来恢复D3D设备上下文到默认状态就可以了,它会卸下所有绑定的资源。剩下的我们就交给智能指针就OK了。

DX相关代码如下

// DX相关全局变。
HWND g_hWnd;
template <class T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
ComPtr<ID3D11Device>        g_pD3D11Device;
ComPtr<ID3D11DeviceContext> g_pImmediateContext;
ComPtr<IDXGISwapChain>        g_pSwapChain;
ComPtr<IDXGISwapChain1>        g_pSwapChain1;
ComPtr<ID3D11RenderTargetView> g_pRenderTargetView;
UINT width = 400;
UINT height = 600;
// 初始化D3D
HRESULT InitD3DDevice()
{
    HRESULT hr = S_OK;

    // D3D11_CREATE_DEVICE_FLAG枚举类型值
    UINT flags = 0;
#if _DEBUG
    flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif // DEBUG

    // 特性等级数组
    D3D_FEATURE_LEVEL featureLevels[] = 
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1
    };

    UINT numFeatureLevel = ARRAYSIZE(featureLevels);

    D3D_FEATURE_LEVEL featureLevel;

    hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL, flags,
        featureLevels, numFeatureLevel, D3D11_SDK_VERSION, g_pD3D11Device.GetAddressOf(), &featureLevel,
        g_pImmediateContext.GetAddressOf());

    if (hr == E_INVALIDARG)
    {
        // Direct3D 11.0不识别D3D_FEATURE_11_1,所以我们可以取第二个元素试试
        hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL, flags,
            &featureLevels[1], numFeatureLevel - 1, D3D11_SDK_VERSION, g_pD3D11Device.GetAddressOf(), &featureLevel,
            g_pImmediateContext.GetAddressOf());
    }
    // 检测是否创建成功
    if (FAILED(hr))
    {
        MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
        return hr;
    }

    // 检测是否支持特征等级11.0或11.0
    if (featureLevel != D3D_FEATURE_LEVEL_11_1 && featureLevel != D3D_FEATURE_LEVEL_11_0)
    {
        MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);
        return hr;
    }
    // Obtain DXGI factory from device.
    ComPtr<IDXGIDevice>dxgiDevice;
    ComPtr<IDXGIAdapter>dxgiAdapter;
    ComPtr<IDXGIFactory1>dxgiFactory1;
    ComPtr<IDXGIFactory2>dxgiFactory2;

    if (SUCCEEDED(g_pD3D11Device.As(&dxgiDevice)))
    {
        if (SUCCEEDED(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())))
        {
            hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf()));                        
        }
    }

    if (FAILED(hr))
        return hr;

    //
    // Create swap chain
    //

    // 查看对象是否有IDXGIFactory2接口
    dxgiFactory1.As(&dxgiFactory2);

    if(dxgiFactory2)
    {
        // dx 11.1 or later
        DXGI_SWAP_CHAIN_DESC1 sd = {};
        sd.Width = 400;
        sd.Height = 600;
        sd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.BufferCount = 1;

        hr = dxgiFactory2->CreateSwapChainForHwnd(g_pD3D11Device.Get(), g_hWnd, &sd, nullptr, nullptr, g_pSwapChain1.GetAddressOf());

        if (SUCCEEDED(hr))
        {
            hr = g_pSwapChain1.As(&g_pSwapChain);
        }

        if (SUCCEEDED(hr))
        {
            // 注意我们上面因为没有填充DXGI_SWAP_CHAIN_FULLSCREEN_DESC结构体,所以我们要禁用ALT+ENTER与全屏的关联。
            dxgiFactory2->MakeWindowAssociation(g_hWnd, DXGI_MWA_NO_ALT_ENTER);
        }

    }

    //
    // DXGI和D3D设备交互
    //

    // 得到原始内存
    ComPtr<ID3D11Texture2D> pBackBuffer;
    g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(pBackBuffer.GetAddressOf()));

    // 创建渲染目标视图
    g_pD3D11Device->CreateRenderTargetView(pBackBuffer.Get(), nullptr, g_pRenderTargetView.GetAddressOf());

    // 为渲染管道设置渲染目标
    g_pImmediateContext->OMSetRenderTargets(1, g_pRenderTargetView.GetAddressOf(), nullptr);

    // 初始化视口
    D3D11_VIEWPORT vp = {};
    vp.Width = static_cast<float>(width);
    vp.Height = static_cast<float>(height);
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;

    g_pImmediateContext->RSSetViewports(1, &vp);

    return hr;
}

// 主程序退出后的清理。
void Clear()
{
    g_pImmediateContext->ClearState();
}

// 渲染
void Render()
{
    if (g_pImmediateContext&&g_pSwapChain)
    {
        static float blue[4] = { 0.0f, 0.0f, 1.0f, 1.0f };  // RGBA = (0,0,255,255)
        g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView.Get(), blue);
        g_pSwapChain->Present(0, 0);
    }
}