앱 개체 및 DirectX
DirectX 게임의 UWP(유니버설 Windows 플랫폼)는 많은 Windows UI 사용자 인터페이스 요소 및 개체를 사용하지 않습니다. 대신 Windows 런타임 스택의 하위 수준에서 실행되기 때문에 보다 근본적인 방식, 즉 앱 개체에 직접 액세스하고 상호 운용하는 방식으로 사용자 인터페이스 프레임워크와 상호 운용해야 합니다. 이 상호 운용이 발생하는 시기와 방법 및 DirectX 개발자가 UWP 앱 개발 시 이 모델을 효과적으로 사용하는 방법을 알아봅니다.
읽는 동안 발생하는 익숙하지 않은 그래픽 용어 또는 개념에 대한 자세한 내용은 Direct3D 그래픽 용어집을 참조하세요.
중요한 핵심 사용자 인터페이스 네임스페이스
먼저 UWP 앱에 포함(사용)해야 하는 Windows 런타임 네임스페이스에 대해 알아보겠습니다. 자세한 내용은 조금 후에 살펴보겠습니다.
- Windows.ApplicationModel.Core
- Windows.ApplicationModel.Activation
- Windows.UI.Core
- Windows.System
- Windows.Foundation
참고 항목
UWP 앱을 개발하는 경우가 아니면 JavaScript 또는 XAML 특정 네임스페이스에 제공된 유형 대신 이러한 라이브러리 및 네임스페이스에 제공된 사용자 인터페이스 구성 요소를 사용하세요.
Windows 런타임 앱 개체
UWP 앱에서 보기를 가져올 수 있고 스왑 체인(디스플레이 버퍼)을 연결할 수 있는 창 및 뷰 공급자를 얻으려고 합니다. 이 보기를 실행 중인 앱의 창 관련 이벤트에 연결할 수도 있습니다. CoreWindow 유형으로 정의된 앱 개체의 부모 창을 가져오려면 IFrameworkViewSource를 구현합니다. IFrameworkViewSource를 구현하는 방법을 보여주는 C++/WinRT 코드 예제는 DirectX 및 Direct2D와의 컴퍼지션 네이티브 상호 운용을 참조하세요.
다음은 핵심 사용자 인터페이스 프레임워크를 사용하여 창을 가져오는 기본 단계입니다.
IFrameworkView를 구현하는 형식을 만듭니다. 이것이 사용자의 보기입니다.
이 형식에서는 다음을 정의합니다.
- CoreApplicationView의 인스턴스를 매개 변수로 사용하는 Initialize 메서드입니다. CoreApplication.CreateNewView를 호출하여 이 형식의 인스턴스를 가져올 수 있습니다. 앱 개체는 앱을 시작할 때 호출합니다.
- CoreWindow 인스턴스를 매개 변수로 사용하는 SetWindow 메서드입니다. 새 CoreApplicationView 인스턴스의 CoreWindow 속성에 액세스하여 이 형식의 인스턴스를 가져올 수 있습니다.
- 진입점에 대한 문자열을 유일한 매개 변수로 사용하는 Load 메서드입니다. 이 메서드를 호출할 때 앱 개체는 진입점 문자열을 제공합니다. 여기서 리소스를 설정합니다. 여기에서 디바이스 리소스를 만듭니다. 앱 개체는 앱을 시작할 때 호출합니다.
- CoreWindow 개체를 활성화하고 창 이벤트 디스패처를 시작하는 Run 메서드입니다. 앱의 프로세스가 시작될 때 앱 개체가 호출합니다.
- Load 호출에 설정된 리소스를 정리하는 Uninitialize 메서드입니다. 앱 개체는 앱을 닫을 때 이 메서드를 호출합니다.
IFrameworkViewSource를 구현하는 형식을 만듭니다. 뷰 공급자입니다.
이 형식에서는 다음을 정의합니다.
- 1단계에서 만든 IFrameworkView 구현의 인스턴스를 반환하는 CreateView라는 메서드입니다.
뷰 공급자의 인스턴스를 Main에서 CoreApplication.Run에 전달합니다.
이러한 기본 사항을 염두에 두고 이 접근 방식을 확장해야 하는 더 많은 옵션을 살펴보겠습니다.
핵심 사용자 인터페이스 형식
유용한 Windows 런타임에서 다른 핵심 사용자 인터페이스 형식은 다음과 같습니다.
- Windows.ApplicationModel.Core.CoreApplicationView
- Windows.UI.Core.CoreWindow
- Windows.UI.Core.CoreDispatcher
이러한 형식을 사용하여 앱의 보기, 특히 앱의 상위 창의 콘텐츠를 그리는 비트에 액세스하고 해당 창에 대해 발생한 이벤트를 처리할 수 있습니다. 앱 창의 프로세스는 격리되고 모든 콜백을 처리하는 ASTA(애플리케이션 단일 스레드 아파트)입니다.
앱 보기는 앱 창에 대한 보기 공급자에 의해 생성되며, 대부분의 경우 특정 프레임워크 패키지 또는 시스템 자체에 의해 구현되므로 직접 구현할 필요가 없습니다. DirectX의 경우 앞에서 설명한 것처럼 씬 뷰 공급자를 구현해야 합니다. 다음 구성 요소와 동작 간에는 특정 1대1 관계가 있습니다.
- CoreApplicationView 형식으로 표현되고 창을 업데이트하기 위한 메서드를 정의하는 앱의 뷰입니다.
- 앱의 스레딩 동작을 정의하는 특성인 ASTA입니다. ASTA에서는 COM STA 특성 형식의 인스턴스를 만들 수 없습니다.
- 앱이 시스템에서 가져오거나 구현하는 뷰 공급자입니다.
- CoreWindow 형식으로 표현되는 상위 창입니다.
- 모든 활성화 이벤트에 대한 소싱입니다. 뷰 및 창에는 모두 별도의 활성화 이벤트가 있습니다.
요약하면 앱 개체는 뷰 공급자 팩터리를 제공합니다. 뷰 공급자를 만들고 앱에 대한 상위 창을 인스턴스화합니다. 뷰 공급자는 앱의 상위 창에 대한 앱 보기를 정의합니다. 이제 뷰 및 상위 창의 세부 정보를 살펴보겠습니다.
CoreApplicationView 동작 및 속성
CoreApplicationView는 현재 앱 보기를 나타냅니다. 앱 싱글톤은 초기화 중에 앱 보기를 만들지만, 활성화될 때까지 뷰가 다시 휴면 상태로 유지됩니다. CoreApplicationView.CoreWindow 속성에 액세스하여 보기를 표시하는 CoreWindow를 가져올 수 있으며, CoreApplicationView.Activated 이벤트에 대리자를 등록하여 보기에 대한 활성화 및 비활성화 이벤트를 처리할 수 있습니다.
CoreWindow 동작 및 속성
CoreWindow 인스턴스인 상위 창은 앱 개체가 초기화될 때 만들어지고 뷰 공급자에게 전달됩니다. 앱에 표시할 창이 있으면 표시합니다. 그렇지 않으면 단순히 뷰를 초기화합니다.
CoreWindow는 입력 및 기본 창 동작에 대한 다수의 이벤트를 제공합니다. 사용자 고유의 대리자를 등록하여 이러한 이벤트를 처리할 수 있습니다.
CoreDispatcher 인스턴스를 제공하는 CoreWindow.Dispatcher 속성에 액세스하여 창에 대한 창 이벤트 디스패처를 가져올 수도 있습니다.
CoreDispatcher 동작 및 속성
CoreDispatcher 형식의 창에 대한 이벤트 디스패치의 스레딩 동작을 확인할 수 있습니다. 이 형식에서 특히 중요한 메서드 중 하나는 창 이벤트 처리를 시작하는 CoreDispatcher.ProcessEvents 메서드입니다. 앱에 대해 잘못된 옵션으로 이 메서드를 호출하면 모든 종류의 예기치 않은 이벤트 처리 동작이 발생할 수 있습니다.
CoreProcessEventsOption 옵션 | 설명 |
---|---|
CoreProcessEventsOption.ProcessOneAndAllPending | 큐에서 현재 사용 가능한 모든 이벤트를 디스패치합니다. 보류 중인 이벤트가 없는 경우 다음 새 이벤트를 기다립니다. |
CoreProcessEventsOption.ProcessOneIfPresent | 큐에 보류 중인 경우 하나의 이벤트를 디스패치합니다. 보류 중인 이벤트가 없는 경우 새 이벤트가 발생할 때까지 기다리지 말고 즉시 반환합니다. |
CoreProcessEventsOption.ProcessUntilQuit | 새 이벤트를 기다린 후 사용 가능한 모든 이벤트를 디스패치합니다. 창이 닫히거나 애플리케이션이 CoreWindow 인스턴스에서 Close 메서드를 호출할 때까지 이 동작을 계속합니다. |
CoreProcessEventsOption.ProcessAllIfPresent | 큐에서 현재 사용 가능한 모든 이벤트를 디스패치합니다. 보류 중인 이벤트가 없으면 즉시 반환합니다. |
DirectX를 사용하는 UWP는 CoreProcessEventsOption.ProcessAllIfPresent 옵션을 사용하여 그래픽 업데이트를 방해할 수 있는 차단 동작을 방지해야 합니다.
DirectX 개발자에 대한 ASTA 고려 사항
UWP 및 DirectX 앱의 런타임 표현을 정의하는 앱 개체는 ASTA(애플리케이션 단일 스레드 아파트)라는 스레딩 모델을 사용하여 앱의 UI 보기를 호스트합니다. UWP 및 DirectX 앱을 개발하는 경우 UWP 및 DirectX 앱에서 디스패치하는 모든 스레드가 Windows::System::Threading API를 사용하거나 CoreWindow::CoreDispatcher를 사용해야 하므로 ASTA의 속성에 익숙합니다. (앱에서 CoreWindow::GetForCurrentThread를 호출하여 ASTA에 대한 CoreWindow 개체를 가져올 수 있습니다.)
UWP DirectX 앱의 개발자로서 알아야 할 가장 중요한 점은 main()에서 Platform::MTAThread를 설정하여 앱 스레드가 MTA 스레드를 디스패치하도록 설정해야 한다는 것입니다.
[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
auto myDXAppSource = ref new MyDXAppSource(); // your view provider factory
CoreApplication::Run(myDXAppSource);
return 0;
}
UWP DirectX 앱의 앱 개체가 활성화되면 UI 보기에 사용할 ASTA가 만들어집니다. 새 ASTA 스레드는 뷰 공급자 팩터리를 호출하여 앱 개체에 대한 뷰 공급자를 만들고, 결과적으로 뷰 공급자 코드가 해당 ASTA 스레드에서 실행됩니다.
또한 ASTA에서 스핀오프하는 모든 스레드는 MTA에 있어야 합니다. 스핀오프하는 모든 MTA 스레드는 여전히 재진입 문제를 발생시키고 교착 상태를 초래할 수 있습니다.
ASTA 스레드에서 실행되도록 기존 코드를 포팅하는 경우 다음 사항을 염두에 두어야 합니다.
대기 기본 형식(예: CoWaitForMultipleObjects)은 STA와 ASTA에서 다르게 동작합니다.
COM 호출 모달 루프는 ASTA에서 다르게 작동합니다. 발신 통화가 진행 중인 동안에는 더 이상 관련 없는 전화를 받을 수 없습니다. 예를 들어 다음 동작은 ASTA에서 교착 상태를 만들고 앱이 즉시 충돌합니다.
- ASTA는 MTA 개체를 호출하고 인터페이스 포인터 P1을 전달합니다.
- 나중에 ASTA는 동일한 MTA 개체를 호출합니다. MTA 개체는 ASTA로 반환되기 전에 P1을 호출합니다.
- P1은 관련 없는 호출을 차단하므로 ASTA를 입력할 수 없습니다. 그러나 MTA 스레드는 P1을 호출하려고 할 때 차단됩니다.
다음과 같은 방법으로 해결할 수 있습니다.
- 병렬 패턴 라이브러리(PPLTasks.h)에 정의된 비동기 패턴 사용
- 임의 호출을 허용하기 위해 가능한 한 빨리 앱의 ASTA(앱의 주 스레드)에서 CoreDispatcher::ProcessEvents를 호출합니다.
즉, 앱의 ASTA에 관련 없는 호출을 즉시 전달할 수는 없습니다. 비동기 호출에 대한 자세한 내용은 C++의 비동기 프로그래밍을 참조하세요.
전반적으로 UWP 앱을 디자인할 때 MTA 스레드를 직접 만들고 관리하지 않고 앱의 CoreWindow 및 CoreDispatcher::P rocessEvents에 CoreDispatcher를 사용하여 모든 UI 스레드를 처리합니다. CoreDispatcher로 처리할 수 없는 별도의 스레드가 필요한 경우 비동기 패턴을 사용하고 앞서 언급한 지침을 따라 재진입 문제를 방지합니다.