Mobile UI Best Practices / Optimizations

More questions about general best practices for developing performant UI on mobile platform (rather than a specific issue):

  1. Is there an ideal way to structure dynamic widgets or data-driven content so updates don’t trigger a full reflow of a scrollable or nested layout?
  2. Are there specific widget types or layout containers that are particularly expensive for mobile?
  3. What are recommendations for using/implementing world space UI?
  4. What’s the recommended approach for handling responsive layout changes (e.g. resizing or orientation shifts) to minimize expensive invalidations?

Hi,

I believe you’ve already seen it, but I’ll share the Mobile UI Performance Tips talk here for posterity. On to your questions:

Is there an ideal way to structure dynamic widgets or data-driven content so updates don’t trigger a full reflow of a scrollable or nested layout?

Try to use ListView-based widgets wherever you can (ListView, TileView, TreeView) since they’re virtualized and will automatically manage a pool of widgets. A nice benefit there is that you could modify the underlying data that is being displayed without needing to regenerate the widget if the data being changed isn’t currently visible. Beyond that, there’s a bit of a tradeoff based on how you hide widgets that are no longer visible:

  • Destroy/Recreate - creation cost can be high, but no overhead once destroyed/garbage collected
  • Set Visibility to Collapsed - no layout cost or update cost, but still exists in memory. Often the best choice for things that you hide/show frequently
  • Set Visibility to Hidden - continues to incur layout cost, but no update cost. Can be better to avoid recalculating other parts of your layout. For example, a panel that you push/pop widgets into (like a container for toast notification) might benefit from being set to Hidden when empty so that it’s not causing other layout changes as things are added and removed (assuming it’s statically sized based on other elements in the hierarchy)

Are there specific widget types or layout containers that are particularly expensive for mobile?

Mentioned in the talk, but overlays and canvas panels can both cause many draw calls. This can be fixed for canvas panels by enabling “Explicit Canvas Child ZOrder”, but overlays will always assume one draw call per child, so use those only when you need things to layer properly. Background Blur can also be quite expensive (and has mobile fallback options for that reason), so avoid that if you can.

What are recommendations for using/implementing world space UI?

The typical approach is to use widget components, which will render the widget to an intermediary render target and apply that texture to a plane. Make sure the content widget is as small as possible (set the designer to desired size to make sure there isn’t unneeded transparent space), and use Draw at Desired Size on the component to make sure it clamps down to just the space it needs. Widget components should now be compatible with global invalidation, but double check that things aren’t invalidating unnecessarily. For static widgets, you can enable Manually Redraw so that there’s no overhead at all when it’s not changing (we’ve already “cached” the widget to the render target, so we can just keep drawing that as-is).

What’s the recommended approach for handling responsive layout changes (e.g. resizing or orientation shifts) to minimize expensive invalidations?

Resizing the window (on a desktop platform) or switching orientation (on mobile) will always end up triggering a full invalidation, there’s probably no way around that but it’s typically fine since those are already disruptive operations. For more contained layout changes, the trick to limiting the scope of an invalidation is ensuring desired size doesn’t change. This can get a bit strange with a full-screen canvas, as moving things further down/right than any existing widget will technically increase it’s desired size (but we don’t care, since we’re already stretching it to the full viewport), and could cause the other children of that canvas to update. It’s probably best to build things first and then point Slate Insights at it to see where those big invalidations are happening, but we’ve hacked around this in the past by making a container widget return 0,0 in ComputeDesiredSize regardless of it’s children. A similar workaround is possible using a SizeBox that overrides those values, which can be useful for at least testing things out before committing to making custom containers that make some assumptions about how they’ll be used in your layout.

Best,

Cody

thanks Cody! followup question from one of our UI Tech Artists… in your talk you mention squeezing extra draw call savings by using texture atlases. Is there a reason to use texture atlases vs texture arrays or does it come down to personal preference?

Hi,

I haven’t done any testing with texture arrays, but it sounds like the main limitation there would be that the contents would be uniformly sized (vs. an atlas which could pack different sized textures). Texture atlas support does lean on Paper2D and it’s tooling, but I suspect it would be a better workflow for UI textures since we don’t typically generate mipmaps for those.