In Chrome, the rendered content is handled as tiles, rectangular blocks of web contents and browser UI.
When a tile is no longer used, by such as the window is hidden or the tab is moved, we can discard the tiles so that we can free memory. For most cases, users only see several tabs and windows at the same time. On the other hand, we may still want to keep tiles even they are not visible right now, for example when the tab is switched but the user comes back to the original one immediately. If we discard invisible frames all the time, the user will suffer from the long re-rendering time every time the user switches the tab.
To resolve this tradeoff smartly, we have a mechanism to keep resources for approptiate time and scenarios.
## FrameEvictionManager / FrameEvictionClient
When viz receives the frame, viz keeps the frame until the new frame is submitted from renderer. When new frame is submitted, viz can finally release the previous frame as it will be overriden by the new frame.
However, as explained in the last section, it would harm the memory performance if we hold onto all resources for each renderer.
Therefore, we use LRU cache-like system to discard the old frames.
[FrameEvictionManager](https://source.chromium.org/chromium/chromium/src/+/main:components/viz/client/frame_eviction_manager.h) is responsible for globaly managing which renderers keep their compositor frame when offscreen.
[FrameEvictionManagerClient](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=30;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) will evict the frame when [FrameEvictionManager](https://source.chromium.org/chromium/chromium/src/+/main:components/viz/client/frame_eviction_manager.h) decides to by [FrameEvictor::EvictCurrentFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_evictor.cc;l=68;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e). Its inner implementation is defined by, for example, [DelegatedFrameHost](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:content/browser/renderer_host/delegated_frame_host.cc;l=380;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e).
## Lock
We handles whether we can discard the frame or not by "lock".
When a frame becomes visible/invisible, it calles [FrameEvictor::SetVisible](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_evictor.cc;l=30;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e). If it goes into visible, we [LockFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=61;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e), and [UnlockFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=73;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) otherwise.
[LockFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=61;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) will add a frame to [`locked_frames_`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=113;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) and [UnlockFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=73;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) will first [RemoveFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=54;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) from [`locked_frames_`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=113;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) list, [RegisterUnlockedFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=86;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) and [CullOldUnlockedFrames](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=164;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e).
[`locked_frames_`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=113;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) is a map of [FrameEvictionManagerClient](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=30;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e), which represents a frame, to the number of locks.
[`unlocked_frames_`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=116-117;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) is a list of the pair of [FrameEvictionManagerClient](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=30;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) and time ticks which represents when the frames was unlocked.
## How we discard frames
There are 3 ways to be discarded:
1. When new frame is added and it exceeds the maximum number of resources to hold
2. When a frame has been unlocked for a while
3. When memory is pressured and have to free up memory
### 1. Exceeds the limit
As described in the previous section, on [UnlockFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=73;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e), we call [CullOldUnlockedFrames](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=164;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) to discard the frames which exceeds the limit of [GetMaxNumberOfSavedFrames](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=101;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e).
### 2. Idle for a while
We call [RegisterUnlockedFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=86;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) when a new frame is registered as unlocked. It sets timer called [`idle_frames_culling_timer_`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=126;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) which will trigger [CullOldUnlockedFrames](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=164;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) in [5 minutes](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.h;l=81;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) and then discard the frames which had been not locked for [more than 5 minutes](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=175;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e).
### 3. Memory Pressure
When memory pressure becomes higher, we discard more frames to free up memory occupied by previous resources.
[MemoryPressureListener](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/memory/memory_pressure_listener.h) listen the memory pressure level and notifies [FrameEvictionManager::OnMemotyPressure](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=193;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) on memory pressure level changed.
We have 3 levels:
- [`MEMORY_PRESSURE_LEVEL_NONE`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/memory/memory_pressure_listener.h;l=58;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e) which implies there is no problem
- [`MEMORY_PRESSURE_LEVEL_MODERATE`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/memory/memory_pressure_listener.h;l=62;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e) which advises modules to free buffers that are cheap to re-allocate and not immediately needed
- [`MEMORY_PRESSURE_LEVEL_CRITICAL`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/memory/memory_pressure_listener.h;l=67;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e) which advises modules to free up all possible memory.
As for [FrameEvictionManager](https://source.chromium.org/chromium/chromium/src/+/main:components/viz/client/frame_eviction_manager.h), it keeps [50%](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=25;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) of recently saved frames for moderate level and [10%](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=26;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) for critical and purges the rest of frames from [CullUnlockedFrame](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/viz/client/frame_eviction_manager.cc;l=147;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e)
## ScopedPause
Sometimes we really would like to keep frames without auto-discarding system.
In such case, we can use [ScopedPause](https://source.chromium.org/chromium/chromium/src/+/main:components/viz/client/frame_eviction_manager.h;l=46-54;drc=6a6f97ebb1773ffcc869c3854245c50069b457b6) to pause frame eviction within its scope.
There is only one usage right now besides tests, [DefaultWindowOcclusionChangeBuilder](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/aura/window_occlusion_change_builder.cc;l=28;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e).
The usage is like this: [Its destructor](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/aura/window_occlusion_change_builder.cc;l=26;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) calculates occlusion state and applies the changes to windows from [Window::SetOcclusionInfo](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/aura/window.cc;l=1011;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e). During this application, we don't want [FrameEvictionManager](https://source.chromium.org/chromium/chromium/src/+/main:components/viz/client/frame_eviction_manager.h) to refer to the intermedite occlusion state to decide frame eviction. Therefore, it creates [ScopedPause](https://source.chromium.org/chromium/chromium/src/+/main:components/viz/client/frame_eviction_manager.h;l=46-54;drc=6a6f97ebb1773ffcc869c3854245c50069b457b6) instance so that it can update and apply all occlusion state before [FrameEvictionManager](https://source.chromium.org/chromium/chromium/src/+/main:components/viz/client/frame_eviction_manager.h) starts discarding unneeded frames.
## Note
[WindowOcclusionTracker](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/aura/window_occlusion_tracker.cc;l=334;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) creates [DefaultWindowOcclusionChangeBuilder](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/aura/window_occlusion_change_builder.cc;l=28;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) instance to [MaybeComputeOcclusion](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/aura/window_occlusion_tracker.cc;l=269;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e).
[WindowOcclusionTracker](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/aura/window_occlusion_tracker.h;l=50;drc=b73134cfcce34a13f25202d077c0aa9dc03b662e) notifies tracked window when their occlusion state change.