FORTNITE : BATTLE ROYALE
ON MOBILE
Jack Porter
ONE GAME, ALL PLATFORMS
Jack Porter
ONE GAME, ALL PLATFORMS
We set out to support cross-play across all platforms, therefore: ● The same map is used on all platforms
● Anything that affects gameplay must be supported
● The engagement distance is the same across platforms
Our approach is to use scalability to run the same content on all platforms. One benefit of this approach is that the optimizations we make to run on mobile help low-end PC and 60fps console.
CONTROLS, HUD, etc.
This talk is focused on optimization and so we don’t cover it here but a lot of work went into making FNBR a fun experience on mobile
● Movement, opening doors, etc. ● Aiming and shooting
● Looting, inventory
ONE GAME, ALL PLATFORMS
ONE GAME, ALL PLATFORMS
ONE GAME, ALL PLATFORMS
RENDERING FEATURES - PC + CONSOLE
Movable Directional Light Cascaded Shadow Maps Ray-traced Distance Field Shadows
Movable Skylight Distance Field Ambient Occlusion Screen Space Ambient Occlusion
Local Lights Point + Spot Shadows Shadow Caching Materials Physically Based Subsurface Scattering Two-sided foliage Effects Volumetric Fog Light Shafts GPU Particle Simulation
Foliage Animation Postprocessing Bloom Object Outlines ACES Tonemapper Anti-aliasing Temporal AA MSAA
RENDERING FEATURES - MOBILE
Movable Directional Light Cascaded Shadow Maps Ray-traced Distance Field Shadows
Movable Skylight
Distance Field Ambient Occlusion Screen Space Ambient Occlusion
Local Lights
Point + Spot Shadows Shadow Caching
Materials
Physically Based (w/ approx) Subsurface Scattering
Two-sided foliage
FX
Volumetric Fog Light Shafts GPU Particle Simulation
Foliage Animation Postprocessing Bloom Object Outlines ACESTonemapper Anti-aliasing Temporal AA MSAA*
SCALABILITY
In order to both get FNBR running at 60fps on consoles and playable on
mobile devices we approached the problem as one of scalability.
We need as many knobs as possible that we can tune to hit our framerate and memory budgets across a variety of devices.
SCALABILITY GROUPS
Intended for settings that can change at runtime
Groups that can be changed independently ● View Distance ● Texture ● Shadows ● Foliage ● Effects ● Anti-aliasing
Groups can be overridden per-platform Each group sets console variables
Built into the engine (grass.DensityScale) or defined by the game (fort.Scalability.AIBudget)
DEVICE PROFILES
Platform specific Device Profile Selector picks the profile based on hardware Each profile defined in config files, sets console variables
EXAMPLE: iOS
MEMORY SCALABILITY
How to handle memory when setting scalability settings Memory doesn't always correlate with performance
Can define different settings based on memory, platform defines buckets (smallest, smaller, default, etc.)
WHERE WE STARTED
Of course, the game was built for PC and console so this was a major effort! We started by seeing how it would run on a 4GB device… and ran out of memory.
MEMORY: TARGETS
On iOS we allow ourselves 700MB of memory per GB of physical memory ● <1.4 GB for 2 GB devices (most phones)
● <2.1 GB for 3 GB devices (“+” phones)
● <2.8 GB for 4 GB devices (the big iPadPros)
On Android the memory situation is more complicated, depends on device ● <500 MB for 2GB devices (we don’t fit on these right now)
● <1.4 GB for 3 GB devices (seems ok on most) Also, hard to get accurate memory numbers from Android...
Streaming
● Split POIs into streaming levels
Engine
● 2-byte strings (were 4-bytes!)
Textures
● Used ASTC optimizing for size
● Removed / reduced non-streaming textures ● Adjusted streaming pool
● Content optimization
Meshes
● Per-platform min LOD, cooked out unused LODs
● Cooked out meshes culled by low detail mode ● Don’t load grass / foliage on low memory devices ● Content optimization
Materials
● Cook out medium and high quality variants ● Disable feature permutations we don’t use, e.g.
dynamic point lights
● Reduce unique materials in content ● Removed editor-only properties
Audio
● Per-platform variation culling ● Per-platform downsampling ● Per-platform compression quality ● Quality node
● Content optimization
Fortnite
● Separate Save the World content (WIP) ● Stream cosmetics instead of preloading (WIP)
MEMORY OPTIMIZATION - COLLISION
Per-poly complex collision for static meshes
● Disable Enable Collision on sections where possible ● Use LOD For Collision
667 collision polys 2669 collision polys
AUDIO: PER-PLATFORM QUALITY
Cook-time downsampling rather than reducing quality of source asset
Compression quality scaling per asset and platform
FRAMERATE TARGETS
30fps at the highest visual fidelity possible
However…
Maxing out the CPU and GPU generates a lot of heat which causes the device to downclock. (plus, it’s not comfortable to play a game on a phone when it’s incredibly hot!)
TRACKING ENVIRONMENT PERF
Added ProfileGo, can be
scripted to hop around the map and run commands
Track environment
performance at a selection of
POIs
Mostly interested in rendering here
Not entirely automated (yet)
TRACKING ENVIRONMENT PERF
Identifying perf regressions Validating optimizations Able to run experiments in isolation
PROFILING
QA captured profiles and stats during daily playtests
PROFILING: HITCHES
DUAL CORE iOS DEVICES
On most iOS devices, we have two cores to work with*
Traditional game / render threads
One task thread for async tasks, mostly streaming
*iPhone7 has 2 “fast”, 2 “efficient” cores, but you only get two active at a time This profile is from an iPhone 6S
4+ CORE iOS DEVICES
On iPhone 8 and X we have 2 “fast” and 4 “efficient” cores
We add a 3 more task threads for: ● Parallel animation evaluation
● Particle simulation ● Physics tasks
● Async scene queries ● Texture streaming
● Scene culling for rendering And a dedicated audio thread
ANDROID DEVICES
On Android, we are counting on having 4 cores, 2 “big” and 2 “little”
We add an RHI Thread for submitting OpenGL commands
This is a big win for us as we’re heavily bound by the OpenGL driver.
(note that Android is very much WIP)
CPU: RENDERING
Scene traversal scales with total number of meshes in the world
● Culling, relevance, etc.
● Level streaming greatly reduced the total size of the scene
Draw calls, draw calls, draw calls
● This is our main bottleneck
● More than the DrawIndexedPrimitive call itself, also state setup ● On console we see up to 3500 draw calls per frame, PC over 4500 ● On mobile we can’t afford nearly that much
DRAW CALLS: iOS vs Android
After optimizations, this is where we are now
CityOverlook is a stress test and on the high end
CityStreets is on par with our average scenes
On Android, this remains our biggest performance challenge as you can see
CityOverlook CityStreets
Draw Calls CPU Time (ms) Draw Calls CPU Time (ms)
iPhone 8+ 1281 4.7 605 2.73
iPhone 7+ 1246 7.11 544 4.22
Galaxy S8 (Adreno) 1207 28.75 597 19.21
Galaxy S8 (Mali) 1088 38.3 479 19.39
Shadows are disabled, so these stats are primarily for rendering the main scene, occlusion queries.
On iOS, CPU Time accounts for both engine time setting up the draw call and time spent in the Metal driver.
On Android, CPU Time is primarily time spent in the OpenGL driver. Engine work happens in a parallel thread.
DRAW CALLS
Aggressive distance culling, cull as soon as you won’t notice / a player can’t hide behind it
Cut a lot of decorative objects that don’t impact gameplay Use occlusion culling to only draw what can be seen
OCCLUSION CULLING
We use the hardware occlusion system that we use on all other platforms Required some work to be optimal on mobile:
● Add a frame of latency, occlusion happens near the end of the frame ● Add an additional frame of latency when RHIT is running (Android)
● Virtualize queries due to driver bugs on some Android devices that limit how many can be in flight
HIERARCHICAL LOD
Long view distances allow you to see POIs from very far away
Use UE4 HLOD tools and Simplygon to auto-generate proxy meshes for POIs
Great for this case, but we can’t render HLOD any closer because we can’t change
HIERARCHICAL LOD
HIERARCHICAL LOD
DESTRUCTIBLE HLOD
Leverage our HLOD tools to add another LOD to the chain, a “destructible HLOD”
Merge the lowest LOD of all meshes
together, create a texture atlas, and render in one draw call
Object ID stored in vertex color, used to hide objects that have been destroyed
HIERARCHICAL LOD
HIERARCHICAL LOD
HIERARCHICAL LOD
GPU OPTIMIZATION
Easiest to tune with scalability settings
Resolution, shadows, foliage, material quality
Anisotropic filtering: 1x, 2x, 4x Terrain tessellation
RESOLUTION
Backbuffer resolution is what the UI renders at (contentScaleFactor on iOS)
3D resolution is separate, upscaled before rendering UI (this adds some GPU cost) Preferred scaling backbuffer, only scaled 3D separately on iPhone 6S Tuned per-device on iOS
FOLIAGE
Grass and foliage scales, both density and cull distance
None on low-memory devices
MATERIAL QUALITY
Low, medium, and high quality levels Only Low enabled for mobile
Allowed artists to simplify or remove vertex animation shaders: wind, wobble, etc.
Also reduce pixel cost on some materials: fewer textures, simpler blending, etc.
SHADOWS
Dynamic shadows have a large impact on both GPU and CPU perf
Scalability for shadow map resolution, # cascades, shadow distance, object size threshold
Low - no shadows
Mid - 1 cascade, 1024x1024, ~size of a building High - 2 cascades, 1024x1024, 25% further distance than mid
MOBILE IMPROVEMENTS IN 4.20
Minimum static mesh LOD per-platform Minimum skeletal mesh LOD per-platform Hardware occlusion improvements
RHI thread for OpenGL on Android HLOD tools and workflow optimization Audio quality node
Audio variation culling
Audio per-platform downsampling
Audio per-platform compression quality Shading model tweaks to better match PC Reflection capture brightness fix
Landscape support for four layers Landscape tessellation improvements
2-byte strings on iOS and Android No memory cost for unused LODs:
Static meshes Skeletal meshes
Material quality levels Grass and foliage
High detail components and meshes High detail emitters in Cascade
Settings based on device memory Material memory reduction
Editor scriptability for bulk asset changes Particle component pooling
● Thank you ● Questions?