GPG 사이트에서 과객 이라는 분이 올리신 글입니다.
좋은글 같아서 퍼왔습니다. 과객님께 직접 허락은 못받았지만...
류광님께서 허락을 받으시고 gpgiki 에 올리 셨습니다.

GPG 게시판
http://gpgstudy.com/forum/viewtopic.php?t=1451

gpgiki
http://www.gpgstudy.com/gpgiki/DxDeviceLostHandling

----------------------------------------------------------------------------------------
< 디바이스 소실의 두가지 경우 >

1. 디바이스 소실되었고 복구 불가능의 경우
  : 이 경우에는 대책이 없다. 전부(IDirect3DDevice를 통해 생성된 모든 리소스들과
    IDirect3DDeivce객체) 해제한 후 처음부터 다시 생성한다.
    
2. 디바이스 소실되었고 복구 가능의 경우
  : 이 경우에는 디바이스에 올라간 리소스(리소스 생성시 D3DPOOL_DEFAULT 플래그를
    이용하여 생성된 객체들)들만 새로 로딩하면 된다.


< 특이사항 >

-  디바이스가 소실되어도 IDirect3DDevice관련 함수들이 성공을 반환 할 경우가 있다.
  도움말에 보면 "디바이스가 소실되어도 동작은 보증된다"고 되어있다. 물론 이때의
  동작은 헛동작이다. 아무 효과가 없는것이다. 왜 이런식인가 생각해보니, 밑의 글을
  읽어보면 알겠지만,, 언제 어떻게 디바이스 손실이 발생할지 모르니 프로그래머로써는
  이런 상황을 대처 할 수 없는것이다. 디바이스와 관련된 모든 함수들에 대해서, 리턴값을
  체크하지 않는한 불가능하다는 얘기이다. 그리고 손실된 디바이스 자원이 언제 넘어오게
  될지도 모르는 상황에서 이런 처리는 너무 고난이도일껏 같다. 차라리 처리되는것 처럼
  보이고, 실제로는 처리가 안되게 하는것이 훨씬 이득일껏 같다.
  어디까지 제 추측입니다. 틀렸어도 양해를..


< 왜 이딴게 있는걸까? >

예를들면, 2개의 응용프로그램 A와 B가 돌아가는 상황에서, A란 놈과 B란 놈 둘다 비디오
램을 많이 사용하는 프로그램이었다 치자. 이 상황에서 A란 프로그램이 돌아가고 있는
상황이어서 비디오 램의 여유분이 없었다 치자, 자.. 이제 문제의 B가 실행되어 졌을 경우,,
어떻게 해야할까? Win32는 멀티 태스킹 환경이다. 고로 여러 응용프로그램들이 같은
하드웨어 자원을 공유한다. 즉, DOS처럼 하나의 응용프로그램이 하드웨어 자원을 독점하면
안된다는 것이다. 고로 윈도우즈가 택한방식이 활성화 애플리케이션에게 자원을 넘겨주자는
방식이다. A가 활성화되면, A에게 자원을 주고, B가 활성화되면 B에게 자원을 준다.
그러므로 사용자는 두 응용프로그램을 동시에 사용할 수 있는것이다. 덕분에 프로그래머는
귀찮지만은... 어디까지 제 추측입니다. 틀렸어도 양해를.. 아니지 지적을..


< 디바이스 소실 처리 >

0. 디바이스를 복구 할 수 있는지 검사해본다.
  cf) IDirect3DDevice::TestCooperativeLevel()을 호출하여,,
      반환값(HRESULT)이 D3DERR_DEVICELOST라면 디바이스를 처음부터 다시
      생성해야하는 경우이고, D3DERR_DEVICENOTRESET이라면 장치는 소실됬지만,
      IDirect3DDevice::Reset()을 통해서 복구 할 수 있는 상태이다.

