HoloLens 개발메모 #6

HoloLens의 카메라를 다루는 방법에 대해서 약간 감을 잡았다고 생각했다. 이제 뭘 어떻게 해야할지 알겠다…라고 생각하고 앞으로 어떻게 진행할지 계획을 잡았다.

그런데 현재 프레임 레이트가 너무 안나온다. 이상할 정도로 안나온다. 문서에 보면 60FPS를 유지하라고 나와있지만 현재 이미지만 띄우는 타이틀 화면에서조차 30프레임이 안나온다. 에뮬레이터쪽이 성능이 더 나오긴 한다. 하지만 에뮬레이터에서도 60프레임은 안나온다.
같은 상황 데스크탑에선 400프레임을 넘기고 있다.

흠…엔진이 debug빌드라 그런가…이렇게까지 차이가 날리는 없지만 워낙 하드웨어 성능이 떨어져서 그럴수 있겠다 싶어 release 빌드로 테스트하기로 했다.
엔진과 데모앱을 relase빌드로 만들어서 HoloLens 배포했다. 그렇게 실행해보니…

‘캐릭터 모델이 안나오네.’

대체 이게 뭔 일인가. 이 엔진은 내가 수년간 테스트해왔다. debug,release, x86/x64/arm까지 늘 테스트해왔다. 동일 코드의 arm release빌드는 Windows Phone에서도 잘 돌아간다.

몇번이나 테스트를 해봐도 release빌드에선 캐릭터 모델이 렌더링 되지 않는다.
같은 엔진, 같은 렌더링 코드를 사용하는 win32빌드, UWP빌드를 테스트해도 모두 정상. 심지어 같은 코드를 HoloLens 에뮬레이터에서 돌려도 debug/release빌드 모두 정상적으로 렌더링 된다.

HoloLens디바이스에서 release빌드로 돌리는 경우만 문제가 발생한다.

이런 류의 버그는 십수년간 꽤나 잡아왔다. 테스트 환경이 데스크탑이었다면 이 정도는 웃으면서 디버깅할 수 있다. 문제는 HoloLens 디바이스에서만 문제가 발생한다는 것이다. 즉 HoloLens를 머리에 쓰고 디버깅을 해야한다. 이건 큰 문제다. 이걸 쓰고 모니터를 바라보고 있으면 눈알이 빠질것 같다.

  1. 우선 나름의 디버깅 환경을 구축했다.
  2. HoloLens에 전원 케이블을 연결한다.
  3. 전원 설정에서 파워 케이블이 연결된 경우 30분뒤(이게 최대치다)에 sleep모드로 진입하도록 설정한다.
  4. 옆에 노트북을 펴놓고 HoloLens의 IP어드레스로 접속한다.
  5. Mixeed Reality Capture 메뉴에서 Holograms에만 체크를 하고 Live View를 켠다.
  6. OK.이것으로 HoloLens를 머리에 쓰지 않아도 모니터 화면으로 HoloLens의 화면을 볼 수 있다.


wp_20170103_00_33_36_rich

그리고 하루종일 원인을 조사했다. 내 엔진은 shader cache file을 만들기는 하지만 기본적으로 최초 로딩시에 HLSL Shader 파일들을 컴파일 한다. 이때 debug빌드에선 모든 최적화를 끈다. Release빌드에선 Optimize Level 3으로 최적화를 켠다.

여기서 캐릭터를 렌더링하는 쉐이더가 들어있는 파일에 대해서 최적화를 끄면 정상 작동한다. HLSL파일을 컴파일할때 최적화 옵션을 켜면 문제가 된다.
그리고 그 중에서 특정 Geometry Shader에 대해 최적화를 켜면 문제가 발생하는 것을 확인했다.

사실 이 Geometry Shader는 꼭 필요한 shader는 아니다.
HoloLens의 렌더링 파이프 라인에서 굉장히 중요한게 왼쪽/오른쪽 눈에 해당하는 Render Target 배열에서 어느쪽에 출력할지를 결정하는 것이다. Draw()호출은 한번만 한다. 단 한번 호출할때 몇개의 인스턴스를 그릴지 파라미터로 넘겨주게 된다. Shader 안에서 SV_InstanceID 시맨틱으로 전달된 Instance ID에 따라 왼쪽/오른쪽 눈에 대응하는 RTV 배열 중 어느 RTV에 출력할지를 결정한다.
그런데 D3D 11.1까지의 스펙으로는 Geometry Shader에서만 RTV Array 의 index를 지정할 수 있다. 따라서 Geometry Shader에서 실제로 기하구조를 변경하지 않더라도 단지 RTV Array의 Index를 지정하기 위해 Geometry Shader를 작성해야한다.
물론 Geometry Shader를 통과하지 않으면 성능이 더 향상된다.

