native: perf telemetry, 2 frames in flight, 120 Hz pacing, GPU shadow map
- --perf-log CSV + logcat telemetry (perf_log.h, EterBase/PerfCounters.h), incl. display refresh rate / app vsync rate, thermal, CPU clocks; tools/pull_perf_logs.sh + tools/perf_summary.py - Vulkan renderer: two frames in flight, in-frame texture uploads (no vkQueueWaitIdle), --fps-cap / --refresh-rate / --frames-in-flight - Android: ANativeWindow_setFrameRate, ADPF performance hint, appCategory=game (ColorOS no longer drops to 60 Hz when idle) - Character shadow map rendered on the GPU into an offscreen render target instead of CPU rasterization + per-frame 1 MB upload; --cpu-shadow keeps the old path. Device (120 Hz, 32 mobs): 75 -> ~115 fps, script thread 8.9 -> 3.4 ms. draw_capture format v10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3e3708ef06
commit
c2cc093ed0
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
// Android frame-rate and CPU-clock plumbing for the live client's render loop.
|
||||
//
|
||||
// - display_refresh_rate(): the panel's refresh rate now (MainActivity.currentRefreshRate), and
|
||||
// render_rate(): the app-vsync rate the system actually gives the app (MainActivity.currentRenderRate),
|
||||
// for the perf log's refresh_hz / render_hz columns.
|
||||
// - request_frame_rate(): ANativeWindow_setFrameRate (API 30), the surface's preferred rate. The display
|
||||
// mode itself is chosen by MainActivity (--refresh-rate -> preferredDisplayModeId); the vendor
|
||||
// (ColorOS etc.) can still cap an app it does not know.
|
||||
// - PerformanceHint: ADPF APerformanceHint (API 33). The loop reports each frame's CPU work against the
|
||||
// frame budget, so the governor raises the clocks before a frame misses instead of idling the CPU at
|
||||
// its lowest step between short bursts (the 556-748 MHz seen in perf-20260929-153205.csv).
|
||||
//
|
||||
// Every NDK entry point is looked up at run time: minSdk is 24. On other platforms all of this is a no-op.
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/native_window.h>
|
||||
#include <dlfcn.h>
|
||||
#include <jni.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace android_perf {
|
||||
|
||||
#ifdef __ANDROID__
|
||||
inline void* libandroid() {
|
||||
static void* lib = dlopen("libandroid.so", RTLD_NOW);
|
||||
return lib;
|
||||
}
|
||||
|
||||
inline double call_activity_float(const char* name) {
|
||||
auto* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
|
||||
auto activity = static_cast<jobject>(SDL_GetAndroidActivity());
|
||||
if (!env || !activity) return -1.0;
|
||||
double hz = -1.0;
|
||||
jclass cls = env->GetObjectClass(activity);
|
||||
if (jmethodID method = env->GetMethodID(cls, name, "()F"))
|
||||
hz = env->CallFloatMethod(activity, method);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
hz = -1.0;
|
||||
}
|
||||
env->DeleteLocalRef(cls);
|
||||
env->DeleteLocalRef(activity);
|
||||
return hz;
|
||||
}
|
||||
|
||||
inline double display_refresh_rate() { return call_activity_float("currentRefreshRate"); }
|
||||
// The app-vsync rate (Choreographer); -1 until the first measurement.
|
||||
inline double render_rate() { return call_activity_float("currentRenderRate"); }
|
||||
inline double battery_celsius() { return call_activity_float("batteryTemperature"); }
|
||||
|
||||
// false when the call is unavailable (API < 30) or rejected.
|
||||
inline bool request_frame_rate(SDL_Window* window, float fps) {
|
||||
using SetFrameRate = int32_t (*)(ANativeWindow*, float, int8_t);
|
||||
static const auto set_frame_rate =
|
||||
libandroid() ? reinterpret_cast<SetFrameRate>(dlsym(libandroid(), "ANativeWindow_setFrameRate")) : nullptr;
|
||||
if (!set_frame_rate || !window) return false;
|
||||
auto* native = static_cast<ANativeWindow*>(SDL_GetPointerProperty(
|
||||
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, nullptr));
|
||||
if (!native) return false;
|
||||
// ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT: a game, not fixed-rate video.
|
||||
return set_frame_rate(native, fps, 0) == 0;
|
||||
}
|
||||
|
||||
class PerformanceHint {
|
||||
public:
|
||||
// `thread_ids`: the threads that do each frame's work (render thread + script thread).
|
||||
bool open(const std::vector<int32_t>& thread_ids, std::int64_t target_ns) {
|
||||
void* lib = libandroid();
|
||||
if (!lib || thread_ids.empty()) return false;
|
||||
get_manager_ = reinterpret_cast<GetManager>(dlsym(lib, "APerformanceHint_getManager"));
|
||||
create_ = reinterpret_cast<CreateSession>(dlsym(lib, "APerformanceHint_createSession"));
|
||||
update_target_ = reinterpret_cast<UpdateTarget>(dlsym(lib, "APerformanceHint_updateTargetWorkDuration"));
|
||||
report_ = reinterpret_cast<Report>(dlsym(lib, "APerformanceHint_reportActualWorkDuration"));
|
||||
close_ = reinterpret_cast<Close>(dlsym(lib, "APerformanceHint_closeSession"));
|
||||
if (!get_manager_ || !create_ || !report_ || !close_) return false;
|
||||
void* manager = get_manager_();
|
||||
if (!manager) return false;
|
||||
session_ = create_(manager, thread_ids.data(), thread_ids.size(), target_ns);
|
||||
target_ns_ = target_ns;
|
||||
return session_ != nullptr;
|
||||
}
|
||||
|
||||
~PerformanceHint() {
|
||||
if (session_ && close_) close_(session_);
|
||||
}
|
||||
|
||||
bool active() const { return session_ != nullptr; }
|
||||
|
||||
void set_target(std::int64_t target_ns) {
|
||||
if (!session_ || !update_target_ || target_ns == target_ns_ || target_ns <= 0) return;
|
||||
update_target_(session_, target_ns);
|
||||
target_ns_ = target_ns;
|
||||
}
|
||||
|
||||
void report(std::int64_t actual_ns) {
|
||||
if (session_ && actual_ns > 0) report_(session_, actual_ns);
|
||||
}
|
||||
|
||||
private:
|
||||
using GetManager = void* (*)();
|
||||
using CreateSession = void* (*)(void*, const int32_t*, size_t, int64_t);
|
||||
using UpdateTarget = int (*)(void*, int64_t);
|
||||
using Report = int (*)(void*, int64_t);
|
||||
using Close = void (*)(void*);
|
||||
GetManager get_manager_ = nullptr;
|
||||
CreateSession create_ = nullptr;
|
||||
UpdateTarget update_target_ = nullptr;
|
||||
Report report_ = nullptr;
|
||||
Close close_ = nullptr;
|
||||
void* session_ = nullptr;
|
||||
std::int64_t target_ns_ = 0;
|
||||
};
|
||||
|
||||
inline int32_t current_thread_id() { return int32_t(gettid()); }
|
||||
#else
|
||||
inline double display_refresh_rate() {
|
||||
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay());
|
||||
return mode ? double(mode->refresh_rate) : -1.0;
|
||||
}
|
||||
inline double render_rate() { return display_refresh_rate(); }
|
||||
inline double battery_celsius() { return -1.0; }
|
||||
inline bool request_frame_rate(SDL_Window*, float) { return false; }
|
||||
class PerformanceHint {
|
||||
public:
|
||||
bool open(const std::vector<int32_t>&, std::int64_t) { return false; }
|
||||
bool active() const { return false; }
|
||||
void set_target(std::int64_t) {}
|
||||
void report(std::int64_t) {}
|
||||
};
|
||||
inline int32_t current_thread_id() { return 0; }
|
||||
#endif
|
||||
|
||||
} // namespace android_perf
|
||||
Reference in New Issue
Block a user