#include "tuningfork/tuningfork_extra.h" #include "tuningfork/protobuf_util.h" #include "tuningfork_internal.h" #include "tuningfork_utils.h" #include <cinttypes> #include <dlfcn.h> #include <memory> #include <vector> #include <cstdlib> #include <sstream> #include <thread> #include <fstream> #include <mutex> #define LOG_TAG "TuningFork" #include "Log.h" #include "swappy/swappy_extra.h" #include <android/asset_manager_jni.h> #include <jni.h> using namespace tuningfork; namespace { using PFN_Swappy_initTracer = void (*)(const SwappyTracer* tracer); constexpr TFInstrumentKey TFTICK_WAIT_TIME = 2; constexpr TFInstrumentKey TFTICK_SWAP_TIME = 3; class DynamicSwappy { typedef void* Handle; Handle lib_; PFN_Swappy_initTracer inject_tracer_; public: DynamicSwappy(const char* libraryName) { static char defaultLibNames[][20] = {"libgamesdk.so", "libswappy.so", "libunity.so"}; std::vector<const char*> libNames = { libraryName, NULL, defaultLibNames[0], defaultLibNames[1], defaultLibNames[2]}; for(auto libName: libNames) { lib_ = dlopen(libName, RTLD_NOW); if( lib_ ) { inject_tracer_ = (PFN_Swappy_initTracer)dlsym(lib_, "Swappy_injectTracer"); if(inject_tracer_) { return; } else { dlclose(lib_); } } } ALOGW("Couldn't find Swappy_injectTracer"); lib_ = nullptr; } ~DynamicSwappy() { if(lib_) dlclose(lib_); } void injectTracer(const SwappyTracer* tracer) const { if(inject_tracer_) inject_tracer_(tracer); } bool valid() const { return lib_ != nullptr; } }; class SwappyTuningFork { DynamicSwappy swappy_; SwappyTracer trace_; VoidCallback frame_callback_; TFTraceHandle waitTraceHandle_ = 0; TFTraceHandle swapTraceHandle_ = 0; public: SwappyTuningFork(const CProtobufSerialization& settings_ser, JNIEnv* env, jobject activity, VoidCallback cbk, const char* libName) : swappy_(libName), trace_({}), frame_callback_(cbk) { trace_.startFrame = swappyStartFrameCallback; trace_.preWait = swappyPreWaitCallback; trace_.postWait = swappyPostWaitCallback; trace_.preSwapBuffers = swappyPreSwapBuffersCallback; trace_.postSwapBuffers = swappyPostSwapBuffersCallback; trace_.userData = this; if(swappy_.valid()) { TuningFork_init(&settings_ser, env, activity); swappy_.injectTracer(&trace_); } } bool valid() const { return swappy_.valid(); } // Swappy trace callbacks static void swappyStartFrameCallback(void* userPtr, int /*currentFrame*/, long /*currentFrameTimeStampMs*/) { SwappyTuningFork* _this = (SwappyTuningFork*)userPtr; _this->frame_callback_(); TuningFork_frameTick(TFTICK_SYSCPU); } static void swappyPreWaitCallback(void* userPtr) { SwappyTuningFork* _this = (SwappyTuningFork*)userPtr; _this->waitTraceHandle_ = TuningFork_startTrace(TFTICK_WAIT_TIME); } static void swappyPostWaitCallback(void* userPtr) { SwappyTuningFork *_this = (SwappyTuningFork *) userPtr; if (_this->waitTraceHandle_) { TuningFork_endTrace(_this->waitTraceHandle_); _this->waitTraceHandle_ = 0; } TuningFork_frameTick(TFTICK_SYSGPU); } static void swappyPreSwapBuffersCallback(void* userPtr) { SwappyTuningFork* _this = (SwappyTuningFork*)userPtr; _this->swapTraceHandle_ = TuningFork_startTrace(TFTICK_SWAP_TIME); } static void swappyPostSwapBuffersCallback(void* userPtr, long /*desiredPresentationTimeMs*/) { SwappyTuningFork *_this = (SwappyTuningFork *) userPtr; if (_this->swapTraceHandle_) { TuningFork_endTrace(_this->swapTraceHandle_); _this->swapTraceHandle_ = 0; } } // Static methods static std::unique_ptr<SwappyTuningFork> s_instance_; static bool Init(const CProtobufSerialization* settings, JNIEnv* env, jobject activity, const char* libName, void (*frame_callback)()) { s_instance_ = std::unique_ptr<SwappyTuningFork>( new SwappyTuningFork(*settings, env, activity, frame_callback, libName)); return s_instance_->valid(); } }; std::unique_ptr<SwappyTuningFork> SwappyTuningFork::s_instance_; // Gets the serialized settings from the APK. // Returns false if there was an error. bool GetSettingsSerialization(JNIEnv* env, jobject activity, CProtobufSerialization& settings_ser) { auto asset = apk_utils::GetAsset(env, activity, "tuningfork/tuningfork_settings.bin"); if (asset == nullptr ) return false; ALOGI("Got settings from tuningfork/tuningfork_settings.bin"); // Get serialized settings from assets uint64_t size = AAsset_getLength64(asset); settings_ser.bytes = (uint8_t*)::malloc(size); memcpy(settings_ser.bytes, AAsset_getBuffer(asset), size); settings_ser.size = size; settings_ser.dealloc = ::free; AAsset_close(asset); return true; } // Gets the serialized fidelity params from the APK. // Call this function once with fps_ser=NULL to get the count of files present, // then allocate an array of CProtobufSerializations and pass this as fps_ser // to a second call. void GetFidelityParamsSerialization(JNIEnv* env, jobject activity, CProtobufSerialization* fps_ser, int* fp_count) { std::vector<AAsset*> fps; for( int i=1; i<16; ++i ) { std::stringstream name; name << "tuningfork/dev_tuningfork_fidelityparams_" << i << ".bin"; auto fp = apk_utils::GetAsset(env, activity, name.str().c_str()); if ( fp == nullptr ) break; fps.push_back(fp); } *fp_count = fps.size(); if( fps_ser==nullptr ) return; for(int i=0; i<*fp_count; ++i) { // Get serialized FidelityParams from assets AAsset* asset = fps[i]; CProtobufSerialization& fp_ser = fps_ser[i]; uint64_t size = AAsset_getLength64(asset); fp_ser.bytes = (uint8_t*)::malloc(size); memcpy(fp_ser.bytes, AAsset_getBuffer(asset), size); fp_ser.size = size; fp_ser.dealloc = ::free; AAsset_close(asset); } } // Get the name of the tuning fork save file. Returns true if the directory // for the file exists and false on error. bool GetSavedFileName(JNIEnv* env, jobject activity, std::string& name) { // Create tuningfork/version folder if it doesn't exist std::stringstream tf_path_str; tf_path_str << file_utils::GetAppCacheDir(env, activity) << "/tuningfork"; if (!file_utils::CheckAndCreateDir(tf_path_str.str())) { return false; } tf_path_str << "/V" << apk_utils::GetVersionCode(env, activity); if (!file_utils::CheckAndCreateDir(tf_path_str.str())) { return false; } tf_path_str << "/saved_fp.bin"; name = tf_path_str.str(); return true; } // Get a previously save fidelity param serialization. bool GetSavedFidelityParams(JNIEnv* env, jobject activity, CProtobufSerialization* params) { std::string save_filename; if (GetSavedFileName(env, activity, save_filename)) { std::ifstream save_file(save_filename, std::ios::binary); if (save_file.good()) { save_file.seekg(0, std::ios::end); params->size = save_file.tellg(); params->bytes = (uint8_t*)::malloc(params->size); params->dealloc = ::free; save_file.seekg(0, std::ios::beg); save_file.read((char*)params->bytes, params->size); ALOGI("Loaded fps from %s (%zu bytes)", save_filename.c_str(), params->size); return true; } ALOGI("Couldn't load fps from %s", save_filename.c_str()); } return false; } // Save fidelity params to the save file. bool SaveFidelityParams(JNIEnv* env, jobject activity, const CProtobufSerialization* params) { std::string save_filename; if (GetSavedFileName(env, activity, save_filename)) { std::ofstream save_file(save_filename, std::ios::binary); if (save_file.good()) { save_file.write((const char*)params->bytes, params->size); ALOGI("Saved fps to %s (%zu bytes)", save_filename.c_str(), params->size); return true; } ALOGI("Couldn't save fps to %s", save_filename.c_str()); } return false; } // Check if we have saved fidelity params. bool SavedFidelityParamsFileExists(JNIEnv* env, jobject activity) { std::string save_filename; if (GetSavedFileName(env, activity, save_filename)) { return file_utils::FileExists(save_filename); } return false; } // Download FPs on a separate thread void StartFidelityParamDownloadThread(JNIEnv* env, jobject activity, const CProtobufSerialization& defaultParams, ProtoCallback fidelity_params_callback, int initialTimeoutMs, int ultimateTimeoutMs) { static std::mutex threadMutex; std::lock_guard<std::mutex> lock(threadMutex); static std::thread fpThread; if (fpThread.joinable()) { ALOGW("Fidelity param download thread already started"); return; } JavaVM *vm; env->GetJavaVM(&vm); auto newActivity = env->NewGlobalRef(activity); fpThread = std::thread([=](CProtobufSerialization defaultParams) { CProtobufSerialization params = {}; int waitTimeMs = initialTimeoutMs; bool first_time = true; JNIEnv *newEnv; if (vm->AttachCurrentThread(&newEnv, NULL) == 0) { while (true) { if (TuningFork_getFidelityParameters(&defaultParams, ¶ms, waitTimeMs)) { ALOGI("Got fidelity params from server"); SaveFidelityParams(newEnv, newActivity, ¶ms); CProtobufSerialization_Free(&defaultParams); fidelity_params_callback(¶ms); CProtobufSerialization_Free(¶ms); break; } else { ALOGI("Could not get fidelity params from server"); if (first_time) { fidelity_params_callback(&defaultParams); first_time = false; } if (waitTimeMs > ultimateTimeoutMs) { ALOGW("Not waiting any longer for fidelity params"); CProtobufSerialization_Free(&defaultParams); break; } waitTimeMs *= 2; // back off } } newEnv->DeleteGlobalRef(newActivity); vm->DetachCurrentThread(); } }, defaultParams); } } // anonymous namespace extern "C" { bool TuningFork_findSettingsInAPK(JNIEnv* env, jobject activity, CProtobufSerialization* settings_ser) { if(settings_ser) { return GetSettingsSerialization(env, activity, *settings_ser); } else { return false; } } void TuningFork_findFidelityParamsInAPK(JNIEnv* env, jobject activity, CProtobufSerialization* fps, int* fp_count) { GetFidelityParamsSerialization(env, activity, fps, fp_count); } bool TuningFork_initWithSwappy(const CProtobufSerialization* settings, JNIEnv* env, jobject activity, const char* libraryName, VoidCallback frame_callback) { return SwappyTuningFork::Init(settings, env, activity, libraryName, frame_callback); } void TuningFork_setUploadCallback(void(*cbk)(const CProtobufSerialization*)) { tuningfork::SetUploadCallback(cbk); } TFErrorCode TuningFork_initFromAssetsWithSwappy(JNIEnv* env, jobject activity, const char* libraryName, VoidCallback frame_callback, int fpFileNum, ProtoCallback fidelity_params_callback, int initialTimeoutMs, int ultimateTimeoutMs) { CProtobufSerialization ser; if (!TuningFork_findSettingsInAPK(env, activity, &ser)) return TFERROR_NO_SETTINGS; if (!TuningFork_initWithSwappy(&ser, env, activity, libraryName, frame_callback)) return TFERROR_NO_SWAPPY; CProtobufSerialization defaultParams = {}; // Special meaning for negative fpFileNum: don't load saved params, overwrite them instead bool resetSavedFPs = fpFileNum<0; fpFileNum = abs(fpFileNum); // Use the saved params as default, if they exist if (!resetSavedFPs && SavedFidelityParamsFileExists(env, activity)) { GetSavedFidelityParams(env, activity, &defaultParams); } else { int nfps=0; TuningFork_findFidelityParamsInAPK(env, activity, NULL, &nfps); if (nfps>0) { std::vector<CProtobufSerialization> fps(nfps); TuningFork_findFidelityParamsInAPK(env, activity, fps.data(), &nfps); int chosen = fpFileNum - 1; // File indices start at 1 for (int i=0;i<nfps;++i) { if (i==chosen) { defaultParams = fps[i]; } else { CProtobufSerialization_Free(&fps[i]); } } if (chosen>=0 && chosen<nfps) { ALOGI("Using params from dev_tuningfork_fidelityparams_%d.bin as default", fpFileNum); } else { return TFERROR_INVALID_DEFAULT_FIDELITY_PARAMS; } } else { return TFERROR_NO_FIDELITY_PARAMS; } // Save the default params SaveFidelityParams(env, activity, &defaultParams); } StartFidelityParamDownloadThread(env, activity, defaultParams, fidelity_params_callback, initialTimeoutMs, ultimateTimeoutMs); return TFERROR_OK; } } // extern "C"