스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
몰입형 앱을 위한 Metal 알아보기
Metal을 활용해 visionOS에서 완전 몰입형 경험을 어떻게 렌더링하는지 알아보세요. 이 플랫폼에서 렌더링 세션을 설정하고 기본적인 렌더 루프를 만드는 방법을 소개한 뒤 공간 입력을 통합해 상호 작용이 가능한 경험을 만드는 방법을 공유합니다.
챕터
- 0:06 - Intro
- 1:50 - App Architecture
- 5:58 - Render configuration
- 11:29 - Render Loop
- 13:00 - Render a frame
- 17:28 - User input
- 20:22 - Wrap-Up
리소스
관련 비디오
WWDC23
WWDC21
-
다운로드
♪ 감미로운 인스트루멘탈 힙합 ♪ ♪ Apple 소프트웨어 엔지니어 포 새스트르 미겔입니다 오늘은 xrOS에 Metal로 몰입형 경험을 만드는 방법을 이야기하려 합니다 올해 xrOS가 공개되면서 익숙한 기술로 Apple 생태계 내에서 몰입형 경험을 만들 수 있게 되었죠 RealityKit를 사용하면 여러분의 가상 콘텐츠를 현실에 녹인 경험을 만들 수 있습니다 한편으로 사용자가 응용 프로그램을 통해 완전 몰입형 경험을 할 수 있다면 xrOS에서는 여러분만의 가상 콘텐츠로 현실 콘텐츠를 완전히 대체하는 것도 가능합니다 완전 몰입형 경험을 만들 때는 몇 가지 렌더링 메서드를 선택할 수 있는데요 RealityKit를 계속 사용해도 되고 선호에 따라 Metal과 ARKit API를 택해도 좋습니다 Rec Room은 완전 몰입형 경험을 제공하는 응용 프로그램의 훌륭한 사례인데요 CompositorServices로 렌더링 세션을 만들고 Metal API로 프레임을 렌더링하며 ARKit로 월드와 손을 추적합니다 이 모든 기술 지원이 가능했던 건 Unity 에디터 덕분이었죠 엔진을 직접 작성하고 싶다면 CompositorServices API를 사용해서 xrOS 내 Metal 렌더링에 접근할 수 있습니다 ARKit와 결합해서 월드 추적과 손 추적을 더해 완전 몰입형 경험을 만들 수 있죠 여러분의 엔진이 xrOS에서 작동하게 설정하려면 CompositorServices가 핵심입니다 렌더 루프 설정법을 보여 드리고 프레임을 렌더링하는 방법도 소개하겠습니다 마지막으로는 ARKit를 사용해서 상호 작용이 가능한 경험을 만드는 방법도 알려 드리죠 xrOS 앱의 아키텍처부터 시작해 봅시다 오늘 세션을 최대로 활용하려면 먼저 Metal API와 Metal 렌더링 기술을 사용해 본 경험이 있어야 합니다 Metal을 사용한 적이 없다면 샘플 코드와 문서를 developer.apple.com/Metal에서 확인해 주세요 xrOS에서 Metal로 몰입형 경험을 만들 때는 우선 SwiftUI로 응용 프로그램과 렌더링 세션을 만들게 됩니다 렌더링 세션을 만든 뒤에는 익숙한 언어로 변경할 수 있습니다 C 혹은 C++로 엔진 내부 요소를 정의하는 거죠 우선 SwiftUI 앱 프로토콜을 준수하는 타입을 만듭니다 이 프로토콜을 준수하려면 앱의 scene 목록을 정의해야 하죠 xrOS의 scene은 크게 세 유형입니다 윈도우 유형 scene에서 제공하는 경험은 macOS 같은 2D 플랫폼과 유사합니다 볼륨 유형에서는 바운드 내에서 콘텐츠가 렌더링되며 Shared Space 내에서 다른 응용 프로그램과 공존합니다 ImmersiveSpace 유형이면 어디서든 콘텐츠 렌더링이 가능합니다 완전 몰입형 경험을 Metal로 렌더링할 때는 항상 ImmersiveSpace 유형을 선택하게 됩니다 ImmersiveSpace는 새로운 SwiftUI scene 유형으로 xrOS에서 사용할 수 있고 완전 몰입형 경험의 컨테이너로 기능합니다 ImmersiveSpace의 사용법을 알아보려면 'SwiftUI로 윈도우를 넘어서기' 세션을 참고하세요 ImmersiveSpace scene을 만든다면 응용 프로그램이 콘텐츠를 제공할 때 ImmersiveSpaceContent 프로토콜을 준수하는 타입을 사용하게 됩니다 ImmersiveSpace scene을 위한 콘텐츠를 만들 때는 응용 프로그램에 RealityKit가 자주 쓰입니다 내부에서 CoreAnimation과 MaterialX를 사용하죠 하지만 응용 프로그램 콘텐츠 렌더링에 Metal의 기능을 대신 사용하겠다면 다른 선택지가 생깁니다 CompositorServices API는 Metal과 ARKit로 몰입형 렌더링 기능을 응용 프로그램에 제공합니다 xrOS에 도입된 새로운 CompositorServices API는 Metal 렌더링 인터페이스를 제공해서 ImmersiveSpace 콘텐츠를 렌더링할 수 있습니다 CompositorServices를 쓰면 응용 프로그램이 Compositor 서버에 직접 렌더링할 수 있습니다 IPC 오버헤드가 적어 지연이 최소화되며 처음부터 빌드되어서 C 언어와 Swift API를 모두 지원하죠 CompositorServices를 사용할 때는 ImmersiveSpaceContent를 CompositorLayer라고 부릅니다 CompositorLayer를 만들려면 두 가지 매개변수를 넣어야 하죠 첫 번째 매개변수는 CompositorLayerConfiguration 프로토콜입니다 렌더링 세션의 동작과 기능을 정의하는 프로토콜이죠 두 번째는 LayerRenderer입니다 레이러 렌더링 세션의 인터페이스죠 응용 프로그램은 이 객체를 사용해서 새로운 프레임 렌더링을 예약하고 실행합니다 몰입형 경험을 Metal로 작성한다면 먼저 앱 유형을 정의하세요 scene 유형으로는 ImmersiveSpace를 사용하고 콘텐츠 유형으로는 CompositorLayer를 씁니다 CompositorLayer가 콘텐츠 렌더링 준비를 마치면 렌더링 세션의 인스턴스로 시스템이 응용 프로그램을 호출할 겁니다 커스텀 엔진의 인스턴스를 만들기에 좋은 위치죠 엔진 인스턴스가 생겼으니 start를 호출해 렌더 스레드를 만들고 렌더 루프를 실행할 수 있습니다 고려할 사항이 한 가지 있는데요 응용 프로그램에서 scene 목록을 정의할 때는 기본 SwiftUI가 윈도우 scene을 만든다는 걸 유념하세요 앱의 첫 scene이 ImmersiveSpace라도 그렇습니다 기본 동작을 변경하려면 앱의 information property list를 수정하면 됩니다 추가할 키는 UIApplicationPreferred DefaultSceneSessionRole입니다 application scene manifest에 이 키를 넣으면 응용 프로그램의 기본 scene 타입이 변경되죠 Compositor SpaceContent로 공간을 사용한다면 사용할 키는 CPSceneSessionRole ImmersiveSpaceApplication입니다 응용 프로그램 설정을 마치고 렌더 루프에 들어가기 전에는 CompositorServices에 LayerRenderer 설정을 전달합니다 CompositorLayer에 구성을 제공하려면 CompositorLayerConfiguration 프로토콜을 준수한 새 타입을 만들어야 합니다 이 프로토콜로 설정을 수정하고 렌더링 세션의 일부 동작도 수정할 수 있죠 CompositorLayerConfiguration은 두 가지 매개변수를 제공합니다 첫 번째는 레이어 기능으로 기기에서 사용 가능한 기능을 쿼리할 수 있습니다 유효한 구성을 만들 때 이 기능을 사용하세요 두 번째 매개변수는 LayerRenderer Configuration으로 렌더링 세션의 구성을 정의하는 타입입니다 이 구성으로는 엔진이 레이어에 콘텐츠를 매핑하는 방식을 정의하고 포비티드 렌더링을 활성화하며 파이프라인의 색상 관리를 정의할 수 있습니다 이제 각각의 프로퍼티가 여러분의 엔진에 어떻게 영향을 미치는지 이야기해 보죠 먼저 포비티드 렌더링을 봅시다 이 기능의 주된 목적은 콘텐츠를 각도당 픽셀 수를 더 높여 렌더링하면서 텍스처 크기는 키우지 않는 겁니다 일반적인 디스플레이 파이프라인에서는 픽셀이 텍스처에 선형으로 분포합니다 xrOS에서는 샘플률을 낮게 쓸 수 있는 디스플레이 구역을 맵으로 정의해서 워크플로를 최적화합니다 프레임 렌더링에 요구되는 성능을 낮추면서도 디스플레이의 시각적 충실도는 유지되죠 가능하면 포비에이션을 사용하는 게 중요합니다 그러면 더 나은 시각 경험이 창출되거든요 포비에이션이 렌더링 파이프라인에 미치는 영향을 시각화할 때는 Xcode의 Metal Debugger를 활용하면 좋습니다 Metal Debugger로는 타깃 텍스처와 렌더링 파이프라인에 사용된 래스터화 레이트 맵을 검사할 수 있습니다 이 캡처에는 텍스처의 콘텐츠가 나오지만 래스터화 레이트 맵을 위한 확대나 축소는 없습니다 더 많이 압축된 텍스처 구역에 주목하면 샘플률이 다른 게 눈에 띌 텐데요 Metal Debugger의 attachment viewer 옵션으로 디스플레이에 보일 최종 결과 시각화를 위해 이미지를 확대 및 축소할 수 있습니다 Compositor가 제공하는 포비에이션 맵은 MTLRasterizationRateMap을 각 프레임에 사용합니다 포비에이션 지원 여부는 항상 확인하면 좋습니다 플랫폼에 따라 달라지니까요 가령 xrOS 시뮬레이터에서는 포비에이션을 사용할 수 없습니다 포비에이션을 활성화하려면 isFoveationEnabled를 구성에 넣으세요 두 번째 프로퍼티는 LayerRenderer 레이아웃입니다 이 프로퍼티는 여러분의 엔진에서 특히 중요한 구성입니다 헤드셋의 디스플레이가 렌더링된 응용 프로그램 콘텐츠로 매핑되는 방식을 정의하죠 각 눈은 우선 Compositor가 제공하는 Metal 텍스처로 매핑됩니다 그러면 Compositor가 해당 텍스처 내에 사용될 슬라이스의 인덱스를 제공하죠 끝으로 Compositor가 해당 텍스처 슬라이스에서 쓸 뷰포트를 제공합니다 LayerRenderer 레이아웃으로는 텍스처 슬라이스와 뷰포트 사이에 다양한 매핑을 선택할 수 있습니다 layered로는 Compositor가 한 가지 텍스처를 두 슬라이스, 두 뷰포트와 사용합니다 dedicated라면 Compositor는 두 가지 텍스처를 각각 하나의 슬라이스와 뷰포트랑 사용하고요 마지막으로 shared에서는 Compositor가 한 가지 텍스처를 하나의 슬라이스와 두 가지 뷰포트랑 사용합니다 사용할 레이아웃 선택은 렌더링 파이프라인 설정에 따라 달라집니다 가령 layered와 shared에서는 단일 패스로 렌더링할 수 있어서 렌더링 파이프라인 최적화가 가능합니다 shared 레이아웃에서는 기존 코드 베이스를 포트하는 게 더 수월할 수 있죠 포비티드 렌더링 옵션이 없으니까요 layered 레이아웃은 최적의 레이아웃입니다 scene을 단일 패스로 렌더링할 수 있으면서도 포비티드 렌더링을 유지할 수 있죠 마지막으로 볼 구성 프로퍼티는 색상 관리입니다 Compositor에서는 콘텐츠 렌더링이 extended linear display P3 색 공간에서 이뤄져야 합니다 xrOS는 2.0 EDR 헤드룸을 지원하는데 이는 SDR의 두 배입니다 Compositor는 기본적으로 HDR 렌더링이 가능한 픽셀 형식을 사용하지 않지만 여러분의 응용 프로그램이 HDR를 지원하면 레이어 구성에서 rgba16Float를 지정할 수 있습니다 EDR로 HDR를 렌더링하는 방법을 알아보려면 'EDR로 하는 HDR 렌더링 살펴보기' 세션을 참고하세요 응용 프로그램에 커스텀 구성을 만들려면 CompositorLayerConfiguration 프로토콜을 준수하는 새 타입을 정의하는 게 우선입니다 이 프로토콜을 준수하려면 makeConfiguration 메서드를 추가하세요 이 메서드로는 여러분이 수정할 수 있는 레이어 기능과 구성이 제공됩니다 앞서 언급한 세 프로퍼티를 활성화하려면 먼저 포비에이션이 지원되는지 확인하고 이 기기에서 지원하는 레이아웃을 확인하세요 이 정보가 있으면 유효한 레이아웃을 구성에 설정할 수 있죠 시뮬레이터 같은 일부 기기에서 그렇듯 Compositor가 하나의 뷰만 렌더링하면 레이어를 사용할 수 없습니다 기기가 포비에이션을 지원하면 그걸 true로 설정하세요 끝으로 colorFormat은 rgba16Float로 설정해야 HDR 콘텐츠를 렌더링할 수 있습니다 Compositor 레이어를 만드는 코드로 돌아가면 방금 만든 구성 타입을 추가할 수 있게 됩니다 이제 렌더링 세션이 구성되었으니 렌더 루프를 설정할 수 있는데요 먼저 CompositorLayer의 LayerRenderer 객체를 사용해 봅시다 우선 리소스를 로딩하고 객체를 초기화합니다 엔진이 프레임을 렌더링하는 데 필요한 객체죠 다음으로는 레이어 상태를 확인합니다 레이어가 정지했다면 실행될 때까지 기다리세요 레이어의 대기가 풀리면 레이어 상태를 다시 확인합니다 레이어가 실행되고 있으면 프레임을 렌더링할 수 있습니다 해당 프레임의 렌더링이 끝나면 레이어 상태를 다시 확인하고 다음 프레임을 렌더링하세요 레이어 상태가 무효화되어 있다면 렌더 루프용으로 만든 리소스를 해제합니다 이제 render_loop의 주요 함수를 정의해 봅시다 지금까지 Swift를 사용한 건 ImmersiveSpace API를 Swift에서만 쓸 수 있었기 때문인데요 지금부터는 C 언어로 바꿔 렌더 루프를 작성하겠습니다 말씀드렸듯 렌더 루프의 첫 단계는 프레임을 렌더링하는 데 필요한 객체를 할당하고 초기화하는 겁니다 커스텀 엔진에서 setup 함수를 호출하면 되죠 다음은 루프의 메인 섹션입니다 layerRenderer 상태 확인이 먼저인데요 정지 상태라면 layerRenderer가 실행될 때까지 스레드는 일시적으로 중지됩니다 레이어 상태가 실행 중이면 엔진이 프레임을 렌더링하고요 마지막으로 레이어가 무효화되어 있다면 렌더 루프가 종료될 겁니다 render_loop 함수의 마지막 단계는 사용한 리소스를 정리하는 겁니다 앱이 렌더 루프를 거치고 있으니 프레임 렌더링 방식을 설명하겠습니다 xrOS 내 콘텐츠 렌더링은 언제나 기기의 시점에서 이뤄집니다 ARKit를 사용해 기기 방향과 이동 정보를 얻을 수 있죠 ARKit는 iOS에서 이미 사용 가능하며 이제 xrOS는 완전히 새로운 API를 도입합니다 여기에는 몰입형 경험 제작에 유용한 추가 기능이 있습니다 ARKit에서는 월드 추적과 손 추적은 물론 다른 월드 감지 기능까지 응용 프로그램에 추가할 수 있습니다 새로운 ARKit API도 처음부터 빌드되어서 C 언어와 Swift API를 지원하니 기존 렌더링 엔진과 통합하기가 한층 간단해집니다 ARKit나 xrOS에 관해 더 알아보려면 '공간 컴퓨팅을 위한 ARKit 알아보기'를 확인하세요 이제 렌더 루프에서 한 프레임을 렌더링할 차례입니다 프레임을 렌더링할 때는 Compositor가 두 주요 섹션을 정의합니다 하나는 업데이트로 여기서 수행하는 작업은 입력 지연이 중요하지 않은 작업입니다 scene의 애니메이션을 업데이트하거나 캐릭터를 업데이트하고 시스템 내에서 손 스켈레톤 자세 같은 입력을 수집하는 작업이죠 프레임의 두 번째 섹션은 제출 섹션입니다 지연 시간이 중요한 작업은 이때 수행하게 되죠 헤드셋 자세에 종속된 콘텐츠도 여기서 렌더링합니다 각 섹션의 타이밍을 정의하고자 Compositor는 타이밍 객체를 제공합니다 이 구성도는 각기 다른 프레임 섹션에 타이밍이 미치는 영향을 나타냅니다 CPU와 GPU 트랙은 응용 프로그램에서 실행된 작업을 보여 주며 Compositor 트랙은 프레임 디스플레이를 위해 Compositor 서버가 수행한 작업을 보여 줍니다 CompositorServices의 타이밍 타입은 세 가지 주요 시간값을 정의합니다 첫 번째는 최적 입력 시간입니다 지연 시간이 중요한 입력을 쿼리하고 프레임 렌더링을 시작할 최적의 시간이죠 두 번째는 렌더링 데드라인입니다 이 시간은 CPU와 GPU가 동작해서 프레임 렌더링을 마쳐야 할 시간을 말하죠 세 번째는 프레젠테이션 시간으로 프레임이 디스플레이에 나타나는 시간을 말합니다 프레임의 두 섹션에서 업데이트 섹션의 발생은 최적 입력 시간보다 앞서야 합니다 업데이트 이후에는 최적 입력 시간까지 대기하다가 프레임 제출을 시작하게 되죠 프레임 제출을 수행하면 렌더링 작업이 GPU에 제출됩니다 CPU와 GPU의 작업이 렌더링 데드라인 전에 끝나야 한다는 점을 주목하세요 아니면 Compositor 서버가 이 프레임을 사용하지 못하고 예전 프레임을 대신 사용하게 됩니다 렌더링 데드라인에서는 Compositor 서버가 시스템 내 다른 레이어와 이 프레임을 합칩니다 렌더 루프 코드로 돌아와서 render_new_frame 함수를 정의할 차례인데요 엔진의 render_new_frame 함수에서는 layerRenderer의 프레임을 먼저 쿼리합니다 프레임 객체로 타이밍 정보를 판정할 수 있는데 이 타이밍 정보로 update 범위를 설정하고 interval을 제출하세요 다음은 업데이트 섹션 구현입니다 프레임에서 start를 호출하고 update를 종료해 섹션을 정의하세요 내부에서는 기기 입력값을 수집하고 프레임 콘텐츠를 업데이트합니다 업데이트를 완료하면 제출을 시작하기 전에 최적 입력 시간까지 대기합니다 대기가 끝나면 제출 섹션을 정의하도록 start를 호출하고 submission을 종료합니다 이 섹션에서는 먼저 drawable 객체를 쿼리하세요 CAMetalLayer와 유사하게 drawable 객체에는 렌더링 파이프라인을 설정하는 데 필요한 타깃 텍스처와 정보가 들어 있습니다 drawable 객체가 있으면 최종 타이밍 정보를 얻을 수 있죠 Compositor는 이 정보로 프레임을 렌더링합니다 최종 타이밍이 있으면 ar_pose를 쿼리할 수 있는데 drawable에서는 pose 설정이 중요합니다 Compositor는 이걸 사용해서 프레임에 재투영을 수행합니다 여기서 저는 pose를 얻고자 엔진 객체에서 get_ar_pose 함수를 호출하지만 여러분이 이 함수 콘텐츠를 구현하려면 ARKit의 월드 추적 API를 사용하셔야 합니다 함수의 마지막 단계는 모든 GPU 작업을 인코딩하고 프레임을 제출하는 겁니다 submit_frame 안에서는 drawable을 사용해서 평소처럼 프레임의 콘텐츠를 렌더링하세요 이제 렌더 루프가 프레임을 렌더링하고 있으니 몰입형 경험에서 상호 작용을 가능하게 할 차례입니다 이 영상을 보시면 Unity를 사용한 Rec Room이 ARKit와 Compositor API를 활용해 응용 프로그램에 상호 작용성을 어떻게 더하는지 알 수 있습니다 이 상호 작용을 주도하는 주요 입력 소스는 두 가지입니다 ARKit의 HandTracking으로는 가상 손을 렌더링할 손 스켈레톤이 제공됩니다 LayerRenderer의 핀치 이벤트는 사용자 상호 작용을 주도하죠 경험에서 상호 작용을 가능하게 하려면 먼저 사용자 입력값을 수집해서 scene 콘텐츠에 적용해야 합니다 프레임의 업데이트 섹션에서 모든 작업이 발생합니다 주요 입력 소스는 두 가지입니다 LayerRenderer와 ARKit의 HandTracking 제공자죠 LayerRenderer로는 응용 프로그램이 핀치 이벤트를 수신할 때마다 업데이트가 이뤄집니다 이 업데이트는 공간 이벤트 형태로 표시되죠 이벤트에는 세 가지 주요 프로퍼티가 있습니다 phase로는 이벤트의 활성화 여부를 알 수 있습니다 완료되었는지 혹은 취소되었는지를요 selection ray로는 이벤트가 시작될 때 주목된 scene 콘텐츠를 판단할 수 있습니다 마지막 이벤트 프로퍼티는 manipulator pose입니다 이 프로퍼티는 핀치의 pose로 이벤트가 지속되는 동안 매 프레임에 업데이트됩니다 HandTracking API로는 스켈레톤을 얻을 수 있습니다 왼손과 오른손 모두요 이제 코드에 입력 지원을 추가해 보죠 입력값을 수집하기에 앞서 여러분의 응용 프로그램이 가상 손을 렌더링할지 passthrough 손을 사용할지를 결정해야 하는데요 upperLimbVisibility scene 수정자를 ImmersiveSpace에 추가하면 passthrough 손을 나타내거나 숨길 수 있습니다 공간 이벤트에 접근하려 할 때 돌아갈 지점은 CompositorLayer 렌더링 핸들러를 정의한 곳입니다 여기서 layerRenderer에 블록을 등록하면 새로운 공간 이벤트가 있을 때마다 업데이트를 받습니다 엔진 코드를 C 언어로 작성하면 SwiftUI 공간 이벤트를 C 타입에 매핑하게 됩니다 C 언어 코드 내에서는 C 이벤트 컬렉션을 받을 수 있습니다 공간 이벤트 업데이트 처리 시에는 한 가지 사항을 기억해 주세요 업데이트는 메인 스레드에서 전달됩니다 엔진 내에서 이벤트를 읽고 작성할 때 일부 동기화 메커니즘을 사용하게 된다는 의미죠 이벤트가 엔진에 저장되었으니 gather input 함수를 구현할 차례입니다 먼저 이 프레임에서 현재 입력 상태를 저장할 객체를 생성해야 합니다 이 입력 상태에는 LayerRenderer에서 수신한 이벤트가 저장되죠 내장 스토리지 접근이 안전한지 꼭 확인하세요 손 스켈레톤과 관련해서는 ARKit의 hand tracking provider API로 최신 손 앵커를 얻을 수 있습니다 응용 프로그램에 입력 지원이 생겼으니 도구는 모두 갖춰졌습니다 xrOS에 완전 몰입형 경험을 만들 도구죠 요약하자면 SwiftUI로는 응용 프로그램을 정의하며 CompositorServices와 Metal로는 렌더 루프를 설정하고 3D 콘텐츠를 디스플레이합니다 마지막으로 ARKit를 사용해서 상호 작용이 가능한 경험을 만들 수 있습니다 시청해 주셔서 감사합니다 ♪
-
-
4:45 - App architecture
@main struct MyApp: App { var body: some Scene { ImmersiveSpace { CompositorLayer { layerRenderer in let engine = my_engine_create(layerRenderer) let renderThread = Thread { my_engine_render_loop(engine) } renderThread.name = "Render Thread" renderThread.start() } } } }
-
10:32 - CompositorLayer Configuration
// CompositorLayer configuration struct MyConfiguration: CompositorLayerConfiguration { func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { let supportsFoveation = capabilities.supportsFoveation let supportedLayouts = capabilities.supportedLayouts(options: supportsFoveation ? [.foveationEnabled] : []) configuration.layout = supportedLayouts.contains(.layered) ? .layered : .dedicated configuration.isFoveationEnabled = supportsFoveation // HDR support configuration.colorFormat = .rgba16Float } }
-
12:20 - Render loop
void my_engine_render_loop(my_engine *engine) { my_engine_setup_render_pipeline(engine); bool is_rendering = true; while (is_rendering) @autoreleasepool { switch (cp_layer_renderer_get_state(engine->layer_renderer)) { case cp_layer_renderer_state_paused: cp_layer_renderer_wait_until_running(engine->layer_renderer); break; case cp_layer_renderer_state_running: my_engine_render_new_frame(engine); break; case cp_layer_renderer_state_invalidated: is_rendering = false; break; } } my_engine_invalidate(engine); }
-
15:56 - Render new frame
void my_engine_render_new_frame(my_engine *engine) { cp_frame_t frame = cp_layer_renderer_query_next_frame(engine->layer_renderer); if (frame == nullptr) { return; } cp_frame_timing_t timing = cp_frame_predict_timing(frame); if (timing == nullptr) { return; } cp_frame_start_update(frame); my_input_state input_state = my_engine_gather_inputs(engine, timing); my_engine_update_frame(engine, timing, input_state); cp_frame_end_update(frame); // Wait until the optimal time for querying the input cp_time_wait_until(cp_frame_timing_get_optimal_input_time(timing)); cp_frame_start_submission(frame); cp_drawable_t drawable = cp_frame_query_drawable(frame); if (drawable == nullptr) { return; } cp_frame_timing_t final_timing = cp_drawable_get_frame_timing(drawable); ar_pose_t pose = my_engine_get_ar_pose(engine, final_timing); cp_drawable_set_ar_pose(drawable, pose); my_engine_draw_and_submit_frame(engine, frame, drawable); cp_frame_end_submission(frame); }
-
18:57 - App architecture + input support
@main struct MyApp: App { var body: some Scene { ImmersiveSpace { CompositorLayer(configuration: MyConfiguration()) { layerRenderer in let engine = my_engine_create(layerRenderer) let renderThread = Thread { my_engine_render_loop(engine) } renderThread.name = "Render Thread" renderThread.start() layerRenderer.onSpatialEvent = { eventCollection in var events = eventCollection.map { my_spatial_event($0) } my_engine_push_spatial_events(engine, &events, events.count) } } } .upperLimbVisibility(.hidden) } }
-
18:57 - Push spatial events
void my_engine_push_spatial_events(my_engine *engine, my_spatial_event *spatial_event_collection, size_t event_count) { os_unfair_lock_lock(&engine->input_event_lock); // Copy events into an internal queue os_unfair_lock_unlock(&engine->input_event_lock); }
-
19:57 - Gather inputs
my_input_state my_engine_gather_inputs(my_engine *engine, cp_frame_timing_t timing) { my_input_state input_state = my_input_state_create(); os_unfair_lock_lock(&engine->input_event_lock); input_state.current_pinch_collection = my_engine_pop_spatial_events(engine); os_unfair_lock_unlock(&engine->input_event_lock); ar_hand_tracking_provider_get_latest_anchors(engine->hand_tracking_provider, input_state.left_hand, input_state.right_hand); return input_state; }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.