1.디바이스를 복구 할 수 있다면,,,
  1.1. D3DPOOL_DEFAULT로 잡은 리소스들을 전부 릴리즈한다.
      (안그러면 다음에 호출될 IDirect3DDevice::Reset()이 실패할것이다.)
  1.2. IDirect3DDevice::Reset()을 호출한다.
      (Reset()을 그냥 IDirect3DDevice의 복구명령어라 생각하면 이해가 편함)
  1.3. 디바이스를 다시 셋팅한다.(랜더스테이트, 뷰 행렬등..)
  1.3. D3DPOOL_DEFAULT로 잡은 리소스들을 다시 로드한다.

2.디바이스를 복구 할 수 없다면,,,
  2.1. 모든 리소스들을 릴리즈한다.
  2.2. 디바이스를 릴리즈한다.
  2.3. 디바이스를 새로 생성한다.
  2.4. 디바이스를 다시 셋팅한다.(랜더스테이트, 뷰 행렬등..)
  2.4. 모든 리소스들을 다시 로드한다.

  
  
< 디바이스가 소실되는경우 >

# 풀스크린 모드일때..
  - 다른 애플리케이션이 활성화 되어서, 나의 애플리케이션이 쪼그라 들경우,,
    (즉, 작업표시줄로 쏙 들어갈경우)
  - 전력관리 이벤트(시스템 대기모드라든지)가 발생 할 경우..
    cf)이경우 윈도우 메세지로 윈도우프로시져에서 검출 할 수 있을것 같음. <= 정확히는 모르겠음
  - 다른 애플리케이션이 풀스크린 모드로 생성될경우..

# 윈도우 모드일때..
  - 전력관리 이벤트가 발생했을 경우.
  - 다른 애플리케이션이 풀스크린 모드로 생성될경우..
  (이경우 제 카드에서는 문제가 없었음)
  
  
< 테스트 사항 >

카드 : Radeon 9000 128M,  OS : Win 2000

A,B : D3D를 사용하는 애플리케이션

0. A:win, B:win ==> A:full, B:win      // (A를 풀스크린으로 전환)
  : A:ok, B:ok

1. A:full, B:win ===> A:win B:win      // (A를 윈도우모드로 전환)
  - A:ok, B:lost(고칠수 있음)

2. A:full, B:win ===> A:full(minimize), B:win // (마우스로 바탕화면을 찍거나 Alt+Tab)
  - A:lost(고칠수 없음), B:lost(고칠수 없음)

3. A:full(lost), B:win ===> A:full(lost), B:full // (B를 풀스크린으로 전환)
  - A:lost상태 유지, B:ok (A는 시작하기 전에 이미 lost상태였음)
    
4. A:full(lost), B:full ===> A:full(lost), B:full(lost)  // (바탕화면 찍음)
  - A:(lost상태유지), B:lost(고칠수 없음) (A는 시작전에 이미 lost상태였음)
    
5. A:full, B:win ===> A:종료, B:win(lost)  // (A를 종료)
  - A:종료됨, B:lost(고칠수 있음)
    
X. 기타 등등, 여러 상황에 따라 디바이스 로스트가 발생 된다.
  이런 상황이 더 있으나 해결책을 어느정도 찾은것 같으므로 더 이상은 생략.
X. 그리고 다른 그래픽 카드의경우는 어떻게 처리될지 잘모르겠음.


< 결론 >

1. Present()함수의 리턴값은 항상 체크해야 한다. 위와 같이 언제 디바이스 손실이
  발생 할지 모른다.

2. 임의로 체크해 보고 싶은 경우. TestCooperativeLevel()함수를 이용하자.



< 방법론 >

필수사항 : Present()리턴값은 항상 반드시 꼭 체크하자. 언제 어떻게 어떤 이유로
        Present()가 실패할지 모른다.  이건 필수이다.
        그리고 Present()가 실패하면 위와같은 방법으로 복구하면 될꺼 같다.
    
0. Present()함수 실패시만 복원한다.
  : 단점으로는 에러를 알아내는 시점이 Present()가 호출될때(뷰포트를 하나 생성한
    경우라면 한프레임에 한번씩)뿐 이라는 것이다. 이 단점은 랜더링 결과물에 접근하여,
    그것이 애플리케이션 로직에 적용되는 경우라면 치명적일 수 있다고 예상된다.
    그점을 제외하면 가장 무난할 껏 같다.
          
