Search Unity

로딩 화면을 좋아하는 사람은 없습니다. 그런데 혹시 AUP(Async Upload Pipeline, 비동기식 업로드 파이프라인) 파라미터를 간단히 조정해서 로딩 시간을 획기적으로 개선할 수 있다는 사실, 알고 계셨나요? 이번 글에서는 AUP를 통해서 메시와 텍스처를 로딩하는 방법을 자세히 소개해 볼까 합니다. 이 방법을 이해하게 되면 로딩 시간을 훨씬 단축할 수 있을 것입니다. 이 방법을 적용한 일부 프로젝트에서는 성능이 2배 이상 개선된 효과를 보여주었습니다.

이 글을 통해 AUP가 작동하는 원리를 기술적인 관점에서 살펴보고, 어떤 종류의 API가 최고의 효율을 이끌어낼 수 있는지 알아보겠습니다.

사용해 보기

2018.3 베타 버전에서 가장 최적화된 최신 AUP를 이용하실 수 있습니다.

 

지금 2018.3 베타 다운로드

 

먼저, AUP를 언제 사용하는지, 그리고 로딩 프로세스가 어떻게 작동하는지 자세히 살펴보겠습니다.

AUP는 언제 사용할까요?

2018.3 이전 버전까지 AUP는 텍스처만 처리했습니다. 그러나 2018.3 베타를 시작으로, 이제 텍스처와 메시도 AUP로 로딩됩니다. 그렇지만 일부 예외도 있습니다. 읽기/쓰기 기능이 활성화된 텍스처, 또는 읽기/쓰기가 활성화되어 있거나 압축된 메시는 AUP를 사용하지 않습니다. (참고로 2018.2에서 도입된 텍스처 밉맵 스트리밍(Texture Mipmap Streaming)도 AUP를 사용합니다.)

로딩 프로세스의 작동 방식

빌드 프로세스를 진행하는 동안 텍스처나 메시 오브젝트는 직렬화된 파일에 기록되고 대량의 바이너리 데이터(텍스처 또는 버텍스 데이터)는 함께 제공되는 .resS 파일에 기록됩니다. 이러한 레이아웃은 플레이어 데이터와 에셋 번들 모두에 적용됩니다. 이렇게 오브젝트와 바이너리 데이터를 별도로 관리하면 직렬화된 파일(일반적으로 작은 오브젝트를 포함)을 더욱 신속하게 로딩할 수 있고 이후 .resS 파일의 대량 바이너리 데이터의 로딩을 간소화할 수 있습니다. 텍스처/메시 오브젝트는 역직렬화될 때, 커맨드를 AUP의 커맨드 대기열로 제출합니다. 해당 커맨드 실행이 완료되면, 텍스처/메시 데이터가 GPU에 업로드되어 오브젝트가 메인 스레드에 통합될 수 있습니다.

그림: 빌드를 위해 직렬화되는 시점의 메시 및 텍스처 데이터 레이아웃

업로드 프로세스를 진행하는 동안 .resS 파일의 대량 바이너리 데이터는 크기가 고정된 링 버퍼로 읽혀 들어갑니다. 메모리에 입력된 데이터는 렌더 스레드에서 시간 분할 방식으로 GPU에 업로드됩니다. 이때 링 버퍼의 크기와 시간 분할 기간이 사용자가 변경하여 시스템 동작에 영향을 줄 수 있는 두 가지 파라미터입니다.

AUP는 각 커맨드에 대해 다음의 프로세스를 진행합니다.

  1. 링 버퍼에서 필요한 만큼 메모리를 사용할 수 있을 때까지 대기합니다.
  2. 소스 .resS 파일의 데이터를 할당된 메모리로 읽어 들입니다.
  3. 포스트 프로세싱(텍스처 압축 풀기, 메시 충돌 생성, 플랫폼별 수정 등)을 수행합니다.
  4. 시간 분할 방식으로 렌더 스레드에 업로드합니다.
  5. 링 버퍼 메모리를 해제합니다.

커맨드 여러 개를 동시에 처리할 수도 있습니다. 단 이럴 경우 모든 커맨드는 각자 필요한 메모리를 동일한 공유 링 버퍼에서 할당해야 합니다. 링 버퍼가 채워지고 나면 새로운 커맨드는 대기 상태가 됩니다. 커맨드가 대기 상태에 있다고 해서 메인 스레드가 차단되는 문제가 발생하지는 않으며, 프레임 속도에도 영향을 주지 않습니다. 비동기 로딩 프로세스만 느려집니다.

