DirectX Graphics 프로그래밍 가이드 튜토리얼, 샘플, 툴, 힌트 프로그래밍의 힌트 퍼포먼스의 최적화   [목차열람] [주소복사] [슬롯비우기]
퍼포먼스의 최적화
 
Microsoft DirectX 9.0

퍼포먼스의 최적화


3D 그래픽스를 사용하는 리얼타임 애플리케이션의 개발자에게 있어, 퍼포먼스의 최적화는 관심사의 1 개이다. 여기에서는, 최고의 퍼포먼스를 얻을 수 있는 코드를 기술하기 위한 가이드 라인에 대해 설명한다.

퍼포먼스에 관한 일반적인 힌트

다음에 나타내는 일반적인 가이드 라인에 따라, 애플리케이션의 퍼포먼스를 향상시키면 좋다.

데이타베이스 및 컬링(Culling)

Microsoft® Direct3D® 가 뛰어난 퍼포먼스를 실현하기 위한 열쇠가 되는 것은, 월드내의 개체를 거두는 신뢰성 있는 데이타베이스를 구축하는 것이다. 이것은 래스터화나 하드웨어의 개선보다 중요하다.

다각형의 개수는, 관리할 수 있는 범위내에서 최소한으로 억제할 필요가 있다. 다각형의 개수를 줄이려면 , 최초부터 그 개수가 적어도 되는 것 같은 모델을 구축한다. 다각형의 추가는, 개발 단계의 후반으로, 퍼포먼스를 희생하지 않고 추가할 수 있는 경우에 실시한다. 최고 속도의 다각형은 드로잉(Drawing) 하지 않는 다각형인 것을 잊지 않는 것.

기본도형의 배치처리

실행시에 최고의 렌더링 퍼포먼스를 얻으려면 , 기본도형를 배치처리 해, 렌더링 스테이트의 변경 회수를 가능한 한 줄인다. 예를 들어, 1 개의 개체로 2 개의 텍스처를 사용하고 있는 경우, 최초의 텍스처를 사용하는 삼각형을 그룹화 해, 필요한 렌더링 스테이트를 그 삼각형에 부가해 텍스처를 변경한다. 다음에, 이제 1 개의 텍스처를 사용하는 삼각형을 모두 그룹화 한다. Direct3D 의 가장 단순한 하드웨어 지원은, 하드웨어 추상 계층 (HAL)을 개입시켜, 렌더링 스테이트의 배치 및 기본도형의 배치에 의해 호출된다. 명령의 배치처리를 효율적으로 실시하면, 실행시의 HAL 의 호출 회수가 감소한다.

조명에 관한 힌트

라이트를 적용했을 경우, 렌더링 되는 각 프레임에 정점 단위의 부하가 더해진다. 따라서, 애플리케이션이 라이트를 신중하게 사용하면, 퍼포먼스의 대폭적인 향상을 전망할 수 있다. 다음의 힌트의 대부분은,"최고 속도의 코드는 불려 가지 않는 코드이다" 라고 하는 생각에 근거한다.

텍스처 사이즈

텍스처 맵핑의 퍼포먼스는, 메모리의 속도에 크게 의존한다. 애플리케이션의 텍스처를 캐쉬할 때의 퍼포먼스를 최대한으로 높일 방법이 몇개인가 있다.

동적 텍스처의 사용법

드라이버가 동적 텍스처를 지원 하고 있을지 어떨지를 확인하려면 ,D3DCAPS9 구조체의 D3DCAPS2_DYNAMICTEXTURES 플래그를 조사한다.

동적 텍스처를 사용하는 경우는, 항상 다음의 점에 주의 해야 한다.

동적 텍스처는 포맷 마다 1 개만, 또 가능하면 사이즈 마다 1 개인 만큼 생성 하는 것이 좋겠다. 모든 레벨을 잠그는 것에 의해 오버헤드가 증대하므로, 동적인 밉맵, 큐브, 및 볼륨은 추천 할 수 없다. 밉맵의 경우, 최상정도 레벨에서만 LOCK_DISCARD 를 사용할 수 있다. 최상정도 레벨을 잠금만으로 모든 레벨이 파기된다. 이 동작은, 볼륨 및 큐브에서도 같이이다. 큐브의 경우, 최상정도 레벨 및 면 0 이 잠금 된다.

다음의 의사 코드는, 동적 텍스처의 사용예를 나타내고 있다.

