* This is a *very* light-touch analytics system that will track the
simplest and most anonymous statistics that can be useful in
determining which features are most used or perhaps underused, and
where it's best to direct development attention.
* It is entirely implemented in the UI layer, no analytics-gathering
code exists in the library that's injected into programs, and of
course no capture data (screenshots, resource contents, shaders, etc
etc) is transmitted.
* Once it's turned on, it will apply to both development and release
builds. It tracks stats over a month, and then at the beginning of a
new month it sends the previous data.
* When the user first starts up a build with analytics if there's no
previous analytics database then they are informed of the new code and
asked to approve it. They have the option of selecting to manually
verify any sent reports, or just opt-ing out entirely.
* When we need to cache a texture or buffer locally then before this
commit we'd transfer the whole resource, and re-transfer it every time
the event changed even if the contents haven't changed.
* Instead now we track the previous contents on both sides, and just
send a list of deltas to apply.
* On android reading pixel-by-pixel out of the mapped memory is
*incredibly* slow - taking up to 1.5s for a fullscreen depth texture.
It's faster to copy to a temporary array, and this shouldn't have a
huge effect on other platforms.
* Previously each read and write would 1:1 become a send or recv call.
* Now we buffer writes and send in batch (or when a chunk finishes),
and every time we read we try to non-blocking read more data to fill
the buffer, to allow batching reads where possible without blocking
on data that will never come.
* This makes API-agnostic processing over strucutred data a *little*
easier. At least it's possible to differentiate initialisation from
frame capture chunks.
* For a few primary cases, we check to see if the API call failed (or is
looking like it will fail, in the case of vulkan) and bail out. This
will cause the capture to fail to load.
* In each function, we check that serialisation succeeded before going
on to actually call into the API. If something went wrong, then we
bail out and fail to load the capture.