Q. 
m_pdxgiSwapChain->SetFullscreenState(FALSE, NULL);  무슨 코드?
- 
    => 전체 화면 모드 비활성화;
    전체 화면모드는 swapchain이 전적으로 관리해서 GPU 리소스 최적화 가능;
    ==> 창모드는 운영체제에서 렌더링 결과를 관리하기에 VSync 설정이나 프레임 제한이 더 자연스럽게  적용되어 GPU 과부하 줄어들 수 있음.


Q. 
swapChain에 알파모드 왜 필요한가?
- 
    => 
    알파값을 변동하는 그래픽 후 처리가 있을 수 있기 떄문;
    **출력 일관성을 유지하게 하기 위해서;** 
    **SwapChain 버퍼와 RTV의 포맷은 동일해야 한다;**


Q. 
::ZeroMemory(&dxgiSwapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC1)) 
- 
    => 메모리 블록을 `0x00` 으로 초기화; 내부적으로 `memset`으로 구현
    `#define ZeroMemory(Destination, Length) memset((Destination), 0, (Length))`


Q.
::WaitForSingleObject(m_hFenceEvent, INFINITE); 
- 
    => Windows API에서 제공하는 동기화 함수;
    =>이 함수는 특정 객체가 **신호 상태(Signal State)**가 될 때까지 **대기하는 역할**
    => `m_hFenceEvent`:
    - 대기할 객체의 핸들로, 보통 이벤트 객체 또는 동기화 기본 객체를 나타냅니다. Direct3D 12에서는 GPU가 명령을 완료했는지 확인하기 위해 페이스(Fence)와 연결된 이벤트 핸들이 자주 사용


Q. 
dxgiSwapChainDesc.SampleDesc.Quality = (m_bMsaa4xEnable) ? (m_nMsaa4xQualityLevels - 1) : 0; <- Msaa4x 하면 왜 퀄리티 레벨 1개 낮춤?
- 
    => 품질 레벨은 일반적으로 0 부터 시작하기 떄문;


Q.
    hResult = ::CreateDXGIFactory2(nDXGIFactoryFlags, __uuidof(IDXGIFactory4), (void
        **)&m_pdxgiFactory); <- 왜 IDXGIFactory4인데, Create는 2?
-         
    => `CreateDXGIFactory2`는 유지하기 위한 설계로 고정, 반환되는 인터페이스의 버전은 전달된 UUID(`__uuidof(IDXGIFactory4)`)에 따라 달라짐 (4 쓰는 이유는 최신 인터페이스 사용위해)


Q.
Create 할 때, IID_PPV_ARGS 대신 __uuidof(), (void**) 직접 지정
- 
    =>