DrawProceduralTexture(pTex)
{
    // pTex should not be very small because overhead of 
    //   calling driver every D3DLOCK_DISCARD will not 
    //   justify the performance gain.  Experimentation is encouraged.
    pTex->Lock(D3DLOCK_DISCARD);
    <Overwrite *entire* texture>
    pTex->Unlock();
    pDev->SetTexture();
    pDev->DrawPrimitive();
}

동적인 정점 버퍼와 인덱스 버퍼의 사용법

그래픽스 프로세서에 의한 사용중에 정적인 정점 버퍼를 잠그면 퍼포먼스가 큰폭으로 저하할 가능성이 있다. 잠금 호출은, 호출원의 애플리케이션에 돌아오기 전에, 그래픽스 프로세서가 버퍼로부터 정점 데이터 또는 인덱스 데이터의 읽기를 완료할 때까지 대기할 필요가 있어, 대폭적인 지연이 생긴다. 그래픽스 프로세서는 잠금 포인터에 돌아오기 전에 커멘드를 완료할 필요가 있기 (위해)때문에, 스태틱 버퍼에 대해서 잠금과 렌더링을 1 프레임 근처 여러 차례 실행하는 경우도, 그래픽스 프로세서는 렌더링 커멘드를 버퍼에 넣을 수가 없다. 버퍼에 저장 된 커멘드가 없으면 애플리케이션이 정점 버퍼 또는 인덱스 버퍼에의 저장을 완료해 렌더링 커멘드를 발행할 때까지, 그래픽스 프로세서는 아이돌이 된다.

이상은 정점 데이터 또는 인덱스 데이터가 전혀 변화하지 않는 것이지만, 이것은 반드시 가능하지 않다. 애플리케이션이 프레임 마다 정점 데이터 또는 인덱스 데이터를 변경하는 경우는 많이 있어, 프레임 근처 여러 차례 변경하는 것 조차 있다. 이러한 경우는, D3DUSAGE_DYNAMIC 를 사용해 정점 버퍼 또는 인덱스 버퍼를 생성 할 필요가 있다. 이 이용 플래그에 의해 Direct3D 를 빈번한 잠금 처리용으로 최적화한다. D3DUSAGE_DYNAMIC 는 버퍼를 빈번하게 잠그는 경우에만 유용하고, 변화하지 않는 데이터는 정적인 정점 버퍼 또는 인덱스 버퍼에 저장 할 필요가 있다.

애플리케이션으로 동적인 정점 버퍼 사용시의 퍼포먼스를 향상시키려면 , 적절한 플래그를 지정해 IDirect3DVertexBuffer9::Lock 또는 IDirect3DIndexBuffer9::Lock 를 호출할 필요가 있다. D3DLOCK_DISCARD 는, 애플리케이션이 낡은 정점 데이터 또는 인덱스 데이터를 버퍼내 로 유지할 필요가 없는 것을 나타낸다. D3DLOCK_DISCARD 로 잠금을 호출한 시점에서 그래픽스 프로세서가 아직 버퍼를 사용중인 경우는, 낡은 버퍼 데이터 대신에 메모리의 새로운 영역의 포인터가 반환된다. 이것에 의해, 애플리케이션이 새로운 버퍼에 데이터를 저장 할 때까지, 그래픽스 프로세서는 낡은 데이터를 계속 사용할 수가 있다. 애플리케이션에 의한 추가의 메모리 메니지먼트는 필요없다. 그래픽스 프로세서가 낡은 버퍼를 다 사용하면 , 낡은 버퍼는 자동적으로 재이용 또는 파기된다. D3DLOCK_DISCARD 를 사용해 버퍼를 잠그면 항상 버퍼 전체가 파기되는 것, 비제로의 오프셋(offset) 또는 제한부의 사이즈 필드를 지정 하면, 잠그지 않은 버퍼 영역의 정보는 보관 유지되지 않는 것에 주의 해야 한다.

스프라이트를 렌더링 하기 위해서 4 개의 정점을 추가하는 경우와 같이, 애플리케이션이 1 회의 잠금으로 저장 하는 데이터의 양이 적은 경우가 있다. D3DLOCK_NOOVERWRITE 는, 이미 사용중인 동적 버퍼내의 데이터를 애플리케이션이 덧쓰기하지 않는 것을 나타낸다. 잠금 호출은 낡은 데이터의 포인터를 돌려준다. 이것에 의해, 애플리케이션은 정점 버퍼 또는 인덱스 버퍼내의 미사용 영역에 새로운 데이터를 추가할 수 있다. 드로잉(Drawing) 처리로 사용한 정점 또는 인덱스는, 그래픽스 프로세서에 의해 아직 사용중일 가능성이 있기 (위해)때문에, 애플리케이션으로 변경해서는 안 된다. 동적 버퍼가 가득 되어 메모리의 새로운 영역을 납득할 수 없게 되면 애플리케이션은 D3DLOCK_DISCARD 를 사용해, 그래픽스 프로세서가 다 사용한 후의 낡은 정점 데이터 또는 인덱스 데이터를 파기한다.