이러한 영향은 다음과 같이 요약할 수 있습니다.

로드 파이프라인 비교
AUP 미사용 AUP 사용 사용자에 대한 영향
메모리 사용 기본 힙에서 데이터를 읽으면서 할당(메모리의 상위 수위선) 고정 크기 링 버퍼 메모리의 상위 수위선 감소
업로드 프로세스 데이터를 이용할 수 있을 때 업로드 고정된 시간 분할로 분할식 업로딩 히칭(hitch) 없는 업로딩
포스트 프로세싱 로딩 스레드에서 수행(로딩 스레드를 차단) 백그라운드 작업으로 수행 로딩 시간 단축

로딩 파라미터 조정에 이용할 수 있는 공용 API

2018.3에서는 런타임 시점에서 다음 세 가지 파라미터를 조정하여 AUP를 최대로 활용할 수 있습니다.

  • asyncUploadTimeSlice – 각 프레임의 렌더 스레드에서 텍스처와 메시 데이터를 업로드하는 데 소요되는 시간입니다(단위: 밀리 초). 비동기 로드 작업이 진행 중일 때는 시스템이 이 크기로 두 가지 시간 분할을 수행합니다. 기본값은 2ms입니다. 이 값이 너무 작으면 텍스처/메시 GPU 업로드 시 병목 현상이 발생할 수 있습니다. 반대로 이 값이 너무 크면, 프레임 속도에 히칭이 발생할 수 있습니다.
  • asyncUploadBufferSize – 메가바이트 단위의 링 버퍼 크기입니다. 각 프레임에 업로드 시간 분할이 발생할 때 링 버퍼에 전체 시간 분할을 활용할 만큼 충분한 데이터가 있는지 확인해야 합니다. 링 버퍼가 너무 작으면 업로드 시간 분할이 짧아집니다. 기본값은 2018.2의 경우 4MB였으나, 2018.3에서 16MB로 증가했습니다.
  • asyncUploadPersistentBuffer – 2018.3 버전부터 적용된 이 플래그로 모든 대기 중인 읽기 작업이 완료될 때 업로드 링 버퍼가 할당 해제되는지 판단합니다. 이 버퍼를 할당하거나 할당 해제할 때 종종 메모리 단편화 문제가 발생할 수 있으므로 보통 기본값(true)을 유지하는 것이 좋습니다. 로딩 중이 아닐 때 메모리를 꼭 회수해야 한다면 이 값을 false로 설정하면 됩니다.

이러한 설정은 스크립팅 API 또는 QualitySettings 메뉴를 통해서 조정할 수 있습니다.

예시 워크플로

이제 기본값인 2ms의 시간 분할 및 4MB의 링 버퍼를 사용하여 AUP를 통해 여러 텍스처와 메시를 업로드할 때의 워크로드를 살펴보겠습니다. 이 예시에서는 로딩 중이므로 렌더 프레임당 2개의 시간 분할이 수행되며, 따라서 업로드 시간은 4ms가 되어야 합니다. 이 예시에서 프로파일러 데이터를 확인해 보면 약 1.5ms만 사용합니다. 또한 업로드 직후, 링 버퍼의 메모리가 사용 가능한 상태가 되면서 새로운 읽기 작업이 시작되는 것을 알 수 있습니다. 이는 더욱 큰 링 버퍼가 필요하다는 의미입니다.

그럼 링 버퍼를 늘려 보겠습니다. 지금 로딩 화면에 있으므로 업로드 시간 분할을 늘리는 것도 좋습니다. 여기에서 링 버퍼를 16MB로, 시간 분할을 4ms로 조정하면 다음과 같은 결과를 확인할 수 있습니다.

이제 렌더 스레드 시간의 대부분이 업로드에 사용되고 업로드 사이의 짧은 시간만 프레임 렌더링에 사용되는 것을 볼 수 있습니다.

아래는 업로드 시간 분할 및 링 버퍼 크기가 다양하게 설정된 워크로드 샘플의 로딩 시간을 나타낸 것입니다. 테스트는 OS X El Capitan을 실행하는 2.8GHz Intel Core i7 MacBook Pro에서 진행했습니다. 업로드 속도와 입출력 속도는 플랫폼과 기기마다 다를 수 있습니다. 워크로드는 유니티에서 내부 성능 테스트에 사용하는 Viking Village 예시 프로젝트에 포함되어 있습니다. 다른 오브젝트도 로딩이 되기 때문에 다양한 값 가운데 성능의 우위를 정확하게 가리는 것은 불가능합니다. 그러나 이 경우에는 4MB/2ms 설정을 16MB/4ms 설정으로 전환하면 텍스처와 메시 로딩이 두 배 이상 빠르다고 말할 수 있습니다.

