Google is committed to advancing racial equity for Black communities. See how.

Add frame timing functions

Use the functions in this topic to integrate the Tuning Fork library into your game code.

The header file at include/tuningfork/tuningfork.h contains the core interface for the Tuning Fork library. The file at include/tuningfork/tuningfork_extra.h contains utility functions.

Several functions take serializations of protocol buffers. For more on using protocol buffers within your game, see About protocol buffers.

Function parameters and return values are explained in the headers and reference API documentation.

Android Performance Tuner lifecycle functions

Use the following functions to control the lifecycle of a Tuning Fork instance.

Initialize

TFErrorCode TuningFork_init(const TFSettings* settings, JNIEnv* env, jobject context);

You must call this function once at start-up, typically from within native code executed by the app’s onCreate() method. It allocates the data needed by the Tuning Fork library.

You must have a tuningfork_settings.bin file present in assets/tuningfork within your app, which contains the histogram and annotation settings. To convert the text file to binary, see Text versus binary representations.

The fields you fill in settings determine how the library initializes itself.

/**
 * @brief Initialization settings
 *   Zero any values that are not being used.
 */
struct TFSettings {
  /**
   * Cache object to be used for upload data persistence.
   * If unset, data is persisted to /data/local/tmp/tuningfork
   */
  const TFCache* persistent_cache;
  /**
   * The address of the Swappy_injectTracers function.
   * If this is unset, you need to call TuningFork_tick explicitly.
   * If it is set, telemetry for 4 instrument keys is automatically recorded.
   */
  SwappyTracerFn swappy_tracer_fn;
  /**
   * Callback
   * If set, this is called with the fidelity parameters that are downloaded.
   * If unset, you need to call TuningFork_getFidelityParameters explicitly.
   */
  ProtoCallback fidelity_params_callback;
  /**
   * A serialized protobuf containing the fidelity parameters to be uploaded
   *  for training.
   * Set this to nullptr if you are not using training mode. Note that these
   *  are used instead of the default parameters loaded from the APK, if they
   *  are present and there are neither a successful download nor saved parameters.
   */
  const CProtobufSerialization* training_fidelity_params;
  /**
   * A null-terminated UTF-8 string containing the endpoint that Tuning Fork
   * will connect to for parameter, upload, and debug requests. This overrides
   * the value in base_uri in the settings proto and is intended for debugging
   * purposes only.
   */
  const char* endpoint_uri_override;
  /**
   * The version of Swappy that swappy_tracer_fn comes from.
   */
  uint32_t swappy_version;
  /**
   * The number of each metric that is allowed to be allocated at any given
   * time. If any element is zero, the default for that metric type is used.
   * Memory for all metrics is allocated up-front at initialization. When all
   * metrics of a given type are allocated, further requested metrics are not
   * added and data is lost.
   */
  TuningFork_MetricLimits max_num_metrics;
};

If you pass in the Swappy_injectTracer() function (OpenGL, Vulkan) from the Frame Pacing API at initialization, the Tuning Fork library automatically records frame time without you explicitly calling the tick functions yourself. This is done in the demo app:

void InitTf(JNIEnv* env, jobject activity) {
   SwappyGL_init(env, activity);
   swappy_enabled = SwappyGL_isEnabled();
   TFSettings settings {};
   if (swappy_enabled) {
       settings.swappy_tracer_fn = &SwappyGL_injectTracer;
   }
...
}

Destroy

TFErrorCode TuningFork_destroy();

You can call this function at shut-down. This function attempts to submit all currently stored-histogram data for later upload before deallocating any memory used by the Tuning Fork library.

Flush

TFErrorCode TuningFork_flush();

This function flushes the recorded histograms (for example, when the game is sent to the background or foreground). It doesn't flush data if the minimum upload period, which has a default of one minute, has not elapsed since the previous upload.

Set fidelity parameters

TFErrorCode TuningFork_setFidelityParameters(const CProtobufSerialization* params);

This function overrides the current fidelity parameters with which frame data is associated. You should call this function when a player manually changes the quality settings of the game.

Annotations

TFErrorCode TuningFork_setCurrentAnnotation(const CProtobufSerialization* annotation);

This function sets the annotation to associate with subsequent ticks. It returns TFERROR_INVALID_ANNOTATION if there was an error decoding the annotation and TFERROR_OK if there was no error.

Per-frame functions

TFErrorCode TuningFork_frameTick(TFInstrumentKey key);

This function records the time between the previous tick with the given key and the current time in the histogram associated with key and the current annotation.

TFErrorCode TuningFork_frameDeltaTimeNanos(TFInstrumentKey key, TFDuration dt);

This function records duration in the histogram associated with key and the current annotation.

TFErrorCode TuningFork_startTrace(TFInstrumentKey key, TraceHandle* handle);

This function sets a handle to a trace handle associated with the given key.

TFErrorCode TuningFork_endTrace(TraceHandle handle);

This function records the time interval since the associated TuningFork_startTrace() call in the histogram associated with the key that was used and the current annotation.

App lifecycle functions

typedef enum TuningFork_LifecycleState {
    TUNINGFORK_STATE_UNINITIALIZED = 0,
    TUNINGFORK_STATE_ONCREATE = 1,
    TUNINGFORK_STATE_ONSTART = 2,
    TUNINGFORK_STATE_ONSTOP = 3,
    TUNINGFORK_STATE_ONDESTROY = 4,
} TuningFork_LifecycleState;