1. CD3DApplication(Direct3D Wizzard에서 제공)의 방식
  : 이 클래스를 보면,, 꽤나 복잡한 방식(주로 윈도우 메세지를 핸들링한다.)으로 처리
    하는데(즉, 디바이스 손실이 일어나기전에 미치 처리함)으로 해결하고 있다. 꼭
    이렇게 까지 해야 하는지는 잘모르겠지만...  요는 여러가지 상황에 미리 대처한다는
    방식이다. 위의 <테스트 사항>같은 짓을 많이 시도해보고, 어느때 디바이스가 손실되는지
    정확하게 알아낸 후, 미리알 수 있는경우(풀스크린에서 Alt+tab에 의해 갑자기
    비활성화 시킨다든지 등등) 할수 있는데까지 까지 하는것이다. 그런 눈물겨운 노력이
    CD3DApplication에 보면 잘 나온다. 이런건 보고 베끼자...^^ 이경우 100% 문제를
    해결했다고 볼 수는 없다고 생각되지만 0번의 경우보단 낫다. 이런 문제를 봐선
    Direct3D의 설계의 결함같기도 하다. 결국 이런 문제들을 100% 해결 할 수는 없다고
    생각된다.
      
결과 : 실제로 1번과 같이 대부분 예측되는 치명 적인 몇몇 가지들에 대해 미리 알아내서
      처리해주면,, 거의 문제는 안생긴다. 대신 코딩이 지저분해지는건 어쩔수 없다.


< stub code >

void RenderAll()
{
  // ...
  // 모든 그래픽들에 대한 랜더링을 한다.
  // ...
    
  // 백버퍼 플리핑.
  hr = m_pDevice->Present( NULL, NULL, NULL, NULL );
  if ( FAILED(hr) )
  {
      if ( D3DERR_DEVICELOST == hr )
      {
        // 디바이스를 소실했고, 복구가 불가능 상태
      }
      else ( D3DERR_DEVICENOTRESET == hr )
      {
        // 디바이스를 소실했지만, 복구가 가능한경우.
      }
  }
}


< 설계 >

한가지 방법은 랜더러 클래스(또는 이런 이벤트를 처리할 객체)에 두가지상황(디바이스 손실시
복구가능/복구불가능)이 발생했을때, 로딩할 목록을 등록해 놓은 후, 이벤트가 발생하면 각각
해당 에벤트에 맞게끔 다시 로드하는 식으로, 구성하면 될 껏 같다.

//**** 디바이스 리소스 인터페이스..
// 디바이스 의존적인 리소스들의 추상클래스.
struct IDeviceRes
{
  virtual void Invalidate() = 0;
  virtual bool SetValidate() = 0;
};

//**** 텍스쳐 클래스
class CTextureRes : public IDeviceRes
{
public:
  virtual void Invalidate()      { 텍스쳐 해제; }
  virtual bool SetValidate()      { 텍스쳐 다시 로드; }
    
  bool  Load();
  void  Unload();
    
protected:
  char            m_szFileName[256];
  LPDIRECT3DTEXTURE9  m_pTexture;
};

//**** 텍스쳐를 관리할 클래스(클라이언트에서 접근하여 사용)
class CDataContainer
{
public:
  LoadTexture( const char* szFileName )
  {
      // 1. 텍스쳐 로딩
      // 2. 만약 텍스쳐가 VRam에 잡혔다면..(D3DPOOL_DEFAULT)
      //    2.1 랜더러의 의 VRam리스트에 등록(m_pRenderer->m_VRamRes)
      // 3. 텍스쳐가 다른모드로 생성되었다면..(D3DPOOL_MANAGED)
      //      3.1 랜더러의 의 VRam리스트에 등록(m_pRenderer->m_ManagedRes)
  }
    