이러한 파라미터를 이용한 테스트 결과는 다음과 같습니다.

AUP 설정 변경에 따른 로딩 시간 분석(단위: 밀리초)

따라서 이 예시 프로젝트의 경우 로딩 시간을 최적화하려면 아래와 같은 설정이 필요합니다.

핵심 요점 및 권장 사항

텍스처와 메시의 로딩 속도 최적화를 위해 일반적으로 권장되는 방법은 다음과 같습니다.

  • 프레임 속도를 떨어뜨리지 않는 최대 크기의asyncUploadTimeSlice를 선택합니다.
  • 화면을 로딩하는 동안 일시적으로asyncUploadTimeSlice를 늘립니다.
  • 프로파일러를 사용하여 시간 분할 이용률을 살펴봅니다. 프로파일러에서 시간 분할은AsyncResourceUpload로 나타날 것입니다. 시간 분할이 완전하게 활용되고 있지 않은 경우 QualitySettings.asyncUploadBufferSize를 늘립니다.
  • 일반적으로asyncUploadBufferSize가 커질수록 로딩은 빨라집니다. 따라서 메모리에 여유가 있다면 16MB 또는 32MB로 늘립니다.
  • 로딩하지 않는 동안 불가피하게 런타임 메모리 사용을 줄여야 하는 상황이 아니라면, QualitySettings.asyncUploadPersistentBuffer 설정을 true로 유지합니다.

자주 묻는 질문

질문: 렌더 스레드에서 시간 분할 업로딩은 얼마나 자주 발생하나요?

  • 시간 분할 업로딩은 렌더 프레임당 한 번, 또는 비동기 로드 작업 중 두 번 발생합니다. VSync는 이 파이프라인에 영향을 줍니다. 렌더 스레드가 VSync를 기다리는 동안, 업로딩이 진행될 수 있습니다. 실행 프레임이 16ms이고 하나의 프레임이 예를 들어 17ms로 길어지면, 결론적으로 15ms 동안 VSync를 기다리게 됩니다. 일반적으로 프레임 속도가 높을수록 업로드 시간 분할이 발생하는 빈도가 증가합니다.

질문: AUP를 통해서 로드되는 것은 무엇인가요?

  • 읽기/쓰기가 활성화되지 않은 텍스처가 AUP를 통해서 업로드됩니다.
  • 2에서는 텍스처 밉맵이 AUP를 통해서 스트리밍됩니다.
  • 3에서는 읽기/쓰기가 활성화되지 않은 비압축 메시도 AUP를 통해서 업로드됩니다.

질문: 예를 들어 매우 큰 텍스처가 있다고 할 때, 링 버퍼의 크기가 현재 업로드되고 있는 데이터를 수용하지 못하는 경우에는 어떻게 되나요?

  • 업로드 커맨드가 링 버퍼보다 큰 경우 링 버퍼가 모두 소모될 때까지 대기합니다. 그런 다음 링 버퍼가 할당 크기에 맞추어 재할당됩니다. 업로드가 완료되면 링 버퍼는 재할당을 거쳐 원래 크기로 돌아갑니다.

질문: 동기식 로드 API, 즉 Resources.Load, AssetBundle.LoadAsset 등은 어떻게 작동하나요?

  • 동기식 로드 호출은 AUP를 사용하고, 기본적으로 비동기 업로드 작업이 완료될 때까지 메인 스레드를 차단합니다. 사용되는 로딩 API 유형은 작업에 영향을 미치지 않습니다.

의견 전달하기

유니티는 언제나 여러분의 피드백을 기다립니다. 댓글을 달아주시거나, Unity 2018.3 베타 포럼에 의견을 남겨 주세요!

 

32 코멘트

코멘트 구독