TFErrorCode TuningFork_reportLifecycleEvent(TuningForkLifecycleState state);

Call this function from the appropriate lifecycle methods in your game's main Activity, passing the appropriate enum. By recording the game's lifecycle events, APT is better able to understand when your game may be crashing or when users may be quitting (for example, during long loading events).

Loading time functions

It is important to record when your game is performing loading for two reasons: 1. To avoid polluting your frame time data while loading. 1. To analyse load times to see when and where load times are longer than acceptable.

A loading event can have associated metadata:

typedef struct TuningFork_LoadingTimeMetadata {
    enum LoadingState {
        UNKNOWN_STATE = 0,
        // The first time the game is run
        FIRST_RUN = 1,
        // App is not backgrounded
        COLD_START = 2,
        // App is backgrounded
        WARM_START = 3,
        // App is backgrounded, least work needed
        HOT_START = 4,
        // Asset loading between levels
        INTER_LEVEL = 5
    } state;
    enum LoadingSource {
        UNKNOWN_SOURCE = 0,
        // Uncompressing data.
        MEMORY = 1,
        // Reading assets from APK bundle.
        APK = 2,
        // Reading assets from device storage.
        DEVICE_STORAGE = 3,
        // Reading assets from external storage, e.g. SD card.
        EXTERNAL_STORAGE = 4,
        // Loading assets from the network.
        NETWORK = 5,
        // Shader compilation.
        SHADER_COMPILATION = 6,
        // Time spent between process starting and onCreate.
        PRE_ACTIVITY = 7,
        // Total time spent between process starting and first render frame.
        FIRST_TOUCH_TO_FIRST_FRAME = 8
    } source;
    int32_t compression_level;  // 0 = no compression, 100 = max compression
    enum NetworkConnectivity {
        UNKNOWN = 0,
        WIFI = 1,
        CELLULAR_NETWORK = 2
    } network_connectivity;
    uint64_t network_transfer_speed_bps;  // bandwidth in bits per second
    uint64_t network_latency_ns;          // latency in nanoseconds
} TuningFork_LoadingTimeMetadata;

If any fields aren't relevant to your needs, set them to zero.

A loading event can also have an associated annotation which is defined in the same way as frame time annotations, using one or more fields in the Annotation message in the dev_tuningfork.proto file.

TuningFork_ErrorCode TuningFork_startRecordingLoadingTime( const TuningFork_LoadingTimeMetadata* eventMetadata, uint32_t eventMetadataSize, const TuningFork_CProtobufSerialization* annotation, TuningFork_LoadingEventHandle* handle);

This function starts recording a loading time event associated with the given metadata and annotation, and fills in a handle to be used in the TuningFork_stopRecordingLoadingTime() function.

TuningFork_ErrorCode TuningFork_stopRecordingLoadingTime( TuningFork_LoadingEventHandle handle);

This function stops recording an event previously started by TuningFork_startRecordingLoadingTime(). The event is uploaded at the next session flush.

TuningFork_ErrorCode TuningFork_recordLoadingTime( uint64_t time_ns, const TuningFork_LoadingTimeMetadata* eventMetadata, uint32_t eventMetadataSize, const TuningFork_CProtobufSerialization* annotation);

It is strongly encouraged that you directly use the start and stop functions above; but if you can't, you can call this function to record a duration and its associated metadata and annotation.

Advanced functions

The following functions are available in tuningfork_extra.h.

Find and load files in an APK

TFErrorCode TuningFork_findFidelityParamsInApk(JNIEnv* env, jobject context, const char* filename, CProtobufSerialization* fidelityParams);

This function loads fidelityParams from the assets/tuningfork directory in the APK with the given filename. fidelityParams must be a serialization of a FidelityParams message. For more information, see Define quality levels.

Ownership of the serialization is passed to the caller, which must call CProtobufSerialization_Free to deallocate any memory held.

Download fidelity parameters on a separate thread

void TuningFork_startFidelityParamDownloadThread(const CProtobufSerialization* defaultParams, ProtoCallback fidelity_params_callback);

Activates a download thread to retrieve fidelity params. The thread retries the request until the params download or a timeout occurs. Downloaded params are stored locally. When the app is restarted, it uses these downloaded params rather than the default params.

Save and delete fidelity parameters stored on device

TFErrorCode TuningFork_saveOrDeleteFidelityParamsFile(JNIEnv* env, jobject context, const CProtobufSerialization* fidelity_params);

This function is only needed in expert mode where fidelity parameters are downloaded from a server. It either saves over or deletes (if fidelity_params is null) the locally stored files that are used when the server is unreachable.

Web requests

The library makes the following kinds of requests to the server endpoint:

  • A generateTuningParameters request is made at initialization.
  • During gameplay, an uploadTelemetry request is periodically made to send data to the server.
  • Debug APKs can also send debugInfo requests, which inform a debug server of the settings, default fidelity parameters, and dev_tuningfork.proto structure.

Offline players

If there is no available connection at initialization, the request is retried several times with an increasing back-off time.

If there is no connection at upload, the upload is cached. You can provide your own caching mechanism by passing a TFCache object at initialization. If you do not provide your own cache, uploads are stored as files in temporary storage.