Direct X 프로그래밍 학습에 대한 조언

간만에 페이스북 DirectX 그룹에 질문이 올라와서 답변을 달았다.
답변 달다보니 몇 가지 강조할 내용은 정리해서 어디 남겨야겠단 생각이 들었다.

DirectX 프로그래밍을 학습하려고 한다면…

1. 현대적인 C++과 달리 모든 DirectX, 그러니까 DirectX 2부터 12까지 모두 포인터를 죽도록 사용하며 아주 능숙하게 사용해야합니다. 포인터 사용에 능숙하지 못하다면 C/C++프로그래밍 기초부터 다시 학습하도록 합니다.

2. DirectX 프로그래밍을 학습하려면 COM에 대한 이해가 필요합니다. 여기서 필요한 COM지식은 C++의 virtual function과 다중상속 개념을 이해하고 실습하는 것과 거의 같습니다. 실제로 in-process COM은 C++의 virtual function과 다중상속 기능을 이용해서 구현되었습니다. 시중의 COM서적들은 불필요하게 방대한 내용을 담고 있으며 DirectX와 같은 in-process COM과 너무 동떨어진 내용을 담고 있습니다. 따라서 COM책을 구해서 본다면 개요 정도만 읽고 넘어갑니다. 그보단 DirectX코드를 사용할때 disassmbly코드를 따라가는 쪽이 훨씬 도움이 많이 됩니다. 혹은 MSDN을 참고하도록 합니다.

3. DirectX 12부터 MS는 샘플코드에 CComPtr클래스 떡칠을 하기 시작했습니다. 자기들은 그게 편할지 모르겠지만 이 망할 캡슐화된 클래스는 동작원리를 감춥니다. 초보자에게는 혼란만 가중시킬 뿐입니다. COM에 대해서 잘 모른다면 CComPtr을 사용해선 안됩니다. 완전히 잘못 사용하게 될뿐더러 뭐가 잘못됐는지 이해하지 못합니다.

4. 3의 연장선상에서 하는 얘긴데 학습단계에서 빌어먹을 wrapper클래스는 가져다 쓰지도 만들어쓰지도 않도록 합니다. raw포인터와 raw api만을 사용합니다.

5. DirectX 9을 사용해봤다면 DirectX 11을 학습하는데 큰 어려움이 없습니다. 그렇다고 처음 시작하면서 9를 먼저 학습할 필요는 없습니다. DirectX 9를 모르고 DirectX 11을 처음 학습한다면 약간 어렵겠지만 큰 문제가 되진 않습니다. 그러니 11부터 시작하도록 합니다.

6. 10과 11은 거의 유사합니다. 10에서 Compute Shader와 멀티 스레드 렌더링 관련 기능이 추가된게 11입니다. 현재 10으로 개발하는 게임은 없고 99%의 Windows및 XBOX게임은 DirectX 11 타겟으로 개발합니다. 그러니 10도 건너뜁니다. 다만 DirectX 10의 샘플 코드는 학습 및 응용에 도움이 됩니다.

7. DirectX 12를 학습하려고 한다면 DirectX 11에 대해 빠싹하게 알고 있어야 합니다. DirectX 11과 12는 완전 다르지만 그래도 11은 기본적으로 알고 있어야 합니다. 11을 알면 도움이 되는 정도가 아니고 11을 모르면 12는 결코 이해하지 못합니다. 첫째로는 12의 API이름과 11의 API이름이 거의 같기 때문이고, 둘째로는 11의 기본적인 사항에서 무엇이 어떤 의도로 어떻게 변경되었는가를 이해해야 12의 프로그래밍이 가능하기 때문입니다. 따라서 11은 필수입니다.

요즘같이 엔진 가져다 쓰는 개발이 대부분인 시대에 DirectX를 학습하려는 사람은 거의 없겠지만 만약 시도하는 이가 있다면 이 글을 읽고 삽질을 덜 하길 바랍니다.