그래픽스 프로세서가 정점을 아직 사용중인지 아닌지의 확인에는, 비동기 문의 메카니즘이 도움이 된다. 정점을 사용하는 마지막 DrawPrimitive 호출의 뒤, D3DQUERYTYPE_EVENT 타입의 문의를 발행한다. IDirect3DQuery9::GetData 가 S_OK 를 돌려주었을 경우, 정점은 이미 사용중은 아니다. 버퍼를 잠글 때 D3DLOCK_DISCARD 를 지정했을 경우, 또는 플래그를 지정하지 않았던 경우는, 항상 정점이 그래픽스 프로세서와 적절히 동기 하지만, 플래그를 지정하지 않고 잠금을 실행 하면, 위의와 우리 퍼포먼스의 저하를 가져올 가능성이 있다. BeginScene ,EndScene ,Present 등의 그 외의 API 호출에서는, 그래픽스 프로세서가 정점을 다 사용했던 것은 보증되지 않는다.

동적 버퍼와 적절한 잠금 플래그의 사용법은 다음과 같다.

	    // USAGE STYLE 1
    // Discard the entire vertex buffer and refill with thousands of vertices.
    // Might contain multiple objects and/or require multiple DrawPrimitive 
    //   calls separated by state changes, etc.
 
    // Determine the size of data to be moved into the vertex buffer.
    UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
 
    // Discard and refill the used portion of the vertex buffer.
    CONST DWORD dwLockFlags = D3DLOCK_DISCARD;
    
    // Lock the vertex buffer.
    BYTE* pBytes;
    if( FAILED( m_pVertexBuffer->Lock( 0, 0, &pBytes, dwLockFlags ) ) )
        return false;
    
    // Copy the vertices into the vertex buffer.
    memcpy( pBytes, pVertices, nSizeOfData );
    m_pVertexBuffer->Unlock();
 
    // Render the primitives.
    m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, nNumberOfVertices/3)
	    // USAGE STYLE 2
    // Reusing one vertex buffer for multiple objects
 
    // Determine the size of data to be moved into the vertex buffer.
    UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
 
    // No overwrite will be used if the vertices can fit into 
    //   the space remaining in the vertex buffer.
    DWORD dwLockFlags = D3DLOCK_NOOVERWRITE;
    
    // Check to see if the entire vertex buffer has been used up yet.
    if( m_nNextVertexData > m_nSizeOfVB - nSizeOfData )
    {
        // No space remains.  Start over from the beginning 
        //   of the vertex buffer.
        dwLockFlags = D3DLOCK_DISCARD;
        m_nNextVertexData = 0;
    }
    
    // Lock the vertex buffer.
    BYTE* pBytes;
    if( FAILED( m_pVertexBuffer->Lock( (UINT) m_nNextVertexData, nSizeOfData, 
               &pBytes, dwLockFlags ) ) )
        return false;
    
    // Copy the vertices into the vertex buffer.
    memcpy( pBytes, pVertices, nSizeOfData );
    m_pVertexBuffer->Unlock();
 
    // Render the primitives.
    m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 
               m_nNextVertexData/m_nVertexStride, nNumberOfVertices/3)
 
    // Advance to the next position in the vertex buffer.
    m_nNextVertexData += nSizeOfData;

메쉬의 사용법

메쉬를 최적화하려면 , Direct3D 의 인덱스 첨부 삼각형 스트립은 아니고, 인덱스 첨부 삼각형을 사용한다. 하드웨어는, 연속하는 삼각형의 95% 가 실제로 스트립을 형성하고 있는 것으로서 검출해, 거기에 응해 조정을 실시한다. 많은 드라이버는, 유산 하드웨어에 대해서도 이러한 처리를 실시한다.

