Home | History | Annotate | Download | only in tuningfork
      1 
      2 #include "tuningfork/tuningfork_extra.h"
      3 #include "tuningfork/protobuf_util.h"
      4 #include "tuningfork_internal.h"
      5 #include "tuningfork_utils.h"
      6 
      7 #include <cinttypes>
      8 #include <dlfcn.h>
      9 #include <memory>
     10 #include <vector>
     11 #include <cstdlib>
     12 #include <sstream>
     13 #include <thread>
     14 #include <fstream>
     15 #include <mutex>
     16 
     17 #define LOG_TAG "TuningFork"
     18 #include "Log.h"
     19 #include "swappy/swappy_extra.h"
     20 
     21 #include <android/asset_manager_jni.h>
     22 #include <jni.h>
     23 
     24 using namespace tuningfork;
     25 
     26 namespace {
     27 
     28 using PFN_Swappy_initTracer = void (*)(const SwappyTracer* tracer);
     29 
     30 constexpr TFInstrumentKey TFTICK_WAIT_TIME = 2;
     31 constexpr TFInstrumentKey TFTICK_SWAP_TIME = 3;
     32 
     33 class DynamicSwappy {
     34     typedef void* Handle;
     35     Handle lib_;
     36     PFN_Swappy_initTracer inject_tracer_;
     37 public:
     38     DynamicSwappy(const char* libraryName) {
     39         static char defaultLibNames[][20] = {"libgamesdk.so", "libswappy.so", "libunity.so"};
     40         std::vector<const char*> libNames = {
     41             libraryName, NULL, defaultLibNames[0], defaultLibNames[1], defaultLibNames[2]};
     42         for(auto libName: libNames) {
     43             lib_ = dlopen(libName, RTLD_NOW);
     44             if( lib_ ) {
     45                 inject_tracer_ = (PFN_Swappy_initTracer)dlsym(lib_, "Swappy_injectTracer");
     46                 if(inject_tracer_) {
     47                     return;
     48                 } else {
     49                     dlclose(lib_);
     50                 }
     51             }
     52         }
     53         ALOGW("Couldn't find Swappy_injectTracer");
     54         lib_ = nullptr;
     55     }
     56     ~DynamicSwappy() {
     57         if(lib_) dlclose(lib_);
     58     }
     59     void injectTracer(const SwappyTracer* tracer) const {
     60         if(inject_tracer_)
     61             inject_tracer_(tracer);
     62     }
     63     bool valid() const { return lib_ != nullptr; }
     64 };
     65 
     66 class SwappyTuningFork {
     67     DynamicSwappy swappy_;
     68     SwappyTracer trace_;
     69     VoidCallback frame_callback_;
     70     TFTraceHandle waitTraceHandle_ = 0;
     71     TFTraceHandle swapTraceHandle_ = 0;
     72 public:
     73     SwappyTuningFork(const CProtobufSerialization& settings_ser, JNIEnv* env, jobject activity,
     74                      VoidCallback cbk, const char* libName)
     75         : swappy_(libName), trace_({}), frame_callback_(cbk) {
     76         trace_.startFrame = swappyStartFrameCallback;
     77         trace_.preWait =  swappyPreWaitCallback;
     78         trace_.postWait = swappyPostWaitCallback;
     79         trace_.preSwapBuffers = swappyPreSwapBuffersCallback;
     80         trace_.postSwapBuffers = swappyPostSwapBuffersCallback;
     81         trace_.userData = this;
     82         if(swappy_.valid()) {
     83             TuningFork_init(&settings_ser, env, activity);
     84             swappy_.injectTracer(&trace_);
     85         }
     86     }
     87     bool valid() const { return swappy_.valid(); }
     88 
     89     // Swappy trace callbacks
     90     static void swappyStartFrameCallback(void* userPtr, int /*currentFrame*/,
     91                                          long /*currentFrameTimeStampMs*/) {
     92         SwappyTuningFork* _this = (SwappyTuningFork*)userPtr;
     93         _this->frame_callback_();
     94         TuningFork_frameTick(TFTICK_SYSCPU);
     95     }
     96     static void swappyPreWaitCallback(void* userPtr) {
     97         SwappyTuningFork* _this = (SwappyTuningFork*)userPtr;
     98         _this->waitTraceHandle_ = TuningFork_startTrace(TFTICK_WAIT_TIME);
     99     }
    100     static void swappyPostWaitCallback(void* userPtr) {
    101         SwappyTuningFork *_this = (SwappyTuningFork *) userPtr;
    102         if (_this->waitTraceHandle_) {
    103             TuningFork_endTrace(_this->waitTraceHandle_);
    104             _this->waitTraceHandle_ = 0;
    105         }
    106         TuningFork_frameTick(TFTICK_SYSGPU);
    107     }
    108     static void swappyPreSwapBuffersCallback(void* userPtr) {
    109         SwappyTuningFork* _this = (SwappyTuningFork*)userPtr;
    110         _this->swapTraceHandle_ = TuningFork_startTrace(TFTICK_SWAP_TIME);
    111     }
    112     static void swappyPostSwapBuffersCallback(void* userPtr, long /*desiredPresentationTimeMs*/) {
    113         SwappyTuningFork *_this = (SwappyTuningFork *) userPtr;
    114         if (_this->swapTraceHandle_) {
    115             TuningFork_endTrace(_this->swapTraceHandle_);
    116             _this->swapTraceHandle_ = 0;
    117         }
    118     }
    119     // Static methods
    120     static std::unique_ptr<SwappyTuningFork> s_instance_;
    121 
    122     static bool Init(const CProtobufSerialization* settings, JNIEnv* env,
    123                      jobject activity, const char* libName, void (*frame_callback)()) {
    124         s_instance_ = std::unique_ptr<SwappyTuningFork>(
    125             new SwappyTuningFork(*settings, env, activity, frame_callback, libName));
    126         return s_instance_->valid();
    127     }
    128 };
    129 
    130 std::unique_ptr<SwappyTuningFork> SwappyTuningFork::s_instance_;
    131 
    132 // Gets the serialized settings from the APK.
    133 // Returns false if there was an error.
    134 bool GetSettingsSerialization(JNIEnv* env, jobject activity,
    135                                         CProtobufSerialization& settings_ser) {
    136     auto asset = apk_utils::GetAsset(env, activity, "tuningfork/tuningfork_settings.bin");
    137     if (asset == nullptr )
    138         return false;
    139     ALOGI("Got settings from tuningfork/tuningfork_settings.bin");
    140     // Get serialized settings from assets
    141     uint64_t size = AAsset_getLength64(asset);
    142     settings_ser.bytes = (uint8_t*)::malloc(size);
    143     memcpy(settings_ser.bytes, AAsset_getBuffer(asset), size);
    144     settings_ser.size = size;
    145     settings_ser.dealloc = ::free;
    146     AAsset_close(asset);
    147     return true;
    148 }
    149 
    150 // Gets the serialized fidelity params from the APK.
    151 // Call this function once with fps_ser=NULL to get the count of files present,
    152 // then allocate an array of CProtobufSerializations and pass this as fps_ser
    153 // to a second call.
    154 void GetFidelityParamsSerialization(JNIEnv* env, jobject activity,
    155                                             CProtobufSerialization* fps_ser,
    156                                             int* fp_count) {
    157     std::vector<AAsset*> fps;
    158     for( int i=1; i<16; ++i ) {
    159         std::stringstream name;
    160         name << "tuningfork/dev_tuningfork_fidelityparams_" << i << ".bin";
    161         auto fp = apk_utils::GetAsset(env, activity, name.str().c_str());
    162         if ( fp == nullptr ) break;
    163         fps.push_back(fp);
    164     }
    165     *fp_count = fps.size();
    166     if( fps_ser==nullptr )
    167         return;
    168     for(int i=0; i<*fp_count; ++i) {
    169         // Get serialized FidelityParams from assets
    170         AAsset* asset = fps[i];
    171         CProtobufSerialization& fp_ser = fps_ser[i];
    172         uint64_t size = AAsset_getLength64(asset);
    173         fp_ser.bytes = (uint8_t*)::malloc(size);
    174         memcpy(fp_ser.bytes, AAsset_getBuffer(asset), size);
    175         fp_ser.size = size;
    176         fp_ser.dealloc = ::free;
    177         AAsset_close(asset);
    178     }
    179 }
    180 
    181 // Get the name of the tuning fork save file. Returns true if the directory
    182 //  for the file exists and false on error.
    183 bool GetSavedFileName(JNIEnv* env, jobject activity, std::string& name) {
    184 
    185     // Create tuningfork/version folder if it doesn't exist
    186     std::stringstream tf_path_str;
    187     tf_path_str << file_utils::GetAppCacheDir(env, activity) << "/tuningfork";
    188     if (!file_utils::CheckAndCreateDir(tf_path_str.str())) {
    189         return false;
    190     }
    191     tf_path_str << "/V" << apk_utils::GetVersionCode(env, activity);
    192     if (!file_utils::CheckAndCreateDir(tf_path_str.str())) {
    193         return false;
    194     }
    195     tf_path_str << "/saved_fp.bin";
    196     name = tf_path_str.str();
    197     return true;
    198 }
    199 
    200 // Get a previously save fidelity param serialization.
    201 bool GetSavedFidelityParams(JNIEnv* env, jobject activity, CProtobufSerialization* params) {
    202     std::string save_filename;
    203     if (GetSavedFileName(env, activity, save_filename)) {
    204         std::ifstream save_file(save_filename, std::ios::binary);
    205         if (save_file.good()) {
    206             save_file.seekg(0, std::ios::end);
    207             params->size = save_file.tellg();
    208             params->bytes = (uint8_t*)::malloc(params->size);
    209             params->dealloc = ::free;
    210             save_file.seekg(0, std::ios::beg);
    211             save_file.read((char*)params->bytes, params->size);
    212             ALOGI("Loaded fps from %s (%zu bytes)", save_filename.c_str(), params->size);
    213             return true;
    214         }
    215         ALOGI("Couldn't load fps from %s", save_filename.c_str());
    216     }
    217     return false;
    218 }
    219 
    220 // Save fidelity params to the save file.
    221 bool SaveFidelityParams(JNIEnv* env, jobject activity, const CProtobufSerialization* params) {
    222     std::string save_filename;
    223     if (GetSavedFileName(env, activity, save_filename)) {
    224         std::ofstream save_file(save_filename, std::ios::binary);
    225         if (save_file.good()) {
    226             save_file.write((const char*)params->bytes, params->size);
    227             ALOGI("Saved fps to %s (%zu bytes)", save_filename.c_str(), params->size);
    228             return true;
    229         }
    230         ALOGI("Couldn't save fps to %s", save_filename.c_str());
    231     }
    232     return false;
    233 }
    234 
    235 // Check if we have saved fidelity params.
    236 bool SavedFidelityParamsFileExists(JNIEnv* env, jobject activity) {
    237     std::string save_filename;
    238     if (GetSavedFileName(env, activity, save_filename)) {
    239         return file_utils::FileExists(save_filename);
    240     }
    241     return false;
    242 }
    243 
    244 // Download FPs on a separate thread
    245 void StartFidelityParamDownloadThread(JNIEnv* env, jobject activity,
    246                                       const CProtobufSerialization& defaultParams,
    247                                       ProtoCallback fidelity_params_callback,
    248                                       int initialTimeoutMs, int ultimateTimeoutMs) {
    249     static std::mutex threadMutex;
    250     std::lock_guard<std::mutex> lock(threadMutex);
    251     static std::thread fpThread;
    252     if (fpThread.joinable()) {
    253         ALOGW("Fidelity param download thread already started");
    254         return;
    255     }
    256     JavaVM *vm;
    257     env->GetJavaVM(&vm);
    258     auto newActivity = env->NewGlobalRef(activity);
    259     fpThread = std::thread([=](CProtobufSerialization defaultParams) {
    260         CProtobufSerialization params = {};
    261         int waitTimeMs = initialTimeoutMs;
    262         bool first_time = true;
    263         JNIEnv *newEnv;
    264         if (vm->AttachCurrentThread(&newEnv, NULL) == 0) {
    265             while (true) {
    266                 if (TuningFork_getFidelityParameters(&defaultParams,
    267                                                      &params, waitTimeMs)) {
    268                     ALOGI("Got fidelity params from server");
    269                     SaveFidelityParams(newEnv, newActivity, &params);
    270                     CProtobufSerialization_Free(&defaultParams);
    271                     fidelity_params_callback(&params);
    272                     CProtobufSerialization_Free(&params);
    273                     break;
    274                 } else {
    275                     ALOGI("Could not get fidelity params from server");
    276                     if (first_time) {
    277                         fidelity_params_callback(&defaultParams);
    278                         first_time = false;
    279                     }
    280                     if (waitTimeMs > ultimateTimeoutMs) {
    281                         ALOGW("Not waiting any longer for fidelity params");
    282                         CProtobufSerialization_Free(&defaultParams);
    283                         break;
    284                     }
    285                     waitTimeMs *= 2; // back off
    286                 }
    287             }
    288             newEnv->DeleteGlobalRef(newActivity);
    289             vm->DetachCurrentThread();
    290         }
    291     }, defaultParams);
    292 }
    293 
    294 } // anonymous namespace
    295 
    296 extern "C" {
    297 
    298 bool TuningFork_findSettingsInAPK(JNIEnv* env, jobject activity,
    299                                   CProtobufSerialization* settings_ser) {
    300     if(settings_ser) {
    301         return GetSettingsSerialization(env, activity, *settings_ser);
    302     } else {
    303         return false;
    304     }
    305 }
    306 void TuningFork_findFidelityParamsInAPK(JNIEnv* env, jobject activity,
    307                                         CProtobufSerialization* fps, int* fp_count) {
    308     GetFidelityParamsSerialization(env, activity, fps, fp_count);
    309 }
    310 
    311 bool TuningFork_initWithSwappy(const CProtobufSerialization* settings, JNIEnv* env,
    312                                jobject activity, const char* libraryName,
    313                                VoidCallback frame_callback) {
    314     return SwappyTuningFork::Init(settings, env, activity, libraryName, frame_callback);
    315 }
    316 
    317 void TuningFork_setUploadCallback(void(*cbk)(const CProtobufSerialization*)) {
    318     tuningfork::SetUploadCallback(cbk);
    319 }
    320 
    321 TFErrorCode TuningFork_initFromAssetsWithSwappy(JNIEnv* env, jobject activity,
    322                                                 const char* libraryName,
    323                                                 VoidCallback frame_callback,
    324                                                 int fpFileNum,
    325                                                 ProtoCallback fidelity_params_callback,
    326                                                 int initialTimeoutMs, int ultimateTimeoutMs) {
    327     CProtobufSerialization ser;
    328     if (!TuningFork_findSettingsInAPK(env, activity, &ser))
    329         return TFERROR_NO_SETTINGS;
    330     if (!TuningFork_initWithSwappy(&ser, env, activity, libraryName, frame_callback))
    331         return TFERROR_NO_SWAPPY;
    332     CProtobufSerialization defaultParams = {};
    333     // Special meaning for negative fpFileNum: don't load saved params, overwrite them instead
    334     bool resetSavedFPs = fpFileNum<0;
    335     fpFileNum = abs(fpFileNum);
    336     // Use the saved params as default, if they exist
    337     if (!resetSavedFPs && SavedFidelityParamsFileExists(env, activity)) {
    338         GetSavedFidelityParams(env, activity, &defaultParams);
    339     } else {
    340         int nfps=0;
    341         TuningFork_findFidelityParamsInAPK(env, activity, NULL, &nfps);
    342         if (nfps>0) {
    343             std::vector<CProtobufSerialization> fps(nfps);
    344             TuningFork_findFidelityParamsInAPK(env, activity, fps.data(), &nfps);
    345             int chosen = fpFileNum - 1; // File indices start at 1
    346             for (int i=0;i<nfps;++i) {
    347                 if (i==chosen) {
    348                     defaultParams = fps[i];
    349                 } else {
    350                     CProtobufSerialization_Free(&fps[i]);
    351                 }
    352             }
    353             if (chosen>=0 && chosen<nfps) {
    354                 ALOGI("Using params from dev_tuningfork_fidelityparams_%d.bin as default",
    355                     fpFileNum);
    356             } else {
    357                 return TFERROR_INVALID_DEFAULT_FIDELITY_PARAMS;
    358             }
    359         } else {
    360             return TFERROR_NO_FIDELITY_PARAMS;
    361         }
    362         // Save the default params
    363         SaveFidelityParams(env, activity, &defaultParams);
    364     }
    365     StartFidelityParamDownloadThread(env, activity, defaultParams, fidelity_params_callback,
    366         initialTimeoutMs, ultimateTimeoutMs);
    367     return TFERROR_OK;
    368 }
    369 
    370 } // extern "C"
    371