특징 IID_PPV_ARGS __uuidof()와 (void) 직접 지정**
코드 간결성 간결함, 코드 가독성 증가 더 많은 코드 작성 필요
타입 안정성 컴파일 타임에 포인터 타입 불일치 오류를 감지 수동으로 타입 관리, 실수할 가능성 존재
유연성 고정적인 인터페이스 요청 상황에 적합 더 많은 커스터마이제이션이 가능
디버깅 및 유지보수 쉬운 디버깅 및 유지보수 코드를 명확히 이해해야 유지보수가 쉬움
호환성 현대적인 DirectX 및 컴파일러 환경에서 최적화됨 오래된 컴파일러와 특수한 환경에서 더 유연하게 작동 가능
Q.
void CGameFramework::FrameAdvance()
{
    ProcessInput();
    AnimateObjects();
    HRESULT hResult = m_pd3dCommandAllocator->Reset();
    hResult = m_pd3dCommandList->Reset(m_pd3dCommandAllocator, NULL);

여기서 할당자 리셋하면 힙은??, 입력처리 할 떄마다 명령 다 비우기???
- 
    => `Reset()`은 Allocator에 기록된 **명령만 초기화**
    => **Command Queue**에 이미 제출된 명령에는 영향을 미치지 않습니다. 즉, GPU가 실행 중인 작업은 안전하게 유지
    => 각 프레임마다 새로운 GPU 명령을 기록하기 위해 기존 명령을 제거 (이 단계의 후면 버퍼는 새로 쓰일 예정)


Q.
void CGameFramework::FrameAdvance() 에서 렌더 타겟 리소스 배리어 왜 2번?
<- 왜 렌더 타겟 2번 렌더링하고 스왑 버퍼 교환??
- 
    => 첫번쨰 리소스 배리어는 후면 버퍼를 `제시` ->`렌더링` 상태로 전환 (쓰기모드)
    => 두번째 리소스 배리어는 후면 버퍼를 `렌더링`->`제시` 상태로 전환 (읽기모드)


Q.
D3D12_CPU_DESCRIPTOR_HANDLE d3dDsvCPUDescriptorHandle = m_pd3dDsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(); //깊이-스텐실 서술자의 CPU 주소를 계산한다. <- 필요한 이유는?;;;
- 
    => CPU 측에서 주소를 바탕으로 서술자 힙에 접근, 특정 힙을 참조하려면(여러 서술자를 관리하는 힙이 ) 시작 주소에서 해당 서술자까지의 오프셋을 계산;
    =>`ID3D12DescriptorHeap`는 여러 서술자를 하나의 연속된 메모리 공간에 모아서 관리합니다.


Q.
void CGameTimer::Reset()
{
    __int64 nPerformanceCounter;
    ::QueryPerformanceCounter((LARGE_INTEGER*)&nPerformanceCounter);
    m_nLastTime = nPerformanceCounter;
    m_nCurrentTime = nPerformanceCounter;
    m_bStopped = false;
} <- 이거 왜 퍼포먼스 타이머 지원한다고 가정하고 초기화??
- 
    => `timeGetTime()` 함수는 Windows 멀티미디어 타이머 API의 일부로 내부 시스템 타이머 기반이기 Reset이 필요 없기에 성능 카운트 하드웨어 Reset 부분만 존재


Q. 아래 코드 동작의 주의 해야할 부분
```
m_pdxgiSwapChain->Present1(1, 0, &dxgiPresentParameters); 
m_pdxgiSwapChain->Present(0, 0); 
m_nSwapChainBufferIndex = m_pdxgiSwapChain->GetCurrentBackBufferIndex();
```
- 
    => 첫 번쨰 인자는 `0`이면, 수직동기화 비활성; `1`이면 활성. (**근데 적용이 안되는 현상!**) <= 외부 소프트웨어 문제
    => 비정상적인 FPS는 CPU와 GPU가 동기화가 제대로 안 이루어진다는 신호
    ==> 버퍼 인덱스가 실제 인덱스와 맞지 않으면 동기화가 제대로 안되니, 스왑이 완전히 처리되기 전에 인덱스 전환 X
    => FPS 1000 이상이면 티어링 발생 가능성 높음.

'프레임워크 > DirectX' 카테고리의 다른 글

05. 렌더링 파이프라인  (7) 2025.03.11
04.Direct3D의_초기화03_요약그림  (0) 2025.03.07
04.Direct3D의_초기화03  (1) 2025.03.04
04.Direct3D의_초기화02  (0) 2025.03.04
04.Direct3D의_초기화01  (1) 2025.03.01

foreach 방식 (범위 기반 for 문)

  • c++11 부터 도입

      for (auto& a : arrayList) {
          statement
      }
  • c++20 이상

    • 초기자 사용 가능 (지역 벗어나면 소멸)

      for (array arr {1,2,3}; int i : arr)
      

'공부 > C++' 카테고리의 다른 글

템플릿(Template)  (0) 2025.03.08
상속  (0) 2025.03.07
(C++17) optional & variant  (0) 2025.03.05
(C++17,20)구조적 바인딩  (0) 2025.03.05
튜플(Tuple)  (0) 2025.03.05

참고 : 씹어먹는 C++ - <17 - 5. C++ 17 의 std::optional, variant, tuple 살펴보기>

optional

  • std::optional

  • c++17 이상

  • 원하는 값을 보관할 수 도, 안할 수 도 있는 클래스

  • 일반적으로 레퍼런스 보관 불가

  • 사용 예시

      std::optional<std::string> GetValue(int a) {
        if (a > 0) {
          return "sfsfs";
        }
        // nullopt 는 <optional> 에 정의된 객체로 비어있는 optional 을 의미한다.
        return std::nullopt;
      }
    
      int main() {
        std::cout << "값이 있으면 제대로 출력 " << GetValue(3).value()
                  << std::endl;
        std::cout << "값 있는지 확인, 2방식 동일 " << std::boolalpha
                  << GetValue(-4).has_value() << GetValue(-4) 
                  << std::endl;  // (bool 반환이 기본이기에)
      std::cout << "값 없으면 지정한 기본 값 출력 "
                  << GetValue(-4).value_or(3) 
                  << std::endl; // 기본값 타입은 optional 타입과 동일해야 함
      }
  • 레퍼런스 우회 보관방법 (std::reference_wrapper 사용)

      #include <functional>
      #include <iostream>
      #include <optional>
      #include <utility>
    
      class A {
       public:
        int data;
      };
    
      int main() {
        A a;
        a.data = 5;
    
        // maybe_a 는 a 의 복사복이 아닌 a 객체 자체의 레퍼런스를 보관하게 된다.
        std::optional<std::reference_wrapper<A>> maybe_a = std::ref(a);
    
        maybe_a->get().data = 3;
    
        // 실제로 a 객체의 data 가 바뀐 것을 알 수 있다.
        std::cout << "a.data : " << a.data << std::endl;
      }

    variant

  • std:variant

  • c++17 이상

  • one-of 를 구현

  • 정의할 때 포함하고자 하는 타입들을 명시

  • 반드시 값을 들고 있어야 한다

    • std::monostate를 통해, 피해갈 수 있음
  • 같은 타입 중복 사용 불가

  • 사용 예시

      std::variant<int, std::string, double> v;

'공부 > C++' 카테고리의 다른 글

상속  (0) 2025.03.07
foreach 방식 (범위 기반 for 문)  (0) 2025.03.05
(C++17,20)구조적 바인딩  (0) 2025.03.05
튜플(Tuple)  (0) 2025.03.05
클래스  (0) 2025.03.03

구조적 바인딩

  • 17 이상 (컨테이너들)
  • auto 키워드 필수
  • 참조나 const의 경우, auto&, const auto&
    array<int> values {1, 2, 3};
    auto [a, b, c] {values};
    

pair mP {"a", 1};
auto [f, s] {mP};


# 사용자 타입 구조적 바인딩
- 20 이상
```cpp
strcut Point {int x, y, z;};
Point p;
p.x = 1; p.y = 2; p.z = 3;
auto [x, y, z] {p};

'공부 > C++' 카테고리의 다른 글

foreach 방식 (범위 기반 for 문)  (0) 2025.03.05
(C++17) optional & variant  (0) 2025.03.05
튜플(Tuple)  (0) 2025.03.05
클래스  (0) 2025.03.03
함수  (1) 2025.03.03

튜플(Tuple)

  • 튜플은 여러 개의 데이터를 하나로 묶을 수 있음

  • 고정된 크기:

    • 튜플은 크기가 컴파일 타임에 고정
  • 순차 접근 불가능:

    • 튜플은 인덱스 기반으로 특정 요소에 접근가능, 반복(iteration) 불가
  • 타입 제약 없음:

    • 튜플은 서로 다른 타입의 요소를 저장할 수 있습니다.
  • 값을 가져올 떄는 std::get<{index}}> 사용

  • 사용 예시

      #include <iostream>
      #include <string>
      #include <tuple>
    
      int main() {
        std::tuple<int, double, std::string> tp;
        tp = std::make_tuple(1, 3.14, "hi");
    
        std::cout << std::get<0>(tp) << ", " << std::get<1>(tp) << ", "
                  << std::get<2>(tp) << std::endl;
      }

'공부 > C++' 카테고리의 다른 글

(C++17) optional & variant  (0) 2025.03.05
(C++17,20)구조적 바인딩  (0) 2025.03.05
클래스  (0) 2025.03.03
함수  (1) 2025.03.03
enum_class  (0) 2025.03.03

4.3 Direct3D의 초기화

  • 초기화 과정 단계 요약
    1) D3D12CreateDevice 함수를 이용해서 ID3D12Device 생성
      1) 실패하면 `WARP` 장치 `EnumWarpAdapter`로 찾아서 다시 생성
    2) ID3D12Fence 객체를 생성하고 서술자 크기 획득
      1) `ID3D12Device::CreateFence`로 울타리 생성
      2) `RTV`, `DSV`, `CbvSrvUav` 서술자 크기
    3) 4X MSAA 품질 수준 지원 여부 점검
      3.5) `Debug`모드에서 `Display 확인`
    4) 명령 대기열과 명령 목록 할당자, 그리고 주 명령 목록 생성
    5) 교환 사슬을 서술하고 생성
    6) 응용 프로그램에 필요한 서술자 힙들 생성
    7) 후면 버퍼의 크기를 설정하고, 후면 버퍼에 대한 렌더 대상 뷰를 생성
    8) 깊이·스텐실 버퍼 생성하고, 연관된 깊이·스텐실 버퍼 생성
    9) 뷰포트와 가위 판정용 사각형 설정

    4.3.1 장치 생성

  • 장치(device) : 디스플레이 어댑터를 나타내는 객체
    • 일반적으로 물리적인 3차원 그래픽 하드웨어 장치
    • WARP 어댑터도 존재 : 하드웨어 그래픽 가능성을 흉내 내는 소프트웨어 디스플레이 어댑터
  • ID3D12Device : 기능 지원 점검, 자원, 뷰, 명령 목록 등의 다른 모든 'D3D' 인터페이스 객체 생성에 사용
      HRESULT WINAPI D3D12CreateDevice(
          IUnknown* pAdapter,
          D3D_FEATURE_LEVEL MinimumFeatureLevel,
          REFIID riid, // Expected: ID3D12Device
          void** ppDevice );
    • pAdapter :
      • 디스플레이 어댑터 지정
      • 널 포인터 지정 시, 시스템 기본 디스플레이 어댑터 사용
    • MinimumFeatureLevel : 응용 프로그램 요구 최소 기능 수준
    • riid : 생성하고자 하는 ID3D12Device의 COM ID
    • CreateDevice사용 예시
        #if defined(DEBUG) || defined(_DEBUG) 
        // Enable the D3D12 debug layer.
        {
            ComPtr<ID3D12Debug> debugController;
            ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))); 
            debugController->EnableDebugLayer();
        }
        #endif
        ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));
        // Try to create hardware device.
        HRESULT hardwareResult = D3D12CreateDevice( 
            nullptr,       // default adapter
            D3D_FEATURE_LEVEL_11_0,
            IID_PPV_ARGS(&md3dDevice));
        // Fallback to WARP device. 
        if(FAILED(hardwareResult)) 
        {
            ComPtr<IDXGIAdapter> pWarpAdapter;
            ThrowIfFailed(mdxgiFactory-> EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));
            ThrowIfFailed(D3D12CreateDevice( 
                pWarpAdapter.Get(),
                D3D_FEATURE_LEVEL_11_0,
                IID_PPV_ARGS(&md3dDevice)));
        }
      • CreateDevice 호출 실패 시, 소프트웨어 어댑터 WARP(Windows Advanced Rasterization Platform) 를 호출.
        • 사전에 다음과 같은 IDXGIFactory4EnumWarpAdapter 메서드 호출
            ComPtr<IDXGIFactory4> mdxgiFactory;
            CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory));
            mdxgiFactory->EnumWarpAdapter( IID_PPV_ARGS(&pWarpAdapter));
        • mdxgiFactory 객체는 교환 사슬을 생성하는 데에도 사용 (교환 사슬이 "DXGI"의 일부 이기 때문)

4.3.2 울타리 생성과 서술자 크기 얻기

  • CPU와 GPU 동기화를 위한 울타리 객체 생성
  • 이후 필요한 서술자 크기 미리 조회해서 설정
    • GPU마다 필요 서술자 크기가 다를 수 있어, 실행시점에 적절한 메서드 호출 필요
    • 추후 바로 사용을 위해, 적절한 멤버 변수들에 저장
      ThrowIfFailed(md3dDevice->CreateFence(
      0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence) ));
      mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
      mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
      mCbvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

4.3.3 4X MSAA 품질 수준 지원 점검

  • 4X MSAA가 기본인 이유
    • 비용이 그리 크지 않으면서 화질 개선이 많이 됨
    • 품질레벨은 0부터 시작 -> m4xMsaaQuality - 1이 최대 품질
    • D3D11 장치가 모든 렌더 대상 형식에서 지원
      • m4xMsaaQuality > 0 이어야 한다
        D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
        msQualityLevels.Format = mBackBufferFormat;
        msQualityLevels.SampleCount = 4;
        msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
        msQualityLevels.NumQualityLevels = 0;
        ThrowIfFailed(md3dDevice->CheckFeatureSupport(
        D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
        &msQualityLevels,
        sizeof(msQualityLevels)));
        m4xMsaaQuality = msQualityLevels.NumQualityLevels;
        assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");

4.3.4 명령 대기열과 명령 목록 생성

ComPtr<ID3D12CommandQueue> mCommandQueue;
ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
ComPtr<ID3D12GraphicsCommandList> mCommandList; 
void D3DApp::CreateCommandObjects()
{
    D3D12_COMMAND_QUEUE_DESC queueDesc = {};
    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    ThrowIfFailed(md3dDevice->CreateCommandQueue( 
        &queueDesc, IID_PPV_ARGS(&mCommandQueue)));
    ThrowIfFailed(md3dDevice->CreateCommandAllocator(
        D3D12_COMMAND_LIST_TYPE_DIRECT,
        IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
    ThrowIfFailed(md3dDevice->CreateCommandList( 
        0,
        D3D12_COMMAND_LIST_TYPE_DIRECT,
        mDirectCmdListAlloc.Get(), // 연관된 명령 할당자
        nullptr,          // 초기 파이프라인 상태 객체
        IID_PPV_ARGS(mCommandList.GetAddressOf())));
    // 닫힌 상태 시작. 이후 명령 목록을 처음 참조 할 떄
    // Reset을 호출하는데, Reset을 호출하려면 명령 목록이 닫혀 있어야 함
    mCommandList->Close();
}

4.3.5 교환 사슬의 서술과 생성

  • SwapChainRTV의 포맷은 일치해야 한다

  • DXGI_SWAP_CHAIN_DESC 구조체 인스턴스 멤버들을 지금 생성하고자 하는 교환 사슬에 맞게 설정

      typedef struct DXGI_SWAP_CHAIN_DESC
      {
          DXGI_MODE_DESC BufferDesc;
          DXGI_SAMPLE_DESC SampleDesc;
          DXGI_USAGE BufferUsage;
          UINT BufferCount;
          HWND OutputWindow;
          BOOL Windowed;
          DXGI_SWAP_EFFECT SwapEffect;
          DXGI_SWAP_CHAIN_DESC
          UINT Flags;
      } DXGI_SWAP_CHAIN_DESC;
    
      typedef struct DXGI_MODE_DESC
      {
          UINT Width;           // 버퍼 해상도 너비
          UINT Height;           // 버퍼 해상도 높이
          DXGI_RATIONAL RefreshRate; 
          DXGI_FORMAT Format;       // 버퍼 디스플레이 형식
          DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;  // 순차 주사 vs 비월주사
          DXGI_MODE_SCALING Scaling;    // How the image 
          is stretched 
          // over the
          monitor.
      } DXGI_MODE_DESC;
    • BufferDesc
      : 생성하고자 하는 후면 버퍼 속성 서술; DXGI_MODE_DESC 참조
      • SampleDesc : 다중 표본화 표본 개수와 품질 수준 서술; 단일 표본화 설정 시 (개수 1, 수준 0)
      • BufferUsage : 후면 버퍼에 렌더링할 것이니, DXGI_USAGE_RENDER_TARGET_OUTPUT 지정
      • BufferCount : 교환 사슬이 사용할 버퍼 개수 (이중 버퍼링에서는 2)
      • OutputWindow : 렌더링 결과 표시될 창 핸들
      • Windowed : 창 모드 (true), 전체화면 (false)
      • SwapEffect : 스왑 방식; DXGI_SWAP_EFFECT_FLIP_DISCARD 지정 (스왑 후, 이전 프레임 버리기)
      • DXGI_SWAP_CHAIN_DESC : "RTV"의 포맷과 맞춰주기 위해 중요함
    • Flags : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH 지정 시, 전체화면 전환 될 떄 현재 크기에 가장 잘 맞는 디스플레이 모드 선택. (지정 안하면 현재 데스크톱 디스플레이 모드 사용)
  • 교환 사슬 생성

      HRESULT IDXGIFactory::CreateSwapChain(
          IUnkown *pDevice,   // ID3D12CommandQueue 포인터
          DXGI_SWAP_CHAIN_DESC *pDesc,  // 교환 사슬 서술 구조체 포인터
          IDXGISwapChain **ppSwapChain);  // 생성된 교환 사슬 인터페이스 반환
  • 이전 교환 사슬 먼저 해제 후, 새 교환 사슬 생성; (이전과 다른 설정으로 교환 사슬 생성, 실행 도중에 다중표뵨화 설정 변경 가능)

    • 교환 사슬은 명령 대기열을 이용해서 방출(flush) 수행

      DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
      void D3DApp::CreateSwapChain()
      {
      // 새 교환 사슬 생성 전에 먼저 기존 교환 사슬 해제.
      mSwapChain.Reset();
      
      DXGI_SWAP_CHAIN_DESC sd;
      sd.BufferDesc.Width = mClientWidth;
      sd.BufferDesc.Height = mClientHeight;
      sd.BufferDesc.RefreshRate.Numerator = 60;
      sd.BufferDesc.RefreshRate.Denominator = 1;
      sd.BufferDesc.Format = mBackBufferFormat;
      sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
      sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
      sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
      sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
      sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
      sd.BufferCount = SwapChainBufferCount;
      sd.OutputWindow = mhMainWnd;
      sd.Windowed = true;
      sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; 
      sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
      // Note: 교환 사슬은 명령 대기열을 이용해서 방출(flush) 수행
      ThrowIfFailed(mdxgiFactory->CreateSwapChain( 
        mCommandQueue.Get(), &sd, mSwapChain.GetAddressOf()));
      }

4.3.6 서술자 힙 생성

  • 서술자 힙 : ID3D12DescriptorHeap 인터페이스로 대표

  • SwapChainBufferCount에 설정된 개수만큼의 렌더 대상 뷰(RTV, render target view)들과 하나의 깊이-스텐실 뷰(DSV, depth/stencil view)가 필요

    • RTV는 교환 사슬에서 렌더링 대상이 되는 버퍼 자원 서술
    • DSV는 깊이 판정을 위한 버퍼 자원 서술
  • 서술자 힙은 서술자 종류마다 따로 생성

  • 힙 생성 코드

      ComPtr<ID3D12DescriptorHeap> mRtvHeap;
      ComPtr<ID3D12DescriptorHeap> mDsvHeap;
      void D3DApp::CreateRtvAndDsvDescriptorHeaps()
      {
          D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
          rtvHeapDesc.NumDescriptors = SwapChainBufferCount;
          rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
          rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
          rtvHeapDesc.NodeMask = 0;
          ThrowIfFailed(md3dDevice->CreateDescriptorHeap( 
              &rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
    
          D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
          dsvHeapDesc.NumDescriptors = 1;
          dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
          dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
          dsvHeapDesc.NodeMask = 0;
          ThrowIfFailed(md3dDevice->CreateDescriptorHeap( 
              &dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
      }
  • 힙 서술자에 대한 핸들 예제

      static const int SwapChainBufferCount = 2;
      int mCurrBackBuffer = 0;
    
      D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView()const
      {
          // 편의를 위해 CD3D12_CPU_DESCRIPTOR_HANDLE의 생성자를 사용
          // 생성자는 주어진 오프셋에 해당하는 후면 버퍼 rtv의 핸들을 돌려줌
          return CD3DX12_CPU_DESCRIPTOR_HANDLE(
              mRtvHeap->GetCPUDescriptorHandleForHeapStart(),// 첫 핸들
              mCurrBackBuffer,   // 오프셋 색인
              mRtvDescriptorSize); // 서술자 바이트 크기
      }
    
      D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView()const
      {
          return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
      }

4.3.7 RTV 생성

  • 자원 자체를 직접 파이프라인의 단계에 묶지 않음
    • 대신 서술자(자원에 대한 뷰)를 생성해서 파이프라인 단계에 묶음
    • 후면 버퍼를 파이프라인의 출력 병합기에 묶기위해 후면 버퍼에 대한 렌더 대상 뷰 생성
      • 후면 버퍼 파이프라인 출력 병합기에 묶기 = D3D가 장면을 후면 버퍼에 렌더링 가능함
    • 스왑체인버퍼와 RTV의 포맷은 동일해야 함
  • 교환 사슬에 저장된 버퍼 자원 얻기
      HRESULT IDXGISwapChain::GetBuffer(  // *해당 후면 버퍼 참조횟수 증가
          UINT Buffer,  // 후면 버퍼 식별 색인
          REFIID riid,  // `ID3D12Resource` 인터페이스 COM ID
          void **ppSurface); // 반환
  • RTV 생성
      void ID3D12Device::CreateRenderTargetView (
          ID3D12Resource *pResource,
          const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,
          D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor);
    1) pResource : 렌더 대상 사용 자원 가리키는 포인터
    2) pDesc : 구조체는 RTV를 서술; 자원에 담긴 원소들의 자료 형식에 관한 멤버를 가짐
          - 무형식 자원이 아니면 `nullptr` 지정 가능 (첫 번쨰 밉맵 수준에 대한 뷰 생성)
    3) DestDescriptor : 생성된 RTV가 저장될 서술자 핸들
  • 교환 사슬 두 버퍼에 대해 각각 RTV 생성 코드
    ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(
      mRtvHeap->GetCPUDescriptorHandleForHeapStart());
    for (UINT i = 0; i < SwapChainBufferCount; i++)
    {
    // Get the ith buffer in the swap chain.
    ThrowIfFailed(mSwapChain->GetBuffer(
      i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
    // Create an RTV to it.
    md3dDevice->CreateRenderTargetView(
      mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);
    // Next entry in heap.
    rtvHeapHandle.Offset(1, mRtvDescriptorSize); 
    }

4.3.8 깊이-스텐실 버퍼와 뷰 새성

  • 깊이-스텐실 버퍼는 2차원 텍스쳐 -> 텍스처는 GPU 자원의 하나 -> 자원 서술
  • 텍스처 자원 서술 구조체
    typedef struct D3D12_RESOURCE_DESC
    {
      D3D12_RESOURCE_DIMENSION Dimension; // 배열 차원 지정
      UINT64 Alignment; 
      UINT64 Width; // 너비(텍셀 단위), 버퍼 자원의 경우에는 버퍼 바이트 개수 지정
      UINT Height;  // 텍셀 단위
      // 3차원 텍스처 깊이(텍셀 단위)or 1,2 차원 배열크기; (3차원 배열 지원 x)
      UINT16 DepthOrArraySize;  
      UINT16 MipLevels;  // 밉맵 수준 개수
      DXGI_FORMAT Format;  // 텍셀 자료 형식; DXGI_FORMAT 열거형의 한 멤버(4.1.5참고)
      DXGI_SAMPLE_DESC SampleDesc; // 다중표본화 (4.1.7 및 4.1.8)
          D3D12_TEXTURE_LAYOUT Layout; // 텍스처 배치; D3D12_TEXTURE_LAYOUT 열거형
      D3D12_RESOURCE_MISC_FLAG MiscFlags; // 깊이-스텐실 경우, D3D12_RESOURCE_MISC_DEPTH_STENCIL 지정
    } D3D12_RESOURCE_DESC;
  • GPU 자원은 GPU힙에 존재 -> GPU 힙은 GPU 메모리 블록(특정 속성들을 가지고 있음)
  • 자원 생성 및 지정된 속성들에 부합하는 힙에 자원 맡기기(commit)
      HRESULT ID3D12Device::CreateCommittedResource( 
          const D3D12_HEAP_PROPERTIES *pHeapProperties, 
          D3D12_HEAP_MISC_FLAG HeapMiscFlags,
          const D3D12_RESOURCE_DESC *pResourceDesc,
          D3D12_RESOURCE_USAGE InitialResourceState,
          const D3D12_CLEAR_VALUE *pOptimizedClearValue, 
          REFIID riidResource,
          void **ppvResource);
      typedef struct D3D12_HEAP_PROPERTIES {
          D3D12_HEAP_TYPE     Type;
          D3D12_CPU_PAGE_PROPERTIES CPUPageProperties; 
          D3D12_MEMORY_POOL    MemoryPoolPreference; 
          UINT CreationNodeMask;
          UINT VisibleNodeMask;
      } D3D12_HEAP_PROPERTIES;
    • pHeapProperties : 자원을 맡길 힙의 속성 담은 구조체 포인터
      • D3D12_HEAP_TYPE : 힙의 종류
        • D3D12_HEAP_TYPE_DEFAULT :
          • 기본 힙. GPU가 접근할 자원들이 담김
          • 깊이-스텐실 버퍼는 GPU가 전적관리이기에 여기 담기는게 좋음
        • D3D12_HEAP_TYPE_UPLOAD :
          • 자료 올리기 힙; CPU에서 GPU로 자료를 올려서 갱신해야 할 자원
        • D3D12_HEAP_TYPEREADBACK :
          • 다시 읽기 힙; CPU가 읽어야 할 자원들을 이 힙에 맡김
        • D3D12_HEAP_TYPE_CUSTOM
    • HeapMiscFlags : 추가 플래그
    • pResourceDesc : 생성 자원 서술
    • InitialResourceState : 4.2.3 참고
    • pOptimizedClearValue : 자원 지우기에 최적화된 값을 나태는 D3D12_CLEAR_VALUE 포인터; 최적화 지우기 설정 안할려면 널 지정
    • riidResource : 생성하려는 자원 COM ID
    • ppvResource : 새로 생성된 자원 반환
  • 연관된 깊이-스텐실 버퍼 사용전 연관된 깊이-스텐실 뷰 생성
      // Create the depth/stencil buffer and view. 
      D3D12_RESOURCE_DESC depthStencilDesc; 
      depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; depthStencilDesc.Alignment = 0; 
      depthStencilDesc.Width = mClientWidth; 
      depthStencilDesc.Height = mClientHeight; 
      depthStencilDesc.DepthOrArraySize = 1; 
      depthStencilDesc.MipLevels = 1; 
      depthStencilDesc.Format = mDepthStencilFormat; depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1; depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
      depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; 
      depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; 
      D3D12_CLEAR_VALUE optClear; 
      optClear.Format = mDepthStencilFormat; 
      optClear.DepthStencil.Depth = 1.0f; 
      optClear.DepthStencil.Stencil = 0; 
      ThrowIfFailed(md3dDevice->CreateCommittedResource( 
          &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), 
          D3D12_HEAP_FLAG_NONE, 
          &depthStencilDesc, 
          D3D12_RESOURCE_STATE_COMMON, 
          &optClear, 
          IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf()))); 
      // Create descriptor to mip level 0 of entire resource using the 
      // format of the resource. 
      md3dDevice->CreateDepthStencilView( 
          mDepthStencilBuffer.Get(), nullptr, 
          DepthStencilView()); 
      // Transition the resource from its initial state to be used as a depth buffer.
      mCommandList->ResourceBarrier( 
          1, &CD3DX12_RESOURCE_BARRIER::Transition( m
              DepthStencilBuffer.Get(), 
              D3D12_RESOURCE_STATE_COMMON, 
              D3D12_RESOURCE_STATE_DEPTH_WRITE));
  • 확장 힙 속성 구조체 CD3DX12_HEAP_PROPERTIES
      explicit CD3DX12_HEAP_PROPERTIES( 
          D3D12_HEAP_TYPE type, 
          UINT creationNodeMask = 1, 
          UINT nodeMask = 1 ) 
      { 
      Type = type; 
      CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; 
      MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; 
      CreationNodeMask = creationNodeMask; 
      VisibleNodeMask = nodeMask; 
      }

4.3.9 뷰포트 설정

  • 뷰포트 : 장면을 그려 넣고자 하는 후면 버퍼의 부분 직사각형 영역
      typedef struct D3D12_VIEWPORT { 
      FLOAT TopLeftX; FLOAT TopLeftY; 
      FLOAT Width; FLOAT Height; 
      FLOAT MinDepth; FLOAT MaxDepth; } D3D12_VIEWPORT;
    • Depth 범위는 [0, 1];
      • Min,Max가 0이면, 모든 물체 깊이 값이 0 -> 다른 모든 객체보다 앞에 나타남
      • 보통은 Min = 0, Max = 1
    • FLOAT 이기에 소수점 가능
  • 뷰포트를 D3D에 설정
      D3D12_VIEWPORT vp; 
      vp.TopLeftX = 0.0f; 
      vp.TopLeftY = 0.0f; 
      vp.Width = static_cast(mClientWidth); 
      vp.Height = static_cast(mClientHeight); 
      vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f;
      mCommandList->RSSetViewports(1, &vp); // 뷰포트 개수, 뷰포트 구조체들 배열 포인터
    • 하나의 렌더 대상에 여러 개 뷰포트 지정 불가
    • 명령 목록을 재설정(Reset)하면 뷰포트들도 재설정 필요

4.3.10 가위 직사각형 설정

  • 가위 직사각형(scissor rectangle)
    • 특정 픽셀 선별(culling)
    • 후면 버퍼 기준으로 가위 직사각형 설정하면, 렌더링 시 가위 직사각형 바깥에 있는 픽셀들은 후면 버퍼에 래스터화되지 않음
      • 래스터화(rasterization) : 3D 모델을 2D 화면에 렌더링하는 과정
    • 가려지는 요소들을 고려하지 않을 수 있는 최적화 기법
    • 사용 예시 ID3D12CommandList::RSSetScissorRects
      mScissorRect = { 0, 0, mClientWidth/2, mClientHeight/2 };
      mCommandList->RSSetScissorRects(1, &mScissorRect); // 갯수, 구조체들 배열 포인터
    • 하나의 렌더 대상에 여러 개 뷰포트 지정 불가
    • 명령 목록을 재설정(Reset)하면 뷰포트들도 재설정 필요

'프레임워크 > DirectX' 카테고리의 다른 글

04.Direct3D의_초기화03_요약그림  (0) 2025.03.07
Direct3D의 초기화_게임프레임워크&타이머 코드 의문점  (0) 2025.03.07
04.Direct3D의_초기화02  (0) 2025.03.04
04.Direct3D의_초기화01  (1) 2025.03.01
02.Matrix  (0) 2025.02.25

4.2 CPU와 GPU의 상호작용

  • 최적의 성능을 얻기 위해 CPU, GPU를 최대한 사용 <- 동기화를 최소화

4.2.1 명령 대기열과 명령 목룍

  • GPU명령 대기열(command queue) 1개 보유
  • CPU는 그리기 명령이 담긴 명령 목록(command list)를 D3D API를 통해서 명령 대기열에 제출
    • 명령 대기열에 제출 했다고, GPU가 즉시 실행 X
    • ID3D12CommandQueue : 명령 대기열을 대표하는 인터페이스
      1) D3D12_COMMAND_QUEUE_DESC 구조체를 채움
      2) ID3D12Device::CreateCommandQueue를 호출
        Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
        D3D12_COMMAND_QUEUE_DESC queueDesc = {};
        queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 
        queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; 
        ThrowIfFailed(md3dDevice->CreateCommandQueue(
            &queueDesc, IID_PPV_ARGS(&mCommandQueue)));
      • #define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)
        • COM 인터페이스에서 특정 인터페이스를 요청할 떄 사용
        • COM에서는 인터페이스를 식별하고 사용하기 위해 GUID(Globally Unique Identifier)
        • __uuidof(**(ppType)) : (**(ppType))의 가리키는 인터페이스 타입의 GUID를 반환
        • IID_PPV_ARGS_Helper(ppType) : ppType에 대해 추가적인 처리를 수행하는 도우미 매크로, *ppType을 `void`로 캐스팅***
      • ID3D12CommandQueue::ExecuteCommandLists : 명령 목록 요소들을 대기열에 추가/제출 <- CPU의 실행 차단 x
          void ID3D12CommandQueue::ExecuteCommandLists(
              UINT Count, // 배열에 있는 명령 목록들의 개수
              ID3D12CommandList *const *ppCommandLists // 명령 목록들의 배열 첫 원소 가리키는 포인터);
          )
      • CommandList에 대해
        • ID3D12GraphicsCommandList : ID3D12CommandList를 상속하는 실제 그래픽 작업을 위한 명령 목록 인터페이스
        • "CommandQueue"에 추가한 뒤에는 지워도 됨.
          • 실제 추가되는 명령은 "CommandAllocator"를 참조하기 때문
        • 뷰포트 설정 및 렌더 대상 뷰 지우고, 그리기 호출 ( 즉시 실행 x)
            // mCommandList는 ID3D12CommandList 포인터
            mCommandList->RSSetViewports(1, &mScreenViewport);
            mCommandList->ClearRederTargetView(mBackBufferView, Colors::LightSteelBlue, 0, nullptr);
            mCommandList->DrawIndexedInstanced(36, 1, 0, 0, 0);
        • 명령의 기록이 끝나면, 닫기 : mCommandList->Close();
        • ID3D12CommandAllocator형식의 메모리 할당자가 1 개 연관
          • '명령 목록'에 추가된 명령이 저장되는 곳
        • CreateCommandList : 생성
            HRESULT ID3D12Device::CreateCommandList(
                UNIT nodeMask, // GPU가 하나면 0, 아니면 연관시킬 어댑터 노드 지어 bit mask 값
                D3D12_COMMAND_LIST_TYPE type, // 아래 type 유형 참조
                ID3D12CommandAllocator *pCommandAllocator, // 할당자 종류는 목록 종류와 일치
                ID3D12PipelineState *pInitialState // 번들 및 초기화, 실제 그리기 명령 없으면 널 지정 가능
                REFIID riid,
                void **ppCommandList); // 생성된 명령 목록 가리키는 포인터
          • ID3D12Device::GetNodeCount : GPU 어댑터 노드 개수
        • Reset :
          • 초기화, 번거로움 없이 메모리 재사용
            • size는 0이 되어도, capacity는 그대로
          • 필요 렌더링 명령 모두 GPU 제출 후, 다음 프레임을 위해 재사용 용도 
          • Queue에 제출된 명령에는 영향x
            • 할당자의 메모리에 여전히 존재
          •  
          • HRESULT ID3D12CommandList::Reset( ID3D12CommandAllocator *pAllocator, ID3D12PipelineState *pInitialState);
        • 명령 메모리 할당자
            HRESULT ID3D12Device::CreateCommandAllocator(
                D3D!@_COMMAND_LIST_TYPE type,
                REFIID riid,
                void **ppCommandAllocator);
          • type : 할당자와 연관 시킬 수 있는 명령 목록 종류
            • D3D12_COMMAND_LIST_TYPE_DIRECT : GPU 직접 실행 명령 목록
            • D3D12_COMMAND_LIST_TYPE_BUNDLE :
              • CPU 부담을 줄이기 위한 일련의 명령들을 '묶음' 단위로 최적화
              • 렌더링 도중 실행 최적화를 위해, 번들 명령 전처리
              • 성능상 이득이 있는 경우만 사용
          • riid : ID3D12CommandAllocator인터페이스의 COM ID
          • ppCommandAllocator : 생성된 명령 할당자를 가리키는 포인터
          • 목록 생성 || 재설정 시, "열린" 상태가 됨 주의

4.2.2 CPU/GPU 동기화


  • 1) CPU가 'C' 명려을 GPU에 제출
    2) GPU 명령 수행중, R 업뎃 - CPU 'Resource'에 p1 저장
    3) GPU 명령 수행중 , R 업뎃- CPU 'Resource'에 p2 로 저장 변경
    • GPU 명령 수행중에 참조하는 R의 값이 변하는 문제가 발생 <- 비동기로 돌아가서
  • flush(방출한다) : 대기열의 모든 명령을 처리하는 것
    • ID3D12Fence가 필요 : GPU, CPU 동기화 수단
      • 울타리 생성
          HRESULT ID3D12Device::CreateFence(
              UINT64 InitialValue,
              D3D12_FENCE_FLAGS Flags,
              REFIID riid;
              void **ppFence);
      • 울타리를 이용한 명령 대기열 비우기
          UINT64 mCurrentFence = 0;
          void D3DApp::FlushCommandQueue()
          {
              // Advance the fence value to mark commands up to this fence point.
              mCurrentFence++;
              // Add an instruction to the command queue to set a new fence point.
              // Because we are on the GPU timeline, the new fence point won’t be 
              // set until the GPU finishes processing all the commands prior to 
              // this Signal().
              ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));
              // Wait until the GPU has completed commands up to this fence point.
              if(mFence->GetCompletedValue() < mCurrentFence) 
              {
                  HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
                  // Fire event when GPU hits current fence. 
                  ThrowIfFailed(mFence->    SetEventOnCompletion(mCurrentFence, eventHandle));
                  // Wait until the GPU hits current fence event is fired.
                  WaitForSingleObject(eventHandle, INFINITE); 
                  CloseHandle(eventHandle);
              }
          }

4.2.3 자원 상태 전이

  • 자원 위험 상황(resource hazard) : 자원의 기록이 제대로 안된 상태에서 자료를 읽을려 할 떄 생기는 문제
  • 임의의 상태 전이를 'D3D'에게 보고하는 것은 '응용 프로그램 몫'
  • 리소스 배리어는 후면 버퍼를 `제시` ->`렌더링` 상태로 전환 (쓰기모드)
  • 리소스 배리어는 후면 버퍼를 `렌더링`->`제시` 상태로 전환 (읽기모드)
  • 자원 상태 전이 :
    • 전이 자원 장벽(transition resource barrier)들의 배열을 설정해서 지정
      • D3D12_RESOURCE_BARRIER_DESC
        struct CD3DX12_RESOURCE_BARRIER : public D3D12_RESOURCE_BARRIER
        {
          // [...] convenience methods
          static inline CD3DX12_RESOURCE_BARRIER Transition(
          _In_ ID3D12Resource* pResource,
          D3D12_RESOURCE_STATES stateBefore,
          D3D12_RESOURCE_STATES stateAfter,
          UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
          D3D12_RESOURCE_BARRIER_FLAGS flags = D3D12_RESOURCE_BARRIER_FLAG_NONE)
          {
              CD3DX12_RESOURCE_BARRIER result;
              ZeroMemory(&result, sizeof(result));
              D3D12_RESOURCE_BARRIER &barrier = result;
              result.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
              result.Flags = flags;
              barrier.Transition.pResource = pResource;
              barrier.Transition.StateBefore = stateBefore; 
              barrier.Transition.StateAfter = stateAfter; 
              barrier.Transition.Subresource = subresource; 
              return result;
          }
        // [...] more convenience methods 
        };
      • CDIDX12_ 확장 버전은 d3dx12.h에 정의
    • 텍스처 자원 제시 상태 -> 렌더 대상 상태 전이 예시
       mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
           CurrentBackBuffer(),
           D3D12_RESOURCE_STATE_PRESENT, 
           D3D12_RESOURCE_STATE_RENDER_TARGET));

4.2.4 명령 목록을 이용한 다중 스레드 활용

  • 명령 목록 구축에 다중 스레드 적용 주의점
    • 명령 목록은 자유 스레드(free-threaded) 모형을 따르지 않음;
      • 여러 스레드가 같은 명령 공유x, 동시에 호출 x, 각 스레드는 자신만의 명령 목록
    • 명령 할당자도 자유 스레드X, 각 스레드는 각자 자신만의 명령 할당자를 가짐
    • 명령 대기열은 자유 스레드 모형;
      • 여러 스레드가 동시 호출 , 스레드들이 각자 자신이 생성한 명령 목록을 동시에 명령 대기열에 제출 가능
    • 성능상 이유로, 응용 프로그램은 동시 기록 가능 명령 목록들의 최대 개수를 초기화 시점에 설정해야 함

 

'프레임워크 > DirectX' 카테고리의 다른 글

Direct3D의 초기화_게임프레임워크&타이머 코드 의문점  (0) 2025.03.07
04.Direct3D의_초기화03  (1) 2025.03.04
04.Direct3D의_초기화01  (1) 2025.03.01
02.Matrix  (0) 2025.02.25
01.Vector  (0) 2025.02.25

Class

  • 어떤 종류의 모든 객체에게 공통인 멤버 변수와 멤버 함수를 정의하는 형틀
  • 객체 지향에서의 기본적인 빌딩 블록
  • 인스턴스 : 클래스로부터 만들어지는 객체
  • 멤버 변수 : 클래스 내부의 변수, 인스터스에 개별 존재
  • 멤버 함수 : 클래스 내부의 함수, 인스턴스가 1개의 멤버함수 공유

멤버 변수 내부 정의 주의점

  • 멤버 변수의 경우, 내부에 정의하게 될 경우, 인라인 함수로 사용 되기에 주의 해야 한다.

=default

  • 기본 구현을 사용하여 생성자, 소멸자, 복사연산자 및 이동연사자를 명시적으로 정의할 때 사용

    class MyClass {
    public:
      MyClass() = default;           // 기본 생성자
      ~MyClass() = default;          // 소멸자
      MyClass(const MyClass&) = default;  // 복사 생성자
      MyClass& operator=(const MyClass&) = default;  // 복사 대입 연산자
      MyClass(MyClass&&) = default;  // 이동 생성자
      MyClass& operator=(MyClass&&) = default;  // 이동 대입 연산자
    
      // 다른 멤버 함수들...
    };

new와 일반 선언 차이

특징 new 사용 (동적 할당) 변수 선언 (자동/정적 할당)
메모리 영역 힙 (Heap) 스택 (Stack) 또는 정적
생명 주기 명시적으로 관리 (delete) 스코프 종료 시 자동 소멸
성능 메모리 할당/해제가 느림 상대적으로 빠름
유연성 더 큰 데이터 구조에 유리 간단하고 안전함
  • new를 사용하는 경우: 객체를 스코프와 무관하게 더 오랫동안 유지하거나, 동적으로 크기 조정 가능한 데이터 구조(예: 동적 배열, 연결 리스트 등)를 사용할 때 적합.
  • 변수 선언을 사용하는 경우: 성능과 코드 안전성이 중요한 경우, 객체의 생명 주기가 스코프 내로 제한되어도 되는 경우 적합.

생성자

  • 기본 생성자
      Employee() {}
  • 복사 생성자
      Employee(const Employee& employee) {}

복사 생성자

  • 동일한 클래스의 객체를 복사하여 객체를 생성할 때 사용
  • 호출되는 경우
    • 같은 종류의 객체로 초기화 : MyClass obj(obj2);
      • 정의 시에만 호출, 이미 생성된 객체 간에는 대입 연산
    • 객체를 함수에 전달 :
      • MyClass func(MyClass obj) { // 여기서 호출 };
    • 함수가 객체를 반환 :
      • MyClass func(MyClass obj) { MyClass tmp; return tmp;// 여기서 호출 };

C++ 복사

  • int, char 배열은 기본적으로 DeepCopy를 지원.
  • 객체의 경우, 기본적으로 ShallowCopy를 지원.
    • 기본 복사 생성자를 사용 시, 배열 복사에서 문제가 발생하는 이유.
    • 대입 연산의 경우(같은 객체 간 대입), 깊은 복사
        MyClass obj("Hello");
        obj = obj; // 자기 자신에 대한 대입

초기화 리스트

  • C++에서 객체의 생성자(constructor)를 호출할 때 멤버 변수를 초기화하는 방법 중 하나

          // 초기화 리스트를 사용하여 멤버 변수를 초기화
          MyClass(int a, int b) : memberA(a), memberB(b) {
              std::cout << "Constructor called!";
      }
  • 생성자의 본문이 실행되기 전에 멤버 변수를 초기화하는 데 사용

    • 본문에서 초기화 하는 것보다 효율적

      int a = 10; // <- 초기화 리스트 방식
      
      int a;
      a = 10; // <- 초기화 리스트x
  • 상수 멤버 변수 초기화 : 상수 멤버 변수를 초기화 리스트로 초기화가능

  • C++11 부터 초기화 시, {}를 사용하는 uniform initialization으로 통일

      AClass a {1, 2, 3};
    • 축소 변환 (3.14를 3으로 자르는 int 변환같은)을 방지하는 기능이 있음
  • C++20 부터는 지정 초기자 기능 사용가능

      AClass{
       int a {3};
       int b;
       int c = 5;
      }
    
      AClass a {
          .a = 5;
          .b = 4;
      } // a = 5, b = 4, c = 5 로 초기화
      // 생략된 대상은 디폴트 값으로 초기화;
      // 구조체에 멤버를 추가해도 기존 코드는 정상 작동

초기자 리스트 생성자

  • 참고 : 전무가를 위한 c++
  • initializer_list<{type}> 을 매개변수로 가지는 생성자
      class ES
      {
      public:
          ES(initializer_list<double> args)
          {
              for (const auto& value : args)
              {
                  m_sequence.push_back(value);
              }
          }
      }

위임 생성자

  • 생성자에서 다른 생성자를 호출

  • 위임한 생성자를 먼저 호출함

      class Person {
      public:
          // 기본 생성자
          Person() : Person("Unknown", 0) {
              std::cout << "Default constructor called\n";
          }
          // 이름만 초기화하는 생성자
          Person(const std::string& name) : Person(name, 0) {
              std::cout << "Constructor with name only called\n";
          }
          // 이름과 나이를 초기화하는 생성자 (메인 생성자)
          Person(const std::string& name, int age) : name(name), age(age) {
              std::cout << "Main constructor called\n";
          }
      };
      int main() {
          Person p1;                      // 기본 생성자 호출
          Person p2("Alice");             // 이름만 초기화
          Person p3("Bob", 30);           // 이름과 나이 초기화
    
          p1.printInfo(); // Main -> Default
          p2.printInfo(); // Main -> Name Only
          p3.printInfo(); // Main
      }

명시적 생성자

  • 생성자는 받은 값을 암묵적으로 변환하여 호출 됨. (변환 생성자)

  • 이를 막고자 하면, explicit 키워드를 생성자 앞에 붙임.

    • 암묵적 변환이 필요한 경우 아니면, explicit 지정 권장
  • 사용 예

      void process(const MyClass& c)
      class MyClass{
      public:
          explicit MyClass(int);    
      }
    
      process({1}) // X  <- 암시적 변환 허용 막기
      process(MyClass{1}); // O

사용자 정의 소멸자 사용 주의점

  • c++11부터 사용자 정의 소멸자가 있으면, 기본으로 복제 생성자를 생성해주지 않음

복제 대입연산자

  • AClass& operator=(const AClass& rhs) = default

이동 연산자

  • 이동 연산자를 사용하면, 복제 대신 얇은 복사와 비슷한 방식으로 소유권을 옮김.
    • 소유권을 옮긴 것이기에 주소는 여전히 해당 위치를 가르키고 있음.
    • 그렇기에, nullptr로 초기화 해 주는게 좋음
  • &&를 사용해서 매개변수를 rvalue로 변환, noexcept키워드를 사용해 중간에 에러로 멈추지 않도록 함
  • 함수에서 로컬 변수나 매개 변수를 리턴할 때는 사용 금지
    • RVO, NRVO를 막아 최적화가 더 안좋아짐
    • 3항 연산자로 return 문에 작성도 최적화를 막으니 사용 금지
  • 이동 생성자
      SpreadSheet::SpreadSheet(SpreadSheet&& src) noexcept {};
  • 이동 대입 연산자
      SpreadSheet& SpreadSheet::operator=(SpreadSheet&& src) noexcept {};

'공부 > C++' 카테고리의 다른 글

(C++17,20)구조적 바인딩  (0) 2025.03.05
튜플(Tuple)  (0) 2025.03.05
함수  (1) 2025.03.03
enum_class  (0) 2025.03.03
C++ 변수  (0) 2025.03.03

+ Recent posts

let textNodes = document.querySelectorAll("div.tt_article_useless_p_margin.contents_style > *:not(figure):not(pre)"); textNodes.forEach(function(a) { a.innerHTML = a.innerHTML.replace(/`(.*?)`/g, '$1'); });