Insta360–Unity streaming plugin
CrossGaussian에서 현장의 현재 모습을 보여 주려면 Insta360 카메라 영상을 Unity 안으로 실시간으로 가져와야 했다. 카메라가 보내는 것은 Unity에서 바로 쓸 수 있는 텍스처가 아니라 앞·뒤 렌즈의 H.264 packet이다. Camera SDK의 callback에서 시작해 hardware decode, 색 공간 변환, native plugin, Unity render thread까지 이어지는 경로가 필요했다.
이 작업은 두 구현으로 나뉜다. 먼저 Direct3D 12 기반의 독립 버전을 만들었고 이후 CrossGaussian에는 Direct3D 11 native texture를 사용하는 별도 구현을 통합했다. 공개 저장소는 전자다. 두 버전은 그래픽스 API와 frame 전달 방식이 다르므로 하나의 완성된 pipeline처럼 합쳐 설명하지 않는다.
공개 버전의 frame 경로
공개 버전은 Insta360 X3에서 받은 두 H.264 stream을 각각 decode한 뒤 front texture와 back texture로 전달한다. 전체 흐름은 다음과 같다.
flowchart TD camera["Insta360 Camera SDK<br/>front · back H.264 packet"] decode["Native decode thread<br/>FFmpeg h264_cuvid · CUDA frame<br/>NPP NV12 → RGB"] queue["Front · back frame queue"] render["Unity render thread<br/>cudaMemcpyDeviceToHost · GPU → CPU<br/>CPU RGB → RGBA"] output["Direct3D 12 texture<br/>Dual-fisheye skybox"] camera --> decode --> queue --> render --> output
카메라 callback은 두 렌즈의 packet을 stream index와 함께 decoder로 넘긴다. FFmpeg의 h264_cuvid decoder가 CUDA hardware frame을 만들고 NPP가 NV12 데이터를 3채널 RGB 데이터로 변환한다. 변환한 두 frame은 서로 다른 queue에 넣어 camera thread와 render thread가 같은 memory를 동시에 건드리지 않도록 했다.
Native plugin과 Unity render thread 연결
Unity는 main thread와 render thread의 실행 시점이 다르다. C#에서 native 함수를 바로 호출해 graphics resource를 수정하는 대신 GL.IssuePluginEvent()로 render event를 보냈다. Native plugin은 이 event에서 front·back queue를 확인하고 현재 Direct3D 12 texture를 갱신한다.
여기서 CUDA device memory의 RGB 데이터를 cudaMemcpyDeviceToHost로 CPU memory에 복사한다. CPU에서는 3채널 데이터를 Unity texture가 사용할 RGBA 형식으로 바꾸고 BeginModifyTexture로 받은 texture memory에 쓴다. Unity 쪽에서는 두 texture를 dual-fisheye skybox material에 연결해 360° 장면으로 보여 준다.
이 구조로 camera stream callback, GPU decode와 색 변환, Unity의 rendering 순서를 하나의 실행 경로로 묶었다. 반면 decode 결과가 GPU에 있는데도 CPU로 내렸다가 Direct3D 12 texture로 다시 올리는 복사가 남았다. 공개 버전은 CUDA–Direct3D 12 zero-copy 구현이 아니다.
CrossGaussian에 들어간 후속 구현
CrossGaussian에 실제로 통합한 후속 버전은 Direct3D 11 native texture를 중심으로 구성했다. NV12RenderPlugin.cpp에서 NV12 frame을 Unity rendering 경로에 연결했고 공개 Direct3D 12 버전과는 다른 계보다.
Azure DevOps의 branch 중복을 제거한 이력에서 Insta360 저장소의 내 커밋은 전체 77개 중 57개였다. NV12RenderPlugin.cpp의 파일 이력은 6개 모두 내 커밋이다. 이 수치는 Camera SDK callback부터 Unity texture까지의 연동을 주도했다는 근거다. 다만 SDK와 decoder, native plugin framework까지 직접 만들었다는 뜻은 아니다.
공개한 범위와 기반 코드
공개 저장소는 Unity의 Native Rendering Plugin example을 fork해 Windows Direct3D 12용으로 수정한 것이다. RenderAPI_*, Unity graphics interface header와 저수준 plugin 골격에는 원본 예제 코드가 남아 있다.
그 위에 Insta360 stream 수신과 FFmpeg/CUDA decode를 담당하는 Insta360.cpp, frame queue 경계를 정의한 RenderingPluginInterface.h, C# 연동을 담당하는 insta360.cs, dual-fisheye skybox shader를 추가했다. 공개 저장소에는 이 변경이 하나의 합쳐진 커밋으로 올라가 있어 세부 개발 과정을 커밋 수로 보여 주기는 어렵다.
카메라 접근은 Insta360 Camera SDK, decode는 FFmpeg와 NVIDIA CUDA, 색 변환은 NVIDIA NPP에 의존한다. 이 라이브러리들의 기능을 직접 구현했다고 표현하지 않는다.
현재 한계
공개 버전은 Insta360 X3에서만 확인했다. 다른 camera model과 GPU, driver, Unity version의 호환성은 검증하지 않았다. end-to-end latency, dropped frame, CPU·GPU 사용률을 같은 조건에서 반복 측정한 benchmark도 없다. 장시간 실행 시 memory 사용량과 안정성을 기록한 결과 역시 남아 있지 않다.
가장 큰 구조적 한계는 GPU–CPU–GPU round trip이다. 다음 단계는 CUDA와 Direct3D resource를 직접 공유해 복사를 줄이고 camera packet 수신부터 HMD 표시까지 구간별 latency와 frame loss를 측정하는 것이다. 이 작업은 future work이며 현재 구현의 성과로 계산하지 않는다.