cancel
Showing results for 
Search instead for 
Did you mean: 

How did I port PC|Rift version of my game to mobile VR using UE4. tips and opt tricks to hit 60fps.

Mohammed_hashim
Expert Protege
Hello guys, below I wrote my journey on how did I port my PC Rift/Vive version of "StardustVR" to mobile, it wasn't an easy job but hopefully below tips will be useful for your guys too. UE4 never meant for mobile, its really heavy since most of the feature enabled by default so you will have to disable them, I run an empty level on my Galaxy S7 and I was getting 30 fps, this made me think on what kind of sorcery devs used to make their UE4 game run 60 fps on Gearvr/Go/Quest.

I didn't know it will be this long  ::smile:  , maybe that's why I'm getting stuck on my games, I thought it will be 5 lines of tips. so grab a coffee before start reading. it might be helpful for you guys so you don't fall into the same mistake I did. since most of the fixes i found after trial and error for hours or even days.

Porting your game into mobile is a sword with 2 edges. It's really cool to see your game runs on your pocket mobile that needed Core i7 CPU with GTX 1080Ti before. but at the same time if you are so attached to your game it will break your heart (literally) which is the main point that might stop you if you ask me, since you spent months on one of the feature/particle effect/ material but when you port it into mobile you will have to cut it down, if you do it you will able to port your game with 60 fps, if you don't then forget about finding another way to hit 60 fps without a sacrifice.

Synopsis to reach 60 fps:

