Vertex Buffer는 단어 그대로 정점을 담고 있는 buffer를 의미한다.
DirectX11에서 Polygon을 그리기 위해서는 primitive data가 필요하다.
Primitive data는 점, 선, 면을 의미한다. (도형의 최소 단위)
[공식문서]
이러한 primitive data를 렌더링 하기 위해서는 vertex, index buffer를 필요로 한다.
struct SimpleVertex
{
XMFLOAT3 Pos;
};
Vertex data를 작성한다.
// create vertex buffer
constexpr SimpleVertex vertices[] =
{
XMFLOAT3(0.0f, 0.5f, 0.5f),
XMFLOAT3(0.5f, -0.5f, 0.5f),
XMFLOAT3(-0.5f, -0.5f, 0.5f),
};
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(D3D11_BUFFER_DESC));
bd.Usage = D3D11_USAGE_DEFAULT; // default로 사용
bd.ByteWidth = sizeof(SimpleVertex) * 3; // vertex 3개
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // vertex buffer로 바인드
bd.CPUAccessFlags = 0; // cpu access하지 않음.
Vertex buffer를 설정한다.
D3D11_SUBRESOURCE_DATA initData;
ZeroMemory(&initData, sizeof(D3D11_SUBRESOURCE_DATA));
initData.pSysMem = vertices; // 버퍼 데이터 초기화
Subresource를 작성한다.
Vertex buffer를 생성하는 것은 이전에 OpenGL의 경험을 통해서 쉽게 이해할 수 있었는데, Subresource라는 개념은 처음 들어봐서 찾아보았다.
[참고자료] [공식문서]
Resource는 복수의 Subresource 집합이라고 볼 수 있다.
즉, Subresource는 Resource의 전체 혹은 일부분을 참조할 수 있다.
또한 공식문서를 참고해보면, Buffer는 하나의 Subresource를 가질 수 있으나, texture의 경우에는 다른 것 같다.
Texture의 경우에는 Mipmap 혹은 LOD의 경우에 여러개의 Subresource를 가질 수 있는 거 같다.
hr = g_pDevice->CreateBuffer(&bd, &initData, &g_pVertexBuffer);
if (FAILED(hr))
{
return hr;
}
// set vertex buffer
UINT stride = sizeof(SimpleVertex);
UINT offset = 0;
g_pImmediateContext->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset);
// set primitive topology
g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
Vertex buffer를 생성 및 설정하고, primitive topology를 설정한다.
float4 VS(float4 Pos : POSITION) : SV_Position
{
return Pos;
}
float4 PS(float4 Pos : SV_Position) : SV_Target
{
return float4(1.0f, 0.0f, 0.0f, 1.0f);
}
가장 기본적인 Vertex, Pixel 셰이더 함수이다.
SV의 의미가 뭔지 알아봤는데, System value라는 뜻이라고 한다. 렌더링 파이프라인에서 특별한 의미를 가지는 Semantics라고 한다.
Semantics는 또 무엇인가. [공식문서]
공식 문서를 읽어봤을 때, 파라미터에 특정한 의미를 부여한다고 보면 될 거 같다.
SV_Position은 pixel의 좌표를 의미하고, SV_Target은 Color를 의미하는거 같다.
(왜 SV_Color가 아닌것이지..)
// compile the vertex shader
ID3DBlob* pVSBlob = nullptr;
hr = CompileShaderFromFile(L"Tutorial02.fx", "VS", "vs_4_0", &pVSBlob);
if (FAILED(hr))
{
MessageBox(nullptr, L"The FX file cannot be compiled. Please run this executable from the directory that contains the FX file.",
L"Error", MB_OK);
return hr;
}
위는 Vertex shader를 컴파일 하는 코드이다.
[공식문서]
ID3DBlob은 임의의 데이터를 반환하는 데 사용되는 Interface이다.
컴퓨터 과학에서는 Blob(Binary large object)으로 줄여 말하는 듯 하다.
즉, 컴파일 된 Binary 코드를 담을 수 있는 클래스이라고 생각된다.
HRESULT CompileShaderFromFile(LPCTSTR szFileName, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut)
{
HRESULT hr = S_OK;
DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
// Set the D3DCOMPILE_DEBUG flag to embed debug information in the shaders.
// Setting this flag improves the shader debugging experience, but still allows
// the shaders to be optimized and to run exactly the way they will run in
// the release configuration of this program.
dwShaderFlags |= D3DCOMPILE_DEBUG;
#endif
ID3DBlob* pErrorBlob;
hr = D3DCompileFromFile(
szFileName, // 파일명
nullptr, // 매크로 정의
nullptr, // Include 파일 정의
szEntryPoint, // 셰이더 Entry point 이름(메인 함수 이름)
szShaderModel, // 셰이더 컴파일 버전(vs_4_0 = Vertex shader version 4.0)
dwShaderFlags, // 컴파일 옵션
0, // 이펙트 컴파일 옵션
ppBlobOut, // 컴파일 된 Byte코드
&pErrorBlob // 에러 메세지
);
if (FAILED(hr))
{
if (pErrorBlob != nullptr)
{
OutputDebugStringA(static_cast<LPCSTR>(pErrorBlob->GetBufferPointer()));
}
else
{
pErrorBlob->Release();
}
return hr;
}
if (pErrorBlob)
{
pErrorBlob->Release();
}
return S_OK;
}
작성된 CompileShaderFromFile를 더 파보자.
첫 부분을 보면 셰이더를 컴파일 하기 위해서 옵션을 지정한다. [공식문서]
공식문서에 따르면, 굉장히 많은 옵션들이 존재하는데 D3DCOMPILE_ENABLE_STRICTNESS의 의미는 구식문법을 엄밀히 검사해 금지한다는 뜻이다.
D3DCOMPILE_DEBUG는 역시 작성된 뜻 그대로 DEBUG모드일 때를 의미한다.
D3DCompileFromFile가 실질적으로 Compile를 수행하는 함수이다. [공식문서]에 따르면 D3DCompile2를 사용하는 것을 추천하는데, 그 이유는 Window store에 출시한 앱에서 사용할 수 없다고 하는데, 지금은 공부 중인 단계이므로 D3DCompileFromFile를 사용하겠습니다 :>
// create the vertex shader
hr = g_pDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), nullptr, &g_pVertexShader);
if (FAILED(hr))
{
pVSBlob->Release();
return hr;
}
위 함수(CompileShaderFromFile)를 통해 컴파일 된 데이터가 ID3DBlob interface에 들어오게 되는데, 들어온 데이터를 통해서 Vertex Shader를 생성한다.
CreateVertexShader 함수를 알아보자. [공식문서]
다른 파라미터들은 알겠는데, pClassLinkage의 역할이 무엇인지 모르겠다.
그래서 공식문서를 뒤져보았는데.. HLSL에 동적 링크를 압축한다고 한다.
HLSL에 동적 링크(DLL)를 압축한다는 뜻이 뭔지 모르겠어서 더 찾아보았다.
그러나 찾을 수 없었다. github open source project들을 찾아봤는데 모든 곳에서 다 nullptr을 파라미터로 보내준다.
혹시 몰라서 ID3D11ClassLinkage interface가 사용되는 곳이 있는지 확인해보았는데, 없었다.
사용되지 않는 파라미터라는 결론이 나왔다. (혹시 아시는 분 있으면 댓글로 알려주세요 :>)
다음으로는 IA(Input assembly stage)에서 이 Vertex buffer의 데이터를 어떤 식으로 읽는지를 알려줘야한다.
// define the input layout (정점 데이터를 GPU에게 알려주는 구조체)
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{"POSITION", // Element의 목적이 무엇인이 알려주는 문자열 (POSITION, NORMAL, ...)
0, // 동일한 Element의 목적을 가진 정점의 Index
DXGI_FORMAT_R32G32B32_FLOAT, // 자료형
0, // 정점 버퍼 슬롯 index
0, // 정점 버퍼 offset
D3D11_INPUT_PER_VERTEX_DATA, // D3D11_INPUT_PER_VERTEX_DATA를 사용함.
0 // Instancing에 사용됨
},
};
constexpr UINT numElements = ARRAYSIZE(layout);
아직까지는 모든 멤버 변수의 역할이 정확히 무엇인지는 모르겠다. (정점 버퍼 슬롯에 관한 내용을 좀 더 공부해봐야겠다.)
// create the input layout
hr = g_pDevice->CreateInputLayout(layout, numElements, pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), &g_pVertexLayout);
pVSBlob->Release();
if (FAILED(hr))
{
return hr;
}
// set the input layout
g_pImmediateContext->IASetInputLayout(g_pVertexLayout);
설정한 구조체를 생성하고 설정하는 과정이다.
// compile the pixel shader
ID3DBlob* pPSBlob = nullptr;
hr = CompileShaderFromFile(L"Tutorial02.fx", "PS", "ps_4_0", &pPSBlob);
if (FAILED(hr))
{
MessageBox(nullptr, L"The FX file cannot be compiled. Please run this executable from the directory that contains the FX file.",
L"Error", MB_OK);
return hr;
}
// create the pixel shader
hr = g_pDevice->CreatePixelShader(pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), nullptr, &g_pPixelShader);
pPSBlob->Release();
if (FAILED(hr))
{
return hr;
}
Vertex shader를 만드는 과정에서 Compile 할 때, 메인함수 이름과 Shader 버전을 제외하고는 동일하다.

