C++ 11부터 지원되는 Range-based for(범위 기반 for)는 기존의 C++ STL의 컨테이너, 배열 등에서 반복문을 좀 더 쉽게 사용하게 해주는 기능이다.
이 기능은 Microsoft의 Visual C++ 기능인 for each문과 그 기능과 사용법이 비슷해서 쉽게 사용할 수 있다.
기존의 for문과 iterator를 이용한 방법과 Visual C++ for each문을 이용한 방법 그리고 C++ 11 표준의 Range-based for문을 이용한 방법을 비교하자면 아래와 같다.
vector<Object*> vect;
// 기존의 for문과 auto 키워드를 이용한 iterator 반복 처리 for (auto obj = vect.begin(); obj != vect.end(); obj++) { // Object에 처리 }
// Visual C++ 타입의 for each문을 이용한 반복 처리 for each(auto obj in vect) { // Object에 처리 } // C++ 11타입의 Range-based for문을 이용한 반복 처리 for (auto obj : vect) { // Object에 처리 }
코드를 살펴보면 분명하게 미세하게나마 코드의 양이 줄어드는 것이 보인다. 같은 동작을 하는 데도 기존의 for문과 auto 형식의 복잡한 타이핑 없이 처리할 수 있다는 것은 프로그래머의 피로도를 상당히 줄여준다.(물론 auto 키워드가 나오기 전에는 더더욱 복잡했었다.)
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
Multi thread 프로그래밍을 할때 현재 thread가 작업하는 메모리를 다른 thread에 덮어쓰거나 잘못 사용하는 일이 발생하지 않도록 주의해야한다.
이러한 문제를 대비하기 위한 기법이 바로 thread 동기화인데 Critical Section, Semaphore, Mutex 등이 있다.
이러한 것들을 사용하지 않는다면
Tread 동기화를 사용하지 않은 스레드 작업
#include <iostream>
#include <Windows.h>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;
int iArr[500]; // 두 thread가 작업할 메모리 공간
void test1() // test1 함수는 iArr에 0을 채운다.
{
for (int i = 0; i < 500; i++)
{
iArr[i] = 0;
Sleep(1);
}
}
void test2() // test2 함수는 iArr에 1을 채운다.
{
for (int i = 0; i < 500; i++)
{
iArr[i] = 1;
Sleep(1);
}
}
int main()
{
// 각 함수를 스레드로 작동
thread t1(test1);
thread t2(test2);
// 두 함수가 끝나기를 기다린다.
t1.join();
t2.join();
// 결과값 출력
for (int i = 0; i < 500; i++)
{
cout << iArr[i];
if ((i +1) % 50 == 0)
{
cout << endl;
}
}
}
위의 결과 처럼 중간 중간 값을 덮어씌워서 오작동을 일으킬 수 있다.
하지만 위에서 말한 thread 동기화 기법을 사용하면 이 문제는 해결된다.
Mutex를 사용한 thread 작업
#include <iostream>
#include <Windows.h>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;
int iArr[500];
mutex m; // Thread Lock을 걸 Mutex 클래스
void test1()
{
m.lock(); // 메모리에 lock을 걸어 다른 thread에서 사용하지 못하게 한다.
for (int i = 0; i < 500; i++)
{
iArr[i] = 0;
Sleep(1);
}
m.unlock(); // 메모리에 대한 작업이 끝난 이후에 lock을 해제한다.
}
void test2()
{
m.lock();
for (int i = 0; i < 500; i++)
{
iArr[i] = 1;
Sleep(1);
}
m.unlock();
}
int main()
{
// 각 함수를 스레드로 작동
thread t1(test1);
thread t2(test2);
// 두 함수가 끝나기를 기다린다.
t1.join();
t2.join();
// 결과값 출력
for (int i = 0; i < 500; i++)
{
cout << iArr[i];
if ((i +1) % 50 == 0)
{
cout << endl;
}
}
}
Mutex를 사용하면 처음 lock을 건 thread에서 작업이 끝난 이후에야 다른 thread에서 그 메모리에 접근해서 작업이 가능하기 때문에 모든 배열에 1이 출력이 된다.
이렇게 별 문제가 없어 보이는 thread lock에는 문제가 있는데 바로 Race Condition이다. 이 race condition은 일반적으로 1번 thread와 2번 thread가 있고 데이터가 담긴 메모리 A, B가 있을 때, 1번 thread가 A메모리에 lock을 건 상태에서 B메모리에 작업을 하려고 하고, 2번 thread는 B메모리에 lock을 건 상태에서 A메모리에 작업을 하려고 할때 발생한다. 이렇게 되면 1번 thread와 2번 thread는 서로 자신의 작업이 끝나야 각각의 메모리의 lock 해제하는데 서로에게 lock이 걸린 메모리 때문에 작업을 그 이후로 진행할 수 없기 때문에 프로그램은 작동을 정지하고 만다. 이런 상황을 다른 말로 dead lock, 교착상태라고도 한다.
하지만 scoped lock은 사실 이런 완벽한 교착 상태를 예방하기 위한 것이라기 보다는 사소한 프로그래머의 실수를 방지하기 위한 것이다. 다음의 코드를 보자.
프로그래머의 실수!
#include <iostream>
#include <Windows.h>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;
int iArr[500];
mutex m;
void test1()
{
// 이 프로그래머는 훌륭하게 lock을 걸었지만
m.lock();
for (int i = 0; i < 500; i++)
{
iArr[i] = 0;
Sleep(1);
}
// unlock을 까먹고 하지 않았다!
}
void test2()
{
m.lock();
for (int i = 0; i < 500; i++)
{
iArr[i] = 1;
Sleep(1);
}
m.unlock();
}
int main()
{
thread t1(test1);
thread t2(test2);
t1.join();
t2.join();
for (int i = 0; i < 500; i++)
{
cout << iArr[i];
if ((i +1) % 50 == 0)
{
cout << endl;
}
}
}
위의 코드처럼 실수로 thread lock을 건 이후에 작업이 끝나고 unlock을 까먹는다면 2번 thread는 1번 thread가 걸어둔 lock이 해제되기를 영원히 기다릴 것이다. 위 코드처럼 간단한 thread 예제라면 이러한 문제를 쉽게 찾아내겠지만 수만줄을 넘어가고 여러 종류의 thread가 여러 개 돌아가는 커다란 프로그램에서 저런 기억하기 어려운 사소한 실수를 범하게 된다면 얼마나 긴 시간을 허비하게 될까?
그래서 나온 것이 scoped lock이라는 것인데 이것의 원리는 매우 간단하다. 우리가 사용하는 Mutex를 하나의 클래스로 가볍게 감싸는 것이다. 그리고 클래스의 생성자가 실행될 때 lock()을 실행하고 클래스의 소멸자가 실행될 때 unlock()을 실행해 주는 것이다.
이것은 프로그래머가 직접 구현해도 될 정도로 간단한 작업이지만 표준 C++에서는 이미 지원되고 있으니 해당 기능을 사용하면 된다.
Scoped Lock 사용법
#include <iostream>
#include <Windows.h>
#include <thread>
#include <memory> // Scoped lock 기능을 사용하기 위해 include 해야하는 header
#include <mutex>
using namespace std;
int iArr[500];
mutex m;
void test1()
{
lock_guard<mutex> g(m);
// lock_guard 클래스의 템플릿에 mutex 클래스를 넣고 생성자에 우리가 사용하는 mutex 객체를 넣어주면 된다.
// lock_guard 객체가 생성될 때 자동으로 thread에 lock이 걸린다.
for (int i = 0; i < 500; i++)
{
iArr[i] = 0;
Sleep(1);
}
// 그리고 함수가 끝날 때 or 스코프를 벗어날 때 자동으로 lock_guard 객체가 소멸되면서 thead lock이 해제된다.
}
void test2()
{
lock_guard<mutex> g(m);
for (int i = 0; i < 500; i++)
{
iArr[i] = 1;
Sleep(1);
}
}
int main()
{
thread t1(test1);
thread t2(test2);
t1.join();
t2.join();
for (int i = 0; i < 500; i++)
{
cout << iArr[i];
if ((i +1) % 50 == 0)
{
cout << endl;
}
}
}
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
프로그래밍에서 사용되는 용어의 주된 특징은 그 원어는 거의 전부(어쩌면 거의가 아니라 그냥 전부)가 영어에서 온다는 것이다. 그렇기 때문에 발생하는 문제는 비영어권인 지역의 사람이 정보를 접하기 위해서는
영어로 된 자료를 직접 번역해서 읽는다.
번역된 자료를 읽는다.
이 두 가지 중 하나를 선택해야 한다. 물론 오랫동안 프로그래밍 해온 개발자로서는 제대로 된 정보를 얻기 위해서는 1번이 좀 더 나은 선택이라는 것을 알고 있고 어느 정도 영어에 친숙해진 이후라 1번을 주로 택하게 되겠지만 아직 프로그래밍을 시작한지 얼마 안된 개발자/지망생은 일반적으로 영어에 친숙하지 않고 번역된 자료를 선호하는 경향이 좀 있기도 하고, 쉽게 접할 수 있는 자료가 번역된 자료이기 때문에 2번을 주로 하게 된다.
물론 번역된 쉽게 접할 수 있는 자료라는 것은 아주 좋은 것이지만 몇 가지 문제가 항상 읽는 사람을 큰 혼란에 빠뜨린다.
용어의 과도한 한글화로 인한 원래 용어를 알 수 없게 되는 경우
용어의 한글화를 하는 도중에 그 단어의 표면적인 뜻만을 번역해서 그 용어가 의미하는 바를 알 수 없게 되는 경우
딱딱한 번역체로 인해서 아주 읽기 힘든 글이 되는 경우
뭐... 번역자들에게도 고충이 없겠냐마는 위의 세 가지 문제는 언제나 외국의 문물을 받아들이려는 학생을 힘들게 한다.
이번에 surface라는 용어에서는 그런 2번의 문제를 매우 심하게 느꼈는데 DXGI_USAGE의 열거형들을 조사하면서 이 surface라는 단어를 만났다. 그래서 이 열거형들의 내용이 번역된 자료를 찾아나섰는데 거기엔 이 surface를 다른 설명없이 그냥 표면이라는 단어 하나로 번역해 놓은 것이었다.
Direct3D의 surface라는 용어를 표면이라는 단어로 진정한 용어의 뜻 대신 그냥 표면적인 단어의 뜻만 보여주는 상황이라니... 이 얼마나 어이없는 일인가?
이것은 그리 똑똑하지는 못한 내 생각이지만 번역을 할때 이 원칙을 세워 지켰으면 좋겠다.
한글로 번역했을때 그 뜻이 쉽게 혹은 누구나 알기 쉽게 드러나지 않는 용어는 섣불리 한글로 번역하지 않고 그 용어에 대한 충분한 설명을 곁들일 것.
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
DirectX의 기능 중 일부는 더 이상 DirectX SDK와 함께 배포되지 않는다는 것을 명심해야한다. 하지만 그것들은 Windows SDK와 함께 배포된다. 그래서 우리는 D3D에서 디버그 출력을 활성화하려고 할 때 직면할 수 있는 모든 가능한 경우를 커버하려고 한다.
DirectX SDK(June 2010)과 Windows7에서 작업하는 경우
Windows7 or Windows8에 있는 Windows SDK를 사용하는 경우
Windows8.1과 거기에 속한 Windows SDK를 사용하는 겨우
새로운 DirectX Control Panel
DirectX Control Panel을 이용하면 D3D Debug를 활성화하고 다른 것들을 관리할 수 있는데 우선은 DirectX SDK(June 2010) Version의 Control Panel과 Windows SDK Version의 Control Panel을 구별하는 법을 배워야 한다.
이것은 아주 쉬운데 : 새로운 버전의 DirectX Control Panel은 단 하나의 탭만을 포함하고 있다(Direct3D 10.x/11.x):
D3D 10.x 나 11.x로 개발을 할 때는 이전의 버전의 Control Panel으로는 아무런 효과가 없을 것이기 때문에 새 버전의 Control Panel을 사용해야 한다.
여전히 D3D9과 DX SDK 2010으로 개발하려고 한다면 이전 버전의 Control Panel을 사용하면 된다.
Note
"Feature Level 제한"과 같은 패널의 새로운 기능을 배우려면 위의 동영상을 참조하라.
Windows7
D3D 9.x
만약 여전히 D3D9으로 개발을 하려고 한다면, 솔직히 말해서 새로운 버전을 사용하는 것을 심각하게 고려해보기를 바란다. 하지만 그렇게 할 수 없는 경우라면, 응용 프로그램에서 Debug를 활성화 해야한다. 위에서 설명했던 DX SDK 2010 버전의 Control Panel을 사용하고, Direct3D 9 탭에서 "Use Debug Version of Direct3D 9"을 찾아서 선택하고, Debug Output Level을 "More" 쪽으로 움직여야 한다.
다음의 이미지처럼 :
DirectX 라이브러리의 Debug 버전을 사용하도록 DirectX 응용 프로그램을 강제해야 한다. 그래서 바로 Visual Studio에서 Debug Output을 보기 시작해야 한다.
Managed D3D9 applications (SlimDX, SharpDX 및 유사한 wrappers)
만약 C#을 이용해서 개발하는 경우에는, Visual Studio에서 기본 프로젝트 속성의 Debug 탭에서 "Enable native code debugging" 플래그를 활성화해야 한다는 것을 명심해야 한다. 그렇지 않으면 기본 디버그 출력을 출력 창을 통해서 얻을 수 없게 된다.
D3D 10.x / 11.x
Important Note
D3D 10.x와 11.x의 Debugging용 필수 구성 요소는 더 이상 이전의 DirectX SDK(June 2010)과 함께 설치되지 않는다. D3D 10.x와 11.x의 Debugging용 필수 구성 요소를 설치하기 위해 (Win7이 있는 경우에도) Windows 8 SDK를 설치해야 한다. 필수 구성 요소를 얻지 못한 경우에는 "debug" 플래그 장치의 생성에 실패한다(자세한 정보는 아래를 참조). 구성 요소를 가지고 있는지 확인하는 쉬운 방법은 C:\Windows\System32에 새로운 DX Control Panel이 있는지 확인하는 것이다. 해당 경로에 "DXCpl.exe" 파일이 있는지 확인하면 된다.
D3D 10.x/11.x에서의 Debug Output을 활성화사는 법은 조금 다르다. 설정은 응용 프로그램 별로 해야한다(Control Panel의 목록에 exe를 추가하고, 거기에 대한 특정 구성을 설정해야한다). 다음을 따라하면 된다.
새로운 DirectX Control Panel을 열고 Direct3D 10.x / 11.x 탭으로 이동한다.
DX panel으로 컨트롤하고자 하는 응용 프로그램의 목록에 exe파일을 추가하기 위해 "Edit List"를 클릭한다.
하나의 창이 팝업된다(아래의). "..."을 클릭하고 추가하고자하는 exe파일이 있는 경로로 이동하고, exe 파일을 선택하고 "Ok" 버튼을 누른다.
메인 탭으로 돌아가서 원하는 구성과(아마 강제 debug 출력을 하기 위한 "Force On"을 설정하기를 원할 것이다) 보고 싶지 않은 모든 메시지 유형을 끄는 "mute all ther message"(만약 그렇게 하기를 원한다면)을 선택한다.
exe 파일을 Control panel의 관리 응용 프로그램 목록에 등록했다면, 다음 단계는 D3D device의 DirectX Debug Layer에 연결하는 것이다.
아래의 링크에서 더 많은 정보를 찾을 수 있다. 하지만 기본적으로 해야할 것은 D3D11_CREATE_DEVICE_DEBUG 플래그를 포함한 생성 플래그를 이용하여 device를 만드는 것이다.
Managed D3D 10.x / 11.x 응용 프로그램(SlimDX, SharpDX and 유사한 wrappers)
C#으로 개발할 때는 D3D 9과 같이, Visual Studio의 메인 프로젝트 속성의 Debug 탭에서 "Enable native code debugging" 플래그를 활성화 시키는 것을 기억해야 한다. 그렇지 않다면 기본 디버그 출력은 Output window를 통해서 얻을 수 없다(자세한 내용은 이 게시물의 위 내용을 참고).
Windows 8.x + Windows SDK
이번 파트는 Windows 8.1와 새로운 Windows SDK 버전을 사용하여 작업하는 경우를 커버한다.
D3D 9.x
Windows 8에서의 Debugging D3D 9 응용 프로그램은 Windows 7에서와 마찬가지로 정확하게 같은 동작을 한다. 당연하게도 새로운 Windows SDK는 D3D9을 구성하는 도구를 포함하고 있지 않기 때문에, 이전 버전의 Control panel에 액세스하려면 DX SDK(June 2010)을 설치해야만 한다.(나의 모든 컴퓨터가 Windows 8.1로 업데이트되어 이런 작업이 모두 작동하는지 확인하지 못했다. 여기에 대해서 어떠한 피드백이든 환영한다.)
확실하게 말할 수 있는 것은, 불행하게도, D3D9 debugging은 Windows8.1에서 작동하지 않을 것으로 보인다는 것이다. 만약 이전 버전의 DX control panel을 열면 D3D9 탭의 모든 debug 부분이 회색으로 표시되는 것을 볼 수 있을 것이다.(나는 어떻게 해서는 이것을 되도록 노력했지만 운이 없는지 실패했다. 그래서 만약에 이것을 가능하게 한다면 나에게 알려주기 바란다.)
D3D 10.x / 11.x
D3D 10.x와 11.x의 Debug output 활성화는 Windows 7과 거의 동일하다. 이때를 제외하면 DXSDK 폴더 대신에 C:\Windows\System32에 위치한 새로운 버전의 DX Control Panel을 사용해야 한다.(Enabling debug output for D3D 10.x and 11.x is pretty much the same as in the case of Windows 7, unless this time you will need to use the NEW version of the DX Control Panel, located in C:\Windows\System32 instead of the usual DXSDK folders.)
또한, D3D11_CREATE_DEVICE_DEBUG 생성 플래그를 지정하여 Device를 생성해야하고(위의 설명을 참조), C#으로 개발하는 경우라면, 메인 프로젝트에서 "Enable native code debugging" 옵션을 활성화 해야한다.
문제 해결
응용 프로그램은 작동하지만 debug output을 얻지 못하는 경우 : D3D9으로 개발하는 경우 이전 버전의 Control Panel의 debug 라이브러리를 활성화해야 한다. 또한 C#으로 작업하는 경우에는, "Enable native code debugging" 옵션을 확실하게 활성화해야 한다. D3D 10/11으로 작업하는 경우에는, D3D11_CREATE_DEVICE_DEBUG 플래그를 이용하여 장치를 생성하고, 작업하는 응용 프로그램을 DX Control Panel의 프로그램 관리 목록에 등록해야 한다. 모든 경우에 항상 적절한 DX Control Panel을 사용해야 한다(자세한 것은 위의 내용을 참조).
D3D 10.x / 11.x 에서 debug 생성 플래그를 이용한 device 생성을 시도하는 동안 응용 프로그램이 실패하는 경우 : 이것은 일반적으로 올바른 SDK가 설치되어 있지 않은 경우에 발생한다. 만약에 Windows 7 이나 Windows 8을 사용한다면 Windows8 SDK를 설치해야 한다. 만약에 최신 Windows 8.1을 사용하고 있다면 Windows 8.1 SDK를 설치해야 한다. Windows 8.1은 Windows 8.0 SDK 버전과 호환되지 않는다. D3D 10.x/11.x 구성요소가 있는지 확인하는 간단한 방법은 C:\Windows\System32 의 경로에 새로운 버전의 DX Control Panel이 있는지 확인하는 것이다.
원문 링크
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
Direct3D는 어떻게 보면 두 개의 레이어가 따로 있다. C++ 쪽에서 Debug를 돌려도 렌더링 쪽에서는 느려질 수 있는 것은 그렇게 많지 않다.
Direct3D에서는 실제 GPU로 들어가는 부분에 Direct3D Runtime이 있다. 여기에도 Debug 빌드와 Release 빌드가 있는데 이것은 C++ 코드와 전혀 상관없이 DirectX Control Panel에 들어가서 수정할 수 있는 것이다. 그것을 Release로 만들면 최적화가 적용되어서 빨라지고 Debug로 만들면 최적화 기능이 꺼지고 Debug 기능들로 인해서 느려진다.
일반적으로 대부분의 프로그래머는 빠르고 에러도 덜 뱉는다는 이유로 Direct3D를 Release로 켜놓고 사용하는데 어느 순간엔가는 이것을 Debug 빌드로 돌려서 Debug를 해야하는 순간이 온다. Memory leak 문제가 발생할 수도 있고, 참조 카운터 문제가 있을 수도 있다. 그래서 이것을 Debug로 바꾸는 순간 엄청난 Warning과 Error를 발생시키는 경우가 있다. 매 프레임마다 같은 혹은 비슷한 장면은 계속해서 그려야하는 렌더링의 특징 상 break를 걸기도 어렵고 Debugging도 어려워진다.
그렇기 때문에 그래픽 프로그래머는 반드시 Direct3D Runtime을 Debug 빌드로 두고 작업을 해야한다. Release 빌드에선 에러나 경고를 뱉지 않던 코드가 Debug 빌드에서 쏟아져 나올 수 있다. 그런 것을 무시하고 Release 빌드로만 작업하고 그런 에러와 경고가 쌓인 상태로 나중에 다른 프로그래머가 Debug를 하려고 하면 수많은 경고와 에러로 인해서 도저히 Debug 작업을 할 수 없게 된다. 결국 작업을 하기 위해서는 이 수많은 에러와 경고들을 처리할 수 밖에 없는데 이렇게 수 개월치 쌓인 문제를 한꺼번에 처리하는 것보다는 에러 하나, 경고 하나가 발생했을때 바로 처리하는 것이 훨씬 깔끔하고 빠른 문제 처리 방법이다.
렌더링 쪽에서는 크게 Debug 빌드가 느린 것 같지는 않은데 게임 쪽에서는 프레임 속도가 제대로 안나오면 문제가 있을 수도 있다.
참고 :: https://www.youtube.com/watch?v=eOF6IZU4nxQ
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
Severity Code Description Project File Line Suppression State
Error LNK2019 unresolved external symbol _D3D11CreateDevice@40 referenced in function "protected: bool __thiscall D3DApp::InitDirect3D(void)" (?InitDirect3D@D3DApp@@IAE_NXZ)
Direct3D 11 프로그래밍과 관련해서 LNK2019 Error 응답하지 않는 외부 심볼 D3D11CreateDevice 에 대한 해결책.
d3d11 정적 라이브러리 파일을 연결해 주어야 한다.
d3d11과 관련된 함수를 사용하는 최상위 헤더에
#pragma comment(lib, "d3d11.lib")
를 추가해줄 것.
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.