-Enable forward render (don't ask).

-Reduce drawcall to below 100 and poly below 100K,

-Reduce all of your texture size to 128 ~ 512 except skysphere.

-Make your game unlit. I don't know if its possible to use phyiscal material with UE4 and hit 60 fps.

-Make all of your particles effect's material unlit, lower the texture resolution of it, lower its components and complexity and lower its lifetime.

-Change audio SFX from the original sample rate 44100hz to 22050hz for all of your sounds.

-Disable HDR/postprocess.

-Lower number of components on your character.

-Simple material.

-Use ASTC which works on all GearVR and its fastest with low size.

- No AA

-Lower everything 🙂

-----

Here is the detail:

1-You have to sacrifice and let your ego to hit that 60 fps. its the hardest part, believe me, I had to disable a lot of things and mainly I have to disable bloom effect (HDR) in my game and my game is only about the environment and the beauty of the graphic, it doesn't have much of gameplay, so disabling my blood (HDR) increased my fps from 40 to 60 but the game looked really dull and no more light shining and glowing effect on my creatures or my levels.

- To hit 60 fps on Gearvr/Go you will have to keep under the limitation of 100 drawcall and 100K poly. and believe me its really hard to keep 100 drawcall.

*in case you don't know, drawcall in simple term is the number of separate static mesh x the number of materials on that static mesh.

My gunfire alone had more than 10 drawcall so if I fire fast on the empty scene and I have 10 bullets on my scene that reach 100 drawcall LOL

2-Get a phone with snapdragon and not Mali GPU, for some reason, UE4 profile or oculus profile doesn't support Mali GPU and probably Unity too, even official Mali GPU profile didn't help me to show a simple utilization percentage. so I was looking for my fps problem like a blind man, Oculus Go has a snapdragon but I think its harder to profile as you will have to put it on your head every time you want to check unlike galaxy phone which let you run your game on flat screen without Oculus GearVR once you enable GearVR dev mode on your phone.

3-Oculus Go almost have the same spec as Galaxy S7. so if your able to run your game on Go then the only GearVR you will lose is S6 and note 4 maybe which is a small part of the market now, but if your game runs 60 fps on S7/Go then you can make it run 60 fps on S6 by simply lowering the res a bit.

4- Mobile phones is unlike PC you can set mobile CPU and GPU clock any time with simple commands (there is 4 level of CPU and GPU. 1 is the lowest clock and 4 is the highest clock) available with Oculus SDK. mobile CPU and GPU is really powerful but looks like you can't set it to "level 4" all the time and if you did then your phone will get the high temp in 10 min and reach the thermal limit and once it does that, Oculus will force the phone to run on low performance 30 fps and you will not able to pass oculus store performance test.

The best number works is CPU at "level 2" and "GPU at level 3" this is the default with UE4 and it runs fine on my galaxy s7 for 30 min without a problem and it passed the oculus performance test. so keep this on your mind.

---------------

Threads....

If you remember I mentioned before about game thread:

Snapdragon has 4 cores. exynos device (Mali GPU) has 8 cores but it doesn't matter since the phone only uses 4 of them at the time and the rest 4 during low power mode etc...


*$-Engine has access to 3 cores of Oculus Quest (4th one reserved for TimeWarp), as for Oculus Go it has access to 2 cores and I believe it's the same with GearVR. I don't know how below threads distribute on cores (since we have more threads than cores) so if you know please comment about it as I looked everywhere.

From what I experienced, the game based on UE4 uses 3 main CPU thread:

-CPU Game thread (your game logic)

-CPU Render thread (drawcall)

-CPU audio thread.

All of these thread runs separately so if one of them takes more time than 16 ms (60 fps) you will have a problem with your fps, at the same time if you try to optimize your game thread lower to 14 ms (by simplifying your game logic) but your render thread is 18 ms you will get final fps of 18 ms, in short words you will have to optimize each thread separately and once its lower than 16 ms then leave it and move on to the next one since optimizing one thread more than other won't benefit you.

The reason I detail explained this is because you will face drawcall problem like me 🙂 and fixing other issues won't fix that bottleneck.

*Try to learn CPU profiling provided on UE4 engine it's really easy, you will have to capture the data from your mobile with commands and bring the file back to your engine and open it and analyze the bottleneck. there is no other way, the good thing its simple and it will only take a few mins to learn. read and watch below 2 links.
 https://www.unrealengine.com/en-US/blog/how-to-improve-game-thread-cpu-performance?sessionInvalidate...
https://www.youtube.com/watch?v=GaRLNcdmU4U&t=10s

As for GPU optimization, you will have to test with "vr.PixelDensity" like "vr.PixelDensity 0.5" if you get some boost on fps then you are GPU bound. you will have to optimize your shaders. since my game was unlit and I deleted all the fog and fancy shaders and disabled all post process I didn't face much problem later. if you do then learn on how to optimize your GPU side. 

-----Now-------
-How to optimize drawcall (CPU Render thread)

So getting back to the main issue which is the limit of drawcall and poly. it wasn't hard for me regarding poly since my game is already using low poly meshes so I only shrank few of high poly inside the engine (one of the UE4 engine features to decrease poly).

As for drawcall, here is the thing will help you to lower it:

1- Merge all the actors to one big mesh.

2-Instanced Stereo Rendering/Multi-view for VR. this will cure your drawcall by 50%

3- Reduce the number of materials.

For me, since my game is standing in one place and all of my views is almost the same, I merged my level all to one mesh that had 10 materials (after decreasing my materials) so I got 10 drawcall for my level. before it was more than 300 mesh and each with ~4 material.

- How to optimize (CPU Game thread), I found out moving components is very toxic on mobile, so I tried to lower it as much as possible by removing all the extra collision boxes and spheres and merge some static mesh into one. my game was already optimized not much of events run peer tick/fps. since UE4 have a problem with GPU particle (its bug in the engine of 4.21 I tested I believe its not fixed yet, I already opened a ticket with Oculus team, hopfully they will fix it or its fixed on 4.22 which I haven't tested it yet) this really hearted my project so I had to convert all the particle effects into CPU type and optimize my particles by reducing its component and lowering its texture resolution. and lower its lifetime.

And since I merged all of my actor into one big mesh I didn't had the need for Occlusion Culling, so i disabled Occlusion Culling which was taking some of my fps. (~2 ms probably)

-How to optimize CPU audio thread wasn't that much hard, I tried to convert most of my SFX from original sample rate 44100hz to 22050hz and this reduced the size a lot and fixed the time needed to read this sfx from the slow memory of mobile that was causing spike during running this sfx.

-----

Regarding GPU optimization, I didn't have many problems and I was able to run my game with 124% Res since my game already using unlit materials with simple material instructions and full rough material. but I used ES2 and HDR Off (which disabled all of my post-processes) otherwise it was impossible to run empty level with HDR on gearvr.

Here is the links that helped me, some of them are unity and others are UE4 but all are the same principle:





* pdf and youtube talk with the name "How to scale down and not get caught"



These are few videos I still haven't checked them but going to drop it here so I get back to it later:








https://software.intel.com/en-us/articles/vr-developer-tutorial-testing-and-profiling-the-premium-vr...


As you can see, there is a lot of good resources found on Oculus sites, and I'm thankful for all the devs who shared their optimization tricks on Oculus forums and blogs, without them I wouldn't able to publish my game. 

I will past my DefaultEngine.ini setting regarding rendering setting on the 1st comment section.

*$ Source https://developer.oculus.com/blog/down-the-rabbit-hole-w-oculus-quest-the-hardware-software/
1 REPLY 1

Mohammed_hashim
Expert Protege
Here is my INI.engine setting, it will only work for the unlit version of your game, again only for "UNLIT game" so don't copy past it to your project like that, it will break your game if you are using baked light, copy and past only things you need to disable. and make sure you backup your project first not just .ini file. 
if you use bake light and you are too lazy to copy every single line from below then backup your project and copy past below to your .ini then go to your project setting and enable "AllowStaticLighting" or "OcclusionQueries" or "AtmosphericFog" etc..

Btw there is no "r.screenpercentage=x" since you should only depend on "vr.PixelDensity=1" or something greater from the options menu or based on device.

[SystemSettings]
r.TranslucentLightingVolume=0

[/Script/Engine.RendererSettings]
r.MobileHDR=False
r.Mobile.DisableVertexFog=True
r.Shadow.CSM.MaxMobileCascades=1
r.MobileMSAA=1
r.Mobile.UseLegacyShadingModel=True
r.Mobile.AllowDitheredLODTransition=False
r.Mobile.AllowSoftwareOcclusion=False
r.DiscardUnusedQuality=False
r.AllowOcclusionQueries=False
r.MinScreenRadiusForLights=0.030000
r.MinScreenRadiusForDepthPrepass=0.030000
r.MinScreenRadiusForCSMDepth=0.010000
r.PrecomputedVisibilityWarning=False
r.TextureStreaming=False
Compat.UseDXT5NormalMaps=False
r.ClearCoatNormal=False
r.ReflectionCaptureResolution=64
r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True
r.ForwardShading=True
r.VertexFoggingForOpaque=True
r.AllowStaticLighting=False
r.NormalMapsForStaticLighting=False
r.GenerateMeshDistanceFields=False
r.DistanceFieldBuild.EightBit=False
r.GenerateLandscapeGIData=False
r.DistanceFieldBuild.Compress=False
r.TessellationAdaptivePixelsPerTriangle=48.000000
r.SeparateTranslucency=False
r.TranslucentSortPolicy=0
TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000)
r.CustomDepth=0
r.CustomDepthTemporalAAJitter=False
r.PostProcessing.PropagateAlpha=0
r.DOF.Algorithm=False
r.DefaultFeature.Bloom=True
r.DefaultFeature.AmbientOcclusion=False
r.DefaultFeature.AmbientOcclusionStaticFraction=False
r.DefaultFeature.AutoExposure=False
r.DefaultFeature.AutoExposure.Method=0
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=False
r.UsePreExposure=False
r.DefaultFeature.MotionBlur=False
r.DefaultFeature.LensFlare=False
r.TemporalAA.Upsampling=False
r.DefaultFeature.AntiAliasing=0
r.DefaultFeature.LightUnits=1
r.DefaultBackBufferPixelFormat=4
r.Shadow.UnbuiltPreviewInGame=True
r.StencilForLODDither=False
r.EarlyZPass=0
r.EarlyZPassMovable=False
r.EarlyZPassOnlyMaterialMasking=False
r.DBuffer=False
r.ClearSceneMethod=1
r.BasePassOutputsVelocity=False
r.SelectiveBasePassOutputs=False
bDefaultParticleCutouts=False
fx.GPUSimulationTextureSizeX=1024
fx.GPUSimulationTextureSizeY=1024
r.AllowGlobalClipPlane=False
r.GBufferFormat=1
r.MorphTarget.Mode=True
r.GPUCrashDebugging=False
vr.InstancedStereo=False
vr.MultiView=True
vr.MobileMultiView=True
vr.MobileMultiView.Direct=True
vr.MonoscopicFarField=False
vr.RoundRobinOcclusion=True
vr.ODSCapture=False
r.WireframeCullThreshold=5.000000
r.SupportStationarySkylight=False
r.SupportLowQualityLightmaps=False
r.SupportPointLightWholeSceneShadows=False
r.SupportAtmosphericFog=False
r.SkinCache.CompileShaders=False
r.Mobile.EnableStaticAndCSMShadowReceivers=False
r.Mobile.EnableMovableLightCSMShaderCulling=False
r.Mobile.AllowDistanceFieldShadows=False
r.Mobile.AllowMovableDirectionalLights=False
r.MobileNumDynamicPointLights=0
r.MobileDynamicPointLightsUseStaticBranch=False
r.SkinCache.SceneMemoryLimitInMB=128.000000
r.GPUSkin.Limit2BoneInfluences=False
r.SupportDepthOnlyIndexBuffers=False
r.SupportReversedIndexBuffers=False
r.SupportMaterialLayers=False