#include <Windows.h>
#include <d3d11.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
//--------------------------------------------------------------------------------------
// Using namespaces
//--------------------------------------------------------------------------------------
using namespace DirectX;
//--------------------------------------------------------------------------------------
// Structures
//--------------------------------------------------------------------------------------
struct SimpleVertex
{
XMFLOAT3 Pos;
};
//--------------------------------------------------------------------------------------
// Global Variables
//--------------------------------------------------------------------------------------
HINSTANCE g_hInstance;
HWND g_hWnd;
D3D_DRIVER_TYPE g_driverType = D3D_DRIVER_TYPE_NULL;
D3D_FEATURE_LEVEL g_featureLevel = D3D_FEATURE_LEVEL_11_0;
ID3D11Device* g_pDevice = nullptr;
ID3D11DeviceContext* g_pImmediateContext = nullptr;
IDXGISwapChain* g_pSwapChain = nullptr;
ID3D11RenderTargetView* g_pRenterTargetView = nullptr;
ID3D11VertexShader* g_pVertexShader = nullptr;
ID3D11InputLayout* g_pVertexLayout = nullptr;
ID3D11PixelShader* g_pPixelShader = nullptr;
ID3D11Buffer* g_pVertexBuffer = nullptr;
//--------------------------------------------------------------------------------------
// Forward declarations
//--------------------------------------------------------------------------------------
HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow);
HRESULT InitDevice();
void CleanupDevice();
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void Render();
//--------------------------------------------------------------------------------------
// Entry point to the program. Initializes everything and goes into a message processing
// loop. Idle time is used to render the scene.
//--------------------------------------------------------------------------------------
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
if (FAILED(InitWindow(hInstance, nCmdShow)))
{
return 0;
}
if (FAILED(InitDevice()))
{
CleanupDevice();
return 0;
}
MSG msg = { nullptr };
while (WM_QUIT != msg.message)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Render();
}
}
CleanupDevice();
return static_cast<int>(msg.wParam);
}
HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow)
{
// Register Window class
WNDCLASSEX wcex;
ZeroMemory(&wcex, sizeof(wcex));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = reinterpret_cast<HBRUSH>((COLOR_WINDOW + 1));
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = L"TutorialWindowClass";
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wcex))
{
return E_FAIL;
}
g_hInstance = hInstance;
RECT rc = { 0, 0, 640, 480 };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
g_hWnd = CreateWindowEx(NULL,
L"TutorialWindowClass",
L"Direct3D 11 Tutorial 1: Direct3D 11 Basics",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
rc.right - rc.left,
rc.bottom - rc.top,
nullptr,
nullptr,
hInstance,
nullptr);
if (g_hWnd == nullptr)
{
return E_FAIL;
}
ShowWindow(g_hWnd, nCmdShow);
return S_OK;
}
HRESULT CompileShaderFromFile(LPCTSTR szFileName, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut)
{
HRESULT hr = S_OK;
DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
// Set the D3DCOMPILE_DEBUG flag to embed debug information in the shaders.
// Setting this flag improves the shader debugging experience, but still allows
// the shaders to be optimized and to run exactly the way they will run in
// the release configuration of this program.
dwShaderFlags |= D3DCOMPILE_DEBUG;
#endif
ID3DBlob* pErrorBlob;
hr = D3DCompileFromFile(
szFileName, // 파일명
nullptr, // 매크로 정의
nullptr, // Include 파일 정의
szEntryPoint, // 셰이더 Entry point 이름(메인 함수 이름)
szShaderModel, // 셰이더 컴파일 버전(vs_4_0 = Vertex shader version 4.0)
dwShaderFlags, // 컴파일 옵션
0, // 이펙트 컴파일 옵션
ppBlobOut, // 컴파일 된 Byte코드
&pErrorBlob // 에러 메세지
);
if (FAILED(hr))
{
if (pErrorBlob != nullptr)
{
OutputDebugStringA(static_cast<LPCSTR>(pErrorBlob->GetBufferPointer()));
}
else
{
pErrorBlob->Release();
}
return hr;
}
if (pErrorBlob)
{
pErrorBlob->Release();
}
return S_OK;
}
HRESULT InitDevice()
{
HRESULT hr = S_OK;
RECT rc;
GetClientRect(g_hWnd, &rc);
const UINT width = rc.right - rc.left;
const UINT height = rc.bottom - rc.top;
UINT createDeviceFlags = 0;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_DRIVER_TYPE driverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
constexpr UINT numDriverTypes = ARRAYSIZE(driverTypes);
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};
constexpr UINT numFeatureLevels = ARRAYSIZE(featureLevels);
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(DXGI_SWAP_CHAIN_DESC));
sd.BufferCount = 1;
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
{
g_driverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDeviceAndSwapChain(
nullptr, // device를 생성하기 위한 video adapter(nullptr = default adapter)
g_driverType, // driver type
nullptr, // software handle
createDeviceFlags, // device flag
featureLevels, // feature level array
numFeatureLevels, // feature level array length
D3D11_SDK_VERSION, // sdk version
&sd, // swap chain structure
&g_pSwapChain, // 생성된 swap chain
&g_pDevice, // 생성된 device
&g_featureLevel, // 생성된 feature level
&g_pImmediateContext); // 생성된 device context
if (SUCCEEDED(hr))
{
break;
}
}
if (FAILED(hr))
{
return hr;
}
ID3D11Texture2D* pBackBuffer = nullptr;
hr = g_pSwapChain->GetBuffer(
0, // 백버퍼 index
__uuidof(ID3D11Texture2D), // back buffer를 다루기 위한 interface
reinterpret_cast<LPVOID*>(&pBackBuffer) // back buffer interface output
);
if (FAILED(hr))
{
return hr;
}
hr = g_pDevice->CreateRenderTargetView(
pBackBuffer, // View에서 접근할 리소스
nullptr, // RTV 정의
&g_pRenterTargetView); // RTV를 받아올 변수
// 사용한 back buffer를 Release
pBackBuffer->Release();
if (FAILED(hr))
{
return hr;
}
g_pImmediateContext->OMSetRenderTargets(
1, // Render target의 개수(최대 8개)
&g_pRenterTargetView, // Render target view의 배열
nullptr // Depth stencil view 포인터
);
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);
// compile the vertex shader
ID3DBlob* pVSBlob = nullptr;
hr = CompileShaderFromFile(L"Tutorial02.fx", "VS", "vs_4_0", &pVSBlob);
if (FAILED(hr))
{
MessageBox(nullptr, L"The FX file cannot be compiled. Please run this executable from the directory that contains the FX file.",
L"Error", MB_OK);
return hr;
}
// create the vertex shader
hr = g_pDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), nullptr, &g_pVertexShader);
if (FAILED(hr))
{
pVSBlob->Release();
return hr;
}
// define the input layout (정점 데이터를 GPU에게 알려주는 구조체)
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{"POSITION", // Element의 목적이 무엇인이 알려주는 문자열 (POSITION, NORMAL, ...)
0, // 동일한 Element의 목적을 가진 정점의 Index
DXGI_FORMAT_R32G32B32_FLOAT, // 자료형
0, // 정점 버퍼 슬롯 index
0, // 정점 버퍼 offset
D3D11_INPUT_PER_VERTEX_DATA, // D3D11_INPUT_PER_VERTEX_DATA를 사용함.
0 // Instancing에 사용됨
},
};
constexpr UINT numElements = ARRAYSIZE(layout);
// create the input layout
hr = g_pDevice->CreateInputLayout(layout, numElements, pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), &g_pVertexLayout);
pVSBlob->Release();
if (FAILED(hr))
{
return hr;
}
// set the input layout
g_pImmediateContext->IASetInputLayout(g_pVertexLayout);
// compile the pixel shader
ID3DBlob* pPSBlob = nullptr;
hr = CompileShaderFromFile(L"Tutorial02.fx", "PS", "ps_4_0", &pPSBlob);
if (FAILED(hr))
{
MessageBox(nullptr, L"The FX file cannot be compiled. Please run this executable from the directory that contains the FX file.",
L"Error", MB_OK);
return hr;
}
// create the pixel shader
hr = g_pDevice->CreatePixelShader(pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), nullptr, &g_pPixelShader);
pPSBlob->Release();
if (FAILED(hr))
{
return hr;
}
// create vertex buffer
constexpr SimpleVertex vertices[] =
{
XMFLOAT3(0.0f, 0.5f, 0.5f),
XMFLOAT3(0.5f, -0.5f, 0.5f),
XMFLOAT3(-0.5f, -0.5f, 0.5f),
};
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(D3D11_BUFFER_DESC));
bd.Usage = D3D11_USAGE_DEFAULT; // default로 사용
bd.ByteWidth = sizeof(SimpleVertex) * 3; // vertex 3개
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // vertex buffer로 바인드
bd.CPUAccessFlags = 0; // cpu access하지 않음.
D3D11_SUBRESOURCE_DATA initData;
ZeroMemory(&initData, sizeof(D3D11_SUBRESOURCE_DATA));
initData.pSysMem = vertices; // 버퍼 데티
hr = g_pDevice->CreateBuffer(&bd, &initData, &g_pVertexBuffer);
if (FAILED(hr))
{
return hr;
}
// set vertex buffer
UINT stride = sizeof(SimpleVertex);
UINT offset = 0;
g_pImmediateContext->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset);
// set primitive topology
g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return S_OK;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (uMsg)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
void Render()
{
constexpr float clearColor[4] = { 0.0f, 0.125f,0.3f,1.0f };
g_pImmediateContext->ClearRenderTargetView(g_pRenterTargetView, clearColor);
g_pImmediateContext->VSSetShader(g_pVertexShader, nullptr, 0);
g_pImmediateContext->PSSetShader(g_pPixelShader, nullptr, 0);
g_pImmediateContext->Draw(3, 0);
g_pSwapChain->Present(0, 0);
}
void CleanupDevice()
{
if (g_pImmediateContext)
{
g_pImmediateContext->ClearState();
}
if (g_pVertexBuffer)
{
g_pVertexBuffer->Release();
}
if (g_pVertexLayout)
{
g_pVertexLayout->Release();
}
if (g_pVertexShader)
{
g_pVertexShader->Release();
}
if (g_pPixelShader)
{
g_pPixelShader->Release();
}
if (g_pRenterTargetView)
{
g_pRenterTargetView->Release();
}
if (g_pSwapChain)
{
g_pSwapChain->Release();
}
if (g_pImmediateContext)
{
g_pImmediateContext->Release();
}
if (g_pDevice)
{
g_pDevice->Release();
}
}