코멘트를 달 수 없습니다.

  1. If you update the application with windows modules installer worker high cpu it can be possible to go with the update of the processor, and with that, you can get the update regarding the template, with the shotgun template.

  2. Thank you for good information.
    I want to apply this but I can’t find asyncUpload info in profiler.
    async mesh upload, resource upload etc I can’t see anything.
    I use LoadSceneAsync in coroutine.
    Is there setting to use async upload?
    I use Unity 2018.3.0b9 and 2017.3.1p4
    Thank you

  3. Thank you for good information.
    I want to apply this but I can’t find asyncUpload info in profiler.
    async mesh upload, resource upload etc I can’t see anything.
    I use LoadSceneAsync in coroutine.
    Is there setting to use async upload?
    I use Unity 2018.3.0b9 and 2017.3.1p4
    Thank you

  4. Still confused with time slice , is there any useful resource to understand time slic in the asynchronous upload progress ?
    for example, why add this time slice feature, how this value affect the asynchronous upload ?what ‘s the relative btween time slice with frame rate?

  5. About the ring buffer… Do I properly understand that having a larger ring buffer would mean that stutters are more likely to occur during the upload time?

    1. Joseph Scheinberg

      11월 5, 2018 8:43 오후

      Hi! Increasing the size of the ring buffer will NOT introduce stuttering. You want a large enough ring buffer so that each render thread timeslice can be fully utilized. If you’re ring buffer is small, you might consume all the pending uploads it contains before the timeslice is complete. The idea is to keep the ring buffer primed with data so the uploading step can use the entire timeslice

  6. Can this be used to Load big Textures from disk without Hickups? It would be wonderfull to Load a Texture in Async mode like mentioned here: https://feedback.unity3d.com/suggestions/async-texture2d-dot-loadimage-and-other-texture-operations?page=2

    Would this be possible?

    1. Joseph Scheinberg

      10월 11, 2018 6:32 오후

      Currently the AUP is only used to load textures that were built through the build process

      1. Does this include AssetBundles, too?

        1. Joseph Scheinberg

          10월 15, 2018 5:44 오후

          Yes it does

  7. Thanks so much!
    Now big problem is Asynchronous loading shaders – when shaders appears on screen, it’s take 100-600ms on render tread on iPhone 6 – it’s totally jork game…

    You can try our project: MadOut2 BigCityOnline

    Need add possibility to load and compile shaders async too!

    1. That is because of Shader Warming. The first time your shader is used it takes time to compile/load. To mitigate this you need to prewarm them manually, durin a startup or loading. You can easily do this through ShaderVariantCollection.WarmUp(), do note not to put all of your shaders in there.
      https://docs.unity3d.com/ScriptReference/ShaderVariantCollection.WarmUp.html

  8. Robert Cummings

    10월 9, 2018 3:46 오후

    Awesome feature. Question to Unity staff: how does this feature affect multi scene async loading, if it does? I would like to load in sections of my level as I move around. Also, what API is there to manage meshes and audio (if any)? Texture mip streaming was a great start but there is not much information about everything else.

    Thanks again!

    1. Joseph Scheinberg

      10월 9, 2018 7:31 오후

      The AUP is used for textures and meshes whether you load a scene async or sync. With the new async uploading of mesh data, you should be able to realize faster mesh loading times in most cases (especially if your meshes have collision) as well as smoother uploading since the uploads are time sliced over multiple frames. So this should be good for your case of loading/unloading in sub-scenes during gameplay.

      Once the Mesh data is loaded through the AUP, it stays loaded until the Mesh object is unloaded. At the moment you can’t stream the mesh data in and out while keeping the Mesh object loaded. So you might want to load your sections asynchronously as individual scenes.

      1. Robert Cummings

        10월 10, 2018 4:39 오후

        Thank you for the informative reply, Joseph! I currently have designed the game around the concept of just streaming in a village or other event when triggered by culling groups API. Due to a major lack of information around the whole subject I suppose I need a guide on best practises for managing streaming in cases like this. Mesh data. Collision data, audio, probes and so on!

        Thanks for your hard work on this.

  9. Hi,

    thank you very much for this post, great information!

    You wrote “the higher the frame rate, the more frequently upload time slices will occur”. Does this mean if I turn off VSync and set the applicationTargetFramerate as high as possible, it affects the loading time in a positive fashion?

    I’m asking, because I did the exact opposite. I reduced framerate to 20fps and turned on VSync during loading screens, thinking it would give Unity more resources to actually load scenes, assets and integrate those faster. I thought I trade faster loading for more hiccups in framerate.

    Thanks for your answer in advance.

    1. Joseph Scheinberg

      10월 9, 2018 3:31 오후

      Hi Peter,

      That’s correct. You can give more time for mesh/texture uploading by increasing the timeslice or increasing the framerate or both. Reducing your framerate to 20fps would likely make your loading times worse as you concluded. As an experiment you might try measuring loading times after turning vsync off and increases both your buffer size and time slice.

      1. Hi Joseph,

        thanks for the answer.

        I gave it a try. Turned vsync off, increased buffer from 4 to 16 and time slice from 2 to 4 and 8. It didn’t affect loading times, to my surprise. The scene I tested had about 250 unique meshes and 500 unique textures, mostly splat- and alpha maps from terrain.

        I tested this with Unity 2017.4.9f1 on Console.

        1. Joseph Scheinberg

          10월 11, 2018 6:12 오후

          Sounds like your loading speeds aren’t bottlenecked on the async upload pipeline. It’s difficult to diagnose the performance problem without more info. Prior to 2018.3 however, the terrain system would make all referenced textures readable, and as a result they can’t use the async upload pipeline. In 2018.3 the terrain code was refactored and it no longer marks textures as readable.

        2. Joseph Scheinberg

          10월 11, 2018 6:30 오후

          Sounds like your loading speeds aren’t bottlenecked on the async upload pipeline. It’s difficult to diagnose the performance problem without more info. Prior to 2018.3 however, the terrain system would make all referenced textures readable, and as a result they can’t use the async upload pipeline. In 2018.3 the terrain code was refactored and it no longer marks textures as readable.

  10. What about triggering the upload of resources? Is this still bound to renderers becoming visible on cameras and does it still require “trick”s like rendering one frame behind a full-screen overlay? Or are there proper APIs for ensuring resources that I know will be needed can be loaded/uploaded completely during a loading screen?

    1. Joseph Scheinberg

      10월 9, 2018 3:11 오후

      When textures and meshes are loaded with any loading API, their data will be uploaded to the GPU through the AUP. This is independent of Renderer components in your scene. mipmap streaming on the other hand is influenced by Renderer objects in the scene, and you do have to perform an update before they will start streaming in. More information on mipmap streaming can be found here: https://docs.google.com/document/d/1P3OUoQ_y6Iu9vKcI5B3Vs2kWhQYSXe02h6YrkDcEpGM/edit#heading=h.d2nucy9ys0gh

      1. Hey Joseph, when you said “When textures and meshes are loaded with any loading API, their data will be uploaded to the GPU through the AUP. This is independent of Renderer components in your scene.” — Is this new behavior to 2018.3? I ask because I am using 2018.2: During a loading screen, I load a GameObject with large textures via an AssetBundle, and spawn it in the scene. There is a noticeable hiccup the first time the user looks at the GameObject. Would I expect this hitch to go away by upgrading to 2018.3?

  11. as console developer who stock with unity 2017 … it pisses me of that all the cool stuff is only unity 2018 without any support for 2017 make regret that i didn’t switch to unreal engine

    1. The updates in UE 4.21 are not in UE 4.20… just saying….
      Happy coding ;)

      1. they offer work arounds unlike unity

        1. Uh… how does it screw over older versions when you can just… download all the previous versions if you want to?

    2. Just update to 2018?

    3. Soooo… if I understand your post… are you saying : “INJUSTICE!!! I picked and am clinging on an older version of your software and it doesn’t do what the newer version does – you suck, It would never happen in other superior engines, I demand support for everything everywhere “. Dude, either get with the newer versions or go to Unreal and deal with their pros and cons there- porting your project to a newer version of Unity will probably be easier on your soul than porting it to Unreal… You are always limited to the functionality offered in your version, maybe sometimes you get support later on, but they are primarily trying to make new stuff and implement it in the newer versions and move forward so you can get new S*!t in the newer versions FASTER and more robust instead of using countless resources focusing on compatibility issues for people using outdated versions. Maybe you wanna work on your new VR project and you will chose Unity 4.2, but you want job system and ECS and VR and basically everything? And I bet If I go find and install several versions of Unreal I will probably find dosens of examples with similar/same problems. I’m not sure why your post triggered me but I find your logic a bit faulty. Or just perhaps Unity might be looking for people that are willing to make packages to offer support of new functionalities for older versions and by what I read you are the right man/woman/apache for the job.

      1. unity is not up to date on console we are way behind you can’t use the latest version

        1. Any source to back this up? First time I hear about it but I haven’t dabbled in consoles with Unity yet.