  UnloadTexture( const char* szFileName )
  {
      // 랜더러의 디바이스 목록에서 지운다.
      m_pRenderer->Unregist( szFileName );
  }

protected:
  vector<CTextureRes*>  m_TextureContainer;
  CRenderer*            m_pRenderer;
}

//**** 문제의 랜더러 클래스
class CRenderer
{
public:
  // 디바이스가 해제 됬거나 해제 하라.
  // - 이 함수는 외부에서 임의로 호출이 가능하다.
  void InvalidateDevice( HRESULT hrDeviceState )
  {
      if ( FAILED( hrDeviceState )
      {
        if ( D3DERR_DEVICENOTRESET == hrDeviceState )
        {
            // 1. m_VRamRes 리소스만 해제한다.
        }
        else if ( D3DERR_DEVICELOST == hrDeviceState )
        {
            // 1. m_VRamRes과 m_ManagedRes들에 대해서 해제한다.(전 리소스들에 대해 수행)
            // 2. 디바이스를 릴리즈한다.
        }
      }
      m_hrDeviceState = hrDeviceState; // 디바이스 상태값 업데이트
  }
    
  // 디바이스가 활성화하라.
  // - 이 함수도 외부에서 임의로 호출이 가능하다.
  bool SetValidateDevice()
  {
      if ( FAILED(m_hrDeviceState) )
      {
        if ( D3DERR_DEVICENOTRESET == m_hrDeviceState )
        {
            //**** 디바이스를 복구할수 있다.
            // 1. IDrect3DDevice::Reset()한다.
            // 2. 랜더 스테이트 / 뷰행렬 셋팅
            // 3. m_VRamRes 리소스들을 다시 로딩한다.
        }
        else if ( D3DERR_DEVICELOST == hrDeviceState )
        {
            //**** m_VRamRes 복구할수 없다.
            // 1. 디바이스를 재생성한다.
            // 2. 랜더 스테이트 / 뷰행렬 셋팅
            // 3. m_VRamRes / m_ManagedRes 리소스를 로딩한다.
        }
          
        // 검증해본다.
        m_hrDeviceState = m_pDevice->TestCooperativeLevel();
        return SUCCEEDED(m_hrDeviceState);
      }
      return true;
  }

  // 랜더링한다.    
  void RenderAll()
  {
      //**** 디바이스가 유효한 상태가 아니라면 그냥 리턴.
      if ( FAILED(m_hrDeviceState) )  return;
      
      // ...
      //**** 모든 그래픽들에 대한 랜더링을 한다.
      // ...
      
      //**** 백버퍼 플리핑.
      m_hrDeviceState = m_pDevice->Present( NULL, NULL, NULL, NULL );
      if ( FAILED(m_hrDeviceState) )
      {
        // 디바이스에 뭔가 이상이 있다면,,
        InvalidateDevice( m_hrDeviceState );
          
        // 외부에 알려준다. 아마 누군가에게 알려줄 필요가 있을껏이다.
        // CApp나 혹은 다른 누군가에게..
      }
  }
    
protected:
  //**** properties
  // 디바이스 의존적인 메모리 관리
  vector<IDeviceResource*>  m_VRamRes;      // POOL_DEFAULT
  vector<IDeviceResource*>  m_ManagedRes;  // MANAGED or etc
  HRESULT                  m_hrDeviceState;      // 현재의 디바이스 상태값
    
  LPDIRECT3DDEVICE9        m_pDevice;
};


< 추신 >

괜찮은 방법이나, 사용되어지는 방법이 있으면 올려주세요.
저도 구현은 안해본 사람입니다. 아마 틀린 부분도 있으리라 생각됩니다. 많은 지적을..
써놓고 보니 장문이네요.

----------------------------------------------------------------------------------------

참고로 기타 건의,수정사항등은 아래의 페이지에서... ^^;;

GPG 게시판
http://gpgstudy.com/forum/viewtopic.php?t=1451

gpgiki
http://www.gpgstudy.com/gpgiki/DxDeviceLostHandling