Direct3D extension (D3DX) 메쉬 개체는, 각 삼각형, 즉 면에 DWORD 로 태그 붙이고 할 수가 있다. 이 태그는, 그 면의속성이라고 부른다. DWORD 의 의미들은 사용자가 정의한다. D3DX 는 이것들을 사용해 메쉬를 서브 세트로 분류한다. 애플리케이션은,ID3DXMesh::LockAttributeBuffer 의 호출을 사용해 각면의 속성을 설정한다. ID3DXMesh::Optimize 메서드에서는, D3DXMESHOPT_ATTRSORT 옵션을 사용해, 메쉬의 정점 및 면을 속성에 의해 그룹화 할 수 있다. 이것을 실시하면, 메쉬 개체에 의해 속성 테이블이 계산된다. 애플리케이션은 ID3DXBaseMesh::GetAttributeTable 를 호출해, 이 테이블을 취득할 수 있다. 메쉬가 속성에 의해 분류되지 않은 경우, 이 호출은 0 을 돌려준다. 속성 테이블은 ID3DXMesh::Optimize 메서드가 생성하므로, 애플리케이션이 이 테이블을 설정할 수 없다. 속성에 의한 분류는 데이터의 영향을 받는다. 따라서, 애플리케이션은, 메쉬가 속성에 근거해 분류되고 있는 것을 알 수 있는 경우에서도,ID3DXMesh::Optimize 를 호출해 속성 테이블을 생성할 필요가 있다.

이하에, 메쉬의 다양한 속성에 대해 가리킨다.

속성 ID

속성 식별자 (ID)는, 면의 그룹을 속성 그룹에 관련짓는 값이다. 이 ID 는,ID3DXBaseMesh::DrawSubset 로 드로잉(Drawing) 하는 면의 서브 세트를 기술한다. 속성 ID 는, 속성 버퍼내의 면에 대해서 지정한다. 속성 ID 의 실제의 값은, 32 비트에 들어가면 어떠한 값에서도 상관없지만, 일반적으로는 0 ~ n (n 는 속성의 수)를 사용한다.

속성 버퍼

속성 버퍼는, 각면이 속하는 속성 그룹을 지정하는 DWORD (각면에 1 개)의 배열이다. 이 버퍼는, 메쉬의 생성시에 제로에 초기화되지만, 로드 루틴에 의해 값을 지정하는지, ID 0 의 속성이 복수개 필요한 경우에는 사용자에 의해 지정할까의 몇개의 필요가 있다. 이 버퍼에는,ID3DXMesh::Optimize 로 속성에 근거해 메쉬를 소트 하기 위해서 사용하는 정보가 저장 된다. 속성 테이블이 제시되지 않은 경우,ID3DXBaseMesh::DrawSubset 는 이 버퍼를 조사해, 드로잉(Drawing) 하는 지정의 속성의 면을 선택한다.

속성 테이블

속성 테이블은, 메쉬에 의해 소유·보관 유지되는 구조체이다. 이것을 생성하려면 , 속성 소트 기능 또는 최적화의 강화 기능을 유효하게 해 ID3DXMesh::Optimize 를 호출할 필요가 있다. 속성 테이블을 사용하면 기본도형를 드로잉(Drawing) 하는 ID3DXBaseMesh::DrawSubset 의 단일의 호출을 재빠르게 실시할 수가 있다. 또, 프로그래시브 메쉬도 이 구조체를 보관 유지하므로, 현재의 상세 레벨로 액티브한 면과 정점을 표시할 수 있다.

z 버퍼의 퍼포먼스

신이 전부터 후에 렌더링 되도록(듯이), z 버퍼링을 사용해 텍스처 처리를 실시하면, 애플리케이션의 퍼포먼스를 향상시킬 수가 있다. 텍스처를 적용해, z 버퍼를 사용한 기본도형는, 주사선 단위로 z 버퍼에 대해 사전에 테스트된다. 전에 렌더링 된 다각형에 의해 주사선이 숨어 있는 경우는, 그 다각형이 시스템에 의해 재빠르게 효율적으로 제거된다. z 버퍼링에 의해 퍼포먼스는 향상하지만, 이 방법은 장면(scene)로 같은 픽셀을 여러 차례 드로잉(Drawing) 할 경우에 가장 유효하다. 이것을 정확하게 계산하는 것은 어렵지만, 근사치를 요구할 수 있다. 같은 픽셀을 1 회 밖에 드로잉(Drawing) 하지 않는 경우는, z 버퍼링을 오프로 해, 장면(scene)를 나중에 전에 렌더링 하면, 최고의 퍼포먼스를 얻을 수 있다.



© 2002 Microsoft Corporation. All rights reserved.
↑TOP