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:
DynamicSwappy(const char * libraryName)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 }
~DynamicSwappy()56 ~DynamicSwappy() {
57 if(lib_) dlclose(lib_);
58 }
injectTracer(const SwappyTracer * tracer) const59 void injectTracer(const SwappyTracer* tracer) const {
60 if(inject_tracer_)
61 inject_tracer_(tracer);
62 }
valid() const63 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:
SwappyTuningFork(const CProtobufSerialization & settings_ser,JNIEnv * env,jobject activity,VoidCallback cbk,const char * libName)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 }
valid() const87 bool valid() const { return swappy_.valid(); }
88
89 // Swappy trace callbacks
swappyStartFrameCallback(void * userPtr,int,long)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 }
swappyPreWaitCallback(void * userPtr)96 static void swappyPreWaitCallback(void* userPtr) {
97 SwappyTuningFork* _this = (SwappyTuningFork*)userPtr;
98 _this->waitTraceHandle_ = TuningFork_startTrace(TFTICK_WAIT_TIME);
99 }
swappyPostWaitCallback(void * userPtr)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 }
swappyPreSwapBuffersCallback(void * userPtr)108 static void swappyPreSwapBuffersCallback(void* userPtr) {
109 SwappyTuningFork* _this = (SwappyTuningFork*)userPtr;
110 _this->swapTraceHandle_ = TuningFork_startTrace(TFTICK_SWAP_TIME);
111 }
swappyPostSwapBuffersCallback(void * userPtr,long)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
Init(const CProtobufSerialization * settings,JNIEnv * env,jobject activity,const char * libName,void (* frame_callback)())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.
GetSettingsSerialization(JNIEnv * env,jobject activity,CProtobufSerialization & settings_ser)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.
GetFidelityParamsSerialization(JNIEnv * env,jobject activity,CProtobufSerialization * fps_ser,int * fp_count)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.
GetSavedFileName(JNIEnv * env,jobject activity,std::string & name)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.
GetSavedFidelityParams(JNIEnv * env,jobject activity,CProtobufSerialization * params)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.
SaveFidelityParams(JNIEnv * env,jobject activity,const CProtobufSerialization * params)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.
SavedFidelityParamsFileExists(JNIEnv * env,jobject activity)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
StartFidelityParamDownloadThread(JNIEnv * env,jobject activity,const CProtobufSerialization & defaultParams,ProtoCallback fidelity_params_callback,int initialTimeoutMs,int ultimateTimeoutMs)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 ¶ms, waitTimeMs)) {
268 ALOGI("Got fidelity params from server");
269 SaveFidelityParams(newEnv, newActivity, ¶ms);
270 CProtobufSerialization_Free(&defaultParams);
271 fidelity_params_callback(¶ms);
272 CProtobufSerialization_Free(¶ms);
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
TuningFork_findSettingsInAPK(JNIEnv * env,jobject activity,CProtobufSerialization * settings_ser)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 }
TuningFork_findFidelityParamsInAPK(JNIEnv * env,jobject activity,CProtobufSerialization * fps,int * fp_count)306 void TuningFork_findFidelityParamsInAPK(JNIEnv* env, jobject activity,
307 CProtobufSerialization* fps, int* fp_count) {
308 GetFidelityParamsSerialization(env, activity, fps, fp_count);
309 }
310
TuningFork_initWithSwappy(const CProtobufSerialization * settings,JNIEnv * env,jobject activity,const char * libraryName,VoidCallback frame_callback)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
TuningFork_setUploadCallback(void (* cbk)(const CProtobufSerialization *))317 void TuningFork_setUploadCallback(void(*cbk)(const CProtobufSerialization*)) {
318 tuningfork::SetUploadCallback(cbk);
319 }
320
TuningFork_initFromAssetsWithSwappy(JNIEnv * env,jobject activity,const char * libraryName,VoidCallback frame_callback,int fpFileNum,ProtoCallback fidelity_params_callback,int initialTimeoutMs,int ultimateTimeoutMs)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