HoloLens의 GPU는 D3D Feature Level 11.3을 준수한다. 11.3 스펙의 기능중 하나는 RTV Array에 대한 index를 Geometry Shader가 아닌 Vertex Shader나 Pixel Shader에서 지정할 수 있다. 따라서 앞서 언급한 Geometry Shader를 작성할 필요가 없다.
하지만 HoloLens 디바이스 외의 , 아마도 현재 대부분의 GPU는 이 스펙을 지원하지 않는다. 그리고 해당 스펙을 지원하지 않는 GPU 위에서 돌아가는 HoloLens 에뮬레이터도 역시 이 스펙을 지원할 수 없다. 내 GTX970은 11.3을 지원할거라 생각했는데 체크해보니 지원하지 않는다.

Optimize를 켰을때 문제가 되는 Geometry Shader는 바로 이 단순한게 RTV Index지정을 위한 그 Shader코드다. 하는 일이 거의 없는 매우 단순한 코드이므로 최적화를 켰을때 오동작 하는 것이 더 이해가 되지 않는다. 아마도 이건 컴파일의 문제가 아니고 HoloLens GPU나 드라이버의 문제일 것이다.

휴…아무튼 내 입장에선 이 문제를 피해갈 수 있으면 그걸로 족하다. 내 버그는 아니니까.
그래서 MS의 가이드라인대로 Vertex Shader에서 RTV Array Index를 지정하는 기능이 있는지를 체크해서 Geometry Shader를 사용할지 말지 결정한다.
이 기능의 지원 여부에 따라 shader코드도 다르게 컴파일하고, 렌더링할때도 Geometry Shader를 사용하지 않도록 엔진 코드를 뜯어고쳤다.

일단 체크 코드는 다음과 같다.

D3D11_FEATURE_DATA_D3D11_OPTIONS3 options;
	m_pD3DDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS3, &options, sizeof(options));
	m_bUseGSForRTVArray = options.VPAndRTArrayIndexFromAnyShaderFeedingRasterizer == 0;

D3D11_FEATURE_D3D11_OPTIONS3 기능을 지원하면 GeometryShader를 사용하지 않는다.
이에 따라 HLSL코드에 Vertex Shader에서 출력할 구조체에 SV_RenderTargetArrayIndex 변수를 넣을지도 결정한다. 이것은 D3DCompile2()함수를 호출할때 전처리기 선언 구문(VS_RTV_ARRAY
)을 전달하는 식으로 처리한다.
HLSL코드는 다음과 같은 식이다.

struct VS_OUTPUT
{
    float4  Position    : SV_Position;
    float4  cpPos       : TEXCOORD0;
    uint    ArrayIndex  : BLENDINDICES;
#ifdef VS_RTV_ARRAY
    uint RTVIndex : SV_RenderTargetArrayIndex;
#endif
};
struct GS_OUTPUT: VS_OUTPUT
{
#ifndef VS_RTV_ARRAY
    uint RTVIndex : SV_RenderTargetArrayIndex;
#endif
};
struct PS_INPUT: VS_OUTPUT
{
};

VS_OUTPUT vsDefault(uint VertexID : SV_VertexID, uint instId : SV_InstanceID)
{
    VS_OUTPUT output;

    uint ArrayIndex = instId % 2;
	output.Position = float4(arrBasePos[VertexID].xy, 0.0, 1.0);
	output.cpPos = arrBasePos[VertexID];
    output.ArrayIndex = ArrayIndex;
#ifdef VS_RTV_ARRAY
    output.RTVIndex = ArrayIndex;
#endif
	return output;
}

[maxvertexcount(3)]
void gsDefault(triangle VS_OUTPUT input[3], inout TriangleStream<GS_OUTPUT> TriStream)
{
    GS_OUTPUT output[3];

	for (uint i=0; i<3; i++)
	{
		output[i].Position = input[i].Position;
		output[i].cpPos = input[i].cpPos;
		output[i].ArrayIndex = input[i].ArrayIndex;
		output[i].RTVIndex = input[i].ArrayIndex;
		TriStream.Append(output[i]);
	}
}

HoloLens의 양쪽 눈에 대한 출력이 필요한 모든 HLSL 코드를 다 뜯어 고쳤다.
GSSetShader()를 호출하는 C++코드도 싹 고쳤다.

이 작업에 반나절이 걸렸다. 그리고 잘 돌아간다.
이제 Release 빌드도 HoloLens디바이스에서 정상적으로 작동한다.
그런데 프레임 레이트는 여전히 낮다.
debug/release 빌드의 문제는 아니다. 이제 성능 저하의 원인을 찾아야겠다.


HoloLens 개발메모 #6”에 대한 답글 2개

  1. 귀중한 자료 감사합니당~ 지금은 win32로 DX11을 공부하고있어 아직은 이른 내용이지만 유익한 내용 보면서 공부에 참고하고있습니당 이를테면 모든 구성설정에서 컴파일이 되어야한다던지 이부분은 내가 개발하고있는 구성설정에서만 컴파일이되면되지라는 생각을 다시하게 된 부분입니당~ 다시한번더 유익한 내용 올려주셔서 감사합니당~

    Liked by 1명

답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

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

Facebook 사진

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

%s에 연결하는 중