Direct X 프로그래밍 학습에 대한 조언”에 대한 답글 1개

  1. 굉장히 공감하는 글이네요
    특히 4번 같은 경우 물방울책이나 인터넷 11 관련 강좌 시리즈 보면 프레임워크나 wrapper 클래스 바탕으로 진행하는 경우 있어서…차라리 MS쪽 샘플 자료 보면서 맨땅의 헤딩으로 공부헀던 기억이 나네요
    11도 샘플도 12 나올 때쯤인가 후로 나온 것들 보면 comptr 떡칠 되었던 기억도 나고요

    좋아요

  2. 조언 감사드립니다. 그냥 집에 11책이있어 이걸로 공부하고 있는데 하면서도
    9를 건너뛰어도 되는지 12책이 나왔는데 12로 했어야하는것 아닌가 내심 갈등을 했었는데 깔끔하게 정리해주셔서 감사드립니다.

    좋아요

  3. 한달전즈음 2회차로 포프님과 영천님 대화영상을 보니까 ,

    그땐 안들리던 가상함수테이블이 들리더라구요. 면접질문으로 많이 쓰인다고,

    영천님이 정말 관심이 있다면 디스어셈블리로 쫓아가볼것이다 라고 하셨는데

    처음에 전 가상함수테이블을 들었을 때 , 작동과정이 오퍼레이터 함수같이

    부모클래스포인터 변수 에 자식클래스포인터를 대입하면 아마 감춰진 부모클래스멤버 함수포인터 변수의 값이

    자식클래스 함수포인터로 바뀔것이다. 어느정도 말이 된다 생각해서 넘어갔었는데요.(디스어셈블리도 몰랐고 , 볼 줄

    도 몰라서 , 면접에서 저렇게 대답하면 100% 탈락인가요?이건 그냥 자기상상대로

    대답하면 어떻게되는지 알고싶어서 ..)

    오늘에서야 관련 지식글을 보다가 틀렸다는 사실을 알았습니다.(설명글과 그 설명을 디스어셈블리로 도출)

    그래서 의문이 생기는 것 들은 다른데서 찾을게아니라 디스어셈블리를 알면 그 자리에서

    해결이 되겠다 싶더라고요. 이런 신념이 맞는걸까요? 아니 이건 확신하지만

    현재 WIN32API 2D 게임프로그래밍 중반즘 공부하고 있는데 , 게임하나라도 완성해보고

    (조금이나마 더 프로그래밍에 익숙해지고 ) 어셈블리를 공부하는게 좋을까요? 아니면 이제부터라도 곁들여

    공부하는게 좋을까요?

    또 너무나 얕은 지식을 알아가면서도 하다보면 어떤 신념 같은게 생깁니다.

    예를들어 예전엔 “그냥 모든게 숫자고 어떤 문제들을 만나도 숫자로 생각하면 해결되는구나” 라거나,

    vector 에 배열을 동적할당 하고 카운트가 다 차면 , 두배의 배열을 할당해서 카피하고 기존의배열은 메모리해제

    하는걸로봐서 컴퓨터도 너무나 물리적인,물리적(인스탄스)으로 돌아가는구나 ,

    현재 map 이나 vector , list를 사용하고 있어서 조금씩

    와닿고있는 조건문과 반복문이면 된다라는 포프님영상제목을보고 나중에 저 신념으로 바뀌겠구나

    생각하고 있습니다.

    문제인지 궁금한 부분은 항상 지금껏 생각하고 거기서 기억나는 신념들을 여럿 떠올리며 그 신념을 기반으로

    코딩하려고 하는 것 입니다.(코딩을 하기가 심플해져서 수월해지는 “기분”때문에, 착각인지 모르지만)

    이런경험이 있으셔서 그렇게하면안된다 라던지,

    아니면 어떤 신념같은게 있으신지 궁금합니다.

    좋아요

    1. 면접에 vtable을 물어봤는데 대답을 잘못했다…라고 하면 그것 때문에 떨어질거라고는 생각하지 않습니다. 보통 면접관 입장에서 여러 질문을 던지고 그 중 하나 대답 못했다고 바로 떨어뜨리진 않거든요.
      하지만 vtable질문을 포함해서 대부분 대답을 잘 못했다면 떨어지겠죠. 저라면 vtable 질문에 좋은 답변을 했다면 그것만으로도 높은 점수를 줄겁니다. 그런데 그거 하나 대답 못했다고 바로 떨어뜨릴것 같지는 않네요.

      어셈블리 코드에 대해서 말씀을 드리자면, 결국 일을 제대로 하려하면 어셈블리 코드 안볼수가 없게 됩니다. 디버깅도 디버깅이지만 소스코드만으로는 원리가 이해되지 않을때가 많죠. 이럴때 어셈블리 코드로 따라가보면 원리를 이해할 수 있습니다. 전 디버깅할때디스어셈블창을 많이 들여다봅니다만, 그보다도 원리를 알고 싶을때, 예를 들면 exception의 동작원리( https://megayuchi.com/2017/12/14/c-exception의-내부-구현에-대한-잡설/ )라든가 SRWLock의 동작원리(https://megayuchi.com/2017/06/25/srwlock-빠른-성능의-비결/) 라든가 궁금할때 디스어셈블된 코드를 봅니다.

      신념이라고 하기엔 좀 어울리지 않는것 같고요. C/C++에서 어떤 기능을 사용해도 얘네들은 네이티브 코드이기 때문에 어셈블리로 할 수 있는거보다 더 할 수 있는건 없습니다. 따라서 STL이든 C++ 20의 새로운 기능이건간에 디스어셈블리 코드로 따라가보면 원리는 다 이해할 수 있고 같은 기능을 스스로 만들수도 있습니다. 그래서 네이티브 언어의 새로운 기능에 대해선 전 별로 관심이 없는 편이고요. 두려움도 없습니다. 엄청 마법같은 기능은 처음부터 존재할수 없거든요.

      어셈블리어를 학습해야하는가? 라고 하면 책보고 예제 써가면서 일부러 학습할 필요는 없다고 생각합니다.
      예전에 제가 페이스북에 적은 글이 있네요. 이거 참고하시면 될것 같습니다.

      효과적인 어셈블리어 학습방법.

      1. Visual Studio의 disassembly창을항상 띄워둔다.

      2. 함수의 일부를 inline 어셈블리어로 작성한다. 가령 a+b를 리턴하는 함수를 작성한다면 C언어로 작성해놓고 c= a+b 만 어셈블리코드로 바꾼다.

      2. 인라인 어셈블리를 사용하되 naked call을 이용해서 스택 프레임을 직접 구성한다. epilogue, prologue를 직접 어셈으로 작성한다. 이 시점에서 네이티브 어셈블리 코드와 사실상 똑같다.

      3. masm으로 함수 하나씩을 짠다. 인라인 어셈은 32비트로 제한되지만 여기서부터 x64도 가능. 커맨드라인에서 어셈블 할 필요없다. .asm파일을 VS로 드래그앤드롭하면 ml64또는 masm으로 빌드하도록 설정을 자동으로 다 맞춰준다. .asm파일에 브레이크 포인트 찍어서 디버깅도 할 수 있다.

      4. 모르는건 웹검색을 해도 되지만 VS의 디스어셈블 창을 보는게 대체로 더 효과적이다. 가령 FPU사용법을 모르겠다..그러면 32비트 모드에서 float연산을 해보고 디스어셈블 창을 확인하면 된다. 디스어셈블 창에 나온 명령어가 뭔지 모르겠으면 구글검색하면 된다.

      5. 어차피 유저모드에서 하드웨어를 직접 건드리는 명령은 사용불가능하므로 이 정도 학습이면 유저모드에서 어셈블리어를 사용하기엔 충분하다.
      프로텍티드 모드로 진입하거나 MMU를 제어하거나 in/out port를 사용하는건 다른 학습이 필요하다.

      15년쯤 전에 잡지 강좌로 내보냈던 글인데 이것도 참고하시고요.
      http://yuchi.asuscomm.com/xe/Programming_QA/5950

      좋아요

      1. C/C++에서 어떤 기능을 사용해도 얘네들은 네이티브 코드이기 때문에 어셈블리로 할 수 있는거보다 더 할 수 있는건 없습니다. 따라서 STL이든 C++ 20의 새로운 기능이건간에 디스어셈블리 코드로 따라가보면 원리는 다 이해할 수 있고 같은 기능을 스스로 만들수도 있습니다. 그래서 네이티브 언어의 새로운 기능에 대해선 전 별로 관심이 없는 편이고요. 두려움도 없습니다. 엄청 마법같은 기능은 처음부터 존재할수 없거든요.

        // 이부분이 평소 공상만 하던걸 긁어주는 부분이었습니다. 네이티브 코드는 어셈블리와 1:1대응이라 같은기능을
        스스로 만들수있다.

        지금 당장 인라인함수(강의영상으로 배웠지만 가상테이블처럼 당시에 아리송해서,,)
        도 정확히 몰라 이해는 무리지만 참고할만한 글들과 링크를 해주셔서 감사합니다.

        질문이 다시 생각하니 뭔가 어떤 신념으로 문제를 해결하면 그걸믿고 간단히 해결하고싶은마음에서 비롯된것
        같습니다.

        좋아요

  4. 이 좋은 글을 이제 봅니다. 매번 영천님 유튜브만보다가 호기심이 생겨서 directx12 책사서 공부하는 고등학생입니다..책에서는 ComPtr을 거의 스마트 포인터 개념으로만 생각하라고해서 무시하고 썼었는데.. directx11부터 배워야할 것을 이제라도 알아서 다행입니다. 포인터는 ID3D12 *m_device
    처럼 쓰시라는거죠? 은어에 약해서….아무쪼록 정말 감사합니당 ㅎㅎ

    좋아요

      1. 저.. 영천님 ComPtr의 Reset()은 어떻게 구현해야할까요?
        그리구 구현하기 위해 알아야 될 개념은 뭐가있을까요…

        좋아요

  5. DirectX 12을 이용한 게임 프로그래밍 입문(DirectX 9의 용책 개정판)을 공부하고 있습니다. 저자는 어떤 버전을 하든지 상관이 없다는 데, 12을 하면 내부동작원리를 잘 알지 못하게 되서 그런건가요?

    좋아요

    1. 반대입니다. 12는 내부 동작에 대한 이해가 없으면 사용할수 없습니다. 11을 못쓰면 12는 훨씬 어렵습니다.
      저자가 12를 깊이 안파서 그런 소릴 하는거 같은데요. 실제로 그 책 내용을 보면 11때 책이나 12때 책이나 내용이 거의 차이가 없습니다. 그리고 12할때 아무 도움도 안되죠. 그책만 읽어서는 dx12로 삼각형 하나도 제대로 그릴수 없을겁니다. 제가 왜 이런 소리를 하는지 그 책으로 직접 학습해가면 알수 있겠죠.

      좋아요

  6. 오늘에서야 잡지에 기고하신 글을 띄엄띄엄보다 6화까지 속도 붙여보던중 의문이 생겼습니다.
    cpu의 stack메모리에서 DWORD ptr[ebp-4] 로 예를 들어보면
    ebp-4 즉, 16진수로 된 메모리어드레스에 억세스 한 것이잖아요?
    근데 그럼 ebp-4 가 위치한 메모리영역이 어디인지 의문입니다.

    좋아요

    1. ebp – 4면 x86 아키텍처일때 함수 진입후 첫번째 로컬 변수의 주소입니다. 단 디버그 모드에선 변수 앞뒤로 padding을 붙일 수 있으므로 릴리즈 모드에서 컴파일 했을때이거나 직접 어셈블리어로 함수 하나를 만들었을때 얘기죠.
      x86 calling convention과 stack frame으로 검색해보세요.

      좋아요

      1. 네 제가 부연설명을 적었다가 다시 뺏는데요. 죄송하지만 다시 추가해 적어볼께요..

        질문이 완전 다른 내용으로 와전된 것 같아서..ㅠ.ㅠ

        음.. 강좌내용이 좋아서 stack frame과 ebp-4 포인터연산 으로 첫번째 로컬변수인건 어렵지않게 추론할수 있었는데요.

        (함수에서의 padding이 되는건 여기서 배우네요)

        제가 알고싶었던건 ebp 나 esp 레지스터나 , 어차피 값은 포인터 잖아요?

        우리가 지역 포인터변수 에 힙영역 메모리 할당받고 그 주소를 넣듯,

        레지스터의 값인 포인터도 어느메모리영역에서 할당받았을텐데 그게 어딘지 궁금합니다.
        (포인터들은 스택이잖아요.. 포인터가 가리키고있는곳들은요?)

        혹시라도 제 질문이
        x86 calling convention, stack frame
        으로 찾을수 없는부분일까봐서,. 댓글로 다시 남기어요.

        좋아요

      2. 함수에 진입하면 EBP레지스터는 ESP레지스터의 값을 복사해서 들고 있습니다. 이 함수 나가기 전까지 ESP레지스터 내용이 계속 바뀌기 때문에함수 진입시 스택 프레임의 경계를 EBP에 백업해두는겁니다. 따라서 ebp와 esp는 같은 영역의 메모리 주소를 담게 됩니다. 이 메모리 영역이 stack메모리입니다. OS밑에서 프로세스의 스레드로 돌아가든 OS없이 막바로 CPU를 제어해서 돌리든(OS 그 자체) CPU가 CALL/RET 명령을 쓸수 있으려면 stack메모리가 지정이 되어야 합니다. OS 밑에서 프로세스가 생성되고 그 안에서 기본(메인) 스레드가 시작할때 stack메모리가 지정됩니다. 물론 이 메모리 영역은 내 프로세스 공간 안의 읽고 쓸수 있는 메모리 공간입니다. 또한 개별 스레드를 생성할때도 os에 의해 stack메모리가 지정됩니다. 이때 stack 메모리 사이즈도 같이 설정할 수 있습니다. 기본 메인 스레드의 스택 사이즈는 링커에서 설정할수 있고요.

        좋아요

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중